From 4ad8e7cbecc91e65c35443891c7446b246aeae0e Mon Sep 17 00:00:00 2001 From: km2023 Date: Mon, 29 Jun 2026 11:19:40 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=93?= =?UTF-8?q?=E5=BA=93,=E9=85=8D=E7=BD=AE=20Git=20LFS=20=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=A4=A7=E7=B4=A0=E6=9D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - git init (分支 main) + remote origin → gitea comi-logic/vtubermini - 根级 .gitignore 排除 node_modules/dist/android-build/releases(APK)/日志/.codex-logs - .gitattributes 用 Git LFS 跟踪 *.png/*.mp4 等媒体(141 个文件) Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitattributes | 22 + .gitignore | 35 + DEV-PLAN.md | 377 + Design-Brief.md | 209 + Development-Requirements-Breakdown.md | 320 + Product-Spec-CHANGELOG.md | 45 + Product-Spec.md | 553 + Requirement-Breakdown.mmd | 131 + UX-UE-Function-Flow.mmd | 92 + assets/1777526300869.png | 3 + assets/20260601-151641.png | 3 + assets/20260601-151658.mp4 | 3 + assets/20260601-151705.mp4 | 3 + assets/二级页面-纪念日页面-弹窗.png | 3 + assets/二级页面-纪念日页面-隔断.png | 3 + assets/二级页面-纪念日页面.png | 3 + assets/日常首页-夜晚.mp4 | 3 + assets/日常首页-白天.mp4 | 3 + design/Pencil-MVP-Prompt.md | 70 + design/exports/jHoSN.png | 3 + design/pencil-smoke-gpt55.pen | 215 + design/pencil-smoke-gpt55.png | 3 + design/vtubermini-mvp-v1-export.png | 3 + design/vtubermini-mvp-v1-preview.png | 3 + design/vtubermini-mvp-v1-usage.json | 6 + design/vtubermini-mvp-v1.pen | 14482 ++++++++++++++++ vtubermini/.gitignore | 24 + vtubermini/PRODUCT.md | 33 + vtubermini/README.md | 73 + vtubermini/android/.gitignore | 101 + vtubermini/android/app/.gitignore | 2 + vtubermini/android/app/build.gradle | 54 + vtubermini/android/app/capacitor.build.gradle | 19 + vtubermini/android/app/proguard-rules.pro | 21 + .../myapp/ExampleInstrumentedTest.java | 26 + .../android/app/src/main/AndroidManifest.xml | 41 + .../java/com/vtubermini/app/MainActivity.java | 54 + .../main/res/drawable-land-hdpi/splash.png | 3 + .../main/res/drawable-land-mdpi/splash.png | 3 + .../main/res/drawable-land-xhdpi/splash.png | 3 + .../main/res/drawable-land-xxhdpi/splash.png | 3 + .../main/res/drawable-land-xxxhdpi/splash.png | 3 + .../main/res/drawable-port-hdpi/splash.png | 3 + .../main/res/drawable-port-mdpi/splash.png | 3 + .../main/res/drawable-port-xhdpi/splash.png | 3 + .../main/res/drawable-port-xxhdpi/splash.png | 3 + .../main/res/drawable-port-xxxhdpi/splash.png | 3 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable/ic_launcher_background.xml | 170 + .../app/src/main/res/drawable/splash.png | 3 + .../app/src/main/res/layout/activity_main.xml | 12 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | 3 + .../mipmap-hdpi/ic_launcher_foreground.png | 3 + .../res/mipmap-hdpi/ic_launcher_round.png | 3 + .../src/main/res/mipmap-mdpi/ic_launcher.png | 3 + .../mipmap-mdpi/ic_launcher_foreground.png | 3 + .../res/mipmap-mdpi/ic_launcher_round.png | 3 + .../src/main/res/mipmap-xhdpi/ic_launcher.png | 3 + .../mipmap-xhdpi/ic_launcher_foreground.png | 3 + .../res/mipmap-xhdpi/ic_launcher_round.png | 3 + .../main/res/mipmap-xxhdpi/ic_launcher.png | 3 + .../mipmap-xxhdpi/ic_launcher_foreground.png | 3 + .../res/mipmap-xxhdpi/ic_launcher_round.png | 3 + .../main/res/mipmap-xxxhdpi/ic_launcher.png | 3 + .../mipmap-xxxhdpi/ic_launcher_foreground.png | 3 + .../res/mipmap-xxxhdpi/ic_launcher_round.png | 3 + .../res/values/ic_launcher_background.xml | 4 + .../app/src/main/res/values/strings.xml | 7 + .../app/src/main/res/values/styles.xml | 30 + .../app/src/main/res/xml/file_paths.xml | 5 + .../getcapacitor/myapp/ExampleUnitTest.java | 18 + vtubermini/android/build.gradle | 29 + vtubermini/android/capacitor.settings.gradle | 3 + vtubermini/android/gradle.properties | 22 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + vtubermini/android/gradlew | 251 + vtubermini/android/gradlew.bat | 94 + vtubermini/android/settings.gradle | 5 + vtubermini/android/variables.gradle | 16 + vtubermini/anniversary-page-check.png | 3 + vtubermini/anniversary-upload-clip-check.png | 3 + vtubermini/anniversary-upload-wide-test.svg | 1 + vtubermini/capacitor.config.ts | 9 + vtubermini/docs/adaptive-architecture.md | 143 + vtubermini/docs/android-build.md | 32 + vtubermini/docs/api-contract.md | 36 + vtubermini/docs/trusted-rules.md | 21 + vtubermini/eslint.config.js | 22 + vtubermini/index.html | 13 + vtubermini/package-lock.json | 3949 +++++ vtubermini/package.json | 39 + vtubermini/pnpm-lock.yaml | 2578 +++ vtubermini/public/favicon.svg | 1 + vtubermini/public/icons.svg | 24 + vtubermini/scripts/check-android-env.mjs | 30 + vtubermini/src/App.tsx | 472 + vtubermini/src/admin/admin-app.tsx | 34 + vtubermini/src/admin/admin-layout.tsx | 78 + .../src/admin/pages/admin-dashboard.tsx | 57 + .../admin/pages/anniversary-config-page.tsx | 32 + vtubermini/src/admin/pages/audit-log-page.tsx | 29 + vtubermini/src/admin/pages/card-pool-page.tsx | 69 + .../src/admin/pages/shop-orders-page.tsx | 72 + .../src/admin/pages/user-assets-page.tsx | 120 + .../assets/desktop-assets/1777526300869.png | 3 + .../assets/desktop-assets/1780380476301.png | 3 + .../assets/desktop-assets/20260601-151658.mp4 | 3 + .../assets/desktop-assets/20260601-151705.mp4 | 3 + .../desktop-assets/activity-image-1.png | 3 + .../desktop-assets/activity-image-2.png | 3 + .../assets/desktop-assets/currency-star.png | 3 + .../desktop-assets/enter-garden-button.png | 3 + .../assets/desktop-assets/gacha-result-bg.png | 3 + .../heart-archive/1780456110605.png | 3 + .../heart-archive/1780466208619.png | 3 + .../heart-archive/1780466393837.png | 3 + .../heart-archive/1780467961316.png | 3 + .../heart-archive/1780468924364.png | 3 + .../heart-archive/archive-card-1.png | 3 + .../heart-archive/archive-card-2.png | 3 + .../heart-archive/archive-card-3.png | 3 + .../heart-archive/archive-card-4.png | 3 + .../heart-archive/archive-card-5.png | 3 + .../archive-card-chenguang-weilan.png | 3 + .../archive-card-chiying-xingge.png | 3 + .../archive-card-huajian-mansu.png | 3 + .../heart-archive/archive-card-mist-notes.png | 3 + .../archive-card-rain-umbrella.png | 3 + .../archive-card-silent-contract.png | 3 + .../heart-archive/archive-card-star-lab.png | 3 + .../heart-archive/archive-card-storm-side.png | 3 + .../archive-card-xingyao-shengyu.png | 3 + .../archive-card-xingye-xiehou.png | 3 + .../heart-archive/archive-reference.png | 3 + .../heart-archive/detail-chenguang-weilan.mp4 | 3 + .../heart-archive/detail-chiying-xingge.mp4 | 3 + .../heart-archive/detail-huajian-mansu.mp4 | 3 + .../heart-archive/detail-xingyao-shengyu.mp4 | 3 + .../heart-archive/detail-xingye-xiehou.mp4 | 3 + .../heart-archive/detail-星夜邂逅.png | 3 + .../heart-archive/detail-星耀圣羽.png | 3 + .../heart-archive/detail-赤影行歌.png | 3 + .../heart-archive/heart-archive-bg.png | 3 + .../heart-archive/heart-archive-title.png | 3 + .../desktop-assets/heart-archive/rank-a.png | 3 + .../desktop-assets/heart-archive/rank-b.png | 3 + .../desktop-assets/heart-archive/rank-c.png | 3 + .../desktop-assets/heart-archive/rank-sr.png | 3 + .../desktop-assets/heart-archive/rank-ssr.png | 3 + .../assets/desktop-assets/heart-moment-bg.png | 3 + .../heart-moment-draw-single.mp4 | 3 + .../desktop-assets/heart-moment-draw-ten.mp4 | 3 + .../desktop-assets/heart-moment-loop.mp4 | 3 + .../heart-moment-result-intro.mp4 | 3 + .../heart-moment-result-loop.mp4 | 3 + .../heart-moment-collect-button.png | 3 + .../heart-moment-reveal/heart-reveal-1.png | 3 + .../heart-moment-reveal/heart-reveal-10.png | 3 + .../heart-moment-reveal/heart-reveal-2.png | 3 + .../heart-moment-reveal/heart-reveal-3.png | 3 + .../heart-moment-reveal/heart-reveal-4.png | 3 + .../heart-moment-reveal/heart-reveal-5.png | 3 + .../heart-moment-reveal/heart-reveal-6.png | 3 + .../heart-moment-reveal/heart-reveal-7.png | 3 + .../heart-moment-reveal/heart-reveal-8.png | 3 + .../heart-moment-reveal/heart-reveal-9.png | 3 + .../desktop-assets/heart-moment-ssr.png | 3 + .../home-anniversary/home-anniversary.png | 3 + .../home-covers/cover-chiying-xingge.mp4 | 3 + .../home-covers/cover-xingyao-shengyu.mp4 | 3 + .../home-covers/cover-xingye-xiehou.mp4 | 3 + .../desktop-assets/home-dialog/choice-box.png | 3 + .../desktop-assets/home-dialog/dialog-box.png | 3 + .../desktop-assets/home-icons/activity.png | 3 + .../desktop-assets/home-icons/album.png | 3 + .../desktop-assets/home-icons/anniversary.png | 3 + .../home-icons/avatar-frame.png | 3 + .../home-icons/bottom-icon-bar.png | 3 + .../desktop-assets/home-icons/moments.png | 3 + .../assets/desktop-assets/home-icons/pet.png | 3 + .../home-icons/profile-plate.png | 3 + .../assets/desktop-assets/home-icons/shop.png | 3 + .../desktop-assets/home-icons/support.png | 3 + .../assets/desktop-assets/home-icons/vote.png | 3 + .../desktop-assets/pet-feeding-juvenile.mp4 | 3 + .../desktop-assets/pet-idle-adult-loop.mp4 | 3 + .../desktop-assets/pet-idle-juvenile-loop.mp4 | 3 + .../desktop-assets/pet-juvenile-to-adult.mp4 | 3 + .../desktop-assets/shop-currency-gem.png | 3 + .../shop-products/canvas-bag.png | 3 + .../desktop-assets/shop-products/keychain.png | 3 + .../desktop-assets/shop-products/mug.png | 3 + .../desktop-assets/shop-products/notebook.png | 3 + .../shop-products/sticker-pack.png | 3 + .../assets/desktop-assets/shop-section-1.png | 3 + .../assets/desktop-assets/shop-section-2.png | 3 + .../assets/desktop-assets/shop-section-3.png | 3 + .../assets/desktop-assets/shop-section-4.png | 3 + .../二级页面-纪念日页面-弹窗.png | 3 + .../二级页面-纪念日页面-隔断.png | 3 + .../desktop-assets/二级页面-纪念日页面.png | 3 + .../assets/desktop-assets/抽卡界面-循环2.mp4 | 3 + .../desktop-assets/抽卡界面2-UI-ssr标.png | 3 + .../desktop-assets/抽卡界面2-UI-中间描述.png | 3 + .../desktop-assets/抽卡界面2-UI-单抽.png | 3 + .../desktop-assets/抽卡界面2-UI-连抽.png | 3 + .../assets/desktop-assets/日常首页-夜晚.mp4 | 3 + .../assets/desktop-assets/日常首页-白天.mp4 | 3 + vtubermini/src/assets/hero.png | 3 + vtubermini/src/assets/react.svg | 1 + vtubermini/src/assets/vite.svg | 1 + vtubermini/src/components/album/card-grid.tsx | 36 + .../anniversary/anniversary-edit-modal.tsx | 205 + .../anniversary/custom-anniversary-list.tsx | 62 + .../anniversary/join-date-panel.tsx | 34 + .../anniversary/reminder-preview.tsx | 31 + .../anniversary/system-anniversary-list.tsx | 28 + vtubermini/src/components/app-shell.tsx | 44 + .../src/components/common/currency-icon.tsx | 26 + .../src/components/common/page-header.tsx | 21 + .../src/components/common/state-block.tsx | 33 + .../src/components/gacha/gacha-actions.tsx | 42 + .../src/components/gacha/gacha-card-assets.ts | 85 + .../components/gacha/gacha-result-modal.tsx | 55 + .../components/gacha/gacha-result-page.tsx | 124 + .../gacha/heart-moment-card-reveal.tsx | 90 + .../gacha/heart-moment-draw-video.tsx | 99 + .../src/components/gacha/pool-summary.tsx | 51 + .../src/components/home/anniversary-card.tsx | 37 + .../components/home/announcement-panel.tsx | 56 + .../src/components/home/asset-summary.tsx | 38 + vtubermini/src/components/home/hero-scene.tsx | 51 + .../src/components/home/quick-actions.tsx | 64 + .../src/components/orders/order-card.tsx | 33 + vtubermini/src/components/pet/pet-actions.tsx | 44 + vtubermini/src/components/pet/pet-stage.tsx | 49 + vtubermini/src/components/pet/pet-stats.tsx | 45 + .../src/components/pet/points-ledger.tsx | 34 + .../profile/birthday-setup-modal.tsx | 91 + .../src/components/shop/exchange-form.tsx | 166 + .../src/components/shop/product-card.tsx | 24 + .../components/shop/shop-currency-icon.tsx | 5 + vtubermini/src/index.css | 699 + vtubermini/src/lib/admin-rules.ts | 47 + .../src/lib/anniversary-image-layout.ts | 36 + vtubermini/src/lib/anniversary-rules.ts | 179 + vtubermini/src/lib/asset-ledger.ts | 51 + vtubermini/src/lib/gacha-rules.ts | 271 + vtubermini/src/lib/home-layout.ts | 99 + vtubermini/src/lib/mock-data.ts | 242 + vtubermini/src/lib/pet-rules.ts | 270 + vtubermini/src/lib/platform.ts | 22 + vtubermini/src/lib/shop-rules.ts | 79 + vtubermini/src/lib/types.ts | 292 + vtubermini/src/main.tsx | 10 + vtubermini/src/pages/activity-page.tsx | 114 + vtubermini/src/pages/album-page.tsx | 572 + vtubermini/src/pages/anniversary-page.tsx | 272 + vtubermini/src/pages/diamond-ledger-page.tsx | 43 + vtubermini/src/pages/gacha-log-page.tsx | 48 + vtubermini/src/pages/gacha-page.tsx | 50 + vtubermini/src/pages/heart-moments-page.tsx | 213 + vtubermini/src/pages/home-page.tsx | 1094 ++ vtubermini/src/pages/login-page.tsx | 71 + vtubermini/src/pages/my-page.tsx | 409 + vtubermini/src/pages/notifications-page.tsx | 29 + vtubermini/src/pages/order-list-page.tsx | 30 + vtubermini/src/pages/pet-page.tsx | 161 + vtubermini/src/pages/points-ledger-page.tsx | 49 + vtubermini/src/pages/product-detail-page.tsx | 92 + vtubermini/src/pages/rules-page.tsx | 44 + vtubermini/src/pages/shop-page.tsx | 104 + vtubermini/src/pages/support-page.tsx | 54 + vtubermini/src/services/api-client.ts | 3 + vtubermini/src/services/contracts.ts | 31 + vtubermini/src/services/mock-server.ts | 62 + vtubermini/src/services/service-errors.ts | 23 + vtubermini/tsconfig.app.json | 25 + vtubermini/tsconfig.json | 7 + vtubermini/tsconfig.node.json | 24 + vtubermini/vite.config.ts | 338 + 284 files changed, 34085 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 DEV-PLAN.md create mode 100644 Design-Brief.md create mode 100644 Development-Requirements-Breakdown.md create mode 100644 Product-Spec-CHANGELOG.md create mode 100644 Product-Spec.md create mode 100644 Requirement-Breakdown.mmd create mode 100644 UX-UE-Function-Flow.mmd create mode 100644 assets/1777526300869.png create mode 100644 assets/20260601-151641.png create mode 100644 assets/20260601-151658.mp4 create mode 100644 assets/20260601-151705.mp4 create mode 100644 assets/二级页面-纪念日页面-弹窗.png create mode 100644 assets/二级页面-纪念日页面-隔断.png create mode 100644 assets/二级页面-纪念日页面.png create mode 100644 assets/日常首页-夜晚.mp4 create mode 100644 assets/日常首页-白天.mp4 create mode 100644 design/Pencil-MVP-Prompt.md create mode 100644 design/exports/jHoSN.png create mode 100644 design/pencil-smoke-gpt55.pen create mode 100644 design/pencil-smoke-gpt55.png create mode 100644 design/vtubermini-mvp-v1-export.png create mode 100644 design/vtubermini-mvp-v1-preview.png create mode 100644 design/vtubermini-mvp-v1-usage.json create mode 100644 design/vtubermini-mvp-v1.pen create mode 100644 vtubermini/.gitignore create mode 100644 vtubermini/PRODUCT.md create mode 100644 vtubermini/README.md create mode 100644 vtubermini/android/.gitignore create mode 100644 vtubermini/android/app/.gitignore create mode 100644 vtubermini/android/app/build.gradle create mode 100644 vtubermini/android/app/capacitor.build.gradle create mode 100644 vtubermini/android/app/proguard-rules.pro create mode 100644 vtubermini/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java create mode 100644 vtubermini/android/app/src/main/AndroidManifest.xml create mode 100644 vtubermini/android/app/src/main/java/com/vtubermini/app/MainActivity.java create mode 100644 vtubermini/android/app/src/main/res/drawable-land-hdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-land-mdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-land-xhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-land-xxhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-land-xxxhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-port-hdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-port-mdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-port-xhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-port-xxhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-port-xxxhdpi/splash.png create mode 100644 vtubermini/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 vtubermini/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 vtubermini/android/app/src/main/res/drawable/splash.png create mode 100644 vtubermini/android/app/src/main/res/layout/activity_main.xml create mode 100644 vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 vtubermini/android/app/src/main/res/values/ic_launcher_background.xml create mode 100644 vtubermini/android/app/src/main/res/values/strings.xml create mode 100644 vtubermini/android/app/src/main/res/values/styles.xml create mode 100644 vtubermini/android/app/src/main/res/xml/file_paths.xml create mode 100644 vtubermini/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java create mode 100644 vtubermini/android/build.gradle create mode 100644 vtubermini/android/capacitor.settings.gradle create mode 100644 vtubermini/android/gradle.properties create mode 100644 vtubermini/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 vtubermini/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 vtubermini/android/gradlew create mode 100755 vtubermini/android/gradlew.bat create mode 100644 vtubermini/android/settings.gradle create mode 100644 vtubermini/android/variables.gradle create mode 100644 vtubermini/anniversary-page-check.png create mode 100644 vtubermini/anniversary-upload-clip-check.png create mode 100644 vtubermini/anniversary-upload-wide-test.svg create mode 100644 vtubermini/capacitor.config.ts create mode 100644 vtubermini/docs/adaptive-architecture.md create mode 100644 vtubermini/docs/android-build.md create mode 100644 vtubermini/docs/api-contract.md create mode 100644 vtubermini/docs/trusted-rules.md create mode 100644 vtubermini/eslint.config.js create mode 100644 vtubermini/index.html create mode 100644 vtubermini/package-lock.json create mode 100644 vtubermini/package.json create mode 100644 vtubermini/pnpm-lock.yaml create mode 100644 vtubermini/public/favicon.svg create mode 100644 vtubermini/public/icons.svg create mode 100644 vtubermini/scripts/check-android-env.mjs create mode 100644 vtubermini/src/App.tsx create mode 100644 vtubermini/src/admin/admin-app.tsx create mode 100644 vtubermini/src/admin/admin-layout.tsx create mode 100644 vtubermini/src/admin/pages/admin-dashboard.tsx create mode 100644 vtubermini/src/admin/pages/anniversary-config-page.tsx create mode 100644 vtubermini/src/admin/pages/audit-log-page.tsx create mode 100644 vtubermini/src/admin/pages/card-pool-page.tsx create mode 100644 vtubermini/src/admin/pages/shop-orders-page.tsx create mode 100644 vtubermini/src/admin/pages/user-assets-page.tsx create mode 100644 vtubermini/src/assets/desktop-assets/1777526300869.png create mode 100644 vtubermini/src/assets/desktop-assets/1780380476301.png create mode 100644 vtubermini/src/assets/desktop-assets/20260601-151658.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/20260601-151705.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/activity-image-1.png create mode 100644 vtubermini/src/assets/desktop-assets/activity-image-2.png create mode 100644 vtubermini/src/assets/desktop-assets/currency-star.png create mode 100644 vtubermini/src/assets/desktop-assets/enter-garden-button.png create mode 100644 vtubermini/src/assets/desktop-assets/gacha-result-bg.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/1780456110605.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/1780466208619.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/1780466393837.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/1780467961316.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/1780468924364.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-1.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-2.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-3.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-4.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-5.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-mist-notes.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-silent-contract.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-star-lab.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-storm-side.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/archive-reference.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-chenguang-weilan.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-chiying-xingge.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-huajian-mansu.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-xingyao-shengyu.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-xingye-xiehou.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-星夜邂逅.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-星耀圣羽.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/detail-赤影行歌.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-bg.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-title.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/rank-a.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/rank-b.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/rank-c.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/rank-sr.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-archive/rank-ssr.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-bg.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-draw-single.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-draw-ten.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-loop.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-result-intro.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-result-loop.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-moment-collect-button.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-1.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-10.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-2.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-3.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-4.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-5.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-6.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-7.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-8.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-9.png create mode 100644 vtubermini/src/assets/desktop-assets/heart-moment-ssr.png create mode 100644 vtubermini/src/assets/desktop-assets/home-anniversary/home-anniversary.png create mode 100644 vtubermini/src/assets/desktop-assets/home-covers/cover-chiying-xingge.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/home-covers/cover-xingyao-shengyu.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/home-covers/cover-xingye-xiehou.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/home-dialog/choice-box.png create mode 100644 vtubermini/src/assets/desktop-assets/home-dialog/dialog-box.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/activity.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/album.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/anniversary.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/avatar-frame.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/bottom-icon-bar.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/moments.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/pet.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/profile-plate.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/shop.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/support.png create mode 100644 vtubermini/src/assets/desktop-assets/home-icons/vote.png create mode 100644 vtubermini/src/assets/desktop-assets/pet-feeding-juvenile.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/pet-idle-adult-loop.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/pet-idle-juvenile-loop.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/pet-juvenile-to-adult.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/shop-currency-gem.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-products/canvas-bag.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-products/keychain.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-products/mug.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-products/notebook.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-products/sticker-pack.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-section-1.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-section-2.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-section-3.png create mode 100644 vtubermini/src/assets/desktop-assets/shop-section-4.png create mode 100644 vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-弹窗.png create mode 100644 vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-隔断.png create mode 100644 vtubermini/src/assets/desktop-assets/二级页面-纪念日页面.png create mode 100644 vtubermini/src/assets/desktop-assets/抽卡界面-循环2.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/抽卡界面2-UI-ssr标.png create mode 100644 vtubermini/src/assets/desktop-assets/抽卡界面2-UI-中间描述.png create mode 100644 vtubermini/src/assets/desktop-assets/抽卡界面2-UI-单抽.png create mode 100644 vtubermini/src/assets/desktop-assets/抽卡界面2-UI-连抽.png create mode 100644 vtubermini/src/assets/desktop-assets/日常首页-夜晚.mp4 create mode 100644 vtubermini/src/assets/desktop-assets/日常首页-白天.mp4 create mode 100644 vtubermini/src/assets/hero.png create mode 100644 vtubermini/src/assets/react.svg create mode 100644 vtubermini/src/assets/vite.svg create mode 100644 vtubermini/src/components/album/card-grid.tsx create mode 100644 vtubermini/src/components/anniversary/anniversary-edit-modal.tsx create mode 100644 vtubermini/src/components/anniversary/custom-anniversary-list.tsx create mode 100644 vtubermini/src/components/anniversary/join-date-panel.tsx create mode 100644 vtubermini/src/components/anniversary/reminder-preview.tsx create mode 100644 vtubermini/src/components/anniversary/system-anniversary-list.tsx create mode 100644 vtubermini/src/components/app-shell.tsx create mode 100644 vtubermini/src/components/common/currency-icon.tsx create mode 100644 vtubermini/src/components/common/page-header.tsx create mode 100644 vtubermini/src/components/common/state-block.tsx create mode 100644 vtubermini/src/components/gacha/gacha-actions.tsx create mode 100644 vtubermini/src/components/gacha/gacha-card-assets.ts create mode 100644 vtubermini/src/components/gacha/gacha-result-modal.tsx create mode 100644 vtubermini/src/components/gacha/gacha-result-page.tsx create mode 100644 vtubermini/src/components/gacha/heart-moment-card-reveal.tsx create mode 100644 vtubermini/src/components/gacha/heart-moment-draw-video.tsx create mode 100644 vtubermini/src/components/gacha/pool-summary.tsx create mode 100644 vtubermini/src/components/home/anniversary-card.tsx create mode 100644 vtubermini/src/components/home/announcement-panel.tsx create mode 100644 vtubermini/src/components/home/asset-summary.tsx create mode 100644 vtubermini/src/components/home/hero-scene.tsx create mode 100644 vtubermini/src/components/home/quick-actions.tsx create mode 100644 vtubermini/src/components/orders/order-card.tsx create mode 100644 vtubermini/src/components/pet/pet-actions.tsx create mode 100644 vtubermini/src/components/pet/pet-stage.tsx create mode 100644 vtubermini/src/components/pet/pet-stats.tsx create mode 100644 vtubermini/src/components/pet/points-ledger.tsx create mode 100644 vtubermini/src/components/profile/birthday-setup-modal.tsx create mode 100644 vtubermini/src/components/shop/exchange-form.tsx create mode 100644 vtubermini/src/components/shop/product-card.tsx create mode 100644 vtubermini/src/components/shop/shop-currency-icon.tsx create mode 100644 vtubermini/src/index.css create mode 100644 vtubermini/src/lib/admin-rules.ts create mode 100644 vtubermini/src/lib/anniversary-image-layout.ts create mode 100644 vtubermini/src/lib/anniversary-rules.ts create mode 100644 vtubermini/src/lib/asset-ledger.ts create mode 100644 vtubermini/src/lib/gacha-rules.ts create mode 100644 vtubermini/src/lib/home-layout.ts create mode 100644 vtubermini/src/lib/mock-data.ts create mode 100644 vtubermini/src/lib/pet-rules.ts create mode 100644 vtubermini/src/lib/platform.ts create mode 100644 vtubermini/src/lib/shop-rules.ts create mode 100644 vtubermini/src/lib/types.ts create mode 100644 vtubermini/src/main.tsx create mode 100644 vtubermini/src/pages/activity-page.tsx create mode 100644 vtubermini/src/pages/album-page.tsx create mode 100644 vtubermini/src/pages/anniversary-page.tsx create mode 100644 vtubermini/src/pages/diamond-ledger-page.tsx create mode 100644 vtubermini/src/pages/gacha-log-page.tsx create mode 100644 vtubermini/src/pages/gacha-page.tsx create mode 100644 vtubermini/src/pages/heart-moments-page.tsx create mode 100644 vtubermini/src/pages/home-page.tsx create mode 100644 vtubermini/src/pages/login-page.tsx create mode 100644 vtubermini/src/pages/my-page.tsx create mode 100644 vtubermini/src/pages/notifications-page.tsx create mode 100644 vtubermini/src/pages/order-list-page.tsx create mode 100644 vtubermini/src/pages/pet-page.tsx create mode 100644 vtubermini/src/pages/points-ledger-page.tsx create mode 100644 vtubermini/src/pages/product-detail-page.tsx create mode 100644 vtubermini/src/pages/rules-page.tsx create mode 100644 vtubermini/src/pages/shop-page.tsx create mode 100644 vtubermini/src/pages/support-page.tsx create mode 100644 vtubermini/src/services/api-client.ts create mode 100644 vtubermini/src/services/contracts.ts create mode 100644 vtubermini/src/services/mock-server.ts create mode 100644 vtubermini/src/services/service-errors.ts create mode 100644 vtubermini/tsconfig.app.json create mode 100644 vtubermini/tsconfig.json create mode 100644 vtubermini/tsconfig.node.json create mode 100644 vtubermini/vite.config.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b9b7c7d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Git LFS — 大二进制媒体资源 +# 图片 +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text +*.psd filter=lfs diff=lfs merge=lfs -text +# 视频 +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.webm filter=lfs diff=lfs merge=lfs -text +*.m4v filter=lfs diff=lfs merge=lfs -text +# 音频 +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.m4a filter=lfs diff=lfs merge=lfs -text +# 安装包(双保险,通常已被 .gitignore 排除) +*.apk filter=lfs diff=lfs merge=lfs -text +*.aab filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eda64c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# ── macOS ── +.DS_Store + +# ── Logs ── +*.log +logs/ +dev-server*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* + +# ── Tooling logs / scratch ── +.codex-logs/ + +# ── Dependencies ── +node_modules/ + +# ── Build output ── +dist/ +dist-ssr/ +*.local + +# ── Capacitor / Android 构建产物 ── +# (vtubermini/android/.gitignore 另行处理 build/ .gradle/ local.properties 等) +releases/ +*.apk +*.aab + +# ── Editor ── +.vscode/* +!.vscode/extensions.json +.idea/ +*.suo +*.sw? diff --git a/DEV-PLAN.md b/DEV-PLAN.md new file mode 100644 index 0000000..254ae05 --- /dev/null +++ b/DEV-PLAN.md @@ -0,0 +1,377 @@ +# Development Plan — VTuber Mini + +> 本文件记录 VTuber Mini 的开发阶段划分、当前进度和剩余工作。 +> 新 session 启动时应首先阅读此文件,了解项目状态后再继续开发。 + +--- + +## 当前状态 + +- 产品规格已完成:`Product-Spec.md` +- 设计规范已完成:`Design-Brief.md` +- 设计交付已存在:`design/vtubermini-mvp-v1.pen`、`design/vtubermini-mvp-v1-preview.png`、`design/vtubermini-mvp-v1-export.png` +- 代码项目已创建:`vtubermini/` +- Phase 1 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证。 +- Phase 2 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认视觉和交互效果。 +- Phase 3 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认宠物互动体验。 +- Phase 4 的小游戏实现已从当前 App 入口和代码中移除,宠物积分回到每日照顾闭环。 +- Phase 5 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认抽卡和卡册体验。 +- Phase 6 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认商城兑换和订单体验。 +- Phase 7 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认纪念日记录和提醒体验。 +- Phase 8 已实现并通过 `npm.cmd run lint`、`npm.cmd run build`、本地 dev server 访问验证,等待用户确认我的页面、流水、规则、客服和通知体验。 +- Product Spec 已更新至 v1.2,新增宠物玩法目标、积分产出模型、长期养成、情绪反馈和玩法边界。 +- Product Spec 已更新至 v1.3,小游戏从当前宠物积分闭环中移除;宠物每日照顾满勤仍可获得 80 积分。 +- 当前本机可用:Node.js、`npm.cmd`、`npx.cmd` +- 当前本机不可用或未加入 PATH:Git、Java/JDK、Android SDK、Gradle + +--- + +## Phase 1: React + Capacitor App 骨架 + +**交付内容**: +- 搭建 `vtubermini/` 子目录下的 React、TypeScript、Vite、Capacitor 项目骨架。 +- 配置 Tailwind CSS 设计 token,落地 `dream-blue`、`crystal-blue`、`moon-white`、`deep-blue`、`rose-gold`。 +- 实现移动端 390px 设计宽度的 App Shell、底部 5 Tab、页面路由和基础页面容器。 +- 创建模拟数据层,提供用户资产、宠物状态、卡池、卡册、商城商品、订单、纪念日数据。 +- 实现首页、宠物、抽卡、商城、我的 5 个 Tab 的静态入口和纪念日二级页入口。 + +**关键文件**: +- `vtubermini/package.json` — 项目脚本和依赖声明。 +- `vtubermini/vite.config.ts` — Vite 配置。 +- `vtubermini/capacitor.config.ts` — Capacitor App 配置。 +- `vtubermini/src/main.tsx` — React 入口。 +- `vtubermini/src/App.tsx` — App 路由和页面装配。 +- `vtubermini/src/styles/index.css` — Tailwind 引入、全局样式和设计 token。 +- `vtubermini/src/components/app-shell.tsx` — 移动端应用壳、底部 Tab 和安全区布局。 +- `vtubermini/src/lib/mock-data.ts` — MVP 模拟数据源。 +- `vtubermini/src/lib/types.ts` — 用户、资产、宠物、卡牌、商品、订单、纪念日类型。 + +**验收标准**: +- `npm.cmd run build` 编译通过。 +- `npm.cmd run dev -- --host 127.0.0.1` 能启动并显示移动端 App 壳。 +- 浏览器打开本地地址后能切换 5 个底部 Tab,首页能进入纪念日页面。 + +--- + +## Phase 2: 首页与基础导航体验 + +**交付内容**: +- 实现深蓝沉浸式首页,展示主播主题视觉、积分余额、钻石余额、活动公告和快捷入口。 +- 实现首页纪念日卡片,展示最近纪念日、倒计时、入团陪伴天数和查看全部入口。 +- 实现公告与活动模块,展示活动规则入口和兑换提醒。 +- 实现空状态、加载状态和错误提示组件的基础样式,供后续页面复用。 + +**关键文件**: +- `vtubermini/src/pages/home-page.tsx` — 首页页面。 +- `vtubermini/src/components/home/hero-scene.tsx` — 首页深蓝主视觉和主播信息区。 +- `vtubermini/src/components/home/asset-summary.tsx` — 积分和钻石资产摘要。 +- `vtubermini/src/components/home/quick-actions.tsx` — 宠物、抽卡、商城、纪念日快捷入口。 +- `vtubermini/src/components/home/anniversary-card.tsx` — 最近纪念日卡片。 +- `vtubermini/src/components/common/state-block.tsx` — 空、加载、错误状态展示。 + +**验收标准**: +- 首页信息层级符合 Design Brief:深蓝主视觉清楚,操作入口不被装饰遮挡。 +- 首页能从纪念日卡片跳转到纪念日页面。 +- `npm.cmd run build` 编译通过,底部 Tab 切换不破坏首页状态。 + +--- + +## Phase 3: 宠物小屋与积分上限规则 + +**交付内容**: +- 实现宠物小屋页面,展示小狗形象占位、饱腹、心情、清洁、活力、亲密度状态。 +- 实现喂食、抚摸、清洁、玩耍四类互动,更新模拟状态、任务进度和积分流水。 +- 实现每日积分上限 80 分、每日操作次数限制和超限提示。 +- 实现积分钱包摘要和宠物相关积分流水列表。 + +**关键文件**: +- `vtubermini/src/pages/pet-page.tsx` — 宠物小屋页面。 +- `vtubermini/src/components/pet/pet-stage.tsx` — 小狗主视觉和状态表现。 +- `vtubermini/src/components/pet/pet-stats.tsx` — 宠物状态值展示。 +- `vtubermini/src/components/pet/pet-actions.tsx` — 宠物互动按钮。 +- `vtubermini/src/components/pet/daily-task-panel.tsx` — 今日任务和积分上限。 +- `vtubermini/src/lib/pet-rules.ts` — 宠物互动、次数限制、积分上限计算。 +- `vtubermini/src/lib/asset-ledger.ts` — 积分流水写入和余额计算。 + +**验收标准**: +- 用户点击宠物互动后能看到状态变化、任务进度变化和积分余额变化。 +- 达到每日次数或每日积分上限后,系统展示明确提示并不再增加积分。 +- `npm.cmd run build` 编译通过,首页资产摘要能反映宠物互动后的模拟资产变化。 + +--- + +## Phase 4: 小狗小游戏(已退役) + +**交付内容**: +- 原小游戏方案已从当前 App 入口、路由、类型、服务契约和代码文件中移除。 +- 当前宠物积分来源改为每日看小狗、喂食、抚摸、清洁、玩耍和全部照顾完成奖励。 +- 每日满勤仍受 80 积分上限控制。 + +**关键文件**: +- `vtubermini/src/pages/pet-page.tsx` — 当前宠物日常照顾闭环。 +- `vtubermini/src/lib/pet-rules.ts` — 当前宠物积分、次数、成长和每日完成奖励规则。 +- `vtubermini/src/services/contracts.ts` — 当前服务契约不再包含小游戏结算接口。 + +**验收标准**: +- App 内无小游戏入口和小游戏可导航路由。 +- 用户通过每日宠物照顾即可拿满 80 积分。 +- `npm.cmd run lint` 和 `npm.cmd run build` 编译通过。 + +--- + +## Phase 5: 抽卡、卡池与卡册 + +**交付内容**: +- 实现抽卡页,展示当前卡池、活动时间、单抽成本、稀有度概率、保底进度、重复卡处理规则。 +- 实现单抽和十连抽模拟流程,扣减积分、生成结果、写入抽卡日志。 +- 实现重复卡转积分和预留碎片字段,结果弹窗清楚显示新卡或重复转换。 +- 实现卡册页面,按系列、稀有度、已拥有/未拥有筛选卡牌。 + +**关键文件**: +- `vtubermini/src/pages/gacha-page.tsx` — 抽卡页面。 +- `vtubermini/src/pages/album-page.tsx` — 卡册页面。 +- `vtubermini/src/components/gacha/pool-summary.tsx` — 卡池信息和概率公示。 +- `vtubermini/src/components/gacha/gacha-actions.tsx` — 单抽和十连抽操作。 +- `vtubermini/src/components/gacha/gacha-result-modal.tsx` — 抽卡结果弹窗。 +- `vtubermini/src/components/album/card-grid.tsx` — 卡册网格。 +- `vtubermini/src/lib/gacha-rules.ts` — 卡池版本、权重抽取、保底和重复卡规则。 + +**验收标准**: +- 积分不足时不能抽卡,并展示明确原因。 +- 抽卡后积分、卡册、抽卡日志和重复卡转换结果同步更新。 +- 抽卡页清楚展示概率、成本、保底和重复处理规则,`npm.cmd run build` 编译通过。 + +--- + +## Phase 6: 钻石商城与兑换订单 + +**交付内容**: +- 实现钻石商城页面,展示商品图片、名称、规格、库存、钻石价格、发货周期和兑换规则。 +- 实现商品详情和兑换确认流程,校验钻石余额、库存和收货信息必填项。 +- 实现兑换成功后扣减钻石、锁定库存、生成订单和写入钻石流水。 +- 实现订单列表,展示待发货、已发货、已完成、售后中、已取消、已退款状态。 + +**关键文件**: +- `vtubermini/src/pages/shop-page.tsx` — 钻石商城页面。 +- `vtubermini/src/pages/product-detail-page.tsx` — 商品详情页面。 +- `vtubermini/src/pages/order-list-page.tsx` — 兑换订单列表。 +- `vtubermini/src/components/shop/product-card.tsx` — 商品卡片。 +- `vtubermini/src/components/shop/exchange-form.tsx` — 兑换确认和收货信息表单。 +- `vtubermini/src/components/orders/order-card.tsx` — 订单状态卡片。 +- `vtubermini/src/lib/shop-rules.ts` — 库存、钻石余额和订单生成规则。 + +**验收标准**: +- 钻石不足、库存不足、收货信息缺失时不能提交兑换,并展示明确原因。 +- 兑换成功后钻石余额、库存、订单记录和钻石流水同步更新。 +- 商城页面不出现充值、购买、提现或用户间交易入口,`npm.cmd run build` 编译通过。 + +--- + +## Phase 7: 纪念日记录与提醒 + +**交付内容**: +- 实现纪念日页面,区分入团时间、系统纪念日和用户自定义纪念日。 +- 实现入团时间设置,展示已陪伴天数和下一次入团周年倒计时。 +- 实现自定义纪念日新增、编辑、删除,支持一次性日期、每年重复、提醒开关和提前提醒天数。 +- 实现站内提醒模拟列表,关联抽卡或商城活动时展示活动规则入口。 + +**关键文件**: +- `vtubermini/src/pages/anniversary-page.tsx` — 纪念日页面。 +- `vtubermini/src/components/anniversary/join-date-panel.tsx` — 入团时间和陪伴天数。 +- `vtubermini/src/components/anniversary/system-anniversary-list.tsx` — 主播生日和开播周年。 +- `vtubermini/src/components/anniversary/custom-anniversary-list.tsx` — 用户自定义纪念日列表。 +- `vtubermini/src/components/anniversary/anniversary-edit-modal.tsx` — 添加和编辑弹窗。 +- `vtubermini/src/lib/anniversary-rules.ts` — 倒计时、周年、重复日期和提醒计算。 + +**验收标准**: +- 用户能设置入团时间,并看到已陪伴天数和周年倒计时。 +- 用户能新增、编辑、删除自定义纪念日,页面排序按即将到来优先。 +- 系统纪念日和用户纪念日视觉分区清楚,`npm.cmd run build` 编译通过。 + +--- + +## Phase 8: 我的页面、资产流水与规则页 + +**交付内容**: +- 实现我的页面,展示账号信息、积分余额、钻石余额和常用入口。 +- 实现积分流水、钻石流水、抽卡记录、规则与协议页面。 +- 实现客服与申诉入口的静态表单,覆盖资产异常、抽卡异常、兑换异常和风控申诉类型。 +- 实现消息通知中心,展示抽卡结果、钻石到账、兑换成功、订单状态和纪念日提醒。 + +**关键文件**: +- `vtubermini/src/pages/my-page.tsx` — 我的页面。 +- `vtubermini/src/pages/points-ledger-page.tsx` — 积分流水页面。 +- `vtubermini/src/pages/diamond-ledger-page.tsx` — 钻石流水页面。 +- `vtubermini/src/pages/gacha-log-page.tsx` — 抽卡记录页面。 +- `vtubermini/src/pages/rules-page.tsx` — 积分、钻石、抽卡、商城和隐私规则。 +- `vtubermini/src/pages/support-page.tsx` — 客服与申诉表单。 +- `vtubermini/src/pages/notifications-page.tsx` — 消息通知中心。 + +**验收标准**: +- 我的页面能跳转到资产流水、抽卡记录、订单、纪念日、规则和客服入口。 +- 规则页包含积分规则、钻石规则、概率公示、商城兑换规则、隐私政策和未成年人提示。 +- 所有资产流水能追溯来源、金额、时间和关联业务编号,`npm.cmd run build` 编译通过。 + +--- + +## Phase 9: 后台工坊基础管理 + +**交付内容**: +- 实现桌面宽度的后台工坊入口,使用清晰管理端布局,不沿用前台重玻璃视觉。 +- 实现用户与资产管理,支持查看用户资产、发放积分、发放钻石并填写原因。 +- 实现卡牌与卡池管理,支持查看卡牌、稀有度、概率、保底规则和卡池版本。 +- 实现商城商品与兑换订单管理,支持查看库存、上下架状态和订单发货状态。 +- 实现纪念日配置管理,支持配置主播生日、开播周年、主题图说明和活动入口。 +- 实现操作审计日志列表,记录操作人、时间、对象、原因和审批状态。 + +**关键文件**: +- `vtubermini/src/admin/admin-app.tsx` — 后台工坊入口。 +- `vtubermini/src/admin/admin-layout.tsx` — 后台布局、导航和页面容器。 +- `vtubermini/src/admin/pages/admin-dashboard.tsx` — 后台概览。 +- `vtubermini/src/admin/pages/user-assets-page.tsx` — 用户与资产管理。 +- `vtubermini/src/admin/pages/card-pool-page.tsx` — 卡牌与卡池管理。 +- `vtubermini/src/admin/pages/shop-orders-page.tsx` — 商品与订单管理。 +- `vtubermini/src/admin/pages/anniversary-config-page.tsx` — 纪念日配置管理。 +- `vtubermini/src/admin/pages/audit-log-page.tsx` — 操作审计日志。 + +**验收标准**: +- 浏览器能在移动端 App 和后台工坊之间切换入口。 +- 后台资产发放必须要求填写原因,并写入模拟资产流水和操作审计。 +- 后台表格、筛选和日志信息可读,`npm.cmd run build` 编译通过。 + +--- + +## Phase 10: 服务端接口契约与本地可信规则模拟 + +**交付内容**: +- 抽离前端模拟业务规则,定义服务端接口契约和请求/响应类型。 +- 实现本地 API 适配层,把积分、钻石、抽卡、订单、纪念日和后台发放统一改为异步服务调用。 +- 创建服务端可信规则文档,明确前端不能自行决定积分、钻石、抽卡结果或订单状态。 +- 增加错误路径:网络失败、余额不足、库存不足、卡池下架、重复提交和表单校验失败。 + +**关键文件**: +- `vtubermini/src/services/api-client.ts` — 前端 API 客户端。 +- `vtubermini/src/services/contracts.ts` — API 请求和响应类型。 +- `vtubermini/src/services/mock-server.ts` — 本地内存服务端模拟。 +- `vtubermini/src/services/service-errors.ts` — 业务错误码和错误文案。 +- `vtubermini/docs/api-contract.md` — 服务端接口契约。 +- `vtubermini/docs/trusted-rules.md` — 服务端可信规则说明。 + +**验收标准**: +- 前端页面不再直接调用规则函数修改资产,统一通过服务层请求。 +- 所有核心业务错误有明确错误码和用户可读文案。 +- `npm.cmd run build` 编译通过,宠物、抽卡、商城、后台发放的主流程仍可用。 + +--- + +## Phase 11: Android 工程接入与打包准备 + +**交付内容**: +- 初始化 Capacitor Android 工程,配置 App 名称、包名、图标占位和启动页占位。 +- 补充 Android 构建环境检查脚本,检测 JDK、Android SDK、Gradle 和 adb。 +- 编写 APK 打包说明,明确本机缺少 JDK/Android SDK 时的安装前置条件。 +- 在 Android 构建环境可用后执行 Web 构建、Capacitor 同步和 Android Debug APK 构建。 + +**关键文件**: +- `vtubermini/android/` — Capacitor Android 工程。 +- `vtubermini/scripts/check-android-env.mjs` — Android 构建环境检查脚本。 +- `vtubermini/docs/android-build.md` — Android 打包和环境配置说明。 +- `vtubermini/capacitor.config.ts` — Android App 名称、包名和 Web 输出路径。 + +**验收标准**: +- `npm.cmd run build` 成功生成 Web 构建产物。 +- `npm.cmd run cap:sync` 在 Android 工程存在且依赖可用时同步成功。 +- 本机安装 JDK 和 Android SDK 后,能按 `docs/android-build.md` 构建 Debug APK。 + +--- + +## Phase 12: 宠物玩法 v2 补强 + +**交付内容**: +- 按 Product Spec v1.3 调整宠物积分模型:登录看小狗 10 分、喂食 20 分、抚摸 21 分、清洁 10 分、玩耍互动 14 分、全部照顾完成奖励 5 分,总上限仍为每日 80 分。 +- 补齐每日照顾闭环:今日登录看小狗、全部照顾完成状态、连续照顾天数、累计照顾天数、今日推荐动作和积分接近单抽成本时的抽卡提示。 +- 增加轻量亲密等级:根据亲密度展示 Lv.1-Lv.10、当前等级称号和下一等级进度;不影响抽卡概率、积分倍率或商城兑换。 +- 优化宠物页情绪体验:主视觉展示状态短句,互动成功反馈同时包含陪伴文案和积分变化,达到上限后明确提示继续互动只增加亲密度。 +- 优化交互状态:达到每日次数的操作按钮禁用并显示“已完成”,今日任务区显示全部照顾完成态,底部展示今日积分进度和抽卡入口。 + +**关键文件**: +- `vtubermini/src/lib/types.ts` — 扩展宠物状态字段:亲密等级、每日登录、全部照顾完成、连续照顾、累计照顾。 +- `vtubermini/src/lib/pet-rules.ts` — 宠物互动、登录看小狗、全部照顾完成奖励、推荐动作和亲密等级计算。 +- `vtubermini/src/lib/mock-data.ts` — 补齐宠物 v2 初始模拟数据。 +- `vtubermini/src/components/pet/pet-stage.tsx` — 主视觉状态短句、今日推荐动作和亲密等级展示。 +- `vtubermini/src/components/pet/pet-actions.tsx` — 按钮禁用态和已完成文案。 +- `vtubermini/src/pages/pet-page.tsx` — 宠物页抽卡轻提示和整体信息顺序。 +- `vtubermini/src/pages/rules-page.tsx` — 更新积分规则文案。 +- `vtubermini/docs/api-contract.md`、`vtubermini/docs/trusted-rules.md` — 同步宠物 v2 服务端可信规则。 + +**验收标准**: +- 用户进入宠物页即可触发或展示“登录看小狗”进度,今日积分模型能跑满但不超过 80 分。 +- 四项每日照顾全部完成后,只发放一次完成奖励;达到积分上限后不再增加积分,但仍可展示亲密度或陪伴反馈。 +- 亲密等级、连续照顾、今日推荐动作、全部照顾完成态和抽卡轻提示能在宠物页清楚展示。 +- 宠物状态不影响抽卡概率、积分倍率、商城价格或实物兑换资格。 +- `npm.cmd run lint` 和 `npm.cmd run build` 编译通过。 + +--- + +## 技术栈 + +| 层级 | 技术 | 版本 | 说明 | +| --- | --- | --- | --- | +| 运行时 | Node.js | 24.16.0 | 当前本机已安装版本,用于开发和构建。 | +| 包管理 | npm | 11.13.0 | 当前本机 `npm.cmd` 可用;PowerShell 中避免直接调用 `npm.ps1`。 | +| 前端框架 | React | 19.x | 构建移动 App 和后台工坊界面。 | +| 构建工具 | Vite | 8.x | React + TypeScript 开发服务器和生产构建。 | +| 类型系统 | TypeScript | 5.x | 开启 strict mode,禁止业务代码使用 `any`。 | +| 样式 | Tailwind CSS | 4.x | 设计 token、响应式布局和组件样式。 | +| 移动壳 | Capacitor | 8.x | Web App 打包为 Android 工程和 APK。 | +| 图标 | lucide-react | 0.x | 按钮、Tab、状态和入口图标。 | +| 本地数据 | 内存模拟服务 | 项目内实现 | Phase 1-9 使用,Phase 10 抽象为服务接口,后续可替换云端 API。 | +| Android 构建 | Android SDK + JDK + Gradle | 系统环境 | 当前本机缺失;Phase 11 前必须安装并配置。 | + +--- + +## 数据库表 + +| 表名 | 所属 Phase | 用途 | +| --- | --- | --- | +| `users` | Phase 10 | 用户身份、昵称、头像、登录状态和账号基础信息。 | +| `user_assets` | Phase 10 | 用户积分余额、钻石余额和碎片余额。 | +| `asset_ledger` | Phase 10 | 积分、钻石、碎片的获得、消耗、退回和后台发放流水。 | +| `pet_states` | Phase 10 / Phase 12 | 饱腹、心情、清洁、活力、亲密度、亲密等级、连续照顾天数、累计照顾天数、装备状态和回忆节点。 | +| `daily_task_states` | Phase 10 / Phase 12 | 每日积分获得量、每日积分上限、登录看小狗状态、全部照顾完成状态、互动次数和重置日期。 | +| `pet_items` | Phase 12 后续 | 宠物装饰配置,包括头饰、颈饰、小窝背景、来源、解锁条件和上下架状态。 | +| `pet_memories` | Phase 12 后续 | 宠物回忆节点配置,包括触发条件、标题、文案、关联纪念日或抽卡事件。 | +| `cards` | Phase 10 | 卡牌编号、系列、稀有度、名称、图片和上下架状态。 | +| `card_pools` | Phase 10 | 卡池标识、版本、活动时间、消耗积分、保底规则和重复规则。 | +| `card_pool_entries` | Phase 10 | 卡池版本内的卡牌范围、稀有度权重和概率配置。 | +| `owned_cards` | Phase 10 | 用户拥有的卡牌、获取时间、来源和重复次数。 | +| `gacha_logs` | Phase 10 | 每次抽卡的卡池版本、消耗、结果、重复转换和业务流水编号。 | +| `shop_products` | Phase 10 | 商城商品、规格、图片、钻石价格、库存、发货周期和售后规则。 | +| `exchange_orders` | Phase 10 | 兑换订单、收货信息、库存锁定、物流状态和售后状态。 | +| `anniversaries` | Phase 10 | 入团时间、主播生日、开播周年和用户自定义纪念日。 | +| `notifications` | Phase 10 | 站内通知、纪念日提醒、钻石到账、兑换和订单状态消息。 | +| `support_tickets` | Phase 10 | 资产异常、抽卡异常、兑换异常和风控申诉记录。 | +| `admin_audit_logs` | Phase 10 | 后台操作人、时间、对象、原因、审批状态和业务流水编号。 | + +--- + +## 开发规则 + +- 代码放在 `vtubermini/` 子目录,根目录保留规划文档和设计资产。 +- 每个 Phase 开始前先读取本文件、`Product-Spec.md` 和 `Design-Brief.md` 中对应章节。 +- 每完成一个 Phase 执行四步走:Code Review → 测试完整性 → 编译验证 → 功能测试。 +- 四步走全部通过后才能确认 Phase 完成。 +- Commit message 格式:`phase-N: 简要描述`;当前本机 Git 不可用时先跳过提交并记录原因。 +- 包管理器使用 `npm.cmd`。 +- 业务资产规则必须逐步收敛到服务层;前端页面不能长期直接决定积分、钻石、抽卡结果或订单状态。 +- 毛玻璃仅用于容器背景,关键数字、概率、价格、库存和订单状态必须保持高对比度可读。 +- MVP 不实现充值、提现、用户间交易、卡牌转卖、多主播入驻、多只宠物、复杂会员体系。 + +--- + +## 最新进度补充 + +- Phase 9 已实现:新增后台工坊入口、桌面管理布局、用户资产发放、卡牌卡池、商城订单、纪念日配置和审计日志页面;后台发放会写入资产流水与审计日志。 +- Phase 10 已实现:新增 `src/services` 服务层契约、本地 mock server、API client、错误码与接口文档;核心资产变动流程已从 `App.tsx` 直接规则调用改为服务层调用。 +- Phase 11 已实现准备项:新增 Android 工程、`cap:sync` 脚本、Android 环境检查脚本和打包文档;当前本机缺 Java JDK、Android SDK、adb、Gradle,需安装后才能执行 Debug APK 构建。 +- Phase 12 已实现:宠物玩法 v2 补强完成,包含每日看小狗奖励、全部照顾完成奖励、亲密等级、连续照顾、推荐动作、完成态和抽卡轻提示;小游戏已从当前宠物积分闭环中移除。 diff --git a/Design-Brief.md b/Design-Brief.md new file mode 100644 index 0000000..bebca95 --- /dev/null +++ b/Design-Brief.md @@ -0,0 +1,209 @@ +# Design Brief: VTuber Mini + +## 产品概要 + +- 产品名称:VTuber Mini +- 产品类型:Mobile App,第一阶段输出 Android APK +- 目标用户:单个虚拟主播的粉丝,尤其是愿意每日互动、收集虚拟小卡、参与周边兑换和纪念日运营活动的用户。 +- 核心功能:宠物小屋与小狗小游戏、积分抽卡、虚拟小卡卡册、钻石商城、纪念日、我的资产与订单、后台工坊。 + +## 设计方向 + +### 情绪关键词 + +- **蓝色梦境**:整体视觉像进入一个冷色、通透、带柔光的主播专属空间,不做普通工具 App 的白底列表感。 +- **轻奢收藏**:抽卡、卡册、会员、商城区域要有玻璃、钻石、皇冠、玫瑰等收藏感和仪式感。 +- **温柔陪伴**:宠物小屋和纪念日不能过冷,要保留亲密、安静、陪伴感,让用户愿意每天回来。 + +### 参考图 + +| 参考来源 | 喜欢的方面 | 不直接照搬的方面 | +| --- | --- | --- | +| `C:/Users/59719/Desktop/assets/1777526300869.png` | 雾霾蓝主色、水晶蓝辅助色、月光白底色、深邃蓝文字、玫瑰金点缀;玻璃玫瑰、钻石皇冠、悬浮钻石、毛玻璃卡片;按钮和卡片的通透高光 | 参考图是视觉系统展板,不是移动端 App。实际 App 要减少大段装饰和文字密度,保证手机屏幕上操作清楚 | +| `C:/Users/59719/Desktop/assets/20260601-151641.png` | 首页使用深蓝夜景、主播立绘、发光导航图标,沉浸感强 | 右侧有明显遮挡或占位区域,后续需要确认是隐私遮挡还是素材缺失 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面.png` | 二级页使用月光白大留白、浅蓝玻璃玫瑰和人物半身视觉,适合作为纪念日内容页基底 | 当前内容承载区大面积留空,开发设计时必须补足真实信息层级 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面-弹窗.png` | 弹窗容器延续浅蓝玻璃、玫瑰纹理和柔光边框 | 需要确认实际透明通道和边缘显示效果 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面-隔断.png` | 展示首页到二级页的视觉隔断关系:上半部深蓝沉浸,下半部浅色内容承载 | 需要确认作为设计稿参考还是可切图素材 | + +### 参考产品与模式 + +| 参考产品/模式 | 喜欢的方面 | 不喜欢的方面 | +| --- | --- | --- | +| 参考图中的 DL.L9 蓝色梦境系统 | 高级、统一、冷色梦幻、组件有会员感 | 如果全页面都重玻璃,会影响阅读和性能 | +| 主流移动端 Glassmorphism | 通透、层次、适合会员卡、抽卡结果、纪念日卡片 | 低对比度容易影响可读性,正文区域不能过度透明 | +| 粉丝抽卡/收藏类 App | 卡池、卡册、稀有度、结果弹窗需要有强反馈 | 不能做成博彩感太强的刺激界面 | + +### 反面参考 + +- 普通电商蓝白后台风格:不符合主播粉丝 App 的情绪价值。 +- 过度赛博霓虹风:会和“蓝色梦境、温柔陪伴”冲突。 +- 全屏强动效抽奖风:容易显得廉价,也会增加合规和沉迷风险。 + +## 视觉规范 + +### 色彩方向 + +- **主题模式**:浅色为主,局部深蓝夜景氛围;不做全局深色主题。 +- **色彩温度**:冷色。 +- **品牌主色方向**:雾霾蓝、冰晶蓝、月光白、深邃蓝。 +- **强调色方向**:少量玫瑰金用于 VIP、SSR、兑换成功、周年纪念等高价值状态。 +- **背景方向**:浅蓝到月光白的柔和渐变,叠加细微星点、冰晶纹理或光斑。 +- **品牌资产**:当前没有正式 logo,先以主播名、动物化小狗形象、玻璃玫瑰/皇冠/钻石元素构建视觉识别。 + +#### 颜色 Token + +| Token | 色值 | 用途 | +| --- | --- | --- | +| `dream-blue` | `#4A90E2` | 主按钮、重点入口、进度条、可点击强调 | +| `crystal-blue` | `#B8D4F1` | 毛玻璃边缘、浅色卡片背景、二级页辅助色 | +| `moon-white` | `#E8F4F8` | 页面底色、内容区底色、柔光层 | +| `deep-blue` | `#2C5F8D` | 标题、关键数字、深色状态文字 | +| `rose-gold` | `#D4AF37` | VIP、SSR、纪念奖励、稀有状态点缀 | + +这些色值来自用户提供的视觉系统图,后续设计稿和代码应以它们作为第一版设计 token。允许根据可读性微调明度,但不能改成另一套蓝色体系。 + +### 页面层级 + +- **日常首页**:使用深蓝夜景、主播立绘、发光图标、粒子和花瓣,承担最强情绪价值。 +- **二级内容页**:使用月光白和冰晶蓝,保留玫瑰与柔光装饰,承担阅读、配置、填写、查询。 +- **弹窗/表单**:使用浅色毛玻璃容器,深蓝标题和高对比输入区,不能只做透明装饰。 +- **后台工坊**:不使用沉浸夜景,采用清晰管理端布局,只继承蓝白色系和少量品牌元素。 + +### 信息密度 + +- **密度方向**:适中偏宽松。 +- **参考基准**:接近参考图右侧 UI 组件区的节奏,而不是左侧色彩说明区的展板密度。 +- **理由**:App 有宠物、抽卡、商城、纪念日、资产等多个入口,首页需要清晰分组;抽卡和商城要有情绪价值,但资产、规则、订单必须可读。 + +### 排版方向 + +- **字体气质**:中文标题优雅、清晰,有轻奢感;正文使用现代无衬线,保证移动端可读。 +- **中文字体偏好**:设计稿可使用偏优雅的标题字;开发实现优先使用系统字体,必要时只为标题引入品牌字体。 +- **标题风格**:首页和活动页标题可以更精致;功能页标题保持小而清晰,不做大字堆叠。 +- **数字字体**:积分、钻石、倒计时、概率、抽卡结果编号使用更硬朗的数字风格,强化资产感。 + +### 视觉元素 + +- **核心元素**:玻璃玫瑰、皇冠、钻石、星芒、月光、冰晶、水滴、毛玻璃卡片。 +- **宠物元素**:小狗形象要软萌、圆润、亲近;可以使用蓝色丝带、皇冠、小披风等主播主题装饰。 +- **卡牌元素**:卡面边框使用稀有度光效,SSR 可加入玫瑰金、星芒和晶体折射。 +- **纪念日元素**:使用月光、花瓣、星轨、倒计时环,不使用闹钟式工具图标风。 + +### 组件风格 + +- **卡片**:毛玻璃卡片为主,白蓝半透明底,边缘有细描边和柔和阴影。 +- **按钮**:主按钮使用蓝色玻璃胶囊按钮,带轻微高光和右箭头;危险操作用低饱和红,不使用强烈警告色。 +- **图标**:线性图标,细线蓝色;核心图标包括皇冠、玫瑰、钻石、心形、星芒、日历、小狗爪印。 +- **进度条**:蓝色发光条,用于亲密度、每日积分上限、卡池保底进度。 +- **标签**:VIP、SSR、限定、周年等标签使用小面积玫瑰金或深蓝底。 + +### 交互风格 + +- **动画程度**:适中。 +- **过渡效果**:卡片轻微浮起、柔光扫过、星点闪烁、抽卡结果渐显;避免长时间不可跳过动画。 +- **整体节奏**:宠物页慢一点、柔一点;抽卡页有仪式感但不刺激;商城和订单页干净直接。 + +## 核心页面视觉备注 + +### 首页 + +- **核心交互**:查看资产、进入宠物、抽卡、商城、纪念日和活动。 +- **视觉方向**:按 `20260601-151641.png` 和首页视频方向执行。页面以深蓝夜景和主播立绘为核心,顶部展示等级/心动值/日历等状态,右侧和底部使用发光图标入口。 +- **特殊要求**:首页不能做成纯活动海报,必须保留清楚的操作入口。 + +### 宠物小屋 + +- **核心交互**:照顾主播动物化小狗,完成每日任务和小游戏。 +- **视觉方向**:温柔、陪伴、柔光。背景可以是蓝色梦境房间、月光窗边、冰晶小窝,但小狗区域要更暖、更可爱。 +- **特殊要求**:宠物操作按钮必须直观,不要被装饰元素淹没。积分上限和剩余次数要明确。 + +### 小狗小游戏 + +- **核心交互**:根据表现获得浮动积分,受每日上限控制。 +- **视觉方向**:轻量、清楚、低干扰。可用“接星星/接骨头/躲水晶障碍”这类蓝色梦境主题玩法。 +- **特殊要求**:游戏 UI 不能堆太多玻璃效果,移动目标和分数必须高对比度。 + +### 抽卡页 + +- **核心交互**:查看卡池、概率、保底,消耗积分抽取小卡。 +- **视觉方向**:仪式感最强的页面。卡池主视觉可使用玻璃玫瑰、皇冠、钻石粒子和深蓝夜景。 +- **特殊要求**:概率、消耗、保底规则必须显著可见,不能为了氛围弱化规则信息。 + +### 抽卡结果 + +- **核心交互**:展示获得小卡、稀有度、重复转化结果。 +- **视觉方向**:卡牌从柔光中显现,稀有度决定光效强弱;SSR 使用深蓝+玫瑰金+晶体折射。 +- **特殊要求**:重复卡转化要清楚显示,不要让用户误以为获得了新卡。 + +### 卡册 + +- **核心交互**:浏览已拥有/未拥有小卡,按系列和稀有度筛选。 +- **视觉方向**:收藏册感,像透明卡盒或水晶展柜;已拥有卡片有高光,未拥有卡片使用淡蓝剪影。 +- **特殊要求**:筛选器要克制,不做复杂后台式筛选栏。 + +### 钻石商城 + +- **核心交互**:浏览实物奖品,用钻石兑换。 +- **视觉方向**:精品陈列感,商品卡片像展示柜;钻石价格和库存要清楚。 +- **特殊要求**:不能让商城像付费充值页。钻石来源是后台发放,视觉上强调“兑换权益”而不是“购买诱导”。 + +### 纪念日页 + +- **核心交互**:查看入团天数、主播生日、开播周年、自定义纪念日和提醒。 +- **视觉方向**:按 `二级页面-纪念日页面.png` 执行,使用浅蓝/月光白背景、右上人物半身、蓝玫瑰、花瓣、白色大内容容器。整体是安静纪念,不做闹钟工具页。 +- **特殊要求**:系统纪念日和用户自定义纪念日要视觉分区。添加纪念日表单要简单,不要像日程管理 App。 + +### 纪念日弹窗 + +- **核心交互**:添加或编辑自定义纪念日、修改入团时间、设置提醒。 +- **视觉方向**:按 `二级页面-纪念日页面-弹窗.png` 执行,弹窗使用浅色毛玻璃、柔光边框、玫瑰纹理。 +- **特殊要求**:输入框、日期选择、提醒开关必须高对比度,不能只依赖透明玻璃背景。 + +### 我的页 + +- **核心交互**:查看账号、资产、订单、规则、客服、纪念日入口。 +- **视觉方向**:比首页更克制,偏清单式但保持毛玻璃卡片质感。 +- **特殊要求**:资产和订单入口要清楚,不要为了美观降低查账效率。 + +### 后台工坊 + +- **核心交互**:运营配置用户、资产、卡池、商城、订单和纪念日。 +- **视觉方向**:不沿用前台梦幻视觉,后台应采用清楚、稳重、高信息密度的管理端风格。 +- **特殊要求**:后台可以使用蓝白体系,但不要毛玻璃过重;表格、筛选、审计日志优先可读性。 + +## 状态设计 + +- **空状态**:使用轻量插画或线性图标,例如空卡册用淡蓝卡片剪影,空纪念日用月光日历,空订单用水晶礼盒;文案简短并给一个明确操作按钮。 +- **加载状态**:页面列表用骨架屏;抽卡用短时光效加载;宠物页用小狗轻动作;商城和订单页不用复杂动画。 +- **错误状态**:内联提示为主,使用低饱和红或深蓝提示框;资产扣减、兑换失败、抽卡失败必须给明确原因和重试入口。 +- **成功状态**:兑换成功、钻石到账、纪念日保存成功使用蓝色柔光和轻微星芒,避免夸张庆祝动画。 + +## 可访问性与可用性约束 + +- 毛玻璃只用于容器背景,正文和关键数字必须放在高对比度区域。 +- 抽卡概率、积分消耗、钻石价格、库存、订单状态必须清晰可读。 +- 蓝色系不要一味叠加,必须使用月光白、深邃蓝和少量玫瑰金拉开层次。 +- 所有主操作按钮要有明确文字和方向图标,不只依赖装饰。 +- 动画必须可快速结束,不能阻塞用户查规则、查订单或跳过结果。 + +## 资产清单 + +| 文件 | 用途 | 状态 | +| --- | --- | --- | +| `C:/Users/59719/Desktop/assets/1777526300869.png` | 总视觉系统参考:颜色、字体、元素、组件、氛围 | 已纳入规范 | +| `C:/Users/59719/Desktop/assets/20260601-151641.png` | 日常首页静态参考 | 已纳入首页规范;需确认遮挡区域 | +| `C:/Users/59719/Desktop/assets/日常首页-白天.mp4` | 首页白天动态效果 | 待逐帧复核 | +| `C:/Users/59719/Desktop/assets/日常首页-夜晚.mp4` | 首页夜晚动态效果 | 待逐帧复核 | +| `C:/Users/59719/Desktop/assets/20260601-151658.mp4` | 短动效参考,具体场景待确认 | 待逐帧复核 | +| `C:/Users/59719/Desktop/assets/20260601-151705.mp4` | 短动效参考,具体场景待确认 | 待逐帧复核 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面.png` | 纪念日二级页主视觉参考 | 已纳入规范 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面-弹窗.png` | 纪念日弹窗参考 | 已纳入规范;需确认透明通道 | +| `C:/Users/59719/Desktop/assets/二级页面-纪念日页面-隔断.png` | 首页到二级页的隔断和过渡参考 | 已纳入规范;需确认切图用途 | + +## 待补充 + +- 主播正式名称、Logo、头像、动物化小狗设定图。 +- 是否已有固定品牌色、字体或粉丝团视觉资产。 +- 用户明确讨厌的视觉风格。 +- 小狗小游戏的最终玩法类型。 +- 4 个 MP4 文件的逐帧视觉复核;当前本机缺少 `ffmpeg/ffprobe`,暂未抽帧检查。 diff --git a/Development-Requirements-Breakdown.md b/Development-Requirements-Breakdown.md new file mode 100644 index 0000000..1e1ecd8 --- /dev/null +++ b/Development-Requirements-Breakdown.md @@ -0,0 +1,320 @@ +# VTuber Mini 开发需求拆解文档 + +## 1. 项目定位 + +VTuber Mini 是一个面向单个虚拟主播粉丝的 Android 优先 App。第一阶段目标不是做通用多主播平台,而是交付一个可运营的粉丝互动闭环: + +- 粉丝每日照顾主播动物化小狗,获得积分。 +- 粉丝用积分抽取主播虚拟小卡,并在卡册中收藏。 +- 运营通过后台发放钻石。 +- 粉丝用钻石在商城兑换实物周边。 +- 粉丝记录入团时间、主播生日、开播周年和自定义纪念日。 +- 运营通过后台管理资产、卡池、商城、订单、纪念日配置和审计日志。 + +当前项目状态:前端可交互 MVP 已搭建,业务规则目前在本地 mock service 中模拟。生产化仍需要真实后端、数据库、鉴权、权限、风控、订单履约和 Android 构建环境。 + +## 2. 开发分工建议 + +| 分工 | 负责范围 | 主要交付 | +| --- | --- | --- | +| A 前台基础体验 | App 壳、登录、首页、我的、规则、通知 | 移动端主流程、导航、入口、规则和用户中心 | +| B 宠物与积分 | 宠物小屋、每日任务、积分上限、积分流水 | 每日照顾闭环和积分可信结算接口 | +| C 抽卡收藏 | 卡池、抽卡、卡册、抽卡日志、重复卡处理 | 抽卡体验、概率公示、卡牌资产记录 | +| D 商城订单 | 钻石钱包、商品、兑换、订单、库存 | 钻石兑换实物周边闭环 | +| E 纪念日 | 入团时间、系统纪念日、自定义纪念日、提醒 | 陪伴关系记录和提醒能力 | +| F 后台与服务端 | 后台工坊、API、数据库、审计、风控 | 生产可信业务服务 | +| G 构建交付 | Capacitor、Android 环境、APK、发布说明 | Android 可安装包和构建文档 | + +## 3. 前台 App 需求 + +### 3.1 App 壳与登录 + +**目标**:用户能稳定进入 App,并在核心模块之间切换。 + +**需求** +- Android 优先,使用 React + Vite + TypeScript + Capacitor。 +- 底部 5 个主 Tab:首页、宠物、抽卡、商城、我的。 +- 二级页包括纪念日、卡册、订单、积分流水、钻石流水、抽卡记录、规则、客服、通知。 +- 登录后加载用户资产、宠物状态、卡册、订单、纪念日等个人数据。 + +**验收** +- 5 个 Tab 切换不丢状态。 +- 未登录用户不能进入业务主界面。 +- 用户身份必须绑定资产、卡册、订单、纪念日数据。 + +### 3.2 首页 + +**目标**:让用户快速看到主播氛围、个人资产和核心入口。 + +**需求** +- 展示主播主题视觉。 +- 展示积分余额和钻石余额。 +- 提供宠物、抽卡、商城、纪念日、活动等快捷入口。 +- 展示最近纪念日卡片和公告活动。 + +**验收** +- 资产数字与服务端返回一致。 +- 所有核心入口可点击进入。 +- 视觉装饰不能遮挡操作入口。 + +### 3.3 宠物小屋 + +**目标**:用户每日照顾主播小狗,形成回访和积分产出。 + +**需求** +- 展示小狗状态:饱腹、心情、清洁、活力、亲密度、亲密等级。 +- 支持每日看小狗奖励。 +- 支持喂食、抚摸、清洁、玩耍四类互动。 +- 每日积分上限暂定 80。 +- 展示今日任务进度、今日剩余可获得积分、连续照顾天数、累计照顾天数。 +- 达到每日上限后,继续互动只能增加陪伴反馈或亲密度,不能继续发积分。 + +**验收** +- 每日照顾可跑满 80 分,但不能超过 80 分。 +- 每类互动达到每日次数后应禁用或提示已完成。 +- 积分发放必须写资产流水。 +- 宠物状态不得影响抽卡概率、积分倍率、商城价格或实物兑换资格。 + +### 3.4 抽卡与卡册 + +**目标**:用户用积分抽取主播小卡,并沉淀收藏。 + +**需求** +- 展示当前卡池、卡池版本、活动时间、单抽成本、概率、保底、重复卡处理规则。 +- 支持单抽和十连。 +- 抽卡消耗积分。 +- 抽中卡牌后写入用户卡册。 +- 重复卡自动转换为碎片或积分,第一阶段可先转积分,但需要保留碎片字段。 +- 抽卡记录要包含卡池版本、消耗、结果、是否重复、转换结果。 +- 卡册支持按系列、稀有度、拥有状态筛选。 + +**验收** +- 积分不足不能抽卡。 +- 卡池下架不能抽卡。 +- 抽卡结果、扣积分、发卡、重复转换、日志必须同事务完成。 +- 概率、成本、保底和重复规则在页面明确展示。 + +### 3.5 钻石商城与订单 + +**目标**:用户用后台发放的钻石兑换实物周边。 + +**需求** +- 展示商品图片、名称、规格、库存、钻石价格、发货周期、兑换规则、售后说明。 +- 用户提交收货人、手机号、地址后兑换。 +- 兑换时校验钻石余额、库存、商品状态、收货信息。 +- 成功后扣钻石、锁定或扣减库存、生成订单、写钻石流水。 +- 订单展示待发货、已发货、已完成、售后中、已取消、已退款等状态。 + +**验收** +- 钻石不足、库存不足、地址缺失时不能提交。 +- 兑换成功后余额、库存、订单、流水同步更新。 +- 前台不出现充值、购买钻石、提现、转赠、用户间交易入口。 + +### 3.6 纪念日 + +**目标**:记录粉丝和主播的陪伴关系。 + +**需求** +- 支持设置入团时间,并展示已陪伴天数和下一次入团周年倒计时。 +- 展示系统纪念日:主播生日、开播周年。 +- 支持用户自定义纪念日:名称、日期、是否每年重复、提醒开关、提前提醒天数、备注。 +- 纪念日列表按即将到来排序。 +- 纪念日提醒可以先做站内模拟,后续接推送。 + +**验收** +- 用户能新增、编辑、删除自己的自定义纪念日。 +- 系统纪念日和用户纪念日视觉分区清楚。 +- 纪念日默认不直接发积分或钻石,奖励必须走活动配置或后台资产发放。 + +### 3.7 我的、规则、客服、通知 + +**目标**:用户能查账、查规则、查看通知并提交问题。 + +**需求** +- 我的页展示账号信息、积分余额、钻石余额。 +- 入口包括积分流水、钻石流水、抽卡记录、卡册、订单、纪念日、规则、客服、通知。 +- 规则页包含积分规则、钻石规则、抽卡概率、商城兑换、隐私政策、未成年人提示。 +- 客服申诉覆盖资产异常、抽卡异常、兑换异常、风控申诉。 +- 通知中心展示抽卡结果、钻石到账、兑换成功、订单状态、纪念日提醒。 + +**验收** +- 所有入口可达。 +- 资产流水可追溯来源、金额、时间和业务编号。 +- 规则信息与实际业务规则一致。 + +## 4. 后台工坊需求 + +### 4.1 用户与资产管理 + +**需求** +- 查看用户基础信息和资产余额。 +- 给指定用户发放积分、钻石或卡牌。 +- 发放必须填写原因。 +- 发放必须写资产流水和后台审计日志。 + +**验收** +- 无原因不能发放。 +- 普通用户不能调用后台发放接口。 +- 审计日志包含操作人、时间、对象、原因、业务编号和状态。 + +### 4.2 卡牌与卡池管理 + +**需求** +- 管理卡牌:名称、系列、稀有度、图片、上下架状态。 +- 管理卡池:版本、活动时间、成本、概率、保底、重复卡规则、上下架状态。 +- 概率或卡牌范围变更时创建新版本,不覆盖历史版本。 + +**验收** +- 历史抽卡记录能追溯到当时卡池版本。 +- 卡池不能为空奖。 +- 概率配置必须可公示。 + +### 4.3 商城与订单管理 + +**需求** +- 管理商品:图片、名称、规格、钻石价格、库存、上下架、发货周期、售后规则。 +- 管理订单:待发货、已发货、已完成、售后中、已取消、已退款。 +- 后台可填写物流信息和处理售后状态。 + +**验收** +- 下架商品不能兑换。 +- 库存不能超卖。 +- 订单状态变更有审计记录。 + +### 4.4 纪念日配置 + +**需求** +- 配置主播生日、开播周年、主题图、祝福文案、活动入口。 +- 系统纪念日用户不可直接编辑。 + +**验收** +- 前台能读取后台配置。 +- 系统纪念日和用户自定义纪念日权限隔离。 + +## 5. 服务端与数据需求 + +### 5.1 核心 API + +建议保持当前服务响应结构: + +```ts +interface ServiceResponse { + ok: boolean + data?: T + error?: { + code: string + message: string + } +} +``` + +核心接口: + +| 接口 | 用途 | +| --- | --- | +| `GET /api/me/state` | 获取用户首页聚合状态 | +| `POST /api/pet/visit` | 每日看小狗奖励 | +| `POST /api/pet/actions` | 宠物互动与积分结算 | +| `POST /api/gacha/draw` | 积分抽卡 | +| `POST /api/shop/exchange` | 钻石兑换商品 | +| `POST /api/admin/assets/grants` | 后台资产发放 | +| `PATCH /api/anniversaries/join-date` | 设置入团时间 | +| `POST /api/anniversaries/custom` | 新增或编辑自定义纪念日 | +| `DELETE /api/anniversaries/custom/{id}` | 删除自定义纪念日 | + +### 5.2 数据表 + +| 表 | 用途 | +| --- | --- | +| `users` | 用户身份、昵称、生日、基础资料 | +| `user_assets` | 积分、钻石、碎片余额 | +| `asset_ledger` | 所有资产变更流水 | +| `pets` | 宠物状态、亲密度、连续照顾 | +| `pet_daily_states` | 每日任务、次数、每日积分上限 | +| `cards` | 卡牌配置 | +| `user_cards` | 用户持卡与重复次数 | +| `gacha_pools` | 卡池版本与规则 | +| `gacha_pool_rates` | 卡池概率配置 | +| `gacha_logs` | 抽卡主日志 | +| `gacha_log_items` | 抽卡结果明细 | +| `shop_products` | 商城商品 | +| `exchange_orders` | 兑换订单 | +| `anniversaries` | 系统和用户纪念日 | +| `notifications` | 站内通知 | +| `support_tickets` | 客服申诉 | +| `admin_audit_logs` | 后台操作审计 | + +### 5.3 服务端可信规则 + +- 前端不能决定积分、钻石、碎片余额。 +- 前端不能生成抽卡结果。 +- 前端不能决定订单最终状态。 +- 宠物奖励、抽卡、兑换、后台发放必须由服务端事务处理。 +- 所有资产变更必须写流水。 +- 抽卡扣款、发卡、重复转换、日志、保底进度必须同事务完成。 +- 商城扣钻、锁库存、生成订单必须同事务完成。 +- 后台发放必须鉴权、记录原因、写审计。 + +## 6. 合规与风控要求 + +- 抽卡页必须展示概率、成本、卡池范围、保底、重复卡处理规则。 +- 卡池概率变更必须创建新版本。 +- 不允许空奖。 +- MVP 不开放钻石充值、转赠、提现、二级交易。 +- 实物兑换涉及姓名、手机号、地址,必须做权限控制、日志脱敏和数据保留策略。 +- 需要预留未成年人提示、隐私政策和活动规则展示。 +- 需要处理重复提交、库存并发、余额并发、抽卡重复扣款、每日奖励多端重复领取。 + +## 7. Android 构建交付 + +**当前状态** +- Capacitor Android 工程已存在。 +- 当前本机缺 JDK、Android SDK、adb、Gradle,无法直接完成 Debug APK 构建。 + +**需求** +- 补齐 Android 构建环境。 +- 执行 Web 构建和 Capacitor 同步。 +- 输出可安装 Debug APK。 +- 保留构建文档和环境检查脚本。 + +**验收** +- `npm.cmd run lint` 通过。 +- `npm.cmd run build` 通过。 +- `npm.cmd run check:android` 不再报关键缺项。 +- `npm.cmd run cap:sync` 成功。 +- 生成 Android Debug APK。 + +## 8. 第一阶段不做 + +- 多主播入驻。 +- 用户充值钻石。 +- 付费抽卡。 +- 用户间交易、卡牌转卖、提现、二级市场。 +- 多只宠物、宠物繁殖、战斗、开放地图。 +- 社区动态、评论、私信。 +- 复杂会员体系。 +- AI 能力接入。 + +## 9. 开发前必须确认的问题 + +| 问题 | 影响 | +| --- | --- | +| 宠物奖励到底发积分还是钻石?每日 80 上限约束哪种资产? | 影响宠物规则、资产流水、经济系统 | +| 商城兑换到底扣钻石还是积分?`diamondPrice` 字段是否修正? | 影响商城、订单、资产表设计 | +| 喂食是否和其他互动一样受每日次数和每日上限限制? | 影响宠物风控和刷资产风险 | +| 抽卡是否需要真实随机、种子审计、概率快照和保底继承? | 影响抽卡后端实现和合规 | +| 商品是否有每账号限购? | 影响商城订单规则 | +| 订单是否需要取消、退款、售后、物流单号、地址修改窗口? | 影响订单状态机和后台 | +| 后台发放是否需要审批流? | 影响后台权限、审计和运营流程 | +| 纪念日提醒是否需要真实推送?提醒时间是否按用户时区处理? | 影响通知服务和定时任务 | +| 碎片资产是否保留第一阶段可见入口? | 影响重复卡处理和后续兑换 | + +## 10. 关联文件 + +- 产品需求:`Product-Spec.md` +- 设计规范:`Design-Brief.md` +- 开发计划:`DEV-PLAN.md` +- 需求关系图:`Requirement-Breakdown.mmd` +- API 契约:`vtubermini/docs/api-contract.md` +- 可信规则:`vtubermini/docs/trusted-rules.md` +- Android 构建说明:`vtubermini/docs/android-build.md` diff --git a/Product-Spec-CHANGELOG.md b/Product-Spec-CHANGELOG.md new file mode 100644 index 0000000..9f6e521 --- /dev/null +++ b/Product-Spec-CHANGELOG.md @@ -0,0 +1,45 @@ +# 变更记录 + +## [v1.3] - 2026-06-08 + +### 修改 + +- 移除小游戏作为宠物系统的积分来源,积分改为完全由每日看小狗、喂食、抚摸、清洁、玩耍和全部照顾完成奖励产出。 +- 调整宠物每日积分模型:满勤仍为每日 80 积分,取消小游戏后通过照顾行为补足产出。 +- 调整宠物页闭环,底部操作从“小游戏入口”改为“今日积分进度”和“去抽卡/积分流水”。 + +## [v1.2] - 2026-06-08 + +### 新增 + +- 新增宠物玩法目标,明确宠物小屋承担每日回访、积分主产出、长期轻养成和抽卡引流。 +- 新增宠物长期养成设计,包括连续照顾、亲密等级、解锁奖励、宠物装饰、宠物回忆和周任务。 +- 新增宠物情绪反馈与页面引导规则,要求展示今日推荐动作、陪伴反馈、积分上限提示、全部照顾完成态和抽卡轻提示。 +- 新增宠物玩法边界,明确不做多宠物、战斗、繁殖、开放地图、排行榜、强惩罚衰减和宠物属性影响抽卡概率。 +- 新增宠物状态数据字段,包括亲密等级、今日登录看小狗、全部照顾完成状态、累计照顾天数、周任务状态和宠物回忆节点。 + +### 修改 + +- 调整宠物最小可玩循环,从单纯互动积分扩展为“状态展示、推荐照顾、互动反馈、完成奖励、抽卡引导”的闭环。 +- 调整每日积分产出模型,拆分轻度、中度和满勤用户节奏,并将满勤上限收敛为每日 80 积分。 +- 本版本曾规划小游戏积分档位,v1.3 已从当前宠物积分闭环中移除。 +- 明确宠物状态值只做表现和推荐,不做抽卡概率、积分倍率、商城价格或实物兑换资格加成。 + +## [v1.1] - 2026-06-02 + +### 新增 + +- 新增纪念日功能,用于记录用户入团时间、主播生日、主播开播周年纪念日和用户自定义纪念日。 +- 新增纪念日页面,从首页纪念日卡片和我的页入口进入,不额外占用底部 Tab。 +- 新增入团时间展示,系统计算已陪伴天数和下一次入团周年倒计时。 +- 新增系统纪念日配置,后台可配置主播生日、开播周年、主题图、祝福文案和关联活动入口。 +- 新增用户自定义纪念日,支持名称、日期、每年重复、提醒开关、提前提醒时间和备注。 +- 新增纪念日提醒规则,支持站内提醒或推送提醒。 +- 新增纪念日数据模型,覆盖系统纪念日、用户纪念日、提醒配置和活动关联。 + +### 修改 + +- 首页新增最近纪念日卡片,展示倒计时、入团陪伴天数和查看全部入口。 +- 我的页入口新增“纪念日”。 +- 后台工坊模块新增“纪念日配置”。 +- MVP 范围新增“纪念日记录与提醒”。 diff --git a/Product-Spec.md b/Product-Spec.md new file mode 100644 index 0000000..0305fb1 --- /dev/null +++ b/Product-Spec.md @@ -0,0 +1,553 @@ +# Product Spec: VTuber Mini + +## 产品概述 + +VTuber Mini 是一个面向单个虚拟主播粉丝的安卓周边 App 模板。用户通过照顾“主播动物化小狗形象”获得积分,再用积分抽取主播虚拟小卡;平台运营人员通过后台工坊发放钻石,用户在商城中使用钻石兑换实物周边奖品。 + +目标用户是该虚拟主播的粉丝,尤其是愿意每日互动、收集虚拟小卡、参与周边兑换活动的用户。产品核心价值是把粉丝日常互动、虚拟收藏和周边兑换整合成一个可持续运营的闭环,而不是只做一个静态周边商城。 + +第一版只服务一个主播,不做多主播入驻。后续为其他主播复制时,通过替换主播资料、主题视觉、小狗形象、卡牌素材、卡池配置、商城奖品和活动配置来复用模板。 + +## 应用场景 + +- 粉丝每天打开 App,进入宠物小屋照顾主播动物化小狗形象,完成喂食、抚摸、清洁、玩耍等操作获得积分,但每日积分有上限。 +- 粉丝积攒积分后进入抽卡页,查看当前卡池、概率和卡牌预览,消耗积分抽取主播虚拟小卡,并在卡册中收藏。 +- 粉丝抽到重复小卡时,系统自动将重复卡转化为碎片或少量积分,避免重复抽取完全无价值。 +- 运营人员在后台工坊给指定用户发放钻石,例如活动奖励、主播线下活动补偿、运营补贴或特殊任务奖励。 +- 粉丝进入钻石商城,使用后台发放的钻石兑换实物周边奖品,填写收货信息并查看兑换订单状态。 +- 粉丝进入纪念日页面,记录自己的入团时间,查看主播生日和开播周年纪念日,并添加个人自定义纪念日,系统在临近日期时提醒用户。 + +## 功能需求 + +### 核心功能 + +- 用户登录:用户打开 App 并登录账号,系统识别用户身份,加载用户积分、钻石、卡册、宠物互动记录和商城订单。 +- 首页:用户进入首页,系统展示主播主题视觉、当前积分和钻石余额、宠物入口、抽卡入口、商城入口、活动公告。 +- 宠物小屋:用户进入宠物小屋,系统展示主播动物化小狗形象、状态值、今日可获得积分、今日任务进度和互动按钮。 +- 宠物互动:用户点击喂食、抚摸、清洁、玩耍等操作,系统更新小狗状态、用户亲密度和每日任务进度。 +- 宠物日常照顾:用户通过每日看小狗、喂食、抚摸、清洁、玩耍和全部照顾完成奖励获得积分,并受每日积分上限控制。 +- 每日积分上限:用户通过宠物日常获得积分时,系统检查当日上限,超过上限后只给予亲密度或表现反馈,不再发放积分。 +- 积分钱包:用户查看积分余额和积分流水,系统展示积分获得、抽卡消耗、重复卡返还等记录。 +- 卡池列表:用户查看当前卡池,系统展示卡池名称、活动时间、单抽消耗、卡牌范围、稀有度概率、保底规则和抽取记录入口。 +- 积分抽卡:用户消耗积分进行单抽或十连抽,系统按当前卡池版本生成结果,扣减积分,发放小卡,并写入抽卡日志。 +- 卡册收藏:用户查看已拥有和未拥有的小卡,系统按系列、稀有度、获取时间展示卡牌,并支持查看卡面详情。 +- 重复卡处理:用户抽到已拥有小卡时,系统自动转化为碎片或少量积分,并写入资产流水。 +- 钻石钱包:用户查看钻石余额和钻石流水,系统展示后台发放、商城消耗、订单退款等记录。 +- 钻石商城:用户浏览实物奖品,系统展示奖品图片、名称、规格、库存、兑换所需钻石、发货周期和兑换规则。 +- 奖品兑换:用户选择奖品并提交收货信息,系统校验钻石余额和库存,扣减钻石,锁定库存,生成兑换订单。 +- 订单记录:用户查看兑换订单,系统展示订单状态、收货信息、物流信息、售后入口。 +- 纪念日:用户查看入团时间、主播生日、主播开播周年和自定义纪念日,系统展示倒计时、已陪伴天数和纪念日提醒。 +- 入团时间记录:用户首次设置或修改入团时间,系统保存日期并计算加入粉丝团后的累计天数。 +- 自定义纪念日:用户添加、编辑或删除个人纪念日,系统保存名称、日期、是否每年重复、提醒开关和备注。 +- 后台工坊:运营人员管理用户、积分、钻石、卡池、卡牌、商城奖品、兑换订单和运营公告。 +- 后台资产发放:运营人员给用户发放积分、钻石或卡牌,系统要求填写发放原因,记录操作人、时间、对象和流水。 +- 后台卡池配置:运营人员配置卡牌、稀有度、概率、保底、活动时间和上下架状态,系统保存卡池版本。 +- 后台商城管理:运营人员配置实物奖品、库存、兑换价格、上下架状态、订单发货状态和售后处理。 + +### 辅助功能 + +- 公告与活动:运营人员发布活动公告,用户在首页或消息中心查看。 +- 消息通知:系统通知用户抽卡结果、钻石到账、兑换成功、发货、订单异常和风控提醒。 +- 纪念日提醒:系统在主播生日、开播周年、用户入团周年和用户自定义纪念日前推送提醒,提醒提前天数由用户或后台配置决定。 +- 客服与申诉:用户提交资产异常、抽卡异常、兑换异常或风控申诉,后台可查看并处理。 +- 风控限制:系统根据设备、账号、IP、操作频率、同地址多号兑换等规则标记异常行为。 +- 用户协议与规则页:系统展示积分规则、钻石规则、抽卡概率、商城兑换规则、隐私政策和未成年人提示。 + +## 纪念日规则 + +### 纪念日类型 + +| 类型 | 来源 | 用户是否可编辑 | 用途 | +| --- | --- | --- | --- | +| 入团时间 | 用户设置 | 可编辑 | 记录用户加入粉丝团或开始关注主播的日期,展示陪伴天数和入团周年 | +| 主播生日 | 后台配置 | 不可编辑 | 展示主播生日倒计时,承接生日活动、卡池或商城专题 | +| 开播周年 | 后台配置 | 不可编辑 | 展示主播开播周年倒计时,承接周年活动、纪念卡池或周边兑换 | +| 自定义纪念日 | 用户创建 | 可编辑 | 记录用户个人设定的纪念日,例如第一次看直播、第一次抽到 SSR、线下见面日 | + +### 展示规则 + +- 纪念日页面按“即将到来”优先排序,同一天多个纪念日按系统纪念日优先展示。 +- 入团时间展示为“已陪伴 X 天”,入团周年展示下一次周年倒计时。 +- 主播生日和开播周年支持后台配置主题图、活动入口、祝福文案和关联卡池。 +- 自定义纪念日支持一次性日期和每年重复两种模式。 +- 纪念日不直接发放积分或钻石。若运营希望做纪念奖励,必须通过活动或后台资产发放配置,并写入资产流水。 + +### 提醒规则 + +- 用户可为入团周年和自定义纪念日开启或关闭提醒。 +- 主播生日和开播周年默认展示站内提醒,是否推送由后台配置。 +- 默认提醒时间为当天 09:00,可支持提前 1 天、3 天、7 天提醒。 +- 提醒内容不能夸大诱导消费,关联抽卡或商城活动时必须展示活动规则。 + +## 宠物玩法规则 + +### 宠物定位 + +小狗是主播的动物化形象,所有用户看到的是同一只主播主题小狗。系统不为每个用户生成独立宠物,只记录每个用户自己的互动进度、亲密度、积分产出、装饰解锁和任务完成状态。 + +### 玩法目标 + +宠物玩法承担四个目标: + +1. 给用户一个每日回访理由,让粉丝每天打开 App 时先看到主播动物化小狗的状态。 +2. 作为积分主产出来源,通过每日照顾稳定产出积分,并严格受每日上限控制。 +3. 承载轻量长期养成,通过亲密等级、连续照顾、装饰和回忆沉淀陪伴感。 +4. 将积分自然导向抽卡和卡册收藏,但不让宠物属性影响抽卡概率、积分倍率或商城兑换。 + +### 最小可玩循环 + +1. 用户进入宠物小屋。 +2. 系统展示小狗形象、今日状态、推荐照顾动作、今日任务和今日剩余可获得积分。 +3. 用户执行喂食、抚摸、清洁或玩耍。 +4. 系统更新状态值、亲密度、任务进度和即时反馈文案。 +5. 系统检查每日积分上限,结算积分或仅结算亲密度。 +6. 用户完成全部每日照顾后,系统发放一次完成奖励;若今日积分已满,只展示完成反馈,不再发积分。 +7. 用户积分接近或达到单抽成本时,宠物页提示进入抽卡页抽取主播虚拟小卡。 + +### 状态值 + +| 状态 | 用途 | MVP 处理 | +| --- | --- | --- | +| 饱腹值 | 反馈喂食行为 | 影响小狗表情和任务进度,不影响抽卡概率 | +| 心情值 | 反馈抚摸和玩耍 | 影响小狗表情和亲密度,不影响积分倍率 | +| 清洁值 | 反馈清洁行为 | 影响小狗视觉状态,不设置严重惩罚 | +| 活力值 | 反馈玩耍和互动频率 | 用于限制连续操作,避免无脑刷 | +| 亲密度 | 长期成长值 | 解锁装饰、称号、表情或语音 | + +状态值只做表现和推荐,不做强惩罚。用户未登录、断签或状态较低时,不扣积分、不扣亲密度、不锁抽卡。低状态只用于提示用户下一步该照顾什么,例如“今天优先:清洁”“它想被摸摸”“玩累了,适合轻轻陪它一会儿”。 + +### 积分产出 + +积分产出来自每日照顾,并必须有每日上限。宠物玩法建议把用户分为三档节奏: + +| 活跃度 | 用户行为 | 每日积分预期 | +| --- | --- | ---: | +| 轻度 | 登录看小狗并完成部分照顾 | 35-45 | +| 中度 | 完成大部分照顾 | 60-70 | +| 满勤 | 全部照顾和完成奖励 | 80 | + +第一版建议数值: + +| 行为 | 每日次数 | 单次积分 | 每日上限 | +| --- | ---: | ---: | ---: | +| 登录看小狗 | 1 | 10 | 10 | +| 喂食 | 2 | 10 | 20 | +| 抚摸 | 3 | 7 | 21 | +| 清洁 | 1 | 10 | 10 | +| 玩耍互动 | 2 | 7 | 14 | +| 完成全部每日照顾 | 1 | 5 | 5 | + +MVP 每日总积分上限建议为 80 积分。若单抽成本设为 100 积分,用户约 1-2 天可完成一次抽卡。具体数值后续可在运营配置中调整。 + +重复提交、网络重放或疑似脚本操作不重复结算积分。达到每日上限后,互动只结算亲密度和反馈,不再发放积分。 + +### 长期养成 + +长期养成只提供陪伴、收藏和展示价值,不提供抽卡概率、积分倍率或商城折扣。 + +| 养成层 | 设计 | MVP 处理 | +| --- | --- | --- | +| 连续照顾 | 记录连续照顾天数和累计照顾天数 | 展示连续 X 天;断签不惩罚,只重置连续展示 | +| 亲密等级 | 亲密度累计后提升 Lv.1-Lv.30 | MVP 可先做 Lv.1-Lv.10,后续扩展 | +| 解锁奖励 | 解锁称号、表情、台词、语音或小屋视觉变化 | 第一版优先做称号、表情和台词 | +| 宠物装饰 | 头饰、颈饰、小窝背景三类槽位 | 二期实现,来源为亲密等级、活动或碎片兑换 | +| 宠物回忆 | 记录第一次喂食、连续 7 天、第一次抽到 SSR、主播生日当天来访等节点 | 二期实现,与纪念日系统联动 | +| 周任务 | 本周照顾 5 天、完成 10 次互动、完成 3 次全部照顾等 | 二期实现,奖励以装饰、称号和少量积分为主 | + +### 情绪反馈与页面引导 + +- 宠物页主视觉必须先展示小狗状态和陪伴反馈,再展示任务收益。 +- 系统根据最低状态值生成今日推荐动作,例如“今天优先:喂食”“它想被摸摸”“毛毛需要整理一下”。 +- 互动成功反馈要同时包含情绪反馈和积分反馈,例如“小狗叼着小点心蹭了蹭你,获得 10 积分”。 +- 达到每日积分上限后,继续互动只提升亲密度和表现反馈,文案应明确“今天积分已满,继续陪它仍会增加亲密度”。 +- 四项每日照顾全部完成后,今日任务区展示完成态,例如“今日照顾完成,明天它还会等你”。 +- 当积分接近或达到单抽成本时,宠物页展示进入抽卡页的轻提示,但不能用倒计时或强刺激文案诱导消费。 + +### 玩法边界 + +- 不做多只宠物、多主播宠物、宠物繁殖、战斗、开放地图。 +- 不让宠物属性影响抽卡概率、积分倍率、商城价格或实物兑换资格。 +- 不做用户间宠物互动、排行榜、留言和互访,避免引入审核、举报和隐私成本。 +- 不做强惩罚衰减,不因为用户断签或未照顾而扣除资产、亲密度或已有奖励。 +- 重复卡长期建议转为碎片,碎片可用于兑换指定低稀有卡、宠物装饰、表情或称号;碎片不得兑换实物、不得交易、不得提现。 + +## 抽卡与卡册规则 + +### 卡池 + +- 卡池必须展示卡牌范围、稀有度、概率、活动时间、消耗积分、保底规则和重复卡处理规则。 +- 每次抽卡必须绑定卡池版本。概率或卡牌范围变更时,不覆盖旧版本,创建新版本。 +- 卡池不允许设置空奖。 +- MVP 支持单抽;十连抽可作为第一版增强项。 + +### 稀有度 + +默认稀有度建议: + +| 稀有度 | 说明 | +| --- | --- | +| N | 常规小卡 | +| R | 特别表情或日常服装小卡 | +| SR | 活动主题小卡 | +| SSR | 限定视觉或纪念小卡 | + +### 重复卡 + +重复卡默认转化为碎片。碎片可用于后续兑换指定小卡、宠物装饰或少量积分。MVP 可先只做“重复卡转积分”,但要保留碎片字段,避免后续迁移困难。 + +## 钻石与商城规则 + +### 钻石定位 + +钻石是后台工坊发放的运营资产,MVP 不开放用户充值,不支持用户间转赠、交易、提现或兑换现金。钻石主要用于商城兑换实物周边奖品。 + +### 发放规则 + +后台发放钻石必须记录: + +- 发放用户 +- 发放数量 +- 发放原因 +- 操作人员 +- 操作时间 +- 审批状态 +- 资产流水编号 + +### 商城兑换 + +- 商城商品必须展示图片、名称、规格、库存、钻石价格、发货周期、售后规则。 +- 用户兑换时,系统校验库存和钻石余额。 +- 兑换成功后,系统扣减钻石、锁定库存、生成订单。 +- 后台可更新订单状态:待发货、已发货、已完成、售后中、已取消、已退款。 + +## UI 布局 + +### App 整体结构 + +移动端底部 Tab 布局,建议 5 个主入口: + +1. 首页 +2. 宠物 +3. 抽卡 +4. 商城 +5. 我的 + +纪念日不单独占底部 Tab,第一版从首页纪念日卡片和我的页入口进入,避免主导航过重。 + +### 首页 + +- 顶部展示主播主题横幅、主播头像或标志、当前活动入口。 +- 资产区展示积分余额、钻石余额。 +- 快捷入口展示宠物小屋、当前卡池、商城新品。 +- 纪念日卡片展示最近一个纪念日、倒计时、入团陪伴天数和查看全部入口。 +- 公告区展示运营公告、活动规则、兑换提醒。 + +### 宠物页 + +- 主视觉区域展示小狗形象和当前状态,不使用复杂开放地图。 +- 状态区展示饱腹、心情、清洁、活力、亲密度。 +- 操作区提供喂食、抚摸、清洁、玩耍按钮。 +- 今日任务区展示任务进度、可领取积分和今日剩余积分上限。 +- 底部操作区展示今日积分进度,并在积分接近单抽成本时引导进入抽卡页。 + +### 抽卡页 + +- 顶部展示当前卡池视觉、活动时间和剩余时间。 +- 中部展示卡牌预览、稀有度概率、保底规则入口。 +- 底部固定抽卡按钮:单抽、十连抽。 +- 结果弹窗展示抽到的小卡、稀有度、是否重复、重复转化结果。 + +### 卡册页 + +- 按系列、稀有度、已拥有/未拥有筛选。 +- 卡片网格展示小卡缩略图、稀有度、拥有状态。 +- 点击卡片进入详情页,展示大图、编号、获得时间、来源。 + +### 商城页 + +- 商品列表展示实物奖品图片、名称、库存、钻石价格。 +- 商品详情展示规格、兑换规则、发货周期、售后说明。 +- 兑换确认页展示收货地址、钻石扣减、库存锁定提示。 + +### 我的页 + +- 展示账号信息、积分余额、钻石余额。 +- 入口包括:积分流水、钻石流水、抽卡记录、卡册、兑换订单、纪念日、地址管理、客服与申诉、规则与协议。 + +### 纪念日页 + +- 顶部展示入团时间和已陪伴天数,未设置时展示设置入口。 +- 系统纪念日区展示主播生日、开播周年、倒计时、活动入口和后台配置的主题图。 +- 自定义纪念日区展示用户创建的纪念日列表,支持添加、编辑、删除。 +- 添加纪念日表单包含:名称、日期、是否每年重复、提醒开关、提前提醒时间、备注。 +- 空状态提示用户添加第一次看直播、第一次抽卡、第一次参加线下活动等纪念日。 + +### 后台工坊 + +后台优先做 Web 管理端,不放入用户 App。模块包括: + +- 用户管理 +- 资产管理 +- 钻石发放 +- 卡牌管理 +- 卡池管理 +- 抽卡日志 +- 商城商品 +- 兑换订单 +- 风控记录 +- 公告管理 +- 纪念日配置 +- 操作审计 + +## 用户使用流程 + +### 每日互动流程 + +1. 用户登录 App。 +2. 用户进入首页查看积分、钻石和活动。 +3. 用户进入宠物页照顾小狗。 +4. 用户完成每日照顾任务。 +5. 系统按每日看小狗、喂食、抚摸、清洁、玩耍和全部照顾完成奖励结算积分。 +6. 用户领取积分,系统写入积分流水。 +7. 用户进入抽卡页消耗积分抽卡。 + +### 积分抽卡流程 + +1. 用户进入抽卡页。 +2. 用户查看卡池、概率、保底和消耗。 +3. 用户点击单抽或十连抽。 +4. 系统校验积分余额和卡池状态。 +5. 系统按卡池版本生成抽卡结果。 +6. 系统扣减积分并发放小卡。 +7. 系统展示结果并写入抽卡日志。 +8. 用户进入卡册查看新卡。 + +### 钻石兑换流程 + +1. 后台运营人员给用户发放钻石。 +2. 用户收到钻石到账通知。 +3. 用户进入商城选择奖品。 +4. 用户提交兑换并填写收货地址。 +5. 系统扣减钻石、锁定库存、生成订单。 +6. 后台运营人员处理发货。 +7. 用户查看物流和订单状态。 + +### 纪念日使用流程 + +1. 用户进入首页,看到最近纪念日卡片。 +2. 用户点击纪念日卡片,进入纪念日页。 +3. 用户设置入团时间,系统计算已陪伴天数和下一次入团周年。 +4. 用户查看主播生日和开播周年,系统展示倒计时和关联活动入口。 +5. 用户添加自定义纪念日,设置名称、日期、重复方式和提醒。 +6. 到达提醒时间后,系统发送站内提醒或推送提醒。 + +### 后台运营流程 + +1. 运营人员登录后台。 +2. 配置卡牌和卡池。 +3. 配置商城奖品和库存。 +4. 审批并发放钻石。 +5. 查看抽卡日志和资产流水。 +6. 处理兑换订单和用户申诉。 + +## AI 能力需求 + +| 能力类型 | 用途说明 | 应用位置 | +| --- | --- | --- | +| 暂不需要 | MVP 不依赖 AI 能力,优先保证宠物互动、积分、抽卡、商城和后台资产系统稳定 | 无 | + +后续可选 AI 增强: + +- AI 生成小卡文案、卡面标题或活动公告草稿。 +- AI 客服辅助回答规则、订单和资产问题。 +- AI 风控辅助标记异常积分获取行为。 + +这些不进入 MVP,避免第一版复杂度失控。 + +## 技术方向 + +| 维度 | 选择 | 理由 | +| --- | --- | --- | +| 产品类型 | Mobile App,第一阶段 Android APK | 用户明确要求先在安卓平台使用,输出物为 APK | +| 推荐前端技术 | React + TypeScript + Vite + Capacitor | 当前本机已有 Node/npm,可先快速搭建前端和移动壳;最终通过 Android 工具链打 APK | +| 后台工坊 | Web 管理端 | 运营配置和订单处理更适合桌面浏览器后台 | +| 后端服务 | 云端 API + 数据库 | 积分、钻石、抽卡、商城、订单和风控都不能只放本地 | +| 数据存储 | 云端数据库为主,本地缓存为辅 | 资产类数据必须服务端可信,App 本地只缓存展示数据 | +| APK 构建 | Capacitor Android 工程 + Android SDK/JDK | 当前环境缺 Android SDK/JDK,后续打包前必须补齐 | + +## 技术说明 + +### 服务端必须负责的逻辑 + +- 登录鉴权 +- 积分发放与扣减 +- 钻石发放与扣减 +- 抽卡随机结果生成 +- 卡池版本锁定 +- 抽卡日志记录 +- 商城库存锁定 +- 订单生成 +- 风控判断 +- 后台操作审计 +- 系统纪念日配置 +- 纪念日提醒任务调度 + +前端不能自行决定积分、钻石、抽卡结果或订单状态。 + +### 本地 App 可以负责的逻辑 + +- 页面展示 +- 动画反馈 +- 宠物状态表现 +- 临时 UI 状态 +- 离线时的只读缓存 +- 纪念日倒计时展示 + +宠物积分最终结算必须由服务端校验每日次数、每日上限和重复提交状态。 + +### 当前环境约束 + +当前工作区为空项目。本机已检测到 Node.js 和 npm,但缺少 Java/JDK、Android SDK、Gradle、Flutter 等 APK 构建工具。可以先搭建 Web/Capacitor 项目骨架;要真正输出 APK,需要先安装并配置 Android 构建环境。 + +## 数据需求 + +### 用户资产 + +| 数据 | 说明 | +| --- | --- | +| user_id | 用户标识 | +| points_balance | 积分余额 | +| diamonds_balance | 钻石余额 | +| points_log | 积分获得和消耗流水 | +| diamonds_log | 钻石获得和消耗流水 | +| owned_cards | 用户拥有的小卡 | +| gacha_logs | 抽卡记录 | +| orders | 商城兑换订单 | + +### 宠物状态 + +| 数据 | 说明 | +| --- | --- | +| pet_profile_id | 主播动物化小狗形象配置 | +| intimacy | 用户与小狗的亲密度 | +| intimacy_level | 亲密等级,用于解锁称号、表情、台词、语音或装饰 | +| hunger | 饱腹状态 | +| mood | 心情状态 | +| cleanliness | 清洁状态 | +| energy | 活力状态 | +| daily_points_earned | 今日宠物玩法已获得积分 | +| daily_points_cap | 今日宠物玩法积分上限 | +| daily_action_counts | 今日各互动行为次数 | +| daily_login_seen | 今日是否已进入宠物页看小狗 | +| daily_care_completed | 今日全部照顾任务是否已完成 | +| daily_reset_date | 当前日常周期 | +| streak_days | 连续照顾天数 | +| total_care_days | 累计照顾天数 | +| weekly_task_state | 本周宠物任务进度 | +| owned_pet_items | 已解锁宠物装饰 | +| equipped_pet_items | 当前装备宠物装饰 | +| unlocked_pet_memories | 已解锁的宠物回忆节点 | + +### 卡池 + +| 数据 | 说明 | +| --- | --- | +| pool_id | 卡池标识 | +| pool_version | 卡池版本 | +| card_list | 卡池内卡牌 | +| rarity_weights | 稀有度概率 | +| start_at / end_at | 活动时间 | +| cost_points | 单抽积分消耗 | +| pity_rule | 保底规则 | +| duplicate_rule | 重复卡处理规则 | + +### 商城 + +| 数据 | 说明 | +| --- | --- | +| sku_id | 商品标识 | +| name | 商品名称 | +| image | 商品图片 | +| specs | 商品规格 | +| diamond_price | 钻石兑换价 | +| stock | 库存 | +| shipping_rule | 发货规则 | +| after_sales_rule | 售后规则 | + +### 纪念日 + +| 数据 | 说明 | +| --- | --- | +| anniversary_id | 纪念日标识 | +| user_id | 用户标识;系统纪念日可为空 | +| type | 类型:入团时间、主播生日、开播周年、自定义 | +| title | 纪念日名称 | +| date | 纪念日日期 | +| repeat_yearly | 是否每年重复 | +| reminder_enabled | 是否开启提醒 | +| reminder_offsets | 提前提醒时间,例如当天、提前 1 天、提前 3 天 | +| note | 用户备注 | +| theme_image | 系统纪念日主题图 | +| linked_activity_id | 关联活动标识 | +| created_at / updated_at | 创建和更新时间 | + +## 合规与风控要求 + +- 抽卡页必须展示概率、卡牌范围、保底规则、重复卡处理规则。 +- 抽卡概率调整必须生成新卡池版本,不能覆盖历史配置。 +- 抽卡日志、资产流水、后台操作日志必须可追踪。 +- 不设置空奖。 +- MVP 不开放钻石充值。 +- MVP 不开放用户间卡牌交易、钻石转赠、提现或二级市场。 +- 未成年人规则需要预留,正式上线前必须明确实名、防沉迷、抽卡和商城限制策略。 +- 宠物积分必须有每日上限、每日次数限制和重复提交防护。 +- 商城兑换必须有库存锁定、订单记录、物流状态和售后入口。 +- 后台发放资产必须记录原因、操作人和审批状态。 +- 纪念日提醒若关联抽卡或商城活动,必须清楚展示活动规则、概率或兑换条件,不用倒计时制造强诱导消费。 + +## MVP 范围 + +### 第一阶段必须完成 + +- 安卓 App 基础壳 +- 登录与用户身份 +- 首页 +- 宠物小屋 +- 每日积分上限 +- 积分钱包与流水 +- 卡池列表 +- 积分抽卡 +- 卡册 +- 钻石钱包 +- 钻石商城 +- 兑换订单 +- 纪念日记录与提醒 +- 后台工坊基础管理 +- 卡池概率公示 +- 抽卡日志 +- 资产流水 + +### 第一阶段暂不做 + +- 多主播入驻 +- 用户充值钻石 +- 付费抽卡 +- 用户间交易 +- 卡牌转卖或提现 +- 多只宠物 +- 宠物繁殖、战斗、开放地图 +- 社区动态、评论、私信 +- 复杂会员体系 + +## 待确认问题 + +| 问题 | 默认建议 | +| --- | --- | +| 登录方式 | 第一版用手机号验证码或第三方登录二选一 | +| 是否需要实名 | 若涉及未成年人或高价值兑换,建议预留实名能力 | +| 单抽积分成本 | 暂定 100 积分 | +| 每日积分上限 | 暂定 80 积分 | +| 钻石是否永不过期 | 建议 MVP 永不过期,后续再做活动钻石 | +| 实物奖品价值上限 | 建议第一版只放低价值周边,降低合规和履约风险 | +| 后端技术栈 | 待开发计划阶段确定 | +| 纪念日是否发奖励 | 默认不直接发奖励;如需发放,必须走活动配置或后台资产发放流水 | diff --git a/Requirement-Breakdown.mmd b/Requirement-Breakdown.mmd new file mode 100644 index 0000000..3a1900e --- /dev/null +++ b/Requirement-Breakdown.mmd @@ -0,0 +1,131 @@ +flowchart TD + %% VTuber Mini requirements breakdown for product/dev communication. + + user["粉丝用户"] + operator["运营人员"] + + subgraph frontend["前台 App 需求"] + shell["App 壳与登录
Android 优先 / React + Capacitor
5 个底部 Tab"] + home["首页
主播主题视觉 / 资产摘要 / 公告
宠物、抽卡、商城、纪念日入口"] + pet["宠物小屋
每日看小狗 / 喂食 / 抚摸 / 清洁 / 玩耍
每日积分上限 80 / 亲密等级 / 连续照顾"] + gacha["抽卡与卡册
卡池版本 / 概率公示 / 单抽 / 十连
保底 / 重复卡转换 / 抽卡日志 / 卡册"] + shop["钻石商城与订单
商品列表 / 商品详情 / 收货信息
库存校验 / 兑换订单 / 物流与售后状态"] + anniversary["纪念日
入团时间 / 主播生日 / 开播周年
自定义纪念日 / 提醒设置"] + my["我的与支持
资产流水 / 抽卡记录 / 订单入口
规则协议 / 通知中心 / 客服申诉"] + end + + subgraph admin["后台工坊需求"] + adminUser["用户与资产管理
积分、钻石发放必须填原因"] + adminPool["卡牌与卡池管理
卡池版本不可覆盖历史"] + adminShop["商城与订单管理
商品库存 / 上下架 / 发货状态"] + adminAnniv["纪念日配置
主播生日 / 开播周年 / 主题图 / 活动入口"] + audit["审计日志
操作人 / 时间 / 对象 / 原因 / 业务编号"] + end + + subgraph backend["服务端与数据需求"] + api["API 契约
统一 ServiceResponse
错误码与可读错误信息"] + asset["资产可信结算
points / diamonds / shards
余额以后端为准,所有变更写流水"] + petRules["宠物规则服务
每日次数 / 每日上限 / 完成奖励
亲密等级、连续天数服务端计算"] + gachaRules["抽卡规则服务
概率配置 / 保底 / 结果生成
扣款、发卡、日志同事务"] + shopRules["商城兑换服务
余额 / 库存 / 地址校验
扣款、锁库存、生成订单同事务"] + annivRules["纪念日服务
本人数据权限 / 提醒任务 / 系统纪念日配置"] + risk["风控与合规
幂等 / 防重复提交 / PII 脱敏
概率公示 / 未成年人提示 / 审计"] + end + + subgraph delivery["构建与交付"] + webBuild["Web 构建
npm.cmd run lint
npm.cmd run build"] + android["Android 打包
Capacitor Android 工程
需 JDK / Android SDK / adb / Gradle"] + apk["Debug APK
环境补齐后 cap sync 并构建"] + end + + subgraph agents["建议并行开发分工"] + agentA["Agent A 前台基础
App 壳 / 首页 / 我的 / 规则 / 通知"] + agentB["Agent B 宠物积分
宠物小屋 / 每日任务 / 积分流水"] + agentC["Agent C 抽卡收藏
卡池 / 抽卡 / 卡册 / 日志"] + agentD["Agent D 商城订单
钻石钱包 / 商城 / 订单 / 库存"] + agentE["Agent E 纪念日
入团时间 / 系统纪念日 / 自定义提醒"] + agentF["Agent F 后台服务
后台工坊 / API 契约 / 审计 / 风控"] + agentG["Agent G 构建交付
Capacitor / Android 环境 / APK"] + end + + subgraph openQuestions["开发前必须确认"] + q1["宠物奖励到底发积分还是钻石?
每日 80 上限约束哪种资产?"] + q2["商城兑换到底扣钻石还是积分?
diamondPrice 字段是否需要修正?"] + q3["喂食是否和其他互动一样受每日次数与上限限制?"] + q4["抽卡是否需要真实随机、种子审计、概率快照与保底继承?"] + q5["订单是否需要取消、退款、售后、物流单号、地址修改窗口?"] + q6["后台发放是否需要审批流?
目前审计状态字段已预留 pending/approved/rejected"] + end + + user --> shell + shell --> home + shell --> pet + shell --> gacha + shell --> shop + shell --> my + home --> anniversary + my --> anniversary + + operator --> adminUser + operator --> adminPool + operator --> adminShop + operator --> adminAnniv + adminUser --> audit + adminPool --> audit + adminShop --> audit + adminAnniv --> audit + + shell --> api + home --> api + pet --> petRules + gacha --> gachaRules + shop --> shopRules + anniversary --> annivRules + my --> api + + adminUser --> asset + petRules --> asset + gachaRules --> asset + shopRules --> asset + adminPool --> gachaRules + adminShop --> shopRules + adminAnniv --> annivRules + + api --> asset + asset --> risk + petRules --> risk + gachaRules --> risk + shopRules --> risk + annivRules --> risk + audit --> risk + + frontend --> webBuild + admin --> webBuild + backend --> webBuild + webBuild --> android + android --> apk + + agentA -.负责.-> shell + agentA -.负责.-> home + agentA -.负责.-> my + agentB -.负责.-> pet + agentB -.协作.-> petRules + agentC -.负责.-> gacha + agentC -.协作.-> gachaRules + agentD -.负责.-> shop + agentD -.协作.-> shopRules + agentE -.负责.-> anniversary + agentE -.协作.-> annivRules + agentF -.负责.-> admin + agentF -.负责.-> backend + agentG -.负责.-> delivery + + q1 -.阻塞.-> petRules + q1 -.影响.-> asset + q2 -.阻塞.-> shopRules + q2 -.影响.-> asset + q3 -.阻塞.-> petRules + q4 -.影响.-> gachaRules + q5 -.影响.-> shopRules + q6 -.影响.-> adminUser + q6 -.影响.-> audit diff --git a/UX-UE-Function-Flow.mmd b/UX-UE-Function-Flow.mmd new file mode 100644 index 0000000..a90f594 --- /dev/null +++ b/UX-UE-Function-Flow.mmd @@ -0,0 +1,92 @@ +flowchart TD + %% VTuber Mini UX/UE concise functional relationship and user flow. + + start(["打开 App"]) + login["登录 / 进入账号"] + profile{"是否已设置
生日和入团时间?"} + setup["资料补全弹窗
生日 / 入团时间"] + home["首页
主播氛围 + 资产摘要 + 核心入口"] + + pet["宠物小屋
每日照顾小狗"] + petReward["获得积分
每日上限 80"] + gacha["抽卡页
查看卡池 / 概率 / 保底"] + draw["单抽 / 十连"] + album["卡册
查看已拥有 / 未拥有小卡"] + + shop["钻石商城
浏览周边商品"] + product["商品详情
库存 / 价格 / 规则"] + exchange["提交兑换
收货信息"] + orders["订单列表
发货 / 售后状态"] + + anniversary["纪念日
入团日 / 主播生日 / 开播周年"] + customAnniv["自定义纪念日
新增 / 编辑 / 提醒"] + + my["我的
资产 / 记录 / 设置入口"] + ledgers["资产流水
积分 / 钻石"] + logs["抽卡记录"] + rules["规则与协议
概率 / 兑换 / 隐私 / 未成年人提示"] + support["客服申诉
资产 / 抽卡 / 兑换 / 风控"] + notices["通知中心
到账 / 订单 / 纪念日提醒"] + + admin["后台工坊
运营配置与处理"] + adminAssets["资产发放
积分 / 钻石 / 卡牌"] + adminPool["卡池配置
卡牌 / 概率 / 保底 / 版本"] + adminShop["商城管理
商品 / 库存 / 订单"] + adminAnniv["纪念日配置
生日 / 周年 / 活动入口"] + audit["审计日志
操作人 / 原因 / 业务编号"] + + start --> login --> profile + profile -- 否 --> setup --> home + profile -- 是 --> home + + home -->|每日回访主线| pet + pet --> petReward + petReward -->|积分足够| gacha + home -->|直接抽卡| gacha + gacha --> draw --> album + album -->|继续收集| gacha + + home -->|兑换周边| shop + shop --> product --> exchange --> orders + my --> orders + + home -->|陪伴记录| anniversary + anniversary --> customAnniv + my --> anniversary + + home --> my + my --> ledgers + my --> logs + my --> rules + my --> support + my --> notices + + notices -->|查看详情| orders + notices -->|查看提醒| anniversary + logs -->|查看卡牌| album + ledgers -->|资产变动来源| pet + ledgers -->|抽卡消耗| gacha + ledgers -->|兑换消耗| shop + + admin --> adminAssets --> audit + admin --> adminPool --> audit + admin --> adminShop --> audit + admin --> adminAnniv --> audit + + adminAssets -.发放钻石 / 积分.-> ledgers + adminPool -.驱动卡池展示.-> gacha + adminShop -.驱动商品和订单.-> shop + adminShop -.更新订单状态.-> orders + adminAnniv -.驱动系统纪念日.-> anniversary + + classDef entry fill:#EAF4FF,stroke:#4B8AC9,color:#102033 + classDef core fill:#FFF7E8,stroke:#D9952E,color:#2A1B00 + classDef record fill:#F0F7EF,stroke:#6FA66A,color:#102810 + classDef admin fill:#F7F0FF,stroke:#8E68C7,color:#1E1233 + classDef risk fill:#FFF0F0,stroke:#C76A6A,color:#331212 + + class start,login,home entry + class pet,petReward,gacha,draw,shop,product,exchange,anniversary,customAnniv core + class album,my,ledgers,logs,rules,support,notices,orders record + class admin,adminAssets,adminPool,adminShop,adminAnniv admin + class audit risk diff --git a/assets/1777526300869.png b/assets/1777526300869.png new file mode 100644 index 0000000..5f44a7d --- /dev/null +++ b/assets/1777526300869.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d71c012e7f15fa1b715ac8202873a9aabb42c4c40cfbd1dad177395d89cc411d +size 2627955 diff --git a/assets/20260601-151641.png b/assets/20260601-151641.png new file mode 100644 index 0000000..b4c08e0 --- /dev/null +++ b/assets/20260601-151641.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d12b32cd55f3c798e110e872df036fd787ff871ac6d12e1fe5e32e271029d8db +size 4057494 diff --git a/assets/20260601-151658.mp4 b/assets/20260601-151658.mp4 new file mode 100644 index 0000000..4bfbe0a --- /dev/null +++ b/assets/20260601-151658.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e833e75c76b3419113eee2fe4e95a1cfffe501b0398c9935519e8e78dc74403d +size 1326994 diff --git a/assets/20260601-151705.mp4 b/assets/20260601-151705.mp4 new file mode 100644 index 0000000..13b24dd --- /dev/null +++ b/assets/20260601-151705.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63d60c21a177f3140a7249a8a2b2b6da68563897ab7999ad10a4895fccbc6167 +size 1588763 diff --git a/assets/二级页面-纪念日页面-弹窗.png b/assets/二级页面-纪念日页面-弹窗.png new file mode 100644 index 0000000..669fd90 --- /dev/null +++ b/assets/二级页面-纪念日页面-弹窗.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd9400df569c4e73997ba969ed1dabe59ae0b8441edecc5174f400ef1f4d0668 +size 610857 diff --git a/assets/二级页面-纪念日页面-隔断.png b/assets/二级页面-纪念日页面-隔断.png new file mode 100644 index 0000000..dc2a861 --- /dev/null +++ b/assets/二级页面-纪念日页面-隔断.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02c3e66b8ea1386f591a78864b3af8e21f7d7d04d750ef042a85eddf68af2a72 +size 16840 diff --git a/assets/二级页面-纪念日页面.png b/assets/二级页面-纪念日页面.png new file mode 100644 index 0000000..4b7f27f --- /dev/null +++ b/assets/二级页面-纪念日页面.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f6740dcfe4c6a483023e44bb2da58788d33a602a153c75b1d5f472538e1c128 +size 1102092 diff --git a/assets/日常首页-夜晚.mp4 b/assets/日常首页-夜晚.mp4 new file mode 100644 index 0000000..f78da8f --- /dev/null +++ b/assets/日常首页-夜晚.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a2da89e9aff04b7bbc5edfc18fb436ee5a149f86f84ba8b1b6830b92a1c8110 +size 7302829 diff --git a/assets/日常首页-白天.mp4 b/assets/日常首页-白天.mp4 new file mode 100644 index 0000000..d45d155 --- /dev/null +++ b/assets/日常首页-白天.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2b3c81cd201531418bf02b160b431be5c6225aef06127d186c7a07a440278da +size 7127175 diff --git a/design/Pencil-MVP-Prompt.md b/design/Pencil-MVP-Prompt.md new file mode 100644 index 0000000..9805df6 --- /dev/null +++ b/design/Pencil-MVP-Prompt.md @@ -0,0 +1,70 @@ +# Pencil MVP Design Prompt + +Create a Pencil `.pen` design file for the VTuber Mini Android app MVP. + +Use the attached `Product-Spec.md` and `Design-Brief.md` as the source of truth. Use the attached visual reference images from `C:/Users/59719/Desktop/assets` as style references. + +## Output Scope + +Create a well-organized Pencil canvas with these sections: + +1. Design System + - Color tokens from `Design-Brief.md` + - Typography examples + - Core components: glass card, primary button, secondary button, asset pill, tab bar item, progress bar, badge, input, modal container, card thumbnail, product card, anniversary card + +2. Mobile App Key Screens + - Login + - Home + - Pet Room + - Mini Game + - Gacha Pool + - Gacha Result Modal + - Card Album + - Diamond Shop + - Product Detail + - Order List + - Anniversary Page + - Anniversary Edit Modal + - My Page + +3. Admin Workshop Skeleton + - Admin dashboard + - User/asset management + - Card pool management + - Shop/order management + - Anniversary configuration + +## Visual Direction + +Follow the user-provided assets: + +- Home: deep blue immersive scene, glowing icons, character focus, particles, flower petals. +- Secondary pages: moon-white and crystal-blue glass UI, soft rose decoration, readable content containers. +- Components: glassmorphism, blue glow, rose-gold accents for VIP/SSR/anniversary. +- Keep text and key numbers readable. Do not place important text directly on low-contrast transparent areas. +- Use realistic Chinese content, not Lorem ipsum. + +## Required Screen Size + +Use Android mobile frames around 390 x 844 or 393 x 852 for app screens. +Use desktop frames around 1440 x 900 for admin workshop screens. + +## Important Constraints + +- Do not create payment/recharge UI. +- Do not create user trading/withdrawal UI. +- Gacha screens must show probability/rules entry, cost, pity progress, and duplicate card handling. +- Shop screens must show stock, diamond price, shipping rule, and after-sales/rules entry. +- Anniversary screens must distinguish system anniversaries from user custom anniversaries. +- Pet and mini-game screens must show daily points cap and remaining attempts. + +## Deliverable Quality + +The file should be useful for design review and later implementation: + +- Clear frame names. +- Reusable component section. +- Page frames aligned in a grid. +- Consistent colors and typography. +- Include key empty/error/loading state callouts where space allows. diff --git a/design/exports/jHoSN.png b/design/exports/jHoSN.png new file mode 100644 index 0000000..8680004 --- /dev/null +++ b/design/exports/jHoSN.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6134dc3ed418e69b5fd11d9d24279e34b37e243e532a195f8026f7996d1a600c +size 45917 diff --git a/design/pencil-smoke-gpt55.pen b/design/pencil-smoke-gpt55.pen new file mode 100644 index 0000000..5b4dde7 --- /dev/null +++ b/design/pencil-smoke-gpt55.pen @@ -0,0 +1,215 @@ +{ + "version": "2.13", + "children": [ + { + "type": "frame", + "id": "jHoSN", + "x": 0, + "y": 0, + "name": "VTuber Mini Mobile", + "clip": true, + "width": 390, + "height": 844, + "fill": "#F6FAFFFF", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "RJmCa", + "name": "Status Bar", + "width": "fill_container", + "height": 62, + "fill": "#00000000", + "padding": [ + 0, + 20 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "sUAyp", + "name": "Status Time", + "fill": "#1A2740", + "content": "9:41", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600" + }, + { + "type": "frame", + "id": "KPuQD", + "name": "Status Indicators", + "fill": "#00000000", + "gap": 7, + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "g8wUk", + "name": "Signal Icon", + "width": 17, + "height": 17, + "icon": "signal", + "library": "lucide", + "fill": "#1A2740" + }, + { + "type": "icon", + "id": "Tqh0U", + "name": "Wifi Icon", + "width": 17, + "height": 17, + "icon": "wifi", + "library": "lucide", + "fill": "#1A2740" + }, + { + "type": "icon", + "id": "DkWWF", + "name": "Battery Icon", + "width": 21, + "height": 21, + "icon": "battery-full", + "library": "lucide", + "fill": "#1A2740" + } + ] + } + ] + }, + { + "type": "frame", + "id": "i6fYMb", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 36, + "padding": [ + 0, + 24, + 80, + 24 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "PM0BK", + "name": "Title Group", + "width": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 10, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "rPCkb", + "name": "Title", + "fill": "#10203A", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini", + "lineHeight": 1.05, + "textAlign": "center", + "fontFamily": "Geist", + "fontSize": 42, + "fontWeight": "700" + }, + { + "type": "text", + "id": "kr6CO", + "name": "Subtitle", + "fill": "#5B6B82", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Live avatar controls", + "lineHeight": 1.35, + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "LUNCi", + "name": "Blue Glass Button", + "width": 236, + "height": 58, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#78B7FFFF", + "position": 0 + }, + { + "color": "#246BFFDD", + "position": 1 + } + ] + }, + "cornerRadius": 29, + "stroke": "#FFFFFF99", + "strokeWidth": 1, + "effect": [ + { + "type": "background_blur", + "radius": 18 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#246BFF3D", + "offset": { + "x": 0, + "y": 14 + }, + "blur": 28 + } + ], + "gap": 10, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "WNNtt", + "name": "Button Sparkle Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#FFFFFF" + }, + { + "type": "text", + "id": "JPZ8u", + "name": "Button Label", + "fill": "#FFFFFF", + "content": "Start", + "fontFamily": "Inter", + "fontSize": 17, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/design/pencil-smoke-gpt55.png b/design/pencil-smoke-gpt55.png new file mode 100644 index 0000000..67419fa --- /dev/null +++ b/design/pencil-smoke-gpt55.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfcf01f7cb8d3237feca2c3e522b6b672c473f8af8c442bd394568ad6a6ba71 +size 8393 diff --git a/design/vtubermini-mvp-v1-export.png b/design/vtubermini-mvp-v1-export.png new file mode 100644 index 0000000..6504a96 --- /dev/null +++ b/design/vtubermini-mvp-v1-export.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06332257736ac4c9e357b16699d4d935aeb8baf7d5d6d25766974779628ad067 +size 1921438 diff --git a/design/vtubermini-mvp-v1-preview.png b/design/vtubermini-mvp-v1-preview.png new file mode 100644 index 0000000..462a121 --- /dev/null +++ b/design/vtubermini-mvp-v1-preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c20b1631383f3240d5c9ec1d5067525de64b9c32d8b334940ee4c2ca8722ea5d +size 763907 diff --git a/design/vtubermini-mvp-v1-usage.json b/design/vtubermini-mvp-v1-usage.json new file mode 100644 index 0000000..efca921 --- /dev/null +++ b/design/vtubermini-mvp-v1-usage.json @@ -0,0 +1,6 @@ +{ + "agentType": "codex", + "model": "gpt-5.5", + "reason": "finished", + "usage": null +} \ No newline at end of file diff --git a/design/vtubermini-mvp-v1.pen b/design/vtubermini-mvp-v1.pen new file mode 100644 index 0000000..0156421 --- /dev/null +++ b/design/vtubermini-mvp-v1.pen @@ -0,0 +1,14482 @@ +{ + "version": "2.13", + "children": [ + { + "type": "frame", + "id": "v5gpU", + "x": 0, + "y": 0, + "name": "00 Design System - Blue Dream", + "clip": true, + "width": 1440, + "height": 960, + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 24, + "padding": 32, + "children": [ + { + "type": "frame", + "id": "L40HM", + "name": "Design System Header", + "width": "fill_container", + "height": 128, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 90, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#102A57", + "position": 0 + }, + { + "color": "#4A90E2", + "position": 1 + } + ] + }, + "cornerRadius": 24, + "gap": 24, + "padding": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "n4J7Rl", + "name": "Glass Crown Mark", + "width": 82, + "height": 82, + "fill": "#FFFFFF22", + "cornerRadius": 24, + "stroke": "#FFFFFF99", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#4A90E255", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 24 + }, + "layout": "none", + "children": [ + { + "type": "icon", + "id": "Od9wU", + "x": 20, + "y": 18, + "name": "Crown Icon", + "width": 42, + "height": 42, + "icon": "Crown", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "ellipse", + "id": "o4pTr", + "x": 56, + "y": 14, + "name": "Glow Dot", + "fill": "#E8F4F8AA", + "width": 12, + "height": 12 + } + ] + }, + { + "type": "frame", + "id": "A10iM", + "name": "Header Copy", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "SZNGw", + "name": "Title", + "fill": "#FFFFFF", + "content": "VTuber Mini 蓝色梦境 MVP", + "fontFamily": "$font-heading", + "fontSize": 38, + "fontWeight": "700" + }, + { + "type": "text", + "id": "o3Aafg", + "name": "Subtitle", + "fill": "#D8ECFF", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "安卓粉丝 App + 后台工坊设计系统:宠物陪伴、积分抽卡、钻石兑换、纪念日运营。", + "lineHeight": 1.35, + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "BVozH", + "name": "Token Badge", + "fill": "#FFFFFF1F", + "cornerRadius": 18, + "stroke": "#FFFFFF66", + "strokeWidth": 1, + "layout": "vertical", + "gap": 4, + "padding": [ + 12, + 16 + ], + "children": [ + { + "type": "text", + "id": "jZuOK", + "name": "Badge Label", + "fill": "#D8ECFF", + "content": "Visual Tone", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "t0tUWF", + "name": "Badge Value", + "fill": "#FFFFFF", + "content": "冷色通透 / 轻奢收藏 / 温柔陪伴", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "LDtPE", + "name": "Tokens and Typography", + "width": "fill_container", + "height": 230, + "gap": 20, + "children": [ + { + "type": "frame", + "id": "LSqis", + "name": "Color Tokens", + "width": 520, + "height": "fill_container", + "fill": "#E8F4F8", + "cornerRadius": 20, + "layout": "vertical", + "gap": 14, + "padding": 20, + "children": [ + { + "type": "text", + "id": "Z9VsZ", + "name": "Section Title", + "fill": "$deep-blue", + "content": "颜色 Token", + "fontFamily": "$font-heading", + "fontSize": 26, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "IBr0Y", + "name": "Swatches", + "width": "fill_container", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "KRduU", + "name": "dream-blue", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "t1Baq", + "name": "dream-blue swatch", + "fill": "#4A90E2", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "OWHHh", + "name": "dream-blue label", + "fill": "$deep-blue", + "content": "dream-blue", + "fontFamily": "$font-data", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Ksi8c", + "name": "dream-blue use", + "fill": "$muted-blue", + "content": "主按钮", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "isJDs", + "name": "crystal-blue", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "H2oGE", + "name": "crystal-blue swatch", + "fill": "#B8D4F1", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "EHc2L", + "name": "crystal-blue label", + "fill": "$deep-blue", + "content": "crystal-blue", + "fontFamily": "$font-data", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "FFRlK", + "name": "crystal-blue use", + "fill": "$muted-blue", + "content": "玻璃边缘", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Jf24A", + "name": "moon-white", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "T62xip", + "name": "moon-white swatch", + "fill": "#E8F4F8", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "Y1KAI", + "name": "moon-white label", + "fill": "$deep-blue", + "content": "moon-white", + "fontFamily": "$font-data", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "FrWdA", + "name": "moon-white use", + "fill": "$muted-blue", + "content": "页面底", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "eiJKb", + "name": "deep-blue", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "h0oEQ", + "name": "deep-blue swatch", + "fill": "#2C5F8D", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "C9349", + "name": "deep-blue label", + "fill": "$deep-blue", + "content": "deep-blue", + "fontFamily": "$font-data", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "DVdvO", + "name": "deep-blue use", + "fill": "$muted-blue", + "content": "标题", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "dO5lx", + "name": "rose-gold", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "qIgPt", + "name": "rose-gold swatch", + "fill": "#D4AF37", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "OzCGA", + "name": "rose-gold label", + "fill": "$deep-blue", + "content": "rose-gold", + "fontFamily": "$font-data", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Ag7CX", + "name": "rose-gold use", + "fill": "$muted-blue", + "content": "SSR/VIP", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "LBqA5", + "name": "Typography", + "width": 390, + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": 20, + "children": [ + { + "type": "text", + "id": "x5jKKX", + "name": "Type Title", + "fill": "$deep-blue", + "content": "排版", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "text", + "id": "bSgJn", + "name": "Heading Example", + "fill": "$deep-blue", + "content": "璃月纪念日", + "fontFamily": "$font-heading", + "fontSize": 34, + "fontWeight": "700" + }, + { + "type": "text", + "id": "j4WzD", + "name": "Body Example", + "fill": "$ink-blue", + "content": "今天也和小狗一起收集星光积分。", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "lmbTc", + "name": "Data Example", + "fill": "$dream-blue", + "content": "12,680 pts / 320 钻", + "fontFamily": "$font-data", + "fontSize": 24, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "AQjuC", + "name": "Review Rules", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 10, + "padding": 20, + "children": [ + { + "type": "text", + "id": "wD8v9", + "name": "Rules Title", + "fill": "$deep-blue", + "content": "设计审查重点", + "fontFamily": "$font-heading", + "fontSize": 26, + "fontWeight": "700" + }, + { + "type": "text", + "id": "L4tFxv", + "name": "Rule - 抽卡必须显示概率、保底、重复卡处理", + "fill": "$ink-blue", + "content": "• 抽卡必须显示概率、保底、重复卡处理", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Zewdg", + "name": "Rule - 商城只展示钻石兑换,不出现充值入口", + "fill": "$ink-blue", + "content": "• 商城只展示钻石兑换,不出现充值入口", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "uEZzJ", + "name": "Rule - 宠物页显示每日积分上限和剩余小游戏次数", + "fill": "$ink-blue", + "content": "• 宠物页显示每日积分上限和剩余小游戏次数", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "hkbTV", + "name": "Rule - 纪念日区分系统纪念日与用户自定义纪念日", + "fill": "$ink-blue", + "content": "• 纪念日区分系统纪念日与用户自定义纪念日", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Alumi", + "name": "Rule - 后台资产发放必须有原因、审批、审计记录", + "fill": "$ink-blue", + "content": "• 后台资产发放必须有原因、审批、审计记录", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "IYqD4", + "name": "Reusable Components", + "width": "fill_container", + "height": 500, + "gap": 20, + "children": [ + { + "type": "frame", + "id": "SsgK5", + "name": "Core Components Column A", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "Zbeap", + "name": "Component / Glass Card", + "reusable": true, + "width": 380, + "height": 132, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#4A90E222", + "offset": { + "x": 0, + "y": 12 + }, + "blur": 28 + }, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "bwFvM", + "name": "Component Title", + "fill": "$deep-blue", + "content": "今日星光任务", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "lkwOr", + "name": "Component Body", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "已获得 42/80 积分,完成全部照顾可再领 20。", + "lineHeight": 1.35, + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "PQLHK", + "name": "Component / Primary Button", + "reusable": true, + "width": 220, + "height": 52, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 90, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#4A90E2", + "position": 0 + }, + { + "color": "#2C5F8D", + "position": 1 + } + ] + }, + "cornerRadius": 999, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#4A90E255", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 18 + }, + "gap": 8, + "padding": [ + 0, + 18 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qTI3W", + "name": "Button Label", + "fill": "#FFFFFF", + "content": "进入宠物小屋", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "C7OwSI", + "name": "Button Arrow", + "width": 18, + "height": 18, + "icon": "chevron-right", + "library": "lucide", + "fill": "#FFFFFF" + } + ] + }, + { + "type": "frame", + "id": "XW7K2", + "name": "Component / Secondary Button", + "reusable": true, + "width": 220, + "height": 52, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 8, + "padding": [ + 0, + 18 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "lGBJG", + "name": "Button Label", + "fill": "$deep-blue", + "content": "查看规则", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "x01DP3", + "name": "Component / Asset Pill", + "reusable": true, + "width": 170, + "height": 44, + "fill": "#E8F4F8", + "cornerRadius": 999, + "stroke": "#FFFFFF", + "strokeWidth": 1, + "gap": 8, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "KOmsr", + "name": "Asset Icon", + "width": 18, + "height": 18, + "icon": "diamond", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "ZV4LW", + "name": "Asset Value", + "fill": "$deep-blue", + "content": "钻石 320", + "fontFamily": "$font-data", + "fontSize": 15, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "NHe0S", + "name": "Component / Progress Bar", + "reusable": true, + "width": 360, + "height": 44, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "G9atF0", + "name": "Progress Track", + "width": "fill_container", + "height": 12, + "fill": "#D8ECFF", + "cornerRadius": 999, + "layout": "none", + "children": [ + { + "type": "rectangle", + "cornerRadius": 999, + "id": "qMnCC", + "x": 0, + "y": 0, + "name": "Progress Fill", + "fill": "#4A90E2", + "width": 228, + "height": 12 + } + ] + }, + { + "type": "text", + "id": "HXJyr", + "name": "Progress Label", + "fill": "$muted-blue", + "content": "保底进度 38 / 90", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "vK4b1", + "name": "Core Components Column B", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "XzBdl", + "name": "Component / Badge", + "reusable": true, + "width": 90, + "height": 30, + "fill": "#D4AF3722", + "cornerRadius": 999, + "stroke": "#D4AF37", + "strokeWidth": 1, + "gap": 6, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qIDBZ", + "name": "Badge Text", + "fill": "#8A6412", + "content": "SSR 限定", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "u2cUbD", + "name": "Component / Input", + "reusable": true, + "width": 360, + "height": 56, + "fill": "#FFFFFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 8, + "padding": [ + 0, + 16 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "u9OYgJ", + "name": "Input Text", + "fill": "$ink-blue", + "content": "第一次看直播", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "aXvxb", + "name": "Component / Modal Container", + "reusable": true, + "width": 360, + "height": 190, + "fill": "#FFFFFFE6", + "cornerRadius": 28, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#2C5F8D33", + "offset": { + "x": 0, + "y": 18 + }, + "blur": 36 + }, + "layout": "vertical", + "gap": 14, + "padding": 22, + "children": [ + { + "type": "text", + "id": "WmTEm", + "name": "Modal Title", + "fill": "$deep-blue", + "content": "添加纪念日", + "fontFamily": "$font-heading", + "fontSize": 26, + "fontWeight": "700" + }, + { + "type": "text", + "id": "PoFcd", + "name": "Modal Body", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "名称、日期、是否每年重复、提醒提前时间。", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "e08dE", + "name": "Component / Card Thumbnail", + "reusable": true, + "width": 104, + "height": 148, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#102A57", + "position": 0 + }, + { + "color": "#B8D4F1", + "position": 1 + } + ] + }, + "cornerRadius": 18, + "stroke": "#D4AF37", + "strokeWidth": 1, + "layout": "none", + "children": [ + { + "type": "icon", + "id": "hF3mU", + "x": 34, + "y": 34, + "name": "Card Star", + "width": 36, + "height": 36, + "icon": "sparkles", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "text", + "id": "kb05c", + "x": 12, + "y": 108, + "name": "Card Name", + "fill": "#FFFFFF", + "content": "月冠 SSR", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "OESlm", + "name": "Core Components Column C", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "iFhWT", + "name": "Component / Anniversary Card", + "reusable": true, + "width": 380, + "height": 128, + "fill": "#FFFFFFCC", + "cornerRadius": 22, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 14, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "R5VAG", + "name": "Moon Icon", + "width": 70, + "height": 70, + "fill": "#E8F4F8", + "cornerRadius": 20, + "layout": "none", + "children": [ + { + "type": "icon", + "id": "YSWZk", + "x": 18, + "y": 18, + "name": "Calendar Heart", + "width": 34, + "height": 34, + "icon": "CalendarHeart", + "library": "lucide", + "fill": "#4A90E2" + } + ] + }, + { + "type": "frame", + "id": "C86gG0", + "name": "Anniversary Copy", + "width": "fill_container", + "layout": "vertical", + "gap": 5, + "children": [ + { + "type": "text", + "id": "VgUla", + "name": "Anniversary Title", + "fill": "$deep-blue", + "content": "开播周年倒计时 18 天", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "J9ZLmB", + "name": "Anniversary Body", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "已陪伴 486 天,周年活动规则已公开。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "g3mhW", + "name": "Component / Product Card", + "reusable": true, + "width": 260, + "height": 172, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 10, + "padding": 14, + "children": [ + { + "type": "rectangle", + "cornerRadius": 14, + "id": "lIA7M", + "name": "Product Image", + "fill": "#D8ECFF", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "ygqPF", + "name": "Product Name", + "fill": "$deep-blue", + "content": "蓝玫瑰亚克力立牌", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "Kho6q", + "name": "Product Meta", + "fill": "$muted-blue", + "content": "库存 28 · 180 钻 · 7 个工作日发货", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "tbRDr", + "name": "Component / Tab Bar Item", + "reusable": true, + "width": 190, + "height": 72, + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 4, + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Hfpc1", + "name": "Tab Item Icon", + "width": 22, + "height": 22, + "icon": "sparkles", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "pNOI5", + "name": "Tab Item Label", + "fill": "$dream-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "BKR4K", + "x": 0, + "y": 1060, + "name": "01 Mobile App Key Screens", + "clip": true, + "width": 1760, + "height": 3780, + "fill": "#F7FCFF", + "layout": "none", + "children": [ + { + "type": "text", + "id": "MGIO8", + "x": 32, + "y": 28, + "name": "Mobile Section Title", + "fill": "$deep-blue", + "content": "Mobile App Key Screens / Android 390x844", + "fontFamily": "$font-heading", + "fontSize": 34, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "YPimm", + "x": 32, + "y": 92, + "name": "Login", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#0B1F46", + "position": 0 + }, + { + "color": "#174A78", + "position": 0.55 + }, + { + "color": "#E8F4F8", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "y67pa", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "PCxbd", + "name": "Time", + "fill": "#FFFFFF", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "u3ujpY", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "uWB34", + "name": "Signal", + "fill": "#FFFFFF", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "j9COgb", + "name": "Battery", + "fill": "#FFFFFF", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "AMU8g", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "g7Hr6i", + "name": "Login Visual", + "width": "fill_container", + "height": 250, + "fill": "#FFFFFF14", + "cornerRadius": 28, + "stroke": "#FFFFFF55", + "strokeWidth": 1, + "layout": "none", + "children": [ + { + "type": "ellipse", + "id": "I24ux7", + "x": 228, + "y": 28, + "name": "Moon Glow", + "fill": "#E8F4F888", + "width": 92, + "height": 92 + }, + { + "type": "ellipse", + "id": "kEtBc", + "x": 122, + "y": 76, + "name": "Avatar Orb", + "fill": "#B8D4F1CC", + "width": 122, + "height": 122 + }, + { + "type": "icon", + "id": "FhjFp", + "x": 164, + "y": 50, + "name": "Crown", + "width": 42, + "height": 42, + "icon": "crown", + "library": "lucide", + "fill": "#D4AF37" + } + ] + }, + { + "type": "text", + "id": "aBxae", + "name": "Login Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "欢迎回到 VTuber Mini", + "fontFamily": "$font-heading", + "fontSize": 30, + "fontWeight": "700" + }, + { + "type": "text", + "id": "lJXXe", + "name": "Login Subtitle", + "fill": "#D8ECFF", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "登录后同步积分、钻石、卡册与兑换订单", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "T1Vaea", + "name": "Login Form", + "width": "fill_container", + "height": 220, + "fill": "#FFFFFFE6", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "OcDL5", + "name": "Phone Input", + "fill": "$muted-blue", + "content": "请输入手机号", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "G9YLN", + "name": "Code Input", + "fill": "$muted-blue", + "content": "请输入验证码 获取验证码", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "UfnDV", + "name": "Login Button", + "width": "fill_container", + "height": 48, + "fill": "#4A90E2", + "cornerRadius": 999, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "CzPCK", + "name": "Login Button Text", + "fill": "#FFFFFF", + "content": "登录", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "IKX0V", + "name": "Agreement", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "☑ 我已阅读并同意《用户协议》《隐私政策》", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "XcRJj", + "x": 454, + "y": 92, + "name": "Home", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#0B1F46", + "position": 0 + }, + { + "color": "#174A78", + "position": 0.55 + }, + { + "color": "#E8F4F8", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "N9Ka6R", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ckLPO", + "name": "Time", + "fill": "#FFFFFF", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "d2F3Dn", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "cjaqA", + "name": "Signal", + "fill": "#FFFFFF", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "AgLWt", + "name": "Battery", + "fill": "#FFFFFF", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "OAe79", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "Hjw44", + "name": "Home Immersive Hero", + "width": "fill_container", + "height": 276, + "fill": "#FFFFFF12", + "cornerRadius": 28, + "stroke": "#FFFFFF44", + "strokeWidth": 1, + "layout": "none", + "children": [ + { + "type": "ellipse", + "id": "N1BZCU", + "x": 172, + "y": 24, + "name": "Character Glow", + "fill": "#B8D4F188", + "width": 150, + "height": 210 + }, + { + "type": "frame", + "id": "lDq7T", + "x": 206, + "y": 78, + "name": "Character Silhouette", + "width": 86, + "height": 154, + "fill": "#E8F4F8CC", + "cornerRadius": 44, + "layout": "none" + }, + { + "type": "icon", + "id": "nH5s4", + "x": 232, + "y": 46, + "name": "Hero Crown", + "width": 34, + "height": 34, + "icon": "crown", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "text", + "id": "JFQpU", + "x": 20, + "y": 24, + "name": "Home Greeting", + "fill": "#FFFFFF", + "textGrowth": "fixed-width", + "width": 180, + "content": "今晚也一起进入蓝色梦境吧", + "lineHeight": 1.15, + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "text", + "id": "HrXIF", + "x": 20, + "y": 108, + "name": "Home Notice", + "fill": "#D8ECFF", + "textGrowth": "fixed-width", + "width": 180, + "content": "周年纪念卡池开启中", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "p6VApW", + "name": "Asset Row", + "width": "fill_container", + "height": 50, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "Xknwu", + "name": "Points Pill", + "height": 34, + "fill": "#FFFFFFD9", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "a8Yavx", + "name": "Points Pill Icon", + "width": 15, + "height": 15, + "icon": "star", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "U4cZGS", + "name": "Points Pill Text", + "fill": "$deep-blue", + "content": "积分 1280", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "GqcbI", + "name": "Diamonds Pill", + "height": 34, + "fill": "#FFFFFFD9", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Al4H3", + "name": "Diamonds Pill Icon", + "width": 15, + "height": 15, + "icon": "diamond", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "UFl5E", + "name": "Diamonds Pill Text", + "fill": "$deep-blue", + "content": "钻石 36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "fBWKQ", + "name": "Quick Entrances", + "width": "fill_container", + "height": 96, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "vuohT", + "name": "入口 宠物小屋", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "jDdtt", + "name": "宠物小屋 Icon", + "width": 23, + "height": 23, + "icon": "paw-print", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "HzGKl", + "name": "宠物小屋 Text", + "fill": "$deep-blue", + "content": "宠物小屋", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "zansx", + "name": "入口 限定卡池", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "W42B7d", + "name": "限定卡池 Icon", + "width": 23, + "height": 23, + "icon": "sparkles", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "LHZb6", + "name": "限定卡池 Text", + "fill": "$deep-blue", + "content": "限定卡池", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "W98cxJ", + "name": "入口 钻石商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "GcqFA", + "name": "钻石商城 Icon", + "width": 23, + "height": 23, + "icon": "shopping-bag", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "C5wjX", + "name": "钻石商城 Text", + "fill": "$deep-blue", + "content": "钻石商城", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "EaJHD", + "name": "入口 卡册", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "H12fE", + "name": "卡册 Icon", + "width": 23, + "height": 23, + "icon": "book-open", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "j7Jms", + "name": "卡册 Text", + "fill": "$deep-blue", + "content": "卡册", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + }, + { + "id": "TLWWU", + "type": "ref", + "ref": "iFhWT", + "name": "Home Anniversary Card", + "width": "fill_container", + "height": 112, + "descendants": { + "VgUla": { + "content": "距离主播生日还有 12 天" + }, + "J9ZLmB": { + "content": "已陪伴 386 天 · 查看全部纪念日" + } + } + }, + { + "type": "frame", + "id": "YWyrv", + "name": "Announcement Card", + "width": "fill_container", + "height": 88, + "fill": "#FFFFFFD9", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "InNTq", + "name": "Announcement 1", + "fill": "$deep-blue", + "content": "钻石到账提醒:活动补贴 +20 钻", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "MzaQh", + "name": "Announcement 2", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "周边兑换发货规则更新,请兑换前确认地址", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Y1dxMH", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#2C5F8D22", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 18 + }, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "cGcZX", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "nqUmo", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "NfXyM", + "name": "首页 Label", + "fill": "$dream-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "whMeM", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Yezij", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "sM91G", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hwowk", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "cYUAZ", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "CsW2T", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "A4ceUz", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "o4hBj", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "h6bON8", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "NEUKy", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "NNZw4", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "Af452", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Hyb1a", + "x": 876, + "y": 92, + "name": "Pet Room", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "W4akn", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Ge3wo", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "fhvID", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "OVL0l", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "VmTGD", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "LfuKW", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "QSV9m", + "name": "Pet Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "小蓝今天在等你摸摸头", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "ZyO5r", + "name": "Pet Visual Room", + "width": "fill_container", + "height": 230, + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "none", + "children": [ + { + "type": "ellipse", + "id": "KoT2M", + "x": 240, + "y": 28, + "name": "Window Moon", + "fill": "#FFFFFFAA", + "width": 68, + "height": 68 + }, + { + "type": "ellipse", + "id": "fqO28", + "x": 126, + "y": 94, + "name": "Dog Body", + "fill": "#FFFFFF", + "width": 132, + "height": 92 + }, + { + "type": "ellipse", + "id": "UjVHP", + "x": 142, + "y": 54, + "name": "Dog Head", + "fill": "#FFFFFF", + "width": 104, + "height": 94 + }, + { + "type": "ellipse", + "id": "QEQ5s", + "x": 124, + "y": 62, + "name": "Left Ear", + "fill": "#B8D4F1", + "width": 40, + "height": 56 + }, + { + "type": "ellipse", + "id": "UsBP9", + "x": 222, + "y": 62, + "name": "Right Ear", + "fill": "#B8D4F1", + "width": 40, + "height": 56 + }, + { + "type": "icon", + "id": "l3oO3", + "x": 176, + "y": 34, + "name": "Pet Crown", + "width": 34, + "height": 34, + "icon": "crown", + "library": "lucide", + "fill": "#D4AF37" + } + ] + }, + { + "type": "frame", + "id": "kQfii", + "name": "Pet Stats", + "width": "fill_container", + "height": 98, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "l3OFF", + "name": "Pet Stats Row", + "width": "fill_container", + "height": "fill_container", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "aRRfS", + "name": "Stat 饱腹 80", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "b5gQw", + "name": "Stat 饱腹 80 Text", + "fill": "$deep-blue", + "content": "饱腹 80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "I3Wvlm", + "name": "Stat 心情 92", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "iHo0o", + "name": "Stat 心情 92 Text", + "fill": "$deep-blue", + "content": "心情 92", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "SfKk2", + "name": "Stat 清洁 76", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "cbKxZ", + "name": "Stat 清洁 76 Text", + "fill": "$deep-blue", + "content": "清洁 76", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "KCu8u", + "name": "Pet Stats Row", + "width": "fill_container", + "height": "fill_container", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "VoppK", + "name": "Stat 活力 60", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qq0Kv", + "name": "Stat 活力 60 Text", + "fill": "$deep-blue", + "content": "活力 60", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "uoFkb", + "name": "Stat 亲密度 Lv.6", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "QhqQ8", + "name": "Stat 亲密度 Lv.6 Text", + "fill": "$deep-blue", + "content": "亲密度 Lv.6", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "PlQkQ", + "name": "Stat 今日 46/80", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LBEFX", + "name": "Stat 今日 46/80 Text", + "fill": "$deep-blue", + "content": "今日 46/80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "D7TF6", + "name": "Interaction Buttons", + "width": "fill_container", + "height": 76, + "gap": 8, + "children": [ + { + "type": "frame", + "id": "zgzht", + "name": "Action 喂食", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "c10EtE", + "name": "喂食 Icon", + "width": 20, + "height": 20, + "icon": "utensils", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "PF3PD", + "name": "喂食 Label", + "fill": "$deep-blue", + "content": "喂食", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "O8TuU", + "name": "Action 抚摸", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "D8CMaU", + "name": "抚摸 Icon", + "width": 20, + "height": 20, + "icon": "heart", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "i8Ww0J", + "name": "抚摸 Label", + "fill": "$deep-blue", + "content": "抚摸", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "U7CmMa", + "name": "Action 清洁", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "G9HqTS", + "name": "清洁 Icon", + "width": 20, + "height": 20, + "icon": "sparkle", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "Soh9L", + "name": "清洁 Label", + "fill": "$deep-blue", + "content": "清洁", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "j5uPes", + "name": "Action 玩耍", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "e8eAk", + "name": "玩耍 Icon", + "width": 20, + "height": 20, + "icon": "gamepad-2", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "text", + "id": "wxkGL", + "name": "玩耍 Label", + "fill": "$deep-blue", + "content": "玩耍", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Gatnt", + "name": "Daily Task Card", + "width": "fill_container", + "height": 112, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "fXkNb", + "name": "Task Main", + "fill": "$deep-blue", + "content": "完成全部照顾 3/5 · 可领 20 积分", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "700" + }, + { + "id": "q0SG9H", + "type": "ref", + "ref": "NHe0S", + "name": "Daily Cap Progress", + "width": "fill_container", + "height": 38, + "descendants": { + "HXJyr": { + "content": "今日积分上限 46 / 80 · 小游戏剩余结算 2/3" + } + } + }, + { + "type": "text", + "id": "IDMtZ", + "name": "Task Tip", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "达到上限后仍可提升亲密度,但不再获得积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "zBaJ9", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#2C5F8D22", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 18 + }, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "OnKJe", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "BuKa4", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "hrAjO", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "IHccl", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "beJkz", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "f8NjD", + "name": "宠物 Label", + "fill": "$dream-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Wa8Sz", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "E16Yhr", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "mtzPV", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "oFvP3", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "wIO9G", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "wHbKs", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Nk7nt", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "rC4DN", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "cLrS6", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "hqBeG", + "x": 1298, + "y": 92, + "name": "Mini Game", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "ia9vU", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "QV4rk", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "Zdx6H", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "oTWiI", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "iiskA", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "w0QCra", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "siQRe", + "name": "Game Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "接住梦境星星", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "UAxXR", + "name": "Game Arena", + "width": "fill_container", + "height": 360, + "fill": "#102A57", + "cornerRadius": 28, + "layout": "none", + "children": [ + { + "type": "text", + "id": "BrBT4", + "x": 0, + "y": 0, + "name": "Score", + "fill": "#FFFFFF", + "content": "分数 1240", + "fontFamily": "$font-body", + "fontSize": 18, + "fontWeight": "700" + }, + { + "type": "text", + "id": "ZLvgZ", + "x": 250, + "y": 14, + "name": "Timer", + "fill": "#B8D4F1", + "content": "剩余 28s", + "fontFamily": "$font-data", + "fontSize": 16, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "IQ1vX", + "x": 58, + "y": 92, + "name": "Falling Star", + "width": 24, + "height": 24, + "icon": "star", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "icon", + "id": "pJNmf", + "x": 178, + "y": 60, + "name": "Falling Star", + "width": 24, + "height": 24, + "icon": "star", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "icon", + "id": "GMs2D", + "x": 294, + "y": 128, + "name": "Falling Star", + "width": 24, + "height": 24, + "icon": "star", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "icon", + "id": "N6KtV", + "x": 96, + "y": 210, + "name": "Falling Star", + "width": 24, + "height": 24, + "icon": "star", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "icon", + "id": "qcgzc", + "x": 252, + "y": 236, + "name": "Falling Star", + "width": 24, + "height": 24, + "icon": "star", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "ellipse", + "id": "ReRNA", + "x": 185, + "y": 168, + "name": "Crystal Obstacle", + "fill": "#B8D4F1AA", + "width": 34, + "height": 42 + }, + { + "type": "ellipse", + "id": "oTpzM", + "x": 154, + "y": 286, + "name": "Dog Player", + "fill": "#FFFFFF", + "width": 82, + "height": 48 + } + ] + }, + { + "type": "text", + "id": "DwCSE", + "name": "Game Hint", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "滑动小狗接住星星,避开碎晶。异常高分、重复提交不结算积分。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "E6dsx9", + "name": "Settlement Rules", + "width": "fill_container", + "height": 108, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "F1KVx", + "name": "Settle 1", + "fill": "$deep-blue", + "content": "低分 +3 · 中分 +5 · 高分 +8", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "700" + }, + { + "type": "text", + "id": "ti4Ps", + "name": "Settle 2", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "今日有效结算 1/3 · 今日剩余可得积分 34", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "id": "KpJhV", + "type": "ref", + "ref": "PQLHK", + "name": "Start Game Button", + "width": "fill_container", + "height": 52, + "descendants": { + "qTI3W": { + "content": "开始游戏" + } + } + } + ] + }, + { + "type": "frame", + "id": "IMMBm", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#2C5F8D22", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 18 + }, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "bLnZE", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "UhZdp", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "YQzLv", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "VJaYd", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Grzsu", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "kQLbm", + "name": "宠物 Label", + "fill": "$dream-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "ENfpd", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "f2dGR", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "AZqvB", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "j8mKz", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "XCB8B", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "q0S61a", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "M5Qz0", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "ES7uL", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "NoyrI", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "UiO6x", + "x": 32, + "y": 1000, + "name": "Gacha Pool", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#0B1F46", + "position": 0 + }, + { + "color": "#174A78", + "position": 0.62 + }, + { + "color": "#E8F4F8", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "o4GN6", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "MU1kk", + "name": "Time", + "fill": "#FFFFFF", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "G5K7h9", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "X9QzD", + "name": "Signal", + "fill": "#FFFFFF", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "TOCAF", + "name": "Battery", + "fill": "#FFFFFF", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "A9uZo3", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "noZK0", + "name": "Pool Hero", + "width": "fill_container", + "height": 252, + "fill": "#FFFFFF14", + "cornerRadius": 28, + "stroke": "#FFFFFF55", + "strokeWidth": 1, + "layout": "none", + "children": [ + { + "type": "ellipse", + "id": "d7r4yU", + "x": 210, + "y": 34, + "name": "Rose Glow", + "fill": "#D4AF3744", + "width": 108, + "height": 108 + }, + { + "type": "icon", + "id": "YmDtn", + "x": 244, + "y": 66, + "name": "Pool Crown", + "width": 42, + "height": 42, + "icon": "crown", + "library": "lucide", + "fill": "#D4AF37" + }, + { + "type": "icon", + "id": "qemdH", + "x": 62, + "y": 46, + "name": "Pool Spark", + "width": 34, + "height": 34, + "icon": "sparkles", + "library": "lucide", + "fill": "#B8D4F1" + }, + { + "type": "text", + "id": "mQwBs", + "x": 20, + "y": 34, + "name": "Pool Title", + "fill": "#FFFFFF", + "textGrowth": "fixed-width", + "width": 210, + "content": "蓝玫瑰周年限定卡池", + "lineHeight": 1.15, + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "text", + "id": "NTHz0", + "x": 20, + "y": 122, + "name": "Pool Time", + "fill": "#D8ECFF", + "textGrowth": "fixed-width", + "width": 220, + "content": "06.01 - 06.30 · 当前积分 1280", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "L6dxj", + "name": "Preview Cards", + "width": "fill_container", + "height": 118, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "eBxhj", + "name": "Preview SSR", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 14, + "id": "wnxm5", + "name": "SSR Card Art", + "fill": "#D4AF37", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "k4uXU", + "name": "SSR Card Label", + "fill": "$deep-blue", + "content": "SSR 周年小卡", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "C1kvu", + "name": "Preview SR", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 14, + "id": "AE7uT", + "name": "SR Card Art", + "fill": "#4A90E2", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "n21Muy", + "name": "SR Card Label", + "fill": "$deep-blue", + "content": "SR 周年小卡", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "cqhux", + "name": "Preview R", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 18, + "layout": "vertical", + "gap": 5, + "padding": 8, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 14, + "id": "Lx0y0", + "name": "R Card Art", + "fill": "#B8D4F1", + "width": "fill_container", + "height": 72 + }, + { + "type": "text", + "id": "q76SI", + "name": "R Card Label", + "fill": "$deep-blue", + "content": "R 周年小卡", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "y4tgZa", + "name": "Probability Rule Card", + "width": "fill_container", + "height": 142, + "fill": "#FFFFFFE6", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "uNDnG", + "name": "Cost", + "fill": "$deep-blue", + "content": "单抽消耗 100 积分 · 十连消耗 1000 积分", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "C92D6", + "name": "Probabilities", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "概率公示:SSR 2% / SR 12% / R 36% / N 50%", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "JvoVp", + "name": "Pity", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "60 抽必得 SSR · 本卡池无空奖", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "xpCnr", + "name": "Duplicate", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "重复小卡自动转化为碎片或积分返还", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "b63qX", + "name": "Gacha Buttons", + "width": "fill_container", + "height": 54, + "gap": 10, + "children": [ + { + "id": "hbyoG", + "type": "ref", + "ref": "PQLHK", + "name": "Single Draw Button", + "width": "fill_container", + "height": "fill_container", + "descendants": { + "qTI3W": { + "content": "单抽一次" + } + } + }, + { + "id": "kz5jm", + "type": "ref", + "ref": "XW7K2", + "name": "Ten Draw Button", + "width": "fill_container", + "height": "fill_container", + "descendants": { + "lGBJG": { + "content": "十连抽" + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "OENij", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "s51l0M", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "s8aJG", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "KPxHH", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "g54rvi", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "sRrmh", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "phiu6", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "nzuSL", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "expyn", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "e82iKH", + "name": "抽卡 Label", + "fill": "$dream-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "lSVr2", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Y8feCW", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "cFCVg", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "zFhiO", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "d0LMH", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "B2gri", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "J2Ml9b", + "x": 454, + "y": 1000, + "name": "Gacha Result Modal", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#0B1F46", + "position": 0 + }, + { + "color": "#174A78", + "position": 0.62 + }, + { + "color": "#E8F4F8", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "e518n", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "pPWUA", + "name": "Time", + "fill": "#FFFFFF", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "oJyjn", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "cfnWC", + "name": "Signal", + "fill": "#FFFFFF", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "DE1Ul", + "name": "Battery", + "fill": "#FFFFFF", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "r9fR95", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "p4oYrl", + "name": "Result Backdrop", + "width": "fill_container", + "height": "fill_container", + "fill": "#081B3ACC", + "cornerRadius": 28, + "layout": "none", + "children": [ + { + "type": "ellipse", + "id": "bsaXu", + "x": 40, + "y": 58, + "name": "Result Glow", + "fill": "#D4AF3744", + "width": 276, + "height": 276 + }, + { + "type": "frame", + "id": "x7ixh", + "x": 30, + "y": 92, + "name": "Result Modal Container", + "width": 298, + "height": 520, + "fill": "#FFFFFFE6", + "cornerRadius": 30, + "stroke": "#D4AF37", + "strokeWidth": 1, + "layout": "vertical", + "gap": 14, + "padding": 20, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Mh2D5", + "name": "Result Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "获得小卡", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "N2eJN", + "name": "Result Card", + "width": 180, + "height": 250, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#102A57", + "position": 0 + }, + { + "color": "#D4AF37", + "position": 1 + } + ] + }, + "cornerRadius": 22, + "layout": "none", + "children": [ + { + "type": "icon", + "id": "iNOWv", + "x": 68, + "y": 70, + "name": "Result Card Spark", + "width": 44, + "height": 44, + "icon": "sparkles", + "library": "lucide", + "fill": "#FFFFFF" + }, + { + "type": "text", + "id": "NwIzE", + "x": 26, + "y": 198, + "name": "Result Card Name", + "fill": "#FFFFFF", + "content": "蓝玫瑰周年纪念", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "gONON", + "name": "SSR Badge", + "height": 30, + "fill": "#D4AF3722", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "FN7d8", + "name": "SSR Badge Text", + "fill": "$deep-blue", + "content": "SSR 限定", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "ZUNtc", + "name": "Result Meta", + "fill": "$deep-blue", + "content": "NO. SSR-006 · 新获得", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "FuaEn", + "name": "Duplicate Rule", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "若重复获得,将转化为 20 碎片并写入抽卡记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "id": "tldJa", + "type": "ref", + "ref": "PQLHK", + "name": "Accept Card Button", + "width": "fill_container", + "height": 48, + "descendants": { + "qTI3W": { + "content": "收下并去卡册查看" + } + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "JMvyA", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "YJTWU", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "vgD5Q", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "deuvj", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ZQoFI", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Z3RdM", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "w9n9jM", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "zkRZf", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "C8cbr2", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "WV54u", + "name": "抽卡 Label", + "fill": "$dream-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "tVyzx", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "DtHo0", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "IkSHS", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "lzOna", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "q22ehY", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "zAXF4", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "NNooM", + "x": 876, + "y": 1000, + "name": "Card Album", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "tptlx", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "VA6Wb", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "EUL3D", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "YA1sg", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "uk8B4", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "YzVCm", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "o7j2kj", + "name": "Album Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "我的小卡册", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "text", + "id": "ZJbbq", + "name": "Album Count", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "已收集 36/80 · 周年系列 12/20", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "k8q5TD", + "name": "Album Filters", + "width": "fill_container", + "height": 36, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "d8oJBK", + "name": "Filter 全部", + "height": 30, + "fill": "#D8ECFF", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "CC8IC", + "name": "Filter 全部 Text", + "fill": "$deep-blue", + "content": "全部", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "AvXxG", + "name": "Filter 已拥有", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "G6sNc", + "name": "Filter 已拥有 Text", + "fill": "$deep-blue", + "content": "已拥有", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "IKfIV", + "name": "Filter 未拥有", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "EeQPp", + "name": "Filter 未拥有 Text", + "fill": "$deep-blue", + "content": "未拥有", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "aXgkW", + "name": "Filter SSR", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Owl7F", + "name": "Filter SSR Text", + "fill": "$deep-blue", + "content": "SSR", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "CTR0b", + "name": "Filter SR", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "w6bi4P", + "name": "Filter SR Text", + "fill": "$deep-blue", + "content": "SR", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "OGkGW", + "name": "Album Grid", + "width": "fill_container", + "height": 460, + "layout": "vertical", + "gap": 10, + "children": [ + { + "type": "frame", + "id": "kN98Q", + "name": "Album Row 1", + "width": "fill_container", + "height": 142, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "KE7GP", + "name": "Album Card 1-1", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "ShCCt", + "name": "Album Art 1-1", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "IF8eU", + "name": "Album Label 1-1", + "fill": "$deep-blue", + "content": "SSR-001", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "TgcGp", + "name": "Album Card 1-2", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "UQeB8", + "name": "Album Art 1-2", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "yHdSo", + "name": "Album Label 1-2", + "fill": "$deep-blue", + "content": "SSR-002", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "N1Hat", + "name": "Album Card 1-3", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "f9jklo", + "name": "Album Art 1-3", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "oz24Q", + "name": "Album Label 1-3", + "fill": "$deep-blue", + "content": "SSR-003", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Wx2jZ", + "name": "Album Row 2", + "width": "fill_container", + "height": 142, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "DhqlF", + "name": "Album Card 2-1", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "MZryz", + "name": "Album Art 2-1", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "q5e9JG", + "name": "Album Label 2-1", + "fill": "$deep-blue", + "content": "SSR-002", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Bj5Ys", + "name": "Album Card 2-2", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "jRCMN", + "name": "Album Art 2-2", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "b6vUr", + "name": "Album Label 2-2", + "fill": "$deep-blue", + "content": "SSR-004", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HERtS", + "name": "Album Card 2-3", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "w6nxZP", + "name": "Album Art 2-3", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "ES9cS", + "name": "Album Label 2-3", + "fill": "$deep-blue", + "content": "SSR-006", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "loYum", + "name": "Album Row 3", + "width": "fill_container", + "height": 142, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "e6ErA", + "name": "Album Card 3-1", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFD9", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "XSqxD", + "name": "Album Art 3-1", + "fill": "#4A90E2", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "MJhu5", + "name": "Album Label 3-1", + "fill": "$deep-blue", + "content": "SSR-003", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "P4jXV6", + "name": "Album Card 3-2", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF99", + "cornerRadius": 16, + "stroke": "#FFFFFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "X1qxm", + "name": "Album Art 3-2", + "fill": "#B8D4F1", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "WFmZe", + "name": "Album Label 3-2", + "fill": "$muted-blue", + "content": "未拥有", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HQgk1", + "name": "Album Card 3-3", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF99", + "cornerRadius": 16, + "stroke": "#FFFFFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 6, + "padding": 8, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "hg7p1", + "name": "Album Art 3-3", + "fill": "#B8D4F1", + "width": "fill_container", + "height": 86 + }, + { + "type": "text", + "id": "sKDpT", + "name": "Album Label 3-3", + "fill": "$muted-blue", + "content": "未拥有", + "fontFamily": "$font-body", + "fontSize": 11, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "WMHQQ", + "name": "Album Empty State Callout", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "A1orK", + "name": "Album Empty Text", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "空系列提示:还没有收集到这一系列的小卡,可从当前卡池获取。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "J2Fpl", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "IknRb", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Av5Jv", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "Hi0vc", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "GjA5k", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "VLEEx", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "lmClh", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "kWx37", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "CCfhI", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "Pkcwz", + "name": "抽卡 Label", + "fill": "$dream-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "pRfXy", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "NgeNO", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "rYivQ", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "S0rvqE", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "P2myr", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "rPfiT", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "rtFE0", + "x": 1298, + "y": 1000, + "name": "Diamond Shop", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "P9BoH8", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "p5r77", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "oMB9i", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "csixm", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "q7Md8", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "sckIc", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "x3Fmd", + "name": "Shop Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "钻石兑换所", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "dglbL", + "name": "Shop Top", + "width": "fill_container", + "height": 42, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "hFLS0", + "name": "My Diamonds", + "height": 30, + "fill": "#D8ECFF", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "oZgVD", + "name": "My Diamonds Text", + "fill": "$deep-blue", + "content": "我的钻石 36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Sulkv", + "name": "Order Entry", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "bBAOc", + "name": "Order Entry Text", + "fill": "$deep-blue", + "content": "我的订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "text", + "id": "aYAKB", + "name": "Shop Notice", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "钻石由活动或后台发放,不支持充值、交易或提现。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "eTkzZ", + "name": "Product List", + "width": "fill_container", + "height": 500, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "zAb1g", + "name": "Product 蓝玫瑰亚克力立牌", + "width": "fill_container", + "height": 150, + "fill": "#FFFFFFD9", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 12, + "padding": 12, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "RKped", + "name": "蓝玫瑰亚克力立牌 Image", + "fill": "#D8ECFF", + "width": 112, + "height": "fill_container" + }, + { + "type": "frame", + "id": "y57jl", + "name": "蓝玫瑰亚克力立牌 Info", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 7, + "children": [ + { + "type": "text", + "id": "wfJeU", + "name": "蓝玫瑰亚克力立牌 Name", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰亚克力立牌", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "nFtQQ", + "name": "蓝玫瑰亚克力立牌 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "库存 18 · 12 钻 · 7-15 个工作日发货", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "tR6fQ", + "name": "蓝玫瑰亚克力立牌 Tag", + "height": 30, + "fill": "#E8F4F8", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "il4nn", + "name": "蓝玫瑰亚克力立牌 Tag Text", + "fill": "$deep-blue", + "content": "限定周边", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "cOTyP", + "name": "Product 纪念徽章套组", + "width": "fill_container", + "height": 150, + "fill": "#FFFFFFD9", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 12, + "padding": 12, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "x9cI7", + "name": "纪念徽章套组 Image", + "fill": "#D8ECFF", + "width": 112, + "height": "fill_container" + }, + { + "type": "frame", + "id": "lC8dP", + "name": "纪念徽章套组 Info", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 7, + "children": [ + { + "type": "text", + "id": "I0kCGY", + "name": "纪念徽章套组 Name", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念徽章套组", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "wGGHC", + "name": "纪念徽章套组 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "库存 6 · 18 钻 · 库存紧张", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "l9lIFM", + "name": "纪念徽章套组 Tag", + "height": 30, + "fill": "#E8F4F8", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "o17yj", + "name": "纪念徽章套组 Tag Text", + "fill": "$deep-blue", + "content": "新品", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "DFT0r", + "name": "Product 月光小卡收纳册", + "width": "fill_container", + "height": 150, + "fill": "#FFFFFFD9", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 12, + "padding": 12, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "fU65G", + "name": "月光小卡收纳册 Image", + "fill": "#D8ECFF", + "width": 112, + "height": "fill_container" + }, + { + "type": "frame", + "id": "BcOAT", + "name": "月光小卡收纳册 Info", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 7, + "children": [ + { + "type": "text", + "id": "Sl5H6", + "name": "月光小卡收纳册 Name", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "月光小卡收纳册", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "Z5wljy", + "name": "月光小卡收纳册 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "库存 28 · 22 钻 · 售后规则可查", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "We73y", + "name": "月光小卡收纳册 Tag", + "height": 30, + "fill": "#E8F4F8", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "K0QtkX", + "name": "月光小卡收纳册 Tag Text", + "fill": "$deep-blue", + "content": "常驻", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "OmpWa", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "bCmoA", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "PPQRH", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "iaq32", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "D0vGP", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "JWyBW", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "W3ZK07", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "N0A57L", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "CmRwS", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "aGeQw", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "VLIs2", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "BSLaO", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "uc6F8", + "name": "商城 Label", + "fill": "$dream-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "LjTIf", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "GUHOD", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "yCYOW", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "vp1MQ", + "x": 32, + "y": 1908, + "name": "Product Detail", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "AFwno", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "djK3J", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "JBR4K", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "benH9", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "N6nTw", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "pKOGT", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "rectangle", + "cornerRadius": 28, + "id": "cARE8", + "name": "Detail Product Image", + "fill": "#D8ECFF", + "width": "fill_container", + "height": 230 + }, + { + "type": "text", + "id": "cBbSW", + "name": "Detail Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰亚克力立牌", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "eu2t7", + "name": "Price Stock Row", + "width": "fill_container", + "height": 34, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "h7BJCo", + "name": "Detail Price", + "height": 30, + "fill": "#D4AF3722", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TbEVK", + "name": "Detail Price Text", + "fill": "$deep-blue", + "content": "需要 12 钻", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "L8DJcJ", + "name": "Detail Stock", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "R5TMm", + "name": "Detail Stock Text", + "fill": "$deep-blue", + "content": "库存 18", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "aTflP", + "name": "Product Specs", + "width": "fill_container", + "height": 144, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "NV0pW", + "name": "Spec 1", + "fill": "$deep-blue", + "content": "规格:15cm · 材质:亚克力", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "ULTmG", + "name": "Spec 2", + "fill": "$ink-blue", + "content": "预计发货:7-15 个工作日", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "MkKUU", + "name": "Spec 3", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "售后规则:发货前可取消,发货后按规则处理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "mmHNA", + "name": "Exchange Confirm", + "width": "fill_container", + "height": 144, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "X8BJiX", + "name": "Address", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "收货地址:上海市浦东新区 · 张小蓝 138****1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "uRPHY", + "name": "Risk Tip", + "fill": "$warning", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "提交后将扣减钻石并锁定库存,生成兑换订单。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "id": "qiX5o", + "type": "ref", + "ref": "PQLHK", + "name": "Redeem Now Button", + "width": "fill_container", + "height": 52, + "descendants": { + "qTI3W": { + "content": "立即兑换" + } + } + } + ] + }, + { + "type": "frame", + "id": "X7VLw", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "Kvl74", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "sjfKl", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "Ow961", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "u8IEqP", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "sV8SB", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "m1Hpt", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "TKclr", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "bxknT", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "tilWT", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fy4Qu", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "Z6KXE", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "y1C6F", + "name": "商城 Label", + "fill": "$dream-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "AhNhZ", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "E0B2W", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "NCrVI", + "name": "我的 Label", + "fill": "$muted-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "BMBC7", + "x": 454, + "y": 1908, + "name": "Order List", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "u4tbQk", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "uAcjY", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "r8nvH9", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "FnBA6", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "W1nqKN", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "fD6cM", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "osCUM", + "name": "Orders Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "兑换订单", + "fontFamily": "$font-heading", + "fontSize": 27, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "Jbgl7", + "name": "Order Filters", + "width": "fill_container", + "height": 36, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "gPeou", + "name": "Order Filter 全部", + "height": 30, + "fill": "#D8ECFF", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "vwLB0", + "name": "Order Filter 全部 Text", + "fill": "$deep-blue", + "content": "全部", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "cOH8O", + "name": "Order Filter 待发货", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "AHi1g", + "name": "Order Filter 待发货 Text", + "fill": "$deep-blue", + "content": "待发货", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "A3UoP6", + "name": "Order Filter 已发货", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "j82tvU", + "name": "Order Filter 已发货 Text", + "fill": "$deep-blue", + "content": "已发货", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "OZ7g1", + "name": "Order Filter 售后中", + "height": 30, + "fill": "#FFFFFFCC", + "cornerRadius": 999, + "gap": 6, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TYEHn", + "name": "Order Filter 售后中 Text", + "fill": "$deep-blue", + "content": "售后中", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "ZJubG", + "name": "Order Cards", + "width": "fill_container", + "height": 450, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "ZCyQf", + "name": "Order 蓝玫瑰亚克力立牌", + "width": "fill_container", + "height": 132, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "p2qkly", + "name": "蓝玫瑰亚克力立牌 Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰亚克力立牌", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "tIMk5", + "name": "蓝玫瑰亚克力立牌 Status", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "消耗 12 钻 · 待发货", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "E7kzuE", + "name": "蓝玫瑰亚克力立牌 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "2026.06.02 · 查看物流 / 联系客服", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "uqMb8", + "name": "Order 纪念徽章套组", + "width": "fill_container", + "height": 132, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "T1m6kW", + "name": "纪念徽章套组 Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念徽章套组", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "qz9OQ", + "name": "纪念徽章套组 Status", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "消耗 18 钻 · 已发货", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "oo5CW", + "name": "纪念徽章套组 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "物流 SF1024 · 查看物流 / 联系客服", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "NTXH5", + "name": "Order 月光小卡收纳册", + "width": "fill_container", + "height": 132, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "ukDwk", + "name": "月光小卡收纳册 Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "月光小卡收纳册", + "fontFamily": "$font-body", + "fontSize": 15, + "fontWeight": "700" + }, + { + "type": "text", + "id": "bShkG", + "name": "月光小卡收纳册 Status", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "消耗 22 钻 · 售后中", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "pVVxD", + "name": "月光小卡收纳册 Meta", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "客服处理中 · 查看物流 / 联系客服", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "QQKQ2", + "name": "Order Empty State", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "K4VMt", + "name": "Order Rule", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "订单状态可追踪,退款将按规则退回钻石并写入流水。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "fdHaq", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "h2sJX2", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "IrZMd", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "EuXxs", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "GjHfJ", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "u9gwPg", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "ghXxK", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "j4VfoU", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "NfsWM", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "nkgXh", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "zZOI2", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "mo8fk", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "j4WAj", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "e9VZds", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "z2NqZ", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "a5zZwl", + "name": "我的 Label", + "fill": "$dream-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "hTLdz", + "x": 876, + "y": 1908, + "name": "Anniversary Page", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "gZfG4", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "E2x2r", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "cRQ0v", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "z3aNC", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "lzVWi", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "gxipq", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "X6HRH", + "name": "Anniversary Hero", + "width": "fill_container", + "height": 184, + "fill": "#FFFFFFCC", + "cornerRadius": 28, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "none", + "children": [ + { + "type": "text", + "id": "Kx1YZ", + "x": 0, + "y": 0, + "name": "Ann Hero Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": 210, + "content": "已陪伴主播 386 天", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "text", + "id": "aS70W", + "x": 0, + "y": 0, + "name": "Join Date", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": 210, + "content": "入团时间 2025.05.12 · 修改", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "ellipse", + "id": "Xt64B", + "x": 252, + "y": 38, + "name": "Rose Decoration", + "fill": "#B8D4F188", + "width": 76, + "height": 76 + }, + { + "type": "icon", + "id": "x7KWEL", + "x": 274, + "y": 58, + "name": "Rose Icon", + "width": 34, + "height": 34, + "icon": "flower-2", + "library": "lucide", + "fill": "$dream-blue" + } + ] + }, + { + "type": "frame", + "id": "mpkGJ", + "name": "System Anniversaries", + "width": "fill_container", + "height": 190, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "iEglA", + "name": "System Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "系统纪念日", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "JsHlL", + "name": "Birthday", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "主播生日 · 距离还有 12 天 · 活动规则已公开", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "Ub9PL", + "name": "Debut", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "开播周年 · 距离还有 42 天 · 关联周年卡池", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "AWRuD", + "name": "Custom Anniversaries", + "width": "fill_container", + "height": 180, + "fill": "#FFFFFFCC", + "cornerRadius": 20, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 14, + "children": [ + { + "type": "text", + "id": "q4gIUS", + "name": "Custom Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "我的纪念日", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "L2G9r", + "name": "Custom 1", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "第一次看直播 · 提前 3 天提醒", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "K4w26", + "name": "Custom 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "第一次抽到 SSR · 已开启提醒", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "l0Mvf", + "name": "Ann Rule", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念日本身不直接发放积分或钻石,奖励以活动规则为准。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "PfJhh", + "type": "ref", + "ref": "PQLHK", + "name": "Add Anniversary Button", + "width": "fill_container", + "height": 52, + "descendants": { + "qTI3W": { + "content": "添加纪念日" + } + } + } + ] + }, + { + "type": "frame", + "id": "zIZeY", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "P9HVNJ", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "lCIKh", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "nXhqW", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "d8Vcz0", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "VjVqU", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "a3eDN", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "u5BFl", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "nZzyI", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "hPimj", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "MPYZw", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "pYrQh", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "iQ4Xc", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "IJqQp", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "MpnPs", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "wnN2k", + "name": "我的 Label", + "fill": "$dream-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "gztrd", + "x": 1298, + "y": 1908, + "name": "Anniversary Edit Modal", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "bxtOj", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "T4In8", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "v67D12", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "S61QaV", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "mgewr", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "ME1uH", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "E2das8", + "name": "Anniversary Page Backdrop", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "KWwLW", + "x": 20, + "y": 86, + "name": "Edit Modal", + "width": 318, + "height": 552, + "fill": "#FFFFFFE6", + "cornerRadius": 30, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": 20, + "children": [ + { + "type": "text", + "id": "BuYMH", + "name": "Edit Modal Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "添加纪念日", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "RwLza", + "name": "Field 纪念日名称:第一次看直播", + "width": "fill_container", + "height": 54, + "fill": "#F7FCFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ydi6R", + "name": "Field Text 纪念日名称:第一次看直播", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念日名称:第一次看直播", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Qr6Iy", + "name": "Field 选择日期:2025.08.20", + "width": "fill_container", + "height": 54, + "fill": "#F7FCFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "w938IF", + "name": "Field Text 选择日期:2025.08.20", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "选择日期:2025.08.20", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "dli5q", + "name": "Field 每年重复:开启", + "width": "fill_container", + "height": 54, + "fill": "#F7FCFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "J8IuJ", + "name": "Field Text 每年重复:开启", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "每年重复:开启", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "jsguJ", + "name": "Field 提醒:提前 3 天", + "width": "fill_container", + "height": 54, + "fill": "#F7FCFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "b4b6iS", + "name": "Field Text 提醒:提前 3 天", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "提醒:提前 3 天", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "kkSGi", + "name": "Field 备注:写下这一天的回忆", + "width": "fill_container", + "height": 54, + "fill": "#F7FCFF", + "cornerRadius": 16, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "padding": [ + 0, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Y5aEn", + "name": "Field Text 备注:写下这一天的回忆", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "备注:写下这一天的回忆", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "qgfAU", + "name": "Edit Permission Rule", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "用户只可编辑入团时间和自定义纪念日;主播生日、开播周年由后台配置。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "H5Htza", + "name": "Modal Buttons", + "width": "fill_container", + "height": 48, + "gap": 10, + "children": [ + { + "id": "o8Tqc1", + "type": "ref", + "ref": "XW7K2", + "name": "Cancel Button", + "width": "fill_container", + "height": "fill_container", + "descendants": { + "lGBJG": { + "content": "取消" + } + } + }, + { + "id": "yQinV", + "type": "ref", + "ref": "PQLHK", + "name": "Save Button", + "width": "fill_container", + "height": "fill_container", + "descendants": { + "qTI3W": { + "content": "保存" + } + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "HJswm", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "X8U1Cz", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "uwK5n", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "ivlJA", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "YTlaf", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "c0USj", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "Q59U7f", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "g1N0V", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "qr4tV", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "u9fAd", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Js9Em", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "n7NC5A", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "J0TRv", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "bl9Em", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "jnzT1", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "iDGiR", + "name": "我的 Label", + "fill": "$dream-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "B2gHa", + "x": 32, + "y": 2816, + "name": "My Page", + "clip": true, + "width": 390, + "height": 844, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 180, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#F7FCFF", + "position": 1 + } + ] + }, + "cornerRadius": 34, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 16, + 14, + 16 + ], + "children": [ + { + "type": "frame", + "id": "tEKzZ", + "name": "Status Bar", + "width": "fill_container", + "height": 52, + "fill": "#00000000", + "padding": [ + 12, + 18 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "HsGX5", + "name": "Time", + "fill": "$deep-blue", + "content": "9:41", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "YUCrI", + "name": "System Icons", + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "aIvdr", + "name": "Signal", + "fill": "#2C5F8D", + "width": 18, + "height": 9 + }, + { + "type": "rectangle", + "cornerRadius": 3, + "id": "S6MgsQ", + "name": "Battery", + "fill": "#2C5F8D", + "width": 24, + "height": 11 + } + ] + } + ] + }, + { + "type": "frame", + "id": "huk2K", + "name": "Content", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "PXADg", + "name": "Profile Header", + "width": "fill_container", + "height": 148, + "fill": "#FFFFFFCC", + "cornerRadius": 28, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 14, + "padding": 18, + "alignItems": "center", + "children": [ + { + "type": "ellipse", + "id": "pKSL3", + "name": "Profile Avatar", + "fill": "#B8D4F1", + "width": 72, + "height": 72 + }, + { + "type": "frame", + "id": "GNZH5", + "name": "Profile Copy", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "text", + "id": "eZV85", + "name": "Profile Name", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝梦粉丝 No.1024", + "fontFamily": "$font-heading", + "fontSize": 24, + "fontWeight": "700" + }, + { + "type": "text", + "id": "Z63VP", + "name": "Profile Level", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "亲密度 Lv.6 · 连续照顾 18 天", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "cs71o", + "name": "My Asset Summary", + "width": "fill_container", + "height": 74, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "mnn1t", + "name": "My Asset 积分", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "padding": 12, + "children": [ + { + "type": "text", + "id": "zwbPv", + "name": "积分 Label", + "fill": "$muted-blue", + "content": "积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "aUu6s", + "name": "积分 Value", + "fill": "$dream-blue", + "content": "1280", + "fontFamily": "$font-body", + "fontSize": 24, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "JayrE", + "name": "My Asset 钻石", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFFCC", + "cornerRadius": 18, + "layout": "vertical", + "gap": 4, + "padding": 12, + "children": [ + { + "type": "text", + "id": "O3Oq9b", + "name": "钻石 Label", + "fill": "$muted-blue", + "content": "钻石", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "n8nDW", + "name": "钻石 Value", + "fill": "$dream-blue", + "content": "36", + "fontFamily": "$font-body", + "fontSize": 24, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "f1kYb", + "name": "My Menu", + "width": "fill_container", + "height": 410, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "wrTpC", + "name": "Menu 积分流水", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "QjAUp", + "name": "Menu Text 积分流水", + "fill": "$deep-blue", + "content": "积分流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "Jvub1", + "name": "Menu Arrow 积分流水", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "X13fxA", + "name": "Menu 钻石流水", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "J0BFI", + "name": "Menu Text 钻石流水", + "fill": "$deep-blue", + "content": "钻石流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "Hnj1c", + "name": "Menu Arrow 钻石流水", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "eono2", + "name": "Menu 抽卡记录", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "b40Vb", + "name": "Menu Text 抽卡记录", + "fill": "$deep-blue", + "content": "抽卡记录", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "IwseG", + "name": "Menu Arrow 抽卡记录", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "FerLK", + "name": "Menu 我的卡册", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "vrg29", + "name": "Menu Text 我的卡册", + "fill": "$deep-blue", + "content": "我的卡册", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "R1IwDg", + "name": "Menu Arrow 我的卡册", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "yQm2u", + "name": "Menu 兑换订单", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "rtzLV", + "name": "Menu Text 兑换订单", + "fill": "$deep-blue", + "content": "兑换订单", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "Y3Vem", + "name": "Menu Arrow 兑换订单", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "UOU4o", + "name": "Menu 地址管理", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ocu0w", + "name": "Menu Text 地址管理", + "fill": "$deep-blue", + "content": "地址管理", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "QAT6c", + "name": "Menu Arrow 地址管理", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "uwrVv", + "name": "Menu 我的纪念日", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "boCJ0", + "name": "Menu Text 我的纪念日", + "fill": "$deep-blue", + "content": "我的纪念日", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "LuwMn", + "name": "Menu Arrow 我的纪念日", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "K8kcTM", + "name": "Menu 客服与申诉", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "DSaIB", + "name": "Menu Text 客服与申诉", + "fill": "$deep-blue", + "content": "客服与申诉", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "yaCb9", + "name": "Menu Arrow 客服与申诉", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "a0x0LP", + "name": "Menu 规则与协议", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "UDcuj", + "name": "Menu Text 规则与协议", + "fill": "$deep-blue", + "content": "规则与协议", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "qURDH", + "name": "Menu Arrow 规则与协议", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + }, + { + "type": "frame", + "id": "AgUbF", + "name": "Menu 抽卡概率规则 / 商城兑换规则 / 未成年人提示", + "width": "fill_container", + "height": 36, + "fill": "#FFFFFFCC", + "cornerRadius": 14, + "padding": [ + 0, + 12 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "u8tM4", + "name": "Menu Text 抽卡概率规则 / 商城兑换规则 / 未成年人提示", + "fill": "$deep-blue", + "content": "抽卡概率规则 / 商城兑换规则 / 未成年人提示", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "icon", + "id": "robtd", + "name": "Menu Arrow 抽卡概率规则 / 商城兑换规则 / 未成年人提示", + "width": 16, + "height": 16, + "icon": "chevron-right", + "library": "lucide", + "fill": "$muted-blue" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "mg4Kn", + "name": "Bottom Tab Bar", + "width": "fill_container", + "height": 72, + "fill": "#FFFFFFD9", + "cornerRadius": 36, + "stroke": "#B8D4F1", + "strokeWidth": 1, + "gap": 4, + "padding": 8, + "children": [ + { + "type": "frame", + "id": "xlMk8", + "name": "Tab 首页", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "hNhTa", + "name": "首页 Icon", + "width": 19, + "height": 19, + "icon": "house", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "g0o0Zf", + "name": "首页 Label", + "fill": "$muted-blue", + "content": "首页", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "QS93w", + "name": "Tab 宠物", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "aw4qg", + "name": "宠物 Icon", + "width": 19, + "height": 19, + "icon": "paw-print", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "yAq5n", + "name": "宠物 Label", + "fill": "$muted-blue", + "content": "宠物", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "NpfKi", + "name": "Tab 抽卡", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "fQdeD", + "name": "抽卡 Icon", + "width": 19, + "height": 19, + "icon": "sparkles", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "kqeYE", + "name": "抽卡 Label", + "fill": "$muted-blue", + "content": "抽卡", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "xKLkE", + "name": "Tab 商城", + "width": "fill_container", + "height": "fill_container", + "fill": "#00000000", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "erEqE", + "name": "商城 Icon", + "width": 19, + "height": 19, + "icon": "shopping-bag", + "library": "lucide", + "fill": "#6F8FB1" + }, + { + "type": "text", + "id": "P2ifI4", + "name": "商城 Label", + "fill": "$muted-blue", + "content": "商城", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "d03fKC", + "name": "Tab 我的", + "width": "fill_container", + "height": "fill_container", + "fill": "#D8ECFF", + "cornerRadius": 28, + "layout": "vertical", + "gap": 2, + "padding": [ + 6, + 4 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "G8MlgW", + "name": "我的 Icon", + "width": 19, + "height": 19, + "icon": "user", + "library": "lucide", + "fill": "#4A90E2" + }, + { + "type": "text", + "id": "eLSpn", + "name": "我的 Label", + "fill": "$dream-blue", + "content": "我的", + "fontFamily": "$font-body", + "fontSize": 10, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "QAN04", + "x": 1880, + "y": 1060, + "name": "02 Admin Workshop Skeleton", + "clip": true, + "width": 1440, + "height": 3380, + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 28, + "padding": 32, + "children": [ + { + "type": "frame", + "id": "J2JhJ", + "name": "Admin Section Header", + "width": "fill_container", + "height": 120, + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": 90, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#E8F4F8", + "position": 0 + }, + { + "color": "#FFFFFF", + "position": 1 + } + ] + }, + "cornerRadius": 24, + "gap": 24, + "padding": 24, + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "z0d4d", + "name": "Admin Header Icon", + "width": 54, + "height": 54, + "icon": "settings", + "library": "lucide", + "fill": "$dream-blue" + }, + { + "type": "frame", + "id": "WT2Jr", + "name": "Admin Header Copy", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "zQpRU", + "name": "Admin Header Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "后台工坊 Review Skeleton", + "fontFamily": "$font-heading", + "fontSize": 32, + "fontWeight": "700" + }, + { + "type": "text", + "id": "iYqKQ", + "name": "Admin Header Subtitle", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "清晰管理端布局,覆盖用户、资产、钻石发放、卡池、商城订单、纪念日配置、风控与审计。", + "fontFamily": "$font-body", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Zoqj5", + "name": "Admin Dashboard", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "jQHOs", + "name": "Admin Dashboard Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "hT4O3", + "name": "Admin Dashboard Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "L0ndt2", + "name": "Admin Dashboard Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "BIkEs", + "name": "Admin Dashboard Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HpgGA", + "name": "Admin Dashboard Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Jdfq1", + "name": "Admin Dashboard Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "q4xck", + "name": "Admin Dashboard Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "I5y7r", + "name": "Admin Dashboard Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "xtGwC", + "name": "Admin Dashboard Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "G985Vz", + "name": "Admin Dashboard Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "u8omI", + "name": "Admin Dashboard Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "wffgF", + "name": "Admin Dashboard Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "bh6bc", + "name": "Admin Dashboard Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "j6LfcA", + "name": "Admin Dashboard Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "y9NFJO", + "name": "Admin Dashboard Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "o3sFu", + "name": "Admin Dashboard Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "OlSny", + "name": "Admin Dashboard Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "kGHJA", + "name": "Admin Dashboard Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "kqrBH", + "name": "Admin Dashboard Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "VHT8H", + "name": "Admin Dashboard Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "JzNgy", + "name": "Admin Dashboard Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "TvCNc", + "name": "Admin Dashboard Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "RrAYc", + "name": "Admin Dashboard Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Hjz5q", + "name": "Admin Dashboard Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "T8ld5", + "name": "Dashboard Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "运营概览", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "x3VkJi", + "name": "Dashboard KPI Row", + "width": "fill_container", + "height": 120, + "gap": 12, + "children": [ + { + "type": "frame", + "id": "p22xg5", + "name": "今日活跃用户", + "width": "fill_container", + "height": 110, + "fill": "#FFFFFF", + "cornerRadius": 18, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "ijtVE", + "name": "今日活跃用户 Label", + "fill": "$muted-blue", + "content": "今日活跃用户", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "SswwJ", + "name": "今日活跃用户 Value", + "fill": "$deep-blue", + "content": "1,286", + "fontFamily": "$font-body", + "fontSize": 30, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "JFZht", + "name": "今日积分发放", + "width": "fill_container", + "height": 110, + "fill": "#FFFFFF", + "cornerRadius": 18, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "F0CGlx", + "name": "今日积分发放 Label", + "fill": "$muted-blue", + "content": "今日积分发放", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "NT6Ld", + "name": "今日积分发放 Value", + "fill": "$dream-blue", + "content": "48,620", + "fontFamily": "$font-body", + "fontSize": 30, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "t1XaQ2", + "name": "今日钻石发放", + "width": "fill_container", + "height": 110, + "fill": "#FFFFFF", + "cornerRadius": 18, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "ncvwS", + "name": "今日钻石发放 Label", + "fill": "$muted-blue", + "content": "今日钻石发放", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "c3Azbn", + "name": "今日钻石发放 Value", + "fill": "$rose-gold", + "content": "920", + "fontFamily": "$font-body", + "fontSize": 30, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "TwfkY", + "name": "待发货订单", + "width": "fill_container", + "height": 110, + "fill": "#FFFFFF", + "cornerRadius": 18, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "qwHZb", + "name": "待发货订单 Label", + "fill": "$muted-blue", + "content": "待发货订单", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "vnyPC", + "name": "待发货订单 Value", + "fill": "$warning", + "content": "38", + "fontFamily": "$font-body", + "fontSize": 30, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "U5i6O", + "name": "风控待处理", + "width": "fill_container", + "height": 110, + "fill": "#FFFFFF", + "cornerRadius": 18, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "layout": "vertical", + "gap": 8, + "padding": 18, + "children": [ + { + "type": "text", + "id": "neSsR", + "name": "风控待处理 Label", + "fill": "$muted-blue", + "content": "风控待处理", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "drUkC", + "name": "风控待处理 Value", + "fill": "$danger", + "content": "7", + "fontFamily": "$font-body", + "fontSize": 30, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "supyA", + "name": "Dashboard Body", + "width": "fill_container", + "height": "fill_container", + "gap": 14, + "children": [ + { + "type": "frame", + "id": "LDQYp", + "name": "Current Pool Status", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 10, + "padding": 16, + "children": [ + { + "type": "text", + "id": "qITsU", + "name": "Pool Status Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "当前卡池状态", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "X1QMmY", + "name": "Pool Status Text", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰周年限定 · v3 · 06.01-06.30 · 单抽 100 积分", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "yNbpx", + "name": "Pool Rule Text", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "概率变更必须创建新版本;前台展示概率、保底、重复卡规则;卡池不可设置空奖。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "xtW09", + "name": "Stock and Risk Alerts", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 10, + "padding": 16, + "children": [ + { + "type": "text", + "id": "U3YbW", + "name": "Alerts Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "库存与风控", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "x4bqDY", + "name": "Alert 纪念徽章套组库存 6,建议补货或下架", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念徽章套组库存 6,建议补货或下架", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "dAIdB", + "name": "Alert 同地址多账号兑换命中 3 条", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "同地址多账号兑换命中 3 条", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "W0BD8N", + "name": "Alert 小游戏异常高分待复核 4 条", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "小游戏异常高分待复核 4 条", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "NH7VT", + "name": "User Asset Management", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "pBwxS", + "name": "User Asset Management Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "owJYl", + "name": "User Asset Management Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "wTj6l", + "name": "User Asset Management Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "cdNDr", + "name": "User Asset Management Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "qKWlb", + "name": "User Asset Management Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "npmfg", + "name": "User Asset Management Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "g6Jwb", + "name": "User Asset Management Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "NAw0G", + "name": "User Asset Management Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "E6kQO", + "name": "User Asset Management Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TEYiO", + "name": "User Asset Management Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "wON9o", + "name": "User Asset Management Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "JIrXO", + "name": "User Asset Management Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "VlVDL", + "name": "User Asset Management Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "aSU7H", + "name": "User Asset Management Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "F3FNcp", + "name": "User Asset Management Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "o6QAFh", + "name": "User Asset Management Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "RYVg7", + "name": "User Asset Management Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "evSwg", + "name": "User Asset Management Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "oJUjO", + "name": "User Asset Management Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Liatu", + "name": "User Asset Management Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "o6BTN", + "name": "User Asset Management Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "N09CO", + "name": "User Asset Management Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "jM7S6", + "name": "User Asset Management Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "r07Rh", + "name": "User Asset Management Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "iNr2c", + "name": "User Asset Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "用户与资产管理", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "c7kzbR", + "name": "User Filters", + "width": "fill_container", + "height": 50, + "gap": 10, + "children": [ + { + "type": "frame", + "id": "dCsDw", + "name": "Filter 用户ID / 手机号", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 12, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "k8ukk", + "name": "Filter Text 用户ID / 手机号", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "用户ID / 手机号", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "XNPxj", + "name": "Filter 风控状态", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 12, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qT6xp", + "name": "Filter Text 风控状态", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "风控状态", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "NPpAd", + "name": "Filter 资产类型", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 12, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "SC0KQ", + "name": "Filter Text 资产类型", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "资产类型", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "cuo83", + "name": "Filter 最近登录", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 12, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "FiqOy", + "name": "Filter Text 最近登录", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "最近登录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "h4meH", + "name": "User Asset Table", + "width": "fill_container", + "height": 244, + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "FvzAe", + "name": "User Table Header", + "width": "fill_container", + "height": 48, + "fill": "#E8F4F8", + "children": [ + { + "type": "frame", + "id": "Gxx3K", + "name": "Header 用户ID 0", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "rJqFQ", + "name": "Text 用户ID 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "用户ID", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Z0wSCW", + "name": "Header 昵称 0", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LrXYO", + "name": "Text 昵称 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "昵称", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HI7Fj", + "name": "Header 积分 0", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "GSQKC", + "name": "Text 积分 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "ITQxx", + "name": "Header 钻石 0", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "AHnBP", + "name": "Text 钻石 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "钻石", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "ATGYA", + "name": "Header 卡牌 0", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "I2n9ar", + "name": "Text 卡牌 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "卡牌", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Z3C6Bc", + "name": "Header 亲密度 0", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "HT6al", + "name": "Text 亲密度 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "亲密度", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "FOC4j", + "name": "Header 最近登录 0", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "SSYQO", + "name": "Text 最近登录 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "最近登录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "QMlsR", + "name": "Header 风控 0", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "izAJx", + "name": "Text 风控 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "风控", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "wlFkN", + "name": "Header 操作 0", + "width": 140, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "xRPuG", + "name": "Text 操作 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "操作", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Y49rXL", + "name": "User Table Row 1", + "width": "fill_container", + "height": 48, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "xPnuY", + "name": "Cell 用户ID 1", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "T5AmA", + "name": "Text 用户ID 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "U1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "YZaE7", + "name": "Cell 昵称 1", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "eI07f", + "name": "Text 昵称 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝梦粉丝", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "U8Mf1", + "name": "Cell 积分 1", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "t6kuC", + "name": "Text 积分 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "1280", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "pnFOK", + "name": "Cell 钻石 1", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "XhPsN", + "name": "Text 钻石 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "vXrez", + "name": "Cell 卡牌 1", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "IobWW", + "name": "Text 卡牌 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36/80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "RcVc8", + "name": "Cell 亲密度 1", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "p9GZ2", + "name": "Text 亲密度 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Lv.6", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "W2m5A", + "name": "Cell 最近登录 1", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "V0gH9i", + "name": "Text 最近登录 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06-02 09:41", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "rs03c", + "name": "Cell 风控 1", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Z2CA5", + "name": "Text 风控 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "正常", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "aKgip", + "name": "Cell 操作 1", + "width": 140, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "xbEno", + "name": "Text 操作 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "流水 / 发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "S9ZWZu", + "name": "User Table Row 2", + "width": "fill_container", + "height": 48, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "weltv", + "name": "Cell 用户ID 2", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "z2Cmjt", + "name": "Text 用户ID 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "U1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "LR7IZ", + "name": "Cell 昵称 2", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "L9dtTP", + "name": "Text 昵称 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝梦粉丝", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "g4zTs", + "name": "Cell 积分 2", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "QRN1i", + "name": "Text 积分 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "1280", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "t5oo4u", + "name": "Cell 钻石 2", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "mGekE", + "name": "Text 钻石 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "GF1Wa", + "name": "Cell 卡牌 2", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "b6ZNq", + "name": "Text 卡牌 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36/80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "HgQmn", + "name": "Cell 亲密度 2", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Icmhl", + "name": "Text 亲密度 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Lv.6", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ncagz", + "name": "Cell 最近登录 2", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ZOUKq", + "name": "Text 最近登录 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06-02 09:41", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "K8MIB", + "name": "Cell 风控 2", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "mZEl3", + "name": "Text 风控 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "正常", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "lasrL", + "name": "Cell 操作 2", + "width": 140, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "hwXyC", + "name": "Text 操作 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "流水 / 发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Rxlwr", + "name": "User Table Row 3", + "width": "fill_container", + "height": 48, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "VFYUO", + "name": "Cell 用户ID 3", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Cqy3j", + "name": "Text 用户ID 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "U1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "NeXEv", + "name": "Cell 昵称 3", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "m33JUT", + "name": "Text 昵称 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝梦粉丝", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "N9OK93", + "name": "Cell 积分 3", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "aCjTx", + "name": "Text 积分 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "1280", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "AoQJ2", + "name": "Cell 钻石 3", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "G8GhB", + "name": "Text 钻石 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "OUJ4x", + "name": "Cell 卡牌 3", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "JSuLS", + "name": "Text 卡牌 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36/80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "v7sDn", + "name": "Cell 亲密度 3", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "w7Buw", + "name": "Text 亲密度 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Lv.6", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "R9Vbnq", + "name": "Cell 最近登录 3", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TaoHL", + "name": "Text 最近登录 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06-02 09:41", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "E6dHC5", + "name": "Cell 风控 3", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Xsxsu", + "name": "Text 风控 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "观察", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "v1CWci", + "name": "Cell 操作 3", + "width": 140, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "zjn3b", + "name": "Text 操作 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "流水 / 发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "hoaev", + "name": "User Table Row 4", + "width": "fill_container", + "height": 48, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "TKRtZ", + "name": "Cell 用户ID 4", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "og5ju", + "name": "Text 用户ID 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "U1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "p4HEaT", + "name": "Cell 昵称 4", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "IEiOq", + "name": "Text 昵称 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝梦粉丝", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "OERQq", + "name": "Cell 积分 4", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "v0Lue7", + "name": "Text 积分 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "1280", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ijcgC", + "name": "Cell 钻石 4", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "C3w5H", + "name": "Text 钻石 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "LN8F4", + "name": "Cell 卡牌 4", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "A0KWd", + "name": "Text 卡牌 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "36/80", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "oc7h0", + "name": "Cell 亲密度 4", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "RSbiP", + "name": "Text 亲密度 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Lv.6", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ZaBVk", + "name": "Cell 最近登录 4", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "iQRmI", + "name": "Text 最近登录 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06-02 09:41", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Rb6o9", + "name": "Cell 风控 4", + "width": 100, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "B5yD3l", + "name": "Text 风控 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "正常", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "looew", + "name": "Cell 操作 4", + "width": 140, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "r91GiD", + "name": "Text 操作 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "流水 / 发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "wfd5E", + "name": "Diamond Grant Workflow", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "m2lQrX", + "name": "Diamond Grant Workflow Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "U2rC1", + "name": "Diamond Grant Workflow Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "G9iYZ", + "name": "Diamond Grant Workflow Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "W7uCt", + "name": "Diamond Grant Workflow Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "z7gVo", + "name": "Diamond Grant Workflow Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "L7Rwmw", + "name": "Diamond Grant Workflow Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "RCBRs", + "name": "Diamond Grant Workflow Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "I846rB", + "name": "Diamond Grant Workflow Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "CUDnJ", + "name": "Diamond Grant Workflow Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "lyqGX", + "name": "Diamond Grant Workflow Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "EbqC6", + "name": "Diamond Grant Workflow Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "k7lTl", + "name": "Diamond Grant Workflow Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "x348f", + "name": "Diamond Grant Workflow Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "s8Ldp", + "name": "Diamond Grant Workflow Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "tQUUB", + "name": "Diamond Grant Workflow Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "GJTCj", + "name": "Diamond Grant Workflow Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "jTgXT", + "name": "Diamond Grant Workflow Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "PgWvu", + "name": "Diamond Grant Workflow Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "el44E", + "name": "Diamond Grant Workflow Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "PRY2n", + "name": "Diamond Grant Workflow Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "f9uXe", + "name": "Diamond Grant Workflow Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "TMRB0", + "name": "Diamond Grant Workflow Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "pcjbA", + "name": "Diamond Grant Workflow Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "B8OiVK", + "name": "Diamond Grant Workflow Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "N90Wj", + "name": "Grant Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "钻石发放审批", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "QuQGC", + "name": "Grant Form Area", + "width": "fill_container", + "height": "fill_container", + "gap": 14, + "children": [ + { + "type": "frame", + "id": "YhZVj", + "name": "Grant Form", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 10, + "padding": 16, + "children": [ + { + "type": "frame", + "id": "V6y8TO", + "name": "Grant Field 发放对象:U1024 / 蓝梦粉丝 / 手机尾号 1024", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "RXQYk", + "name": "Grant Field Text 发放对象:U1024 / 蓝梦粉丝 / 手机尾号 1024", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "发放对象:U1024 / 蓝梦粉丝 / 手机尾号 1024", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "au2jB", + "name": "Grant Field 资产类型:钻石", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "gBy8h", + "name": "Grant Field Text 资产类型:钻石", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "资产类型:钻石", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "TZNm2", + "name": "Grant Field 发放数量:20", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "tasrJ", + "name": "Grant Field Text 发放数量:20", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "发放数量:20", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ss0jh", + "name": "Grant Field 发放原因:周年活动奖励", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ffXro", + "name": "Grant Field Text 发放原因:周年活动奖励", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "发放原因:周年活动奖励", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Y8Sqxo", + "name": "Grant Field 审批状态:待主管复核", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "cFmQO", + "name": "Grant Field Text 审批状态:待主管复核", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "审批状态:待主管复核", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "r2oqLv", + "name": "Grant Field 审批备注:线下活动补偿名单第 2 批", + "width": "fill_container", + "height": 42, + "fill": "#F7FCFF", + "cornerRadius": 10, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "padding": [ + 0, + 12 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qS713", + "name": "Grant Field Text 审批备注:线下活动补偿名单第 2 批", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "审批备注:线下活动补偿名单第 2 批", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "nAXF9", + "name": "Grant Risk Panel", + "width": 360, + "height": "fill_container", + "fill": "#FFF8E6", + "cornerRadius": 16, + "stroke": "#D4AF37", + "strokeWidth": 1, + "layout": "vertical", + "gap": 10, + "padding": 16, + "children": [ + { + "type": "text", + "id": "x93AL", + "name": "Grant Risk Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "发放前校验", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "SzYzl", + "name": "Grant Risk 钻石仅由后台发放,不支持充值、转赠、提现或现金兑换。", + "fill": "$warning", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "钻石仅由后台发放,不支持充值、转赠、提现或现金兑换。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + }, + { + "type": "text", + "id": "z9KzR0", + "name": "Grant Risk 提交后生成发放单号、资产流水号和操作审计编号。", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "提交后生成发放单号、资产流水号和操作审计编号。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + }, + { + "type": "text", + "id": "i67jI", + "name": "Grant Risk 大额或频繁发放需要二次确认。", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "大额或频繁发放需要二次确认。", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "MLP6a", + "name": "Card Pool Management", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "L4Nw3Q", + "name": "Card Pool Management Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "f8hSD6", + "name": "Card Pool Management Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "ADKXC", + "name": "Card Pool Management Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "lrckB", + "name": "Card Pool Management Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Nzgsw", + "name": "Card Pool Management Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Qev7x", + "name": "Card Pool Management Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HrH2X", + "name": "Card Pool Management Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "NWw1P", + "name": "Card Pool Management Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "lueQb", + "name": "Card Pool Management Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "KQPnL", + "name": "Card Pool Management Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "intNn", + "name": "Card Pool Management Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "FObOS", + "name": "Card Pool Management Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "d2zRV", + "name": "Card Pool Management Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "T9YGui", + "name": "Card Pool Management Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "v1XyvV", + "name": "Card Pool Management Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "v8Pwc", + "name": "Card Pool Management Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "PgrqE", + "name": "Card Pool Management Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "bYtTc", + "name": "Card Pool Management Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "n9S5jI", + "name": "Card Pool Management Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LCxM4", + "name": "Card Pool Management Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "mVg5k", + "name": "Card Pool Management Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "SV0Cz", + "name": "Card Pool Management Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "WS8xN", + "name": "Card Pool Management Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "I9zUt", + "name": "Card Pool Management Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "NPgwC", + "name": "Card Pool Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "卡池与抽卡日志", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "POA00", + "name": "Pool Management Table", + "width": "fill_container", + "height": 256, + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "D1jPBs", + "name": "Pool Header", + "width": "fill_container", + "height": 52, + "fill": "#E8F4F8", + "children": [ + { + "type": "frame", + "id": "k0fOM", + "name": "Pool Header 卡池名称 0", + "width": 190, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "l437U", + "name": "Pool Text 卡池名称 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "卡池名称", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "jq1cd", + "name": "Pool Header 版本 0", + "width": 70, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Z5UOt", + "name": "Pool Text 版本 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "版本", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "H5kbU", + "name": "Pool Header 活动时间 0", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qnKYQ", + "name": "Pool Text 活动时间 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "活动时间", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "lOh2c", + "name": "Pool Header 单抽 0", + "width": 80, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "SpY1Z", + "name": "Pool Text 单抽 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "单抽", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "cUL92", + "name": "Pool Header 概率 0", + "width": 210, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "I8ehkB", + "name": "Pool Text 概率 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "概率", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "KQArm", + "name": "Pool Header 保底 0", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "KfZJB", + "name": "Pool Text 保底 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "保底", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "b4I8N", + "name": "Pool Header 重复规则 0", + "width": 160, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "h5vMsX", + "name": "Pool Text 重复规则 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "重复规则", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "menwJ", + "name": "Pool Header 状态 0", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "IqADV", + "name": "Pool Text 状态 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "状态", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "tNDrV", + "name": "Pool Header 操作 0", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "D9XRup", + "name": "Pool Text 操作 0", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "操作", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "DKjnJ", + "name": "Pool Row 1", + "width": "fill_container", + "height": 52, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "TARZ4", + "name": "Pool Cell 卡池名称 1", + "width": 190, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "V3BABw", + "name": "Pool Text 卡池名称 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰周年限定", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "i8OZg", + "name": "Pool Cell 版本 1", + "width": 70, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "gyRzF", + "name": "Pool Text 版本 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "v3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "rb6vQ", + "name": "Pool Cell 活动时间 1", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "iyIrJ", + "name": "Pool Text 活动时间 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06.01-06.30", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "r2OZd", + "name": "Pool Cell 单抽 1", + "width": 80, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "a1oDS", + "name": "Pool Text 单抽 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "100", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "dDnSl", + "name": "Pool Cell 概率 1", + "width": 210, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qjuXa", + "name": "Pool Text 概率 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "SSR2 / SR12 / R36 / N50", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "n3GELG", + "name": "Pool Cell 保底 1", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Egoen", + "name": "Pool Text 保底 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "60抽SSR", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "PX6hi", + "name": "Pool Cell 重复规则 1", + "width": 160, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "VdjPX", + "name": "Pool Text 重复规则 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "碎片或积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "X6nwLm", + "name": "Pool Cell 状态 1", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "W9vjj", + "name": "Pool Text 状态 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "上架", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "F80YY", + "name": "Pool Cell 操作 1", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "YRktT", + "name": "Pool Text 操作 1", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "日志 / 复制版本", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "C40FO", + "name": "Pool Row 2", + "width": "fill_container", + "height": 52, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "rpkzM", + "name": "Pool Cell 卡池名称 2", + "width": 190, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "IFw1W", + "name": "Pool Text 卡池名称 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰周年限定", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "sSyLp", + "name": "Pool Cell 版本 2", + "width": 70, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "fcSbs", + "name": "Pool Text 版本 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "v3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "CPTKd", + "name": "Pool Cell 活动时间 2", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "dhRQt", + "name": "Pool Text 活动时间 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06.01-06.30", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "VyWX0", + "name": "Pool Cell 单抽 2", + "width": 80, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "jomuj", + "name": "Pool Text 单抽 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "100", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "P6XCK", + "name": "Pool Cell 概率 2", + "width": 210, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "h0KJ5", + "name": "Pool Text 概率 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "SSR2 / SR12 / R36 / N50", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "SplTW", + "name": "Pool Cell 保底 2", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "lbXDf", + "name": "Pool Text 保底 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "60抽SSR", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "vX5Ox", + "name": "Pool Cell 重复规则 2", + "width": 160, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LLmKk", + "name": "Pool Text 重复规则 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "碎片或积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "LlTBV", + "name": "Pool Cell 状态 2", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "z08Pp", + "name": "Pool Text 状态 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "上架", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fhecN", + "name": "Pool Cell 操作 2", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "yfD1s", + "name": "Pool Text 操作 2", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "日志 / 复制版本", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "XZkzy", + "name": "Pool Row 3", + "width": "fill_container", + "height": 52, + "fill": "#FFFFFF", + "children": [ + { + "type": "frame", + "id": "BdGyf", + "name": "Pool Cell 卡池名称 3", + "width": 190, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "pYigT", + "name": "Pool Text 卡池名称 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰周年限定", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "aTWaW", + "name": "Pool Cell 版本 3", + "width": 70, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "gDeqF", + "name": "Pool Text 版本 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "v3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "b6ChjT", + "name": "Pool Cell 活动时间 3", + "width": 150, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "a7Wwhy", + "name": "Pool Text 活动时间 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "06.01-06.30", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Gniov", + "name": "Pool Cell 单抽 3", + "width": 80, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "k1EME", + "name": "Pool Text 单抽 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "100", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Y16GJ", + "name": "Pool Cell 概率 3", + "width": 210, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "B0I2E", + "name": "Pool Text 概率 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "SSR2 / SR12 / R36 / N50", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "WPUUb", + "name": "Pool Cell 保底 3", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "l4S2P", + "name": "Pool Text 保底 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "60抽SSR", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "RN8kA", + "name": "Pool Cell 重复规则 3", + "width": 160, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "iaOHo", + "name": "Pool Text 重复规则 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "碎片或积分", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fkyza", + "name": "Pool Cell 状态 3", + "width": 90, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "yymyx", + "name": "Pool Text 状态 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "上架", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "yQeSR", + "name": "Pool Cell 操作 3", + "width": 120, + "height": "fill_container", + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "dHzUN", + "name": "Pool Text 操作 3", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "日志 / 复制版本", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "text", + "id": "NVCu3", + "name": "Pool Compliance Callout", + "fill": "$warning", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "概率或卡牌范围变更需创建新卡池版本,不可覆盖历史版本;前台必须展示概率、保底、重复处理与无空奖说明。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Ig7l9", + "name": "Shop Order Management", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "LZQs8", + "name": "Shop Order Management Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "bMyE4", + "name": "Shop Order Management Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "avh9W", + "name": "Shop Order Management Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "GXoAj", + "name": "Shop Order Management Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "J73q0", + "name": "Shop Order Management Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "isrLZ", + "name": "Shop Order Management Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "dgeR8", + "name": "Shop Order Management Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "A1N4gJ", + "name": "Shop Order Management Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "NOINy", + "name": "Shop Order Management Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "H91IyW", + "name": "Shop Order Management Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "qyjV5", + "name": "Shop Order Management Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LYVGg", + "name": "Shop Order Management Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "sPjXH", + "name": "Shop Order Management Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Z8XaPK", + "name": "Shop Order Management Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "gxchH", + "name": "Shop Order Management Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "J0Ryut", + "name": "Shop Order Management Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "D7Dr4", + "name": "Shop Order Management Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "nUPwC", + "name": "Shop Order Management Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "jCO9p", + "name": "Shop Order Management Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "f5KhWA", + "name": "Shop Order Management Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "O6t2nH", + "name": "Shop Order Management Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "QZ106", + "name": "Shop Order Management Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "hvypv", + "name": "Shop Order Management Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "U6CzwC", + "name": "Shop Order Management Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "w8Qvno", + "name": "Shop Order Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "商城商品与兑换订单", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "P4zXjA", + "name": "Shop Order Split", + "width": "fill_container", + "height": "fill_container", + "gap": 14, + "children": [ + { + "type": "frame", + "id": "Y6uaX", + "name": "SKU Management", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 8, + "padding": 16, + "children": [ + { + "type": "text", + "id": "PNCxt", + "name": "SKU Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "商品库存", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "jSI7m", + "name": "SKU 蓝玫瑰亚克力立牌 · 12 钻 · 可用 18 · 7-15 天发货", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "蓝玫瑰亚克力立牌 · 12 钻 · 可用 18 · 7-15 天发货", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "HvxCw", + "name": "SKU 纪念徽章套组 · 18 钻 · 可用 6 · 库存紧张", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念徽章套组 · 18 钻 · 可用 6 · 库存紧张", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "PwDPZ", + "name": "SKU 月光小卡收纳册 · 22 钻 · 可用 28 · 常驻", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "月光小卡收纳册 · 22 钻 · 可用 28 · 常驻", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "wEhea", + "name": "Order Operations", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 8, + "padding": 16, + "children": [ + { + "type": "text", + "id": "KrwLL", + "name": "Order Ops Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "订单处理", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "nUxmR", + "name": "Order Ops 待发货 38 · 已发货 62 · 售后中 4", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "待发货 38 · 已发货 62 · 售后中 4", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "M3PNCL", + "name": "Order Ops 订单状态:待发货 / 已发货 / 已完成 / 售后中 / 已取消 / 已退款", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "订单状态:待发货 / 已发货 / 已完成 / 售后中 / 已取消 / 已退款", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "W8iSGD", + "name": "Order Ops 退款或取消必须写入钻石流水和订单审计。", + "fill": "$warning", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "退款或取消必须写入钻石流水和订单审计。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "SohuW", + "name": "Anniversary Configuration", + "clip": true, + "width": "fill_container", + "height": 500, + "fill": "#FFFFFF", + "cornerRadius": 20, + "stroke": "#D8ECFF", + "strokeWidth": 1, + "children": [ + { + "type": "frame", + "id": "hVawi", + "name": "Anniversary Configuration Sidebar", + "width": 220, + "height": "fill_container", + "fill": "#E8F4F8", + "layout": "vertical", + "gap": 6, + "padding": 18, + "children": [ + { + "type": "text", + "id": "D7RkqU", + "name": "Anniversary Configuration Brand", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "VTuber Mini 工坊", + "fontFamily": "$font-body", + "fontSize": 17, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "hgtHL", + "name": "Anniversary Configuration Nav 概览", + "width": "fill_container", + "height": 34, + "fill": "#FFFFFFCC", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "zFoEk", + "name": "Anniversary Configuration Nav Text 概览", + "fill": "$dream-blue", + "content": "概览", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "J5h5W", + "name": "Anniversary Configuration Nav 用户管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "slV20", + "name": "Anniversary Configuration Nav Text 用户管理", + "fill": "$ink-blue", + "content": "用户管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "qCMVl", + "name": "Anniversary Configuration Nav 资产管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "WEqla", + "name": "Anniversary Configuration Nav Text 资产管理", + "fill": "$ink-blue", + "content": "资产管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "Kz60M", + "name": "Anniversary Configuration Nav 钻石发放", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "q4j5A", + "name": "Anniversary Configuration Nav Text 钻石发放", + "fill": "$ink-blue", + "content": "钻石发放", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "V9DfII", + "name": "Anniversary Configuration Nav 卡池管理", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "b4ZEzD", + "name": "Anniversary Configuration Nav Text 卡池管理", + "fill": "$ink-blue", + "content": "卡池管理", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "LRli5", + "name": "Anniversary Configuration Nav 商城订单", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "a213m6", + "name": "Anniversary Configuration Nav Text 商城订单", + "fill": "$ink-blue", + "content": "商城订单", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "VjZPg", + "name": "Anniversary Configuration Nav 纪念日配置", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "aVR5A", + "name": "Anniversary Configuration Nav Text 纪念日配置", + "fill": "$ink-blue", + "content": "纪念日配置", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "W6mDd", + "name": "Anniversary Configuration Nav 风控记录", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "cFBcv", + "name": "Anniversary Configuration Nav Text 风控记录", + "fill": "$ink-blue", + "content": "风控记录", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "O0wu4v", + "name": "Anniversary Configuration Nav 操作审计", + "width": "fill_container", + "height": 34, + "fill": "#00000000", + "cornerRadius": 10, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Az9US", + "name": "Anniversary Configuration Nav Text 操作审计", + "fill": "$ink-blue", + "content": "操作审计", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + } + ] + }, + { + "type": "frame", + "id": "PSO2W", + "name": "Anniversary Configuration Main", + "width": "fill_container", + "height": "fill_container", + "fill": "#F7FCFF", + "layout": "vertical", + "gap": 14, + "padding": 18, + "children": [ + { + "type": "frame", + "id": "o3VhzR", + "name": "Anniversary Configuration Top Bar", + "width": "fill_container", + "height": 46, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "K9tLzu", + "name": "Anniversary Configuration Search", + "fill": "$muted-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索用户 / 订单 / 流水", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "p0SrA1", + "name": "Anniversary Configuration Operator", + "fill": "$deep-blue", + "content": "运营账号:LunaOps · 审批待办 6 · 风控告警 3", + "fontFamily": "$font-body", + "fontSize": 12, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "MCRkL", + "name": "Ann Config Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "纪念日配置与审计", + "fontFamily": "$font-heading", + "fontSize": 28, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "nG4fJ", + "name": "Ann Config Content", + "width": "fill_container", + "height": "fill_container", + "gap": 14, + "children": [ + { + "type": "frame", + "id": "xzoTC", + "name": "System Anniversary Config", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 8, + "padding": 16, + "children": [ + { + "type": "text", + "id": "d3kRj", + "name": "System Config Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "系统纪念日", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "LT401", + "name": "System Config 主播生日 · 08.20 · 每年重复 · 站内提醒", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "主播生日 · 08.20 · 每年重复 · 站内提醒", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "b7YCA", + "name": "System Config 开播周年 · 11.12 · 每年重复 · 关联周年卡池", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "开播周年 · 11.12 · 每年重复 · 关联周年卡池", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "F9dDt7", + "name": "System Config 提醒策略:当天 09:00 / 提前 1 天 / 提前 3 天 / 提前 7 天", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "提醒策略:当天 09:00 / 提前 1 天 / 提前 3 天 / 提前 7 天", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "o7XCE", + "name": "Audit Trail Panel", + "width": "fill_container", + "height": "fill_container", + "fill": "#FFFFFF", + "cornerRadius": 16, + "layout": "vertical", + "gap": 8, + "padding": 16, + "children": [ + { + "type": "text", + "id": "sYuPl", + "name": "Audit Title", + "fill": "$deep-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "操作审计", + "fontFamily": "$font-heading", + "fontSize": 22, + "fontWeight": "700" + }, + { + "type": "text", + "id": "tzF5x", + "name": "Audit 修改系统纪念日配置需记录操作人、原因、时间和变更前后。", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "修改系统纪念日配置需记录操作人、原因、时间和变更前后。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "PEECa", + "name": "Audit 关联抽卡或商城活动时,前台必须展示活动规则、概率或兑换条件。", + "fill": "$warning", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "关联抽卡或商城活动时,前台必须展示活动规则、概率或兑换条件。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + }, + { + "type": "text", + "id": "N2Wai", + "name": "Audit 高风险操作:修改概率、批量发放、订单退款、系统纪念日变更。", + "fill": "$ink-blue", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "高风险操作:修改概率、批量发放、订单退款、系统纪念日变更。", + "fontFamily": "$font-body", + "fontSize": 13, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "variables": { + "crystal-blue": { + "type": "color", + "value": "#B8D4F1" + }, + "danger": { + "type": "color", + "value": "#B95B6B" + }, + "deep-blue": { + "type": "color", + "value": "#2C5F8D" + }, + "dream-blue": { + "type": "color", + "value": "#4A90E2" + }, + "font-body": { + "type": "string", + "value": "Inter" + }, + "font-data": { + "type": "string", + "value": "Geist Mono" + }, + "font-heading": { + "type": "string", + "value": "Playfair Display" + }, + "glass-blue": { + "type": "color", + "value": "#D8ECFFCC" + }, + "glass-fill": { + "type": "color", + "value": "#FFFFFFCC" + }, + "ink-blue": { + "type": "color", + "value": "#163B63" + }, + "moon-white": { + "type": "color", + "value": "#E8F4F8" + }, + "muted-blue": { + "type": "color", + "value": "#6F8FB1" + }, + "night-blue": { + "type": "color", + "value": "#102A57" + }, + "radius-card": { + "type": "number", + "value": 20 + }, + "radius-pill": { + "type": "number", + "value": 999 + }, + "rose-gold": { + "type": "color", + "value": "#D4AF37" + }, + "soft-white": { + "type": "color", + "value": "#F7FCFF" + }, + "space-lg": { + "type": "number", + "value": 24 + }, + "space-md": { + "type": "number", + "value": 16 + }, + "space-sm": { + "type": "number", + "value": 8 + }, + "success": { + "type": "color", + "value": "#3BAA8A" + }, + "warning": { + "type": "color", + "value": "#B8872E" + } + } +} \ No newline at end of file diff --git a/vtubermini/.gitignore b/vtubermini/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/vtubermini/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/vtubermini/PRODUCT.md b/vtubermini/PRODUCT.md new file mode 100644 index 0000000..3ec5a95 --- /dev/null +++ b/vtubermini/PRODUCT.md @@ -0,0 +1,33 @@ +# Product + +## Register + +product + +## Users + +VTuber Mini is for fans of a single virtual streamer who return daily to care for the themed pet, collect virtual cards, check anniversaries, and redeem creator merchandise. + +## Product Purpose + +The app combines daily fan interaction, pet-care progression, card collection, anniversary reminders, and merchandise exchange into a repeatable Android app template for one streamer at a time. + +## Brand Personality + +Dreamlike, intimate, collectible. The UI should feel like a cool blue private fan space with gentle companionship and small moments of ceremony. + +## Anti-references + +Avoid generic ecommerce/admin blue-white screens, gambling-like reward pressure, full-screen noisy draw effects, and glass effects that reduce readability. + +## Design Principles + +- Keep the home screen emotionally immersive while preserving clear entry points. +- Treat anniversaries and pet care as companion moments, not utility calendar widgets. +- Make points, diamonds, dates, and rules immediately readable even on decorative surfaces. +- Use restrained crystal, moonlight, and rose-gold accents to signal value without visual clutter. +- Preserve mobile ergonomics over poster-like composition. + +## Accessibility & Inclusion + +Use high-contrast text for all critical numbers and labels, keep motion short and non-blocking, support reduced-motion preferences, and avoid relying on color alone for state such as elapsed versus remaining days. diff --git a/vtubermini/README.md b/vtubermini/README.md new file mode 100644 index 0000000..7dbf7eb --- /dev/null +++ b/vtubermini/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/vtubermini/android/.gitignore b/vtubermini/android/.gitignore new file mode 100644 index 0000000..48354a3 --- /dev/null +++ b/vtubermini/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/vtubermini/android/app/.gitignore b/vtubermini/android/app/.gitignore new file mode 100644 index 0000000..043df80 --- /dev/null +++ b/vtubermini/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/vtubermini/android/app/build.gradle b/vtubermini/android/app/build.gradle new file mode 100644 index 0000000..5774e84 --- /dev/null +++ b/vtubermini/android/app/build.gradle @@ -0,0 +1,54 @@ +apply plugin: 'com.android.application' + +android { + namespace = "com.vtubermini.app" + compileSdk = rootProject.ext.compileSdkVersion + defaultConfig { + applicationId "com.vtubermini.app" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" + implementation project(':capacitor-android') + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation project(':capacitor-cordova-android-plugins') +} + +apply from: 'capacitor.build.gradle' + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") +} diff --git a/vtubermini/android/app/capacitor.build.gradle b/vtubermini/android/app/capacitor.build.gradle new file mode 100644 index 0000000..bbfb44f --- /dev/null +++ b/vtubermini/android/app/capacitor.build.gradle @@ -0,0 +1,19 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/vtubermini/android/app/proguard-rules.pro b/vtubermini/android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/vtubermini/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/vtubermini/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/vtubermini/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f2c2217 --- /dev/null +++ b/vtubermini/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.getcapacitor.app", appContext.getPackageName()); + } +} diff --git a/vtubermini/android/app/src/main/AndroidManifest.xml b/vtubermini/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b06ddbf --- /dev/null +++ b/vtubermini/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vtubermini/android/app/src/main/java/com/vtubermini/app/MainActivity.java b/vtubermini/android/app/src/main/java/com/vtubermini/app/MainActivity.java new file mode 100644 index 0000000..b9cce00 --- /dev/null +++ b/vtubermini/android/app/src/main/java/com/vtubermini/app/MainActivity.java @@ -0,0 +1,54 @@ +package com.vtubermini.app; + +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; + +import com.getcapacitor.BridgeActivity; + +public class MainActivity extends BridgeActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + enableFullscreen(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if (hasFocus) { + enableFullscreen(); + } + } + + private void enableFullscreen() { + Window window = getWindow(); + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.setDecorFitsSystemWindows(false); + WindowInsetsController controller = window.getInsetsController(); + + if (controller != null) { + controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()); + controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + return; + } + + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + ); + } +} diff --git a/vtubermini/android/app/src/main/res/drawable-land-hdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-land-hdpi/splash.png new file mode 100644 index 0000000..9ea7c32 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-land-hdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08cc34ad7713fe7ed58bceaa37b2387b670c53cd60264b4bd6442db3098e75dc +size 7705 diff --git a/vtubermini/android/app/src/main/res/drawable-land-mdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-land-mdpi/splash.png new file mode 100644 index 0000000..d22a036 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-land-mdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cf98b4451bd99b20df26f9e608a46946118be6b0ae90762f9ca1786a30c76ff +size 4040 diff --git a/vtubermini/android/app/src/main/res/drawable-land-xhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-land-xhdpi/splash.png new file mode 100644 index 0000000..8385258 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-land-xhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22f87e1e3bc89aa01a7dbc39c9a4db058cd0bf4ad3fe9f55712bf69eb997f4bf +size 9251 diff --git a/vtubermini/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-land-xxhdpi/splash.png new file mode 100644 index 0000000..e3e9746 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-land-xxhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42aa26392546fcdee1b8d3ac6d4b41bfcceb41dc6a4f3a3c30c24a8a8f4db862 +size 13984 diff --git a/vtubermini/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-land-xxxhdpi/splash.png new file mode 100644 index 0000000..a5a2a82 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-land-xxxhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60393ce8636fd263e4e1fea3fd4ab2de948c6295e898fda9b50ac4e5283be809 +size 17683 diff --git a/vtubermini/android/app/src/main/res/drawable-port-hdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-port-hdpi/splash.png new file mode 100644 index 0000000..eee1353 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-port-hdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5015f4ba3628392b538386c5e210f0b94f352a3160adab934fd0311972137ca +size 7934 diff --git a/vtubermini/android/app/src/main/res/drawable-port-mdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-port-mdpi/splash.png new file mode 100644 index 0000000..b6ef6ae --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-port-mdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07fa579e1c83e04ba7f9cbcbfcf41b68e15fe3638f2c44a04e58b809103e6b69 +size 4096 diff --git a/vtubermini/android/app/src/main/res/drawable-port-xhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-port-xhdpi/splash.png new file mode 100644 index 0000000..6b6b43f --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-port-xhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b73049cb37fe76d6c11b87a796766bf6af0c85483b31eb6a921657b0d764a4b9 +size 9875 diff --git a/vtubermini/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-port-xxhdpi/splash.png new file mode 100644 index 0000000..ca2ebb6 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-port-xxhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c7f1212f25b7b90e9a6e1d320013e4ff3d3e03e634cbb07b7b7981cac51627f +size 13346 diff --git a/vtubermini/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/vtubermini/android/app/src/main/res/drawable-port-xxxhdpi/splash.png new file mode 100644 index 0000000..229a07d --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-port-xxxhdpi/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3db071a03b2f8ffe0dfd4170fc59842d53cd15bba5e88af59401d58efabf7827 +size 17489 diff --git a/vtubermini/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/vtubermini/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/vtubermini/android/app/src/main/res/drawable/ic_launcher_background.xml b/vtubermini/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vtubermini/android/app/src/main/res/drawable/splash.png b/vtubermini/android/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000..d22a036 --- /dev/null +++ b/vtubermini/android/app/src/main/res/drawable/splash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cf98b4451bd99b20df26f9e608a46946118be6b0ae90762f9ca1786a30c76ff +size 4040 diff --git a/vtubermini/android/app/src/main/res/layout/activity_main.xml b/vtubermini/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b5ad138 --- /dev/null +++ b/vtubermini/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..20bdb78 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72b71c3581ca3b5a23b1c168d69b9d855b3f184fa079902a01f088eb4f0607d5 +size 2786 diff --git a/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2921a27 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32baa10d2632a4417454a579f992bd640e0a3cec79321423559b2c9940de58a9 +size 3450 diff --git a/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..c408d69 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfcc1b0fa931b14bb241372c76ab4f04374b67d02363c98d9cb12edfdacdf5f3 +size 4341 diff --git a/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..f9fc785 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27ed3603010ebc278f64f8645741ab132ff517abb5308eb9df6c8e42a48956b2 +size 1869 diff --git a/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..d9b6cd6 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58e78a618778926b1f6d9472a6468de878de8530970934e94aab5ba4ba08cc00 +size 2110 diff --git a/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..a3f1938 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0166fc333074c373fbd0ce6b5defd71552166165ac778121ca9c9dff6b83f0fc +size 2725 diff --git a/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..c8ef54e --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d35dbfff175b83c13ef59cf924abfc810f7b6a158595d7417c5498ea8c7c7ed1 +size 3981 diff --git a/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f5e9e8a --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f88083b8166cc559102f7044688de7525287632ebe09ac45d001ac8bf4b3eae +size 5036 diff --git a/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..64b1207 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40911a00922868686854a4804b93fd6e56b503664696de03f450bff690affb6d +size 6593 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..9d887a4 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed346eb1e3f0280f15709393705899b3ff55c20b88f4e0308006b3c33cf5fe14 +size 6644 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3214685 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a82bc1e9923576275869998925ce0ae021a79aa18b24a0dd87ad6b61ca85053 +size 9793 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7d45792 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ee4cd9ff371dcb2e3938097e434f6fb8731688ed7165e61fc63693ad5b2f455 +size 10455 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..d689a58 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87cb2f2ffe992652bb4fa768c73719a37b5852ab17fbf8e170e888f7a42b0761 +size 9441 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7f3ddc5 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd24fd383253bf8d43f0a81f11c071d76d1d555114376dd647cd9fb38fa0a9da +size 15529 diff --git a/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..fe11355 --- /dev/null +++ b/vtubermini/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab93096331e7cd8ec379f73f1e9adcaaa9ee1115c9f4ff10411a811fb9700174 +size 15916 diff --git a/vtubermini/android/app/src/main/res/values/ic_launcher_background.xml b/vtubermini/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/vtubermini/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/vtubermini/android/app/src/main/res/values/strings.xml b/vtubermini/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..09b3207 --- /dev/null +++ b/vtubermini/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + VTuber Mini + VTuber Mini + com.vtubermini.app + com.vtubermini.app + diff --git a/vtubermini/android/app/src/main/res/values/styles.xml b/vtubermini/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..ba87a45 --- /dev/null +++ b/vtubermini/android/app/src/main/res/values/styles.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/vtubermini/android/app/src/main/res/xml/file_paths.xml b/vtubermini/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..bd0c4d8 --- /dev/null +++ b/vtubermini/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vtubermini/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/vtubermini/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java new file mode 100644 index 0000000..0297327 --- /dev/null +++ b/vtubermini/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/vtubermini/android/build.gradle b/vtubermini/android/build.gradle new file mode 100644 index 0000000..f8f0e43 --- /dev/null +++ b/vtubermini/android/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'com.google.gms:google-services:4.4.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/vtubermini/android/capacitor.settings.gradle b/vtubermini/android/capacitor.settings.gradle new file mode 100644 index 0000000..9a5fa87 --- /dev/null +++ b/vtubermini/android/capacitor.settings.gradle @@ -0,0 +1,3 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') diff --git a/vtubermini/android/gradle.properties b/vtubermini/android/gradle.properties new file mode 100644 index 0000000..2e87c52 --- /dev/null +++ b/vtubermini/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/vtubermini/android/gradle/wrapper/gradle-wrapper.jar b/vtubermini/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 zcma&OWmKeVvL#I6?i3D%6z=Zs?ofE*?rw#G$eqJB ziT4y8-Y@s9rkH0Tz>ll(^xkcTl)CY?rS&9VNd66Yc)g^6)JcWaY(5$5gt z8gr3SBXUTN;~cBgz&})qX%#!Fxom2Yau_`&8)+6aSN7YY+pS410rRUU*>J}qL0TnJ zRxt*7QeUqTh8j)Q&iavh<}L+$Jqz))<`IfKussVk%%Ah-Ti?Eo0hQH!rK%K=#EAw0 zwq@@~XNUXRnv8$;zv<6rCRJ6fPD^hfrh;0K?n z=p!u^3xOgWZ%f3+?+>H)9+w^$Tn1e;?UpVMJb!!;f)`6f&4|8mr+g)^@x>_rvnL0< zvD0Hu_N>$(Li7|Jgu0mRh&MV+<}`~Wi*+avM01E)Jtg=)-vViQKax!GeDc!xv$^mL z{#OVBA$U{(Zr8~Xm|cP@odkHC*1R8z6hcLY#N@3E-A8XEvpt066+3t9L_6Zg6j@9Q zj$$%~yO-OS6PUVrM2s)(T4#6=JpI_@Uz+!6=GdyVU?`!F=d;8#ZB@(5g7$A0(`eqY z8_i@3w$0*es5mrSjhW*qzrl!_LQWs4?VfLmo1Sd@Ztt53+etwzAT^8ow_*7Jp`Y|l z*UgSEwvxq+FYO!O*aLf-PinZYne7Ib6ny3u>MjQz=((r3NTEeU4=-i0LBq3H-VJH< z^>1RE3_JwrclUn9vb7HcGUaFRA0QHcnE;6)hnkp%lY1UII#WPAv?-;c?YH}LWB8Nl z{sx-@Z;QxWh9fX8SxLZk8;kMFlGD3Jc^QZVL4nO)1I$zQwvwM&_!kW+LMf&lApv#< zur|EyC|U@5OQuph$TC_ZU`{!vJp`13e9alaR0Dbn5ikLFH7>eIz4QbV|C=%7)F=qo z_>M&5N)d)7G(A%c>}UCrW!Ql_6_A{?R7&CL`;!KOb3 z8Z=$YkV-IF;c7zs{3-WDEFJzuakFbd*4LWd<_kBE8~BFcv}js_2OowRNzWCtCQ6&k z{&~Me92$m*@e0ANcWKuz)?YjB*VoSTx??-3Cc0l2U!X^;Bv@m87eKHukAljrD54R+ zE;@_w4NPe1>3`i5Qy*3^E9x#VB6?}v=~qIprrrd5|DFkg;v5ixo0IsBmik8=Y;zv2 z%Bcf%NE$a44bk^`i4VwDLTbX=q@j9;JWT9JncQ!+Y%2&HHk@1~*L8-{ZpY?(-a9J-1~<1ltr9i~D9`P{XTIFWA6IG8c4;6bFw*lzU-{+?b&%OcIoCiw00n>A1ra zFPE$y@>ebbZlf(sN_iWBzQKDV zmmaLX#zK!@ZdvCANfwV}9@2O&w)!5gSgQzHdk2Q`jG6KD7S+1R5&F)j6QTD^=hq&7 zHUW+r^da^%V(h(wonR(j?BOiC!;y=%nJvz?*aW&5E87qq;2z`EI(f zBJNNSMFF9U{sR-af5{IY&AtoGcoG)Iq-S^v{7+t0>7N(KRoPj;+2N5;9o_nxIGjJ@ z7bYQK)bX)vEhy~VL%N6g^NE@D5VtV+Q8U2%{ji_=6+i^G%xeskEhH>Sqr194PJ$fB zu1y^){?9Vkg(FY2h)3ZHrw0Z<@;(gd_dtF#6y_;Iwi{yX$?asr?0N0_B*CifEi7<6 zq`?OdQjCYbhVcg+7MSgIM|pJRu~`g?g3x?Tl+V}#$It`iD1j+!x+!;wS0+2e>#g?Z z*EA^k7W{jO1r^K~cD#5pamp+o@8&yw6;%b|uiT?{Wa=4+9<}aXWUuL#ZwN1a;lQod zW{pxWCYGXdEq9qAmvAB904}?97=re$>!I%wxPV#|f#@A*Y=qa%zHlDv^yWbR03%V0 zprLP+b(#fBqxI%FiF*-n8HtH6$8f(P6!H3V^ysgd8de-N(@|K!A< z^qP}jp(RaM9kQ(^K(U8O84?D)aU(g?1S8iWwe)gqpHCaFlJxb*ilr{KTnu4_@5{K- z)n=CCeCrPHO0WHz)dDtkbZfUfVBd?53}K>C5*-wC4hpDN8cGk3lu-ypq+EYpb_2H; z%vP4@&+c2p;thaTs$dc^1CDGlPG@A;yGR5@$UEqk6p58qpw#7lc<+W(WR;(vr(D>W z#(K$vE#uBkT=*q&uaZwzz=P5mjiee6>!lV?c}QIX%ZdkO1dHg>Fa#xcGT6~}1*2m9 zkc7l3ItD6Ie~o_aFjI$Ri=C!8uF4!Ky7iG9QTrxVbsQroi|r)SAon#*B*{}TB-?=@ z8~jJs;_R2iDd!$+n$%X6FO&PYS{YhDAS+U2o4su9x~1+U3z7YN5o0qUK&|g^klZ6X zj_vrM5SUTnz5`*}Hyts9ADwLu#x_L=nv$Z0`HqN`Zo=V>OQI)fh01n~*a%01%cx%0 z4LTFVjmW+ipVQv5rYcn3;d2o4qunWUY!p+?s~X~(ost@WR@r@EuDOSs8*MT4fiP>! zkfo^!PWJJ1MHgKS2D_hc?Bs?isSDO61>ebl$U*9*QY(b=i&rp3@3GV@z>KzcZOxip z^dzA~44;R~cnhWz7s$$v?_8y-k!DZys}Q?4IkSyR!)C0j$(Gm|t#e3|QAOFaV2}36 z?dPNY;@I=FaCwylc_;~kXlZsk$_eLkNb~TIl8QQ`mmH&$*zwwR8zHU*sId)rxHu*K z;yZWa8UmCwju%aSNLwD5fBl^b0Ux1%q8YR*uG`53Mi<`5uA^Dc6Ync)J3N7;zQ*75)hf%a@{$H+%S?SGT)ks60)?6j$ zspl|4Ad6@%-r1t*$tT(en!gIXTUDcsj?28ZEzz)dH)SV3bZ+pjMaW0oc~rOPZP@g! zb9E+ndeVO_Ib9c_>{)`01^`ZS198 z)(t=+{Azi11$eu%aU7jbwuQrO`vLOixuh~%4z@mKr_Oc;F%Uq01fA)^W&y+g16e?rkLhTxV!EqC%2}sx_1u7IBq|}Be&7WI z4I<;1-9tJsI&pQIhj>FPkQV9{(m!wYYV@i5h?A0#BN2wqlEwNDIq06|^2oYVa7<~h zI_OLan0Do*4R5P=a3H9`s5*>xU}_PSztg`+2mv)|3nIy=5#Z$%+@tZnr> zLcTI!Mxa`PY7%{;KW~!=;*t)R_sl<^b>eNO@w#fEt(tPMg_jpJpW$q_DoUlkY|uo> z0-1{ouA#;t%spf*7VjkK&$QrvwUERKt^Sdo)5@?qAP)>}Y!h4(JQ!7{wIdkA+|)bv z&8hBwoX4v|+fie}iTslaBX^i*TjwO}f{V)8*!dMmRPi%XAWc8<_IqK1jUsApk)+~R zNFTCD-h>M5Y{qTQ&0#j@I@tmXGj%rzhTW5%Bkh&sSc=$Fv;M@1y!zvYG5P2(2|(&W zlcbR1{--rJ&s!rB{G-sX5^PaM@3EqWVz_y9cwLR9xMig&9gq(voeI)W&{d6j1jh&< zARXi&APWE1FQWh7eoZjuP z;vdgX>zep^{{2%hem;e*gDJhK1Hj12nBLIJoL<=0+8SVEBx7!4Ea+hBY;A1gBwvY<)tj~T=H`^?3>zeWWm|LAwo*S4Z%bDVUe z6r)CH1H!(>OH#MXFJ2V(U(qxD{4Px2`8qfFLG+=a;B^~Te_Z!r3RO%Oc#ZAHKQxV5 zRYXxZ9T2A%NVJIu5Pu7!Mj>t%YDO$T@M=RR(~mi%sv(YXVl`yMLD;+WZ{vG9(@P#e zMo}ZiK^7^h6TV%cG+;jhJ0s>h&VERs=tuZz^Tlu~%d{ZHtq6hX$V9h)Bw|jVCMudd zwZ5l7In8NT)qEPGF$VSKg&fb0%R2RnUnqa){)V(X(s0U zkCdVZe6wy{+_WhZh3qLp245Y2RR$@g-!9PjJ&4~0cFSHMUn=>dapv)hy}|y91ZWTV zCh=z*!S3_?`$&-eZ6xIXUq8RGl9oK0BJw*TdU6A`LJqX9eS3X@F)g$jLkBWFscPhR zpCv8#KeAc^y>>Y$k^=r|K(DTC}T$0#jQBOwB#@`P6~*IuW_8JxCG}J4va{ zsZzt}tt+cv7=l&CEuVtjD6G2~_Meh%p4RGuY?hSt?(sreO_F}8r7Kp$qQdvCdZnDQ zxzc*qchE*E2=WK)^oRNa>Ttj`fpvF-JZ5tu5>X1xw)J@1!IqWjq)ESBG?J|ez`-Tc zi5a}GZx|w-h%5lNDE_3ho0hEXMoaofo#Z;$8|2;EDF&*L+e$u}K=u?pb;dv$SXeQM zD-~7P0i_`Wk$#YP$=hw3UVU+=^@Kuy$>6?~gIXx636jh{PHly_a2xNYe1l60`|y!7 z(u%;ILuW0DDJ)2%y`Zc~hOALnj1~txJtcdD#o4BCT68+8gZe`=^te6H_egxY#nZH&P*)hgYaoJ^qtmpeea`35Fw)cy!w@c#v6E29co8&D9CTCl%^GV|X;SpneSXzV~LXyRn-@K0Df z{tK-nDWA!q38M1~`xUIt_(MO^R(yNY#9@es9RQbY@Ia*xHhD&=k^T+ zJi@j2I|WcgW=PuAc>hs`(&CvgjL2a9Rx zCbZyUpi8NWUOi@S%t+Su4|r&UoU|ze9SVe7p@f1GBkrjkkq)T}X%Qo1g!SQ{O{P?m z-OfGyyWta+UCXH+-+(D^%kw#A1-U;?9129at7MeCCzC{DNgO zeSqsV>W^NIfTO~4({c}KUiuoH8A*J!Cb0*sp*w-Bg@YfBIPZFH!M}C=S=S7PLLcIG zs7K77g~W)~^|+mx9onzMm0qh(f~OsDTzVmRtz=aZTllgR zGUn~_5hw_k&rll<4G=G+`^Xlnw;jNYDJz@bE?|r866F2hA9v0-8=JO3g}IHB#b`hy zA42a0>{0L7CcabSD+F7?pGbS1KMvT{@1_@k!_+Ki|5~EMGt7T%u=79F)8xEiL5!EJ zzuxQ`NBliCoJMJdwu|);zRCD<5Sf?Y>U$trQ-;xj6!s5&w=9E7)%pZ+1Nh&8nCCwM zv5>Ket%I?cxr3vVva`YeR?dGxbG@pi{H#8@kFEf0Jq6~K4>kt26*bxv=P&jyE#e$| zDJB_~imk^-z|o!2njF2hL*|7sHCnzluhJjwLQGDmC)Y9 zr9ZN`s)uCd^XDvn)VirMgW~qfn1~SaN^7vcX#K1G`==UGaDVVx$0BQnubhX|{e z^i0}>k-;BP#Szk{cFjO{2x~LjK{^Upqd&<+03_iMLp0$!6_$@TbX>8U-f*-w-ew1?`CtD_0y_Lo|PfKi52p?`5$Jzx0E8`M0 zNIb?#!K$mM4X%`Ry_yhG5k@*+n4||2!~*+&pYLh~{`~o(W|o64^NrjP?-1Lgu?iK^ zTX6u3?#$?R?N!{599vg>G8RGHw)Hx&=|g4599y}mXNpM{EPKKXB&+m?==R3GsIq?G zL5fH={=zawB(sMlDBJ+{dgb)Vx3pu>L=mDV0{r1Qs{0Pn%TpopH{m(By4;{FBvi{I z$}x!Iw~MJOL~&)p93SDIfP3x%ROjg}X{Sme#hiJ&Yk&a;iR}V|n%PriZBY8SX2*;6 z4hdb^&h;Xz%)BDACY5AUsV!($lib4>11UmcgXKWpzRL8r2Srl*9Y(1uBQsY&hO&uv znDNff0tpHlLISam?o(lOp#CmFdH<6HmA0{UwfU#Y{8M+7od8b8|B|7ZYR9f<#+V|ZSaCQvI$~es~g(Pv{2&m_rKSB2QQ zMvT}$?Ll>V+!9Xh5^iy3?UG;dF-zh~RL#++roOCsW^cZ&({6q|?Jt6`?S8=16Y{oH zp50I7r1AC1(#{b`Aq5cw>ypNggHKM9vBx!W$eYIzD!4KbLsZGr2o8>g<@inmS3*>J zx8oG((8f!ei|M@JZB`p7+n<Q}?>h249<`7xJ?u}_n;Gq(&km#1ULN87CeTO~FY zS_Ty}0TgQhV zOh3T7{{x&LSYGQfKR1PDIkP!WnfC1$l+fs@Di+d4O=eVKeF~2fq#1<8hEvpwuqcaH z4A8u~r^gnY3u6}zj*RHjk{AHhrrDqaj?|6GaVJbV%o-nATw}ASFr!f`Oz|u_QPkR# z0mDudY1dZRlk@TyQ?%Eti=$_WNFtLpSx9=S^be{wXINp%MU?a`F66LNU<c;0&ngifmP9i;bj6&hdGMW^Kf8e6ZDXbQD&$QAAMo;OQ)G zW(qlHh;}!ZP)JKEjm$VZjTs@hk&4{?@+NADuYrr!R^cJzU{kGc1yB?;7mIyAWwhbeA_l_lw-iDVi7wcFurf5 z#Uw)A@a9fOf{D}AWE%<`s1L_AwpZ?F!Vac$LYkp<#A!!`XKaDC{A%)~K#5z6>Hv@V zBEqF(D5?@6r3Pwj$^krpPDCjB+UOszqUS;b2n>&iAFcw<*im2(b3|5u6SK!n9Sg4I z0KLcwA6{Mq?p%t>aW0W!PQ>iUeYvNjdKYqII!CE7SsS&Rj)eIw-K4jtI?II+0IdGq z2WT|L3RL?;GtGgt1LWfI4Ka`9dbZXc$TMJ~8#Juv@K^1RJN@yzdLS8$AJ(>g!U9`# zx}qr7JWlU+&m)VG*Se;rGisutS%!6yybi%B`bv|9rjS(xOUIvbNz5qtvC$_JYY+c& za*3*2$RUH8p%pSq>48xR)4qsp!Q7BEiJ*`^>^6INRbC@>+2q9?x(h0bpc>GaNFi$K zPH$6!#(~{8@0QZk=)QnM#I=bDx5vTvjm$f4K}%*s+((H2>tUTf==$wqyoI`oxI7>C z&>5fe)Yg)SmT)eA(|j@JYR1M%KixxC-Eceknf-;N=jJTwKvk#@|J^&5H0c+%KxHUI z6dQbwwVx3p?X<_VRVb2fStH?HH zFR@Mp=qX%#L3XL)+$PXKV|o|#DpHAoqvj6uQKe@M-mnhCSou7Dj4YuO6^*V`m)1lf z;)@e%1!Qg$10w8uEmz{ENb$^%u}B;J7sDd zump}onoD#!l=agcBR)iG!3AF0-63%@`K9G(CzKrm$VJ{v7^O9Ps7Zej|3m= zVXlR&yW6=Y%mD30G@|tf=yC7-#L!16Q=dq&@beWgaIL40k0n% z)QHrp2Jck#evLMM1RGt3WvQ936ZC9vEje0nFMfvmOHVI+&okB_K|l-;|4vW;qk>n~ z+|kk8#`K?x`q>`(f6A${wfw9Cx(^)~tX7<#TpxR#zYG2P+FY~mG{tnEkv~d6oUQA+ z&hNTL=~Y@rF`v-RZlts$nb$3(OL1&@Y11hhL9+zUb6)SP!;CD)^GUtUpCHBE`j1te zAGud@miCVFLk$fjsrcpjsadP__yj9iEZUW{Ll7PPi<$R;m1o!&Xdl~R_v0;oDX2z^!&8}zNGA}iYG|k zmehMd1%?R)u6R#<)B)1oe9TgYH5-CqUT8N7K-A-dm3hbm_W21p%8)H{O)xUlBVb+iUR}-v5dFaCyfSd zC6Bd7=N4A@+Bna=!-l|*_(nWGDpoyU>nH=}IOrLfS+-d40&(Wo*dDB9nQiA2Tse$R z;uq{`X7LLzP)%Y9aHa4YQ%H?htkWd3Owv&UYbr5NUDAH^<l@Z0Cx%`N+B*i!!1u>D8%;Qt1$ zE5O0{-`9gdDxZ!`0m}ywH!;c{oBfL-(BH<&SQ~smbcobU!j49O^f4&IIYh~f+hK*M zZwTp%{ZSAhMFj1qFaOA+3)p^gnXH^=)`NTYgTu!CLpEV2NF=~-`(}7p^Eof=@VUbd z_9U|8qF7Rueg&$qpSSkN%%%DpbV?8E8ivu@ensI0toJ7Eas^jyFReQ1JeY9plb^{m z&eQO)qPLZQ6O;FTr*aJq=$cMN)QlQO@G&%z?BKUs1&I^`lq>=QLODwa`(mFGC`0H< zOlc*|N?B5&!U6BuJvkL?s1&nsi$*5cCv7^j_*l&$-sBmRS85UIrE--7eD8Gr3^+o? zqG-Yl4S&E;>H>k^a0GdUI(|n1`ws@)1%sq2XBdK`mqrNq_b4N{#VpouCXLzNvjoFv zo9wMQ6l0+FT+?%N(ka*;%m~(?338bu32v26!{r)|w8J`EL|t$}TA4q_FJRX5 zCPa{hc_I(7TGE#@rO-(!$1H3N-C0{R$J=yPCXCtGk{4>=*B56JdXU9cQVwB`6~cQZ zf^qK21x_d>X%dT!!)CJQ3mlHA@ z{Prkgfs6=Tz%63$6Zr8CO0Ak3A)Cv#@BVKr&aiKG7RYxY$Yx>Bj#3gJk*~Ps-jc1l z;4nltQwwT4@Z)}Pb!3xM?+EW0qEKA)sqzw~!C6wd^{03-9aGf3Jmt=}w-*!yXupLf z;)>-7uvWN4Unn8b4kfIza-X=x*e4n5pU`HtgpFFd))s$C@#d>aUl3helLom+RYb&g zI7A9GXLRZPl}iQS*d$Azxg-VgcUr*lpLnbPKUV{QI|bsG{8bLG<%CF( zMoS4pRDtLVYOWG^@ox^h8xL~afW_9DcE#^1eEC1SVSb1BfDi^@g?#f6e%v~Aw>@w- zIY0k+2lGWNV|aA*e#`U3=+oBDmGeInfcL)>*!w|*;mWiKNG6wP6AW4-4imN!W)!hE zA02~S1*@Q`fD*+qX@f3!2yJX&6FsEfPditB%TWo3=HA;T3o2IrjS@9SSxv%{{7&4_ zdS#r4OU41~GYMiib#z#O;zohNbhJknrPPZS6sN$%HB=jUnlCO_w5Gw5EeE@KV>soy z2EZ?Y|4RQDDjt5y!WBlZ(8M)|HP<0YyG|D%RqD+K#e7-##o3IZxS^wQ5{Kbzb6h(i z#(wZ|^ei>8`%ta*!2tJzwMv+IFHLF`zTU8E^Mu!R*45_=ccqI};Zbyxw@U%a#2}%f zF>q?SrUa_a4H9l+uW8JHh2Oob>NyUwG=QH~-^ZebU*R@67DcXdz2{HVB4#@edz?B< z5!rQH3O0>A&ylROO%G^fimV*LX7>!%re{_Sm6N>S{+GW1LCnGImHRoF@csnFzn@P0 zM=jld0z%oz;j=>c7mMwzq$B^2mae7NiG}%>(wtmsDXkWk{?BeMpTrIt3Mizq?vRsf zi_WjNp+61uV(%gEU-Vf0;>~vcDhe(dzWdaf#4mH3o^v{0EWhj?E?$5v02sV@xL0l4 zX0_IMFtQ44PfWBbPYN#}qxa%=J%dlR{O!KyZvk^g5s?sTNycWYPJ^FK(nl3k?z-5t z39#hKrdO7V(@!TU)LAPY&ngnZ1MzLEeEiZznn7e-jLCy8LO zu^7_#z*%I-BjS#Pg-;zKWWqX-+Ly$T!4`vTe5ZOV0j?TJVA*2?*=82^GVlZIuH%9s zXiV&(T(QGHHah=s&7e|6y?g+XxZGmK55`wGV>@1U)Th&=JTgJq>4mI&Av2C z)w+kRoj_dA!;SfTfkgMPO>7Dw6&1*Hi1q?54Yng`JO&q->^CX21^PrU^JU#CJ_qhV zSG>afB%>2fx<~g8p=P8Yzxqc}s@>>{g7}F!;lCXvF#RV)^fyYb_)iKVCz1xEq=fJ| z0a7DMCK*FuP=NM*5h;*D`R4y$6cpW-E&-i{v`x=Jbk_xSn@2T3q!3HoAOB`@5Vg6) z{PW|@9o!e;v1jZ2{=Uw6S6o{g82x6g=k!)cFSC*oemHaVjg?VpEmtUuD2_J^A~$4* z3O7HsbA6wxw{TP5Kk)(Vm?gKo+_}11vbo{Tp_5x79P~#F)ahQXT)tSH5;;14?s)On zel1J>1x>+7;g1Iz2FRpnYz;sD0wG9Q!vuzE9yKi3@4a9Nh1!GGN?hA)!mZEnnHh&i zf?#ZEN2sFbf~kV;>K3UNj1&vFhc^sxgj8FCL4v>EOYL?2uuT`0eDH}R zmtUJMxVrV5H{L53hu3#qaWLUa#5zY?f5ozIn|PkMWNP%n zWB5!B0LZB0kLw$k39=!akkE9Q>F4j+q434jB4VmslQ;$ zKiO#FZ`p|dKS716jpcvR{QJkSNfDVhr2%~eHrW;fU45>>snr*S8Vik-5eN5k*c2Mp zyxvX&_cFbB6lODXznHHT|rsURe2!swomtrqc~w5 zymTM8!w`1{04CBprR!_F{5LB+2_SOuZN{b*!J~1ZiPpP-M;);!ce!rOPDLtgR@Ie1 zPreuqm4!H)hYePcW1WZ0Fyaqe%l}F~Orr)~+;mkS&pOhP5Ebb`cnUt!X_QhP4_4p( z8YKQCDKGIy>?WIFm3-}Br2-N`T&FOi?t)$hjphB9wOhBXU#Hb+zm&We_-O)s(wc`2 z8?VsvU;J>Ju7n}uUb3s1yPx_F*|FlAi=Ge=-kN?1;`~6szP%$3B0|8Sqp%ebM)F8v zADFrbeT0cgE>M0DMV@_Ze*GHM>q}wWMzt|GYC%}r{OXRG3Ij&<+nx9;4jE${Fj_r* z`{z1AW_6Myd)i6e0E-h&m{{CvzH=Xg!&(bLYgRMO_YVd8JU7W+7MuGWNE=4@OvP9+ zxi^vqS@5%+#gf*Z@RVyU9N1sO-(rY$24LGsg1>w>s6ST^@)|D9>cT50maXLUD{Fzf zt~tp{OSTEKg3ZSQyQQ5r51){%=?xlZ54*t1;Ow)zLe3i?8tD8YyY^k%M)e`V*r+vL zPqUf&m)U+zxps+NprxMHF{QSxv}>lE{JZETNk1&F+R~bp{_T$dbXL2UGnB|hgh*p4h$clt#6;NO~>zuyY@C-MD@)JCc5XrYOt`wW7! z_ti2hhZBMJNbn0O-uTxl_b6Hm313^fG@e;RrhIUK9@# z+DHGv_Ow$%S8D%RB}`doJjJy*aOa5mGHVHz0e0>>O_%+^56?IkA5eN+L1BVCp4~m=1eeL zb;#G!#^5G%6Mw}r1KnaKsLvJB%HZL)!3OxT{k$Yo-XrJ?|7{s4!H+S2o?N|^Z z)+?IE9H7h~Vxn5hTis^3wHYuOU84+bWd)cUKuHapq=&}WV#OxHpLab`NpwHm8LmOo zjri+!k;7j_?FP##CpM+pOVx*0wExEex z@`#)K<-ZrGyArK;a%Km`^+We|eT+#MygHOT6lXBmz`8|lyZOwL1+b+?Z$0OhMEp3R z&J=iRERpv~TC=p2-BYLC*?4 zxvPs9V@g=JT0>zky5Poj=fW_M!c)Xxz1<=&_ZcL=LMZJqlnO1P^xwGGW*Z+yTBvbV z-IFe6;(k1@$1;tS>{%pXZ_7w+i?N4A2=TXnGf=YhePg8bH8M|Lk-->+w8Y+FjZ;L=wSGwxfA`gqSn)f(XNuSm>6Y z@|#e-)I(PQ^G@N`%|_DZSb4_pkaEF0!-nqY+t#pyA>{9^*I-zw4SYA1_z2Bs$XGUZbGA;VeMo%CezHK0lO={L%G)dI-+8w?r9iexdoB{?l zbJ}C?huIhWXBVs7oo{!$lOTlvCLZ_KN1N+XJGuG$rh<^eUQIqcI7^pmqhBSaOKNRq zrx~w^?9C?*&rNwP_SPYmo;J-#!G|{`$JZK7DxsM3N^8iR4vvn>E4MU&Oe1DKJvLc~ zCT>KLZ1;t@My zRj_2hI^61T&LIz)S!+AQIV23n1>ng+LUvzv;xu!4;wpqb#EZz;F)BLUzT;8UA1x*6vJ zicB!3Mj03s*kGV{g`fpC?V^s(=JG-k1EMHbkdP4P*1^8p_TqO|;!Zr%GuP$8KLxuf z=pv*H;kzd;P|2`JmBt~h6|GxdU~@weK5O=X&5~w$HpfO}@l-T7@vTCxVOwCkoPQv8 z@aV_)I5HQtfs7^X=C03zYmH4m0S!V@JINm6#(JmZRHBD?T!m^DdiZJrhKpBcur2u1 zf9e4%k$$vcFopK5!CC`;ww(CKL~}mlxK_Pv!cOsFgVkNIghA2Au@)t6;Y3*2gK=5d z?|@1a)-(sQ%uFOmJ7v2iG&l&m^u&^6DJM#XzCrF%r>{2XKyxLD2rgWBD;i(!e4InDQBDg==^z;AzT2z~OmV0!?Z z0S9pX$+E;w3WN;v&NYT=+G8hf=6w0E1$0AOr61}eOvE8W1jX%>&Mjo7&!ulawgzLH zbcb+IF(s^3aj12WSi#pzIpijJJzkP?JzRawnxmNDSUR#7!29vHULCE<3Aa#be}ie~d|!V+ z%l~s9Odo$G&fH!t!+`rUT0T9DulF!Yq&BfQWFZV1L9D($r4H(}Gnf6k3^wa7g5|Ws zj7%d`!3(0bb55yhC6@Q{?H|2os{_F%o=;-h{@Yyyn*V7?{s%Grvpe!H^kl6tF4Zf5 z{Jv1~yZ*iIWL_9C*8pBMQArfJJ0d9Df6Kl#wa}7Xa#Ef_5B7=X}DzbQXVPfCwTO@9+@;A^Ti6il_C>g?A-GFwA0#U;t4;wOm-4oS})h z5&on>NAu67O?YCQr%7XIzY%LS4bha9*e*4bU4{lGCUmO2UQ2U)QOqClLo61Kx~3dI zmV3*(P6F_Tr-oP%x!0kTnnT?Ep5j;_IQ^pTRp=e8dmJtI4YgWd0}+b2=ATkOhgpXe z;jmw+FBLE}UIs4!&HflFr4)vMFOJ19W4f2^W(=2)F%TAL)+=F>IE$=e=@j-*bFLSg z)wf|uFQu+!=N-UzSef62u0-C8Zc7 zo6@F)c+nZA{H|+~7i$DCU0pL{0Ye|fKLuV^w!0Y^tT$isu%i1Iw&N|tX3kwFKJN(M zXS`k9js66o$r)x?TWL}Kxl`wUDUpwFx(w4Yk%49;$sgVvT~n8AgfG~HUcDt1TRo^s zdla@6heJB@JV z!vK;BUMznhzGK6PVtj0)GB=zTv6)Q9Yt@l#fv7>wKovLobMV-+(8)NJmyF8R zcB|_K7=FJGGn^X@JdFaat0uhKjp3>k#^&xE_}6NYNG?kgTp>2Iu?ElUjt4~E-?`Du z?mDCS9wbuS%fU?5BU@Ijx>1HG*N?gIP+<~xE4u=>H`8o((cS5M6@_OK%jSjFHirQK zN9@~NXFx*jS{<|bgSpC|SAnA@I)+GB=2W|JJChLI_mx+-J(mSJ!b)uUom6nH0#2^(L@JBlV#t zLl?j54s`Y3vE^c_3^Hl0TGu*tw_n?@HyO@ZrENxA+^!)OvUX28gDSF*xFtQzM$A+O zCG=n#6~r|3zt=8%GuG} z<#VCZ%2?3Q(Ad#Y7GMJ~{U3>E{5e@z6+rgZLX{Cxk^p-7dip^d29;2N1_mm4QkASo z-L`GWWPCq$uCo;X_BmGIpJFBlhl<8~EG{vOD1o|X$aB9KPhWO_cKiU*$HWEgtf=fn zsO%9bp~D2c@?*K9jVN@_vhR03>M_8h!_~%aN!Cnr?s-!;U3SVfmhRwk11A^8Ns`@KeE}+ zN$H}a1U6E;*j5&~Og!xHdfK5M<~xka)x-0N)K_&e7AjMz`toDzasH+^1bZlC!n()crk9kg@$(Y{wdKvbuUd04N^8}t1iOgsKF zGa%%XWx@WoVaNC1!|&{5ZbkopFre-Lu(LCE5HWZBoE#W@er9W<>R=^oYxBvypN#x3 zq#LC8&q)GFP=5^-bpHj?LW=)-g+3_)Ylps!3^YQ{9~O9&K)xgy zMkCWaApU-MI~e^cV{Je75Qr7eF%&_H)BvfyKL=gIA>;OSq(y z052BFz3E(Prg~09>|_Z@!qj}@;8yxnw+#Ej0?Rk<y}4ghbD569B{9hSFr*^ygZ zr6j7P#gtZh6tMk6?4V$*Jgz+#&ug;yOr>=qdI#9U&^am2qoh4Jy}H2%a|#Fs{E(5r z%!ijh;VuGA6)W)cJZx+;9Bp1LMUzN~x_8lQ#D3+sL{be-Jyeo@@dv7XguJ&S5vrH` z>QxOMWn7N-T!D@1(@4>ZlL^y5>m#0!HKovs12GRav4z!>p(1~xok8+_{| z#Ae4{9#NLh#Vj2&JuIn5$d6t@__`o}umFo(n0QxUtd2GKCyE+erwXY?`cm*h&^9*8 zJ+8x6fRZI-e$CRygofIQN^dWysCxgkyr{(_oBwwSRxZora1(%(aC!5BTtj^+YuevI zx?)H#(xlALUp6QJ!=l9N__$cxBZ5p&7;qD3PsXRFVd<({Kh+mShFWJNpy`N@ab7?9 zv5=klvCJ4bx|-pvOO2-+G)6O?$&)ncA#Urze2rlBfp#htudhx-NeRnJ@u%^_bfw4o z4|{b8SkPV3b>Wera1W(+N@p9H>dc6{cnkh-sgr?e%(YkWvK+0YXVwk0=d`)}*47*B z5JGkEdVix!w7-<%r0JF~`ZMMPe;f0EQHuYHxya`puazyph*ZSb1mJAt^k4549BfS; zK7~T&lRb=W{s&t`DJ$B}s-eH1&&-wEOH1KWsKn0a(ZI+G!v&W4A*cl>qAvUv6pbUR z#(f#EKV8~hk&8oayBz4vaswc(?qw1vn`yC zZQDl2PCB-&Uu@g9ZQHhO+v(W0bNig{-k0;;`+wM@#@J)8r?qOYs#&vUna8ILxN7S{ zp1s41KnR8miQJtJtOr|+qk}wrLt+N*z#5o`TmD1)E&QD(Vh&pjZJ_J*0!8dy_ z>^=@v=J)C`x&gjqAYu`}t^S=DFCtc0MkBU2zf|69?xW`Ck~(6zLD)gSE{7n~6w8j_ zoH&~$ED2k5-yRa0!r8fMRy z;QjBYUaUnpd}mf%iVFPR%Dg9!d>g`01m~>2s))`W|5!kc+_&Y>wD@@C9%>-lE`WB0 zOIf%FVD^cj#2hCkFgi-fgzIfOi+ya)MZK@IZhHT5FVEaSbv-oDDs0W)pA0&^nM0TW zmgJmd7b1R7b0a`UwWJYZXp4AJPteYLH>@M|xZFKwm!t3D3&q~av?i)WvAKHE{RqpD{{%OhYkK?47}+}` zrR2(Iv9bhVa;cDzJ%6ntcSbx7v7J@Y4x&+eWSKZ*eR7_=CVIUSB$^lfYe@g+p|LD{ zPSpQmxx@b$%d!05|H}WzBT4_cq?@~dvy<7s&QWtieJ9)hd4)$SZz}#H2UTi$CkFWW|I)v_-NjuH!VypONC=1`A=rm_jfzQ8Fu~1r8i{q-+S_j$ z#u^t&Xnfi5tZtl@^!fUJhx@~Cg0*vXMK}D{>|$#T*+mj(J_@c{jXBF|rm4-8%Z2o! z2z0o(4%8KljCm^>6HDK!{jI7p+RAPcty_~GZ~R_+=+UzZ0qzOwD=;YeZt*?3%UGdr z`c|BPE;yUbnyARUl&XWSNJ<+uRt%!xPF&K;(l$^JcA_CMH6)FZt{>6ah$|(9$2fc~ z=CD00uHM{qv;{Zk9FR0~u|3|Eiqv9?z2#^GqylT5>6JNZwKqKBzzQpKU2_pmtD;CT zi%Ktau!Y2Tldfu&b0UgmF(SSBID)15*r08eoUe#bT_K-G4VecJL2Pa=6D1K6({zj6 za(2Z{r!FY5W^y{qZ}08+h9f>EKd&PN90f}Sc0ejf%kB4+f#T8Q1=Pj=~#pi$U zp#5rMR%W25>k?<$;$x72pkLibu1N|jX4cWjD3q^Pk3js!uK6h7!dlvw24crL|MZs_ zb%Y%?Fyp0bY0HkG^XyS76Ts*|Giw{31LR~+WU5NejqfPr73Rp!xQ1mLgq@mdWncLy z%8}|nzS4P&`^;zAR-&nm5f;D-%yNQPwq4N7&yULM8bkttkD)hVU>h>t47`{8?n2&4 zjEfL}UEagLUYwdx0sB2QXGeRmL?sZ%J!XM`$@ODc2!y|2#7hys=b$LrGbvvjx`Iqi z&RDDm3YBrlKhl`O@%%&rhLWZ*ABFz2nHu7k~3@e4)kO3%$=?GEFUcCF=6-1n!x^vmu+Ai*amgXH+Rknl6U>#9w;A} zn2xanZSDu`4%%x}+~FG{Wbi1jo@wqBc5(5Xl~d0KW(^Iu(U3>WB@-(&vn_PJt9{1`e9Iic@+{VPc`vP776L*viP{wYB2Iff8hB%E3|o zGMOu)tJX!`qJ}ZPzq7>=`*9TmETN7xwU;^AmFZ-ckZjV5B2T09pYliaqGFY|X#E-8 z20b>y?(r-Fn5*WZ-GsK}4WM>@TTqsxvSYWL6>18q8Q`~JO1{vLND2wg@58OaU!EvT z1|o+f1mVXz2EKAbL!Q=QWQKDZpV|jznuJ}@-)1&cdo z^&~b4Mx{*1gurlH;Vhk5g_cM&6LOHS2 zRkLfO#HabR1JD4Vc2t828dCUG#DL}f5QDSBg?o)IYYi@_xVwR2w_ntlpAW0NWk$F1 z$If?*lP&Ka1oWfl!)1c3fl`g*lMW3JOn#)R1+tfwrs`aiFUgz3;XIJ>{QFxLCkK30 zNS-)#DON3yb!7LBHQJ$)4y%TN82DC2-9tOIqzhZ27@WY^<6}vXCWcR5iN{LN8{0u9 zNXayqD=G|e?O^*ms*4P?G%o@J1tN9_76e}E#66mr89%W_&w4n66~R;X_vWD(oArwj z4CpY`)_mH2FvDuxgT+akffhX0b_slJJ*?Jn3O3~moqu2Fs1oL*>7m=oVek2bnprnW zixkaIFU%+3XhNA@@9hyhFwqsH2bM|`P?G>i<-gy>NflhrN{$9?LZ1ynSE_Mj0rADF zhOz4FnK}wpLmQuV zgO4_Oz9GBu_NN>cPLA=`SP^$gxAnj;WjJnBi%Q1zg`*^cG;Q)#3Gv@c^j6L{arv>- zAW%8WrSAVY1sj$=umcAf#ZgC8UGZGoamK}hR7j6}i8#np8ruUlvgQ$j+AQglFsQQq zOjyHf22pxh9+h#n$21&$h?2uq0>C9P?P=Juw0|;oE~c$H{#RGfa>| zj)Iv&uOnaf@foiBJ}_;zyPHcZt1U~nOcNB{)og8Btv+;f@PIT*xz$x!G?u0Di$lo7 zOugtQ$Wx|C($fyJTZE1JvR~i7LP{ zbdIwqYghQAJi9p}V&$=*2Azev$6K@pyblphgpv8^9bN!?V}{BkC!o#bl&AP!3DAjM zmWFsvn2fKWCfjcAQmE+=c3Y7j@#7|{;;0f~PIodmq*;W9Fiak|gil6$w3%b_Pr6K_ zJEG@&!J%DgBZJDCMn^7mk`JV0&l07Bt`1ymM|;a)MOWz*bh2#d{i?SDe9IcHs7 zjCrnyQ*Y5GzIt}>`bD91o#~5H?4_nckAgotN{2%!?wsSl|LVmJht$uhGa+HiH>;av z8c?mcMYM7;mvWr6noUR{)gE!=i7cZUY7e;HXa221KkRoc2UB>s$Y(k%NzTSEr>W(u z<(4mcc)4rB_&bPzX*1?*ra%VF}P1nwiP5cykJ&W{!OTlz&Td0pOkVp+wc z@k=-Hg=()hNg=Q!Ub%`BONH{ z_=ZFgetj@)NvppAK2>8r!KAgi>#%*7;O-o9MOOfQjV-n@BX6;Xw;I`%HBkk20v`qoVd0)}L6_49y1IhR z_OS}+eto}OPVRn*?UHC{eGyFU7JkPz!+gX4P>?h3QOwGS63fv4D1*no^6PveUeE5% zlehjv_3_^j^C({a2&RSoVlOn71D8WwMu9@Nb@=E_>1R*ve3`#TF(NA0?d9IR_tm=P zOP-x;gS*vtyE1Cm zG0L?2nRUFj#aLr-R1fX*$sXhad)~xdA*=hF3zPZhha<2O$Ps+F07w*3#MTe?)T8|A!P!v+a|ot{|^$q(TX`35O{WI0RbU zCj?hgOv=Z)xV?F`@HKI11IKtT^ocP78cqHU!YS@cHI@{fPD?YXL)?sD~9thOAv4JM|K8OlQhPXgnevF=F7GKD2#sZW*d za}ma31wLm81IZxX(W#A9mBvLZr|PoLnP>S4BhpK8{YV_}C|p<)4#yO{#ISbco92^3 zv&kCE(q9Wi;9%7>>PQ!zSkM%qqqLZW7O`VXvcj;WcJ`2~v?ZTYB@$Q&^CTfvy?1r^ z;Cdi+PTtmQwHX_7Kz?r#1>D zS5lWU(Mw_$B&`ZPmqxpIvK<~fbXq?x20k1~9az-Q!uR78mCgRj*eQ>zh3c$W}>^+w^dIr-u{@s30J=)1zF8?Wn|H`GS<=>Om|DjzC{}Jt?{!fSJe*@$H zg>wFnlT)k#T?LslW zu$^7Uy~$SQ21cE?3Ijl+bLfuH^U5P^$@~*UY#|_`uvAIe(+wD2eF}z_y!pvomuVO; zS^9fbdv)pcm-B@CW|Upm<7s|0+$@@<&*>$a{aW+oJ%f+VMO<#wa)7n|JL5egEgoBv zl$BY(NQjE0#*nv=!kMnp&{2Le#30b)Ql2e!VkPLK*+{jv77H7)xG7&=aPHL7LK9ER z5lfHxBI5O{-3S?GU4X6$yVk>lFn;ApnwZybdC-GAvaznGW-lScIls-P?Km2mF>%B2 zkcrXTk+__hj-3f48U%|jX9*|Ps41U_cd>2QW81Lz9}%`mTDIhE)jYI$q$ma7Y-`>% z8=u+Oftgcj%~TU}3nP8&h7k+}$D-CCgS~wtWvM|UU77r^pUw3YCV80Ou*+bH0!mf0 zxzUq4ed6y>oYFz7+l18PGGzhB^pqSt)si=9M>~0(Bx9*5r~W7sa#w+_1TSj3Jn9mW zMuG9BxN=}4645Cpa#SVKjFst;9UUY@O<|wpnZk$kE+to^4!?0@?Cwr3(>!NjYbu?x z1!U-?0_O?k!NdM^-rIQ8p)%?M+2xkhltt*|l=%z2WFJhme7*2xD~@zk#`dQR$6Lmd zb3LOD4fdt$Cq>?1<%&Y^wTWX=eHQ49Xl_lFUA(YQYHGHhd}@!VpYHHm=(1-O=yfK#kKe|2Xc*9}?BDFN zD7FJM-AjVi)T~OG)hpSWqH>vlb41V#^G2B_EvYlWhDB{Z;Q9-0)ja(O+By`31=biA zG&Fs#5!%_mHi|E4Nm$;vVQ!*>=_F;ZC=1DTPB#CICS5fL2T3XmzyHu?bI;m7D4@#; ztr~;dGYwb?m^VebuULtS4lkC_7>KCS)F@)0OdxZIFZp@FM_pHnJes8YOvwB|++#G( z&dm*OP^cz95Wi15vh`Q+yB>R{8zqEhz5of>Po$9LNE{xS<)lg2*roP*sQ}3r3t<}; zPbDl{lk{pox~2(XY5=qg0z!W-x^PJ`VVtz$git7?)!h>`91&&hESZy1KCJ2nS^yMH z!=Q$eTyRi68rKxdDsdt+%J_&lapa{ds^HV9Ngp^YDvtq&-Xp}60B_w@Ma>_1TTC;^ zpbe!#gH}#fFLkNo#|`jcn?5LeUYto%==XBk6Ik0kc4$6Z+L3x^4=M6OI1=z5u#M%0 z0E`kevJEpJjvvN>+g`?gtnbo$@p4VumliZV3Z%CfXXB&wPS^5C+7of2tyVkMwNWBiTE2 z8CdPu3i{*vR-I(NY5syRR}I1TJOV@DJy-Xmvxn^IInF>Tx2e)eE9jVSz69$6T`M9-&om!T+I znia!ZWJRB28o_srWlAxtz4VVft8)cYloIoVF=pL zugnk@vFLXQ_^7;%hn9x;Vq?lzg7%CQR^c#S)Oc-8d=q_!2ZVH764V z!wDKSgP}BrVV6SfCLZnYe-7f;igDs9t+K*rbMAKsp9L$Kh<6Z;e7;xxced zn=FGY<}CUz31a2G}$Q(`_r~75PzM4l_({Hg&b@d8&jC}B?2<+ed`f#qMEWi z`gm!STV9E4sLaQX+sp5Nu9*;9g12naf5?=P9p@H@f}dxYprH+3ju)uDFt^V{G0APn zS;16Dk{*fm6&BCg#2vo?7cbkkI4R`S9SSEJ=#KBk3rl69SxnCnS#{*$!^T9UUmO#&XXKjHKBqLdt^3yVvu8yn|{ zZ#%1CP)8t-PAz(+_g?xyq;C2<9<5Yy<~C74Iw(y>uUL$+$mp(DRcCWbCKiGCZw@?_ zdomfp+C5xt;j5L@VfhF*xvZdXwA5pcdsG>G<8II-|1dhAgzS&KArcb0BD4ZZ#WfiEY{hkCq5%z9@f|!EwTm;UEjKJsUo696V>h zy##eXYX}GUu%t{Gql8vVZKkNhQeQ4C%n|RmxL4ee5$cgwlU+?V7a?(jI#&3wid+Kz5+x^G!bb#$q>QpR#BZ}Xo5UW^ zD&I`;?(a}Oys7-`I^|AkN?{XLZNa{@27Dv^s4pGowuyhHuXc zuctKG2x0{WCvg_sGN^n9myJ}&FXyGmUQnW7fR$=bj$AHR88-q$D!*8MNB{YvTTEyS zn22f@WMdvg5~o_2wkjItJN@?mDZ9UUlat2zCh(zVE=dGi$rjXF7&}*sxac^%HFD`Y zTM5D3u5x**{bW!68DL1A!s&$2XG@ytB~dX-?BF9U@XZABO`a|LM1X3HWCllgl0+uL z04S*PX$%|^WAq%jkzp~%9HyYIF{Ym?k)j3nMwPZ=hlCg9!G+t>tf0o|J2%t1 ztC+`((dUplgm3`+0JN~}&FRRJ3?l*>Y&TfjS>!ShS`*MwO{WIbAZR#<%M|4c4^dY8 z{Rh;-!qhY=dz5JthbWoovLY~jNaw>%tS4gHVlt5epV8ekXm#==Po$)}mh^u*cE>q7*kvX&gq)(AHoItMYH6^s6f(deNw%}1=7O~bTHSj1rm2|Cq+3M z93djjdomWCTCYu!3Slx2bZVy#CWDozNedIHbqa|otsUl+ut?>a;}OqPfQA05Yim_2 zs@^BjPoFHOYNc6VbNaR5QZfSMh2S*`BGwcHMM(1@w{-4jVqE8Eu0Bi%d!E*^Rj?cR z7qgxkINXZR)K^=fh{pc0DCKtrydVbVILI>@Y0!Jm>x-xM!gu%dehm?cC6ok_msDVA*J#{75%4IZt}X|tIVPReZS#aCvuHkZxc zHVMtUhT(wp09+w9j9eRqz~LtuSNi2rQx_QgQ(}jBt7NqyT&ma61ldD(s9x%@q~PQl zp6N*?=N$BtvjQ_xIT{+vhb1>{pM0Arde0!X-y))A4znDrVx8yrP3B1(7bKPE5jR@5 zwpzwT4cu~_qUG#zYMZ_!2Tkl9zP>M%cy>9Y(@&VoB84#%>amTAH{(hL4cDYt!^{8L z645F>BWO6QaFJ-{C-i|-d%j7#&7)$X7pv#%9J6da#9FB5KyDhkA+~)G0^87!^}AP>XaCSScr;kL;Z%RSPD2CgoJ;gpYT5&6NUK$86$T?jRH=w8nI9Z534O?5fk{kd z`(-t$8W|#$3>xoMfXvV^-A(Q~$8SKDE^!T;J+rQXP71XZ(kCCbP%bAQ1|%$%Ov9_a zyC`QP3uPvFoBqr_+$HenHklqyIr>PU_Fk5$2C+0eYy^~7U&(!B&&P2%7#mBUhM!z> z_B$Ko?{Pf6?)gpYs~N*y%-3!1>o-4;@1Zz9VQHh)j5U1aL-Hyu@1d?X;jtDBNk*vMXPn@ z+u@wxHN*{uHR!*g*4Xo&w;5A+=Pf9w#PeZ^x@UD?iQ&${K2c}UQgLRik-rKM#Y5rdDphdcNTF~cCX&9ViRP}`>L)QA4zNXeG)KXFzSDa6 zd^St;inY6J_i=5mcGTx4_^Ys`M3l%Q==f>{8S1LEHn{y(kbxn5g1ezt4CELqy)~TV6{;VW>O9?5^ ztcoxHRa0jQY7>wwHWcxA-BCwzsP>63Kt&3fy*n#Cha687CQurXaRQnf5wc9o8v7Rw zNwGr2fac;Wr-Ldehn7tF^(-gPJwPt@VR1f;AmKgxN&YPL;j=0^xKM{!wuU|^mh3NE zy35quf}MeL!PU;|{OW_x$TBothLylT-J>_x6p}B_jW1L>k)ps6n%7Rh z96mPkJIM0QFNYUM2H}YF5bs%@Chs6#pEnloQhEl?J-)es!(SoJpEPoMTdgA14-#mC zghayD-DJWtUu`TD8?4mR)w5E`^EHbsz2EjH5aQLYRcF{l7_Q5?CEEvzDo(zjh|BKg z3aJl_n#j&eFHsUw4~lxqnr!6NL*se)6H=A+T1e3xUJGQrd}oSPwSy5+$tt{2t5J5@(lFxl43amsARG74iyNC}uuS zd2$=(r6RdamdGx^eatX@F2D8?U23tDpR+Os?0Gq2&^dF+$9wiWf?=mDWfjo4LfRwL zI#SRV9iSz>XCSgEj!cW&9H-njJopYiYuq|2w<5R2!nZ27DyvU4UDrHpoNQZiGPkp@ z1$h4H46Zn~eqdj$pWrv;*t!rTYTfZ1_bdkZmVVIRC21YeU$iS-*XMNK`#p8Z_DJx| zk3Jssf^XP7v0X?MWFO{rACltn$^~q(M9rMYoVxG$15N;nP)A98k^m3CJx8>6}NrUd@wp-E#$Q0uUDQT5GoiK_R{ z<{`g;8s>UFLpbga#DAf%qbfi`WN1J@6IA~R!YBT}qp%V-j!ybkR{uY0X|x)gmzE0J z&)=eHPjBxJvrZSOmt|)hC+kIMI;qgOnuL3mbNR0g^<%|>9x7>{}>a2qYSZAGPt4it?8 zNcLc!Gy0>$jaU?}ZWxK78hbhzE+etM`67*-*x4DN>1_&{@5t7_c*n(qz>&K{Y?10s zXsw2&nQev#SUSd|D8w7ZD2>E<%g^; zV{yE_O}gq?Q|zL|jdqB^zcx7vo(^})QW?QKacx$yR zhG|XH|8$vDZNIfuxr-sYFR{^csEI*IM#_gd;9*C+SysUFejP0{{z7@P?1+&_o6=7V|EJLQun^XEMS)w(=@eMi5&bbH*a0f;iC~2J74V2DZIlLUHD&>mlug5+v z6xBN~8-ovZylyH&gG#ptYsNlT?-tzOh%V#Y33zlsJ{AIju`CjIgf$@gr8}JugRq^c zAVQ3;&uGaVlVw}SUSWnTkH_6DISN&k2QLMBe9YU=sA+WiX@z)FoSYX`^k@B!j;ZeC zf&**P?HQG6Rk98hZ*ozn6iS-dG}V>jQhb3?4NJB*2F?6N7Nd;EOOo;xR7acylLaLy z9)^lykX39d@8@I~iEVar4jmjjLWhR0d=EB@%I;FZM$rykBNN~jf>#WbH4U{MqhhF6 zU??@fSO~4EbU4MaeQ_UXQcFyO*Rae|VAPLYMJEU`Q_Q_%s2*>$#S^)&7er+&`9L=1 z4q4ao07Z2Vsa%(nP!kJ590YmvrWg+YrgXYs_lv&B5EcoD`%uL79WyYA$0>>qi6ov7 z%`ia~J^_l{p39EY zv>>b}Qs8vxsu&WcXEt8B#FD%L%ZpcVtY!rqVTHe;$p9rbb5O{^rFMB>auLn-^;s+-&P1#h~mf~YLg$8M9 zZ4#87;e-Y6x6QO<{McUzhy(%*6| z)`D~A(TJ$>+0H+mct(jfgL4x%^oC^T#u(bL)`E2tBI#V1kSikAWmOOYrO~#-cc_8! zCe|@1&mN2{*ceeiBldHCdrURk4>V}79_*TVP3aCyV*5n@jiNbOm+~EQ_}1#->_tI@ zqXv+jj2#8xJtW508rzFrYcJxoek@iW6SR@1%a%Bux&;>25%`j3UI`0DaUr7l79`B1 zqqUARhW1^h6=)6?;@v>xrZNM;t}{yY3P@|L}ey@gG( z9r{}WoYN(9TW&dE2dEJIXkyHA4&pU6ki=rx&l2{DLGbVmg4%3Dlfvn!GB>EVaY_%3+Df{fBiqJV>~Xf8A0aqUjgpa} zoF8YXO&^_x*Ej}nw-$-F@(ddB>%RWoPUj?p8U{t0=n>gAI83y<9Ce@Q#3&(soJ{64 z37@Vij1}5fmzAuIUnXX`EYe;!H-yTVTmhAy;y8VZeB#vD{vw9~P#DiFiKQ|kWwGFZ z=jK;JX*A;Jr{#x?n8XUOLS;C%f|zj-7vXtlf_DtP7bpurBeX%Hjwr z4lI-2TdFpzkjgiv!8Vfv`=SP+s=^i3+N~1ELNWUbH|ytVu>EyPN_3(4TM^QE1swRo zoV7Y_g)a>28+hZG0e7g%@2^s>pzR4^fzR-El}ARTmtu!zjZLuX%>#OoU3}|rFjJg} zQ2TmaygxJ#sbHVyiA5KE+yH0LREWr%^C*yR|@gM$nK2P zo}M}PV0v))uJh&33N>#aU376@ZH79u(Yw`EQ2hM3SJs9f99+cO6_pNW$j$L-CtAfe zYfM)ccwD!P%LiBk!eCD?fHCGvgMQ%Q2oT_gmf?OY=A>&PaZQOq4eT=lwbaf}33LCH zFD|)lu{K7$8n9gX#w4~URjZxWm@wlH%oL#G|I~Fb-v^0L0TWu+`B+ZG!yII)w05DU z>GO?n(TN+B=>HdxVDSlIH76pta$_LhbBg;eZ`M7OGcqt||qi zogS72W1IN%=)5JCyOHWoFP7pOFK0L*OAh=i%&VW&4^LF@R;+K)t^S!96?}^+5QBIs zjJNTCh)?)4k^H^g1&jc>gysM`y^8Rm3qsvkr$9AeWwYpa$b22=yAd1t<*{ zaowSEFP+{y?Ob}8&cwfqoy4Pb9IA~VnM3u!trIK$&&0Op#Ql4j>(EW?UNUv#*iH1$ z^j>+W{afcd`{e&`-A{g}{JnIzYib)!T56IT@YEs{4|`sMpW3c8@UCoIJv`XsAw!XC z34|Il$LpW}CIHFC5e*)}00I5{%OL*WZRGzC0?_}-9{#ue?-ug^ zLE|uv-~6xnSs_2_&CN9{9vyc!Xgtn36_g^wI0C4s0s^;8+p?|mm;Odt3`2ZjwtK;l zfd6j)*Fr#53>C6Y8(N5?$H0ma;BCF3HCjUs7rpb2Kf*x3Xcj#O8mvs#&33i+McX zQpBxD8!O{5Y8D&0*QjD=Yhl9%M0)&_vk}bmN_Ud^BPN;H=U^bn&(csl-pkA+GyY0Z zKV7sU_4n;}uR78ouo8O%g*V;79KY?3d>k6%gpcmQsKk&@Vkw9yna_3asGt`0Hmj59 z%0yiF*`jXhByBI9QsD=+>big5{)BGe&+U2gAARGe3ID)xrid~QN_{I>k}@tzL!Md_ z&=7>TWciblF@EMC3t4-WX{?!m!G6$M$1S?NzF*2KHMP3Go4=#ZHkeIv{eEd;s-yD# z_jU^Ba06TZqvV|Yd;Z_sN%$X=!T+&?#p+OQIHS%!LO`Hx0q_Y0MyGYFNoM{W;&@0@ zLM^!X4KhdtsET5G<0+|q0oqVXMW~-7LW9Bg}=E$YtNh1#1D^6Mz(V9?2g~I1( zoz9Cz=8Hw98zVLwC2AQvp@pBeKyidn6Xu0-1SY1((^Hu*-!HxFUPs)yJ+i`^BC>PC zjwd0mygOVK#d2pRC9LxqGc6;Ui>f{YW9Bvb>33bp^NcnZoH~w9(lM5@JiIlfa-6|k ziy31UoMN%fvQfhi8^T+=yrP{QEyb-jK~>$A4SZT-N56NYEbpvO&yUme&pWKs3^94D zH{oXnUTb3T@H+RgzML*lejx`WAyw*?K7B-I(VJx($2!NXYm%3`=F~TbLv3H<{>D?A zJo-FDYdSA-(Y%;4KUP2SpHKAIcv9-ld(UEJE7=TKp|Gryn;72?0LHqAN^fk6%8PCW z{g_-t)G5uCIf0I`*F0ZNl)Z>))MaLMpXgqWgj-y;R+@A+AzDjsTqw2Mo9ULKA3c70 z!7SOkMtZb+MStH>9MnvNV0G;pwSW9HgP+`tg}e{ij0H6Zt5zJ7iw`hEnvye!XbA@!~#%vIkzowCOvq5I5@$3wtc*w2R$7!$*?}vg4;eDyJ_1=ixJuEp3pUS27W?qq(P^8$_lU!mRChT}ctvZz4p!X^ zOSp|JOAi~f?UkwH#9k{0smZ7-#=lK6X3OFEMl7%)WIcHb=#ZN$L=aD`#DZKOG4p4r zwlQ~XDZ`R-RbF&hZZhu3(67kggsM-F4Y_tI^PH8PMJRcs7NS9ogF+?bZB*fcpJ z=LTM4W=N9yepVvTj&Hu~0?*vR1HgtEvf8w%Q;U0^`2@e8{SwgX5d(cQ|1(!|i$km! zvY03MK}j`sff;*-%mN~ST>xU$6Bu?*Hm%l@0dk;j@%>}jsgDcQ)Hn*UfuThz9(ww_ zasV`rSrp_^bp-0sx>i35FzJwA!d6cZ5#5#nr@GcPEjNnFHIrtUYm1^Z$;{d&{hQV9 z6EfFHaIS}46p^5I-D_EcwwzUUuO}mqRh&T7r9sfw`)G^Q%oHxEs~+XoM?8e*{-&!7 z7$m$lg9t9KP9282eke608^Q2E%H-xm|oJ8=*SyEo} z@&;TQ3K)jgspgKHyGiKVMCz>xmC=H5Fy3!=TP)-R3|&1S-B)!6q50wfLHKM@7Bq6E z44CY%G;GY>tC`~yh!qv~YdXw! zSkquvYNs6k1r7>Eza?Vkkxo6XRS$W7EzL&A`o>=$HXgBp{L(i^$}t`NcnAxzbH8Ht z2!;`bhKIh`f1hIFcI5bHI=ueKdzmB9)!z$s-BT4ItyY|NaA_+o=jO%MU5as9 zc2)aLP>N%u>wlaXTK!p)r?+~)L+0eCGb5{8WIk7K52$nufnQ+m8YF+GQc&{^(zh-$ z#wyWV*Zh@d!b(WwXqvfhQX)^aoHTBkc;4ossV3&Ut*k>AI|m+{#kh4B!`3*<)EJVj zwrxK>99v^k4&Y&`Awm>|exo}NvewV%E+@vOc>5>%H#BK9uaE2$vje zWYM5fKuOTtn96B_2~~!xJPIcXF>E_;yO8AwpJ4)V`Hht#wbO3Ung~@c%%=FX4)q+9 z99#>VC2!4l`~0WHs9FI$Nz+abUq# zz`Of97})Su=^rGp2S$)7N3rQCj#0%2YO<R&p>$<#lgXcUj=4H_{oAYiT3 z44*xDn-$wEzRw7#@6aD)EGO$0{!C5Z^7#yl1o;k0PhN=aVUQu~eTQ^Xy{z8Ow6tk83 z4{5xe%(hx)%nD&|e*6sTWH`4W&U!Jae#U4TnICheJmsw{l|CH?UA{a6?2GNgpZLyzU2UlFu1ZVwlALmh_DOs03J^Cjh1im`E3?9&zvNmg(MuMw&0^Lu$(#CJ*q6DjlKsY-RMJ^8yIY|{SQZ*9~CH|u9L z`R78^r=EbbR*_>5?-)I+$6i}G)%mN(`!X72KaV(MNUP7Nv3MS9S|Pe!%N2AeOt5zG zVJ;jI4HZ$W->Ai_4X+`9c(~m=@ek*m`ZQbv3ryI-AD#AH=`x$~WeW~M{Js57(K7(v ze5`};LG|%C_tmd>bkufMWmAo&B+DT9ZV~h(4jg0>^aeAqL`PEUzJJtI8W1M!bQWpv zvN(d}E1@nlYa!L!!A*RN!(Q3F%J?5PvQ0udu?q-T)j3JKV~NL>KRb~w-lWc685uS6 z=S#aR&B8Sc8>cGJ!!--?kwsJTUUm`Jk?7`H z7PrO~xgBrSW2_tTlCq1LH8*!o?pj?qxy8}(=r_;G18POrFh#;buWR0qU24+XUaVZ0 z?(sXcr@-YqvkCmHr{U2oPogHL{r#3r49TeR<{SJX1pcUqyWPrkYz^X8#QW~?F)R5i z>p^!i<;qM8Nf{-fd6!_&V*e_9qP6q(s<--&1Ttj01j0w>bXY7y1W*%Auu&p|XSOH=)V7Bd4fUKh&T1)@cvqhuD-d=?w}O zjI%i(f|thk0Go*!d7D%0^ztBfE*V=(ZIN84f5HU}T9?ulmEYzT5usi=DeuI*d|;M~ zp_=Cx^!4k#=m_qSPBr5EK~E?3J{dWWPH&oCcNepYVqL?nh4D5ynfWip$m*YlZ8r^Z zuFEUL-nW!3qjRCLIWPT0x)FDL7>Yt7@8dA?R2kF@WE>ysMY+)lTsgNM#3VbXVGL}F z1O(>q>2a+_`6r5Xv$NZAnp=Kgnr3)cL(^=8ypEeOf3q8(HGe@7Tt59;yFl||w|mnO zHDxg2G3z8=(6wjj9kbcEY@Z0iOd7Gq5GiPS5% z*sF1J<#daxDV2Z8H>wxOF<;yKzMeTaSOp_|XkS9Sfn6Mpe9UBi1cSTieGG5$O;ZLIIJ60Y>SN4vC?=yE_CWlo(EEE$e4j?z&^FM%kNmRtlbEL^dPPgvs9sbK5fGw*r@ z+!EU@u$T8!nZh?Fdf_qk$VuHk^yVw`h`_#KoS*N%epIIOfQUy_&V}VWDGp3tplMbf z5Se1sJUC$7N0F1-9jdV2mmGK{-}fu|Nv;12jDy0<-kf^AmkDnu6j~TPWOgy1MT68|D z=4=50jVbUKdKaQgD`eWGr3I&^<6uhkjz$YwItY8%Yp9{z4-{6g{73<_b*@XJ4Nm3-3z z?BW3{aY_ccRjb@W1)i5nLg|7BnWS!B`_Uo9CWaE`Ij327QH?i)9A}4Ug4wmxVVa^b z-4+m%-wwOl7cKH7+=x&nrCrbEC)Q$fpg&V83#uEH;C=GNMz`ps@^RxK%T*8%OPnC` z{WO~J%nxYJ`x|N%?&i7?;{_8t^jM&=50HlaOQj8fS}_`moH$c;vI<|cruPFnpT8yU zS%rPOCUSd5Zdb(zwk`hqwTQn)*&n)uYsP*F_(~xEWq}C= zv30kFmZFwJZ@ELVX3?$dXQh|icO7UrL*_5G=I^xXjImz`ZPp>?g#tf(ej~KaIU0algsG!IS09;>?MvqGg#c{i+}qY|{P8W~O%#>|gFd z<1dr$-oxyRGN17yZo1OwLnzwYs0|;IS_nymNB0IlSzPQ%-r`?T=;_XQ^~&#}b|AB} zkNbN5uB?-sUB-T5QLlg%Uk3)uHB;>VIzGe9_J9 zaeISkQm!v(9d(0ML^b9fR^sfHFlH?7Mvddt37OuR{|O0{uv)(&-6<87W4 zyO>s!=cPgP3O&7xxU5DlIPw_o3O>6o6Qb?JWs3qw#p3sBc3g$?Dx zi(6D+DYgV;GrUis-CL%Qe{nvZnwaVXmbhH(|GFh|Q)k=1uvA$I@1DXI7bKlQ@8D6P zS?(*?><>)G49q0wr;NajpxP4W2G)kHl6^=Z>hrNEI4Mwd_$O6$1dXF;Q#hE(-eeW6 zz03GJF%Wl?HO=_ztv5*zRlcU~{+{k%#N59mgm~eK>P!QZ6E?#Cu^2)+K8m@ySvZ*5 z|HDT}BkF@3!l(0%75G=1u2hETXEj!^1Z$!)!lyGXlWD!_vqGE$Z)#cUVBqlORW>0^ zDjyVTxwKHKG|0}j-`;!R-p>}qQfBl(?($7pP<+Y8QE#M8SCDq~k<+>Q^Zf@cT_WdX3~BSe z+|KK|7OL5Hm5(NFP~j>Ct3*$wi0n0!xl=(C61`q&cec@mFlH(sy%+RH<=s)8aAPN`SfJdkAQjdv82G5iRdv8 zh{9wHUZaniSEpslXl^_ODh}mypC?b*9FzLjb~H@3DFSe;D(A-K3t3eOTB(m~I6C;(-lKAvit(70k`%@+O*Ztdz;}|_TS~B?Tpmi=QKC^m_ z2YpEaT3iiz*;T~ap1yiA)a`dKMwu`^UhIUeltNQ1Yjo=q@bI@&3zH?rVUg=IxLy-ni zyxDu%-Fr{H6owTjZU2O5>nDb=q&Jz_TjeSq%!2m40x&U6w~GQ({quPL73IsJS;f`$ zsuhioqCBj(gJ>2hoo)Gou7(WP*pX)f=Y=!=k!&1K?EYY%jJ~X&DnK{^saPQK<1BJ z_A`_{%ZozcB(3w$z^To^6d|XuT@=X~wtW!+{4ID@N{AB~J6AL5vuY>JwvWCNFKsKh zd}@>q@_WV#QZ&UJ0#?X(pXR!oyXOEG3rqzHbCzGLONDb042i$})fM@XF)uSP(DHUc z^&{|$*xe{cs?Gp8=B%RY3L7#$ve$?TWh>MZdxF1zH1v}1z+$Ov#G7?%D)bBCyDe*% zSeKSpETC2V1){II>@UwJi>4uBN+iAx+82E~gb|Cr&8E^i&)A!uv-g?jzH99wU}8+# z$nh>yvb;TwZmS@7LrvuCu_d0-WxFNI&C7%sWuTL%YU!l|I1{|->=dlOeHOCtUO#zkS3ESO8LHV4hTdQL5EdV zuWD33fFPH}HPrW^s$Qn1Xgp&AT6<-He{{4%eIu3rN=iK|9mURdKXfB&Q?qGok%!cs ze53UP{Z!TO-Y@q2;;k2avA3`lm4OoN4@S*k=UA)7H;qZ`d8`XaYFCv?Ba+uGW@r5v z&&{nf(24WSBOhc7!qF^@0cz;XcUynNaj6w2349;s!K{KVqs5yS{ z7VubS`2OzT^5#1~6Tt^RTvt9-J|D2F>y~>2;jeF>g`hx5l%B3H=aLExQihuYngzlnBTYOTHJQMzl>kwqN5JYs)Ej zblA@ntkUS~xi+}y6|(81helS}Q~&VB37qyV|S3Y=><^1wh%msQM?fz z<58MX(=|PSUKCF#)dbhR%D&xgCD?$aR0qen+wpp6 zst}vX18!Be96TD??j1HsHTUx(a&@F?=gT`Q$oJFFyrh^;zgz!(NlAHGn0cJy@us=w zNhC#l5G;H}+>49Nsh12=ZPO2r*2OBQe5kpb&1?*PIBFitK8}FUfb~S-#hKfF0o#&d z#3aPkB$9scYku&kA6{0xHnBV#&Wei5J>5T-XX-gUXEPo+9b7WL=*XESc(3BshL`aj zXp}QIp*40}oWJt*l043e8_5;H5PI5c)U&IEw5dF(4zjX0y_lk9 zAp@!mK>WUqHo)-jop=DoK>&no>kAD=^qIE7qis&_*4~ z6q^EF$D@R~3_xseCG>Ikb6Gfofb$g|75PPyyZN&tiRxqovo_k zO|HA|sgy#B<32gyU9x^&)H$1jvw@qp+1b(eGAb)O%O!&pyX@^nQd^9BQ4{(F8<}|A zhF&)xusQhtoXOOhic=8#Xtt5&slLia3c*a?dIeczyTbC#>FTfiLST57nc3@Y#v_Eg#VUv zT8cKH#f3=1PNj!Oroz_MAR*pow%Y0*6YCYmUy^7`^r|j23Q~^*TW#cU7CHf0eAD_0 zEWEVddxFgQ7=!nEBQ|ibaScslvhuUk^*%b#QUNrEB{3PG@uTxNwW}Bs4$nS9wc(~O zG7Iq>aMsYkcr!9#A;HNsJrwTDYkK8ikdj{M;N$sN6BqJ<8~z>T20{J8Z2rRUuH7~3 z=tgS`AgxbBOMg87UT4Lwge`*Y=01Dvk>)^{Iu+n6fuVX4%}>?3czOGR$0 zpp*wp>bsFFSV`V;r_m+TZns$ZprIi`OUMhe^cLE$2O+pP3nP!YB$ry}2THx2QJs3< za1;>d-AggCarrQ>&Z!d@;mW+!q6eXhb&`GbzUDSxpl8AJ#Cm#tuc)_xh(2NV=5XMs zrf_ozRYO$NkC=pKFX5OH8v1>0i9Z$ec`~Mf+_jQ68spn(CJwclDhEEkH2Qw;${J$clv__nUjn5jA0wCLEnu1j;v!0vB>Ri6m9`;R{JMS%^)4FC zU0Z44+u$I$w=Bj|iu4DT5h~sS`C*zbmX?@-crY}E+hy>}2~C0Nn(EKk@5^qO4@l@! z6O0lr%tzGC`D^)8xU3FnMZVm0kX1sBWhaQyzVoXFWwr%Ny?=2M{5s#5i7fTu3gEkG zc{(Pr$v=;`Y#&`y*J}#M9ux>0?xu!`$9cUKm#Bdd_&S#LPTS?ZPV6zN6>W6JTS~-LfjL{mB=b(KMk3 z2HjBSlJeyUVqDd=Mt!=hpYsvby2GL&3~zm;0{^nZJq+4vb?5HH4wufvr}IX42sHeK zm@x?HN$8TsTavXs)tLDFJtY9b)y~Tl@7z4^I8oUQq4JckH@~CVQ;FoK(+e0XAM>1O z(ei}h?)JQp>)d=6ng-BZF1Z5hsAKW@mXq+hU?r8I(*%`tnIIOXw7V6ZK(T9RFJJe@ zZS!aC+p)Gf2Ujc=a6hx4!A1Th%YH!Lb^xpI!Eu` zmJO{9rw){B1Ql18d%F%da+Tbu1()?o(zT7StYqK6_w`e+fjXq5L^y(0 z09QA6H4oFj59c2wR~{~>jUoDzDdKz}5#onYPJRwa`SUO)Pd4)?(ENBaFVLJr6Kvz= zhTtXqbx09C1z~~iZt;g^9_2nCZ{};-b4dQJbv8HsWHXPVg^@(*!@xycp#R?a|L!+` zY5w))JWV`Gls(=}shH0#r*;~>_+-P5Qc978+QUd>J%`fyn{*TsiG-dWMiJXNgwBaT zJ=wgYFt+1ACW)XwtNx)Q9tA2LPoB&DkL16P)ERWQlY4%Y`-5aM9mZ{eKPUgI!~J3Z zkMd5A_p&v?V-o-6TUa8BndiX?ooviev(DKw=*bBVOW|=zps9=Yl|-R5@yJe*BPzN}a0mUsLn{4LfjB_oxpv(mwq# zSY*%E{iB)sNvWfzg-B!R!|+x(Q|b@>{-~cFvdDHA{F2sFGA5QGiIWy#3?P2JIpPKg6ncI^)dvqe`_|N=8 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/vtubermini/android/gradlew.bat b/vtubermini/android/gradlew.bat new file mode 100755 index 0000000..5eed7ee --- /dev/null +++ b/vtubermini/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/vtubermini/android/settings.gradle b/vtubermini/android/settings.gradle new file mode 100644 index 0000000..3b4431d --- /dev/null +++ b/vtubermini/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/vtubermini/android/variables.gradle b/vtubermini/android/variables.gradle new file mode 100644 index 0000000..ee4ba41 --- /dev/null +++ b/vtubermini/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 36 + androidxActivityVersion = '1.11.0' + androidxAppCompatVersion = '1.7.1' + androidxCoordinatorLayoutVersion = '1.3.0' + androidxCoreVersion = '1.17.0' + androidxFragmentVersion = '1.8.9' + coreSplashScreenVersion = '1.2.0' + androidxWebkitVersion = '1.14.0' + junitVersion = '4.13.2' + androidxJunitVersion = '1.3.0' + androidxEspressoCoreVersion = '3.7.0' + cordovaAndroidVersion = '14.0.1' +} \ No newline at end of file diff --git a/vtubermini/anniversary-page-check.png b/vtubermini/anniversary-page-check.png new file mode 100644 index 0000000..74f0123 --- /dev/null +++ b/vtubermini/anniversary-page-check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21f1a7743c131a5f2ae898ecdac74f693c158d5020cf14037589029f891e6491 +size 699173 diff --git a/vtubermini/anniversary-upload-clip-check.png b/vtubermini/anniversary-upload-clip-check.png new file mode 100644 index 0000000..3bc98dc --- /dev/null +++ b/vtubermini/anniversary-upload-clip-check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bab6216066de2d7bbad098a7c987c3cd7da19b380434eecbd76f325671aa9dc3 +size 694644 diff --git a/vtubermini/anniversary-upload-wide-test.svg b/vtubermini/anniversary-upload-wide-test.svg new file mode 100644 index 0000000..dbdb29f --- /dev/null +++ b/vtubermini/anniversary-upload-wide-test.svg @@ -0,0 +1 @@ +WIDE TEST \ No newline at end of file diff --git a/vtubermini/capacitor.config.ts b/vtubermini/capacitor.config.ts new file mode 100644 index 0000000..51c5df0 --- /dev/null +++ b/vtubermini/capacitor.config.ts @@ -0,0 +1,9 @@ +import type { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + appId: 'com.vtubermini.app', + appName: 'VTuber Mini', + webDir: 'dist' +}; + +export default config; diff --git a/vtubermini/docs/adaptive-architecture.md b/vtubermini/docs/adaptive-architecture.md new file mode 100644 index 0000000..d08003a --- /dev/null +++ b/vtubermini/docs/adaptive-architecture.md @@ -0,0 +1,143 @@ +# 自适应架构设计提纲(VTuber Mini) + +> 锁定约束:**混合制架构** + **只能用现有美术(不重出图)**。 +> 状态:方案提纲,待 Phase 1 实机验证后细化为实施方案。 + +--- + +## 0. 一句话结论 + +把沉浸页里**本就是独立 PNG 的 UI 层**(底栏 / 图标 / 头像 / 对话框)从固定 9:16 画布 `reference-fit-frame` 里**提出来,锚定到真实屏幕的边角 + safe-area**;背景视频继续 cover 铺满、允许边缘裁切。**一张美术都不用改**,UI 在任意屏幕比例下永远完整可用。抽卡演出页另走等比缩放。 + +--- + +## 1. 现状汇总 + +**栈**:React 19 + Vite 8 + Tailwind 4 + Capacitor(Android)。viewport = `width=device-width, viewport-fit=cover`,真实像素,无 rem 缩放。 + +**页面两类**(决定方案分工): + +| 类型 | 页面 | 现状布局 | 自适应 | +|---|---|---|---| +| 沉浸·交互页 | `home`、`anniversary` | 9:16 美术背景 + UI 百分比绝对定位(`reference-fit-frame`) | 重灾区 | +| 沉浸·演出页 | `gacha`、`heart-moments` | 全屏视频演出 + 叠层 | 重灾区 | +| 功能流式页(~14) | `my`/`shop`/`album`/各 ledger… | flex/grid 文档流 + `safe-page-*` + `clamp` 字号 | 天然较好 | + +**现状适配三件套**: +1. `.device-stage`:手机 = `100dvw × 100dvh`(真实屏幕),≥768px 才锁 9:16 预览框。 +2. `.reference-fit-frame`:`width:max(100%, 56.25svh); height:max(100%, 177.78vw)` → **cover 裁切**语义(取较大值→溢出→`overflow:hidden` 裁掉)。 +3. UI 相对该 9:16 画布百分比定位;字号 `clamp(min, vw, max)`。 + +> ⚠️ **文档与代码矛盾**:`docs/adaptive-scaling.md` 写"偏离 9:16 时 letterbox 留白",但 CSS 实为 cover 裁切。以代码为准,Phase 1 实机复核。 + +--- + +## 2. 第一性:问题的根 + +固定宽高比美术(9:16 ≈ 0.5625)铺到任意比例屏幕(主流手机 19.5:9~20:9 ≈ 0.46,更瘦长;平板/折叠屏更方)。数学上**只有三种结局**: + +- **(a) cover 裁切**:铺满无黑边,但**美术边缘恰是 UI 密集区**,被裁/热区偏移。← 现状 +- **(b) contain/letterbox**:UI 完整,但有黑边/留白,伤沉浸。 +- **(c) stretch**:变形,已排除。 + +真正痛点不是"裁 or 留白",而是:**现状把整页当成一张不可分割的图**——换美术要重标热区、UI 不能随内容流动、"好看"和"可用"焊在同一坐标系。 + +**破局点(关键发现)**:现有美术在资源上**本就是分层的**—— +- 背景:`日常首页-白天/夜晚.mp4`(纯角色场景) +- UI:`home-icons/bottom-icon-bar.png`、`home-icons/*.png`、`avatar-frame.png`、`profile-plate.png`、`home-dialog/*.png` 全是**自带完整视觉的独立 PNG** + +代码却把它们统一关进 `reference-fit-frame` 假装焊死。**把 UI 解耦出去即可**,三选一的数学约束就只剩 L0 背景在扛(背景本来就该能裁)。 + +--- + +## 3. 架构原则:分层解耦 + +``` +┌─ L2 内容流层 文字/列表/卡片,文档流,可滚动 ───────────┐ +│ ┌─ L1 锚定UI层 图标/按钮/头像/对话框 ────────────────┐ │ +│ │ 挂在 device-stage(真实屏幕),锚边角 + safe-area │ │ +│ │ 独立 PNG 自带视觉,永远完整可点,不随美术裁切 │ │ +│ │ ┌─ L0 背景层 9:16 视频,cover 铺满,边缘可裁 ──┐ │ │ +│ │ │ 留在 reference-fit-frame 内 │ │ │ +│ │ └────────────────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────┘ +``` + +**背景管"好看"(可裁),前景 UI 管"可用"(锚定屏幕、永不裁)。两套坐标系。** + +--- + +## 4. 分工方案(混合制) + +### 4.1 沉浸·交互页 `home` / `anniversary` → 分层响应式(B) + +改造动作(**不碰美术**): +1. **背景视频**留在 `reference-fit-frame` 内,`object-cover`,边缘氛围允许裁切(角色在中央,裁左右/上下氛围无碍)。 +2. **UI 层提出**:底栏、侧栏图标、头像框、账号底板、对话框,从 `reference-fit-frame` 移到 `device-stage` 直接子级,改用**锚点定位**(见 §5)。 +3. **底栏**`bottom-icon-bar.png`:锚屏幕底 + `safe-area-inset-bottom`,宽度撑满;其上 4 个热区跟底栏同一容器按百分比/flex 排 → 天然对齐,且不依赖背景视频。把 `object-fill`(拉伸) 改 `object-cover`/固定高度,避免超宽屏拉胖。 +4. **侧栏图标**:锚屏幕右 + `safe-area-inset-right`,纵向 flex 等距。 +5. **顶部头像/账号/消息**:锚屏幕顶 + `safe-area-inset-top`。 +6. **中央对话框 / 纪念日素材**:这些**视觉上要和角色对齐**,保留相对背景画布的百分比(它们落在中央安全区,cover 裁不到)。 + +> 可行前提(已确认):UI 元素都是自带完整视觉的独立 PNG,不依赖背景视频里的"承托凹槽"。**唯一实机验证项**:背景视频边缘是否画死了脱离后会露馅的 UI 凹槽(§7)。 + +### 4.2 沉浸·演出页 `gacha` / `heart-moments` → 等比缩放(A) + +抽卡/开卡是**短时全屏演出**,要的是电影感不是可用性。整段演出按固定设计稿等比 `scale` 铺满(或 cover),可接受少量边缘裁切/黑边。结束回到交互页即回到 B。工程量小,优先级低于 home。 + +### 4.3 功能流式页 → 维持 + +已是 flex/grid 流式,只统一 token + 补 safe-area 死角,最低优先级。 + +--- + +## 5. 锚定定位系统(取代百分比绝对定位) + +把 `{left:84%, top:86%, width:13.6%, height:9%}` 换成**语义锚点 + 真实偏移**: + +```ts +// 锚区域:相对真实屏幕(device-stage) +type Anchor = 'top-left' | 'top-right' | 'right-rail' | 'bottom-dock' | 'center' +interface UIPlacement { + anchor: Anchor + offset: { x: string; y: string } // rem / px / safe-area 表达式 + size: { w: string; h: string } // rem,真实尺寸,不随屏宽缩放失控 +} +``` + +- **区域锚到屏幕边** + `safe-area`,**区域内部用 flex 等距**排图标,不再逐个绝对百分比。 +- 偏移量标定原则:在**接近 9:16 的基准屏**上与现有美术位置一致;越瘦长的屏,UI 贴边比被裁的美术装饰更靠内一点(可接受)。 +- 好处:换屏幕比例 → 背景 cover 裁中央氛围,四周 UI 各自贴边,**永远完整**;换 VTuber 皮肤只换背景视频 + 图标 PNG,锚点不动。 + +--- + +## 6. 配套 + +- **运营编辑器升级**(`admin/pages/anniversary-config-page`、home 布局编辑器):从"拖出百分比矩形"改为"选锚区 + 调偏移",存语义而非画布坐标。现有拖拽编辑模式(`isHomeLayoutEditing`)需同步改造。 +- **Token 体系**:`--space-*` / `--text-*` 统一 `clamp(下限, 流式, 上限)`;流式基准从 `vw` 改 `min(vw, vh)` 或容器查询,避免瘦长屏字号被宽度拖小。 +- **极端比例兜底**:平板/折叠屏给内容区 `max-width` 居中;横屏锁竖屏或降级提示。 + +--- + +## 7. 风险与验证项(Phase 1 必做) + +1. **背景视频边缘是否含画死的 UI 凹槽** —— 若有,UI 脱离后会与视频装饰错位。实机看 home 白天/夜晚视频四边。(最高优先验证项) +2. **cover 实际裁切量** —— 四五台不同比例真机 + 折叠屏,量 home 当前裁了多少、哪些 UI 受影响,复核 cover-vs-letterbox 静态判断。 +3. **底栏拉伸** —— `object-fill` 在超宽屏把底栏图标拉胖,改造时一并修。 +4. **演出页 scale 字号** —— A 方案文字被缩放,小屏可能糊;演出页文字少,风险可控。 + +**反驳/取舍**:若本 app 终生只服务一个 VTuber、美术永不换,B 的"解耦换皮"优势用不上,A 更省。但 PRODUCT.md 写 "template **for one streamer at a time**"(一套模板逐个换 VTuber)→ 支持 B 的换皮解耦。 + +--- + +## 8. 分期路线 + +- **Phase 1(~0.5d)**:实机验证 §7 全部项。先有证据再动架构。 +- **Phase 2**:`home` 按 §4.1 + §5 试点,作为锚定定位系统样板。 +- **Phase 3**:`anniversary` 跟进;升级编辑器到锚点语义;沉淀 token 体系。 +- **Phase 4**:`gacha`/`heart-moments` 等比缩放(A);功能页统一 token。 + +--- + +来源:源码 `src/pages/home-page.tsx`、`src/index.css`、`src/lib/home-layout.ts`、`src/components/app-shell.tsx`、`src/lib/platform.ts`、`docs/adaptive-scaling.md`、`PRODUCT.md`(调研对话 2026-06-29)。 diff --git a/vtubermini/docs/android-build.md b/vtubermini/docs/android-build.md new file mode 100644 index 0000000..e124d2f --- /dev/null +++ b/vtubermini/docs/android-build.md @@ -0,0 +1,32 @@ +# Android Build + +## Prerequisites + +- Java JDK 17 or newer +- Android Studio with Android SDK and platform tools +- `ANDROID_HOME` or `ANDROID_SDK_ROOT` set in the environment +- `adb` and `gradle` available in `PATH`, or Android Studio used for the Gradle build + +Run: + +```powershell +npm.cmd run check:android +``` + +## Sync web assets + +```powershell +npm.cmd run build +npm.cmd run cap:sync +``` + +## Build a debug APK + +After the Android SDK and JDK are installed: + +```powershell +cd android +.\gradlew.bat assembleDebug +``` + +The debug APK is generated under `android/app/build/outputs/apk/debug/`. diff --git a/vtubermini/docs/api-contract.md b/vtubermini/docs/api-contract.md new file mode 100644 index 0000000..94ed817 --- /dev/null +++ b/vtubermini/docs/api-contract.md @@ -0,0 +1,36 @@ +# VTuber Mini API Contract + +This MVP uses a local in-memory mock server. The frontend calls `src/services/api-client.ts`; production can replace it with HTTP requests without changing page components. + +## Core responses + +All service calls return: + +```ts +interface ServiceResponse { + ok: boolean + data?: T + error?: { code: ServiceErrorCode; message: string } +} +``` + +## Endpoints represented by the local service + +- `petAction(state, action)`: validates daily pet interaction limits and returns the next state plus user feedback. +- `petVisit(state)`: validates the daily "visit pet" reward, writes point ledger once per day, and returns the next state. +- `drawGacha(state, mode)`: validates card pool availability and point balance, then returns draw results. +- `exchangeProduct(state, input)`: validates receiver information, diamond balance, stock, and returns an exchange order. +- `grantAssetFromAdmin(state, input)`: requires operator, positive amount, and reason, then writes asset ledger and audit log. +- `updateJoinDate`, `upsertCustomAnniversary`, `deleteCustomAnniversary`: mutate anniversary records through the service boundary. + +## Pet v2 state fields + +Pet state includes daily visit/completion flags, daily point cap, action counts, streak days, total care days, and intimacy level. The service owns point awards for daily visit, interaction rewards, and daily care completion. + +## Error codes + +- `NETWORK_FAILED` +- `INSUFFICIENT_BALANCE` +- `OUT_OF_STOCK` +- `POOL_OFFLINE` +- `VALIDATION_FAILED` diff --git a/vtubermini/docs/trusted-rules.md b/vtubermini/docs/trusted-rules.md new file mode 100644 index 0000000..a0e2292 --- /dev/null +++ b/vtubermini/docs/trusted-rules.md @@ -0,0 +1,21 @@ +# Trusted Rules + +Frontend pages must not decide asset balances, gacha results, order status, or admin grants directly. They call `apiClient`, and the service layer owns the trusted rule simulation. + +## Trusted operations + +- Points and diamonds are changed only through service calls that write `assetLedger`. +- Pet daily visit, interaction rewards, daily care completion rewards, streak counters, intimacy level, and daily point cap are handled by the service-backed pet rules. +- Gacha cost, pity progress, duplicate conversion, and result logging are handled by the service-backed rule module. +- Shop exchange validates receiver fields, stock, diamond balance, order creation, and diamond ledger together. +- Admin grants require a reason and write both asset ledger and `adminAuditLogs`. + +## Pet v2 boundaries + +- Pet status, intimacy level, streak, and decorations must not affect gacha probability, point multipliers, shop prices, or physical reward eligibility. +- Daily pet points remain capped at 80 in the MVP. +- After the daily cap is reached, pet interactions may update intimacy or feedback state, but must not write additional point ledger entries. + +## Production replacement notes + +When replacing the local mock server with a real backend, keep the contract shape in `src/services/contracts.ts`. The backend should calculate all balances and return the authoritative next user-facing state. diff --git a/vtubermini/eslint.config.js b/vtubermini/eslint.config.js new file mode 100644 index 0000000..d9abe7d --- /dev/null +++ b/vtubermini/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist', 'android/app/build', 'android/capacitor-cordova-android-plugins/build']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/vtubermini/index.html b/vtubermini/index.html new file mode 100644 index 0000000..0d693b7 --- /dev/null +++ b/vtubermini/index.html @@ -0,0 +1,13 @@ + + + + + + + vtubermini + + +
+ + + diff --git a/vtubermini/package-lock.json b/vtubermini/package-lock.json new file mode 100644 index 0000000..b9da479 --- /dev/null +++ b/vtubermini/package-lock.json @@ -0,0 +1,3949 @@ +{ + "name": "vtubermini", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vtubermini", + "version": "0.0.0", + "dependencies": { + "@capacitor/android": "^8.3.4", + "@capacitor/cli": "^8.3.4", + "@capacitor/core": "^8.3.4", + "@tailwindcss/vite": "^4.3.0", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capacitor/android": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.3.4.tgz", + "integrity": "sha512-7gJjrG3X32Am1QMLqgMztWTYMLMVNE+VZwhekNxhvYizH4mOV05vH+rC9B+f17bCkYZfyu/qXQX6hoY7kLeVZw==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^8.3.0" + } + }, + "node_modules/@capacitor/cli": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.3.4.tgz", + "integrity": "sha512-QEmyNdiDDVNYl0Mahm7YTVA/3t2tKcy7FWYDapeKGavS6HDNHZSjyTVwQpUXQbDZrrs/PS2Wau3Aii+LIFwm/A==", + "license": "MIT", + "dependencies": { + "@ionic/cli-framework-output": "^2.2.8", + "@ionic/utils-subprocess": "^3.0.1", + "@ionic/utils-terminal": "^2.3.5", + "commander": "^12.1.0", + "debug": "^4.4.0", + "env-paths": "^2.2.0", + "fs-extra": "^11.2.0", + "kleur": "^4.1.5", + "native-run": "^2.0.3", + "open": "^8.4.0", + "plist": "^3.1.0", + "prompts": "^2.4.2", + "rimraf": "^6.0.1", + "semver": "^7.6.3", + "tar": "^7.5.3", + "tslib": "^2.8.1", + "xml2js": "^0.6.2" + }, + "bin": { + "cap": "bin/capacitor", + "capacitor": "bin/capacitor" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@capacitor/cli/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@capacitor/core": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.4.tgz", + "integrity": "sha512-CqRQCkb6HXxcx/N7s+hHTN6ef2CmamFiRMITwm4qB840ph56mS42bzUgn6tKCP+RZjdDweiRHj9ytDDeN6jFag==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@ionic/cli-framework-output": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", + "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==", + "license": "MIT", + "dependencies": { + "@ionic/utils-terminal": "2.3.5", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-array": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz", + "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==", + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-fs": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz", + "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==", + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^8.0.0", + "debug": "^4.0.0", + "fs-extra": "^9.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-fs/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@ionic/utils-object": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz", + "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==", + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-process": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz", + "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==", + "license": "MIT", + "dependencies": { + "@ionic/utils-object": "2.1.6", + "@ionic/utils-terminal": "2.3.5", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz", + "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==", + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-subprocess": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz", + "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==", + "license": "MIT", + "dependencies": { + "@ionic/utils-array": "2.1.6", + "@ionic/utils-fs": "3.1.7", + "@ionic/utils-process": "2.1.12", + "@ionic/utils-stream": "3.1.7", + "@ionic/utils-terminal": "2.3.5", + "cross-spawn": "^7.0.3", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-terminal": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", + "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==", + "license": "MIT", + "dependencies": { + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bplist-parser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "license": "MIT", + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/elementtree": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", + "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==", + "license": "Apache-2.0", + "dependencies": { + "sax": "1.1.4" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz", + "integrity": "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/native-run": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz", + "integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==", + "license": "MIT", + "dependencies": { + "@ionic/utils-fs": "^3.1.7", + "@ionic/utils-terminal": "^2.3.4", + "bplist-parser": "^0.3.2", + "debug": "^4.3.4", + "elementtree": "^0.1.7", + "ini": "^4.1.1", + "plist": "^3.1.0", + "split2": "^4.2.0", + "through2": "^4.0.2", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "bin": { + "native-run": "bin/native-run" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", + "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/vtubermini/package.json b/vtubermini/package.json new file mode 100644 index 0000000..3df9c58 --- /dev/null +++ b/vtubermini/package.json @@ -0,0 +1,39 @@ +{ + "name": "vtubermini", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "cap:sync": "cap sync android", + "android:sync": "npm run build && npm run cap:sync", + "check:android": "node scripts/check-android-env.mjs", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@capacitor/android": "^8.3.4", + "@capacitor/cli": "^8.3.4", + "@capacitor/core": "^8.3.4", + "@tailwindcss/vite": "^4.3.0", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/vtubermini/pnpm-lock.yaml b/vtubermini/pnpm-lock.yaml new file mode 100644 index 0000000..30698de --- /dev/null +++ b/vtubermini/pnpm-lock.yaml @@ -0,0 +1,2578 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@capacitor/android': + specifier: ^8.3.4 + version: 8.4.1(@capacitor/core@8.4.1) + '@capacitor/cli': + specifier: ^8.3.4 + version: 8.4.1 + '@capacitor/core': + specifier: ^8.3.4 + version: 8.4.1 + '@tailwindcss/vite': + specifier: ^4.3.0 + version: 4.3.1(vite@8.1.0(@types/node@24.13.2)(jiti@2.7.0)) + lucide-react: + specifier: ^1.17.0 + version: 1.22.0(react@19.2.7) + react: + specifier: ^19.2.6 + version: 19.2.7 + react-dom: + specifier: ^19.2.6 + version: 19.2.7(react@19.2.7) + tailwindcss: + specifier: ^4.3.0 + version: 4.3.1 + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.6.0(jiti@2.7.0)) + '@types/node': + specifier: ^24.12.3 + version: 24.13.2 + '@types/react': + specifier: ^19.2.14 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.3(vite@8.1.0(@types/node@24.13.2)(jiti@2.7.0)) + eslint: + specifier: ^10.3.0 + version: 10.6.0(jiti@2.7.0) + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.6.0(jiti@2.7.0)) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.3(eslint@10.6.0(jiti@2.7.0)) + globals: + specifier: ^17.6.0 + version: 17.7.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + typescript-eslint: + specifier: ^8.59.2 + version: 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + vite: + specifier: ^8.0.12 + version: 8.1.0(@types/node@24.13.2)(jiti@2.7.0) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@capacitor/android@8.4.1': + resolution: {integrity: sha512-igtDCJ7QQn0P2qHFD9p4KXaa6V1b2PRNt+MxjVwtjTm/BJvqmiazOJq6rPjwFSZnfHm6iFoZk8TfzHd44pyBGw==} + peerDependencies: + '@capacitor/core': ^8.4.0 + + '@capacitor/cli@8.4.1': + resolution: {integrity: sha512-t7F2s7fFHCq113xgrggrmK6ctV0/8E5YfLNVLfPHp4GCTDO+tly9fZvWPf2/sOI8lMm18dLT43qbXLRTz/OZgw==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@capacitor/core@8.4.1': + resolution: {integrity: sha512-xqhOGLbTAYeOWK+IDUNSjQJAPapQjRHrIcgk9PYp52or9zFTaaMko31uNi16N6W+CRJ8VrRram6fOYILkBG2Hg==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ionic/cli-framework-output@2.2.8': + resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-array@2.1.6': + resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-fs@3.1.7': + resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-object@2.1.6': + resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-process@2.1.12': + resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-stream@3.1.7': + resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-subprocess@3.0.1': + resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-terminal@2.3.5': + resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} + engines: {node: '>=16.0.0'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tailwindcss/node@4.3.1': + resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==} + + '@tailwindcss/oxide-android-arm64@4.3.1': + resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.1': + resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.1': + resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.1': + resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.1': + resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.1': + resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/fs-extra@8.1.5': + resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/slice-ansi@4.0.0': + resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} + + '@typescript-eslint/eslint-plugin@8.62.0': + resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.62.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.62.0': + resolution: {integrity: sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.62.0': + resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.62.0': + resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.62.0': + resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.62.0': + resolution: {integrity: sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.62.0': + resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.62.0': + resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.62.0': + resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.62.0': + resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@xmldom/xmldom@0.9.10': + resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + engines: {node: '>=14.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.40: + resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==} + engines: {node: '>=6.0.0'} + hasBin: true + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.4: + resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.380: + resolution: {integrity: sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==} + + elementtree@0.1.7: + resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==} + engines: {node: '>= 0.4.0'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.21.6: + resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} + engines: {node: '>=10.13.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react-refresh@0.5.3: + resolution: {integrity: sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.6.0: + resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} + engines: {node: '>=14.14'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.22.0: + resolution: {integrity: sha512-c9o3l0PiNcgOQDW4F31BEYHudE7kgxVt3o30qMl36ZPwTxXlGB4QnLilhERvVM4uh/pl5MDyY1/gzZSYcHDtBg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + native-run@2.0.3: + resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==} + engines: {node: '>=16.0.0'} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + engines: {node: '>=18'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + plist@3.1.1: + resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} + engines: {node: '>=10.4.0'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sax@1.1.4: + resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + tailwindcss@4.3.1: + resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tar@7.5.19: + resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} + engines: {node: '>=18'} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.62.0: + resolution: {integrity: sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@8.1.0: + resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@capacitor/android@8.4.1(@capacitor/core@8.4.1)': + dependencies: + '@capacitor/core': 8.4.1 + + '@capacitor/cli@8.4.1': + dependencies: + '@ionic/cli-framework-output': 2.2.8 + '@ionic/utils-subprocess': 3.0.1 + '@ionic/utils-terminal': 2.3.5 + commander: 12.1.0 + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 11.3.5 + kleur: 4.1.5 + native-run: 2.0.3 + open: 8.4.2 + plist: 3.1.1 + prompts: 2.4.2 + rimraf: 6.1.3 + semver: 7.8.5 + tar: 7.5.19 + tslib: 2.8.1 + xml2js: 0.6.2 + transitivePeerDependencies: + - supports-color + + '@capacitor/core@8.4.1': + dependencies: + tslib: 2.8.1 + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.7.0))': + dependencies: + eslint: 10.6.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.6.0(jiti@2.7.0))': + optionalDependencies: + eslint: 10.6.0(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@ionic/cli-framework-output@2.2.8': + dependencies: + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-array@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-fs@3.1.7': + dependencies: + '@types/fs-extra': 8.1.5 + debug: 4.4.3 + fs-extra: 9.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-object@2.1.6': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-process@2.1.12': + dependencies: + '@ionic/utils-object': 2.1.6 + '@ionic/utils-terminal': 2.3.5 + debug: 4.4.3 + signal-exit: 3.0.7 + tree-kill: 1.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-stream@3.1.7': + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-subprocess@3.0.1': + dependencies: + '@ionic/utils-array': 2.1.6 + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-process': 2.1.12 + '@ionic/utils-stream': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + cross-spawn: 7.0.6 + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@ionic/utils-terminal@2.3.5': + dependencies: + '@types/slice-ansi': 4.0.0 + debug: 4.4.3 + signal-exit: 3.0.7 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + tslib: 2.8.1 + untildify: 4.0.0 + wrap-ansi: 7.0.0 + transitivePeerDependencies: + - supports-color + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.137.0': {} + + '@rolldown/binding-android-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-x64@1.1.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.3': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tailwindcss/node@4.3.1': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.1 + + '@tailwindcss/oxide-android-arm64@4.3.1': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.1': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.1': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + optional: true + + '@tailwindcss/oxide@4.3.1': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-x64': 4.3.1 + '@tailwindcss/oxide-freebsd-x64': 4.3.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-x64-musl': 4.3.1 + '@tailwindcss/oxide-wasm32-wasi': 4.3.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 + + '@tailwindcss/vite@4.3.1(vite@8.1.0(@types/node@24.13.2)(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.1 + '@tailwindcss/oxide': 4.3.1 + tailwindcss: 4.3.1 + vite: 8.1.0(@types/node@24.13.2)(jiti@2.7.0) + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/fs-extra@8.1.5': + dependencies: + '@types/node': 24.13.2 + + '@types/json-schema@7.0.15': {} + + '@types/node@24.13.2': + dependencies: + undici-types: 7.18.2 + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/slice-ansi@4.0.0': {} + + '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/type-utils': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.62.0 + eslint: 10.6.0(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.62.0 + debug: 4.4.3 + eslint: 10.6.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.62.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.62.0': + dependencies: + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 + + '@typescript-eslint/tsconfig-utils@8.62.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.6.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.62.0': {} + + '@typescript-eslint/typescript-estree@8.62.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.62.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + eslint: 10.6.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.62.0': + dependencies: + '@typescript-eslint/types': 8.62.0 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@6.0.3(vite@8.1.0(@types/node@24.13.2)(jiti@2.7.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.1.0(@types/node@24.13.2)(jiti@2.7.0) + + '@xmldom/xmldom@0.9.10': {} + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + astral-regex@2.0.0: {} + + at-least-node@1.0.0: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.40: {} + + big-integer@1.6.52: {} + + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.4: + dependencies: + baseline-browser-mapping: 2.10.40 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.380 + node-releases: 2.0.50 + update-browserslist-db: 1.2.3(browserslist@4.28.4) + + buffer-crc32@0.2.13: {} + + caniuse-lite@1.0.30001799: {} + + chownr@3.0.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-lazy-prop@2.0.0: {} + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.380: {} + + elementtree@0.1.7: + dependencies: + sax: 1.1.4 + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.21.6: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + env-paths@2.2.1: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.1.1(eslint@10.6.0(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.6.0(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.3(eslint@10.6.0(jiti@2.7.0)): + dependencies: + eslint: 10.6.0(jiti@2.7.0) + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.6.0(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fs-extra@11.3.5: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + globals@17.7.0: {} + + graceful-fs@4.2.11: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ini@4.1.3: {} + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.22.0(react@19.2.7): + dependencies: + react: 19.2.7 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + native-run@2.0.3: + dependencies: + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-terminal': 2.3.5 + bplist-parser: 0.3.2 + debug: 4.4.3 + elementtree: 0.1.7 + ini: 4.1.3 + plist: 3.1.1 + split2: 4.2.0 + through2: 4.0.2 + tslib: 2.8.1 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + + natural-compare@1.4.0: {} + + node-releases@2.0.50: {} + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + plist@3.1.1: + dependencies: + '@xmldom/xmldom': 0.9.10 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react@19.2.7: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + + rolldown@1.1.3: + dependencies: + '@oxc-project/types': 0.137.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 + + safe-buffer@5.2.1: {} + + sax@1.1.4: {} + + sax@1.6.0: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + sisteransi@1.0.5: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + tailwindcss@4.3.1: {} + + tapable@2.3.3: {} + + tar@7.5.19: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.6.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + universalify@2.0.1: {} + + untildify@4.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.4): + dependencies: + browserslist: 4.28.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@8.1.0(@types/node@24.13.2)(jiti@2.7.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.16 + rolldown: 1.1.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + jiti: 2.7.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + xml2js@0.6.2: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlbuilder@15.1.1: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/vtubermini/public/favicon.svg b/vtubermini/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/vtubermini/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vtubermini/public/icons.svg b/vtubermini/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/vtubermini/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vtubermini/scripts/check-android-env.mjs b/vtubermini/scripts/check-android-env.mjs new file mode 100644 index 0000000..e5f1c3a --- /dev/null +++ b/vtubermini/scripts/check-android-env.mjs @@ -0,0 +1,30 @@ +import { spawnSync } from 'node:child_process' + +const checks = [ + { name: 'Java JDK', command: 'java', args: ['-version'] }, + { name: 'Android Debug Bridge', command: 'adb', args: ['version'] }, + { name: 'Gradle', command: 'gradle', args: ['--version'] }, +] + +let failed = false + +for (const check of checks) { + const result = spawnSync(check.command, check.args, { encoding: 'utf8' }) + if (result.status === 0) { + console.log(`[ok] ${check.name}`) + } else { + failed = true + console.log(`[missing] ${check.name}: ${check.command} is not available in PATH`) + } +} + +if (!process.env.ANDROID_HOME && !process.env.ANDROID_SDK_ROOT) { + failed = true + console.log('[missing] Android SDK: set ANDROID_HOME or ANDROID_SDK_ROOT') +} else { + console.log(`[ok] Android SDK: ${process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT}`) +} + +if (failed) { + process.exitCode = 1 +} diff --git a/vtubermini/src/App.tsx b/vtubermini/src/App.tsx new file mode 100644 index 0000000..a5ca7a6 --- /dev/null +++ b/vtubermini/src/App.tsx @@ -0,0 +1,472 @@ +import { useCallback, useState } from 'react' +import { AdminApp } from './admin/admin-app' +import { AppShell } from './components/app-shell' +import { BirthdaySetupModal, type ProfileSetupInput } from './components/profile/birthday-setup-modal' +import { addAssetLedgerEntry } from './lib/asset-ledger' +import { + clampAnniversaryImageLayout, + defaultAnniversaryImageLayout, + normalizeAnniversaryImageLayout, +} from './lib/anniversary-image-layout' +import { updateJoinDate } from './lib/anniversary-rules' +import { clampHomeLayoutRect, defaultHomeLayout } from './lib/home-layout' +import { mockState } from './lib/mock-data' +import { apiClient } from './services/api-client' +import type { + AppRoute, + AnniversaryImageLayout, + AnniversaryInput, + AdminGrantInput, + AdminGrantResult, + ExchangeInput, + ExchangeResult, + GachaDrawMode, + GachaResult, + HomeBackgroundMode, + HomeCoverId, + HomeLayout, + HomeLayoutElementId, + HomeLayoutRect, + PetActionResult, + PetActionType, +} from './lib/types' +import { ActivityPage } from './pages/activity-page' +import { AlbumPage } from './pages/album-page' +import { AnniversaryPage } from './pages/anniversary-page' +import { DiamondLedgerPage } from './pages/diamond-ledger-page' +import { GachaPage } from './pages/gacha-page' +import { GachaLogPage } from './pages/gacha-log-page' +import { HomePage } from './pages/home-page' +import { HeartMomentsPage } from './pages/heart-moments-page' +import { LoginPage } from './pages/login-page' +import { MyPage } from './pages/my-page' +import { NotificationsPage } from './pages/notifications-page' +import { OrderListPage } from './pages/order-list-page' +import { PetPage } from './pages/pet-page' +import { PointsLedgerPage } from './pages/points-ledger-page' +import { ProductDetailPage } from './pages/product-detail-page' +import { RulesPage } from './pages/rules-page' +import { ShopPage } from './pages/shop-page' +import { SupportPage } from './pages/support-page' + +const birthdayStorageKey = 'vtubermini:user-birthday' +const joinDateStorageKey = 'vtubermini:user-join-date' +const legacyBirthdayPromptStorageKey = 'vtubermini:birthday-prompt-skipped' +const profileDatesResetStorageKey = 'vtubermini:profile-dates-reset-20260603' +const hotspotVisibilityStorageKey = 'vtubermini:show-home-hotspots' +const homeBackgroundModeStorageKey = 'vtubermini:home-background-mode' +const homeLayoutEditingStorageKey = 'vtubermini:home-layout-editing' +const anniversaryImageLayoutStorageKey = 'vtubermini:anniversary-image-layout' + +function clearStoredProfileDatesOnce() { + if (localStorage.getItem(profileDatesResetStorageKey) === 'true') { + return + } + + localStorage.removeItem(birthdayStorageKey) + localStorage.removeItem(joinDateStorageKey) + localStorage.removeItem(legacyBirthdayPromptStorageKey) + localStorage.setItem(profileDatesResetStorageKey, 'true') +} + +function getInitialState() { + clearStoredProfileDatesOnce() + + const birthday = localStorage.getItem(birthdayStorageKey) ?? undefined + const joinDate = localStorage.getItem(joinDateStorageKey) ?? undefined + let initialState = { + ...mockState, + anniversaries: mockState.anniversaries.filter((item) => item.type !== 'join'), + } + + if (birthday) { + initialState = { + ...initialState, + user: { + ...initialState.user, + birthday, + }, + } + } + + if (joinDate) { + initialState = updateJoinDate(initialState, joinDate) + } + + return initialState +} + +function isBirthdayToday(birthday?: string) { + if (!birthday) { + return false + } + + const birthdayDate = new Date(`${birthday}T00:00:00`) + const today = new Date() + return birthdayDate.getMonth() === today.getMonth() && birthdayDate.getDate() === today.getDate() +} + +function getStoredHomeBackgroundMode(): HomeBackgroundMode { + const value = localStorage.getItem(homeBackgroundModeStorageKey) + return value === 'day' || value === 'night' || value === 'birthday' || value === 'auto' ? value : 'auto' +} + +function getStoredAnniversaryImageLayout(): AnniversaryImageLayout { + const storedValue = localStorage.getItem(anniversaryImageLayoutStorageKey) + + if (!storedValue) { + return defaultAnniversaryImageLayout + } + + try { + return normalizeAnniversaryImageLayout(JSON.parse(storedValue)) + } catch { + return defaultAnniversaryImageLayout + } +} + +function App() { + const [isLoggedIn, setIsLoggedIn] = useState(false) + const [route, setRoute] = useState('home') + const [state, setState] = useState(getInitialState) + const [selectedProductId, setSelectedProductId] = useState(mockState.products[0]?.id ?? '') + const [showHomeHotspots, setShowHomeHotspots] = useState( + localStorage.getItem(hotspotVisibilityStorageKey) === 'true', + ) + const [homeBackgroundMode, setHomeBackgroundMode] = useState(getStoredHomeBackgroundMode) + const [homeCoverId, setHomeCoverId] = useState(null) + const [homeLayout, setHomeLayout] = useState(defaultHomeLayout) + const [anniversaryImageLayout, setAnniversaryImageLayout] = useState( + getStoredAnniversaryImageLayout, + ) + const [isHomeLayoutEditing, setIsHomeLayoutEditing] = useState( + localStorage.getItem(homeLayoutEditingStorageKey) === 'true', + ) + const [isProfilePromptSkipped, setIsProfilePromptSkipped] = useState(false) + + function handlePetAction(action: PetActionType): PetActionResult { + const response = apiClient.petAction(state, action) + const result = response.data + if (!result) { + return { nextState: state, message: response.error?.message ?? '操作失败。', tone: 'warning' } + } + setState(result.nextState) + return result + } + + function handlePetGrowthReward() { + setState((current) => + addAssetLedgerEntry(current, { + assetType: 'diamonds', + businessId: `pet-growth-${Date.now()}`, + change: 100, + reason: '宠物成长奖励', + sourceType: 'pet', + }), + ) + } + + const handlePetVisit = useCallback(() => { + setState((current) => apiClient.petVisit(current).data?.nextState ?? current) + }, []) + + function handlePetDemoReset() { + setState((current) => ({ + ...current, + pet: { + ...current.pet, + cleanliness: Math.max(32, current.pet.cleanliness - 24), + dailyActionCounts: { + clean: 0, + feed: 0, + pat: 0, + play: 0, + }, + dailyCareCompleted: false, + dailyLoginSeen: false, + dailyPointsEarned: 0, + energy: Math.min(85, Math.max(45, current.pet.energy + 14)), + hunger: Math.max(35, current.pet.hunger - 22), + mood: Math.max(38, current.pet.mood - 16), + }, + })) + } + + function handleGachaDraw(mode: GachaDrawMode): GachaResult { + const response = apiClient.drawGacha(state, mode) + const result = response.data + if (!result) { + return { nextState: state, results: [], message: response.error?.message ?? '抽卡失败。', success: false, totalCost: 0 } + } + setState(result.nextState) + return result + } + + function handleHeartMomentDraw(mode: GachaDrawMode): GachaResult { + const response = apiClient.drawHeartMoment(state, mode) + const result = response.data + if (!result) { + return { nextState: state, results: [], message: response.error?.message ?? '心动抽取失败。', success: false, totalCost: 0 } + } + setState(result.nextState) + return result + } + + function handleExchange(input: ExchangeInput): ExchangeResult { + const response = apiClient.exchangeProduct(state, input) + const result = response.data + if (!result) { + return { nextState: state, message: response.error?.message ?? '兑换失败。', success: false } + } + setState(result.nextState) + return result + } + + function handleOpenProduct(productId: string) { + setSelectedProductId(productId) + setRoute('productDetail') + } + + function handleAnniversarySave(input: AnniversaryInput) { + setState((current) => apiClient.upsertCustomAnniversary(current, input).data ?? current) + } + + function handleAdminGrant(input: AdminGrantInput): AdminGrantResult { + const response = apiClient.grantAssetFromAdmin(state, input) + const result = response.data + if (!result) { + return { nextState: state, message: response.error?.message ?? '发放失败。', success: false } + } + setState(result.nextState) + return result + } + + function handleProfileSave({ birthday, joinDate }: ProfileSetupInput) { + setState((current) => { + const stateWithBirthday = current.user.birthday + ? current + : { + ...current, + user: { + ...current.user, + birthday, + }, + } + + localStorage.setItem(birthdayStorageKey, stateWithBirthday.user.birthday ?? birthday) + localStorage.setItem(joinDateStorageKey, joinDate) + return updateJoinDate(stateWithBirthday, joinDate) + }) + setIsProfilePromptSkipped(true) + } + + function handleBirthdaySave(birthday: string, joinDate = getJoinDate(state) ?? new Date().toISOString().slice(0, 10)) { + handleProfileSave({ birthday, joinDate }) + } + + function handleProfilePromptSkip() { + setIsProfilePromptSkipped(true) + } + + function handleJoinDateUpdate(joinDate: string) { + setState((current) => { + localStorage.setItem(joinDateStorageKey, joinDate) + return apiClient.updateJoinDate(current, joinDate).data ?? current + }) + } + + function getJoinDate(currentState = state) { + return currentState.anniversaries.find((item) => item.type === 'join')?.date + } + + function handleHotspotVisibilityChange(isVisible: boolean) { + localStorage.setItem(hotspotVisibilityStorageKey, String(isVisible)) + setShowHomeHotspots(isVisible) + } + + function handleHomeBackgroundModeChange(mode: HomeBackgroundMode) { + localStorage.setItem(homeBackgroundModeStorageKey, mode) + setHomeBackgroundMode(mode) + } + + function handleHomeCoverChange(coverId: HomeCoverId) { + setHomeCoverId(coverId) + } + + function handleHomeLayoutChange(elementId: HomeLayoutElementId, rect: HomeLayoutRect) { + setHomeLayout((current) => { + const next = { + ...current, + [elementId]: clampHomeLayoutRect(rect), + } + + return next + }) + } + + function handleHomeLayoutReset() { + setHomeLayout(defaultHomeLayout) + } + + async function handleHomeLayoutSaveToCode() { + const response = await fetch('/__vtubermini/save-layouts', { + body: JSON.stringify({ + anniversaryImageLayout, + homeLayout, + }), + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + }) + + if (!response.ok) { + const message = await response.text() + throw new Error(message || '保存失败') + } + } + + function handleAnniversaryImageLayoutChange(layout: AnniversaryImageLayout) { + const next = clampAnniversaryImageLayout(layout) + localStorage.setItem(anniversaryImageLayoutStorageKey, JSON.stringify(next)) + setAnniversaryImageLayout(next) + } + + function handleAnniversaryImageLayoutReset() { + localStorage.removeItem(anniversaryImageLayoutStorageKey) + setAnniversaryImageLayout(defaultAnniversaryImageLayout) + } + + function handleHomeLayoutEditingChange(isEditing: boolean) { + localStorage.setItem(homeLayoutEditingStorageKey, String(isEditing)) + setIsHomeLayoutEditing(isEditing) + } + + const selectedProduct = state.products.find((product) => product.id === selectedProductId) ?? state.products[0] + const shouldShowBirthdayLogin = + homeBackgroundMode === 'birthday' || (homeBackgroundMode === 'auto' && isBirthdayToday(state.user.birthday)) + const loginVariant = shouldShowBirthdayLogin ? 'birthday' : 'default' + const joinDate = getJoinDate() + const shouldShowProfilePrompt = isLoggedIn && (!state.user.birthday || !joinDate) && !isProfilePromptSkipped + + if (!isLoggedIn) { + return setIsLoggedIn(true)} variant={loginVariant} /> + } + + if (route === 'admin') { + return setRoute('home')} onGrantAsset={handleAdminGrant} state={state} /> + } + + const page = { + home: ( + + ), + pet: ( + setRoute('home')} + onPetAction={handlePetAction} + onPetGrowthReward={handlePetGrowthReward} + onPetDemoReset={handlePetDemoReset} + onPetVisit={handlePetVisit} + state={state} + /> + ), + gacha: setRoute('home')} onDraw={handleGachaDraw} onNavigate={setRoute} state={state} />, + moments: ( + setRoute('home')} + onDraw={handleHeartMomentDraw} + state={state} + /> + ), + shop: ( + setRoute('home')} + onOpenProduct={handleOpenProduct} + products={state.products} + /> + ), + my: ( + setRoute('home')} + onNavigate={setRoute} + onSetBirthday={handleBirthdaySave} + onHomeBackgroundModeChange={handleHomeBackgroundModeChange} + onHomeLayoutChange={handleHomeLayoutChange} + onHomeLayoutEditingChange={handleHomeLayoutEditingChange} + onHomeLayoutReset={handleHomeLayoutReset} + onHomeLayoutSaveToCode={handleHomeLayoutSaveToCode} + onAnniversaryImageLayoutChange={handleAnniversaryImageLayoutChange} + onAnniversaryImageLayoutReset={handleAnniversaryImageLayoutReset} + onToggleHomeHotspots={handleHotspotVisibilityChange} + anniversaryImageLayout={anniversaryImageLayout} + homeBackgroundMode={homeBackgroundMode} + homeLayout={homeLayout} + isHomeLayoutEditing={isHomeLayoutEditing} + showHomeHotspots={showHomeHotspots} + state={state} + /> + ), + anniversary: ( + setRoute('home')} + anniversaryImageLayout={anniversaryImageLayout} + isLayoutEditing={isHomeLayoutEditing} + onSaveCustom={handleAnniversarySave} + onUpdateJoinDate={handleJoinDateUpdate} + state={state} + /> + ), + activity: setRoute('home')} />, + album: setRoute('home')} onSetHomeCover={handleHomeCoverChange} state={state} />, + productDetail: selectedProduct ? ( + setRoute('shop')} + onExchange={handleExchange} + onOpenOrders={() => setRoute('orders')} + product={selectedProduct} + state={state} + /> + ) : ( + setRoute('home')} + onOpenProduct={handleOpenProduct} + products={state.products} + /> + ), + orders: setRoute('home')} orders={state.orders} />, + pointsLedger: setRoute('home')} />, + diamondLedger: setRoute('home')} />, + gachaLog: setRoute('home')} />, + rules: setRoute('home')} />, + support: setRoute('home')} tickets={state.supportTickets} />, + notifications: setRoute('home')} />, + admin: null, + }[route] + + return ( + + {page} + {shouldShowProfilePrompt ? ( + + ) : null} + + ) +} + +export default App diff --git a/vtubermini/src/admin/admin-app.tsx b/vtubermini/src/admin/admin-app.tsx new file mode 100644 index 0000000..ff85559 --- /dev/null +++ b/vtubermini/src/admin/admin-app.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { AdminLayout, type AdminPageKey } from './admin-layout' +import { AdminDashboard } from './pages/admin-dashboard' +import { AnniversaryConfigPage } from './pages/anniversary-config-page' +import { AuditLogPage } from './pages/audit-log-page' +import { CardPoolPage } from './pages/card-pool-page' +import { ShopOrdersPage } from './pages/shop-orders-page' +import { UserAssetsPage } from './pages/user-assets-page' +import type { AdminGrantInput, AdminGrantResult, MockState } from '../lib/types' + +interface AdminAppProps { + onBackToApp: () => void + onGrantAsset: (input: AdminGrantInput) => AdminGrantResult + state: MockState +} + +export function AdminApp({ onBackToApp, onGrantAsset, state }: AdminAppProps) { + const [activePage, setActivePage] = useState('dashboard') + + const page = { + dashboard: , + assets: , + cards: , + shop: , + anniversaries: , + audit: , + }[activePage] + + return ( + + {page} + + ) +} diff --git a/vtubermini/src/admin/admin-layout.tsx b/vtubermini/src/admin/admin-layout.tsx new file mode 100644 index 0000000..792ff8e --- /dev/null +++ b/vtubermini/src/admin/admin-layout.tsx @@ -0,0 +1,78 @@ +import type { ReactNode } from 'react' +import { BarChart3, CalendarDays, ClipboardList, Gem, Gift, Layers3, ShieldCheck, Smartphone } from 'lucide-react' + +export type AdminPageKey = 'dashboard' | 'assets' | 'cards' | 'shop' | 'anniversaries' | 'audit' + +interface AdminLayoutProps { + activePage: AdminPageKey + children: ReactNode + onBackToApp: () => void + onNavigate: (page: AdminPageKey) => void +} + +const navItems = [ + { key: 'dashboard', label: '概览', icon: BarChart3 }, + { key: 'assets', label: '用户资产', icon: Gem }, + { key: 'cards', label: '卡牌卡池', icon: Layers3 }, + { key: 'shop', label: '商城订单', icon: Gift }, + { key: 'anniversaries', label: '纪念日配置', icon: CalendarDays }, + { key: 'audit', label: '审计日志', icon: ClipboardList }, +] satisfies Array<{ key: AdminPageKey; label: string; icon: typeof BarChart3 }> + +export function AdminLayout({ activePage, children, onBackToApp, onNavigate }: AdminLayoutProps) { + return ( +
+
+ +
+
+
+

管理端

+

后台工坊基础管理

+
+ +
+
+ {navItems.map(({ key, label }) => ( + + ))} +
+
{children}
+
+
+
+ ) +} diff --git a/vtubermini/src/admin/pages/admin-dashboard.tsx b/vtubermini/src/admin/pages/admin-dashboard.tsx new file mode 100644 index 0000000..d87ff54 --- /dev/null +++ b/vtubermini/src/admin/pages/admin-dashboard.tsx @@ -0,0 +1,57 @@ +import { ClipboardList, Gift, Layers3, UsersRound } from 'lucide-react' +import type { ReactNode } from 'react' +import { DiamondCurrencyIcon, PointsCurrencyIcon } from '../../components/common/currency-icon' +import type { MockState } from '../../lib/types' + +export function AdminDashboard({ state }: { state: MockState }) { + const ownedCards = state.cards.filter((card) => card.owned).length + const pendingOrders = state.orders.filter((order) => order.status === 'pendingShipment').length + + return ( +
+
+

今日运营概览

+

资产、卡池、商城和审计状态

+
+
+ } label="用户" value={state.user.name} /> + } + label="积分" + value={state.user.points} + /> + } + label="钻石" + value={state.user.diamonds} + /> + } label="已拥有卡牌" value={`${ownedCards}/${state.cards.length}`} /> + } label="待发货订单" value={pendingOrders} /> +
+
+
+ + 近期后台操作 +
+
+ {state.adminAuditLogs.slice(0, 4).map((log) => ( +
+ {log.action} + {log.reason} + {log.createdAt} +
+ ))} +
+
+
+ ) +} + +function Metric({ icon, label, value }: { icon: ReactNode; label: string; value: ReactNode }) { + return ( +
+
{icon}{label}
+

{value}

+
+ ) +} diff --git a/vtubermini/src/admin/pages/anniversary-config-page.tsx b/vtubermini/src/admin/pages/anniversary-config-page.tsx new file mode 100644 index 0000000..a5d17e9 --- /dev/null +++ b/vtubermini/src/admin/pages/anniversary-config-page.tsx @@ -0,0 +1,32 @@ +import type { MockState } from '../../lib/types' + +export function AnniversaryConfigPage({ state }: { state: MockState }) { + return ( +
+
+

纪念日配置管理

+

系统纪念日、用户自定义纪念日和活动入口配置。

+
+
+
+ + + + {state.anniversaries.map((item) => ( + + + + + + + + + + ))} + +
标题类型日期统计规则提醒活动入口说明
{item.title}{item.type}{item.date}{item.repeatYearly ? '每年重复 · 还剩' : '累计天数 · 已经'}{item.reminderEnabled ? `提前 ${item.reminderOffsetDays} 天` : '关闭'}{item.linkedActivity ?? '无'}{item.note ?? '无'}
+
+
+
+ ) +} diff --git a/vtubermini/src/admin/pages/audit-log-page.tsx b/vtubermini/src/admin/pages/audit-log-page.tsx new file mode 100644 index 0000000..46bcf00 --- /dev/null +++ b/vtubermini/src/admin/pages/audit-log-page.tsx @@ -0,0 +1,29 @@ +import type { AdminAuditLog } from '../../lib/types' + +export function AuditLogPage({ logs }: { logs: AdminAuditLog[] }) { + return ( +
+

操作审计日志

+
+ + + + + + {logs.map((log) => ( + + + + + + + + + + ))} + +
时间操作人动作对象原因状态业务号
{log.createdAt}{log.operator}{log.action}{log.target}{log.reason}{log.status}{log.businessId}
+
+
+ ) +} diff --git a/vtubermini/src/admin/pages/card-pool-page.tsx b/vtubermini/src/admin/pages/card-pool-page.tsx new file mode 100644 index 0000000..e4f51e6 --- /dev/null +++ b/vtubermini/src/admin/pages/card-pool-page.tsx @@ -0,0 +1,69 @@ +import type { ReactNode } from 'react' +import { PointsCurrencyIcon } from '../../components/common/currency-icon' +import type { MockState, Rarity } from '../../lib/types' + +const rarityClass: Record = { + N: 'bg-[#eef2f7] text-[#55708b]', + R: 'bg-[#e8f3ff] text-[var(--deep-blue)]', + SR: 'bg-[#fff5df] text-[#9b6b11]', + SSR: 'bg-[#ffecec] text-[#b4534b]', +} + +export function CardPoolPage({ state }: { state: MockState }) { + return ( +
+
+

卡牌与卡池管理

+
+ + + + {state.pool.costPoints} + + } + /> + + +
+
+
+

概率配置

+
+ {Object.entries(state.pool.rates).map(([rarity, rate]) => ( +
+ {rarity} +

{rate}%

+
+ ))} +
+
+
+

卡牌列表

+
+ + + + {state.cards.map((card) => ( + + + + + + + + + ))} + +
编号名称系列稀有度拥有重复数
{card.id}{card.title}{card.series}{card.rarity}{card.owned ? '已拥有' : '未拥有'}{card.duplicateCount}
+
+
+
+ ) +} + +function Info({ label, value }: { label: string; value: ReactNode }) { + return

{label}

{value}

+} diff --git a/vtubermini/src/admin/pages/shop-orders-page.tsx b/vtubermini/src/admin/pages/shop-orders-page.tsx new file mode 100644 index 0000000..2481950 --- /dev/null +++ b/vtubermini/src/admin/pages/shop-orders-page.tsx @@ -0,0 +1,72 @@ +import { PointsCurrencyIcon } from '../../components/common/currency-icon' +import type { MockState, OrderStatus } from '../../lib/types' + +const statusLabel: Record = { + afterSales: '售后中', + cancelled: '已取消', + completed: '已完成', + pendingShipment: '待发货', + refunded: '已退款', + shipped: '已发货', +} + +export function ShopOrdersPage({ state }: { state: MockState }) { + return ( +
+
+

商城商品管理

+
+ + + + {state.products.map((product) => ( + + + + + + + + + ))} + +
商品规格积分价库存状态发货规则
{product.name}{product.spec} + + + {product.diamondPrice} + + {product.stock}{product.stock > 0 ? '上架中' : '售罄'}{product.shippingRule}
+
+
+
+

兑换订单

+ {state.orders.length === 0 ? ( +

暂无兑换订单,前台完成兑换后会同步显示。

+ ) : ( +
+ + + + {state.orders.map((order) => ( + + + + + + + + + ))} + +
订单号商品状态收货人积分创建时间
{order.id}{order.productName}{statusLabel[order.status]}{order.receiverName} + + + {order.diamondCost} + + {order.createdAt}
+
+ )} +
+
+ ) +} diff --git a/vtubermini/src/admin/pages/user-assets-page.tsx b/vtubermini/src/admin/pages/user-assets-page.tsx new file mode 100644 index 0000000..8c2bf92 --- /dev/null +++ b/vtubermini/src/admin/pages/user-assets-page.tsx @@ -0,0 +1,120 @@ +import { useState } from 'react' +import type { ReactNode } from 'react' +import { DiamondCurrencyIcon, PointsCurrencyIcon } from '../../components/common/currency-icon' +import type { AdminGrantInput, AdminGrantResult, MockState } from '../../lib/types' + +interface UserAssetsPageProps { + onGrantAsset: (input: AdminGrantInput) => AdminGrantResult + state: MockState +} + +export function UserAssetsPage({ onGrantAsset, state }: UserAssetsPageProps) { + const [assetType, setAssetType] = useState('points') + const [amount, setAmount] = useState(10) + const [operator, setOperator] = useState('admin.moon') + const [reason, setReason] = useState('活动奖励补发') + const [message, setMessage] = useState('') + + function handleGrant() { + const result = onGrantAsset({ amount, assetType, operator, reason }) + setMessage(result.message) + } + + return ( +
+
+

用户与资产管理

+
+ } label="积分余额" value={state.user.points} /> + } label="钻石余额" value={state.user.diamonds} /> +
+
+ + + + + {message ?

{message}

: null} + +
+
+
+

资产流水

+
+ + + + + + {state.assetLedger.map((entry) => ( + + + + + + + + + ))} + +
资产变动来源业务号余额原因
+ + 0 ? 'text-[#1f8a5b]' : 'text-[#b4534b]'}>{entry.change > 0 ? '+' : ''}{entry.change}{entry.sourceType}{entry.businessId}{entry.balanceAfter}{entry.reason}
+
+
+
+ ) +} + +function AssetBox({ icon, label, value }: { icon: ReactNode; label: string; value: number }) { + return ( +
+

+ {icon} + {label} +

+

{value}

+
+ ) +} + +function AssetTypeLabel({ assetType }: { assetType: string }) { + if (assetType === 'points') { + return ( + + + 积分 + + ) + } + + if (assetType === 'diamonds') { + return ( + + + 钻石 + + ) + } + + return assetType +} + +function Field({ label, onChange, value }: { label: string; onChange: (value: string) => void; value: string }) { + return ( + + ) +} diff --git a/vtubermini/src/assets/desktop-assets/1777526300869.png b/vtubermini/src/assets/desktop-assets/1777526300869.png new file mode 100644 index 0000000..5f44a7d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/1777526300869.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d71c012e7f15fa1b715ac8202873a9aabb42c4c40cfbd1dad177395d89cc411d +size 2627955 diff --git a/vtubermini/src/assets/desktop-assets/1780380476301.png b/vtubermini/src/assets/desktop-assets/1780380476301.png new file mode 100644 index 0000000..fa1f82c --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/1780380476301.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f496fa69e0930ecf580e28ffde1794709c2851b80f07e3a1951e037dc15a832 +size 2475140 diff --git a/vtubermini/src/assets/desktop-assets/20260601-151658.mp4 b/vtubermini/src/assets/desktop-assets/20260601-151658.mp4 new file mode 100644 index 0000000..4bfbe0a --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/20260601-151658.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e833e75c76b3419113eee2fe4e95a1cfffe501b0398c9935519e8e78dc74403d +size 1326994 diff --git a/vtubermini/src/assets/desktop-assets/20260601-151705.mp4 b/vtubermini/src/assets/desktop-assets/20260601-151705.mp4 new file mode 100644 index 0000000..13b24dd --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/20260601-151705.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63d60c21a177f3140a7249a8a2b2b6da68563897ab7999ad10a4895fccbc6167 +size 1588763 diff --git a/vtubermini/src/assets/desktop-assets/activity-image-1.png b/vtubermini/src/assets/desktop-assets/activity-image-1.png new file mode 100644 index 0000000..859e7d2 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/activity-image-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:825ddd36c3bf8a956bda2f031aa763d81bf501a5907ee3525a52953df19f80b5 +size 836000 diff --git a/vtubermini/src/assets/desktop-assets/activity-image-2.png b/vtubermini/src/assets/desktop-assets/activity-image-2.png new file mode 100644 index 0000000..c5fc1cb --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/activity-image-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba126b5710759c66fb4381f40f805c008e915aca516ab97ef490e56e647cc638 +size 964885 diff --git a/vtubermini/src/assets/desktop-assets/currency-star.png b/vtubermini/src/assets/desktop-assets/currency-star.png new file mode 100644 index 0000000..53f7cec --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/currency-star.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d527dae22cef2695134ce5c6ddcac24a15cd573baeb56ef6bce31593eab44530 +size 9454 diff --git a/vtubermini/src/assets/desktop-assets/enter-garden-button.png b/vtubermini/src/assets/desktop-assets/enter-garden-button.png new file mode 100644 index 0000000..3d94e4b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/enter-garden-button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0383ef829bc7ac4685561ac0f7943c20b3bba52251b669b01df19db69cc08320 +size 72365 diff --git a/vtubermini/src/assets/desktop-assets/gacha-result-bg.png b/vtubermini/src/assets/desktop-assets/gacha-result-bg.png new file mode 100644 index 0000000..bb59071 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/gacha-result-bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b09fdaad1232ccb915f7fcb50b8362de36cda644203e19712120334dfbe7b51 +size 7987742 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/1780456110605.png b/vtubermini/src/assets/desktop-assets/heart-archive/1780456110605.png new file mode 100644 index 0000000..4ea395b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/1780456110605.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30c2dcbf9043a2c9237b86048e420135c70efa6c2d00cc59dab7328c388a9e73 +size 5381808 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/1780466208619.png b/vtubermini/src/assets/desktop-assets/heart-archive/1780466208619.png new file mode 100644 index 0000000..693b3f5 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/1780466208619.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d67aaaaf94728da865b1cbc5d83439d6c17e40cd48e08418a7c08cde9ca1332 +size 8869750 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/1780466393837.png b/vtubermini/src/assets/desktop-assets/heart-archive/1780466393837.png new file mode 100644 index 0000000..4f43bc4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/1780466393837.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c73347a4bbf93431ce7d811c4d4240f2bc5a6f63d09ff9fe8b294624ba83549 +size 8574615 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/1780467961316.png b/vtubermini/src/assets/desktop-assets/heart-archive/1780467961316.png new file mode 100644 index 0000000..c94c321 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/1780467961316.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34db02034161f160bf0b937f6afd4ad1cf0716e433f464f2ba6e5bc1c9b315b4 +size 6066406 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/1780468924364.png b/vtubermini/src/assets/desktop-assets/heart-archive/1780468924364.png new file mode 100644 index 0000000..86fa374 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/1780468924364.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0f053fc098812f1f03b96199b8d8634eec94fe89efb5dee89261778bff3790b +size 9977356 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-1.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-1.png new file mode 100644 index 0000000..af4d5bb --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28ec19603e694d314dff5f6b4b40fe0f658dd0003b3a3530ddb335455c96f6db +size 2666856 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-2.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-2.png new file mode 100644 index 0000000..e20d5b7 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd610005a2d3043806d533a0e332e6fc5f96f661ab601f51ff8301803a6330b3 +size 3068199 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-3.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-3.png new file mode 100644 index 0000000..f02e953 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a39f7e0e543365058a7795a5eb6cc0ba16b9eb3aa0d20c66b2ae856427f2fd7 +size 3160361 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-4.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-4.png new file mode 100644 index 0000000..f92a31b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f890cca5ff97464e7b4417f47a46e8c488edb97ea47ff75e623fdfa142d39a76 +size 5561168 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-5.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-5.png new file mode 100644 index 0000000..90bb6d2 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a3876174b89cbfd1d01bd324b67e0baf68da083c5684fa90ac6392fff572ccf +size 6282168 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png new file mode 100644 index 0000000..341ff16 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc09d6ef1f2bf9dc99d956a2aaa423c87e3fb8178192d9a25e88dee0a72040e7 +size 6097409 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png new file mode 100644 index 0000000..56b02e5 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b93dc1e8faa8e627f9e96dc537b7f001df2dcbb890c7d123b2fd031580746cc5 +size 5552947 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png new file mode 100644 index 0000000..1f10ab4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c1197d21d60ed541039999d6050d2ddb041c77f8030939b836cabbac55ea934 +size 5368226 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-mist-notes.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-mist-notes.png new file mode 100644 index 0000000..ede14d8 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-mist-notes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d16a567baa015dd5cde6ffd567cf1e4e7031f91000cf51f0465aab21cfff8c1f +size 4741287 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png new file mode 100644 index 0000000..5061bda --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54936f83e82be5a1c23daa3291d204f0829ddc59fb03bac53355c75114d5e796 +size 4811316 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-silent-contract.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-silent-contract.png new file mode 100644 index 0000000..c7aac41 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-silent-contract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d7f913c38ebf242b4512b7067b54d404a982c3b07cad423610a12a565c2cb25 +size 4736963 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-star-lab.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-star-lab.png new file mode 100644 index 0000000..a04a0da --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-star-lab.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db45d2280d91e72945aae3cb9a0681f0f8a86afa51f1250afce1f7fd5e5c3559 +size 5825698 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-storm-side.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-storm-side.png new file mode 100644 index 0000000..63919e6 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-storm-side.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3a513c4a8721e4f1a5d44b73468474a06ce15ef3d8df4787feab02c9ac3a932 +size 4836443 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png new file mode 100644 index 0000000..f74efef --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ee3e78bd3d37627a185da5aed92929452dad2c12d6573adf80ac7e000b7dbcf +size 5871697 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png new file mode 100644 index 0000000..d2df56a --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be3191ef90c2f5e940d2aaa7f7d782017fb477cc1ea4fb455a1f67bead2755b0 +size 5335911 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/archive-reference.png b/vtubermini/src/assets/desktop-assets/heart-archive/archive-reference.png new file mode 100644 index 0000000..e729ad9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/archive-reference.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c23c70880e0175f0b49df271e185c948bdf9157dffda5c6c14038788acfdf95 +size 2946324 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-chenguang-weilan.mp4 b/vtubermini/src/assets/desktop-assets/heart-archive/detail-chenguang-weilan.mp4 new file mode 100644 index 0000000..81c76f6 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-chenguang-weilan.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6181c13557ece7d95ab311bd5aa6e8b2025ea8d2edb967b35631046ac2ca511f +size 6974837 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-chiying-xingge.mp4 b/vtubermini/src/assets/desktop-assets/heart-archive/detail-chiying-xingge.mp4 new file mode 100644 index 0000000..d368de0 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-chiying-xingge.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b61adfbba105b0708471e782b2d8469bfbacc19bc48bf27427653ade09124a +size 6880218 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-huajian-mansu.mp4 b/vtubermini/src/assets/desktop-assets/heart-archive/detail-huajian-mansu.mp4 new file mode 100644 index 0000000..90107b2 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-huajian-mansu.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcd01a97bcf18b8b55a939aa7df40d2df0eafacb36dd149ffc1c1cf86858fb26 +size 6964203 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingyao-shengyu.mp4 b/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingyao-shengyu.mp4 new file mode 100644 index 0000000..7e8c3ae --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingyao-shengyu.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95472292b87e7078ff77843afd83721be7289886cb46d7086a97e368f9bf6d73 +size 7246278 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingye-xiehou.mp4 b/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingye-xiehou.mp4 new file mode 100644 index 0000000..3083df5 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-xingye-xiehou.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54d0afe9584844624c30658cbc14bd1d10a8ebee0791788e7663e31da00bd7e7 +size 7092926 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-星夜邂逅.png b/vtubermini/src/assets/desktop-assets/heart-archive/detail-星夜邂逅.png new file mode 100644 index 0000000..5af6a9e --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-星夜邂逅.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:201bb1b87e93bdd9c3d39257d0239fe7d80bacaf1514706de5298b87a097463a +size 3144117 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-星耀圣羽.png b/vtubermini/src/assets/desktop-assets/heart-archive/detail-星耀圣羽.png new file mode 100644 index 0000000..8dadf1d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-星耀圣羽.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b45692b2324cd10113203e5ff87bb0293343cb6393ec6e89939ecd9236fadc2 +size 3288688 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/detail-赤影行歌.png b/vtubermini/src/assets/desktop-assets/heart-archive/detail-赤影行歌.png new file mode 100644 index 0000000..3923958 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/detail-赤影行歌.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bd1999c4f8a5640851a7d8a1bcdcdbbcfc83e6542033742a09ecfae2d60c20a +size 2859795 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-bg.png b/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-bg.png new file mode 100644 index 0000000..2f4007c --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:758a9c0b59dd8deaf3654dc5fb02fbecc7fdada52ed832260e601b5b9e6d9fad +size 5087080 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-title.png b/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-title.png new file mode 100644 index 0000000..0a8d8b6 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/heart-archive-title.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1521cd9b241592968c4fcc33f4b131f5530f00a7c70b130be2055abccc97b24a +size 391781 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/rank-a.png b/vtubermini/src/assets/desktop-assets/heart-archive/rank-a.png new file mode 100644 index 0000000..ccc5be3 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/rank-a.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0763052abefc8005e8032826b1c605c3496b1a53fde9f565f59cfb8020bf0800 +size 75667 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/rank-b.png b/vtubermini/src/assets/desktop-assets/heart-archive/rank-b.png new file mode 100644 index 0000000..13d01b8 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/rank-b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba765b547bd924230c96fe2fb86bc60ad851d89e73be45c017e78f7e87b01a35 +size 63342 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/rank-c.png b/vtubermini/src/assets/desktop-assets/heart-archive/rank-c.png new file mode 100644 index 0000000..70546ce --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/rank-c.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b8d1cb17f9253ec721b252d2aa53c82297db18a7e0a343158669ab89b2ac365 +size 61648 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/rank-sr.png b/vtubermini/src/assets/desktop-assets/heart-archive/rank-sr.png new file mode 100644 index 0000000..e1838fe --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/rank-sr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b94cf748d6b4bd2dbc0f6781ba8bb0d324c7506fd8b29211f0e08d07f7d75fbd +size 117843 diff --git a/vtubermini/src/assets/desktop-assets/heart-archive/rank-ssr.png b/vtubermini/src/assets/desktop-assets/heart-archive/rank-ssr.png new file mode 100644 index 0000000..19653c3 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-archive/rank-ssr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a4234d70ab5db641ef92791cddd4892428d050fa7757a84028685900a4ba89f +size 152507 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-bg.png b/vtubermini/src/assets/desktop-assets/heart-moment-bg.png new file mode 100644 index 0000000..1cf15cd --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43c604b46660df5bc96b222a83b2dbcb86128a53d9fbe21ec7d7a3ed9855c621 +size 3834858 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-draw-single.mp4 b/vtubermini/src/assets/desktop-assets/heart-moment-draw-single.mp4 new file mode 100644 index 0000000..8d1bb7d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-draw-single.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee33f0fa20c2d219196c1ec1c74cddc04afdd92f55bb6bbc52b6d6cdee4334cb +size 5906800 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-draw-ten.mp4 b/vtubermini/src/assets/desktop-assets/heart-moment-draw-ten.mp4 new file mode 100644 index 0000000..93dda22 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-draw-ten.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9c61bc0a132794c9b1e2cda637bc8d4836b1d63c9712bcf4e12be5c6fb0de66 +size 6976707 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-loop.mp4 b/vtubermini/src/assets/desktop-assets/heart-moment-loop.mp4 new file mode 100644 index 0000000..6c91af4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-loop.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:953dd74c57a2fa0c2152c69b789620a0e180c6cb692d444b12cef7a864a1d2b1 +size 7134416 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-result-intro.mp4 b/vtubermini/src/assets/desktop-assets/heart-moment-result-intro.mp4 new file mode 100644 index 0000000..37746c8 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-result-intro.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:350739975c7cd0c5d52ab0f96eb4113335c0eef6ae2c388fcf79cfdddd533121 +size 2478507 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-result-loop.mp4 b/vtubermini/src/assets/desktop-assets/heart-moment-result-loop.mp4 new file mode 100644 index 0000000..d73b9e5 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-result-loop.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bbfd7dd47002bd1e19d45a4a63834de674b400b877b54489bb81a8d75cb8a83 +size 6486348 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-moment-collect-button.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-moment-collect-button.png new file mode 100644 index 0000000..de7c326 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-moment-collect-button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bea1ad15769834958208fd282ae1da01071dfac850f16174abd4d7ccba63b004 +size 38830 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-1.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-1.png new file mode 100644 index 0000000..af4d5bb --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28ec19603e694d314dff5f6b4b40fe0f658dd0003b3a3530ddb335455c96f6db +size 2666856 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-10.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-10.png new file mode 100644 index 0000000..b7f2c4d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43b238cec5570e08c4c405a8e5d7d7d33e0e6544045910fd55387aaeeb3a271b +size 4914557 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-2.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-2.png new file mode 100644 index 0000000..e20d5b7 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd610005a2d3043806d533a0e332e6fc5f96f661ab601f51ff8301803a6330b3 +size 3068199 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-3.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-3.png new file mode 100644 index 0000000..f02e953 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a39f7e0e543365058a7795a5eb6cc0ba16b9eb3aa0d20c66b2ae856427f2fd7 +size 3160361 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-4.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-4.png new file mode 100644 index 0000000..f92a31b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f890cca5ff97464e7b4417f47a46e8c488edb97ea47ff75e623fdfa142d39a76 +size 5561168 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-5.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-5.png new file mode 100644 index 0000000..90bb6d2 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a3876174b89cbfd1d01bd324b67e0baf68da083c5684fa90ac6392fff572ccf +size 6282168 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-6.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-6.png new file mode 100644 index 0000000..1e0d211 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5f4b0c98d249abe83819fae25507c7be2794355510d8c81c1a62bce46286bd1 +size 5622154 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-7.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-7.png new file mode 100644 index 0000000..a75b15b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b03aea693fb2af51e6b9bc105295c57be94d71b484a1d60f639f7cdc59670ad3 +size 6081753 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-8.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-8.png new file mode 100644 index 0000000..b1d97c0 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c73683027c4f94e930ea13ebb5f4cca10a414eee9d9b59244dee7e0d88bf983 +size 4731295 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-9.png b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-9.png new file mode 100644 index 0000000..a7a6fd8 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-reveal/heart-reveal-9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:489548881976a2956383f9d92bd68273b93fbedcaba4d52d3d7c46e00668780a +size 4850059 diff --git a/vtubermini/src/assets/desktop-assets/heart-moment-ssr.png b/vtubermini/src/assets/desktop-assets/heart-moment-ssr.png new file mode 100644 index 0000000..6804aad --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/heart-moment-ssr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3071c0d7a206953930cda22e1b32a962f1e40e644b0ecc8b842e369f573977ea +size 8558675 diff --git a/vtubermini/src/assets/desktop-assets/home-anniversary/home-anniversary.png b/vtubermini/src/assets/desktop-assets/home-anniversary/home-anniversary.png new file mode 100644 index 0000000..1fb04be --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-anniversary/home-anniversary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da24d22448f167697674c11e8bfd34bb360dc602c79f201168ed702180294266 +size 4051009 diff --git a/vtubermini/src/assets/desktop-assets/home-covers/cover-chiying-xingge.mp4 b/vtubermini/src/assets/desktop-assets/home-covers/cover-chiying-xingge.mp4 new file mode 100644 index 0000000..591c58f --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-covers/cover-chiying-xingge.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4a2b5e26d651d70e47d0cac873e9ca3b5b28c66dca973d5b0073b4a68b7e362 +size 6996649 diff --git a/vtubermini/src/assets/desktop-assets/home-covers/cover-xingyao-shengyu.mp4 b/vtubermini/src/assets/desktop-assets/home-covers/cover-xingyao-shengyu.mp4 new file mode 100644 index 0000000..c2f06f9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-covers/cover-xingyao-shengyu.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92ba674740f861cbab9702e641d3461a909b2813f304dc7fcd1fd9c8c66c974c +size 7257823 diff --git a/vtubermini/src/assets/desktop-assets/home-covers/cover-xingye-xiehou.mp4 b/vtubermini/src/assets/desktop-assets/home-covers/cover-xingye-xiehou.mp4 new file mode 100644 index 0000000..bd8d1b4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-covers/cover-xingye-xiehou.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d9bfc01f0b0452540e069980e9d68d3f606dc76fb905d7753027544db31ba16 +size 7135127 diff --git a/vtubermini/src/assets/desktop-assets/home-dialog/choice-box.png b/vtubermini/src/assets/desktop-assets/home-dialog/choice-box.png new file mode 100644 index 0000000..15588d5 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-dialog/choice-box.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c404e733607682d30cd7f96d71e965b49203d4a2931746fdc7bc8be66be96e1a +size 119805 diff --git a/vtubermini/src/assets/desktop-assets/home-dialog/dialog-box.png b/vtubermini/src/assets/desktop-assets/home-dialog/dialog-box.png new file mode 100644 index 0000000..dac42ca --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-dialog/dialog-box.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12c784e081de4cedbb2680857346187d1b9b8a1cd50d89388a4b1db5278c0622 +size 355418 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/activity.png b/vtubermini/src/assets/desktop-assets/home-icons/activity.png new file mode 100644 index 0000000..ca22908 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/activity.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4bcc15c8c99c2cfe753d66f48a42fff0662c6d1d98a95bdbfd17319f82f33b3 +size 158970 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/album.png b/vtubermini/src/assets/desktop-assets/home-icons/album.png new file mode 100644 index 0000000..6faa505 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/album.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920a324ae075c316512e6f012490cdfcd174fa65ad8e1c9925743e743bb43ed1 +size 125762 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/anniversary.png b/vtubermini/src/assets/desktop-assets/home-icons/anniversary.png new file mode 100644 index 0000000..fffb2ab --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/anniversary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c85cdd19d99353b4fcbe1b87302303ee84adc0ea5721e444d94dc221caf2aa6 +size 176583 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/avatar-frame.png b/vtubermini/src/assets/desktop-assets/home-icons/avatar-frame.png new file mode 100644 index 0000000..dbf83d9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/avatar-frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:900606392fa69dc821d080a9994373fe8c7f7fe32bda801a560586466a199243 +size 1027954 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/bottom-icon-bar.png b/vtubermini/src/assets/desktop-assets/home-icons/bottom-icon-bar.png new file mode 100644 index 0000000..a06c00e --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/bottom-icon-bar.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9bf156d7605d8b6f7e9c8940133d7b1b1ef642847194beae63834b18477a478 +size 237308 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/moments.png b/vtubermini/src/assets/desktop-assets/home-icons/moments.png new file mode 100644 index 0000000..11af9a1 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/moments.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ea056f0a021b1d55f0b001a1cf454578044aafb82fac305df5e5ad49344447 +size 120221 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/pet.png b/vtubermini/src/assets/desktop-assets/home-icons/pet.png new file mode 100644 index 0000000..ccd2866 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/pet.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa7dc42dbba0bb9ed208a49a933a289f18921b1898c94315dc03951712f32644 +size 111685 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/profile-plate.png b/vtubermini/src/assets/desktop-assets/home-icons/profile-plate.png new file mode 100644 index 0000000..c7701ec --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/profile-plate.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff7ae18e8325bb91ec66f2b3f3deb961aefe62031d7caa84f791d81e10adf419 +size 66589 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/shop.png b/vtubermini/src/assets/desktop-assets/home-icons/shop.png new file mode 100644 index 0000000..b7a775e --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/shop.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97a33f5bb40c73ef7336be46b5d37eb77956f3bc898b244a13e1f4cd8611119b +size 115454 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/support.png b/vtubermini/src/assets/desktop-assets/home-icons/support.png new file mode 100644 index 0000000..e793a4b --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/support.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a95a073946123d3e6120c436419fa5ea0d09f6a003fc3731092af5cc21c030f +size 160200 diff --git a/vtubermini/src/assets/desktop-assets/home-icons/vote.png b/vtubermini/src/assets/desktop-assets/home-icons/vote.png new file mode 100644 index 0000000..8496aeb --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/home-icons/vote.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d0b3f2054aed77bf41913135dcbbb5809d470801247ef9a0924a8173cb0a31 +size 151493 diff --git a/vtubermini/src/assets/desktop-assets/pet-feeding-juvenile.mp4 b/vtubermini/src/assets/desktop-assets/pet-feeding-juvenile.mp4 new file mode 100644 index 0000000..7694159 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/pet-feeding-juvenile.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a88291efaf8559a9950f4928c71cfb85efbdc04c56aad31a59997c5f5e465851 +size 10592447 diff --git a/vtubermini/src/assets/desktop-assets/pet-idle-adult-loop.mp4 b/vtubermini/src/assets/desktop-assets/pet-idle-adult-loop.mp4 new file mode 100644 index 0000000..2c897d9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/pet-idle-adult-loop.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c71a5d5984cae2baaf76a45fa152d56fca6a3e82641f622b8ae5d123d4a47556 +size 6844664 diff --git a/vtubermini/src/assets/desktop-assets/pet-idle-juvenile-loop.mp4 b/vtubermini/src/assets/desktop-assets/pet-idle-juvenile-loop.mp4 new file mode 100644 index 0000000..b2bad4d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/pet-idle-juvenile-loop.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:672113bcd4cf2ab5799617727ba70cf5f9e9e4bfa78f951d28f97b1b679e38cd +size 6722286 diff --git a/vtubermini/src/assets/desktop-assets/pet-juvenile-to-adult.mp4 b/vtubermini/src/assets/desktop-assets/pet-juvenile-to-adult.mp4 new file mode 100644 index 0000000..4376032 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/pet-juvenile-to-adult.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ca8a71f6db88730796886b0ab5a59d0e7dac00b0a907b6afa7dd8aaf5e9fffc +size 6603396 diff --git a/vtubermini/src/assets/desktop-assets/shop-currency-gem.png b/vtubermini/src/assets/desktop-assets/shop-currency-gem.png new file mode 100644 index 0000000..152264d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-currency-gem.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7be48a756e4b7e53f74f1d7b4f84526e19732afae545ad6ba2dde0fd1573f9c +size 9972 diff --git a/vtubermini/src/assets/desktop-assets/shop-products/canvas-bag.png b/vtubermini/src/assets/desktop-assets/shop-products/canvas-bag.png new file mode 100644 index 0000000..cb422f9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-products/canvas-bag.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a63c535acee5811ae459974aa06722cc14ffad25360aa5c43a17e31dab78237 +size 6624289 diff --git a/vtubermini/src/assets/desktop-assets/shop-products/keychain.png b/vtubermini/src/assets/desktop-assets/shop-products/keychain.png new file mode 100644 index 0000000..b056fb8 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-products/keychain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:468920e30788876cb88708fd0e26308c97b66e86c1c1f0f80130eebf51bd0a8d +size 5694954 diff --git a/vtubermini/src/assets/desktop-assets/shop-products/mug.png b/vtubermini/src/assets/desktop-assets/shop-products/mug.png new file mode 100644 index 0000000..9e4516d --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-products/mug.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18ba356097341e65ef3bf869ecbd47653ded16fc1de26cb4c6a467e4c15a7db1 +size 4771589 diff --git a/vtubermini/src/assets/desktop-assets/shop-products/notebook.png b/vtubermini/src/assets/desktop-assets/shop-products/notebook.png new file mode 100644 index 0000000..360cc4a --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-products/notebook.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e38b9302d5e46cc77a51c33cb8ba8e7e12ce225ecaa08d937043aac6392fb51 +size 6221187 diff --git a/vtubermini/src/assets/desktop-assets/shop-products/sticker-pack.png b/vtubermini/src/assets/desktop-assets/shop-products/sticker-pack.png new file mode 100644 index 0000000..ea8494a --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-products/sticker-pack.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ace4bc961ccbed0775eaa94e413bef50f1cb306b96dfcca53497ffce533639b2 +size 7127587 diff --git a/vtubermini/src/assets/desktop-assets/shop-section-1.png b/vtubermini/src/assets/desktop-assets/shop-section-1.png new file mode 100644 index 0000000..672a5af --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-section-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d6bd7e7f43e4752882efb4417adb8d5374fa6dfa598d70258797bad097bd633 +size 618396 diff --git a/vtubermini/src/assets/desktop-assets/shop-section-2.png b/vtubermini/src/assets/desktop-assets/shop-section-2.png new file mode 100644 index 0000000..29f24a9 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-section-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94c33339e871591bf8f0a5b835140043d59c6930d8ac46c27d50f915fd3db83e +size 501369 diff --git a/vtubermini/src/assets/desktop-assets/shop-section-3.png b/vtubermini/src/assets/desktop-assets/shop-section-3.png new file mode 100644 index 0000000..4b55b36 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-section-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:792a25711c9af8add3b4843a484ac21646638de4bdd1e397501fad7235ab662a +size 779395 diff --git a/vtubermini/src/assets/desktop-assets/shop-section-4.png b/vtubermini/src/assets/desktop-assets/shop-section-4.png new file mode 100644 index 0000000..d3f25ae --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/shop-section-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:529f13793e39d9c0bb562cf476abe0b519dbe13d9b40740e3e129aeab9318a98 +size 549361 diff --git a/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-弹窗.png b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-弹窗.png new file mode 100644 index 0000000..669fd90 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-弹窗.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd9400df569c4e73997ba969ed1dabe59ae0b8441edecc5174f400ef1f4d0668 +size 610857 diff --git a/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-隔断.png b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-隔断.png new file mode 100644 index 0000000..dc2a861 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面-隔断.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02c3e66b8ea1386f591a78864b3af8e21f7d7d04d750ef042a85eddf68af2a72 +size 16840 diff --git a/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面.png b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面.png new file mode 100644 index 0000000..4b7f27f --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/二级页面-纪念日页面.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f6740dcfe4c6a483023e44bb2da58788d33a602a153c75b1d5f472538e1c128 +size 1102092 diff --git a/vtubermini/src/assets/desktop-assets/抽卡界面-循环2.mp4 b/vtubermini/src/assets/desktop-assets/抽卡界面-循环2.mp4 new file mode 100644 index 0000000..6c91af4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/抽卡界面-循环2.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:953dd74c57a2fa0c2152c69b789620a0e180c6cb692d444b12cef7a864a1d2b1 +size 7134416 diff --git a/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-ssr标.png b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-ssr标.png new file mode 100644 index 0000000..ff00314 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-ssr标.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:041a8fdbf65d66d2e8e015956af4b801ea08bf370e7d08fd07838eb824149e1d +size 203291 diff --git a/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-中间描述.png b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-中间描述.png new file mode 100644 index 0000000..7e8de4e --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-中间描述.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ca1b98a8ad65efe64ed5ff607ff30e2377ecee889cfab9e418d90c3e73affdf +size 679385 diff --git a/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-单抽.png b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-单抽.png new file mode 100644 index 0000000..9807b32 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-单抽.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d883a2889c6e066768e67d8966491e31950a4a8458d685b6139b62baa27c1ab +size 155346 diff --git a/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-连抽.png b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-连抽.png new file mode 100644 index 0000000..b4e08c4 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/抽卡界面2-UI-连抽.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a8b7fb0ea3c2eaa749954b3f3d8b5fadd2fba584841d8e3d83c3861e7849a50 +size 154079 diff --git a/vtubermini/src/assets/desktop-assets/日常首页-夜晚.mp4 b/vtubermini/src/assets/desktop-assets/日常首页-夜晚.mp4 new file mode 100644 index 0000000..f78da8f --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/日常首页-夜晚.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a2da89e9aff04b7bbc5edfc18fb436ee5a149f86f84ba8b1b6830b92a1c8110 +size 7302829 diff --git a/vtubermini/src/assets/desktop-assets/日常首页-白天.mp4 b/vtubermini/src/assets/desktop-assets/日常首页-白天.mp4 new file mode 100644 index 0000000..d45d155 --- /dev/null +++ b/vtubermini/src/assets/desktop-assets/日常首页-白天.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2b3c81cd201531418bf02b160b431be5c6225aef06127d186c7a07a440278da +size 7127175 diff --git a/vtubermini/src/assets/hero.png b/vtubermini/src/assets/hero.png new file mode 100644 index 0000000..9b398ea --- /dev/null +++ b/vtubermini/src/assets/hero.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:881ffbcaafc212e49addad08846a5b82761355fa20624253af3477ba33262c5c +size 13057 diff --git a/vtubermini/src/assets/react.svg b/vtubermini/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/vtubermini/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vtubermini/src/assets/vite.svg b/vtubermini/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/vtubermini/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/vtubermini/src/components/album/card-grid.tsx b/vtubermini/src/components/album/card-grid.tsx new file mode 100644 index 0000000..b2d8b23 --- /dev/null +++ b/vtubermini/src/components/album/card-grid.tsx @@ -0,0 +1,36 @@ +import type { CardItem, Rarity } from '../../lib/types' + +const rarityTone: Record = { + N: 'bg-slate-100 text-slate-600', + R: 'bg-blue-100 text-blue-700', + SR: 'bg-indigo-100 text-indigo-700', + SSR: 'bg-amber-100 text-amber-700', +} + +export function CardGrid({ cards, compact = false }: { cards: CardItem[]; compact?: boolean }) { + return ( +
+ {cards.map((card, index) => ( +
+
+ {card.owned ? '✦' : '?'} + {!card.owned ?
: null} +
+
+ {card.owned ? card.title : '未获得'} + {card.rarity} +
+ {!compact && card.owned ? ( +

+ {card.series} · {card.acquiredAt ?? '刚刚获得'} +

+ ) : null} +
+ ))} +
+ ) +} diff --git a/vtubermini/src/components/anniversary/anniversary-edit-modal.tsx b/vtubermini/src/components/anniversary/anniversary-edit-modal.tsx new file mode 100644 index 0000000..cb27b60 --- /dev/null +++ b/vtubermini/src/components/anniversary/anniversary-edit-modal.tsx @@ -0,0 +1,205 @@ +import { useState } from 'react' +import type { Anniversary, AnniversaryInput } from '../../lib/types' +import modalFrame from '../../assets/desktop-assets/二级页面-纪念日页面-弹窗.png' + +interface AnniversaryEditModalProps { + anniversary: Anniversary | null + onClose: () => void + onSave: (input: AnniversaryInput) => void +} + +export function AnniversaryEditModal({ anniversary, onClose, onSave }: AnniversaryEditModalProps) { + const [title, setTitle] = useState(anniversary?.title ?? '第一次看直播') + const [date, setDate] = useState(anniversary?.date ?? new Date().toISOString().slice(0, 10)) + const [repeatYearly, setRepeatYearly] = useState(anniversary?.repeatYearly ?? false) + const [showLunar, setShowLunar] = useState(anniversary?.showLunar ?? false) + const [imageUrl, setImageUrl] = useState(anniversary?.imageUrl ?? '') + const [reminderEnabled, setReminderEnabled] = useState(anniversary?.reminderEnabled ?? true) + const [reminderOffsetDays, setReminderOffsetDays] = useState(anniversary?.reminderOffsetDays ?? 1) + const [note, setNote] = useState(anniversary?.note ?? '') + const [error, setError] = useState('') + + function handleRepeatYearlyChange(isRepeatYearly: boolean) { + setRepeatYearly(isRepeatYearly) + + if (!isRepeatYearly) { + setReminderEnabled(false) + } + } + + function handleSave() { + if (!title.trim() || !date) { + setError('请填写纪念日名称和日期。') + return + } + + onSave({ + id: anniversary?.id, + title, + date, + showLunar, + imageUrl: imageUrl || undefined, + repeatYearly, + reminderEnabled: repeatYearly ? reminderEnabled : false, + reminderOffsetDays: repeatYearly ? reminderOffsetDays : 0, + note, + }) + } + + function handleImageChange(file: File | null) { + if (!file) { + return + } + + if (!file.type.startsWith('image/')) { + setError('请选择图片文件。') + return + } + + const reader = new FileReader() + reader.onload = () => { + if (typeof reader.result === 'string') { + setImageUrl(reader.result) + setError('') + } + } + reader.onerror = () => setError('图片读取失败,请重新选择。') + reader.readAsDataURL(file) + } + + return ( +
+
+ + +

+ {anniversary ? '编辑纪念日' : '添加纪念日'} +

+ +
+
+ + + + + {repeatYearly ? ( + <> + + + + ) : null} + + setImageUrl('')} /> + {error ?

{error}

: null} +
+
+ + +
+
+
+
+ ) +} + +function ImageUpload({ + imageUrl, + onChange, + onClear, +}: { + imageUrl: string + onChange: (file: File | null) => void + onClear: () => void +}) { + return ( + + ) +} + +function Toggle({ + checked, + label, + onChange, +}: { + checked: boolean + label: string + onChange: (checked: boolean) => void +}) { + return ( + + ) +} + +function Field({ + label, + onChange, + type = 'text', + value, +}: { + label: string + onChange: (value: string) => void + type?: string + value: string +}) { + const inputClass = + 'mt-1 w-full rounded-xl border border-[#cfe4f4] bg-white/82 px-3 py-2 text-[13px] text-[#244d75] outline-none focus:border-[var(--dream-blue)]' + + return ( + + ) +} diff --git a/vtubermini/src/components/anniversary/custom-anniversary-list.tsx b/vtubermini/src/components/anniversary/custom-anniversary-list.tsx new file mode 100644 index 0000000..525425c --- /dev/null +++ b/vtubermini/src/components/anniversary/custom-anniversary-list.tsx @@ -0,0 +1,62 @@ +import { CalendarPlus, Pencil, Trash2 } from 'lucide-react' +import type { Anniversary } from '../../lib/types' +import { + getAnniversaryDays, + getAnniversaryStatusLabel, + getReminderText, + sortAnniversaries, +} from '../../lib/anniversary-rules' + +interface CustomAnniversaryListProps { + anniversaries: Anniversary[] + onAdd: () => void + onDelete: (id: string) => void + onEdit: (anniversary: Anniversary) => void +} + +export function CustomAnniversaryList({ anniversaries, onAdd, onDelete, onEdit }: CustomAnniversaryListProps) { + return ( +
+
+

自定义纪念日

+ +
+ {anniversaries.length === 0 ? ( +
+

还没有自定义纪念日

+

可以记录第一次看直播、第一次抽到 SSR 或第一次参加线下活动。

+ +
+ ) : ( +
+ {sortAnniversaries(anniversaries).map((item) => ( +
+
+
+

{item.title}

+

+ {item.date} · {getAnniversaryStatusLabel(item)} {getAnniversaryDays(item)} 天 · {getReminderText(item)} +

+ {item.note ?

{item.note}

: null} +
+
+ + +
+
+
+ ))} +
+ )} +
+ ) +} diff --git a/vtubermini/src/components/anniversary/join-date-panel.tsx b/vtubermini/src/components/anniversary/join-date-panel.tsx new file mode 100644 index 0000000..fbeead0 --- /dev/null +++ b/vtubermini/src/components/anniversary/join-date-panel.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import type { Anniversary } from '../../lib/types' +import { getDaysLeft, getElapsedDays } from '../../lib/anniversary-rules' + +export function JoinDatePanel({ + anniversary, + onUpdate, +}: { + anniversary?: Anniversary + onUpdate: (date: string) => void +}) { + const [date, setDate] = useState(anniversary?.date ?? '2025-10-01') + const companionDays = anniversary ? getElapsedDays(anniversary.date) : 0 + const daysLeft = anniversary ? getDaysLeft(anniversary) : 0 + + return ( +
+

入团时间

+

已陪伴 {companionDays} 天

+

{anniversary?.date ?? '尚未设置'} · 下一次周年还有 {daysLeft} 天

+
+ setDate(event.target.value)} + type="date" + value={date} + /> + +
+
+ ) +} diff --git a/vtubermini/src/components/anniversary/reminder-preview.tsx b/vtubermini/src/components/anniversary/reminder-preview.tsx new file mode 100644 index 0000000..dd89131 --- /dev/null +++ b/vtubermini/src/components/anniversary/reminder-preview.tsx @@ -0,0 +1,31 @@ +import { Bell } from 'lucide-react' +import type { Anniversary } from '../../lib/types' +import { + getAnniversaryDays, + getAnniversaryStatusLabel, + getReminderText, + sortAnniversaries, +} from '../../lib/anniversary-rules' + +export function ReminderPreview({ anniversaries }: { anniversaries: Anniversary[] }) { + const enabledItems = sortAnniversaries(anniversaries) + .filter((item) => item.repeatYearly && item.reminderEnabled) + .slice(0, 3) + + return ( +
+
+ +

提醒预览

+
+
+ {enabledItems.map((item) => ( +
+ {item.title} · {getAnniversaryStatusLabel(item)}{' '} + {getAnniversaryDays(item)} 天 · {getReminderText(item)} +
+ ))} +
+
+ ) +} diff --git a/vtubermini/src/components/anniversary/system-anniversary-list.tsx b/vtubermini/src/components/anniversary/system-anniversary-list.tsx new file mode 100644 index 0000000..8499812 --- /dev/null +++ b/vtubermini/src/components/anniversary/system-anniversary-list.tsx @@ -0,0 +1,28 @@ +import { Stars } from 'lucide-react' +import type { Anniversary } from '../../lib/types' +import { getAnniversaryDays, getAnniversaryStatusLabel, getReminderText } from '../../lib/anniversary-rules' + +export function SystemAnniversaryList({ anniversaries }: { anniversaries: Anniversary[] }) { + return ( +
+
+ +

系统纪念日

+
+
+ {anniversaries.map((item) => ( +
+
+

{item.title}

+ + {getAnniversaryStatusLabel(item)} {getAnniversaryDays(item)} 天 + +
+

{item.date} · {getReminderText(item)}

+ {item.linkedActivity ?

关联活动:{item.linkedActivity},需展示活动规则。

: null} +
+ ))} +
+
+ ) +} diff --git a/vtubermini/src/components/app-shell.tsx b/vtubermini/src/components/app-shell.tsx new file mode 100644 index 0000000..c1639ab --- /dev/null +++ b/vtubermini/src/components/app-shell.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from 'react' +import { shouldUseWebPhonePreview } from '../lib/platform' +import type { AppRoute } from '../lib/types' + +interface AppShellProps { + activeRoute: AppRoute + children: ReactNode +} + +export function AppShell({ activeRoute, children }: AppShellProps) { + const useWebPhonePreview = shouldUseWebPhonePreview() + const isImmersive = + activeRoute === 'home' || + activeRoute === 'anniversary' || + activeRoute === 'activity' || + activeRoute === 'album' || + activeRoute === 'gacha' || + activeRoute === 'moments' || + activeRoute === 'pet' || + activeRoute === 'shop' + const hasPageManagedScroll = activeRoute === 'my' + + const stage = ( +
+
+ {isImmersive || hasPageManagedScroll ? children :
{children}
} +
+
+ ) + + return ( +
+ {useWebPhonePreview ?
{stage}
: stage} +
+ ) +} diff --git a/vtubermini/src/components/common/currency-icon.tsx b/vtubermini/src/components/common/currency-icon.tsx new file mode 100644 index 0000000..6a24dba --- /dev/null +++ b/vtubermini/src/components/common/currency-icon.tsx @@ -0,0 +1,26 @@ +import currencyStar from '../../assets/desktop-assets/currency-star.png' +import currencyGem from '../../assets/desktop-assets/shop-currency-gem.png' + +interface CurrencyIconProps { + className?: string +} + +export function PointsCurrencyIcon({ className = 'size-5' }: CurrencyIconProps) { + return +} + +export function DiamondCurrencyIcon({ className = 'size-5' }: CurrencyIconProps) { + return +} + +function CurrencyImage({ alt, className, src }: { alt: string; className: string; src: string }) { + return ( + + ) +} diff --git a/vtubermini/src/components/common/page-header.tsx b/vtubermini/src/components/common/page-header.tsx new file mode 100644 index 0000000..24032dd --- /dev/null +++ b/vtubermini/src/components/common/page-header.tsx @@ -0,0 +1,21 @@ +import { ArrowLeft } from 'lucide-react' + +interface PageHeaderProps { + eyebrow: string + title: string + onBack: () => void +} + +export function PageHeader({ eyebrow, onBack, title }: PageHeaderProps) { + return ( +
+ +
+

{eyebrow}

+

{title}

+
+
+ ) +} diff --git a/vtubermini/src/components/common/state-block.tsx b/vtubermini/src/components/common/state-block.tsx new file mode 100644 index 0000000..718399d --- /dev/null +++ b/vtubermini/src/components/common/state-block.tsx @@ -0,0 +1,33 @@ +import { AlertCircle, LoaderCircle, PackageOpen } from 'lucide-react' + +interface StateBlockProps { + actionLabel?: string + message: string + title: string + tone: 'empty' | 'error' | 'loading' +} + +const toneConfig = { + empty: { icon: PackageOpen, color: 'text-[var(--deep-blue)]' }, + error: { icon: AlertCircle, color: 'text-[#b4534b]' }, + loading: { icon: LoaderCircle, color: 'text-[var(--dream-blue)]' }, +} + +export function StateBlock({ actionLabel, message, title, tone }: StateBlockProps) { + const { icon: Icon, color } = toneConfig[tone] + + return ( +
+
+ +
+

{title}

+

{message}

+ {actionLabel ? ( + + ) : null} +
+ ) +} diff --git a/vtubermini/src/components/gacha/gacha-actions.tsx b/vtubermini/src/components/gacha/gacha-actions.tsx new file mode 100644 index 0000000..cb4e35f --- /dev/null +++ b/vtubermini/src/components/gacha/gacha-actions.tsx @@ -0,0 +1,42 @@ +import { PointsCurrencyIcon } from '../common/currency-icon' +import type { GachaDrawMode, GachaPool } from '../../lib/types' + +interface GachaActionsProps { + onDraw: (mode: GachaDrawMode) => void + points: number + pool: GachaPool +} + +export function GachaActions({ onDraw, points, pool }: GachaActionsProps) { + const canSingle = points >= pool.costPoints + const canTen = points >= pool.costPoints * 10 + + return ( +
+

抽取

+
+ + +
+ {!canSingle ?

积分不足,完成每日宠物照顾后再来抽卡。

: null} +
+ ) +} diff --git a/vtubermini/src/components/gacha/gacha-card-assets.ts b/vtubermini/src/components/gacha/gacha-card-assets.ts new file mode 100644 index 0000000..2494e7a --- /dev/null +++ b/vtubermini/src/components/gacha/gacha-card-assets.ts @@ -0,0 +1,85 @@ +import archiveCard1 from '../../assets/desktop-assets/heart-archive/archive-card-1.png' +import archiveCard2 from '../../assets/desktop-assets/heart-archive/archive-card-2.png' +import archiveCard3 from '../../assets/desktop-assets/heart-archive/archive-card-3.png' +import archiveCard4 from '../../assets/desktop-assets/heart-archive/archive-card-4.png' +import archiveCard5 from '../../assets/desktop-assets/heart-archive/archive-card-5.png' +import resultCardChenguang from '../../assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png' +import resultCardChiying from '../../assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png' +import resultCardHuajian from '../../assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png' +import resultCardMistNotes from '../../assets/desktop-assets/heart-archive/archive-card-mist-notes.png' +import resultCardRainUmbrella from '../../assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png' +import resultCardSilentContract from '../../assets/desktop-assets/heart-archive/archive-card-silent-contract.png' +import resultCardStarLab from '../../assets/desktop-assets/heart-archive/archive-card-star-lab.png' +import resultCardStormSide from '../../assets/desktop-assets/heart-archive/archive-card-storm-side.png' +import resultCardXingyao from '../../assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png' +import resultCardXingye from '../../assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png' +import heartReveal1 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-1.png' +import heartReveal2 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-2.png' +import heartReveal3 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-3.png' +import heartReveal4 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-4.png' +import heartReveal5 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-5.png' +import heartReveal6 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-6.png' +import heartReveal7 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-7.png' +import heartReveal8 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-8.png' +import heartReveal9 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-9.png' +import heartReveal10 from '../../assets/desktop-assets/heart-moment-reveal/heart-reveal-10.png' +import type { CardItem } from '../../lib/types' + +const cardImages = [archiveCard1, archiveCard2, archiveCard3, archiveCard4, archiveCard5] + +const cardImageByRarity: Record = { + N: [archiveCard4, archiveCard5], + R: [archiveCard2, archiveCard4], + SR: [archiveCard3, archiveCard5], + SSR: [archiveCard1, archiveCard2, archiveCard3], +} + +const cardImageById: Record = { + 'ARCH-SSR-001': heartReveal3, + 'ARCH-SSR-002': heartReveal2, + 'ARCH-SSR-003': heartReveal1, + 'ARCH-SR-001': heartReveal5, + 'ARCH-SR-002': heartReveal4, + 'ARCH-SR-003': heartReveal7, + 'ARCH-SR-004': heartReveal9, + 'ARCH-SR-005': heartReveal8, + 'ARCH-SR-006': heartReveal6, + 'ARCH-SR-007': heartReveal10, +} + +const resultCardImageById: Record = { + 'ARCH-SSR-001': resultCardXingyao, + 'ARCH-SSR-002': resultCardChiying, + 'ARCH-SSR-003': resultCardXingye, + 'ARCH-SR-001': resultCardChenguang, + 'ARCH-SR-002': resultCardHuajian, + 'ARCH-SR-003': resultCardStormSide, + 'ARCH-SR-004': resultCardMistNotes, + 'ARCH-SR-005': resultCardSilentContract, + 'ARCH-SR-006': resultCardStarLab, + 'ARCH-SR-007': resultCardRainUmbrella, +} + +export function getGachaCardImage(card: CardItem, index: number) { + const cardImage = cardImageById[card.id] + if (cardImage) { + return cardImage + } + + const rarityImages = cardImageByRarity[card.rarity] + const sourceImages = rarityImages.length > 0 ? rarityImages : cardImages + return sourceImages[getStableCardIndex(card.id, index, sourceImages.length)] +} + +export function getHeartMomentRevealImage(card: CardItem, index: number) { + return getGachaCardImage(card, index) +} + +export function getGachaResultCardImage(card: CardItem, index: number) { + return resultCardImageById[card.id] ?? getGachaCardImage(card, index) +} + +function getStableCardIndex(cardId: string, index: number, imageCount: number) { + const idScore = [...cardId].reduce((sum, char) => sum + char.charCodeAt(0), 0) + return (idScore + index) % imageCount +} diff --git a/vtubermini/src/components/gacha/gacha-result-modal.tsx b/vtubermini/src/components/gacha/gacha-result-modal.tsx new file mode 100644 index 0000000..b97b62e --- /dev/null +++ b/vtubermini/src/components/gacha/gacha-result-modal.tsx @@ -0,0 +1,55 @@ +import { PointsCurrencyIcon } from '../common/currency-icon' +import type { GachaResult, Rarity } from '../../lib/types' + +const rarityTone: Record = { + N: 'bg-slate-100 text-slate-600', + R: 'bg-blue-100 text-blue-700', + SR: 'bg-indigo-100 text-indigo-700', + SSR: 'bg-amber-100 text-amber-700', +} + +interface GachaResultModalProps { + result: GachaResult + onClose: () => void + onOpenAlbum: () => void +} + +export function GachaResultModal({ onClose, onOpenAlbum, result }: GachaResultModalProps) { + return ( +
+
+

抽卡结果

+

{result.success ? '获得小卡' : '抽取失败'}

+

{result.message}

+ + {result.results.length > 0 ? ( +
+ {result.results.map((item, index) => ( +
+
+

{item.card.title}

+
+ {item.card.rarity} + {item.duplicate ? ( + + 重复 + {item.convertedPoints} + + ) : null} +
+
+ ))} +
+ ) : null} + +
+ + +
+
+
+ ) +} diff --git a/vtubermini/src/components/gacha/gacha-result-page.tsx b/vtubermini/src/components/gacha/gacha-result-page.tsx new file mode 100644 index 0000000..fc3a62c --- /dev/null +++ b/vtubermini/src/components/gacha/gacha-result-page.tsx @@ -0,0 +1,124 @@ +import { ArrowLeft } from 'lucide-react' +import resultBackground from '../../assets/desktop-assets/gacha-result-bg.png' +import type { GachaResult, Rarity } from '../../lib/types' +import { getGachaResultCardImage } from './gacha-card-assets' + +const rarityTone: Record = { + N: 'text-slate-200', + R: 'text-blue-100', + SR: 'text-amber-100', + SSR: 'text-amber-100', +} + +interface GachaResultPageProps { + result: GachaResult + onBackToGacha: () => void + collectButtonImage?: string + hideBackButton?: boolean +} + +export function GachaResultPage({ collectButtonImage, hideBackButton = false, onBackToGacha, result }: GachaResultPageProps) { + const isTenDraw = result.results.length >= 6 + const isMultiDraw = result.results.length > 1 + + return ( +
+ +
+ +
+
+ {hideBackButton ? null : ( + + )} +
+ +
+

{result.success ? '恭喜获得' : '抽取失败'}

+
+ + {result.results.length > 0 ? ( +
+ {result.results.map((item, index) => ( +
+ {item.card.title} +
+
+

+ {item.card.rarity} +

+

+ {item.card.title} +

+

+ {renderStars(item.card.rarity)} +

+
+
+ ))} +
+ ) : ( +
+ {result.message} +
+ )} + +
+ {collectButtonImage ? ( + + ) : ( + + )} +
+
+
+ ) +} + +function renderStars(rarity: Rarity) { + return { + N: '★★', + R: '★★★', + SR: '★★★★', + SSR: '★★★★★', + }[rarity] +} diff --git a/vtubermini/src/components/gacha/heart-moment-card-reveal.tsx b/vtubermini/src/components/gacha/heart-moment-card-reveal.tsx new file mode 100644 index 0000000..3ac0387 --- /dev/null +++ b/vtubermini/src/components/gacha/heart-moment-card-reveal.tsx @@ -0,0 +1,90 @@ +import type { KeyboardEvent, MouseEvent } from 'react' +import { SkipForward } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import type { GachaDrawResult } from '../../lib/types' +import { getHeartMomentRevealImage } from './gacha-card-assets' + +interface HeartMomentCardRevealProps { + current: number + item: GachaDrawResult + onNext: () => void + onSkip: () => void + total: number +} + +export function HeartMomentCardReveal({ current, item, onNext, onSkip, total }: HeartMomentCardRevealProps) { + const image = getHeartMomentRevealImage(item.card, current - 1) + const [switchingFrom, setSwitchingFrom] = useState(null) + const switchTimerRef = useRef(null) + const isSwitching = switchingFrom === current + + useEffect(() => { + return () => { + if (switchTimerRef.current !== null) { + window.clearTimeout(switchTimerRef.current) + switchTimerRef.current = null + } + } + }, [current]) + + function handleNext() { + if (isSwitching) { + return + } + + if (current >= total) { + onNext() + return + } + + setSwitchingFrom(current) + switchTimerRef.current = window.setTimeout(() => { + switchTimerRef.current = null + onNext() + }, 280) + } + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + handleNext() + } + } + + function handleSkip(event: MouseEvent) { + event.stopPropagation() + onSkip() + } + + return ( +
+ {item.card.title} +
+ {isSwitching ?
+ ) +} diff --git a/vtubermini/src/components/gacha/heart-moment-draw-video.tsx b/vtubermini/src/components/gacha/heart-moment-draw-video.tsx new file mode 100644 index 0000000..e07b3af --- /dev/null +++ b/vtubermini/src/components/gacha/heart-moment-draw-video.tsx @@ -0,0 +1,99 @@ +import { SkipForward } from 'lucide-react' +import { useCallback, useEffect, useRef } from 'react' + +const MAX_VIDEO_WAIT_MS = 18000 + +interface HeartMomentDrawVideoProps { + src: string + onFinish: () => void + collectButtonImage?: string + finishOnClick?: boolean + hideSkipButton?: boolean + loop?: boolean + timeoutMs?: number | null +} + +export function HeartMomentDrawVideo({ + collectButtonImage, + finishOnClick = false, + hideSkipButton = false, + loop = false, + onFinish, + src, + timeoutMs = MAX_VIDEO_WAIT_MS, +}: HeartMomentDrawVideoProps) { + const videoRef = useRef(null) + const finishedRef = useRef(false) + + const finish = useCallback(() => { + if (finishedRef.current) { + return + } + + finishedRef.current = true + onFinish() + }, [onFinish]) + + useEffect(() => { + finishedRef.current = false + const timeoutId = timeoutMs === null ? null : window.setTimeout(finish, timeoutMs) + + videoRef.current?.play().catch(finish) + + return () => { + if (timeoutId !== null) { + window.clearTimeout(timeoutId) + } + } + }, [finish, src, timeoutMs]) + + return ( +
{ + if (!finishOnClick) { + return + } + + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + finish() + } + }} + role={finishOnClick ? 'button' : undefined} + tabIndex={finishOnClick ? 0 : undefined} + > +
+ ) +} diff --git a/vtubermini/src/components/gacha/pool-summary.tsx b/vtubermini/src/components/gacha/pool-summary.tsx new file mode 100644 index 0000000..8ddc535 --- /dev/null +++ b/vtubermini/src/components/gacha/pool-summary.tsx @@ -0,0 +1,51 @@ +import { BadgeInfo, Sparkles } from 'lucide-react' +import { PointsCurrencyIcon } from '../common/currency-icon' +import type { GachaPool, Rarity } from '../../lib/types' + +const rarityTone: Record = { + N: 'bg-slate-100 text-slate-600', + R: 'bg-blue-100 text-blue-700', + SR: 'bg-indigo-100 text-indigo-700', + SSR: 'bg-amber-100 text-amber-700', +} + +export function PoolSummary({ points, pool }: { points: number; pool: GachaPool }) { + return ( + <> +
+
+ + 卡池版本 {pool.version} +
+

单抽消耗

+

+ + {pool.costPoints} +

+

+ 当前 + + {points} +

+
+ +
+
+

概率公示

+ +
+
+ {(Object.entries(pool.rates) as Array<[Rarity, number]>).map(([rarity, rate]) => ( +
+

{rarity}

+

{rate}%

+
+ ))} +
+

+ 保底进度 {pool.pityCurrent}/{pool.pityTarget},{pool.duplicateRule}。卡池不含空奖,概率调整会生成新版本。 +

+
+ + ) +} diff --git a/vtubermini/src/components/home/anniversary-card.tsx b/vtubermini/src/components/home/anniversary-card.tsx new file mode 100644 index 0000000..02bb501 --- /dev/null +++ b/vtubermini/src/components/home/anniversary-card.tsx @@ -0,0 +1,37 @@ +import { ArrowRight, CalendarDays } from 'lucide-react' +import type { Anniversary } from '../../lib/types' +import { getAnniversaryDays, getAnniversaryStatusLabel } from '../../lib/anniversary-rules' + +interface AnniversaryCardProps { + anniversary: Anniversary + companionDays: number + onOpen: () => void +} + +export function AnniversaryCard({ anniversary, companionDays, onOpen }: AnniversaryCardProps) { + const anniversaryDays = getAnniversaryDays(anniversary) + const statusLabel = getAnniversaryStatusLabel(anniversary) + + return ( + + ) +} diff --git a/vtubermini/src/components/home/announcement-panel.tsx b/vtubermini/src/components/home/announcement-panel.tsx new file mode 100644 index 0000000..69c8117 --- /dev/null +++ b/vtubermini/src/components/home/announcement-panel.tsx @@ -0,0 +1,56 @@ +import type { ReactNode } from 'react' +import { ArrowRight, Megaphone, ShieldCheck } from 'lucide-react' +import type { Announcement } from '../../lib/types' +import { StateBlock } from '../common/state-block' + +interface AnnouncementPanelProps { + announcements: Announcement[] +} + +export function AnnouncementPanel({ announcements }: AnnouncementPanelProps) { + if (announcements.length === 0) { + return ( + + ) + } + + return ( +
+
+
+ +

公告与活动

+
+ +
+ +
+ {announcements.map((item) => ( +
+

{item.title}

+

{item.body}

+
+ ))} +
+ +
+ } label="活动规则" /> + } label="兑换提醒" /> +
+
+ ) +} + +function RuleLink({ icon, label }: { icon: ReactNode; label: string }) { + return ( + + ) +} diff --git a/vtubermini/src/components/home/asset-summary.tsx b/vtubermini/src/components/home/asset-summary.tsx new file mode 100644 index 0000000..8658e3b --- /dev/null +++ b/vtubermini/src/components/home/asset-summary.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from 'react' +import { DiamondCurrencyIcon, PointsCurrencyIcon } from '../common/currency-icon' + +interface AssetSummaryProps { + points: number + diamonds: number +} + +export function AssetSummary({ points, diamonds }: AssetSummaryProps) { + return ( +
+ } label="积分余额" tone="blue" value={points} /> + } label="钻石余额" tone="gold" value={diamonds} /> +
+ ) +} + +function AssetPill({ + icon, + label, + tone, + value, +}: { + icon: ReactNode + label: string + tone: 'blue' | 'gold' + value: number +}) { + const toneClass = tone === 'blue' ? 'bg-[#e6f3ff] text-[var(--deep-blue)]' : 'bg-[#fff5d7] text-[#8a6713]' + + return ( +
+
{icon}
+

{label}

+

{value}

+
+ ) +} diff --git a/vtubermini/src/components/home/hero-scene.tsx b/vtubermini/src/components/home/hero-scene.tsx new file mode 100644 index 0000000..460291d --- /dev/null +++ b/vtubermini/src/components/home/hero-scene.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from 'react' +import { CalendarDays, Heart, Sparkles } from 'lucide-react' +import heroImage from '../../assets/hero.png' +import type { UserProfile } from '../../lib/types' + +interface HeroSceneProps { + user: UserProfile +} + +export function HeroScene({ user }: HeroSceneProps) { + return ( +
+
+
+
+ +
+ +
+ Lv.{user.level} {user.name} + + + 心动值 91 + +
+ +
+

+ + VTuber Mini +

+

蓝色梦境周边小屋

+

照顾小狗,收集小卡,用活动钻石兑换周边。

+
+ +
+ } label="六月活动中" /> + } label="纪念卡池开放" /> +
+
+ ) +} + +function StatusChip({ icon, label }: { icon: ReactNode; label: string }) { + return ( + + {icon} + {label} + + ) +} diff --git a/vtubermini/src/components/home/quick-actions.tsx b/vtubermini/src/components/home/quick-actions.tsx new file mode 100644 index 0000000..9c39c26 --- /dev/null +++ b/vtubermini/src/components/home/quick-actions.tsx @@ -0,0 +1,64 @@ +import type { ReactNode } from 'react' +import { CalendarDays, Gift, PawPrint, Sparkles } from 'lucide-react' +import type { AppRoute } from '../../lib/types' + +interface QuickActionsProps { + onNavigate: (route: AppRoute) => void + poolName: string + productCount: number +} + +export function QuickActions({ onNavigate, poolName, productCount }: QuickActionsProps) { + return ( +
+ } + label="宠物小屋" + onClick={() => onNavigate('pet')} + /> + } + label="当前卡池" + onClick={() => onNavigate('gacha')} + /> + } + label="积分商城" + onClick={() => onNavigate('shop')} + /> + } + label="纪念日" + onClick={() => onNavigate('anniversary')} + /> +
+ ) +} + +function QuickAction({ + caption, + icon, + label, + onClick, +}: { + caption: string + icon: ReactNode + label: string + onClick: () => void +}) { + return ( + + ) +} diff --git a/vtubermini/src/components/orders/order-card.tsx b/vtubermini/src/components/orders/order-card.tsx new file mode 100644 index 0000000..0ec5965 --- /dev/null +++ b/vtubermini/src/components/orders/order-card.tsx @@ -0,0 +1,33 @@ +import { PointsCurrencyIcon } from '../common/currency-icon' +import type { ExchangeOrder, OrderStatus } from '../../lib/types' + +const statusLabel: Record = { + pendingShipment: '待发货', + shipped: '已发货', + completed: '已完成', + afterSales: '售后中', + cancelled: '已取消', + refunded: '已退款', +} + +export function OrderCard({ order }: { order: ExchangeOrder }) { + return ( +
+
+
+

{order.productName}

+

{order.createdAt}

+
+ {statusLabel[order.status]} +
+
+

+ 消耗积分: {order.diamondCost} +

+

收货人:{order.receiverName} · {order.phone}

+

地址:{order.address}

+

发货规则:{order.shippingRule}

+
+
+ ) +} diff --git a/vtubermini/src/components/pet/pet-actions.tsx b/vtubermini/src/components/pet/pet-actions.tsx new file mode 100644 index 0000000..6b45a85 --- /dev/null +++ b/vtubermini/src/components/pet/pet-actions.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from 'react' +import { Gamepad2, Heart, Salad, ShowerHead } from 'lucide-react' +import { petActionConfig } from '../../lib/pet-rules' +import type { PetActionType, PetState } from '../../lib/types' + +interface PetActionsProps { + onAction: (action: PetActionType) => void + pet: PetState +} + +const actions: Array<{ icon: ReactNode; type: PetActionType }> = [ + { type: 'feed', icon: }, + { type: 'pat', icon: }, + { type: 'clean', icon: }, + { type: 'play', icon: }, +] + +export function PetActions({ onAction, pet }: PetActionsProps) { + return ( +
+ {actions.map(({ icon, type }) => { + const config = petActionConfig[type] + const usedCount = pet.dailyActionCounts[type] + const isLimited = usedCount >= config.dailyLimit + + return ( + + ) + })} +
+ ) +} diff --git a/vtubermini/src/components/pet/pet-stage.tsx b/vtubermini/src/components/pet/pet-stage.tsx new file mode 100644 index 0000000..572224d --- /dev/null +++ b/vtubermini/src/components/pet/pet-stage.tsx @@ -0,0 +1,49 @@ +import { Heart } from 'lucide-react' +import { getIntimacyTitle, getPetMoodLine, getRecommendedPetAction } from '../../lib/pet-rules' +import type { FeedbackTone, PetState } from '../../lib/types' + +interface PetStageProps { + feedback: { + message: string + tone: FeedbackTone + } + pet: PetState +} + +const feedbackToneClass: Record = { + success: 'bg-white/18 text-white', + warning: 'bg-[#fff5d7] text-[#6f5212]', + info: 'bg-white/16 text-white/88', +} + +export function PetStage({ feedback, pet }: PetStageProps) { + const remaining = Math.max(0, pet.dailyPointsCap - pet.dailyPointsEarned) + const recommendation = getRecommendedPetAction(pet) + const levelProgress = pet.intimacy % 10 + + return ( +
+
🐶
+

{pet.name}

+

{getPetMoodLine(pet)}

+
+
+

今日推荐

+

{recommendation.message}

+
+
+

剩余积分

+

{remaining}

+
+
+
+ {feedback.message} +
+
+ + Lv.{pet.intimacyLevel} {getIntimacyTitle(pet.intimacyLevel)} · 亲密度 {levelProgress}/10 +
+

连续照顾 {pet.streakDays} 天 · 累计陪伴 {pet.totalCareDays} 天

+
+ ) +} diff --git a/vtubermini/src/components/pet/pet-stats.tsx b/vtubermini/src/components/pet/pet-stats.tsx new file mode 100644 index 0000000..57a6a6d --- /dev/null +++ b/vtubermini/src/components/pet/pet-stats.tsx @@ -0,0 +1,45 @@ +import type { PetState } from '../../lib/types' + +export function PetStats({ pet }: { pet: PetState }) { + return ( +
+

状态

+
+ + + + + +
+
+ ) +} + +function StatBar({ label, tag, value }: { label: string; tag: string; value: number }) { + return ( +
+
+ {label} + {tag} · {value} +
+
+
+
+
+ ) +} + +function getStatusTag(type: 'hunger' | 'mood' | 'cleanliness' | 'energy', value: number) { + if (value >= 80) { + return type === 'energy' ? '有精神' : '很好' + } + if (value >= 50) { + return '稳定' + } + return { + hunger: '有点饿', + mood: '想被陪', + cleanliness: '有点乱', + energy: '困困', + }[type] +} diff --git a/vtubermini/src/components/pet/points-ledger.tsx b/vtubermini/src/components/pet/points-ledger.tsx new file mode 100644 index 0000000..4c60b88 --- /dev/null +++ b/vtubermini/src/components/pet/points-ledger.tsx @@ -0,0 +1,34 @@ +import { ScrollText } from 'lucide-react' +import { PointsCurrencyIcon } from '../common/currency-icon' +import type { AssetLedgerEntry } from '../../lib/types' + +export function PointsLedger({ entries }: { entries: AssetLedgerEntry[] }) { + const pointsEntries = entries.filter((entry) => entry.assetType === 'points').slice(0, 4) + + return ( +
+
+ +

积分流水

+
+
+ {pointsEntries.map((entry) => ( +
+
+

{entry.reason}

+

{entry.createdAt}

+
+
+

+ + {entry.change} +

+

+ 余额 {entry.balanceAfter} +

+
+
+ ))} +
+
+ ) +} diff --git a/vtubermini/src/components/profile/birthday-setup-modal.tsx b/vtubermini/src/components/profile/birthday-setup-modal.tsx new file mode 100644 index 0000000..786ebcd --- /dev/null +++ b/vtubermini/src/components/profile/birthday-setup-modal.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react' + +export interface ProfileSetupInput { + birthday: string + joinDate: string +} + +interface BirthdaySetupModalProps { + onClose?: () => void + onSave: (input: ProfileSetupInput) => void + initialJoinDate?: string + skippable?: boolean +} + +export function BirthdaySetupModal({ initialJoinDate = '', onClose, onSave, skippable = false }: BirthdaySetupModalProps) { + const [birthday, setBirthday] = useState('') + const [joinDate, setJoinDate] = useState(initialJoinDate) + const [error, setError] = useState('') + const today = new Date().toISOString().slice(0, 10) + + function handleSave() { + if (!birthday) { + setError('请选择你的生日。') + return + } + + if (!joinDate) { + setError('请选择你的入团日。') + return + } + + if (joinDate > today) { + setError('入团日不能晚于今天。') + return + } + + onSave({ birthday, joinDate }) + } + + return ( +
+
+

账号信息

+

设置你的资料

+

+ 生日会用于特别欢迎页;入团日会用于纪念日系统展示“加入 DL.L9 粉丝团已经多少天”。 +

+ + + + + + {error ?

{error}

: null} + +
+ {skippable ? ( + + ) : null} + +
+
+
+ ) +} diff --git a/vtubermini/src/components/shop/exchange-form.tsx b/vtubermini/src/components/shop/exchange-form.tsx new file mode 100644 index 0000000..e8ef9ac --- /dev/null +++ b/vtubermini/src/components/shop/exchange-form.tsx @@ -0,0 +1,166 @@ +import { useState } from 'react' +import { CheckCircle2, MapPin, WalletCards } from 'lucide-react' +import { ShopCurrencyIcon } from './shop-currency-icon' +import type { ExchangeInput, ExchangeResult, ShopProduct } from '../../lib/types' + +interface ExchangeFormProps { + pointBalance: number + onExchange: (input: ExchangeInput) => ExchangeResult + onSuccess: (result: ExchangeResult) => void + product: ShopProduct +} + +export function ExchangeForm({ pointBalance, onExchange, onSuccess, product }: ExchangeFormProps) { + const [receiverName, setReceiverName] = useState('') + const [phone, setPhone] = useState('') + const [address, setAddress] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState('') + const cannotPay = pointBalance < product.diamondPrice + const isOutOfStock = product.stock <= 0 + const isDisabled = isSubmitting || cannotPay || isOutOfStock + + function handleSubmit() { + if (isDisabled) { + return + } + + const validationError = validateAddressForm(receiverName, phone, address) + if (validationError) { + setError(validationError) + return + } + + setIsSubmitting(true) + + try { + const result = onExchange({ + productId: product.id, + receiverName, + phone, + address, + }) + + if (!result.success) { + setError(result.message) + return + } + + setError('') + onSuccess(result) + } finally { + setIsSubmitting(false) + } + } + + return ( +
+
+
+

兑换流程

+

支付与收货

+
+ + + {pointBalance} + +
+ +
+
+ + 积分支付 +
+
+ + + 使用当前账号积分余额 + + + + {product.diamondPrice} + +
+ {cannotPay ? ( +

+ 积分不足,还差 {product.diamondPrice - pointBalance},无法支付。 +

+ ) : null} +
+ +
+
+ + 收货地址 +
+
+ + + +
+
+ +
+ + 支付后将扣除 {product.diamondPrice} 并锁定 1 件库存,订单状态会进入待发货。 + +
+ {isOutOfStock ?

库存不足,暂时无法兑换。

: null} + {error ?

{error}

: null} + +
+ ) +} + +function validateAddressForm(receiverName: string, phone: string, address: string) { + if (!receiverName.trim()) { + return '请填写收货人。' + } + + if (!/^1[3-9]\d{9}$/.test(phone.trim())) { + return '请填写正确的 11 位手机号。' + } + + if (address.trim().length < 8) { + return '请填写完整收货地址。' + } + + return '' +} + +function Field({ + label, + onChange, + placeholder, + value, +}: { + label: string + onChange: (value: string) => void + placeholder: string + value: string +}) { + return ( + + ) +} diff --git a/vtubermini/src/components/shop/product-card.tsx b/vtubermini/src/components/shop/product-card.tsx new file mode 100644 index 0000000..499d828 --- /dev/null +++ b/vtubermini/src/components/shop/product-card.tsx @@ -0,0 +1,24 @@ +import { ShopCurrencyIcon } from './shop-currency-icon' +import type { ShopProduct } from '../../lib/types' + +export function ProductCard({ onOpen, product }: { onOpen: () => void; product: ShopProduct }) { + return ( + + ) +} diff --git a/vtubermini/src/components/shop/shop-currency-icon.tsx b/vtubermini/src/components/shop/shop-currency-icon.tsx new file mode 100644 index 0000000..f1a8530 --- /dev/null +++ b/vtubermini/src/components/shop/shop-currency-icon.tsx @@ -0,0 +1,5 @@ +import { PointsCurrencyIcon } from '../common/currency-icon' + +export function ShopCurrencyIcon({ className = 'size-5' }: { className?: string }) { + return +} diff --git a/vtubermini/src/index.css b/vtubermini/src/index.css new file mode 100644 index 0000000..8e2a003 --- /dev/null +++ b/vtubermini/src/index.css @@ -0,0 +1,699 @@ +@import "tailwindcss"; + +:root { + --dream-blue: #4a90e2; + --crystal-blue: #b8d4f1; + --moon-white: #e8f4f8; + --deep-blue: #2c5f8d; + --rose-gold: #d4af37; + --ink: #17304a; + --soft-shadow: 0 18px 48px rgba(44, 95, 141, 0.18); + --font-ui: "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", sans-serif; + --font-display: "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", sans-serif; + --text-page-title: clamp(15px, 4vw, 20px); + --text-section-title: clamp(13px, 3.2vw, 16px); + --text-body: clamp(11px, 2.8vw, 14px); + --text-caption: clamp(9px, 2.25vw, 11px); + --text-number-lg: clamp(42px, 11.2vw, 62px); + --text-number-md: clamp(25px, 6.7vw, 38px); + --font-title-weight: 600; + --font-body-weight: 400; + --font-emphasis-weight: 600; + font-family: var(--font-ui); + color: var(--ink); + background: #dceefa; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +html { + width: 100%; + height: 100%; + min-height: 100%; + overflow: hidden; + background: #dceefa; + font-family: var(--font-ui); +} + +body { + width: 100%; + height: 100%; + min-width: 0; + min-height: 100svh; + min-height: 100dvh; + margin: 0; + overflow: hidden; + overscroll-behavior: none; + font-family: var(--font-ui); +} + +button, +input, +textarea, +select { + font: inherit; + font-family: var(--font-ui); +} + +#root { + width: 100%; + height: 100%; + min-height: 100svh; + min-height: 100dvh; + overflow: hidden; + font-family: var(--font-ui); +} + +.device-stage { + width: 100vw; + width: 100dvw; + max-width: 100%; + height: 100svh; + height: 100dvh; + max-height: 100%; + overflow: hidden; + border-radius: 0; +} + +.reference-fit-frame { + position: absolute; + top: 50%; + left: 50%; + width: max(100%, 56.25svh); + width: max(100%, 56.25dvh); + height: max(100%, 177.7778vw); + height: max(100%, 177.7778dvw); + transform: translate(-50%, -50%); +} + +.safe-bottom { + padding-bottom: max(24px, calc(env(safe-area-inset-bottom) + 24px)); +} + +.safe-page-padding { + padding-top: max(16px, calc(env(safe-area-inset-top) + 16px)); + padding-right: max(16px, calc(env(safe-area-inset-right) + 16px)); + padding-left: max(16px, calc(env(safe-area-inset-left) + 16px)); +} + +.safe-page-top { + padding-top: max(16px, calc(env(safe-area-inset-top) + 16px)); +} + +.safe-page-x { + padding-right: max(16px, calc(env(safe-area-inset-right) + 16px)); + padding-left: max(16px, calc(env(safe-area-inset-left) + 16px)); +} + +.safe-page-bottom-nav { + padding-bottom: max(96px, calc(env(safe-area-inset-bottom) + 96px)); +} + +.safe-nav { + bottom: max(12px, calc(env(safe-area-inset-bottom) + 12px)); +} + +.no-scrollbar { + scrollbar-width: none; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +@media (min-width: 768px) { + .device-stage { + width: min(calc(100vw - 24px), calc((100svh - 32px) * 9 / 16), 430px); + height: min(calc(100svh - 32px), calc((100vw - 24px) * 16 / 9), 764px); + border-radius: 28px; + } +} + +.web-phone-preview-root { + display: grid; + place-items: center; + min-height: 100svh; + min-height: 100dvh; + padding: 14px; + overflow: hidden; +} + +.web-phone-frame { + position: relative; + display: grid; + place-items: center; + padding: 42px 12px 16px; + border: 1px solid rgba(212, 226, 240, 0.2); + border-radius: 48px; + background: + linear-gradient(145deg, rgba(255, 255, 255, 0.34) 0%, rgba(255, 255, 255, 0.08) 17%, transparent 36%), + linear-gradient(90deg, #050913 0%, #233049 5%, #0b1221 13%, #121b2d 50%, #050913 87%, #2a3854 95%, #07101d 100%); + box-shadow: + 0 34px 86px rgba(19, 43, 68, 0.34), + 0 10px 24px rgba(3, 9, 18, 0.16), + inset 0 0 0 1px rgba(255, 255, 255, 0.12), + inset 0 8px 18px rgba(255, 255, 255, 0.06), + inset 0 -18px 28px rgba(0, 0, 0, 0.36); +} + +.web-phone-frame::before { + content: ""; + position: absolute; + top: 18px; + left: 50%; + z-index: 3; + width: 74px; + height: 6px; + border-radius: 999px; + background: linear-gradient(180deg, rgba(208, 224, 240, 0.72), rgba(95, 111, 132, 0.56)); + box-shadow: + 0 1px 0 rgba(255, 255, 255, 0.22), + inset 0 1px 2px rgba(255, 255, 255, 0.28), + inset 0 -1px 2px rgba(0, 0, 0, 0.28); + transform: translateX(-50%); +} + +.web-phone-frame::after { + content: ""; + position: absolute; + top: 18px; + left: calc(50% + 52px); + z-index: 3; + width: 7px; + height: 7px; + border-radius: 999px; + background: + radial-gradient(circle at 35% 30%, rgba(173, 218, 255, 0.8), transparent 28%), + radial-gradient(circle at 62% 68%, #111a2a 0%, #050812 62%, #02040a 100%); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 0 4px rgba(92, 153, 213, 0.32); +} + +.web-phone-preview-root .device-stage { + width: min(calc(100vw - 86px), calc((100svh - 132px) * 9 / 16), 390px); + width: min(calc(100dvw - 86px), calc((100dvh - 132px) * 9 / 16), 390px); + height: min(calc(100svh - 132px), calc((100vw - 86px) * 16 / 9), 693.333px); + height: min(calc(100dvh - 132px), calc((100dvw - 86px) * 16 / 9), 693.333px); + z-index: 1; + border: 1px solid rgba(226, 239, 250, 0.24); + border-radius: 31px; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.72), + 0 0 0 4px rgba(8, 13, 24, 0.92), + inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 14px 24px rgba(255, 255, 255, 0.05), + inset 0 -18px 28px rgba(0, 0, 0, 0.22); +} + +.web-phone-preview-root .reference-fit-frame { + top: 0; + left: 0; + width: 100%; + height: 100%; + transform: none; +} + +.web-phone-preview-root .device-stage .fixed { + position: absolute; +} + +.glass-card { + border: 1px solid rgba(255, 255, 255, 0.72); + background: rgba(255, 255, 255, 0.72); + box-shadow: var(--soft-shadow); + backdrop-filter: blur(16px); +} + +.app-titlebar { + height: calc(env(safe-area-inset-top) + 52px); + padding: env(safe-area-inset-top) 16px 0; + display: flex; + align-items: center; + justify-content: center; +} + +.app-titlebar-transparent { + background: transparent; + box-shadow: none; + backdrop-filter: none; +} + +.app-page-title { + color: #173d6d; + font-family: var(--font-display); + font-size: var(--text-page-title); + font-weight: var(--font-title-weight); + line-height: 1.2; + letter-spacing: 0; +} + +.app-back-button { + position: absolute; + top: calc(env(safe-area-inset-top) + 26px); + left: 12px; + transform: translateY(-50%); + width: 46px; + height: 46px; + border: 0; + border-radius: 999px; + color: #17406d; + background: transparent; + font-size: 32px; + font-weight: 300; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 3px; +} + +.text-ui-section { + font-size: var(--text-section-title); + font-weight: var(--font-emphasis-weight); + line-height: 1.35; + letter-spacing: 0; +} + +.text-ui-body { + font-size: var(--text-body); + font-weight: var(--font-body-weight); + line-height: 1.5; + letter-spacing: 0; +} + +.text-ui-caption { + font-size: var(--text-caption); + font-weight: var(--font-body-weight); + line-height: 1.35; + letter-spacing: 0; +} + +.text-ui-number-lg { + font-family: var(--font-display); + font-size: var(--text-number-lg); + font-weight: 300; + line-height: 1; + letter-spacing: 0; +} + +.text-ui-number-md { + font-family: var(--font-display); + font-size: var(--text-number-md); + font-weight: 300; + line-height: 1; + letter-spacing: 0; +} + +.app-pill-button { + min-height: 36px; + border: 1px solid rgba(185, 218, 252, 0.72); + border-radius: 999px; + color: #5a9fee; + background: rgba(255, 255, 255, 0.58); + font-size: var(--text-body); + font-weight: var(--font-emphasis-weight); + letter-spacing: 0; +} + +.dream-panel { + background: + radial-gradient(circle at 20% 0%, rgba(212, 175, 55, 0.26), transparent 24%), + radial-gradient(circle at 82% 12%, rgba(184, 212, 241, 0.36), transparent 28%), + linear-gradient(160deg, #173967 0%, #245b97 48%, #7db8e8 100%); +} + +.primary-button { + border: 0; + border-radius: 999px; + color: white; + background: linear-gradient(135deg, var(--dream-blue), #6cb7f4); + box-shadow: 0 12px 24px rgba(74, 144, 226, 0.28); +} + +.secondary-button { + border: 1px solid rgba(74, 144, 226, 0.24); + border-radius: 999px; + color: var(--deep-blue); + background: rgba(255, 255, 255, 0.64); +} + +.stagger-fade-card { + opacity: 0; + transform: translateY(14px) scale(0.98); + animation: stagger-fade-card-in 520ms cubic-bezier(0.2, 0.82, 0.24, 1) forwards; + will-change: opacity, transform; +} + +.heart-reveal-card-image { + animation: heart-reveal-card-enter 300ms cubic-bezier(0.2, 0.82, 0.24, 1) both; + will-change: opacity, transform, filter; +} + +.heart-reveal-card-image-switching { + animation: heart-reveal-card-switch 280ms cubic-bezier(0.42, 0, 0.24, 1) both; +} + +.heart-reveal-card-flash { + inset: 0; + background: + radial-gradient(ellipse at 50% 48%, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0.12) 34%, transparent 68%), + linear-gradient(115deg, transparent 0%, transparent 34%, rgba(255, 255, 255, 0.38) 47%, rgba(255, 255, 255, 0.22) 53%, transparent 66%, transparent 100%); + background-position: + center, + 120% 0; + background-size: + 100% 100%, + 240% 100%; + animation: heart-reveal-card-flash 280ms ease-out both; + mix-blend-mode: screen; + pointer-events: none; +} + +@keyframes stagger-fade-card-in { + 0% { + opacity: 0; + transform: translateY(14px) scale(0.98); + } + + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes heart-reveal-card-enter { + 0% { + opacity: 0; + filter: brightness(1.22) saturate(1.08); + transform: scale(1.035); + } + + 100% { + opacity: 1; + filter: brightness(1) saturate(1); + transform: scale(1); + } +} + +@keyframes heart-reveal-card-switch { + 0% { + opacity: 1; + filter: brightness(1) saturate(1); + transform: scale(1); + } + + 100% { + opacity: 0; + filter: brightness(1.24) saturate(1.12); + transform: scale(0.965); + } +} + +@keyframes heart-reveal-card-flash { + 0% { + opacity: 0; + background-position: + center, + 120% 0; + } + + 42% { + opacity: 0.95; + } + + 100% { + opacity: 0; + background-position: + center, + -20% 0; + } +} + +@keyframes pet-toast-rise { + 0% { + opacity: 0; + transform: translateY(14px) scale(0.98); + } + + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes home-dialog-float { + 0% { + opacity: 0; + transform: translateY(8px) scale(0.98); + } + + 12%, + 82% { + opacity: 1; + transform: translateY(0) scale(1); + } + + 100% { + opacity: 0; + transform: translateY(-5px) scale(0.99); + } +} + +@keyframes home-idle-dialog-enter { + 0% { + opacity: 0; + transform: translateY(12px) scale(0.985); + } + + 72% { + opacity: 1; + transform: translateY(-1px) scale(1); + } + + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.home-new-badge-breath { + transform-origin: 50% 50%; + animation: home-new-badge-breath 1800ms ease-in-out infinite; +} + +.home-new-badge-shape { + transform-origin: 50% 50%; + transform: scale(0.92, 0.8); +} + +.home-anniversary-title-shell { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + border: 1px solid rgba(199, 233, 255, 0.34); + border-radius: 14px 18px 18px 14px; + background: + linear-gradient(110deg, rgba(10, 27, 61, 0.62), rgba(16, 62, 118, 0.34) 62%, rgba(255, 255, 255, 0.1)), + radial-gradient(circle at 9% 14%, rgba(255, 255, 255, 0.24), transparent 32%); + box-shadow: + 0 9px 18px rgba(1, 7, 20, 0.26), + inset 0 1px 0 rgba(255, 255, 255, 0.2), + inset 0 -1px 0 rgba(123, 194, 255, 0.12); + backdrop-filter: blur(10px); +} + +.home-anniversary-title-shell::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.16) 44%, transparent 62%); + opacity: 0.44; + transform: translateX(-38%); +} + +.home-anniversary-orbit { + position: absolute; + right: -18%; + top: 50%; + width: 56%; + aspect-ratio: 1; + border: 1px solid rgba(174, 226, 255, 0.24); + border-radius: 999px; + transform: translateY(-50%); +} + +.home-anniversary-orbit::before { + content: ""; + position: absolute; + left: 11%; + top: 19%; + width: 5px; + height: 5px; + border-radius: 999px; + background: #fff3ba; + box-shadow: 0 0 9px rgba(255, 232, 158, 0.74); +} + +.home-anniversary-title-copy { + position: relative; + z-index: 1; + display: flex; + min-width: 0; + height: 100%; + flex-direction: column; + justify-content: center; + padding: 0 30% 0 11%; +} + +.home-anniversary-kicker { + margin: 0; + color: rgba(196, 231, 255, 0.86); + font-size: clamp(7px, 1.85vw, 9px); + font-weight: 500; + line-height: 1; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); +} + +.home-anniversary-name { + max-width: 100%; + margin: 5% 0 0; + overflow: hidden; + color: rgba(255, 255, 255, 0.96); + font-size: clamp(11px, 2.8vw, 14px); + font-weight: 650; + line-height: 1.05; + text-overflow: ellipsis; + text-shadow: 0 1px 4px rgba(2, 8, 22, 0.74); + white-space: nowrap; +} + +.home-anniversary-days { + position: relative; + display: grid; + width: 100%; + height: 100%; + place-items: center; + overflow: hidden; + border: 1px solid rgba(208, 239, 255, 0.42); + border-radius: 999px; + background: + radial-gradient(circle at 38% 24%, rgba(255, 255, 255, 0.3), transparent 26%), + linear-gradient(160deg, rgba(4, 20, 52, 0.72), rgba(28, 85, 145, 0.42)); + box-shadow: + 0 10px 20px rgba(1, 7, 20, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.28), + inset 0 -8px 18px rgba(0, 15, 44, 0.18); + text-align: center; + backdrop-filter: blur(12px); +} + +.home-anniversary-days--elapsed { + border-color: rgba(255, 223, 159, 0.5); + background: + radial-gradient(circle at 38% 24%, rgba(255, 255, 255, 0.3), transparent 26%), + linear-gradient(160deg, rgba(52, 28, 58, 0.74), rgba(115, 78, 55, 0.42)); +} + +.home-anniversary-days-ring { + position: absolute; + inset: 8%; + border: 1px solid rgba(148, 219, 255, 0.28); + border-radius: inherit; +} + +.home-anniversary-days--elapsed .home-anniversary-days-ring { + border-color: rgba(247, 199, 105, 0.32); +} + +.home-anniversary-state, +.home-anniversary-number, +.home-anniversary-unit { + position: relative; + z-index: 1; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.58); +} + +.home-anniversary-state { + align-self: end; + margin-top: 10%; + color: rgba(222, 247, 255, 0.92); + font-size: clamp(7px, 1.9vw, 9px); + font-weight: 700; + line-height: 1; +} + +.home-anniversary-days--elapsed .home-anniversary-state { + color: rgba(255, 232, 183, 0.94); +} + +.home-anniversary-number { + margin-top: -2%; + color: #effbff; + font-size: clamp(22px, 6.2vw, 34px); + font-weight: 300; + line-height: 0.84; +} + +.home-anniversary-days--elapsed .home-anniversary-number { + color: #ffe3a8; +} + +.home-anniversary-unit { + align-self: start; + margin-top: -2%; + color: rgba(255, 255, 255, 0.82); + font-size: clamp(7px, 1.9vw, 9px); + font-weight: 600; + line-height: 1; +} + +@keyframes home-new-badge-breath { + 0%, + 100% { + transform: scale(0.94); + filter: brightness(1); + } + + 50% { + transform: scale(1); + filter: brightness(1.06); + } +} + +@media (prefers-reduced-motion: reduce) { + .stagger-fade-card { + opacity: 1; + transform: none; + animation: none; + } + + .heart-reveal-card-image, + .heart-reveal-card-image-switching, + .heart-reveal-card-flash { + opacity: 1; + filter: none; + transform: none; + animation: none; + } + + .home-new-badge-breath { + filter: none; + transform: none; + animation: none; + } + + .animate-\[home-idle-dialog-enter_360ms_cubic-bezier\(0\.2\,0\.82\,0\.24\,1\)_both\] { + opacity: 1; + transform: none; + animation: none; + } + +} diff --git a/vtubermini/src/lib/admin-rules.ts b/vtubermini/src/lib/admin-rules.ts new file mode 100644 index 0000000..6583eac --- /dev/null +++ b/vtubermini/src/lib/admin-rules.ts @@ -0,0 +1,47 @@ +import { addAssetLedgerEntry } from './asset-ledger' +import type { AdminAuditLog, AdminGrantInput, AdminGrantResult, MockState } from './types' + +export function grantAssetFromAdmin(state: MockState, input: AdminGrantInput): AdminGrantResult { + if (input.amount <= 0) { + return { nextState: state, message: '发放数量必须大于 0。', success: false } + } + + if (!input.reason.trim()) { + return { nextState: state, message: '后台发放必须填写原因。', success: false } + } + + const businessId = `admin-grant-${Date.now()}` + const nextState = addAssetLedgerEntry(state, { + assetType: input.assetType, + businessId, + change: input.amount, + reason: input.reason.trim(), + sourceType: 'admin', + }) + + const auditLog: AdminAuditLog = { + id: `audit-${Date.now()}-${state.adminAuditLogs.length + 1}`, + operator: input.operator.trim() || 'admin.moon', + action: input.assetType === 'points' ? 'grant_points' : 'grant_diamonds', + target: state.user.id, + reason: input.reason.trim(), + status: 'approved', + createdAt: formatDateTime(new Date()), + businessId, + } + + return { + nextState: { + ...nextState, + adminAuditLogs: [auditLog, ...state.adminAuditLogs], + }, + message: '资产已发放,并写入资产流水和后台审计。', + success: true, + } +} + +function formatDateTime(date: Date) { + const dateText = date.toISOString().slice(0, 10) + const timeText = date.toTimeString().slice(0, 5) + return `${dateText} ${timeText}` +} diff --git a/vtubermini/src/lib/anniversary-image-layout.ts b/vtubermini/src/lib/anniversary-image-layout.ts new file mode 100644 index 0000000..949c289 --- /dev/null +++ b/vtubermini/src/lib/anniversary-image-layout.ts @@ -0,0 +1,36 @@ +import type { AnniversaryImageLayout } from './types' + +export const defaultAnniversaryImageLayout: AnniversaryImageLayout = { + width: 58, + height: 58, +} + +export function normalizeAnniversaryImageLayout(value: unknown): AnniversaryImageLayout { + if (!isRecord(value)) { + return defaultAnniversaryImageLayout + } + + return clampAnniversaryImageLayout({ + width: readNumber(value.width, defaultAnniversaryImageLayout.width), + height: readNumber(value.height, defaultAnniversaryImageLayout.height), + }) +} + +export function clampAnniversaryImageLayout(layout: AnniversaryImageLayout): AnniversaryImageLayout { + return { + width: clampNumber(layout.width, 36, 96), + height: clampNumber(layout.height, 36, 96), + } +} + +function readNumber(value: unknown, fallback: number) { + return typeof value === 'number' && Number.isFinite(value) ? value : fallback +} + +function clampNumber(value: number, min: number, max: number) { + return Math.min(Math.max(value, min), max) +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} diff --git a/vtubermini/src/lib/anniversary-rules.ts b/vtubermini/src/lib/anniversary-rules.ts new file mode 100644 index 0000000..d8a4660 --- /dev/null +++ b/vtubermini/src/lib/anniversary-rules.ts @@ -0,0 +1,179 @@ +import type { Anniversary, AnniversaryInput, MockState } from './types' + +const LUNAR_DAY_LABELS = [ + '初一', + '初二', + '初三', + '初四', + '初五', + '初六', + '初七', + '初八', + '初九', + '初十', + '十一', + '十二', + '十三', + '十四', + '十五', + '十六', + '十七', + '十八', + '十九', + '二十', + '廿一', + '廿二', + '廿三', + '廿四', + '廿五', + '廿六', + '廿七', + '廿八', + '廿九', + '三十', +] + +export function getDaysLeft(anniversary: Anniversary, from = new Date()) { + const target = getNextOccurrence(anniversary.date, anniversary.repeatYearly, from) + const fromDay = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate()) + const targetDay = Date.UTC(target.getFullYear(), target.getMonth(), target.getDate()) + return Math.max(0, Math.ceil((targetDay - fromDay) / 86_400_000)) +} + +export function getElapsedDays(dateText: string, from = new Date()) { + const start = parseDate(dateText) + const fromDay = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate()) + const startDay = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate()) + return Math.max(0, Math.floor((fromDay - startDay) / 86_400_000)) +} + +export function getAnniversaryDays(anniversary: Anniversary, from = new Date()) { + return anniversary.repeatYearly ? getDaysLeft(anniversary, from) : getElapsedDays(anniversary.date, from) +} + +export function getAnniversaryStatusLabel(anniversary: Anniversary) { + return anniversary.repeatYearly ? '还剩' : '已经' +} + +export function formatLunarDate(dateText: string) { + try { + const formatter = new Intl.DateTimeFormat('zh-CN-u-ca-chinese', { month: 'long', day: 'numeric' }) + const parts = formatter.formatToParts(parseDate(dateText)) + const month = parts.find((part) => part.type === 'month')?.value + const day = Number(parts.find((part) => part.type === 'day')?.value) + + if (!month || !Number.isInteger(day) || day < 1) { + return '' + } + + return `农历${month}${LUNAR_DAY_LABELS[day - 1] ?? day}` + } catch { + return '' + } +} + +export function sortAnniversaries(items: Anniversary[]) { + return [...items].sort((a, b) => { + const dayDiff = getDaysLeft(a) - getDaysLeft(b) + if (dayDiff !== 0) { + return dayDiff + } + + return getTypePriority(a.type) - getTypePriority(b.type) + }) +} + +export function upsertCustomAnniversary(state: MockState, input: AnniversaryInput): MockState { + const reminderEnabled = input.repeatYearly ? input.reminderEnabled : false + const reminderOffsetDays = input.repeatYearly ? input.reminderOffsetDays : 0 + const anniversary: Anniversary = { + id: input.id ?? `custom-${Date.now()}`, + title: input.title.trim(), + type: 'custom', + date: input.date, + showLunar: input.showLunar ?? false, + imageUrl: input.imageUrl, + repeatYearly: input.repeatYearly, + reminderEnabled, + reminderOffsetDays, + note: input.note.trim(), + } + + const exists = state.anniversaries.some((item) => item.id === anniversary.id) + return { + ...state, + anniversaries: exists + ? state.anniversaries.map((item) => (item.id === anniversary.id ? anniversary : item)) + : [anniversary, ...state.anniversaries], + } +} + +export function updateJoinDate(state: MockState, date: string): MockState { + const joinDay: Anniversary = { + id: 'join', + title: '入团纪念日', + type: 'join', + date, + showLunar: false, + repeatYearly: true, + reminderEnabled: true, + reminderOffsetDays: 1, + note: '第一次加入粉丝团的日子', + } + const exists = state.anniversaries.some((item) => item.type === 'join') + + return { + ...state, + anniversaries: exists + ? state.anniversaries.map((item) => + item.type === 'join' + ? { + ...item, + date, + } + : item, + ) + : [joinDay, ...state.anniversaries], + } +} + +export function deleteCustomAnniversary(state: MockState, id: string): MockState { + return { + ...state, + anniversaries: state.anniversaries.filter((item) => item.id !== id || item.type !== 'custom'), + } +} + +export function getReminderText(anniversary: Anniversary) { + if (!anniversary.reminderEnabled) { + return '提醒已关闭' + } + + if (anniversary.reminderOffsetDays === 0) { + return '当天 09:00 提醒' + } + + return `提前 ${anniversary.reminderOffsetDays} 天 09:00 提醒` +} + +function getNextOccurrence(dateText: string, repeatYearly: boolean, from: Date) { + const original = parseDate(dateText) + if (!repeatYearly) { + return original + } + + const candidate = new Date(from.getFullYear(), original.getMonth(), original.getDate()) + if (candidate < new Date(from.getFullYear(), from.getMonth(), from.getDate())) { + candidate.setFullYear(candidate.getFullYear() + 1) + } + + return candidate +} + +function parseDate(dateText: string) { + return new Date(`${dateText}T00:00:00`) +} + +function getTypePriority(type: Anniversary['type']) { + return { birthday: 0, debut: 1, join: 2, custom: 3 }[type] +} diff --git a/vtubermini/src/lib/asset-ledger.ts b/vtubermini/src/lib/asset-ledger.ts new file mode 100644 index 0000000..8608d6f --- /dev/null +++ b/vtubermini/src/lib/asset-ledger.ts @@ -0,0 +1,51 @@ +import type { AssetLedgerEntry, AssetType, MockState } from './types' + +interface LedgerInput { + assetType: AssetType + businessId?: string + change: number + reason: string + sourceType?: string +} + +export function addAssetLedgerEntry(state: MockState, input: LedgerInput): MockState { + const balanceAfter = getBalanceAfter(state, input.assetType, input.change) + const entry: AssetLedgerEntry = { + id: `ledger-${Date.now()}-${state.assetLedger.length + 1}`, + assetType: input.assetType, + businessId: input.businessId ?? `biz-${Date.now()}`, + change: input.change, + reason: input.reason, + sourceType: input.sourceType ?? 'system', + createdAt: formatDateTime(new Date()), + balanceAfter, + } + + return { + ...state, + assetLedger: [entry, ...state.assetLedger], + user: { + ...state.user, + points: input.assetType === 'points' ? balanceAfter : state.user.points, + diamonds: input.assetType === 'diamonds' ? balanceAfter : state.user.diamonds, + }, + } +} + +function getBalanceAfter(state: MockState, assetType: AssetType, change: number) { + if (assetType === 'points') { + return Math.max(0, state.user.points + change) + } + + if (assetType === 'diamonds') { + return Math.max(0, state.user.diamonds + change) + } + + return Math.max(0, change) +} + +function formatDateTime(date: Date) { + const dateText = date.toISOString().slice(0, 10) + const timeText = date.toTimeString().slice(0, 5) + return `${dateText} ${timeText}` +} diff --git a/vtubermini/src/lib/gacha-rules.ts b/vtubermini/src/lib/gacha-rules.ts new file mode 100644 index 0000000..1cf7902 --- /dev/null +++ b/vtubermini/src/lib/gacha-rules.ts @@ -0,0 +1,271 @@ +import { addAssetLedgerEntry } from './asset-ledger' +import type { + AssetType, + CardItem, + GachaDrawMode, + GachaDrawResult, + GachaLogEntry, + GachaResult, + MockState, + Rarity, +} from './types' + +const RARITY_ORDER: Rarity[] = ['N', 'R', 'SR', 'SSR'] +const TEN_DRAW_COUNT = 10 +const DUPLICATE_POINTS = 8 +const HEART_MOMENT_COST_PER_DRAW = 10 + +interface DrawGachaOptions { + assetType?: Extract + cardFilter?: (card: CardItem) => boolean + costPerDraw?: number + costReason?: string + duplicateReason?: string + poolName?: string + poolVersion?: string + sourceType?: string + successMessage?: (count: number) => string +} + +export function drawGacha(state: MockState, mode: GachaDrawMode, options: DrawGachaOptions = {}): GachaResult { + const assetType = options.assetType ?? 'points' + const costPerDraw = options.costPerDraw ?? state.pool.costPoints + const drawCount = mode === 'single' ? 1 : TEN_DRAW_COUNT + const totalCost = costPerDraw * drawCount + const currentBalance = assetType === 'diamonds' ? state.user.diamonds : state.user.points + + if (currentBalance < totalCost) { + const assetLabel = assetType === 'diamonds' ? '钻石' : '积分' + return { + nextState: state, + results: [], + message: `${assetLabel}不足,需要 ${totalCost} ${assetLabel},当前只有 ${currentBalance}。`, + success: false, + totalCost, + } + } + + let cards = [...state.cards] + const cardFilter = options.cardFilter ?? ((card: CardItem) => !isHeartArchiveCard(card)) + const results: GachaDrawResult[] = [] + + for (let index = 0; index < drawCount; index += 1) { + const selected = selectCard(cards, state.pool.pityCurrent + index, cardFilter) + const duplicate = selected.owned + const updatedCard: CardItem = duplicate + ? { ...selected, duplicateCount: selected.duplicateCount + 1 } + : { ...selected, owned: true, acquiredAt: formatDateTime(new Date()), duplicateCount: selected.duplicateCount } + + cards = cards.map((card) => (card.id === selected.id ? updatedCard : card)) + results.push({ + card: updatedCard, + duplicate, + convertedPoints: duplicate ? DUPLICATE_POINTS : 0, + }) + } + + const convertedPoints = results.reduce((sum, result) => sum + result.convertedPoints, 0) + const stateAfterDraw: MockState = { + ...state, + cards, + pool: { + ...state.pool, + pityCurrent: getNextPity(state.pool.pityCurrent, results), + }, + } + + const stateWithCostLedger = addAssetLedgerEntry(stateAfterDraw, { + assetType, + businessId: `gacha-cost-${Date.now()}`, + change: -totalCost, + reason: options.costReason ?? (mode === 'single' ? '积分单抽' : '积分十连抽'), + sourceType: options.sourceType ?? 'gacha', + }) + + const stateWithConvertedPoints = + convertedPoints > 0 + ? addAssetLedgerEntry(stateWithCostLedger, { + assetType: 'points', + businessId: `gacha-duplicate-${Date.now()}`, + change: convertedPoints, + reason: options.duplicateReason ?? '重复小卡转积分', + sourceType: options.sourceType ?? 'gacha', + }) + : stateWithCostLedger + + const log: GachaLogEntry = { + id: `gacha-${Date.now()}-${state.gachaLogs.length + 1}`, + poolName: options.poolName ?? state.pool.name, + poolVersion: options.poolVersion ?? state.pool.version, + assetType, + costPoints: totalCost, + createdAt: formatDateTime(new Date()), + results: results.map((result) => ({ + cardId: result.card.id, + title: result.card.title, + rarity: result.card.rarity, + duplicate: result.duplicate, + convertedPoints: result.convertedPoints, + })), + } + + return { + nextState: { + ...stateWithConvertedPoints, + cards, + gachaLogs: [log, ...state.gachaLogs], + pool: stateAfterDraw.pool, + }, + results, + message: options.successMessage?.(results.length) ?? `抽卡完成,获得 ${results.length} 张小卡。`, + success: true, + totalCost, + } +} + +export function drawHeartMoment(state: MockState, mode: GachaDrawMode): GachaResult { + const currentBalance = state.user.diamonds + const drawCount = mode === 'single' ? 1 : TEN_DRAW_COUNT + const totalCost = HEART_MOMENT_COST_PER_DRAW * drawCount + const heartArchiveCards = state.cards.filter(isHeartArchiveCard) + + if (currentBalance < totalCost) { + return { + nextState: state, + results: [], + message: `钻石不足,需要 ${totalCost} 钻石,当前只有 ${currentBalance}。`, + success: false, + totalCost, + } + } + + if (heartArchiveCards.length === 0) { + return { + nextState: state, + results: [], + message: '当前没有可抽取的心动档案。', + success: false, + totalCost: 0, + } + } + + const acquiredAt = formatDateTime(new Date()) + const selectedCards = selectHeartMomentCards(heartArchiveCards, drawCount) + const results: GachaDrawResult[] = selectedCards.map((card) => { + const duplicate = card.owned + const updatedCard: CardItem = duplicate + ? { ...card, duplicateCount: card.duplicateCount + 1 } + : { ...card, owned: true, acquiredAt, duplicateCount: card.duplicateCount } + + return { + card: updatedCard, + duplicate, + convertedPoints: duplicate ? DUPLICATE_POINTS : 0, + } + }) + const updatedCardsById = new Map(results.map((result) => [result.card.id, result.card])) + const cards = state.cards.map((card) => updatedCardsById.get(card.id) ?? card) + const convertedPoints = results.reduce((sum, result) => sum + result.convertedPoints, 0) + + const stateWithCards: MockState = { + ...state, + cards, + } + const stateWithCostLedger = addAssetLedgerEntry(stateWithCards, { + assetType: 'diamonds', + businessId: `heart-moment-cost-${Date.now()}`, + change: -totalCost, + reason: mode === 'single' ? '心动时刻单抽' : '心动时刻十连抽', + sourceType: 'heart-moment', + }) + const stateWithConvertedPoints = + convertedPoints > 0 + ? addAssetLedgerEntry(stateWithCostLedger, { + assetType: 'points', + businessId: `heart-moment-duplicate-${Date.now()}`, + change: convertedPoints, + reason: '心动时刻重复档案转积分', + sourceType: 'heart-moment', + }) + : stateWithCostLedger + const log: GachaLogEntry = { + id: `gacha-${Date.now()}-${state.gachaLogs.length + 1}`, + poolName: '星夜邂逅心动时刻', + poolVersion: '2026.06-heart', + assetType: 'diamonds', + costPoints: totalCost, + createdAt: acquiredAt, + results: results.map((result) => ({ + cardId: result.card.id, + title: result.card.title, + rarity: result.card.rarity, + duplicate: result.duplicate, + convertedPoints: result.convertedPoints, + })), + } + + return { + nextState: { + ...stateWithConvertedPoints, + cards, + gachaLogs: [log, ...state.gachaLogs], + }, + results, + message: `心动抽取完成,解锁 ${results.length} 份心动档案。`, + success: true, + totalCost, + } +} + +function selectHeartMomentCards(cards: CardItem[], drawCount: number) { + const unownedCards = cards.filter((card) => !card.owned) + const ownedCards = cards.filter((card) => card.owned) + return [...unownedCards, ...ownedCards].slice(0, drawCount) +} + +function selectCard(cards: CardItem[], pityCounter: number, cardFilter: (card: CardItem) => boolean) { + const eligibleCards = cards.filter(cardFilter) + const sourceCards = eligibleCards.length > 0 ? eligibleCards : cards + const shouldForceRare = (pityCounter + 1) % 60 === 0 + const targetRarity = shouldForceRare ? 'SSR' : pickRarity(pityCounter) + const candidates = sourceCards.filter((card) => card.rarity === targetRarity) + + if (candidates.length > 0) { + return candidates[pityCounter % candidates.length] + } + + return sourceCards[pityCounter % sourceCards.length] +} + +function isHeartArchiveCard(card: CardItem) { + return card.id.startsWith('ARCH-') +} + +function pickRarity(counter: number): Rarity { + const roll = (counter * 37 + 19) % 100 + let cursor = 0 + + for (const rarity of RARITY_ORDER) { + cursor += getDefaultRate(rarity) + if (roll < cursor) { + return rarity + } + } + + return 'N' +} + +function getDefaultRate(rarity: Rarity) { + return { N: 76, R: 18, SR: 5, SSR: 1 }[rarity] +} + +function getNextPity(current: number, results: GachaDrawResult[]) { + const hasSsr = results.some((result) => result.card.rarity === 'SSR') + return hasSsr ? 0 : current + results.length +} + +function formatDateTime(date: Date) { + const dateText = date.toISOString().slice(0, 10) + const timeText = date.toTimeString().slice(0, 5) + return `${dateText} ${timeText}` +} diff --git a/vtubermini/src/lib/home-layout.ts b/vtubermini/src/lib/home-layout.ts new file mode 100644 index 0000000..6a33fa6 --- /dev/null +++ b/vtubermini/src/lib/home-layout.ts @@ -0,0 +1,99 @@ +import type { HomeLayout, HomeLayoutElementId, HomeLayoutRect } from './types' + +export const homeLayoutElementOrder: HomeLayoutElementId[] = ['profile', 'homeAnniversaryAsset', 'anniversaryTitle', 'anniversaryStatus', 'anniversaryDays', 'homeIconAnniversary', 'homeIconSupport', 'homeIconActivity', 'homeIconVote', 'homeIconShop', 'homeIconMoments', 'homeMomentsNewBadge', 'homeIconPet', 'homeIconAlbum'] + +export const homeLayoutElementLabels: Record = { + profileLevel: '\u7b49\u7ea7\u6570\u5b57', + profile: '\u8d26\u53f7\u4fe1\u606f', + homeAnniversaryAsset: '\u9996\u9875-\u7eaa\u5ff5\u65e5\u7d20\u6750', + anniversaryTitle: '\u7eaa\u5ff5\u65e5\u540d\u79f0', + anniversaryStatus: '\u7eaa\u5ff5\u65e5\u72b6\u6001', + anniversaryDays: '\u65e5\u5386\u5929\u6570', + homeIconAnniversary: '\u9996\u9875-\u7eaa\u5ff5\u65e5\u56fe\u6807', + homeIconSupport: '\u9996\u9875-\u5e94\u63f4\u56fe\u6807', + homeIconActivity: '\u9996\u9875-\u6d3b\u52a8\u56fe\u6807', + homeIconVote: '\u9996\u9875-\u6295\u7968\u56fe\u6807', + homeIconShop: '\u9996\u9875-\u5546\u57ce\u56fe\u6807', + homeIconMoments: '\u9996\u9875-\u5fc3\u52a8\u65f6\u523b\u56fe\u6807', + homeMomentsNewBadge: '\u9996\u9875-\u5fc3\u52a8\u65f6\u523b\u5168\u65b0\u7cfb\u5217\u6c14\u6ce1', + homeIconPet: '\u9996\u9875-\u5ba0\u7269\u56fe\u6807', + homeIconAlbum: '\u9996\u9875-\u5fc3\u52a8\u6863\u6848\u56fe\u6807', +} + +export const defaultHomeLayout: HomeLayout = { + profileLevel: { left: 5.2, top: 2.4, width: 9.2, height: 8.4 }, + profile: { left: 0.6, top: 1.7, width: 38, height: 7.9 }, + homeAnniversaryAsset: { left: 56.5, top: 0.9, width: 41.4, height: 19.7 }, + anniversaryTitle: { left: 12.6, top: 29.5, width: 46.4, height: 38 }, + anniversaryStatus: { left: 73.1, top: 26, width: 12.6, height: 11 }, + anniversaryDays: { left: 69.2, top: 36.9, width: 20, height: 31 }, + homeIconAnniversary: { left: 84.6, top: 14.7, width: 8.8, height: 5.9 }, + homeIconSupport: { left: 84.6, top: 22.9, width: 8.8, height: 5.9 }, + homeIconActivity: { left: 84.5, top: 31.2, width: 8.8, height: 5.9 }, + homeIconVote: { left: 84.7, top: 39.7, width: 8.8, height: 5.9 }, + homeIconShop: { left: 8.5, top: 86.3, width: 15.9, height: 13.6 }, + homeIconMoments: { left: 31.8, top: 86.4, width: 15.9, height: 13.6 }, + homeMomentsNewBadge: { left: 39.7, top: 86.9, width: 8.8, height: 4 }, + homeIconPet: { left: 54.4, top: 86.4, width: 15.9, height: 13.6 }, + homeIconAlbum: { left: 76.6, top: 84.8, width: 17.4, height: 15.2 }, +} + +export function normalizeHomeLayout(value: unknown): HomeLayout { + if (!isRecord(value)) { + return defaultHomeLayout + } + + const nextLayout = { ...defaultHomeLayout } + + for (const elementId of Object.keys(defaultHomeLayout) as HomeLayoutElementId[]) { + nextLayout[elementId] = normalizeRect(value[elementId], defaultHomeLayout[elementId]) + } + + return nextLayout +} + +export function clampHomeLayoutRect(rect: HomeLayoutRect): HomeLayoutRect { + const width = clampNumber(rect.width, 2, 100) + const height = clampNumber(rect.height, 2, 100) + + return { + left: clampNumber(rect.left, 0, 100 - width), + top: clampNumber(rect.top, 0, 100 - height), + width, + height, + } +} + +function normalizeRect(value: unknown, fallback: HomeLayoutRect): HomeLayoutRect { + if (!isRecord(value)) { + return fallback + } + + return clampHomeLayoutRect({ + left: readNumber(value.left, fallback.left), + top: readNumber(value.top, fallback.top), + width: readNumber(value.width, fallback.width), + height: readNumber(value.height, fallback.height), + }) +} + +function readNumber(value: unknown, fallback: number) { + if (typeof value === 'number' && Number.isFinite(value)) { + return value + } + + if (typeof value === 'string' && value.trim() !== '') { + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : fallback + } + + return fallback +} + +function clampNumber(value: number, min: number, max: number) { + return Math.min(Math.max(value, min), max) +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} diff --git a/vtubermini/src/lib/mock-data.ts b/vtubermini/src/lib/mock-data.ts new file mode 100644 index 0000000..55bcf3e --- /dev/null +++ b/vtubermini/src/lib/mock-data.ts @@ -0,0 +1,242 @@ +import type { MockState } from './types' + +export const mockState: MockState = { + user: { + id: 'fan-001', + name: '月光团员', + level: 12, + points: 168, + diamonds: 1250, + }, + pet: { + name: '蓝莓小汪', + hunger: 72, + mood: 86, + cleanliness: 68, + energy: 54, + intimacy: 41, + intimacyLevel: 5, + dailyPointsEarned: 36, + dailyPointsCap: 80, + dailyActionCounts: { + feed: 0, + pat: 0, + clean: 0, + play: 0, + }, + dailyLoginSeen: false, + dailyCareCompleted: false, + streakDays: 6, + totalCareDays: 24, + }, + assetLedger: [ + { + id: 'ledger-login', + assetType: 'points', + businessId: 'daily-login-20260602', + change: 8, + reason: '登录看小狗', + sourceType: 'pet', + createdAt: '2026-06-02 09:12', + balanceAfter: 132, + }, + { + id: 'ledger-clean', + assetType: 'points', + businessId: 'pet-clean-20260602', + change: 6, + reason: '完成清洁互动', + sourceType: 'pet', + createdAt: '2026-06-02 09:18', + balanceAfter: 138, + }, + { + id: 'ledger-diamond-grant', + assetType: 'diamonds', + businessId: 'admin-grant-20260602', + change: 42, + reason: '后台活动发放', + sourceType: 'admin', + createdAt: '2026-06-02 10:00', + balanceAfter: 42, + }, + ], + pool: { + name: '月光玫瑰纪念卡池', + version: '2026.06-A', + costPoints: 100, + pityCurrent: 18, + pityTarget: 60, + duplicateRule: '重复小卡转化为 8 积分,预留碎片记录', + rates: { N: 76, R: 18, SR: 5, SSR: 1 }, + }, + cards: [ + { + id: 'N-001', + title: '晨间问候', + series: '日常', + rarity: 'N', + owned: true, + acquiredAt: '2026-06-01 10:12', + duplicateCount: 0, + }, + { + id: 'R-014', + title: '水晶丝带', + series: '日常', + rarity: 'R', + owned: true, + acquiredAt: '2026-06-01 12:30', + duplicateCount: 1, + }, + { id: 'SR-006', title: '蓝玫瑰舞台', series: '活动', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'SSR-001', title: '月冠限定', series: '周年', rarity: 'SSR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SSR-001', title: '星耀圣羽', series: '心动档案', rarity: 'SSR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SSR-002', title: '赤影行歌', series: '心动档案', rarity: 'SSR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SSR-003', title: '星夜邂逅', series: '心动档案', rarity: 'SSR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-001', title: '晨光微澜', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-002', title: '花间漫溯', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-003', title: '暗夜守望', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-004', title: '静夜思绪', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-005', title: '商务博弈', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-006', title: '理性边界', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + { id: 'ARCH-SR-007', title: '雨幕之下', series: '心动档案', rarity: 'SR', owned: false, duplicateCount: 0 }, + ], + gachaLogs: [], + products: [ + { + id: 'sku-canvas-bag', + name: '帆布袋', + image: '🛍️', + spec: 'DL.L9 蓝玫瑰单肩帆布袋', + diamondPrice: 600, + stock: 42, + shippingRule: '兑换成功后 7 个工作日内发货', + afterSalesRule: '发货前可联系客服修改地址,签收后 7 天内处理质量问题。', + exchangeRule: '每个账号本期最多兑换 2 件,库存锁定后不可转赠。', + }, + { + id: 'sku-notebook', + name: '笔记本', + image: '📘', + spec: 'A5 蓝玫瑰线圈笔记本', + diamondPrice: 500, + stock: 36, + shippingRule: '兑换成功后 7 个工作日内发货', + afterSalesRule: '非质量问题不支持退换,破损请在签收 48 小时内提交申请。', + exchangeRule: '每个账号本期最多兑换 2 件。', + }, + { + id: 'sku-mug', + name: '马克杯', + image: '☕', + spec: 'DL.L9 陶瓷马克杯 350ml', + diamondPrice: 450, + stock: 28, + shippingRule: '兑换成功后 7 个工作日内发货,易碎品加固包装', + afterSalesRule: '运输破损请保留外包装并在 48 小时内联系客服。', + exchangeRule: '每个账号本期限兑 1 件。', + }, + { + id: 'sku-sticker-pack', + name: '贴纸套装', + image: '🎴', + spec: '角色贴纸 6 枚入', + diamondPrice: 300, + stock: 80, + shippingRule: '兑换成功后 5 个工作日内发货', + afterSalesRule: '贴纸类周边拆封后非质量问题不退换。', + exchangeRule: '每个账号本期最多兑换 3 件。', + }, + { + id: 'sku-keychain', + name: '钥匙扣', + image: '🔑', + spec: 'Q版亚克力钥匙扣', + diamondPrice: 300, + stock: 64, + shippingRule: '兑换成功后 5 个工作日内发货', + afterSalesRule: '发货前可联系客服修改地址,质量问题支持补发。', + exchangeRule: '每个账号本期最多兑换 2 件。', + }, + ], + orders: [], + anniversaries: [ + { + id: 'join', + title: '入团纪念日', + type: 'join', + date: '2025-10-01', + showLunar: false, + repeatYearly: true, + reminderEnabled: true, + reminderOffsetDays: 1, + note: '第一次加入粉丝团的日子', + }, + { + id: 'birthday', + title: '主播生日', + type: 'birthday', + date: '2026-08-18', + showLunar: false, + repeatYearly: true, + reminderEnabled: true, + reminderOffsetDays: 3, + linkedActivity: '生日祝福活动', + }, + { + id: 'debut', + title: '开播周年', + type: 'debut', + date: '2026-11-12', + showLunar: false, + repeatYearly: true, + reminderEnabled: true, + reminderOffsetDays: 7, + linkedActivity: '周年纪念卡池', + }, + ], + announcements: [ + { + id: 'notice-1', + title: '六月照顾任务开启', + body: '完成每日照顾可获得积分,积分上限仍为每日 80。', + }, + { + id: 'notice-2', + title: '商城低价值周边兑换中', + body: '钻石仅由后台活动发放,不开放充值或转赠。', + }, + ], + notifications: [ + { + id: 'notify-diamond', + type: 'diamond', + title: '钻石到账', + body: '后台活动奖励已发放 42 钻石,可前往商城兑换周边。', + createdAt: '2026-06-02 10:00', + read: false, + }, + { + id: 'notify-anniversary', + type: 'anniversary', + title: '主播生日提醒', + body: '主播生日即将到来,关联活动规则已开放查看。', + createdAt: '2026-06-02 10:30', + read: false, + }, + ], + supportTickets: [], + adminAuditLogs: [ + { + id: 'audit-diamond-grant', + operator: 'admin.moon', + action: 'grant_diamonds', + target: 'fan-001', + reason: '后台活动发放', + status: 'approved', + createdAt: '2026-06-02 10:00', + businessId: 'admin-grant-20260602', + }, + ], +} diff --git a/vtubermini/src/lib/pet-rules.ts b/vtubermini/src/lib/pet-rules.ts new file mode 100644 index 0000000..051c368 --- /dev/null +++ b/vtubermini/src/lib/pet-rules.ts @@ -0,0 +1,270 @@ +import { addAssetLedgerEntry } from './asset-ledger' +import type { MockState, PetActionResult, PetActionType } from './types' + +const DAILY_VISIT_POINTS = 10 +const DAILY_CARE_COMPLETE_POINTS = 5 +const FEED_DIAMOND_REWARD = 20 + +interface PetActionConfig { + label: string + pointReward: number + dailyLimit: number + deltas: Partial> +} + +export const petActionConfig: Record = { + feed: { + label: '喂食', + pointReward: FEED_DIAMOND_REWARD, + dailyLimit: 2, + deltas: { hunger: 12, energy: 4, intimacy: 2 }, + }, + pat: { + label: '抚摸', + pointReward: 7, + dailyLimit: 3, + deltas: { mood: 10, intimacy: 4 }, + }, + clean: { + label: '清洁', + pointReward: 10, + dailyLimit: 1, + deltas: { cleanliness: 16, mood: 2, intimacy: 2 }, + }, + play: { + label: '玩耍', + pointReward: 7, + dailyLimit: 2, + deltas: { mood: 8, energy: -8, intimacy: 5 }, + }, +} + +export function applyDailyPetVisit(state: MockState): PetActionResult { + if (state.pet.dailyLoginSeen) { + return { + nextState: state, + message: '今天已经看过小狗了,继续照顾会增加亲密度。', + tone: 'info', + } + } + + const awardedPoints = getAwardedPoints(state, DAILY_VISIT_POINTS) + const stateWithPet = { + ...state, + pet: { + ...state.pet, + dailyLoginSeen: true, + dailyPointsEarned: state.pet.dailyPointsEarned + awardedPoints, + streakDays: state.pet.streakDays + 1, + totalCareDays: state.pet.totalCareDays + 1, + }, + } + + if (awardedPoints === 0) { + return { + nextState: stateWithPet, + message: '小狗今天也等到你了;今日积分已达上限。', + tone: 'info', + } + } + + return { + nextState: addAssetLedgerEntry(stateWithPet, { + assetType: 'diamonds', + businessId: `pet-visit-${Date.now()}`, + change: awardedPoints, + reason: '登录看小狗', + sourceType: 'pet', + }), + message: `小狗听见脚步声抬起头,获得 ${awardedPoints} 积分。`, + tone: 'success', + } +} + +export function applyPetAction(state: MockState, action: PetActionType): PetActionResult { + const config = petActionConfig[action] + const usedCount = state.pet.dailyActionCounts[action] + const isFeedAction = action === 'feed' + + if (usedCount >= config.dailyLimit && !isFeedAction) { + return { + nextState: state, + message: `今天的${config.label}次数已用完,明天再来照顾小狗。`, + tone: 'warning', + } + } + + const awardedPoints = isFeedAction ? FEED_DIAMOND_REWARD : getAwardedPoints(state, config.pointReward) + const nextCounts = { + ...state.pet.dailyActionCounts, + [action]: usedCount + 1, + } + const nextStats = applyStatDeltas(state.pet, config.deltas) + const updatedPet = { + ...state.pet, + ...nextStats, + intimacyLevel: getIntimacyLevel(nextStats.intimacy), + dailyPointsEarned: state.pet.dailyPointsEarned + (isFeedAction ? 0 : awardedPoints), + dailyActionCounts: nextCounts, + } + + let nextState = { + ...state, + pet: updatedPet, + } + + if (awardedPoints > 0) { + nextState = addAssetLedgerEntry(nextState, { + assetType: 'diamonds', + businessId: `pet-${action}-${Date.now()}`, + change: awardedPoints, + reason: `宠物互动:${config.label}`, + sourceType: 'pet', + }) + } + + const completion = maybeApplyDailyCareCompletion(nextState) + nextState = completion.nextState + + if (awardedPoints === 0 && completion.awardedPoints === 0) { + return { + nextState, + message: `${getActionFeedback(action)} 今日积分已达上限,继续陪它会增加亲密度。`, + tone: 'info', + } + } + + const totalPoints = awardedPoints + completion.awardedPoints + const completionText = completion.awardedPoints > 0 ? ' 今日照顾已完成,明天它还会等你。' : '' + + return { + nextState, + message: isFeedAction + ? `小家伙吃饱饱,获得 ${FEED_DIAMOND_REWARD} 钻石。${completionText}` + : `${getActionFeedback(action)} 获得 ${totalPoints} 钻石。${completionText}`, + tone: 'success', + } +} + +export function getIntimacyLevel(intimacy: number) { + return Math.min(10, Math.max(1, Math.floor(intimacy / 10) + 1)) +} + +export function getIntimacyTitle(level: number) { + if (level >= 9) { + return '贴身守护' + } + if (level >= 7) { + return '熟悉的陪伴' + } + if (level >= 4) { + return '慢慢靠近' + } + return '初次相遇' +} + +export function getRecommendedPetAction(pet: MockState['pet']) { + const candidates: Array<{ action: PetActionType; label: string; value: number }> = [ + { action: 'feed', label: '喂食', value: pet.hunger }, + { action: 'pat', label: '抚摸', value: pet.mood }, + { action: 'clean', label: '清洁', value: pet.cleanliness }, + { action: 'play', label: '玩耍', value: pet.energy }, + ] + const lowest = candidates.reduce((current, item) => (item.value < current.value ? item : current)) + + return { + action: lowest.action, + label: lowest.label, + message: getRecommendationMessage(lowest.action), + } +} + +export function getPetMoodLine(pet: MockState['pet']) { + const recommended = getRecommendedPetAction(pet) + + if (recommended.action === 'clean') { + return '毛毛有点乱,想被整理一下。' + } + if (recommended.action === 'feed') { + return '它叼着小碗看向你。' + } + if (recommended.action === 'pat') { + return '今天有点安静,适合轻轻摸摸。' + } + return '它还想再玩一会儿。' +} + +function applyStatDeltas( + pet: MockState['pet'], + deltas: PetActionConfig['deltas'], +): Pick { + return { + hunger: clampStat(pet.hunger + (deltas.hunger ?? 0)), + mood: clampStat(pet.mood + (deltas.mood ?? 0)), + cleanliness: clampStat(pet.cleanliness + (deltas.cleanliness ?? 0)), + energy: clampStat(pet.energy + (deltas.energy ?? 0)), + intimacy: clampStat(pet.intimacy + (deltas.intimacy ?? 0)), + } +} + +function clampStat(value: number) { + return Math.min(100, Math.max(0, value)) +} + +function getAwardedPoints(state: MockState, requestedPoints: number) { + const remainingPoints = Math.max(0, state.pet.dailyPointsCap - state.pet.dailyPointsEarned) + return Math.min(requestedPoints, remainingPoints) +} + +function maybeApplyDailyCareCompletion(state: MockState) { + if (state.pet.dailyCareCompleted || !isDailyCareComplete(state.pet.dailyActionCounts)) { + return { nextState: state, awardedPoints: 0 } + } + + const awardedPoints = getAwardedPoints(state, DAILY_CARE_COMPLETE_POINTS) + const stateWithPet = { + ...state, + pet: { + ...state.pet, + dailyCareCompleted: true, + dailyPointsEarned: state.pet.dailyPointsEarned + awardedPoints, + }, + } + + if (awardedPoints === 0) { + return { nextState: stateWithPet, awardedPoints } + } + + return { + nextState: addAssetLedgerEntry(stateWithPet, { + assetType: 'diamonds', + businessId: `pet-care-complete-${Date.now()}`, + change: awardedPoints, + reason: '完成全部每日照顾', + sourceType: 'pet', + }), + awardedPoints, + } +} + +function isDailyCareComplete(counts: MockState['pet']['dailyActionCounts']) { + return (Object.keys(petActionConfig) as PetActionType[]).every((type) => counts[type] >= petActionConfig[type].dailyLimit) +} + +function getActionFeedback(action: PetActionType) { + return { + feed: '小狗叼着小点心蹭了蹭你。', + pat: '它安静地靠近了一点,尾巴轻轻晃着。', + clean: '毛毛重新亮起来了,今天也保持清爽。', + play: '它追着月光跑了一圈,看起来开心多了。', + }[action] +} + +function getRecommendationMessage(action: PetActionType) { + return { + feed: '今天优先:喂食', + pat: '它想被摸摸', + clean: '今天优先:清洁', + play: '今天优先:玩耍', + }[action] +} diff --git a/vtubermini/src/lib/platform.ts b/vtubermini/src/lib/platform.ts new file mode 100644 index 0000000..30d13a3 --- /dev/null +++ b/vtubermini/src/lib/platform.ts @@ -0,0 +1,22 @@ +export function shouldUseStaticBackgroundMedia() { + if (typeof window === 'undefined') { + return false + } + + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + + return prefersReducedMotion +} + +export function shouldUseWebPhonePreview() { + if (typeof window === 'undefined') { + return false + } + + const capacitor = (window as Window & { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor + if (capacitor?.isNativePlatform?.()) { + return false + } + + return !window.navigator.userAgent.toLowerCase().includes(' wv') +} diff --git a/vtubermini/src/lib/shop-rules.ts b/vtubermini/src/lib/shop-rules.ts new file mode 100644 index 0000000..0be1e03 --- /dev/null +++ b/vtubermini/src/lib/shop-rules.ts @@ -0,0 +1,79 @@ +import { addAssetLedgerEntry } from './asset-ledger' +import type { ExchangeInput, ExchangeOrder, ExchangeResult, MockState } from './types' + +export function exchangeProduct(state: MockState, input: ExchangeInput): ExchangeResult { + const product = state.products.find((item) => item.id === input.productId) + + if (!product) { + return { + nextState: state, + message: '商品不存在或已下架。', + success: false, + } + } + + if (!input.receiverName.trim() || !input.phone.trim() || !input.address.trim()) { + return { + nextState: state, + message: '请完整填写收货人、手机号和收货地址。', + success: false, + } + } + + if (state.user.points < product.diamondPrice) { + return { + nextState: state, + message: `积分不足,需要 ${product.diamondPrice},当前只有 ${state.user.points}。`, + success: false, + } + } + + if (product.stock <= 0) { + return { + nextState: state, + message: '库存不足,暂时无法兑换。', + success: false, + } + } + + const order: ExchangeOrder = { + id: `order-${Date.now()}-${state.orders.length + 1}`, + productId: product.id, + productName: product.name, + diamondCost: product.diamondPrice, + status: 'pendingShipment', + receiverName: input.receiverName.trim(), + phone: input.phone.trim(), + address: input.address.trim(), + createdAt: formatDateTime(new Date()), + shippingRule: product.shippingRule, + } + + const products = state.products.map((item) => (item.id === product.id ? { ...item, stock: item.stock - 1 } : item)) + const stateWithProduct = { + ...state, + products, + orders: [order, ...state.orders], + } + + const nextState = addAssetLedgerEntry(stateWithProduct, { + assetType: 'points', + businessId: order.id, + change: -product.diamondPrice, + reason: `商城兑换:${product.name}`, + sourceType: 'shop', + }) + + return { + nextState, + message: '兑换成功,订单已进入待发货。', + order, + success: true, + } +} + +function formatDateTime(date: Date) { + const dateText = date.toISOString().slice(0, 10) + const timeText = date.toTimeString().slice(0, 5) + return `${dateText} ${timeText}` +} diff --git a/vtubermini/src/lib/types.ts b/vtubermini/src/lib/types.ts new file mode 100644 index 0000000..6dc1bb8 --- /dev/null +++ b/vtubermini/src/lib/types.ts @@ -0,0 +1,292 @@ +export type AppRoute = + | 'home' + | 'pet' + | 'gacha' + | 'shop' + | 'my' + | 'anniversary' + | 'activity' + | 'moments' + | 'album' + | 'productDetail' + | 'orders' + | 'pointsLedger' + | 'diamondLedger' + | 'gachaLog' + | 'rules' + | 'support' + | 'notifications' + | 'admin' + +export type Rarity = 'N' | 'R' | 'SR' | 'SSR' + +export type PetActionType = 'feed' | 'pat' | 'clean' | 'play' + +export type AssetType = 'points' | 'diamonds' | 'shards' + +export type FeedbackTone = 'success' | 'warning' | 'info' + +export type HomeBackgroundMode = 'auto' | 'day' | 'night' | 'birthday' + +export type HomeCoverId = 'xingyao-shengyu' | 'chiying-xingge' | 'xingye-xiehou' + +export type HomeLayoutElementId = + | 'profileLevel' + | 'profile' + | 'homeAnniversaryAsset' + | 'anniversaryTitle' + | 'anniversaryStatus' + | 'anniversaryDays' + | 'homeIconAnniversary' + | 'homeIconSupport' + | 'homeIconActivity' + | 'homeIconVote' + | 'homeIconShop' + | 'homeIconMoments' + | 'homeMomentsNewBadge' + | 'homeIconPet' + | 'homeIconAlbum' + +export interface HomeLayoutRect { + left: number + top: number + width: number + height: number +} + +export type HomeLayout = Record + +export interface AnniversaryImageLayout { + width: number + height: number +} + +export interface PetActionResult { + nextState: MockState + message: string + tone: FeedbackTone +} + +export type GachaDrawMode = 'single' | 'ten' + +export interface GachaDrawResult { + card: CardItem + duplicate: boolean + convertedPoints: number +} + +export interface GachaResult { + nextState: MockState + results: GachaDrawResult[] + message: string + success: boolean + totalCost: number +} + +export interface ExchangeInput { + productId: string + receiverName: string + phone: string + address: string +} + +export interface ExchangeResult { + nextState: MockState + message: string + order?: ExchangeOrder + success: boolean +} + +export interface AnniversaryInput { + id?: string + title: string + date: string + showLunar?: boolean + imageUrl?: string + repeatYearly: boolean + reminderEnabled: boolean + reminderOffsetDays: number + note: string +} + +export interface UserProfile { + id: string + name: string + level: number + points: number + diamonds: number + birthday?: string +} + +export interface PetState { + name: string + hunger: number + mood: number + cleanliness: number + energy: number + intimacy: number + intimacyLevel: number + dailyPointsEarned: number + dailyPointsCap: number + dailyActionCounts: Record + dailyLoginSeen: boolean + dailyCareCompleted: boolean + streakDays: number + totalCareDays: number +} + +export interface AssetLedgerEntry { + id: string + assetType: AssetType + change: number + reason: string + sourceType: string + businessId: string + createdAt: string + balanceAfter: number +} + +export interface CardItem { + id: string + title: string + series: string + rarity: Rarity + owned: boolean + acquiredAt?: string + duplicateCount: number +} + +export interface GachaPool { + name: string + version: string + costPoints: number + pityCurrent: number + pityTarget: number + duplicateRule: string + rates: Record +} + +export interface GachaLogEntry { + id: string + poolName: string + poolVersion: string + assetType?: Extract + costPoints: number + createdAt: string + results: Array<{ + cardId: string + title: string + rarity: Rarity + duplicate: boolean + convertedPoints: number + }> +} + +export interface ShopProduct { + id: string + name: string + image: string + spec: string + diamondPrice: number + stock: number + shippingRule: string + afterSalesRule: string + exchangeRule: string +} + +export type OrderStatus = 'pendingShipment' | 'shipped' | 'completed' | 'afterSales' | 'cancelled' | 'refunded' + +export interface ExchangeOrder { + id: string + productId: string + productName: string + diamondCost: number + status: OrderStatus + receiverName: string + phone: string + address: string + createdAt: string + shippingRule: string +} + +export interface Anniversary { + id: string + title: string + type: 'join' | 'birthday' | 'debut' | 'custom' + date: string + showLunar?: boolean + imageUrl?: string + repeatYearly: boolean + reminderEnabled: boolean + reminderOffsetDays: number + note?: string + linkedActivity?: string +} + +export interface Announcement { + id: string + title: string + body: string +} + +export type NotificationType = 'gacha' | 'diamond' | 'exchange' | 'order' | 'anniversary' + +export interface NotificationItem { + id: string + type: NotificationType + title: string + body: string + createdAt: string + read: boolean +} + +export type SupportTicketType = 'asset' | 'gacha' | 'exchange' | 'risk' + +export interface SupportTicket { + id: string + type: SupportTicketType + title: string + body: string + createdAt: string + status: 'submitted' | 'processing' | 'resolved' +} + +export interface AdminGrantInput { + assetType: Extract + amount: number + reason: string + operator: string +} + +export interface AdminGrantResult { + nextState: MockState + message: string + success: boolean +} + +export interface AdminAuditLog { + id: string + operator: string + action: string + target: string + reason: string + status: 'pending' | 'approved' | 'rejected' + createdAt: string + businessId: string +} + +export interface MockState { + user: UserProfile + pet: PetState + assetLedger: AssetLedgerEntry[] + cards: CardItem[] + gachaLogs: GachaLogEntry[] + pool: GachaPool + products: ShopProduct[] + orders: ExchangeOrder[] + anniversaries: Anniversary[] + announcements: Announcement[] + notifications: NotificationItem[] + supportTickets: SupportTicket[] + adminAuditLogs: AdminAuditLog[] +} diff --git a/vtubermini/src/main.tsx b/vtubermini/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/vtubermini/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/vtubermini/src/pages/activity-page.tsx b/vtubermini/src/pages/activity-page.tsx new file mode 100644 index 0000000..1f240d3 --- /dev/null +++ b/vtubermini/src/pages/activity-page.tsx @@ -0,0 +1,114 @@ +import { useState, type PointerEvent as ReactPointerEvent } from 'react' +import { ArrowLeft, ChevronLeft, ChevronRight } from 'lucide-react' +import activityImageOne from '../assets/desktop-assets/activity-image-1.png' +import activityImageTwo from '../assets/desktop-assets/activity-image-2.png' + +const activityImages = [ + { alt: 'DL.L9 年费专属福利 专属 CD 活动图', src: activityImageOne }, + { alt: 'DL.L9 新 3D 皮套动捕首秀邀请函活动图', src: activityImageTwo }, +] + +export function ActivityPage({ onBack }: { onBack: () => void }) { + const [activeIndex, setActiveIndex] = useState(0) + const [dragStartX, setDragStartX] = useState(null) + const activeImage = activityImages[activeIndex] + + function showPrevious() { + setActiveIndex((current) => (current === 0 ? activityImages.length - 1 : current - 1)) + } + + function showNext() { + setActiveIndex((current) => (current + 1) % activityImages.length) + } + + function handlePointerDown(event: ReactPointerEvent) { + setDragStartX(event.clientX) + event.currentTarget.setPointerCapture(event.pointerId) + } + + function handlePointerUp(event: ReactPointerEvent) { + if (dragStartX === null) { + return + } + + const distance = event.clientX - dragStartX + setDragStartX(null) + + if (Math.abs(distance) < 42) { + return + } + + if (distance > 0) { + showPrevious() + } else { + showNext() + } + } + + return ( +
+
+ +
+ {activeIndex + 1} / {activityImages.length} +
+
+ +
setDragStartX(null)} + onPointerDown={handlePointerDown} + onPointerUp={handlePointerUp} + > + {activeImage.alt} +
+ + {activityImages.length > 1 ? ( + <> + + +
+ {activityImages.map((image, index) => ( +
+ + ) : null} +
+ ) +} diff --git a/vtubermini/src/pages/album-page.tsx b/vtubermini/src/pages/album-page.tsx new file mode 100644 index 0000000..d6b66bb --- /dev/null +++ b/vtubermini/src/pages/album-page.tsx @@ -0,0 +1,572 @@ +import { ArrowLeft, Check } from 'lucide-react' +import { useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react' +import archiveReference from '../assets/desktop-assets/heart-archive/heart-archive-bg.png' +import coverXingye from '../assets/desktop-assets/heart-archive/archive-card-xingye-xiehou.png' +import coverChenguang from '../assets/desktop-assets/heart-archive/archive-card-chenguang-weilan.png' +import coverXingyao from '../assets/desktop-assets/heart-archive/archive-card-xingyao-shengyu.png' +import coverHuajian from '../assets/desktop-assets/heart-archive/archive-card-huajian-mansu.png' +import coverChiying from '../assets/desktop-assets/heart-archive/archive-card-chiying-xingge.png' +import coverMistNotes from '../assets/desktop-assets/heart-archive/archive-card-mist-notes.png' +import coverRainUmbrella from '../assets/desktop-assets/heart-archive/archive-card-rain-umbrella.png' +import coverSilentContract from '../assets/desktop-assets/heart-archive/archive-card-silent-contract.png' +import coverStarLab from '../assets/desktop-assets/heart-archive/archive-card-star-lab.png' +import coverStormSide from '../assets/desktop-assets/heart-archive/archive-card-storm-side.png' +import archiveTitleImage from '../assets/desktop-assets/heart-archive/heart-archive-title.png' +import rankA from '../assets/desktop-assets/heart-archive/rank-a.png' +import rankB from '../assets/desktop-assets/heart-archive/rank-b.png' +import rankC from '../assets/desktop-assets/heart-archive/rank-c.png' +import rankSr from '../assets/desktop-assets/heart-archive/rank-sr.png' +import rankSsr from '../assets/desktop-assets/heart-archive/rank-ssr.png' +import chiyingDetail from '../assets/desktop-assets/heart-archive/detail-赤影行歌.png' +import xingyeDetail from '../assets/desktop-assets/heart-archive/detail-星夜邂逅.png' +import xingyaoDetail from '../assets/desktop-assets/heart-archive/detail-星耀圣羽.png' +import chenguangDetailVideo from '../assets/desktop-assets/heart-archive/detail-chenguang-weilan.mp4' +import chiyingDetailVideo from '../assets/desktop-assets/heart-archive/detail-chiying-xingge.mp4' +import huajianDetailVideo from '../assets/desktop-assets/heart-archive/detail-huajian-mansu.mp4' +import xingyeDetailVideo from '../assets/desktop-assets/heart-archive/detail-xingye-xiehou.mp4' +import xingyaoDetailVideo from '../assets/desktop-assets/heart-archive/detail-xingyao-shengyu.mp4' +import type { HomeCoverId, MockState } from '../lib/types' + +type ArchiveRank = 'five' | 'four' | 'three' | 'two' | 'one' +type ArchiveRankLabel = 'SSR' | 'SR' | 'A' | 'B' | 'C' + +interface ArchiveCard { + id: string + cardId: string + title: string + subtitle: string + rank: ArchiveRank + rankLabel: ArchiveRankLabel + stars: number + owned: boolean + tone: string + coverImage?: string + detailImage?: string + detailVideo?: string + homeCoverId?: HomeCoverId +} + +interface FilterTab { + id: 'all' | ArchiveRank + label: string +} + +const detailImages: Record = { + 星耀圣羽: xingyaoDetail, + 赤影行歌: chiyingDetail, + 星夜邂逅: xingyeDetail, +} + +const detailVideos: Record = { + 星耀圣羽: xingyaoDetailVideo, + 赤影行歌: chiyingDetailVideo, + 星夜邂逅: xingyeDetailVideo, + 晨光微澜: chenguangDetailVideo, + 花间漫溯: huajianDetailVideo, +} + +const coverImages: Record = { + 星耀圣羽: coverXingyao, + 赤影行歌: coverChiying, + 星夜邂逅: coverXingye, + 晨光微澜: coverChenguang, + 花间漫溯: coverHuajian, +} + +const rankImages: Record = { + SSR: rankSsr, + SR: rankSr, + A: rankA, + B: rankB, + C: rankC, +} + +const detailAssetsWithBuiltInBack = new Set(['星耀圣羽', '赤影行歌', '星夜邂逅']) + +const archiveCards: ArchiveCard[] = [ + { + id: 'archive-001', + cardId: 'ARCH-SSR-001', + title: '星耀圣羽', + subtitle: '天使降临', + rank: 'five', + rankLabel: 'SSR', + stars: 5, + owned: false, + tone: 'from-[#eaf5ff] via-[#8fb4e8] to-[#13233f]', + coverImage: coverImages.星耀圣羽, + detailImage: detailImages.星耀圣羽, + detailVideo: detailVideos.星耀圣羽, + homeCoverId: 'xingyao-shengyu', + }, + { + id: 'archive-002', + cardId: 'ARCH-SSR-002', + title: '赤影行歌', + subtitle: '暗夜邀约', + rank: 'five', + rankLabel: 'SSR', + stars: 5, + owned: false, + tone: 'from-[#f4e7dc] via-[#6f2530] to-[#0c0c14]', + coverImage: coverImages.赤影行歌, + detailImage: detailImages.赤影行歌, + detailVideo: detailVideos.赤影行歌, + homeCoverId: 'chiying-xingge', + }, + { + id: 'archive-003', + cardId: 'ARCH-SSR-003', + title: '星夜邂逅', + subtitle: '星辰之约', + rank: 'five', + rankLabel: 'SSR', + stars: 5, + owned: false, + tone: 'from-[#e6f4ff] via-[#245da8] to-[#071529]', + coverImage: coverImages.星夜邂逅, + detailImage: detailImages.星夜邂逅, + detailVideo: detailVideos.星夜邂逅, + homeCoverId: 'xingye-xiehou', + }, + { + id: 'archive-004', + cardId: 'ARCH-SR-001', + title: '晨光微澜', + subtitle: '花影西装', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#fff6e5] via-[#7aa0ce] to-[#17223b]', + coverImage: coverImages.晨光微澜, + detailVideo: detailVideos.晨光微澜, + }, + { + id: 'archive-005', + cardId: 'ARCH-SR-002', + title: '花间漫溯', + subtitle: '午后庭院', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#fff0d8] via-[#8db2df] to-[#1e456f]', + coverImage: coverImages.花间漫溯, + detailVideo: detailVideos.花间漫溯, + }, + { + id: 'archive-021', + cardId: 'ARCH-SR-003', + title: '风暴侧影', + subtitle: '黑夜行装', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#edf2ff] via-[#536684] to-[#080c16]', + coverImage: coverStormSide, + detailImage: coverStormSide, + }, + { + id: 'archive-022', + cardId: 'ARCH-SR-004', + title: '静默契约', + subtitle: '深蓝西装', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#eef4ff] via-[#65758f] to-[#111728]', + coverImage: coverSilentContract, + detailImage: coverSilentContract, + }, + { + id: 'archive-023', + cardId: 'ARCH-SR-005', + title: '雾中手记', + subtitle: '雨夜笔记', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#e8f1ff] via-[#526b8d] to-[#0e1321]', + coverImage: coverMistNotes, + detailImage: coverMistNotes, + }, + { + id: 'archive-024', + cardId: 'ARCH-SR-006', + title: '星云实验', + subtitle: '白衣研究', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#f7fbff] via-[#788cae] to-[#18213a]', + coverImage: coverStarLab, + detailImage: coverStarLab, + }, + { + id: 'archive-025', + cardId: 'ARCH-SR-007', + title: '雨幕伞影', + subtitle: '夜雨同行', + rank: 'four', + rankLabel: 'SR', + stars: 4, + owned: false, + tone: 'from-[#edf3ff] via-[#4d5c75] to-[#070910]', + coverImage: coverRainUmbrella, + detailImage: coverRainUmbrella, + }, +] + +const filterTabs: FilterTab[] = [ + { id: 'all', label: '全部' }, + { id: 'five', label: '五星' }, + { id: 'four', label: '四星' }, +] + +interface AlbumPageProps { + onBack: () => void + onSetHomeCover: (coverId: HomeCoverId) => void + state: MockState +} + +export function AlbumPage({ onBack, onSetHomeCover, state }: AlbumPageProps) { + const [activeFilter, setActiveFilter] = useState('all') + const [selectedCard, setSelectedCard] = useState(null) + const [toast, setToast] = useState('') + const tabDragRef = useRef({ + isDragging: false, + pointerId: -1, + scrollLeft: 0, + startX: 0, + }) + const suppressTabClickRef = useRef(false) + + const displayedCards = useMemo(() => { + const ownedCardIds = new Set(state.cards.filter((card) => card.owned).map((card) => card.id)) + return archiveCards.map((card) => ({ + ...card, + owned: ownedCardIds.has(card.cardId), + })) + }, [state.cards]) + + const filteredCards = useMemo( + () => (activeFilter === 'all' ? displayedCards : displayedCards.filter((card) => card.rank === activeFilter)), + [activeFilter, displayedCards], + ) + + function showToast(message: string) { + setToast(message) + window.setTimeout(() => setToast(''), 1600) + } + + function handleTabPointerDown(event: ReactPointerEvent) { + if (event.pointerType === 'mouse' && event.button !== 0) { + return + } + + tabDragRef.current = { + isDragging: false, + pointerId: event.pointerId, + scrollLeft: event.currentTarget.scrollLeft, + startX: event.clientX, + } + suppressTabClickRef.current = false + } + + function handleTabPointerMove(event: ReactPointerEvent) { + const drag = tabDragRef.current + if (drag.pointerId !== event.pointerId) { + return + } + + const deltaX = event.clientX - drag.startX + if (Math.abs(deltaX) <= 8 && !drag.isDragging) { + return + } + + if (!drag.isDragging && !event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.setPointerCapture(event.pointerId) + } + drag.isDragging = true + suppressTabClickRef.current = true + event.currentTarget.scrollLeft = drag.scrollLeft - deltaX + event.preventDefault() + } + + function handleTabPointerEnd(event: ReactPointerEvent) { + if (tabDragRef.current.pointerId !== event.pointerId) { + return + } + + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId) + } + tabDragRef.current.pointerId = -1 + window.setTimeout(() => { + suppressTabClickRef.current = false + }, 0) + } + + function handleFilterChange(tabId: FilterTab['id']) { + if (suppressTabClickRef.current) { + return + } + + setActiveFilter(tabId) + } + + async function handleCardClick(card: ArchiveCard) { + if (!card.owned) { + showToast('未获得此卡') + return + } + + if (card.detailImage || card.detailVideo) { + setSelectedCard(card) + return + } + + try { + await navigator.clipboard.writeText(`${card.title}|${card.rankLabel}|${card.subtitle}`) + showToast('已添加到剪贴板') + } catch { + showToast('当前浏览器不支持剪贴板') + } + } + + async function handleShare(card: ArchiveCard) { + const text = `${card.title}|${card.rankLabel}|${card.subtitle}` + + if (navigator.share) { + await navigator.share({ text, title: card.title }) + return + } + + await navigator.clipboard.writeText(text) + showToast('已复制分享文案') + } + + void handleShare + + if (selectedCard?.detailImage || selectedCard?.detailVideo) { + return ( + setSelectedCard(null)} + onSetCover={() => { + if (!selectedCard.homeCoverId) { + showToast('暂无可用封面') + return + } + + onSetHomeCover(selectedCard.homeCoverId) + showToast('已设为封面') + }} + toast={toast} + /> + ) + } + + return ( +
+ L9 的心动档案 +
+ + + +
+
+ L9 的心动档案 +
+ {filterTabs.map((tab) => { + const count = tab.id === 'all' ? displayedCards.length : displayedCards.filter((card) => card.rank === tab.id).length + const isActive = activeFilter === tab.id + + return ( + + ) + })} +
+
+ +
+
+ {filteredCards.map((card, index) => ( + handleCardClick(card)} /> + ))} +
+
+
+ + {toast ? : null} +
+ ) +} + +function ArchiveCardView({ card, index, onClick }: { card: ArchiveCard; index: number; onClick: () => void }) { + const rankImage = rankImages[card.rankLabel] + + return ( + + ) +} + +function ArchiveDetailPage({ + card, + onBack, + onSetCover, + toast, +}: { + card: ArchiveCard + onBack: () => void + onSetCover: () => void + toast: string +}) { + const hasBuiltInActionBar = detailAssetsWithBuiltInBack.has(card.title) + const shouldShowFloatingBack = !detailAssetsWithBuiltInBack.has(card.title) + return ( +
+
+ {card.detailVideo ? ( +
+ {toast ? : null} +
+ ) +} + +function Toast({ message }: { message: string }) { + return ( +
+ + {message} +
+ ) +} + +function renderStars(count: number) { + return `${'★'.repeat(count)}${'☆'.repeat(5 - count)}` +} diff --git a/vtubermini/src/pages/anniversary-page.tsx b/vtubermini/src/pages/anniversary-page.tsx new file mode 100644 index 0000000..d4fb554 --- /dev/null +++ b/vtubermini/src/pages/anniversary-page.tsx @@ -0,0 +1,272 @@ +import { useState, type ReactNode } from 'react' +import { CakeSlice, Gift, Sparkles, Tv } from 'lucide-react' +import { AnniversaryEditModal } from '../components/anniversary/anniversary-edit-modal' +import { + formatLunarDate, + getAnniversaryDays, + getAnniversaryStatusLabel, + getDaysLeft, + getElapsedDays, +} from '../lib/anniversary-rules' +import type { Anniversary, AnniversaryImageLayout, AnniversaryInput, MockState } from '../lib/types' +import anniversaryScreen from '../assets/desktop-assets/二级页面-纪念日页面.png' +import anniversaryDivider from '../assets/desktop-assets/二级页面-纪念日页面-隔断.png' + +interface AnniversaryPageProps { + state: MockState + onBack: () => void + onSaveCustom: (input: AnniversaryInput) => void + onUpdateJoinDate: (date: string) => void + anniversaryImageLayout: AnniversaryImageLayout + isLayoutEditing: boolean +} + +interface AnniversaryRowData { + title: string + days: number | null + date: string + lunarDate?: string + imageUrl?: string + statusLabel: string + accent: string + anniversary?: Anniversary +} + +export function AnniversaryPage({ + anniversaryImageLayout, + isLayoutEditing, + onBack, + onSaveCustom, + state, +}: AnniversaryPageProps) { + const [isAdding, setIsAdding] = useState(false) + const [editingAnniversary, setEditingAnniversary] = useState(null) + const joinDay = state.anniversaries.find((item) => item.type === 'join') + const debutDay = state.anniversaries.find((item) => item.type === 'debut') + const hostBirthday = state.anniversaries.find((item) => item.type === 'birthday') + const customDays = state.anniversaries.filter((item) => item.type === 'custom') + const joinedDays = joinDay ? getElapsedDays(joinDay.date) : 0 + const userBirthdayDays = state.user.birthday ? getYearlyDaysLeft(state.user.birthday) : null + + const rows: AnniversaryRowData[] = [] + if (debutDay) { + rows.push({ + title: '距离 DL.L9 直播周年', + days: getDaysLeft(debutDay), + date: formatDate(debutDay.date), + statusLabel: '还剩', + accent: 'tv', + }) + } + + if (hostBirthday) { + rows.push({ + title: '距离 DL.L9 生日', + days: getDaysLeft(hostBirthday), + date: formatDate(hostBirthday.date), + statusLabel: '还剩', + accent: 'cake', + }) + } + + rows.push({ + title: '距离我的生日', + days: userBirthdayDays, + date: state.user.birthday ? formatDate(state.user.birthday) : '未设置', + statusLabel: '还剩', + accent: 'gift', + }) + + rows.push( + ...customDays.map((item) => ({ + title: item.title, + days: getAnniversaryDays(item), + date: formatDate(item.date), + lunarDate: item.showLunar ? formatLunarDate(item.date) : undefined, + imageUrl: item.imageUrl, + statusLabel: getAnniversaryStatusLabel(item), + accent: 'rose', + anniversary: item, + })), + ) + + function handleSave(input: AnniversaryInput) { + onSaveCustom(input) + setIsAdding(false) + setEditingAnniversary(null) + } + + function handleCloseModal() { + setIsAdding(false) + setEditingAnniversary(null) + } + + return ( +
+
+ +

专属纪念日

+
+ +
+ 纪念日 + + +
+
+

加入 DL.L9 粉丝团已经

+

+ {joinedDays.toLocaleString('zh-CN')} +

+

+

+ {joinDay ? `${formatDate(joinDay.date)} - 至今` : '未配置'} +

+
+ +
+ {rows.map((row) => ( + + ))} +
+ + +
+
+ + {isAdding || editingAnniversary ? ( + + ) : null} +
+ ) +} + +function AnniversaryRow({ + imageLayout, + isLayoutEditing, + onEdit, + row, +}: { + imageLayout: AnniversaryImageLayout + isLayoutEditing: boolean + onEdit: (anniversary: Anniversary) => void + row: AnniversaryRowData +}) { + const isEditable = Boolean(row.anniversary) + const imageRadius = Math.min(17, Math.max(12, Math.min(imageLayout.width, imageLayout.height) * 0.29)) + + return ( +
{ + if (row.anniversary) { + onEdit(row.anniversary) + } + }} + role={isEditable ? 'button' : undefined} + style={{ + gridTemplateColumns: `minmax(0, 1.35fr) minmax(64px, 0.82fr) ${imageLayout.width}px`, + }} + tabIndex={isEditable ? 0 : undefined} + > +
+

+ {row.title} +

+

{row.statusLabel}

+
+
+

+ {row.days ?? '--'} + +

+

+ {row.lunarDate ? `${row.lunarDate} - ${row.date}` : row.date} +

+
+ + + {row.imageUrl ? ( + + ) : ( + getAccentIcon(row.accent) + )} + + {isLayoutEditing ? ( + + {Math.round(imageLayout.width)}×{Math.round(imageLayout.height)} + + ) : null} + +
+ ) +} + +function formatDate(dateText: string) { + return dateText.replaceAll('-', '.') +} + +function getYearlyDaysLeft(dateText: string) { + const original = new Date(`${dateText}T00:00:00`) + const today = new Date() + const candidate = new Date(today.getFullYear(), original.getMonth(), original.getDate()) + const todayDay = new Date(today.getFullYear(), today.getMonth(), today.getDate()) + + if (candidate < todayDay) { + candidate.setFullYear(candidate.getFullYear() + 1) + } + + return Math.ceil((candidate.getTime() - todayDay.getTime()) / 86_400_000) +} + +function getAccentIcon(accent: string): ReactNode { + return { + tv: , + cake: , + gift: , + rose: , + }[accent] +} diff --git a/vtubermini/src/pages/diamond-ledger-page.tsx b/vtubermini/src/pages/diamond-ledger-page.tsx new file mode 100644 index 0000000..b91ecf0 --- /dev/null +++ b/vtubermini/src/pages/diamond-ledger-page.tsx @@ -0,0 +1,43 @@ +import { DiamondCurrencyIcon } from '../components/common/currency-icon' +import { PageHeader } from '../components/common/page-header' +import { StateBlock } from '../components/common/state-block' +import type { AssetLedgerEntry } from '../lib/types' + +export function DiamondLedgerPage({ entries, onBack }: { entries: AssetLedgerEntry[]; onBack: () => void }) { + const diamondEntries = entries.filter((entry) => entry.assetType === 'diamonds') + + return ( +
+ + {diamondEntries.length === 0 ? ( + + ) : ( +
+ {diamondEntries.map((entry) => ( +
+
+
+

{entry.reason}

+

{entry.createdAt}

+

{entry.sourceType} · {entry.businessId}

+
+
+

= 0 ? 'text-[var(--deep-blue)]' : 'text-[#b4534b]'}`}> + + {entry.change >= 0 ? '+' : ''} + + {Math.abs(entry.change)} + +

+

+ 余额 {entry.balanceAfter} +

+
+
+
+ ))} +
+ )} +
+ ) +} diff --git a/vtubermini/src/pages/gacha-log-page.tsx b/vtubermini/src/pages/gacha-log-page.tsx new file mode 100644 index 0000000..97afd97 --- /dev/null +++ b/vtubermini/src/pages/gacha-log-page.tsx @@ -0,0 +1,48 @@ +import { DiamondCurrencyIcon, PointsCurrencyIcon } from '../components/common/currency-icon' +import { PageHeader } from '../components/common/page-header' +import { StateBlock } from '../components/common/state-block' +import type { GachaLogEntry } from '../lib/types' + +export function GachaLogPage({ logs, onBack }: { logs: GachaLogEntry[]; onBack: () => void }) { + return ( +
+ + {logs.length === 0 ? ( + + ) : ( +
+ {logs.map((log) => ( + + ))} +
+ )} +
+ ) +} + +function GachaLogCard({ log }: { log: GachaLogEntry }) { + const CostIcon = log.assetType === 'diamonds' ? DiamondCurrencyIcon : PointsCurrencyIcon + + return ( +
+

{log.poolName}

+

+ {log.createdAt} · 版本 {log.poolVersion} · 消耗 + + {log.costPoints} +

+
+ {log.results.map((result, index) => ( + + {result.rarity} {result.title} + {result.duplicate ? ( + + · 重复 + {result.convertedPoints} + + ) : null} + + ))} +
+
+ ) +} diff --git a/vtubermini/src/pages/gacha-page.tsx b/vtubermini/src/pages/gacha-page.tsx new file mode 100644 index 0000000..95090ec --- /dev/null +++ b/vtubermini/src/pages/gacha-page.tsx @@ -0,0 +1,50 @@ +import { useState } from 'react' +import { GachaActions } from '../components/gacha/gacha-actions' +import { GachaResultPage } from '../components/gacha/gacha-result-page' +import { PoolSummary } from '../components/gacha/pool-summary' +import { CardGrid } from '../components/album/card-grid' +import { PageHeader } from '../components/common/page-header' +import type { AppRoute, GachaDrawMode, GachaResult, MockState } from '../lib/types' + +interface GachaPageProps { + state: MockState + onDraw: (mode: GachaDrawMode) => GachaResult + onNavigate: (route: AppRoute) => void + onBack: () => void +} + +export function GachaPage({ onBack, onDraw, onNavigate, state }: GachaPageProps) { + const [result, setResult] = useState(null) + + function handleDraw(mode: GachaDrawMode) { + setResult(onDraw(mode)) + } + + if (result) { + return setResult(null)} result={result} /> + } + + return ( +
+
+ + +
+ + + + +
+
+

卡牌预览

+ 已拥有 {state.cards.filter((card) => card.owned).length}/{state.cards.length} +
+
+ +
+
+
+ ) +} diff --git a/vtubermini/src/pages/heart-moments-page.tsx b/vtubermini/src/pages/heart-moments-page.tsx new file mode 100644 index 0000000..333abb7 --- /dev/null +++ b/vtubermini/src/pages/heart-moments-page.tsx @@ -0,0 +1,213 @@ +import { ArrowLeft } from 'lucide-react' +import { useState } from 'react' +import { DiamondCurrencyIcon } from '../components/common/currency-icon' +import { HeartMomentCardReveal } from '../components/gacha/heart-moment-card-reveal' +import { HeartMomentDrawVideo } from '../components/gacha/heart-moment-draw-video' +import { GachaResultPage } from '../components/gacha/gacha-result-page' +import drawTenVideo from '../assets/desktop-assets/heart-moment-draw-ten.mp4' +import resultIntroVideo from '../assets/desktop-assets/heart-moment-result-intro.mp4' +import resultLoopVideo from '../assets/desktop-assets/heart-moment-result-loop.mp4' +import momentLoopVideo from '../assets/desktop-assets/抽卡界面-循环2.mp4' +import drawTenImage from '../assets/desktop-assets/抽卡界面2-UI-连抽.png' +import drawSingleImage from '../assets/desktop-assets/抽卡界面2-UI-单抽.png' +import momentDescriptionImage from '../assets/desktop-assets/抽卡界面2-UI-中间描述.png' +import ssrBadgeImage from '../assets/desktop-assets/抽卡界面2-UI-ssr标.png' +import collectButtonImage from '../assets/desktop-assets/heart-moment-reveal/heart-moment-collect-button.png' +import type { GachaDrawMode, GachaResult, MockState } from '../lib/types' + +interface HeartMomentsPageProps { + state: MockState + onBack: () => void + onDraw: (mode: GachaDrawMode) => GachaResult +} + +export function HeartMomentsPage({ onBack, onDraw, state }: HeartMomentsPageProps) { + const [result, setResult] = useState(null) + const [drawVideoMode, setDrawVideoMode] = useState(null) + const [resultIntroPending, setResultIntroPending] = useState(false) + const [resultLoopPending, setResultLoopPending] = useState(false) + const [revealIndex, setRevealIndex] = useState(0) + const canSingle = state.user.diamonds >= 10 + const canTen = state.user.diamonds >= 100 + + function handleDraw(mode: GachaDrawMode) { + const drawResult = onDraw(mode) + const hasResult = drawResult.success && drawResult.results.length > 0 + const shouldPlayVideos = hasResult && mode === 'ten' + setResult(drawResult) + setDrawVideoMode(shouldPlayVideos ? mode : null) + setResultIntroPending(shouldPlayVideos) + setResultLoopPending(false) + setRevealIndex(hasResult ? 0 : drawResult.results.length) + } + + function handleDrawVideoFinish() { + setDrawVideoMode(null) + setRevealIndex(0) + } + + function showResultPage() { + setRevealIndex(result?.results.length ?? 0) + } + + function handleResultIntroFinish() { + setResultIntroPending(false) + setResultLoopPending(true) + } + + function handleResultLoopFinish() { + handleBackToGacha() + } + + function handleBackToGacha() { + setResult(null) + setDrawVideoMode(null) + setResultIntroPending(false) + setResultLoopPending(false) + setRevealIndex(0) + } + + function handleRevealNext() { + if (!result) { + return + } + + if (revealIndex + 1 >= result.results.length) { + showResultPage() + return + } + + setRevealIndex((current) => current + 1) + } + + if (result) { + if (drawVideoMode) { + return ( + + ) + } + + const revealItem = result.results[revealIndex] + + if (revealItem) { + return ( + + ) + } + + if (resultIntroPending && result.success && result.results.length > 0) { + return + } + + if (resultLoopPending && result.success && result.results.length > 0) { + return ( + + ) + } + + return ( + + ) + } + + return ( +
+