[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: YunYouJun\ncustom: https://sponsors.yunyoujun.cn\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - dev\n\n  pull_request:\n    branches:\n      - main\n      - dev\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2\n\n      - name: Set node\n        uses: actions/setup-node@v3\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install\n        run: pnpm install\n\n      - name: Lint\n        run: pnpm run lint\n\n  typecheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2\n\n      - name: Set node\n        uses: actions/setup-node@v3\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install\n        run: pnpm install\n\n      - name: Convert CSV to JSON\n        run: npm run convert\n\n      - name: Typecheck\n        run: pnpm run typecheck\n\n  test:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        node-version: [lts/*]\n        os: [ubuntu-latest]\n      fail-fast: false\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: pnpm/action-setup@v2\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - run: pnpm install\n      - run: pnpm run convert\n      - run: pnpm run test\n"
  },
  {
    "path": ".github/workflows/docker-image.yml",
    "content": "# Build and Publish\nname: Docker Image\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check Out Repo\n        uses: actions/checkout@main\n      - name: Docker meta\n        id: meta\n        uses: docker/metadata-action@master\n        with:\n          images: ${{ secrets.DOCKER_HUB_USERNAME }}/cook\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n            # set latest tag for main branch\n            type=raw,value=latest\n      - name: Login to Docker Hub\n        uses: docker/login-action@master\n        with:\n          username: ${{ secrets.DOCKER_HUB_USERNAME }}\n          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}\n      - name: Set up Docker Buildx\n        id: buildx\n        uses: docker/setup-buildx-action@master\n      - name: Build and Push\n        id: docker_build\n        uses: docker/build-push-action@master\n        with:\n          context: ./\n          file: ./Dockerfile\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n      - name: Image digest\n        run: echo ${{ steps.docker_build.outputs.digest }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v3\n\n      # after pnpm\n      - name: Use Node.js LTS\n        uses: actions/setup-node@v4\n        with:\n          node-version: lts/*\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Build\n        run: pnpm build\n\n      # deploy to edgeone\n      # see https://edgeone.ai/zh/document/180255338996572160?product=edgedeveloperplatform\n      - name: Deploy to EdgeOne Pages\n        run: npx edgeone pages deploy dist -n cook-yunyoujun-cn -t ${{ secrets.EDGEONE_API_TOKEN }}\n        env:\n          EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }}\n\n      - name: Release\n        uses: softprops/action-gh-release@v1\n        if: startsWith(github.ref, 'refs/tags/')\n        with:\n          files: dist\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: Cook Dist\n          path: dist\n\n      - run: npx changelogithub # or changelogithub@0.12 if ensure the stable result\n        env:\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".gitignore",
    "content": "# auto generate\napp/data/recipe.json\napp/data/incompatible-foods.json\n\n.DS_Store\n.vite-ssg-dist\n.vite-ssg-temp\n*.local\n\nesbuild-kit\ntsx-*\n\n# nuxt\nnode_modules\n*.log\ndist\n.output\n.nuxt\n.env\n.idea/\n.vercel\n"
  },
  {
    "path": ".npmrc",
    "content": "shamefully-hoist=true\nstrict-peer-dependencies=false\nshell-emulator=true\nauto-install-peers=false\nignore-workspace-root-check=true\n"
  },
  {
    "path": ".nuxtrc",
    "content": "setups.@nuxt/test-utils=\"4.0.0\""
  },
  {
    "path": ".nvmrc",
    "content": "v22.20.0\n"
  },
  {
    "path": ".stackblitzrc",
    "content": "{\n  \"installDependencies\": true,\n  \"startCommand\": \"npm run dev\"\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"antfu.iconify\",\n    \"antfu.unocss\",\n    \"antfu.goto-alias\",\n    \"csstools.postcss\",\n    \"dbaeumer.vscode-eslint\",\n    \"vue.volar\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"cSpell.words\": [\n    \"antfu\",\n    \"demi\",\n    \"iconify\",\n    \"intlify\",\n    \"nuxi\",\n    \"pinia\",\n    \"pnpm\",\n    \"unocss\",\n    \"unplugin\",\n    \"Vite\",\n    \"vitejs\",\n    \"Vitesse\",\n    \"vitest\",\n    \"vueuse\"\n  ],\n  \"files.associations\": {\n    \"*.css\": \"postcss\"\n  },\n\n  // Disable the default formatter, use eslint instead\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n  // Auto fix\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\",\n    \"source.organizeImports\": \"never\"\n  },\n  // Silent the stylistic rules in you IDE, but still auto fix them\n  \"eslint.rules.customizations\": [\n    { \"rule\": \"style/*\", \"severity\": \"off\" },\n    { \"rule\": \"*-indent\", \"severity\": \"off\" },\n    { \"rule\": \"*-spacing\", \"severity\": \"off\" },\n    { \"rule\": \"*-spaces\", \"severity\": \"off\" },\n    { \"rule\": \"*-order\", \"severity\": \"off\" },\n    { \"rule\": \"*-dangle\", \"severity\": \"off\" },\n    { \"rule\": \"*-newline\", \"severity\": \"off\" },\n    { \"rule\": \"*quotes\", \"severity\": \"off\" },\n    { \"rule\": \"*semi\", \"severity\": \"off\" }\n  ],\n\n  // Enable eslint for all supported languages\n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"typescript\",\n    \"typescriptreact\",\n    \"vue\",\n    \"html\",\n    \"markdown\",\n    \"json\",\n    \"jsonc\",\n    \"yaml\"\n  ],\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:lts-alpine AS builder\n\nRUN apk update\nRUN npm install -g pnpm\n\nWORKDIR /app\nCOPY . .\n\nRUN pnpm install && pnpm run build\n\nFROM nginx:stable-alpine\nCOPY --from=builder /app/dist /usr/share/nginx/html\nEXPOSE 80\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 云游君 YunYouJun <me@yunyoujun.cn>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Cook\n\n> 好的，今天我们来做菜 🥬\n> Note: This is primarily a Chinese project and we do not intend to translate to English due to the fact that all the ingredients we are familiar with are in Chinese.\n\n## 版本\n\n[![Release](https://github.com/YunYouJun/cook/actions/workflows/release.yml/badge.svg)](https://github.com/YunYouJun/cook/actions/workflows/release.yml)\n\n### 网页版本\n\n- 网站链接（Cloudflare）：[cook.yunyoujun.cn](https://cook.yunyoujun.cn)\n  - 国内加速（腾讯云）：[cook.yunle.fun](https://cook.yunle.fun)\n- 备用（Netlify）：[cook.yyj.moe](https://cook.yyj.moe)\n- 开发版（Vercel）：[cook.yunle.app](https://cook.yunle.app)\n\n### 小程序版本\n\n~~本仓库为网页版本，小程序版本请在微信搜索「来做菜」。~~\n\n因不可抗力，小程序因跳转 B 站视频而被判定为导流违规下架。\n将不再提供小程序版本。\n\n<!-- ![微信小程序版本](./public/search-cook.png) -->\n\n## 说明\n\n本项目初衷是方便特殊时期隔离在家而材料有限的小伙伴，因此菜谱材料会尽量限制在特定范围内。\n\n更多可参见 [来做菜 | 关于](https://cook.yunyoujun.cn/about)。\n\n欢迎反馈更多菜谱数据：\n\n- 相关链接\n  - [居家菜谱投稿](https://docs.qq.com/form/page/DWk9GWW9oTmlXZU9V)\n  - [反馈建议分享-兔小巢](https://support.qq.com/products/507827)\n\n### Features\n\n~~本项目支持 PWA，使用浏览器打开时，可将其添加到主屏幕以获得近原生 APP 的体验。~~\n\n我们正在开发新的 APP 版本，敬请期待。\n\n## 开发\n\n```bash\n# install dependencies\npnpm install\n\n# convert csv to json\n# automatically executed when postinstall\npnpm convert\n\n# start\npnpm dev\n# http://localhost:3333\n```\n\n### 开发 APP\n\n> [Local Development](https://ionic.nuxtjs.org/cookbook/local-development)\n\n## 部署\n\n### Docker\n\n```bash\n# 从 Docker Hub 拉取最新的镜像\ndocker pull yunyoujun/cook:latest\n# 新建并启动容器，然后打开 http://localhost:3333\ndocker run -it -d --name cook -p 8080:80 yunyoujun/cook:latest\n\n# 启动与停止\ndocker start cook\ndocker stop cook\n```\n\n## 致谢\n\n感谢以下小伙伴为本项目提供的数据支持和 QA ！\n\n- [Runny](https://weibo.com/runny)\n- 麒麟\n- 晴方啾\n- 课代表阿伟\n\n## [Sponsors](https://sponsors.yunyoujun.cn)\n\n感谢至今以来的所有赞助者们！因为你们的支持让我更有动力去做各种尝试。\n\n<p align=\"center\">\n  <a href=\"https://cdn.jsdelivr.net/gh/YunYouJun/sponsors/public/sponsors.svg\">\n    <img src='https://cdn.jsdelivr.net/gh/YunYouJun/sponsors/public/sponsors.svg' alt='Sponsors'/>\n  </a>\n</p>\n"
  },
  {
    "path": "android/.gitignore",
    "content": "# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore\n\n# Built application files\n*.apk\n*.aar\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n#  Uncomment the following line in case you need and you don't have the release build type files in your app\n# release/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# IntelliJ\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/assetWizardSettings.xml\n.idea/dictionaries\n.idea/libraries\n# Android Studio 3 in .gitignore file.\n.idea/caches\n.idea/modules.xml\n# Comment next line if keeping position of elements in Navigation Editor is relevant for you\n.idea/navEditor.xml\n\n# Keystore files\n# Uncomment the following lines if you do not want to check your keystore files in.\n#*.jks\n#*.keystore\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n.cxx/\n\n# Google Services (e.g. APIs or Firebase)\n# google-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\nfastlane/readme.md\n\n# Version control\nvcs.xml\n\n# lint\nlint/intermediates/\nlint/generated/\nlint/outputs/\nlint/tmp/\n# lint/reports/\n\n# Android Profiling\n*.hprof\n\n# Cordova plugins for Capacitor\ncapacitor-cordova-android-plugins\n\n# Copied web assets\napp/src/main/assets/public\n\n# Generated Config files\napp/src/main/assets/capacitor.config.json\napp/src/main/assets/capacitor.plugins.json\napp/src/main/res/xml/config.xml\n"
  },
  {
    "path": "android/app/.gitignore",
    "content": "/build/*\n!/build/.npmkeep\n"
  },
  {
    "path": "android/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    namespace \"cn.yunyoujun.cook\"\n    compileSdk rootProject.ext.compileSdkVersion\n    defaultConfig {\n        applicationId \"cn.yunyoujun.cook\"\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 2\n        versionName \"1.0.1\"\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        aaptOptions {\n             // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.\n             // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61\n            ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'\n        }\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\nrepositories {\n    flatDir{\n        dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'\n    }\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation \"androidx.appcompat:appcompat:$androidxAppCompatVersion\"\n    implementation \"androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion\"\n    implementation \"androidx.core:core-splashscreen:$coreSplashScreenVersion\"\n    implementation project(':capacitor-android')\n    testImplementation \"junit:junit:$junitVersion\"\n    androidTestImplementation \"androidx.test.ext:junit:$androidxJunitVersion\"\n    androidTestImplementation \"androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion\"\n    implementation project(':capacitor-cordova-android-plugins')\n}\n\napply from: 'capacitor.build.gradle'\n\ntry {\n    def servicesJSON = file('google-services.json')\n    if (servicesJSON.text) {\n        apply plugin: 'com.google.gms.google-services'\n    }\n} catch(Exception e) {\n    logger.info(\"google-services.json not found, google-services plugin not applied. Push Notifications won't work\")\n}\n"
  },
  {
    "path": "android/app/capacitor.build.gradle",
    "content": "// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME \"capacitor update\" IS RUN\n\nandroid {\n  compileOptions {\n      sourceCompatibility JavaVersion.VERSION_21\n      targetCompatibility JavaVersion.VERSION_21\n  }\n}\n\napply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"\ndependencies {\n    implementation project(':capacitor-app')\n    implementation project(':capacitor-haptics')\n    implementation project(':capacitor-keyboard')\n    implementation project(':capacitor-status-bar')\n    implementation project(':capacitor-dialog')\n\n}\n\n\nif (hasProperty('postBuildExtras')) {\n  postBuildExtras()\n}\n"
  },
  {
    "path": "android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java",
    "content": "package com.getcapacitor.myapp;\n\nimport static org.junit.Assert.*;\n\nimport android.content.Context;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.platform.app.InstrumentationRegistry;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();\n\n        assertEquals(\"com.getcapacitor.app\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity\n            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation\"\n            android:name=\".MainActivity\"\n            android:label=\"@string/title_activity_main\"\n            android:theme=\"@style/AppTheme.NoActionBarLaunch\"\n            android:launchMode=\"singleTask\"\n            android:exported=\"true\">\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n        </activity>\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\"></meta-data>\n        </provider>\n    </application>\n\n    <!-- Permissions -->\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n</manifest>\n"
  },
  {
    "path": "android/app/src/main/java/cn/yunyoujun/cook/MainActivity.java",
    "content": "package cn.yunyoujun.cook;\n\nimport com.getcapacitor.BridgeActivity;\n\npublic class MainActivity extends BridgeActivity {}\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n    <WebView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android/app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#FFFFFF</color>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/values/strings.xml",
    "content": "<?xml version='1.0' encoding='utf-8'?>\n<resources>\n    <string name=\"app_name\">cook</string>\n    <string name=\"title_activity_main\">cook</string>\n    <string name=\"package_name\">cn.yunyoujun.cook</string>\n    <string name=\"custom_url_scheme\">cn.yunyoujun.cook</string>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:background\">@null</item>\n    </style>\n\n\n    <style name=\"AppTheme.NoActionBarLaunch\" parent=\"Theme.SplashScreen\">\n        <item name=\"android:background\">@drawable/splash</item>\n    </style>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <external-path name=\"my_images\" path=\".\" />\n    <cache-path name=\"my_cache_images\" path=\".\" />\n</paths>"
  },
  {
    "path": "android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java",
    "content": "package com.getcapacitor.myapp;\n\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}\n"
  },
  {
    "path": "android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    \n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.13.0'\n        classpath 'com.google.gms:google-services:4.4.2'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\napply from: \"variables.gradle\"\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "android/capacitor.settings.gradle",
    "content": "// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME \"capacitor update\" IS RUN\ninclude ':capacitor-android'\nproject(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@7.4.3_@capacitor+core@7.4.3/node_modules/@capacitor/android/capacitor')\n\ninclude ':capacitor-app'\nproject(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@7.1.0_@capacitor+core@7.4.3/node_modules/@capacitor/app/android')\n\ninclude ':capacitor-haptics'\nproject(':capacitor-haptics').projectDir = new File('../node_modules/.pnpm/@capacitor+haptics@7.0.2_@capacitor+core@7.4.3/node_modules/@capacitor/haptics/android')\n\ninclude ':capacitor-keyboard'\nproject(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.3_@capacitor+core@7.4.3/node_modules/@capacitor/keyboard/android')\n\ninclude ':capacitor-status-bar'\nproject(':capacitor-status-bar').projectDir = new File('../node_modules/.pnpm/@capacitor+status-bar@7.0.3_@capacitor+core@7.4.3/node_modules/@capacitor/status-bar/android')\n\ninclude ':capacitor-dialog'\nproject(':capacitor-dialog').projectDir = new File('../node_modules/.pnpm/@capacitor+dialog@7.0.2_@capacitor+core@7.4.3/node_modules/@capacitor/dialog/android')\n"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.13-all.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n"
  },
  {
    "path": "android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "android/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android/settings.gradle",
    "content": "include ':app'\ninclude ':capacitor-cordova-android-plugins'\nproject(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')\n\napply from: 'capacitor.settings.gradle'"
  },
  {
    "path": "android/variables.gradle",
    "content": "ext {\n    minSdkVersion = 23\n    compileSdkVersion = 35\n    targetSdkVersion = 35\n    androidxActivityVersion = '1.9.2'\n    androidxAppCompatVersion = '1.7.0'\n    androidxCoordinatorLayoutVersion = '1.2.0'\n    androidxCoreVersion = '1.15.0'\n    androidxFragmentVersion = '1.8.4'\n    coreSplashScreenVersion = '1.0.1'\n    androidxWebkitVersion = '1.12.1'\n    junitVersion = '4.13.2'\n    androidxJunitVersion = '1.2.1'\n    androidxEspressoCoreVersion = '3.6.1'\n    cordovaAndroidVersion = '10.1.1'\n}"
  },
  {
    "path": "app/app.vue",
    "content": "<script setup lang=\"ts\">\n// import { installPrompt } from './utils/pwa'\nimport { isClient } from '@vueuse/core'\nimport { useIndexedDB } from '~/composables/db'\nimport { appName } from '~/constants'\n\n// https://nuxt.com/docs/api/composables/use-head\nuseHead({\n  title: appName,\n  meta: [\n    {\n      name: 'description',\n      content: '好的，今天我们来做菜！',\n    },\n  ],\n})\n\nconst indexedDB = useIndexedDB()\nconst { isDark, setDarkMode, setLightMode } = useDarkMode()\n\nonMounted(() => {\n  // init dark mode\n  if (isClient) {\n    if (isDark.value) {\n      setDarkMode()\n    }\n    else {\n      setLightMode()\n    }\n\n    indexedDB.init()\n  }\n\n  // installPrompt()\n})\n</script>\n\n<template>\n  <!-- <VitePwaManifest /> -->\n  <!-- https://ionic.nuxtjs.org/cookbook/customising-app-vue -->\n  <!-- <NuxtLayout>\n    <NuxtLoadingIndicator />\n    <NuxtPage />\n  </NuxtLayout> -->\n  <ion-app>\n    <ion-router-outlet />\n  </ion-app>\n</template>\n"
  },
  {
    "path": "app/components/BaseFooter.vue",
    "content": "<script lang=\"ts\" setup>\nimport { isClient } from '@vueuse/core'\n\nconst displayICP = ref(true)\n\nonBeforeMount(() => {\n  if (isClient)\n    displayICP.value = ['cook.yunyoujun.cn', 'localhost', '127.0.0.1'].includes(window.location.hostname)\n})\n</script>\n\n<template>\n  <div p=\"4 t-2\" class=\"flex flex-col items-center justify-center\" text=\"sm\">\n    <CurrentVersion />\n    <a v-if=\"displayICP\" opacity=\"80\" class=\"flex\" href=\"https://beian.miit.gov.cn/\" target=\"_blank\">\n      苏ICP备17038157号\n    </a>\n    <div m=\"t-2\" class=\"inline-flex items-center justify-center\" text=\"xs\">\n      <a class=\"inline-flex items-center justify-center\" style=\"color: #ea7b99\" href=\"https://www.bilibili.com/blackboard/dynamic/306882\" target=\"_blank\">\n        <span inline-flex>菜谱视频来源：</span>\n        <div class=\"inline-flex\" i-ri-bilibili-line />\n        <span m=\"l-1\" class=\"inline-flex\" style=\"margin-top: 1px;\">B 站</span>\n      </a>\n    </div>\n    <div m=\"t-2\" opacity=\"80\" class=\"flex items-center justify-center\">\n      ©️&nbsp;<a href=\"https://github.com/YunYouJun/cook\" target=\"_blank\">Cook</a>\n      <div text=\"xs\" m=\"x-1\" i-ri-cloud-line />\n      <a href=\"https://www.yunyoujun.cn\" target=\"_blank\">云游君</a>\n    </div>\n    <div m=\"t-2\" opacity=\"80\">\n      <a href=\"https://yunle.fun\" target=\"_blank\" title=\"云乐坊\">\n        云乐坊工作室\n      </a>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/BasketButton.vue",
    "content": "<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\n\nconst rStore = useRecipeStore()\nconst { displayedRecipe } = storeToRefs(rStore)\n\n/**\n * scroll into view\n */\nfunction recipePanelScrollIntoView() {\n  const panel = recipePanelRef.value as HTMLElement\n  if (panel) {\n    panel.scrollIntoView({ behavior: 'smooth', block: 'start' })\n  }\n}\n</script>\n\n<template>\n  <ion-fab slot=\"fixed\" horizontal=\"end\" vertical=\"bottom\">\n    <!-- <button\n      v-show=\"showBasketBtn\"\n      class=\"rounded rounded-full inline-flex cursor-pointer shadow items-center justify-center fixed z-9 hover:shadow-md\"\n      bg=\"green-50 dark:green-900\" w=\"10\" h=\"10\"\n      bottom=\"22\"\n      right=\"4\"\n      text=\"green-600 dark:green-300\"\n    >\n\n    </button> -->\n\n    <ion-fab-button size=\"small\">\n      <!-- <ion-icon :icon=\"add\" /> -->\n      <span v-if=\"displayedRecipe.length > 0\">\n        <div i-mdi-bowl-mix-outline />\n      </span>\n      <span v-else>\n        <div i-mdi-bowl-outline />\n      </span>\n    </ion-fab-button>\n\n    <ion-fab-list side=\"top\">\n      <ion-fab-button @click=\"rStore.reset\">\n        <ion-icon :icon=\"ioniconsTrashOutline\" />\n      </ion-fab-button>\n      <ion-fab-button @click=\"recipePanelScrollIntoView\">\n        <ion-icon :icon=\"ioniconsListOutline\" />\n      </ion-fab-button>\n    </ion-fab-list>\n  </ion-fab>\n</template>\n"
  },
  {
    "path": "app/components/ChooseFood.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { StuffItem } from '~/types'\nimport { storeToRefs } from 'pinia'\nimport { useEmojiAnimation } from '~/composables/animation'\nimport { useIncompatibleFoods } from '~/composables/incompatible-foods'\n\nimport { meat, staple, tools, vegetable } from '~/data/food'\n\nconst rStore = useRecipeStore()\nconst { curTool } = storeToRefs(rStore)\nconst curStuff = computed(() => rStore.selectedStuff)\n\n// 食物相克检测\nconst { warningMessage, hasWarning, checkIncompatibility } = useIncompatibleFoods()\n\nconst recipeBtnRef = ref<HTMLButtonElement>()\nconst { playAnimation } = useEmojiAnimation(recipeBtnRef)\n\nconst { proxy } = useScriptGoogleTagManager()\n\nconst recipePanelRef = ref()\n\n// 监听食材变化，自动检测相克\nwatch(curStuff, (newIngredients) => {\n  checkIncompatibility(newIngredients)\n}, { deep: true })\n\n// 页面初始化时也检查一次（处理已有选择的情况）\nonMounted(() => {\n  if (curStuff.value.length > 0) {\n    checkIncompatibility(curStuff.value)\n  }\n})\n\nfunction toggleStuff(item: StuffItem, category = '', _e?: Event) {\n  rStore.toggleStuff(item.name)\n\n  if (curStuff.value.includes(item.name))\n    playAnimation(item.emoji)\n\n  proxy.dataLayer.push({\n    event: 'click',\n    category: `${category}_${item.name}`,\n    action: 'click_stuff',\n    label: '食材',\n  })\n  proxy.dataLayer.push({\n    event: 'click_stuff',\n    action: item.name,\n  })\n}\n</script>\n\n<template>\n  <div>\n    <h2 m=\"t-4\" text=\"xl\" font=\"bold\" p=\"1\">\n      🥘 先选一下食材\n    </h2>\n\n    <!-- 食物相克警告提示 -->\n    <ion-toast\n      class=\"incompatible-warning-toast\"\n      :message=\"warningMessage\"\n      :is-open=\"hasWarning\"\n      position=\"top\"\n      :icon=\"ioniconsWarningOutline\"\n      animated\n    >\n      <div\n        v-if=\"hasWarning\"\n        class=\"incompatible-warning-box\"\n        m=\"b-4\" p=\"4\"\n        border=\"~ 2 red-300 dark:red-600 rounded-xl\"\n        text=\"red-800 dark:red-200 sm\"\n        shadow=\"lg\"\n        relative=\"~\"\n        overflow=\"hidden\"\n      >\n        <div flex=\"~ items-start gap-3\">\n          <div text=\"2xl\" flex=\"shrink-0\" class=\"animate-pulse\">\n            🚨\n          </div>\n          <div flex=\"1 col gap-1\">\n            <div font=\"bold\" text=\"base\">\n              食物相克警告！\n            </div>\n            <div leading=\"relaxed\" whitespace=\"pre-line\">\n              {{ warningMessage }}\n            </div>\n          </div>\n        </div>\n      </div>\n    </ion-toast>\n\n    <div>\n      <h2 opacity=\"90\" text=\"base\" font=\"bold\" p=\"1\">\n        🥬 菜菜们\n      </h2>\n      <div>\n        <VegetableTag\n          v-for=\"item, i in vegetable\" :key=\"i\"\n          :active=\"curStuff.includes(item.name)\"\n          @click=\"toggleStuff(item, 'vegetable')\"\n        >\n          <span v-if=\"item.emoji\" class=\"inline-flex\">{{ item.emoji }}</span>\n          <span v-else-if=\"item.image\" class=\"inline-flex\">\n            <img class=\"inline-flex\" w=\"2\" h=\"2\" width=\"10\" height=\"10\" :src=\"item.image\" :alt=\"item.name\">\n          </span>\n          <span class=\"inline-flex\" m=\"l-1\">{{ item.name }}</span>\n        </VegetableTag>\n      </div>\n    </div>\n    <div m=\"y-4\">\n      <h2 opacity=\"90\" text=\"base\" font=\"bold\" p=\"1\">\n        🥩 肉肉们\n      </h2>\n      <div>\n        <MeatTag\n          v-for=\"item, i in meat\" :key=\"i\"\n          :active=\"curStuff.includes(item.name)\"\n          @click=\"toggleStuff(item, 'meat')\"\n        >\n          <span>{{ item.emoji }}</span>\n          <span m=\"l-1\">{{ item.name }}</span>\n        </MeatTag>\n      </div>\n    </div>\n    <div m=\"y-4\">\n      <h2 opacity=\"90\" text=\"base\" font=\"bold\" p=\"1\">\n        🍚 主食也要一起下锅吗？（不选也行）\n      </h2>\n      <div>\n        <StapleTag\n          v-for=\"item, i in staple\" :key=\"i\"\n          :active=\"curStuff.includes(item.name)\"\n          @click=\"toggleStuff(item, 'staple')\"\n        >\n          <span>{{ item.emoji }}</span>\n          <span m=\"l-1\">{{ item.name }}</span>\n        </StapleTag>\n      </div>\n    </div>\n    <div m=\"t-4\">\n      <h2 text=\"xl\" font=\"bold\" p=\"1\">\n        🍳 再选一下厨具\n      </h2>\n      <div>\n        <ToolTag\n          v-for=\"item, i in tools\" :key=\"i\"\n          :active=\"curTool === item.name\"\n          @click=\"rStore.clickTool(item)\"\n        >\n          <span v-if=\"item.emoji\" class=\"inline-flex\">\n            {{ item.emoji }}\n          </span>\n          <span v-else-if=\"item.icon\" class=\"inline-flex\">\n            <div :class=\"item.icon\" />\n          </span>\n          <span class=\"inline-flex\" m=\"l-1\">{{ item.label || item.name }}</span>\n        </ToolTag>\n      </div>\n    </div>\n\n    <RecipePanel ref=\"recipePanelRef\" />\n  </div>\n\n  <Transition>\n    <BasketButton ref=\"recipeBtnRef\" />\n  </Transition>\n</template>\n\n<style>\nion-toast.incompatible-warning-toast {\n  /* --background: #f4f4fa; */\n  --box-shadow: 3px 3px 10px 0 rgba(0, 0, 0, 0.2);\n  /* --color: #4b4a50; */\n  --background: rgba(254, 202, 202, 0.95);\n  --color: #7f1d1d;\n}\n</style>\n"
  },
  {
    "path": "app/components/CommonHeader.vue",
    "content": "<template>\n  <h1 text-2xl font=\"bold\" my=\"4\">\n    <slot />\n  </h1>\n</template>\n"
  },
  {
    "path": "app/components/Counter.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  initial: number\n}>()\n\nconst { count, inc, dec } = useCounter(props.initial)\n</script>\n\n<template>\n  <div>\n    {{ count }}\n    <button class=\"inc\" @click=\"inc()\">\n      +\n    </button>\n    <button class=\"dec\" @click=\"dec()\">\n      -\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/FAQItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\n\ndefineProps<{\n  title: string\n  defaultOpen?: boolean\n}>()\n</script>\n\n<template>\n  <Disclosure v-slot=\"{ open }\" :default-open=\"defaultOpen\" as=\"div\" class=\"mt-2\">\n    <DisclosureButton\n      class=\"w-full flex justify-between rounded-lg bg-blue-100 px-4 py-2 text-left text-sm text-blue-900 font-medium hover:bg-blue-200 focus:outline-none focus-visible:ring focus-visible:ring-blue-500 focus-visible:ring-opacity-75\"\n    >\n      <span>{{ title }}</span>\n      <div\n        i-ri-arrow-drop-up-line\n        :class=\"open ? 'rotate-180 transform' : ''\"\n        class=\"h-5 w-5 text-blue-500\"\n      />\n    </DisclosureButton>\n    <DisclosurePanel class=\"px-2 pb-2 pt-4 text-sm\">\n      <slot />\n    </DisclosurePanel>\n  </Disclosure>\n</template>\n"
  },
  {
    "path": "app/components/FeedbackActions.vue",
    "content": "<script lang=\"ts\" setup>\nimport { links } from '~/constants'\n</script>\n\n<template>\n  <div>\n    <a\n      m=\"2\"\n      class=\"feedback-button\"\n      :href=\"links.contribute\" target=\"_blank\"\n      title=\"居家菜谱投稿\"\n    >\n      立即投稿\n    </a>\n\n    <a\n      m=\"2\"\n      class=\"feedback-button\"\n      :href=\"links.feedback\" target=\"_blank\"\n      alt=\"通过兔小巢反馈\"\n    >\n      立即反馈\n    </a>\n  </div>\n</template>\n\n<style>\n.feedback-button {\n  @apply border-none inline-flex justify-center rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600;\n}\n</style>\n"
  },
  {
    "path": "app/components/README.md",
    "content": "## Components\n\nComponents in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).\n\n### Icons\n\nYou can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/).\n"
  },
  {
    "path": "app/components/RandomRecipe.vue",
    "content": "<script lang=\"ts\" setup>\nconst { count, inc, dec } = useCount()\nconst { random, randomRecipes } = useRandomRecipe(count)\n</script>\n\n<template>\n  <div inline-flex m=\"y-3\">\n    <ion-button shape=\"round\" @click=\"dec()\">\n      <ion-icon slot=\"icon-only\" :icon=\"ioniconsRemoveOutline\" />\n    </ion-button>\n    <div font=\"mono\" class=\"w-15 text-center text-2xl\" m-auto>\n      {{ count }}\n    </div>\n    <ion-button shape=\"round\" @click=\"inc()\">\n      <ion-icon slot=\"icon-only\" :icon=\"ioniconsAddOutline\" />\n    </ion-button>\n  </div>\n\n  <ion-button @click=\"random\">\n    <div class=\"transition\" hover=\"text-blue-500\" i-ri-refresh-line mr-1 inline-flex />\n    <div>随机一下</div>\n  </ion-button>\n\n  <div v-show=\"randomRecipes.length > 0\">\n    <div m=\"t-4\" flex=\"~ col\">\n      <template v-for=\"recipe, i in randomRecipes\" :key=\"i\">\n        <DishTag v-if=\"recipe\" :dish=\"recipe\" />\n      </template>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/RecipePanel.vue",
    "content": "<script lang=\"ts\" setup>\nimport { storeToRefs } from 'pinia'\nimport { recipePanelRef } from '~/composables/global'\n\nconst rStore = useRecipeStore()\n\nconst { selectedStuff, curTool } = storeToRefs(rStore)\n\nconst showSearchInput = ref(false)\n\nconst showTooltip = computed(() => !selectedStuff.value.length && !curTool.value)\n</script>\n\n<template>\n  <div\n    ref=\"recipePanelRef\"\n    class=\"recipe-panel relative shadow transition hover:shadow-md\" m=\"x-2 y-4\"\n    p=\"2\"\n    bg=\"gray-400/8\"\n  >\n    <RecipePanelTitle />\n\n    <ToggleMode />\n\n    <button absolute right-4 top-4 @click=\"showSearchInput = !showSearchInput\">\n      <div v-if=\"!showSearchInput\" i-ri-search-line />\n      <div v-else i-ri-search-fill />\n    </button>\n\n    <div class=\"cook-recipes\" p=\"2\">\n      <SearchFoodInput v-if=\"showSearchInput\" />\n\n      <Transition mode=\"out-in\">\n        <span v-if=\"showTooltip\" text=\"sm\" p=\"2\">\n          你要先选食材或工具哦～\n        </span>\n\n        <div\n          v-else-if=\"rStore.isSearching\"\n\n          relative flex items-center justify-center p-6 text-xl\n        >\n          <div class=\"magnifying-glass\" i-ri-search-line inline-flex />\n        </div>\n\n        <div v-else-if=\"rStore.displayedRecipe.length\">\n          <DishTag v-for=\"item, i in rStore.displayedRecipe\" :key=\"i\" :dish=\"item\" />\n        </div>\n\n        <div v-else text=\"sm\">\n          <span>还没有完美匹配的菜谱呢……</span>\n          <br>\n          <span>大胆尝试一下，或者</span>\n          <a href=\"#\" @click=\"rStore.reset()\">\n            <strong>换个组合</strong>\n          </a>\n          <span>？</span>\n          <br>\n          <div m=\"t-1\">\n            <span>欢迎来</span>\n            <a class=\"text-blue-600 font-bold dark:text-blue-400\" href=\"https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic\" target=\"_blank\">这里</a>\n            <span>反馈新的菜谱！</span>\n          </div>\n        </div>\n      </Transition>\n    </div>\n  </div>\n</template>\n\n<style>\n@keyframes circle-rotate {\n  from {\n    transform: rotate(0turn) translateY(60%) rotate(1turn);\n  }\n  to {\n    transform: rotate(1turn) translateY(60%) rotate(0turn);\n  }\n}\n\n.magnifying-glass {\n  margin: auto;\n  animation: circle-rotate 4s linear infinite;\n}\n</style>\n"
  },
  {
    "path": "app/components/RecipePanelTitle.vue",
    "content": "<template>\n  <div text=\"xl\" font=\"bold\" p=\"1\">\n    🍲 来看看组合出的菜谱吧！\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/SearchFoodInput.vue",
    "content": "<script lang=\"ts\" setup>\nconst rStore = useRecipeStore()\n\nconst searchInput = ref<HTMLInputElement>()\nonMounted(() => {\n  searchInput.value?.focus()\n})\n</script>\n\n<template>\n  <div m=\"auto b-2\" max-w=\"500px\">\n    <div relative text-xs>\n      <div\n        v-if=\"rStore.keyword\" cursor=\"pointer\"\n        justify=\"center\" absolute right-2 inline-flex items-center h=\"full\" opacity=\"70\"\n        @click=\"rStore.clearKeyWord()\"\n      >\n        <div i-ri-close-line />\n      </div>\n      <input\n        id=\"input\"\n        ref=\"searchInput\"\n        v-model=\"rStore.keyword\"\n        placeholder=\"关键字过滤\"\n        aria-label=\"搜索关键字\"\n        type=\"text\"\n        autocomplete=\"false\"\n        p=\"x4 y2\"\n        w=\"full\"\n        text=\"center\"\n        bg=\"white dark:dark-800\"\n        border=\"~ rounded gray-200 dark:gray-700\"\n        class=\"focus:dark:gray-500\"\n      >\n      <label class=\"hidden\" for=\"input\">快速搜索</label>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/Switch.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  strict: boolean\n  toggleStrict: (val: boolean) => void\n}>()\n</script>\n\n<template>\n  <div class=\"inline-flex items-center justify-center\" m=\"t-2\">\n    <span :class=\"!strict && 'text-orange-600'\" font=\"bold\" m=\"x-1\" @click=\"toggleStrict(false)\">\n      模糊匹配\n    </span>\n    <label m=\"x-1\" class=\"switch\">\n      <input :modelValue=\"strict\" type=\"checkbox\" @update:modelValue=\"toggleStrict\">\n      <span class=\"slider round inline-flex items-center justify-center\" />\n    </label>\n    <span :class=\"strict && 'text-green-600'\" font=\"bold\" m=\"x-1\" @click=\"toggleStrict(true)\">\n      精准匹配\n    </span>\n  </div>\n</template>\n\n<style lang=\"scss\">\n.switch {\n  position: relative;\n  display: inline-block;\n  width: 48px;\n  height: 28px;\n\n  input {\n    opacity: 0;\n    width: 0;\n    height: 0;\n  }\n}\n\n.slider {\n  @apply bg-orange-600;\n\n  position: absolute;\n  cursor: pointer;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  -webkit-transition: 0.4s;\n  transition: 0.4s;\n}\n\n$size: 20px;\n\n.slider:before {\n  position: absolute;\n  content: '';\n  height: $size;\n  width: $size;\n  left: 4px;\n  bottom: 4px;\n  background-color: white;\n  -webkit-transition: 0.4s;\n  transition: 0.4s;\n}\n\ninput:checked + .slider {\n  @apply bg-green-600;\n}\n\ninput:checked + .slider:before {\n  -webkit-transform: translateX($size);\n  -ms-transform: translateX($size);\n  transform: translateX($size);\n}\n\n/* Rounded sliders */\n.slider.round {\n  border-radius: 28px;\n\n  &:before {\n    border-radius: 50%;\n  }\n}\n</style>\n"
  },
  {
    "path": "app/components/TheAboutList.vue",
    "content": "<script lang=\"ts\" setup>\nimport { isClient } from '@vueuse/core'\nimport pkg from '~/../package.json'\nimport { icp } from '../constants'\n\nconst displayICP = ref(true)\n\nconst commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)\nconst date = import.meta.env.VITE_APP_BUILD_DATE\nconst buildDate = (new Date(date)).toLocaleDateString()\n\nonBeforeMount(() => {\n  if (isClient)\n    displayICP.value = ['cook.yunyoujun.cn', 'localhost', '127.0.0.1'].includes(window.location.hostname)\n})\n</script>\n\n<template>\n  <ion-list :inset=\"true\">\n    <ion-item>\n      <ion-label>当前版本</ion-label>\n      <ion-text>v{{ pkg.version }}</ion-text>\n    </ion-item>\n\n    <ion-item>\n      <ion-label>构建时间</ion-label>\n      <ion-text>{{ buildDate }}</ion-text>\n    </ion-item>\n\n    <ion-item v-if=\"commitSha\" :href=\"`https://github.com/YunYouJun/cook/commit/${commitSha}`\" target=\"_blank\">\n      <ion-label>Commit Hash</ion-label>\n      <ion-text>{{ commitSha }}</ion-text>\n    </ion-item>\n\n    <ion-item v-if=\"displayICP\" href=\"https://beian.miit.gov.cn/\" target=\"_blank\">\n      <ion-label>备案号</ion-label>\n      <ion-text>{{ icp }}</ion-text>\n    </ion-item>\n  </ion-list>\n\n  <ion-list :inset=\"true\">\n    <ion-item href=\"https://www.bilibili.com/blackboard/dynamic/306882\" target=\"_blank\">\n      <ion-label>数据来源</ion-label>\n      <ion-text class=\"inline-flex items-center justify-center\">\n        <div class=\"inline-flex\" i-ri-bilibili-line />\n        <span m=\"l-1\" class=\"inline-flex\">哔哩哔哩</span>\n      </ion-text>\n    </ion-item>\n\n    <ion-item href=\"https://github.com/YunYouJun/cook\" target=\"_blank\">\n      <ion-label>开源代码</ion-label>\n      <ion-text class=\"inline-flex items-center justify-center\">\n        <div class=\"inline-flex\" i-ri-github-line />\n        <span m=\"l-1\" class=\"inline-flex\">GitHub</span>\n      </ion-text>\n    </ion-item>\n\n    <ion-item router-link=\"/about/me\">\n      <ion-label>项目作者</ion-label>\n      <ion-text class=\"inline-flex items-center justify-center\">\n        云游君\n      </ion-text>\n    </ion-item>\n\n    <ion-item router-link=\"/about/acknowledgements\">\n      <ion-label>致谢名单</ion-label>\n    </ion-item>\n  </ion-list>\n</template>\n"
  },
  {
    "path": "app/components/TheBottomMenu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BottomMenuItem } from '@yunlefun/vue'\n\nconst items: BottomMenuItem[] = [\n  {\n    icon: 'i-ri-home-line',\n    activeIcon: 'i-ri-home-fill',\n    title: '首页',\n    to: '/',\n  },\n  {\n    icon: 'i-ri-compass-2-line',\n    activeIcon: 'i-ri-compass-2-fill',\n    title: '吃什么',\n    to: '/random',\n  },\n  // {\n  //   icon: 'i-ri-compass-2-line',\n  //   activeIcon: 'i-ri-compass-2-fill',\n  //   title: '吃什么',\n  //   to: '/about',\n  // },\n  {\n    icon: 'i-ri-question-line',\n    activeIcon: 'i-ri-question-fill',\n    title: '帮助',\n    to: '/help',\n  },\n  {\n    icon: 'i-ri-user-line',\n    activeIcon: 'i-ri-user-fill',\n    title: '我的',\n    to: '/user',\n  },\n]\n\nconst route = useRoute()\nconst router = useRouter()\n\nfunction onClick(item: BottomMenuItem) {\n  // router.push(item.to || '/')\n  router.replace(item.to || '/')\n}\n</script>\n\n<template>\n  <YlfBottomMenu shadow-2xl pb=\"$cook-bottom-menu-padding-bottom\">\n    <YlfBottomMenuItem\n      v-for=\"item in items\"\n      :key=\"item.to\"\n      :item=\"item\"\n      :active=\"route.path === item.to\"\n      class=\"pt-3\"\n      @click=\"onClick\"\n    />\n  </YlfBottomMenu>\n</template>\n"
  },
  {
    "path": "app/components/ToggleMode.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { SearchMode } from '~/composables/store/recipe'\n\nconst rStore = useRecipeStore()\n\nconst searchModes: {\n  id: SearchMode\n  name: string\n}[] = [{\n  id: 'loose',\n  name: '模糊匹配',\n}, {\n  id: 'strict',\n  name: '严格匹配',\n}, {\n  id: 'survival',\n  name: '生存模式',\n}]\n</script>\n\n<template>\n  <div>\n    <button\n      v-for=\"mode in searchModes\" :key=\"mode.id\" class=\"tag rounded px-2\"\n      :bg=\"mode.id === rStore.curMode ? 'orange-500 dark:orange-600 opacity-100' : 'orange-300 opacity-20'\"\n      :text=\"mode.id === rStore.curMode ? 'orange-100' : 'orange-800 dark:orange-200'\"\n      @click=\"rStore.setMode(mode.id)\"\n    >\n      {{ mode.name }}\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/common/BackBtn.vue",
    "content": "<script lang=\"ts\" setup>\nconst router = useRouter()\nfunction back() {\n  router.back()\n}\n</script>\n\n<template>\n  <YlfIconButton\n    icon=\"i-ri-arrow-left-s-line\"\n    @click=\"back\"\n  />\n</template>\n"
  },
  {
    "path": "app/components/common/DarkToggle.vue",
    "content": "<script setup lang=\"ts\">\nconst { color, toggleDark } = useDarkMode()\n\nuseHead({\n  meta: [{\n    id: 'theme-color',\n    name: 'theme-color',\n    content: () => color.value === 'dark' ? '#222222' : '#ffffff',\n  }],\n})\n</script>\n\n<template>\n  <YlfIconButton\n    class=\"icon-btn hover:text-yellow-400 !outline-none\"\n    text-xl\n    title=\"切换\" @click=\"toggleDark()\"\n  >\n    <div i=\"ri-sun-line dark:ri-moon-line\" />\n  </YlfIconButton>\n</template>\n"
  },
  {
    "path": "app/components/common/SearchRecipe.vue",
    "content": "<script setup lang=\"ts\">\nimport { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\nimport { db } from '~/utils/db'\n\nconst isOpen = ref(false)\n\nfunction closeModal() {\n  isOpen.value = false\n}\nfunction openModal() {\n  isOpen.value = true\n}\n\nconst keyword = ref('')\nasync function getFilterRecipes(keyword: string) {\n  return db.recipes.filter((recipe) => {\n    return recipe.name.includes(keyword)\n  }).toArray()\n}\nconst filteredRecipes = computedAsync(async () => {\n  return await getFilterRecipes(keyword.value)\n})\n</script>\n\n<template>\n  <YlfIconButton\n\n    class=\"icon-btn hover:text-yellow-400 !outline-none\"\n    absolute right-4 top-4 text-xl\n    title=\"切换\" @click=\"openModal\"\n  >\n    <div i=\"ri-search-line\" />\n  </YlfIconButton>\n\n  <TransitionRoot appear :show=\"isOpen\" as=\"template\">\n    <Dialog as=\"div\" class=\"relative z-10\" @close=\"closeModal\">\n      <TransitionChild\n        as=\"template\"\n        enter=\"duration-300 ease-out\"\n        enter-from=\"opacity-0\"\n        enter-to=\"opacity-100\"\n        leave=\"duration-200 ease-in\"\n        leave-from=\"opacity-100\"\n        leave-to=\"opacity-0\"\n      >\n        <div class=\"fixed inset-0 bg-black/10\" />\n      </TransitionChild>\n\n      <div class=\"fixed inset-0 overflow-y-auto\">\n        <div\n          class=\"h-full flex justify-center text-center\"\n        >\n          <TransitionChild\n            as=\"template\"\n            enter=\"duration-300 ease-out\"\n            enter-from=\"opacity-0 scale-95\"\n            enter-to=\"opacity-100 scale-100\"\n            leave=\"duration-200 ease-in\"\n            leave-from=\"opacity-100 scale-100\"\n            leave-to=\"opacity-0 scale-95\"\n          >\n            <DialogPanel\n              class=\"h-full max-w-xl w-full transform overflow-hidden bg-white p-4 text-left align-middle shadow-xl transition-all dark:bg-dark-600\"\n              md=\"rounded-2xl\"\n              overflow=\"auto\"\n              flex=\"~ col\"\n            >\n              <DialogTitle\n                as=\"h3\"\n                class=\"flex items-center justify-center text-lg font-medium leading-6\"\n              >\n                <div relative inline-flex flex=\"grow\">\n                  <div\n                    i-ri-search-line\n                    class=\"absolute left-3 top-2 cursor-pointer text-gray-400\"\n                  />\n                  <input\n                    v-model=\"keyword\"\n                    type=\"text\"\n                    class=\"w-full rounded-full bg-transparent text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 placeholder-gray-400\"\n                    border=\"~ rounded-full gray-300 op-50 focus:border-blue-500\"\n                    placeholder=\"搜索菜谱\"\n                    autofocus py-2 pl-10 pr-3\n                  >\n                  <div\n                    v-if=\"keyword\" i-ri-close-line\n                    class=\"absolute right-3 top-2 cursor-pointer text-gray-400\"\n                    @click=\"keyword = ''\"\n                  />\n                </div>\n                <button op=\"70\" ml-2 inline-flex cursor-pointer text-base @click=\"closeModal\">\n                  取消\n                </button>\n              </DialogTitle>\n              <div flex=\"~ col grow\" overflow=\"auto\" class=\"mt-2\" text-xs>\n                <DishTag v-for=\"item, i in filteredRecipes\" :key=\"i\" :dish=\"item\" />\n              </div>\n            </DialogPanel>\n          </TransitionChild>\n        </div>\n      </div>\n    </Dialog>\n  </TransitionRoot>\n</template>\n"
  },
  {
    "path": "app/components/content/AboutMe.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VueAboutMe } from 'vue-about-me'\nimport 'vue-about-me/style.css'\n\nconst color = useColorMode()\nconst isDark = computed(() => color.value === 'dark')\n\nconst copyright = {\n  name: 'Cook',\n  repo: 'YunYouJun/cook',\n  color: '#0078E7',\n  iconUrl: 'https://sponsors.yunyoujun.cn',\n  author: '云游君',\n  authorUrl: 'https://www.yunyoujun.cn',\n}\n\nconst links = [\n  {\n    type: 'github',\n    label: 'GitHub: YunYouJun',\n    href: 'https://github.com/YunYouJun',\n  },\n  {\n    type: 'telegram',\n    label: 'Telegram Channel',\n    href: 'https://t.me/elpsycn',\n  },\n  {\n    type: 'weibo',\n    label: '微博：机智的云游君',\n    href: 'http://weibo.com/jizhideyunyoujun',\n  },\n  {\n    type: 'twitter',\n    label: 'Twitter: YunYouJun',\n    href: 'https://twitter.com/YunYouJun',\n  },\n  {\n    type: 'wechat',\n    label: '微信公众号：云游君',\n    href: '/wechat',\n    target: '_self',\n  },\n  {\n    type: 'bilibili',\n    label: '云游君Official',\n    href: 'https://space.bilibili.com/1579790',\n  },\n  {\n    type: 'blog',\n    label: '博客：yunyoujun.cn',\n    href: 'http://www.yunyoujun.cn',\n  },\n]\n</script>\n\n<template>\n  <VueAboutMe :is-dark=\"isDark\" :copyright=\"copyright\" :links=\"links\" />\n</template>\n"
  },
  {
    "path": "app/components/content/AboutMenu.vue",
    "content": "<template>\n  <div>\n    <NuxtLink class=\"icon-btn mx-2 hover:text-orange-400\" to=\"/help\" title=\"帮助\">\n      <div i-ri-question-line />\n    </NuxtLink>\n\n    <NuxtLink class=\"icon-btn mx-2 hover:text-blue-400\" to=\"/about\" title=\"关于\">\n      <div i-ri-information-line />\n    </NuxtLink>\n\n    <a class=\"icon-btn mx-2 hover:text-pink-400\" rel=\"noreferrer\" href=\"https://space.bilibili.com/1579790\" target=\"_blank\" title=\"BiliBili\">\n      <div i-ri-bilibili-line />\n    </a>\n\n    <a class=\"icon-btn hover:text-black-400 mx-2\" rel=\"noreferrer\" href=\"https://github.com/YunYouJun/cook\" target=\"_blank\" title=\"GitHub\">\n      <div i-ri-github-line />\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/cookbook/CookbookCard.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Cookbook } from '~/types'\n\ndefineProps<{\n  cookbook: Cookbook\n}>()\n\nconst showDetail = ref(false)\n</script>\n\n<template>\n  <button\n    class=\"bg-$c-bg-alt\"\n    h-36 w-full inline-flex cursor-pointer items-center justify-center shadow\n    @click=\"showDetail = true\"\n  >\n    <slot />\n  </button>\n\n  <CookbookDetail\n    v-if=\"showDetail\"\n    absolute bottom-17 left-2 right-2 top-2 z-1 overflow-hidden shadow\n    :cookbook=\"cookbook\"\n  >\n    <YlfIconButton\n      icon=\"i-ri-close-line\"\n      class=\"absolute right-2 top-2\"\n      @click=\"showDetail = false\"\n    />\n  </CookbookDetail>\n</template>\n"
  },
  {
    "path": "app/components/cookbook/CookbookDetail.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Cookbook } from '~/types'\n\nconst props = defineProps<{\n  cookbook: Cookbook\n}>()\n\nconst recipes = ref<Cookbook['recipes']>(props.cookbook.recipes)\nonMounted(async () => {\n  recipes.value = ((await import('../../data/recipe.json')).default) as unknown as Cookbook['recipes']\n})\n</script>\n\n<template>\n  <div class=\"bg-$c-bg-alt\" flex=\"~ col\">\n    <h3 mt-4 font-bold>\n      {{ cookbook.title }}\n    </h3>\n    <sub op=\"90\" my-3>\n      {{ cookbook.description }}\n    </sub>\n    <div mx-auto mt-2 p-0 border=\"1px\" overflow-y=\"scroll\">\n      <RecipeTable h=\"full\" :recipes=\"recipes\" />\n    </div>\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/cookbook/NewCookbookCard.vue",
    "content": "<script lang=\"ts\" setup>\ndefinePageMeta({\n  layout: 'child',\n  title: '新建食谱书',\n})\n</script>\n\n<template>\n  <NuxtLink\n    class=\"bg-$c-bg-alt\"\n    h-36 w-full inline-flex cursor-pointer items-center justify-center shadow\n    to=\"/cookbooks/new\"\n  >\n    <slot>\n      <div i-ri-add-line />\n    </slot>\n  </NuxtLink>\n</template>\n"
  },
  {
    "path": "app/components/features/InstallPwa.vue",
    "content": "<script lang=\"ts\" setup>\nconst app = useAppStore()\n\nfunction install() {\n  const deferredPrompt = app.deferredPrompt\n  // Show the install prompt\n  deferredPrompt.prompt()\n  // Wait for the user to respond to the prompt\n  deferredPrompt.userChoice.then((choiceResult: any) => {\n    if (choiceResult.outcome === 'accepted')\n      // eslint-disable-next-line no-console\n      console.log('User accepted the install prompt')\n    else\n      // eslint-disable-next-line no-console\n      console.log('User dismissed the install prompt')\n  })\n}\n</script>\n\n<template>\n  <Transition>\n    <div v-if=\"app.deferredPrompt\" text=\"center\" m=\"t-2\">\n      <button\n        class=\"shadow\"\n        text=\"white\" bg=\"green-500\" p=\"x-4 y-2\" m=\"2\"\n        inline-flex items-center justify-center rounded-md font-bold\n        @click=\"install\"\n      >\n        <div i-ri-install-line mr-1 inline-flex />\n        <span inline-flex>安装到桌面</span>\n      </button>\n    </div>\n  </Transition>\n</template>\n"
  },
  {
    "path": "app/components/footer/CdnSupport.vue",
    "content": "<script lang=\"ts\" setup>\n// migrate to 腾讯云 EdgeOne\n</script>\n\n<template>\n  <div mt-2>\n    本站点由\n    <a color=\"#F6821F\" href=\"https://www.cloudflare-cn.com/\" target=\"_blank\" title=\"Cloudflare\" border=\"b-1 dashed\">\n      <span>Cloudflare</span>\n    </a>\n    提供 CDN 支持\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/help/HelpAbout.vue",
    "content": "<template>\n  <FAQItem :default-open=\"true\" title=\"关于\">\n    <div text-left>\n      <ul>\n        <li>\n          它诞生于 2022 年 4 月，时值疫情风控期间，希望能帮助期间的伙伴根据现有食材寻找到合适的菜谱。故原名「隔离食用手册」。\n        </li>\n        <li>\n          如今那个时期已离我们远去，故去掉「隔离」二字。但也很高兴能在这里继续与你相遇，希望它能继续发光发热，在日常生活中帮助到大家。\n        </li>\n        <li>\n          <div class=\"inline-flex items-center justify-center\">\n            代码仓库：<a class=\"inline-flex items-center justify-center\" href=\"https://github.com/YunYouJun/cook\" target=\"_blank\">\n              <div m=\"r-1\" i-ri-github-line inline-flex />YunYouJun/cook</a>\n          </div>\n        </li>\n        <li>\n          <div class=\"inline-flex items-center justify-center\">\n            菜谱视频来源：\n            <a class=\"inline-flex items-center text-sm text-blue-600 dark:text-blue-400\" href=\"https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS\" target=\"_blank\">\n              <div m=\"r-1\" i-ri-bilibili-line inline-flex />\n              <span class=\"inline-flex\">隔离食用手册大全</span>\n            </a>\n          </div>\n        </li>\n      </ul>\n    </div>\n  </FAQItem>\n</template>\n"
  },
  {
    "path": "app/components/layout/CurrentVersion.vue",
    "content": "<script lang=\"ts\" setup>\nimport pkg from '~/../package.json'\n\nconst commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)\nconst date = import.meta.env.VITE_APP_BUILD_DATE\nconst buildDate = (new Date(date)).toLocaleDateString()\n</script>\n\n<template>\n  <div v-if=\"commitSha && buildDate\" mb-2 text-sm>\n    <span>\n      当前版本 v{{ pkg.version }}（{{ buildDate }}）:\n    </span>\n    <span>\n      <a border=\"b-1 dashed\" :href=\"`https://github.com/YunYouJun/cook/commit/${commitSha}`\" target=\"_blank\" alt=\"Cook | GitHub Commit\">\n        {{ commitSha }}\n      </a>\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/layout/SimpleCopyright.vue",
    "content": "<template>\n  <div text=\"center sm\" my-3>\n    <CurrentVersion />\n    <div flex=\"~\" items-center justify-center gap=\"2\">\n      <a\n        href=\"https://github.com/YunYouJun/cook\" target=\"_blank\"\n        class=\"inline-flex items-center justify-center\"\n      >\n        <div i-ri-github-line mr-1 />\n        <span>Code</span>\n      </a>\n      by\n      <a\n        href=\"https://www.bilibili.com/opus/649847454294868008\" target=\"_blank\"\n        class=\"inline-flex items-center justify-center\"\n      >\n        <div i-ri-bilibili-line mr-1 class=\"text-pink-400\" />\n        <span>云游君</span>\n      </a>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/layouts/README.md",
    "content": "## Layouts\n\nVue components in this dir are used as layouts.\n\nBy default, `default.vue` will be used unless an alternative is specified in the route meta.\n\n```html\n<script setup lang=\"ts\">\n  definePageMeta({\n    layout: 'home',\n  })\n</script>\n```\n\nLearn more on https://nuxt.com/docs/guide/directory-structure/layouts\n"
  },
  {
    "path": "app/components/layouts/child.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  title?: string\n}>()\n\nconst route = useRoute()\n</script>\n\n<template>\n  <main class=\"text-center text-gray-700 dark:text-gray-200\" p=\"t-5 b-15\">\n    <div flex items-center justify-between>\n      <BackBtn ml-3 />\n      <h2 flex items-center justify-center text-lg font=\"bold\">\n        {{ route.meta.title }}\n      </h2>\n      <DarkToggle mr-3 />\n    </div>\n\n    <slot />\n    <TheBottomMenu fixed bottom-0 left-0 right-0 />\n  </main>\n</template>\n"
  },
  {
    "path": "app/components/layouts/default.vue",
    "content": "<template>\n  <main class=\"cook-main text-center text-gray-700 dark:text-gray-200\" p=\"t-8 b-$cook-bottom-menu-height\">\n    <slot />\n    <DarkToggle absolute left-4 top-4 />\n    <SearchRecipe />\n    <TheBottomMenu fixed bottom-0 left-0 right-0 />\n  </main>\n</template>\n"
  },
  {
    "path": "app/components/recipe/RecipeTable.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recipes } from '~/types'\n\ndefineProps<{\n  recipes: Recipes\n}>()\n</script>\n\n<template>\n  <table\n    class=\"recipe-table bg-$c-bg\"\n    overflow=\"auto\" h=\"full\"\n  >\n    <thead>\n      <tr>\n        <th />\n        <th>\n          名称\n        </th>\n        <th>\n          工具\n        </th>\n        <th>\n          材料\n        </th>\n      </tr>\n    </thead>\n    <tbody>\n      <RecipeTableItem\n        v-for=\"(recipe, i) in recipes\"\n        :key=\"recipe.name\"\n        :index=\"i\" :recipe=\"recipe\"\n      />\n    </tbody>\n  </table>\n</template>\n\n<style lang=\"scss\">\n.recipe-table {\n  font-size: 0.8rem;\n}\n\ntr,\nth,\ntd {\n  border: 1px solid black;\n}\n</style>\n"
  },
  {
    "path": "app/components/recipe/RecipeTableItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RecipeItem } from '~/types'\n\ndefineProps<{\n  index: number\n  recipe: RecipeItem\n}>()\n</script>\n\n<template>\n  <tr>\n    <td>\n      {{ index }}\n    </td>\n    <td>\n      <a\n        class=\"text-blue-500\" font-bold\n        :href=\"recipe.link || `https://www.bilibili.com/video/${recipe.bv}`\"\n        target=\"_blank\"\n      >\n        {{ recipe.name }}\n      </a>\n    </td>\n    <td>\n      {{ recipe.tools.join('、') }}\n    </td>\n    <td>\n      {{ recipe.stuff.join('、') }}\n    </td>\n  </tr>\n</template>\n"
  },
  {
    "path": "app/components/tags/DateTag.vue",
    "content": "<template>\n  <div\n    class=\"tagSize pureTag rounded\"\n    p=\"x-2\"\n    border=\"~ yellow-200 dark:yellow-800\"\n    bg=\"yellow-300 opacity-20\"\n    text=\"yellow-800 dark:yellow-200\"\n  >\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "app/components/tags/DishLabel.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RecipeItem } from '~/types'\nimport type { DbRecipeItem } from '~/utils/db'\nimport { tools } from '~/data/food'\nimport { getEmojisFromStuff } from '~/utils'\n\nconst props = defineProps<{\n  dish: RecipeItem | DbRecipeItem\n}>()\n\nconst dishEmojis = computed(() => {\n  return getEmojisFromStuff(props.dish.stuff)\n})\n</script>\n\n<template>\n  <span class=\"inline-flex items-center gap-1\">\n    <ion-label>\n      {{ dish.tags?.includes('杂烩') ? '🍲' : dishEmojis.join(' ') }}\n    </ion-label>\n\n    <ion-label>\n      {{ dish.name }}\n    </ion-label>\n\n    <template v-for=\"tool, i in tools\">\n      <span\n        v-if=\"dish.tools?.includes(tool.name)\"\n        :key=\"i\" class=\"inline-block\" :class=\"tool.icon\"\n      />\n    </template>\n  </span>\n</template>\n"
  },
  {
    "path": "app/components/tags/DishTag.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RecipeItem } from '~/types'\nimport type { DbRecipeItem } from '~/utils/db'\nimport { recipeHistories } from '~/composables/store/history'\nimport { tools } from '~/data/food'\nimport { getEmojisFromStuff } from '~/utils'\n\nconst props = defineProps<{\n  dish: RecipeItem | DbRecipeItem\n}>()\n\nconst { proxy } = useScriptGoogleTagManager()\n\nfunction triggerGtm(dish: RecipeItem) {\n  recipeHistories.value.push({\n    recipe: dish,\n    time: Date.now(),\n  })\n\n  proxy.dataLayer.push({\n    event: 'click',\n    category: `dish_${dish.name}`,\n    action: 'click_recipe',\n    label: '跳转菜谱',\n  })\n  proxy.dataLayer.push({\n    event: 'click_dish',\n    action: dish.name,\n  })\n}\n\nconst dishLabel = computed(() => {\n  const emojis = getEmojisFromStuff(props.dish.stuff)\n  return `${props.dish.tags?.includes('杂烩') ? '🍲' : emojis.join(' ')} ${props.dish.name}`\n})\n</script>\n\n<template>\n  <a\n    :href=\"dish.link || `https://www.bilibili.com/video/${dish.bv}`\" target=\"_blank\" class=\"dish-tag tag rounded\" p=\"x-2\"\n    border=\"~ blue-200 dark:blue-800\"\n    bg=\"blue-300 opacity-20\"\n    @click=\"triggerGtm(dish)\"\n  >\n    <span m=\"r-1\" text=\"sm blue-700 dark:blue-200\">\n      {{ dishLabel }}\n    </span>\n    <template v-for=\"tool, i in tools\">\n      <span v-if=\"dish.tools?.includes(tool.name)\" :key=\"i\" :class=\"tool.icon\" />\n    </template>\n  </a>\n</template>\n"
  },
  {
    "path": "app/components/tags/MeatTag.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  active: boolean\n}>()\n</script>\n\n<template>\n  <button\n    class=\"meat-tag tag rounded\" p=\"x-2\"\n    border=\"~ red-200 dark:red-800\"\n    :bg=\"active ? 'red-500 opacity-90' : 'red-300 opacity-20'\"\n    :text=\"active ? 'red-100' : 'red-800 dark:red-200'\"\n  >\n    <slot />\n  </button>\n</template>\n"
  },
  {
    "path": "app/components/tags/StapleTag.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  active: boolean\n}>()\n</script>\n\n<template>\n  <button\n    class=\"tag rounded\" p=\"x-2\" border=\"~ yellow-200 dark:yellow-800\"\n    :bg=\"active ? 'yellow-500 dark:yellow-600 opacity-100' : 'yellow-300 opacity-20'\"\n    :text=\"active ? 'yellow-100' : 'yellow-800 dark:yellow-200'\"\n  >\n    <slot />\n  </button>\n</template>\n"
  },
  {
    "path": "app/components/tags/ToolTag.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  active: boolean\n}>()\n</script>\n\n<template>\n  <button\n    class=\"tag rounded\" p=\"x-2\"\n    border=\"~ stone-200 dark:stone-600\"\n    :bg=\"active ? 'stone-600 opacity-100' : 'stone-300 opacity-5'\"\n    :text=\"active ? 'stone-100' : 'stone-800 dark:stone-200'\"\n  >\n    <slot />\n  </button>\n</template>\n"
  },
  {
    "path": "app/components/tags/VegetableTag.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  active: boolean\n}>()\n</script>\n\n<template>\n  <button\n    class=\"vegetable-tag tag rounded\" p=\"x-2\"\n    border=\"~ green-200 dark:green-800\"\n    :bg=\"active ? 'green-600 opacity-90' : 'green-300 opacity-20'\"\n    :text=\"active ? 'green-100' : 'green-800 dark:green-200'\"\n  >\n    <slot />\n  </button>\n</template>\n"
  },
  {
    "path": "app/components/ylf/YlfForm.vue",
    "content": "<template>\n  <div class=\"ylf-form\" flex=\"~ col\" rounded-md>\n    <slot />\n  </div>\n</template>\n\n<style lang=\"scss\">\n.ylf-form {\n  background-color: var(--ylf-c-bg-alt);\n\n  border: 1px solid var(--ylf-c-border);\n\n  margin: 10px 0;\n  overflow: hidden;\n\n  .ylf-form-item {\n    border-bottom: 1px solid var(--ylf-c-border);\n\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "app/components/ylf/YlfFormItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport { NuxtLink } from '#components'\n\ndefineProps<{\n  icon?: string\n  label?: string\n  /**\n   * Router link\n   */\n  to?: string\n}>()\n</script>\n\n<template>\n  <component\n    :is=\"to ? NuxtLink : 'div'\"\n    :to=\"to\"\n    class=\"ylf-form-item\"\n\n    w-full flex cursor-pointer items-center justify-between p-2 hover:bg-gray-100 dark:hover:bg-dark-400\n  >\n    <div v-if=\"label\" class=\"text-sm\" inline-flex items-center justify-center>\n      <div v-if=\"icon\" :class=\"icon\" mr-2 inline-flex />\n      <span>{{ label }}</span>\n    </div>\n    <div inline-flex>\n      <slot>\n        <div v-if=\"to\" i-ri-arrow-right-s-line />\n      </slot>\n    </div>\n  </component>\n</template>\n"
  },
  {
    "path": "app/components/ylf/YlfIconButton.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  icon?: string\n}>()\n</script>\n\n<template>\n  <button\n    class=\"ylf-icon-button hover:(bg-blue-300 bg-opacity-20)\"\n    h-10 w-10 inline-flex items-center justify-center rounded-full\n  >\n    <slot>\n      <div v-if=\"icon\" text-xl :class=\"icon\" />\n    </slot>\n  </button>\n</template>\n"
  },
  {
    "path": "app/components/ylf/YlfIconItem.vue",
    "content": "<script lang=\"ts\" setup>\ndefineProps<{\n  icon: string\n  label: string\n  to: string\n}>()\n</script>\n\n<template>\n  <NuxtLink\n    :to=\"to\"\n    flex=\"~ col\"\n    border=\"~ solid dark:$ylf-c-border\"\n    bg=\"$ylf-c-bg-alt\"\n    class=\"inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium decoration-none hover-bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500 dark:hover:bg-dark-400\"\n  >\n    <div :class=\"icon\" inline-flex text-lg />\n    <div mt-2 inline-flex text-xs>\n      {{ label }}\n    </div>\n  </NuxtLink>\n</template>\n"
  },
  {
    "path": "app/components/ylf/YlfSwitch.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Switch } from '@headlessui/vue'\n\ndefineProps<{\n  modelValue: boolean\n}>()\n\nconst emit = defineEmits(['update:modelValue'])\nfunction updateModelValue(value: boolean) {\n  emit('update:modelValue', value)\n}\n</script>\n\n<template>\n  <Switch\n    :model-value=\"modelValue\"\n    :class=\"modelValue ? 'bg-blue-600' : 'bg-gray'\"\n    class=\"relative h-6 w-11 inline-flex shrink-0 cursor-pointer border-2 border-transparent rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75\"\n    @update:model-value=\"updateModelValue\"\n  >\n    <span class=\"sr-only\">Use setting</span>\n    <span\n      aria-hidden=\"true\"\n      :class=\"modelValue ? 'translate-x-5' : 'translate-x-0'\"\n      class=\"pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out\"\n    />\n  </Switch>\n</template>\n"
  },
  {
    "path": "app/composables/animation.ts",
    "content": "import type { Ref } from 'vue'\nimport { isClient } from '@vueuse/core'\n\nexport function useEmojiAnimation(recipeBtn: Ref<HTMLButtonElement | undefined>) {\n  const { x, y } = usePointer()\n  const { top, left } = useElementBounding(recipeBtn)\n\n  const playAnimation = (emoji: string) => {\n    if (!isClient)\n      return\n\n    // 单个 Vue 组件实现不适合创建多个元素和清除动画\n    const emojiEl = document.createElement('span')\n    emojiEl.style.position = 'fixed'\n    emojiEl.style.left = `${x.value}px`\n    emojiEl.style.top = `${y.value}px`\n    emojiEl.style.zIndex = '10'\n    emojiEl.style.transition = 'left .4s linear, top .4s cubic-bezier(0.5, -0.5, 1, 1)'\n    emojiEl.textContent = emoji\n    document.body.appendChild(emojiEl)\n\n    setTimeout(() => {\n    // 以防万一，按钮位置没检测出来，就不播放动画了\n      if (!top.value || !left.value) {\n        emojiEl.style.top = `${x.value}px`\n        emojiEl.style.left = `${y.value}px`\n      }\n      else {\n        emojiEl.style.top = `${top.value}px`\n        emojiEl.style.left = `${left.value + 12}px`\n      }\n    }, 1)\n\n    emojiEl.ontransitionend = () => {\n      emojiEl.remove()\n    }\n  }\n\n  return {\n    playAnimation,\n  }\n}\n"
  },
  {
    "path": "app/composables/count.ts",
    "content": "import { useStorage } from '@vueuse/core'\n\nexport function useCount() {\n  const count = useStorage('count', 5)\n\n  function inc() {\n    count.value += 1\n  }\n  function dec() {\n    if (count.value <= 1)\n      return\n    count.value -= 1\n  }\n\n  return {\n    count,\n    inc,\n    dec,\n  }\n}\n"
  },
  {
    "path": "app/composables/dark.ts",
    "content": "import { Capacitor } from '@capacitor/core'\nimport { StatusBar, Style } from '@capacitor/status-bar'\nimport { ionDarkClass } from '~/constants'\n\nexport const useDarkMode = createSharedComposable(() => {\n  const color = useColorMode()\n  const isDark = computed(() => color.value === 'dark')\n\n  async function setLightMode() {\n    document.documentElement.classList.remove(ionDarkClass)\n\n    if (Capacitor.isNativePlatform()) {\n      await StatusBar.setStyle({ style: Style.Light })\n      await StatusBar.setBackgroundColor({ color: '#f2f2f6ff' })\n    }\n  }\n\n  async function setDarkMode() {\n    document.documentElement.classList.add(ionDarkClass)\n\n    if (Capacitor.isNativePlatform()) {\n      await StatusBar.setStyle({ style: Style.Dark })\n      await StatusBar.setBackgroundColor({ color: '#ff000000' })\n    }\n  }\n\n  return {\n    color,\n    isDark,\n    setLightMode,\n    setDarkMode,\n\n    async toggleDark() {\n      const targetMode = color.value === 'dark' ? 'light' : 'dark'\n      color.preference = targetMode\n\n      if (targetMode === 'dark') {\n        setDarkMode()\n      }\n      else {\n        setLightMode()\n      }\n    },\n  }\n})\n"
  },
  {
    "path": "app/composables/db.ts",
    "content": "import { useStorage } from '@vueuse/core'\nimport { lastDbUpdated, namespace } from '~/constants'\nimport { db, initDb } from '~/utils/db'\n\nexport function useIndexedDB() {\n  const dbUpdated = useStorage(`${namespace}:lastDbUpdated`, lastDbUpdated)\n\n  return {\n    init: async () => {\n      const count = await db.recipes.count()\n      if (!count || dbUpdated.value !== lastDbUpdated) {\n        await initDb()\n        dbUpdated.value = lastDbUpdated\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "app/composables/global.ts",
    "content": "export const recipePanelRef = ref()\n"
  },
  {
    "path": "app/composables/helper.ts",
    "content": "import type { MaybeComputedElementRef } from '@vueuse/core'\nimport { isClient, useElementBounding } from '@vueuse/core'\n\n/**\n * trigger show invisible element\n * @param target\n *\n * @deprecated use scrollIntoView instead\n */\nexport function useInvisibleElement(target: MaybeComputedElementRef<HTMLElement>) {\n  const { top } = useElementBounding(target)\n\n  const isVisible = computed(() => {\n    return isClient ? window.scrollY < top.value : true\n  })\n\n  const show = () => {\n    if (isClient)\n      window.scrollTo(0, top.value)\n  }\n\n  return {\n    isVisible,\n    show,\n  }\n}\n"
  },
  {
    "path": "app/composables/incompatible-foods.ts",
    "content": "import type { IncompatibleRule } from '~/types'\nimport { computed, onMounted, readonly, ref } from 'vue'\nimport incompatibleFoodsData from '~/data/incompatible-foods.json'\n\n/**\n * 食物相克检测 composable\n */\nexport function useIncompatibleFoods() {\n  // 用于存储从 JSON 加载的相克规则\n  const incompatibleRules = ref<IncompatibleRule[]>([])\n  // 用于存储并显示给用户的警告信息\n  const warningMessage = ref<string>('')\n  // 加载状态\n  const isLoading = ref(true)\n\n  /**\n   * 在组件挂载后，加载食物相克数据\n   */\n  onMounted(() => {\n    try {\n      // 直接使用导入的数据\n      incompatibleRules.value = incompatibleFoodsData as IncompatibleRule[]\n    }\n    catch (error) {\n      console.error('Failed to load incompatible foods data:', error)\n    }\n    finally {\n      isLoading.value = false\n    }\n  })\n\n  /**\n   * 核心检测函数：检查当前选择的食材是否存在相克组合\n   * @param ingredients - 当前已选的食材列表\n   */\n  const checkIncompatibility = (ingredients: string[]) => {\n    // 重置警告信息\n    warningMessage.value = ''\n\n    // 如果食材少于2个或规则还没加载完成，无需检测\n    if (ingredients.length < 2 || isLoading.value) {\n      return\n    }\n\n    const foundRules: IncompatibleRule[] = []\n\n    const ingredientSet = new Set(ingredients)\n\n    for (const rule of incompatibleRules.value) {\n      // 检查规则中的两种食物是否都存在于我们的食材 Set 中\n      if (ingredientSet.has(rule.foodA) && ingredientSet.has(rule.foodB)) {\n        foundRules.push(rule)\n      }\n    }\n\n    // 如果找到相克组合，生成警告信息\n    if (foundRules.length > 0) {\n      if (foundRules.length === 1) {\n        const rule = foundRules[0]!\n        warningMessage.value\n          = `🚨 危险组合！\\n`\n            + `【${rule.foodA}】+ 【${rule.foodB}】= 有毒？！\\n`\n            + `${rule.reason}\\n`\n            + `换个搭配会更安全哦～`\n      }\n      else {\n        const warnings = foundRules.map(rule =>\n          `【${rule.foodA}】+ 【${rule.foodB}】（${rule.reason}）\\n`,\n        ).join('')\n        warningMessage.value\n          = `🚨 发现 ${foundRules.length} 个危险组合！\\n`\n            + `${warnings}`\n            + `建议调整搭配哦～`\n      }\n    }\n  }\n\n  // 计算属性：是否有警告信息\n  const hasWarning = computed(() => Boolean(warningMessage.value))\n\n  return {\n    incompatibleRules: readonly(incompatibleRules),\n    warningMessage: readonly(warningMessage),\n    hasWarning,\n    isLoading: readonly(isLoading),\n    checkIncompatibility,\n  }\n}\n"
  },
  {
    "path": "app/composables/index.ts",
    "content": "export * from './incompatible-foods'\nexport * from './store'\n\n// others is auto exported\n"
  },
  {
    "path": "app/composables/recipe.ts",
    "content": "import type { DbRecipeItem } from '~/utils/db'\nimport { useStorage } from '@vueuse/core'\nimport { namespace } from '~/constants'\n\n/**\n * 随机几道菜\n * @param total\n */\nexport function useRandomRecipe(total: Ref<number>) {\n  const randomRecipes = useStorage<(DbRecipeItem | undefined)[]>(`${namespace}:random:recipes`, [])\n  async function random() {\n    const length = await db.recipes.count()\n    const randomArr = generateRandomArray(length, total.value)\n    const result = await db.recipes.bulkGet(randomArr)\n    if (result)\n      randomRecipes.value = result.filter(item => !!item)\n  }\n\n  watch(total, () => {\n    random()\n  })\n\n  onMounted(() => {\n    // 如果没有随机菜谱，就生成一次\n    if (randomRecipes.value.length <= 0)\n      random()\n  })\n\n  return {\n    random,\n\n    randomRecipes,\n  }\n}\n"
  },
  {
    "path": "app/composables/store/app.ts",
    "content": "import { useStorage } from '@vueuse/core'\nimport { acceptHMRUpdate, defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { namespace } from '../../constants'\nimport { defaultSettings } from '../../utils/settings'\n\nexport const useAppStore = defineStore('app', () => {\n  const deferredPrompt = ref<Event | any>()\n  const settings = useStorage(`${namespace}:settings`, defaultSettings)\n\n  return {\n    deferredPrompt,\n\n    settings,\n  }\n})\n\nif (import.meta.hot)\n  import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot))\n"
  },
  {
    "path": "app/composables/store/favorite.ts",
    "content": "import type { RecipeItem } from '~/types'\nimport type { DbRecipeItem } from '~/utils/db'\nimport { useStorage } from '@vueuse/core'\nimport { namespace } from '~/constants'\n\nexport interface FavoriteEntry { id: number, time: number }\n\n// Store favorite entries with timestamp in localStorage\nconst rawFavorites = useStorage(`${namespace}:favorites`, [] as any)\n\n// Migration: if old format number[] exists, convert to FavoriteEntry[] with current time\nfunction ensureFavoriteEntries(): FavoriteEntry[] {\n  const now = Date.now()\n  const v = rawFavorites.value\n  if (Array.isArray(v)) {\n    if (v.length === 0)\n      return []\n    // old format: array of numbers\n    if (typeof v[0] === 'number') {\n      const migrated: FavoriteEntry[] = (v as number[]).map(id => ({ id, time: now }))\n      rawFavorites.value = migrated\n      return migrated\n    }\n    // new format\n    if (typeof v[0] === 'object' && v[0] && 'id' in v[0])\n      return v as FavoriteEntry[]\n  }\n  // fallback\n  rawFavorites.value = []\n  return []\n}\n\nexport const favoriteEntries = computed<FavoriteEntry[]>(() => ensureFavoriteEntries())\nexport const favoriteRecipeIds = computed<number[]>(() => favoriteEntries.value.map(e => e.id))\n\nfunction getId(item: RecipeItem | DbRecipeItem): number | null {\n  // Only support DbRecipeItem with numeric id for now\n  return typeof (item as DbRecipeItem).id === 'number' ? (item as DbRecipeItem).id! : null\n}\n\nexport function isFavorited(item: RecipeItem | DbRecipeItem) {\n  const id = getId(item)\n  if (id == null)\n    return false\n  return favoriteRecipeIds.value.includes(id)\n}\n\nexport function toggleFavorite(item: RecipeItem | DbRecipeItem) {\n  const id = getId(item)\n  if (id == null)\n    return\n  const list = ensureFavoriteEntries()\n  const idx = list.findIndex(e => e.id === id)\n  if (idx >= 0)\n    list.splice(idx, 1)\n  else\n    list.push({ id, time: Date.now() })\n  rawFavorites.value = list\n}\n\nexport function getFavoriteTime(item: RecipeItem | DbRecipeItem): number | null {\n  const id = getId(item)\n  if (id == null)\n    return null\n  const list = ensureFavoriteEntries()\n  const entry = list.find(e => e.id === id)\n  return entry?.time ?? null\n}\n"
  },
  {
    "path": "app/composables/store/history.ts",
    "content": "import type { RecipeItem } from '~/types'\nimport { useStorage } from '@vueuse/core'\nimport { namespace } from '~/constants'\n\nexport interface RecipeHistoryItem {\n  recipe: RecipeItem\n  time: number\n}\n\nexport const recipeHistories = useStorage<RecipeHistoryItem[]>(`${namespace}:history`, [])\n"
  },
  {
    "path": "app/composables/store/index.ts",
    "content": "export * from './app'\nexport * from './recipe'\nexport * from './user'\n"
  },
  {
    "path": "app/composables/store/recipe.ts",
    "content": "import type { RecipeItem, StuffItem } from '~/types'\nimport { useStorage } from '@vueuse/core'\nimport { acceptHMRUpdate, defineStore } from 'pinia'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { db } from '../../utils/db'\nimport { useAppStore } from './app'\n\nconst namespace = 'cook'\n\n/**\n * survival: 生存模式\n * strict: 严格\n * loose: 模糊\n */\nexport type SearchMode = 'survival' | 'loose' | 'strict'\n\nexport const useRecipeStore = defineStore('recipe', () => {\n  const { proxy } = useScriptGoogleTagManager()\n  const { settings } = useAppStore()\n\n  /**\n   * 搜索关键字\n   */\n  const keyword = ref('')\n\n  // can not exported\n  const curStuff = settings.keepLocalData ? useStorage(`${namespace}:stuff`, new Set<string>()) : ref(new Set<string>())\n  // const curTools = ref(new Set<string>())\n  const curTool = settings.keepLocalData ? useStorage(`${namespace}:tool`, '') : ref('')\n  const curMode = settings.keepLocalData ? useStorage<SearchMode>(`${namespace}:mode`, 'loose') : ref<SearchMode>('loose')\n\n  const selectedStuff = computed(() => Array.from(curStuff.value))\n  // const selectedTools = computed(() => Array.from(curTools.value))\n  // const selectedTools = ref('')\n\n  function toggleStuff(name: string) {\n    if (!curStuff.value)\n      return\n    if (curStuff.value.has(name))\n      curStuff.value.delete(name)\n    else\n      curStuff.value.add(name)\n  }\n\n  function toggleTools(name: string) {\n    if (curTool.value === name)\n      curTool.value = ''\n    else\n      curTool.value = name\n    // if (curTools.value.has(name))\n    //   curTools.value.delete(name)\n    // else\n    //   curTools.value.add(name)\n  }\n\n  function setMode(mode: SearchMode) {\n    curMode.value = mode\n  }\n\n  /**\n   * 重置\n   */\n  function reset() {\n    curStuff.value.clear()\n    // curTools.value.clear()\n    curTool.value = ''\n  }\n\n  function addStuff(name: string) {\n    curStuff.value.add(name)\n  }\n\n  const isSearching = ref(false)\n  /**\n   * 搜索菜谱\n   */\n  async function searchRecipes() {\n    isSearching.value = true\n    let result: RecipeItem[] = []\n\n    if (curMode.value === 'strict') {\n      result = await db.recipes.filter((item) => {\n        const stuffFlag = selectedStuff.value.every(stuff => item.stuff.includes(stuff))\n        const toolFlag = item.tools.includes(curTool.value)\n        return curTool.value ? (stuffFlag && toolFlag) : stuffFlag\n      }).toArray()\n    }\n    else if (curMode.value === 'loose') {\n      result = await db.recipes.filter((item) => {\n        const stuffFlag = selectedStuff.value.some(stuff => item.stuff.includes(stuff))\n        const toolFlag = Boolean(item.tools?.includes(curTool.value))\n\n        // 同时存在 厨具和材料，则同时判断\n        if (curTool.value && selectedStuff.value.length) {\n          return stuffFlag && toolFlag\n        }\n        else {\n          if (selectedStuff.value.length)\n            return stuffFlag\n          else if (curTool.value)\n            return toolFlag\n\n          return false\n        }\n      }).toArray()\n    }\n    // survival\n    else {\n      result = await db.recipes.filter((item) => {\n        const stuffFlag = item.stuff.every(stuff => selectedStuff.value.includes(stuff))\n        const toolFlag = item.tools?.includes(curTool.value)\n        return Boolean(curTool.value ? (stuffFlag && toolFlag) : stuffFlag)\n      }).toArray()\n    }\n\n    if (keyword.value)\n      result = result.filter(item => item.name.includes(keyword.value))\n\n    isSearching.value = false\n    return result\n  }\n\n  // 默认严格模式\n  const displayedRecipe = ref<RecipeItem[]>([])\n  // fix curStuff watch\n  watch(() => [keyword.value, selectedStuff.value, curTool.value, curMode.value], async () => {\n    displayedRecipe.value = [...(await searchRecipes())]\n  })\n\n  /**\n   * toggle tool\n   * @param item\n   */\n  const clickTool = (item: StuffItem) => {\n    const value = item.name\n    toggleTools(value)\n\n    proxy.dataLayer.push({\n      event: 'click',\n      category: `tool_${value}`,\n      action: 'click_tool',\n      label: '工具',\n    })\n    proxy.dataLayer.push({\n      event: 'click_tool',\n      action: item.name,\n    })\n  }\n\n  const recipesLength = ref(0)\n  onMounted(async () => {\n    db.recipes.count().then((count) => {\n      recipesLength.value = count\n    })\n\n    displayedRecipe.value = await searchRecipes()\n  })\n\n  return {\n    recipesLength,\n\n    keyword,\n    curTool,\n    curMode,\n    selectedStuff,\n\n    isSearching,\n\n    clearKeyWord: () => { keyword.value = '' },\n    toggleStuff,\n    toggleTools,\n    reset,\n    setMode,\n\n    addStuff,\n\n    // useRecipe\n    displayedRecipe,\n    clickTool,\n  }\n})\n\nif (import.meta.hot)\n  import.meta.hot.accept(acceptHMRUpdate(useRecipeStore, import.meta.hot))\n"
  },
  {
    "path": "app/composables/store/user.ts",
    "content": "import { acceptHMRUpdate, defineStore } from 'pinia'\n\nexport const useUserStore = defineStore('user', () => {\n  /**\n   * Current name of the user.\n   */\n  const savedName = ref('')\n  const previousNames = ref(new Set<string>())\n\n  const usedNames = computed(() => Array.from(previousNames.value))\n  const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value))\n\n  /**\n   * Changes the current name of the user and saves the one that was used\n   * before.\n   *\n   * @param name - new name to set\n   */\n  function setNewName(name: string) {\n    if (savedName.value)\n      previousNames.value.add(savedName.value)\n\n    savedName.value = name\n  }\n\n  return {\n    setNewName,\n    otherNames,\n    savedName,\n  }\n})\n\nif (import.meta.hot)\n  import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))\n"
  },
  {
    "path": "app/config/index.ts",
    "content": "export const tabRootPaths = ['/', '/home', '/apps', '/my', '/tabs']\n"
  },
  {
    "path": "app/config/pwa.ts",
    "content": "import type { ModuleOptions } from '@vite-pwa/nuxt'\nimport process from 'node:process'\nimport { appDescription, appName } from '../constants/index'\n\nconst scope = '/'\n\nexport const pwa: ModuleOptions = {\n  registerType: 'autoUpdate',\n  scope,\n  base: scope,\n  manifest: {\n    id: scope,\n    scope,\n    name: appName,\n    short_name: appName,\n    description: appDescription,\n    theme_color: '#ffffff',\n    icons: [\n      {\n        src: 'pwa-192x192.png',\n        sizes: '192x192',\n        type: 'image/png',\n      },\n      {\n        src: 'pwa-512x512.png',\n        sizes: '512x512',\n        type: 'image/png',\n      },\n      {\n        src: 'maskable-icon.png',\n        sizes: '512x512',\n        type: 'image/png',\n        purpose: 'any maskable',\n      },\n    ],\n  },\n  workbox: {\n    globPatterns: ['**/*.{js,css,html,txt,png,ico,svg}'],\n    navigateFallbackDenylist: [/^\\/api\\//],\n    navigateFallback: '/',\n    cleanupOutdatedCaches: true,\n    runtimeCaching: [\n      {\n        urlPattern: /^https:\\/\\/fonts.googleapis.com\\/.*/i,\n        handler: 'CacheFirst',\n        options: {\n          cacheName: 'google-fonts-cache',\n          expiration: {\n            maxEntries: 10,\n            maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days\n          },\n          cacheableResponse: {\n            statuses: [0, 200],\n          },\n        },\n      },\n      {\n        urlPattern: /^https:\\/\\/fonts.gstatic.com\\/.*/i,\n        handler: 'CacheFirst',\n        options: {\n          cacheName: 'gstatic-fonts-cache',\n          expiration: {\n            maxEntries: 10,\n            maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days\n          },\n          cacheableResponse: {\n            statuses: [0, 200],\n          },\n        },\n      },\n    ],\n  },\n  registerWebManifestInRouteRules: true,\n  writePlugin: true,\n  devOptions: {\n    enabled: process.env.VITE_PLUGIN_PWA === 'true',\n    navigateFallback: scope,\n  },\n}\n"
  },
  {
    "path": "app/constants/acknowledgements.ts",
    "content": "/**\n * 致谢名单配置\n */\n\nexport interface Acknowledgement {\n  /**\n   * 描述或贡献说明\n   */\n  description?: string\n  /**\n   * 头像或Logo URL\n   */\n  avatar?: string\n  /**\n   * 社交媒体链接\n   */\n  links?: {\n    /**\n     * 链接类型（用于显示图标）\n     */\n    type?: 'github' | 'bilibili' | 'weibo' | 'twitter' | 'wechat' | 'blog' | 'website' | 'email'\n    /**\n     * 显示文本\n     */\n    label: string\n    /**\n     * 链接地址\n     */\n    href: string\n    /**\n     * 打开方式\n     */\n    target?: '_blank' | '_self'\n  }[]\n}\n\n/**\n * 个人致谢名单（简单列表）\n */\nexport interface PersonalAcknowledgement {\n  /**\n   * 姓名\n   */\n  name: string\n  /**\n   * 个人链接（可选）\n   */\n  link?: string\n}\n\n/**\n * 致谢名单\n */\nexport const acknowledgements: Acknowledgement[] = [\n  {\n    description: '提供优质的菜谱数据来源',\n    links: [\n      {\n        type: 'bilibili',\n        label: '哔哩哔哩 美食专区',\n        href: 'https://www.bilibili.com/blackboard/dynamic/306882',\n        target: '_blank',\n      },\n    ],\n  },\n  {\n    description: '感谢所有为本项目贡献代码、提出建议的开发者们',\n    links: [\n      {\n        type: 'github',\n        label: 'Contributors',\n        href: 'https://github.com/YunYouJun/cook/graphs/contributors',\n        target: '_blank',\n      },\n    ],\n  },\n  {\n    description: '以下开源项目使得项目得以快速实现',\n    links: [\n      {\n        type: 'website',\n        label: 'Nuxt',\n        href: 'https://nuxt.com',\n        target: '_blank',\n      },\n      {\n        type: 'website',\n        label: 'Vue',\n        href: 'https://vuejs.org',\n        target: '_blank',\n      },\n      {\n        type: 'website',\n        label: 'UnoCSS',\n        href: 'https://unocss.dev',\n        target: '_blank',\n      },\n      {\n        type: 'website',\n        label: 'Ionic Framework',\n        href: 'https://ionicframework.com',\n        target: '_blank',\n      },\n      {\n        type: 'website',\n        label: 'Capacitor',\n        href: 'https://capacitorjs.com',\n        target: '_blank',\n      },\n      {\n        type: 'website',\n        label: 'VueUse',\n        href: 'https://vueuse.org',\n        target: '_blank',\n      },\n      // 其他\n      {\n        type: 'website',\n        label: '其他',\n        href: 'https://github.com/YunYouJun/cook/blob/main/package.json',\n      },\n    ],\n  },\n]\n\n/**\n * 个人致谢名单\n */\nexport const personalAcknowledgements: PersonalAcknowledgement[] = [\n  {\n    name: 'Runny',\n    link: 'https://weibo.com/runny',\n  },\n  {\n    name: '麒麟',\n  },\n  {\n    name: '晴方啾',\n  },\n  {\n    name: '课代表阿伟',\n  },\n]\n"
  },
  {
    "path": "app/constants/index.ts",
    "content": "export const appName = '食用手册'\nexport const appDescription = '好的，今天我们来做菜！'\n\nexport const namespace = 'cook'\nexport const lastDbUpdated = '2023-11-11 19:51:02'\n\nexport const icp = '苏ICP备17038157号'\n\nexport * from './acknowledgements'\nexport * from './links'\n\nexport const ionDarkClass = 'ion-palette-dark'\n"
  },
  {
    "path": "app/constants/links.ts",
    "content": "export const links = {\n  cook: 'https://cook.yunyoujun.cn',\n\n  /**\n   * 菜谱投稿链接\n   */\n  contribute: 'https://docs.qq.com/form/page/DWk9GWW9oTmlXZU9V',\n  /**\n   * 兔小巢反馈\n   */\n  feedback: 'https://support.qq.com/product/507827',\n  githubIssue: 'https://github.com/YunYouJun/cook/issues',\n  githubDiscussions: 'https://github.com/YunYouJun/cook/discussions',\n\n  /**\n   * changelog\n   */\n  changelog: 'https://docs.yunyoujun.cn/projects/cook/changelog',\n  releaseNotes: 'https://github.com/YunYouJun/cook/releases',\n\n  /**\n   * 企业微信 云乐坊 kf\n   */\n  wecomKf: 'https://work.weixin.qq.com/kfid/kfc9d4dcc4b6bd1c69e',\n\n  /**\n   * author\n   */\n  yyj: {\n    bilibili: 'https://space.bilibili.com/1579790',\n    bilibiliOpus: 'https://www.bilibili.com/opus/649847454294868008',\n    github: 'https://github.com/YunYouJun',\n    blog: 'https://www.yunyoujun.cn',\n\n    mp: 'https://mp.weixin.qq.com/s/Qdx76v1d0EwRLuX_rYtHFw',\n    /**\n     * 关于食用手册的前世今生与未来\n     */\n    mpArticle: 'https://mp.weixin.qq.com/s/Qdx76v1d0EwRLuX_rYtHFw',\n  },\n}\n"
  },
  {
    "path": "app/data/README.md",
    "content": "# Data\n\n菜谱数据：<https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS>\n\n> 下载为 `csv` 转换使用\n\n- [新菜谱反馈](https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic)\n- [反馈建议分享-兔小巢](https://support.qq.com/products/507827)\n\n## 说明\n\n- tags: 标签\n  - 杂烩：不会显示所有材料 emoji，而是显示 🍲\n\n## 数据库\n\n使用 Dexie.js 重构数据查询\n\n- [Dexie.js](https://github.com/dexie/Dexie.js): A Minimalistic Wrapper for IndexedDB\n"
  },
  {
    "path": "app/data/food.ts",
    "content": "import type { StuffItem } from '../types'\n\n/**\n * 素菜\n */\nexport const vegetable: StuffItem[] = [\n  {\n    name: '土豆',\n    emoji: '🥔',\n  },\n  {\n    name: '胡萝卜',\n    emoji: '🥕',\n  },\n  {\n    name: '花菜',\n    emoji: '🥦',\n  },\n  {\n    name: '白萝卜',\n    emoji: '🥣',\n  },\n  {\n    name: '西葫芦',\n    emoji: '🥒',\n  },\n  {\n    name: '番茄',\n    emoji: '🍅',\n    alias: '西红柿',\n  },\n  {\n    name: '芹菜',\n    emoji: '🥬',\n  },\n  {\n    name: '黄瓜',\n    emoji: '🥒',\n  },\n  {\n    name: '洋葱',\n    emoji: '🧅',\n  },\n  {\n    name: '莴笋',\n    emoji: '🎍',\n  },\n  {\n    name: '菌菇',\n    emoji: '🍄',\n  },\n  {\n    name: '茄子',\n    emoji: '🍆',\n  },\n  {\n    name: '豆腐',\n    emoji: '🍲',\n  },\n  {\n    name: '包菜',\n    emoji: '🥗',\n  },\n  {\n    name: '白菜',\n    emoji: '🥬',\n  },\n]\n\n/**\n * 荤菜\n */\nexport const meat: StuffItem[] = [\n  {\n    name: '午餐肉',\n    emoji: '🥓',\n  },\n  {\n    name: '香肠',\n    emoji: '🌭',\n  },\n  {\n    name: '腊肠',\n    emoji: '🌭',\n  },\n  {\n    name: '鸡肉',\n    emoji: '🐤',\n  },\n  {\n    name: '猪肉',\n    emoji: '🐷',\n  },\n  {\n    name: '鸡蛋',\n    emoji: '🥚',\n  },\n  {\n    name: '虾',\n    emoji: '🦐',\n  },\n  {\n    name: '牛肉',\n    emoji: '🐮',\n  },\n  {\n    name: '骨头',\n    emoji: '🦴',\n  },\n  {\n    name: '鱼（Todo）',\n    emoji: '🐟',\n  },\n]\n\n/**\n * 主食\n */\nexport const staple: StuffItem[] = [\n  {\n    name: '面食',\n    emoji: '🍝',\n  },\n  {\n    name: '面包',\n    emoji: '🍞',\n  },\n  {\n    name: '米',\n    emoji: '🍚',\n  },\n  {\n    name: '方便面',\n    emoji: '🍜',\n  },\n]\n\nexport const tools: StuffItem[] = [\n  {\n    name: '烤箱',\n    emoji: '',\n    icon: 'i-mdi-toaster-oven',\n  },\n  {\n    name: '空气炸锅',\n    emoji: '',\n    icon: 'i-fe-frying-pan',\n  },\n  {\n    name: '微波炉',\n    emoji: '',\n    icon: 'i-ic-outline-microwave',\n  },\n  {\n    name: '电饭煲',\n    emoji: '',\n    icon: 'i-gg-smart-home-cooker',\n  },\n  {\n    label: '一口能炒又能煮的大锅',\n    name: '一口大锅',\n    emoji: '',\n    icon: 'i-mdi-pot-steam-outline',\n  },\n]\n"
  },
  {
    "path": "app/data/incompatible-foods.csv",
    "content": "foodA,foodB,reason\n牛奶,韭菜,牛奶与韭菜同食会影响钙的吸收，降低营养价值。\n"
  },
  {
    "path": "app/data/recipe.csv",
    "content": "﻿name,stuff,bv,difficulty,tags,methods,tools,\n电饭煲版广式腊肠煲饭,腊肠、米,BV1NE411Q7Jj,简单,广式,煲,电饭煲,\n电饭煲版烧鸡,鸡肉、洋葱、菌菇,BV1T54y1U7Cu,简单,好吃,,电饭煲,\n电饭煲焖面,猪肉、面食,BV14b411q7rM,简单,筋道,,电饭煲,\n电饭煲版番茄牛腩焖饭,牛肉、番茄、米,BV1Bv411C7X3,简单,懒人,,电饭煲,\n电饭煲版蜜汁鸡翅,鸡肉,BV1dj411f7sR,简单,懒人,,电饭煲,\n电饭煲版南瓜鸡腿焖饭,鸡肉、洋葱、菌菇、米,BV1Bv411C7X3,简单,懒人,,电饭煲,\n电饭煲版土豆排骨焖饭,猪肉、土豆、米、腊肠,BV1Bv411C7X3,简单,懒人,,电饭煲,\n电饭煲版香菇腊肠焖饭,腊肠、菌菇、洋葱、米、土豆,BV1Bv411C7X3,简单,懒人,,电饭煲,\n电饭煲版香卤牛肉,牛肉、米,BV1dR4y1F7H2,简单,卤味,煮,电饭煲,\n电饭煲叉烧排骨,猪肉、洋葱,BV1Ab4y1p7dw,简单,下饭,,电饭煲,\n电饭煲版红烧肉,猪肉,BV1SW411t7AX,,,,电饭煲,\n电饭煲版可乐鸡翅,鸡肉,BV16J411U79k,,,,电饭煲,\n电饭煲版蛋糕（废手版）,鸡蛋、面食,BV1H7411V7Pz,简单,香软,,电饭煲,\n电饭煲卤菜（开店级别）,鸡肉、鸡蛋、米,BV1ZA411E7KT,简单,小吃,,电饭煲,\n电饭煲卤肉蛋豆腐,鸡肉、鸡蛋、豆腐、米,BV1aM4y1L7QR,简单,小吃,,电饭煲,\n电饭煲版罗宋汤,牛肉、番茄、洋葱、芹菜、胡萝卜、土豆、包菜、香肠,BV16Q4y1m7nU,简单,杂烩,,电饭煲,\n电饭煲版一只番茄饭,土豆、胡萝卜、香肠、番茄、鸡蛋、米,BV1dj411f7sR,简单,杂烩,,电饭煲,\n电饭煲茄子烧肉焖饭,猪肉、茄子、番茄、土豆、洋葱、米,BV18T4y1c7s1,普通,杂烩,,电饭煲,\n电饭煲版茶叶蛋,鸡蛋,BV12W411R7LA,,,,电饭煲,\n电饭煲版番茄炖排骨,番茄、猪肉、米,BV1av411r7mL,,,,电饭煲,\n电饭煲版番茄牛腩,番茄、牛肉、米,BV18i4y1g7sp,,,,电饭煲,\n电饭煲版干妈炒泡面,方便面、鸡蛋,BV1kW411r7Py,简单,,,电饭煲,\n电饭煲版红烧肉,猪肉、米,BV1SW411t7AX,,,,电饭煲,\n电饭煲版胡萝卜腊肠饭,胡萝卜、腊肠、米,BV1tW411b7LS,,,,电饭煲,\n电饭煲版卤鸡翅,鸡肉,BV1UK4y1Y7aC,简单,,卤,电饭煲,\n电饭煲版焖鸡腿土豆,鸡肉、土豆,BV1tR4y1g7kv,简单,,,电饭煲,\n电饭煲版蜜汁叉烧肉,猪肉、米,BV1ci4y1j7Rz,简单,,,电饭煲,\n电饭煲版蜜汁鸡腿,鸡肉,BV1A4411k7iz,普通,,,电饭煲,\n电饭煲版牛腩萝卜清汤,牛肉、白萝卜,BV16Q4y1m7nU,,,,电饭煲,\n电饭煲版茄子猪肉焖面,茄子、猪肉、面食,BV1ib41137ig,复杂,,,电饭煲,\n电饭煲版肉饼蛋,猪肉、鸡蛋,BV13W411a7Ax,普通,,,电饭煲,\n电饭煲版上海菜饭,猪肉、腊肠、米、包菜、莴笋,BV1mW411x7F7,,,,电饭煲,\n电饭煲版蒜蓉排骨,猪肉、米、,BV1Dv411T7bD,,,,电饭煲,\n电饭煲版土豆香肠焖饭,土豆、香肠、米,BV1Kv411q7BM,简单,,,电饭煲,\n电饭煲版吐司（尽量不做，废手）,鸡蛋、面包,BV1vY41137F1,困难,,,电饭煲,\n电饭煲叉烧肉（叉烧酱版）,猪肉、米,BV127411W75D,普通,,,电饭煲,\n电饭煲温泉蛋,鸡蛋,BV1EJ411Y7fU,,,,电饭煲,\n电饭煲窝蛋牛肉饭,牛肉、鸡蛋、米,BV1aA411q7b9,简单,,,电饭煲,\n电饭锅酱牛肉,牛肉、米饭,BV1sv411C7qW,,,,电饭煲,\n烤箱版M记同款薯条,土豆,BV1nA411P7aX,,同款,,烤箱,\n空心小薯片,土豆、鸡蛋,BV1N5411D79P,,零食,烘,烤箱,\n气泡小土豆,土豆,BV1sL41137jN,简单,零食,烘,烤箱,\n烤箱版M记同款麦乐鸡,土豆、鸡肉、鸡蛋,BV19v411J7MG,,同款,,烤箱,\n烤箱版KFC同款香辣鸡翅,鸡肉,BV1jA4y197RR,,同款,,烤箱,\n烤箱版M记同款薯饼,土豆,BV1qy4y1y7Mn,,同款,,烤箱,\n烤箱烧烤,土豆、胡萝卜、花菜、西葫芦、芹菜、洋葱、莴笋、菌菇、茄子、包菜、午餐肉、香肠、鸡肉、猪肉、虾、牛肉、鸡肉,BV17P4y1W7Ze,,杂烩,,烤箱,\n烤箱迎客菜,牛肉、茄子、鸡肉、鸡蛋、豆腐、菌菇、番茄,BV1ug4y1B7CR,,杂烩,,烤箱,\n爆浆鸡腿,鸡肉,BV1ju411X7mx,复杂,,烤,烤箱,\nBBQ烤鸡腿,鸡肉,BV1Zr4y1B7UQ,简单,,烤,烤箱,\nBBQ烟熏手撕猪肉,猪肉,BV1DV411x7SH,复杂,,烤,烤箱,\n馋嘴烤箱版烧烤,土豆、鸡肉,BV1wv411h7d6,普通,,烤,烤箱,\n脆皮五花肉和蜜汁叉烧,猪肉,BV17u411i7jF,普通,,烤,烤箱,\n法棍,面包,BV1Kc411h7eJ,普通,,,烤箱,\n汉堡面包,鸡蛋、面包,BV1ga411h7cw,普通,,,烤箱,\n火山熔岩蛋,土豆、鸡蛋,BV1Xy4y1L7ok,困难,,烘,烤箱,\n烤布丁,鸡蛋,BV1TT4y127vu,,,烘,烤箱,\n烤箱版爆浆鸡腿,鸡肉,BV1ju411X7mx,复杂,,,烤箱,\n烤箱版橙香鸡翅,鸡肉,BV1Mb4y1Q7DU,简单,,烤,烤箱,\n烤箱版脆皮五花肉,猪肉,BV1VS4y137dE,普通,,烤,烤箱,\n烤箱版黑椒牛肉土豆焖饭,牛肉、土豆、胡萝卜、米、鸡蛋,BV14x411D7uN,,,,烤箱,\n烤箱版烤串,猪肉、花菜,BV17P4y1W7Ze,简单,,烤,烤箱,\n烤箱版烤花菜,花菜,BV1Nv411Y7kV,简单,,,烤箱,\n烤箱版烤西葫芦,西葫芦,BV1Nv411Y7kV,简单,,,烤箱,\n烤箱版烤洋葱,洋葱,BV1Nv411Y7kV,简单,,,烤箱,\n烤箱版麻薯,鸡蛋,BV1CR4y1t7CM,普通,,,烤箱,\n烤箱版气泡土豆,土豆,BV1mR4y1E7VH,普通,,烤,烤箱,\n烤箱版烧烤,鸡肉、番茄、土豆,BV1Ra4y1x7Ua,简单,,烤,烤箱,\n烤箱版薯条,土豆,BV1nA411P7aX,普通,,,烤箱,\n烤箱版吮指鸡翅,鸡肉、土豆,BV12W411A7w8,普通,,烤,烤箱,\n烤箱版蒜蓉烤茄子,茄子,BV1Ff4y1P7ng,简单,,,烤箱,\n烤箱鸡翅根,鸡肉,BV1et411R7kG,普通,,烤,烤箱,\n烤箱鸡蛋,鸡蛋,BV1Vp4y167P4,简单,,烤,烤箱,\n烤箱腊肠焖饭,腊肠、花菜、米,BV14x411D7uN,,,,烤箱,\n烤箱蜜汁鸡腿,鸡肉,BV1WW411S72C,普通,,烤,烤箱,\n烤箱面包,面包,BV1wJ411E7YS,简单,,烤,烤箱,\n烤箱牛肉,牛肉,BV1L64y1r7bk,复杂,,烤,烤箱,\n烤箱茄子,茄子,BV165411N7fQ,简单,,烤,烤箱,\n烤箱炸鸡米花,鸡肉,BV1HE411L7T2,普通,,烤,烤箱,\n炼猪油,猪肉,BV12F411v74c,简单,,烤,烤箱,\n流心培根吐司,面包、鸡蛋、猪肉,BV1aZ4y1R7SL,普通,,,烤箱,\n冒油牛排,牛肉，土豆,BV1754y1E7Mj,普通,,烤,烤箱,\n年轮蛋糕（难度max）,面包、鸡蛋,BV1WQ4y1q7iz,困难,,,烤箱,\n牛奶烤土司,鸡蛋，面包,BV1KF411Y7WP,普通,,烤,烤箱,\n千层土豆烘蛋,土豆、鸡蛋,BV1fY41187Xo,困难,,烘,烤箱,\n蔬菜团子,包菜,BV1S44y1P7Qp,简单,,烤,烤箱,\n蒜香芝士面包虾,面包、虾,BV1gB4y1c7GY,困难,,,烤箱,\n土豆面包,土豆、面包、鸡蛋,BV1H4411572T,普通,,,烤箱,\n土豆挞,土豆,BV1fY41187Xo,普通,,烘,烤箱,\n土豆芝士棒,土豆、香肠,BV1DL4y1j7Nu,困难,,烘,烤箱,\n早餐白面包（无鸡蛋版）,面包,BV1Xg4y187BQ,普通,,,烤箱,\n猪肉脯,猪肉,BV174411Z7ar,普通,,烤,烤箱,\n欧洲家庭版欧包,鸡蛋、面包,BV1SJ411L7dG,普通,,,烤箱、电饭煲,\n土豆杂蔬烘蛋,土豆、番茄、胡萝卜、香肠,BV1Y5411S7mu,简单,早餐,烘,烤箱、空气炸锅,\n午餐肉串,午餐肉,BV1MF411t7Ba,简单,零食,炸,空气炸锅,\n午餐肉薯条,午餐肉、鸡蛋、面包,BV1ti4y1f7nE,简单,零食,炸,空气炸锅,\n空气炸锅蒜蓉茄子,茄子,BV1aQ4y1D77r,简单,深夜美食,炸,空气炸锅,\n空气炸锅版M记同款薯条,土豆,BV1tq4y1j72x,,同款,,空气炸锅,\n空气炸锅版M记同款薯饼,土豆,BV1Cb4y167zK,,同款,,空气炸锅,\n空气炸锅版KFC同款奥尔良烤翅,鸡肉,BV1EQ4y1a7C9,,同款,,空气炸锅,\n空气炸锅炸包菜,包菜,BV1eR4y1j715,简单,童年风味,炸,空气炸锅,\n空气炸锅版可乐鸡翅,鸡肉,BV1eT4y1S7Jn,,,,空气炸锅,\n空气炸锅烤时蔬,菌菇、花菜、胡萝卜、西葫芦、包菜、土豆、芹菜、莴笋,BV1a5411u7TH,简单,杂烩,炸,空气炸锅,\n空气炸锅炸万物,土豆、胡萝卜、花菜、西葫芦、芹菜、洋葱、莴笋、菌菇、茄子、包菜、午餐肉、香肠、鸡肉、猪肉、虾、牛肉、鸡肉、番茄、豆腐、面包,BV1wL411F7Cd,简单,杂烩,炸,空气炸锅,\n空气炸锅炸包菜串,包菜,BV1DL4y137CD,简单,,炸,空气炸锅,\n空气炸锅炸馒头,面食,BV1DL4y137CD,简单,,炸,空气炸锅,\n空气炸锅炸香肠串,香肠,BV1DL4y137CD,简单,,炸,空气炸锅,\n虾肉茄子卷,虾、茄子、胡萝卜、鸡蛋,BV1aB4y1U7zQ,普通,,炸,空气炸锅,\n家庭版麦乐鸡块,鸡肉、鸡蛋、土豆、洋葱,BV1uu411v7Qf,普通,,炸,空气炸锅,\n焦糖吐司布丁,鸡蛋、面包、豆腐,BV1sq4y197TK,,,,空气炸锅,\n空气炸锅奥尔良炸鸡腿,鸡肉,BV1JU4y1G7hq,简单,,炸,空气炸锅,\n空气炸锅爆浆巴斯克芝士蛋糕,鸡蛋,BV1Ja411v7DK,,,,空气炸锅,\n空气炸锅炒饭,米、胡萝卜、香肠、鸡蛋,BV1oL4y1T7BX,简单,,炸,空气炸锅,\n空气炸锅炒面,方便面,BV11P4y1u7R1,简单,,炸,空气炸锅,\n空气炸锅炒牛河,面食、牛肉,BV1k44y1P7VY,简单,,炸,空气炸锅,\n空气炸锅脆皮烤肉,猪肉,BV1qU4y1f7G1,简单,,炸,空气炸锅,\n空气炸锅蛋黄酥,虾、土豆,BV1yS4y1u7M6,简单,,炸,空气炸锅,\n空气炸锅地三鲜,土豆、茄子,BV1pf4y1T7bx,简单,,炸,空气炸锅,\n空气炸锅法式吐司,面包、鸡蛋,BV1Fh41187GB,,,,空气炸锅,\n空气炸锅蜂蜜小面包,鸡蛋,BV1Ja411v7DK,,,,空气炸锅,\n空气炸锅干锅虾烤肉串,虾、猪肉,BV1Ka41117mc,,,,空气炸锅,\n空气炸锅虾球,土豆、虾,BV1L3411573m,简单,,炸,空气炸锅,\n空气炸锅虎皮凤爪,鸡肉,BV1VR4y1s7XP,简单,,炸,空气炸锅,\n空气炸锅鸡蛋豆腐羹,鸡蛋、豆腐、猪肉,BV1Hq4y137Vu,,,,空气炸锅,\n空气炸锅减脂餐,菌菇、虾,BV1CY4y1p7qP,简单,,炸,空气炸锅,\n空气炸锅减脂餐,包菜、菌菇、鸡肉,BV1w44y1V7S5,简单,,炸,空气炸锅,\n空气炸锅烤豆腐,豆腐,BV19L411x7bB,简单,,炸,空气炸锅,\n空气炸锅烤鸡腿,鸡肉,BV1Zr4y1B7UQ,普通,,炸,空气炸锅,\n空气炸锅烤鸡胸肉,鸡肉,BV1dF411q7of,简单,,炸,空气炸锅,\n空气炸锅烤面包,面包,BV1T34y1b7FS,简单,,炸,空气炸锅,\n空气炸锅烤蘑菇,菌菇,BV1Jq4y147oM,简单,,炸,空气炸锅,\n空气炸锅烤牛奶,鸡蛋,BV1ub4y1C7bp,简单,,炸,空气炸锅,\n空气炸锅烤排骨,猪肉,BV1H3411J7TH,简单,,炸,空气炸锅,\n空气炸锅烤茄子,茄子,BV1gP4y1s7GJ,简单,,炸,空气炸锅,\n空气炸锅烤肉串,猪肉,BV1WY411G7aX,简单,,炸,空气炸锅,\n空气炸锅烤土豆,土豆,BV1E5411D7RE,简单,,炸,空气炸锅,\n空气炸锅烤五花肉,猪肉,BV1Jq4y147oM,简单,,炸,空气炸锅,\n空气炸锅烤猪蹄,猪肉,BV11q4y1Y7hW,简单,,炸,空气炸锅,\n空气炸锅辣子鸡,鸡肉,BV1yL4y1L71E,简单,,炸,空气炸锅,\n空气炸锅嫩鸡排,鸡肉,BV1L3411W7Nr,,,,空气炸锅,\n空气炸锅气泡面包,面包,BV1Lu411z7FK,简单,,炸,空气炸锅,\n空气炸锅全麦面包,面包,BV1VY411E74H,简单,,炸,空气炸锅,\n空气炸锅肉末豆腐,豆腐,BV1Ja411C75G,简单,,炸,空气炸锅,\n空气炸锅酸奶蛋糕,鸡蛋,BV1HL4y177B3,简单,,炸,空气炸锅,\n空气炸锅蒜香排骨,猪肉,BV1v54y1B7cA,简单,,炸,空气炸锅,\n空气炸锅糖醋里脊,猪肉,BV1rS4y137gC,简单,,炸,空气炸锅,\n空气炸锅甜点（没酵母别选）,鸡蛋,BV1Ja411v7DK,,,,空气炸锅,\n空气炸锅新年硬菜,虾、猪肉,BV1Mq4y1h78M,简单,,炸,空气炸锅,\n空气炸锅早餐饼,鸡蛋、西葫芦、胡萝卜,BV1mu411q715,简单,,炸,空气炸锅,\n空气炸锅炸包菜,包菜,BV1NB4y1N7SH,简单,,炸,空气炸锅,\n空气炸锅炸低卡食材,包菜、菌菇,BV1cF411s7QH,简单,,炸,空气炸锅,\n空气炸锅炸豆腐,豆腐,BV1eR4y1j715,简单,,炸,空气炸锅,\n空气炸锅炸个荤素搭配,土豆、香肠、鸡肉、虾、鸡蛋、花菜,BV18q4y117Cm,简单,,炸,空气炸锅,\n空气炸锅炸鸡,鸡肉,BV1S44y1j7YM,简单,,炸,空气炸锅,\n空气炸锅炸鸡翅,鸡肉,BV14b4y1p7XS,简单,,炸,空气炸锅,\n空气炸锅炸家常蔬菜,花菜、豆腐、鸡肉、菌菇,BV1FU4y1Z7T1,简单,,炸,空气炸锅,\n空气炸锅炸金针菇,菌菇,BV1x4411V7at,简单,,炸,空气炸锅,\n空气炸锅炸茄盒,茄子、猪肉,BV1xP4y1J7eJ,简单,,炸,空气炸锅,\n空气炸锅炸蔬菜,鸡蛋、黄瓜,BV1qr4y1p7BX,普通,,炸,空气炸锅,\n空气炸锅炸薯角,土豆,BV1eR4y1j715,简单,,炸,空气炸锅,\n空气炸锅炸薯条,土豆,BV1tq4y1j72x,简单,,炸,空气炸锅,\n空气炸锅炸素菜,豆腐、洋葱、茄子、菌菇、鸡蛋、米,BV1Ap4y1b7a6,简单,,炸,空气炸锅,\n空气炸锅炸酸奶,鸡蛋,BV1xL411A7xG,简单,,炸,空气炸锅,\n空气炸锅炸土豆豆腐,豆腐、虾、土豆、鸡蛋,BV15Z4y1D73J,简单,,炸,空气炸锅,\n空气炸锅炸吐司,面包、鸡蛋,BV1aQ4y1D77r,简单,,炸,空气炸锅,\n空气炸锅炸五花肉花菜,猪肉、花菜,BV1Jq4y147oM,简单,,炸,空气炸锅,\n空气炸锅炸鲜奶,鸡蛋,BV1AZ4y1R7T2,简单,,炸,空气炸锅,\n空气炸锅炸香肠鸡翅,鸡蛋、香肠、鸡肉,BV15r4y1W7vA,简单,,炸,空气炸锅,\n空气炸锅炸小吃一条街,包菜、香肠、鸡蛋,BV1DL4y137CD,简单,,炸,空气炸锅,\n空气炸锅做蛋挞,鸡蛋,BV1A44y1P7JY,简单,,炸,空气炸锅,\n蜜汁脆皮烤鸡,鸡肉,av895289430,简单,,炸,空气炸锅,\n炸锅版吮指原味鸡,鸡肉,BV155411S7Q1,简单,,炸,空气炸锅,\n微波炉版番茄焖饭,香肠、番茄,BV193411W7nb,普通,减脂餐,微波加热,微波炉,\n微波炉版菌菇鸡肉焖饭,鸡肉、胡萝卜、菌菇、米,BV193411W7nb,普通,减脂餐,微波加热,微波炉,\n微波炉版盐烤土豆,土豆,BV1Cf4y1R7pe,,零食,微波加热,微波炉,\n微波炉版荷包蛋方便面,鸡蛋、方便面、香肠,BV18v411i7JM,简单,速食,微波加热,微波炉,\n微波炉版KFC同款土豆泥,土豆,BV1Ji4y1773B,简单,同款,微波加热,微波炉,\n微波炉版乐事同款薯片,土豆,BV1Ms411D7Qg,,同款,,微波炉,\n微波炉版FKC同款嫩牛五方,牛肉、番茄、面食,BV1Ji4y1773B,,同款,,微波炉,\n微波炉版可乐鸡翅,鸡肉、洋葱,BV1jJ41127ML,,,,微波炉,\n微波炉版蔬菜脆片,土豆、西葫芦、黄瓜,BV1n441187r3,,杂烩,微波加热,微波炉,\n微波炉火锅,菌菇、牛肉、方便面、虾、鸡肉、猪肉、午餐肉、莴笋、豆腐、土豆,BV1o34y1R7BC,,杂烩,微波加热,微波炉,\n微波炉版鸡蛋羹,鸡蛋,BV1HL411E7MM,简单,早饭,微波加热,微波炉,\n微波炉版土豆浓汤,土豆、胡萝卜,BV1HL411E7MM,简单,早饭,微波加热,微波炉,\n微波炉版煲仔饭,米、香肠、鸡蛋,BV145411f7R4,简单,主食,微波加热,微波炉,\n微波炉版燕麦粥,米,BV1cF411Y7pd,,主食,微波加热,微波炉,\n微波炉版叉烧肉,猪肉、米,BV1Hi4y1s7Sc,,,微波加热,微波炉,\n微波炉版蛋炒饭,米、鸡蛋、香肠,BV1T34y147qU,简单,,微波加热,微波炉,\n微波炉版番茄鸡蛋汤,番茄、鸡蛋,BV1qx411n7QF,,,,微波炉,\n微波炉版番茄肉盒,番茄、猪肉、青椒,BV194411R7aH,,,微波加热,微波炉,\n微波炉版肥牛饭,牛肉、洋葱、鸡蛋、米,BV1gh411Y7TU,,,,微波炉,\n微波炉版干脆面,面食,BV1pL4y1j7oe,,,微波加热,微波炉,\n微波炉版虾吃豆腐 ,虾、豆腐,BV1VW41157uy,,,,微波炉,\n微波炉版红烧茄子,茄子、洋葱、米,BV17Q4y1y7NC,普通,,微波加热,微波炉,\n微波炉版厚蛋烧,鸡蛋、包菜,BV1bT4y1o7dv,普通,,微波加热,微波炉,\n微波炉版厚蛋烧（没芝士就别选了）,鸡蛋、包菜、香肠,BV1bT4y1o7dv,,,,微波炉,\n微波炉版胡萝卜蒸菜,胡萝卜、面食,BV1A94y1o7aW,简单,,微波加热,微波炉,\n微波炉版鸡胸肉脆片,鸡肉,BV1xu41197tU,,,,微波炉,\n微波炉版鸡胸肉丸,鸡肉,BV1xu41197tU,,,,微波炉,\n微波炉版烤鸡翅,鸡肉,BV1bT4y1o7dv,,,,微波炉,\n微波炉版辣烤豆腐,豆腐、虾,BV17Q4y1y7NC,普通,,微波加热,微波炉,\n微波炉版麻辣排骨,猪肉,BV1c44y1T79A,简单,,微波加热,微波炉,\n微波炉版蘑菇,菌菇,BV1Nq4y1i72u,简单,,微波加热,微波炉,\n微波炉版嫩豆腐,豆腐,BV1DZ4y1o7ZM,简单,,微波加热,微波炉,\n微波炉版牛奶炖饭,米,BV1W7411c7As,简单,,微波加热,微波炉,\n微波炉版日式嫩蒸鸡胸肉,鸡肉,BV1xu41197tU,,,,微波炉,\n微波炉版烧烤茄子,茄子,BV19x411H7ig,,,,微波炉,\n微波炉版蒜香烤茄子,茄子,BV19x411H7ig,简单,,微波加热,微波炉,\n微波炉版蒜香琵琶腿,鸡肉,BV1qx411777p,,,,微波炉,\n微波炉版吐司杯,鸡蛋、面包,BV1CS4y1k7Xi,,,微波加热,微波炉,\n微波炉版五花肉,猪肉、米,BV1hL4y137Zk,,,微波加热,微波炉,\n微波炉版香干,豆腐,BV1F44y1T7sQ,,,微波加热,微波炉,\n微波炉版香辣豆腐,豆腐、米,BV1bT4y1o7dv,,,,微波炉,\n微波炉版香嫩鸡胸肉,鸡肉,BV1xu41197tU,,,,微波炉,\n微波炉版一个番茄焖饭,番茄、米、胡萝卜、香肠、腊肠、土豆,BV193411W7nb,,,,微波炉,\n微波炉版鱼香肉块,胡萝卜、鸡肉,BV1qt411X7Do,,,,微波炉,\n微波炉版照烧鸡腿饭,米、鸡肉、胡萝卜、花菜,BV16Z4y1V774,,,,微波炉,\n微波炉版蒸蛋羹,鸡蛋,BV19T4y1D7Zd,,,,微波炉,\n微波炉版煮米饭,米,BV193411W7nb,,,,微波炉,\n水煮肉片,猪肉、芹菜、莴笋,BV1ZZ4y1379N,普通,川菜,炒,一口大锅,\n脆口黄瓜,黄瓜,BV1Tb4y1X7ow,简单,脆口,凉拌,一口大锅,\n白萝卜汤,白萝卜,BV1HJ411L7xA,简单,单一食材,煮,一口大锅,\n白灼大虾,虾,BV1Sr4y1m7zF,普通,单一食材,灼,一口大锅,\n炒包菜,包菜,BV1K5411L7Xn,简单,单一食材,炒,一口大锅,\n番茄酱,番茄、面食、米,BV1h4411c7TV,简单,单一食材,煮,一口大锅,\n黑椒土豆泥,土豆,BV1q7411K7gr,简单,单一食材,蒸,一口大锅,\n红烧茄子,茄子,BV1ot4y1S7Hf,普通,单一食材,烧,一口大锅,\n酱筒骨,骨头,BV1Ai4y187KE,普通,单一食材,炖,一口大锅,\n酱土豆,土豆,BV1YD4y1c7ip,简单,单一食材,炒,一口大锅,\n酱油炖白萝卜,白萝卜,BV1us411h7E8,简单,单一食材,炖,一口大锅,\n椒盐炸蘑菇,菌菇、鸡蛋,BV1P54y1L7FS,简单,单一食材,炸,一口大锅,\n浇汁西兰花,花菜,BV1jq4y1M7jR,简单,单一食材,煮,一口大锅,\n金汤虾片,虾,BV1mQ4y1e7T8,困难,单一食材,煮,一口大锅,\n老奶洋芋,土豆,BV1fY41187Xo,简单,单一食材,炒,一口大锅,\n麻辣土豆片,土豆,BV1fY41187Xo,简单,单一食材,煮,一口大锅,\n茄子焖饭,茄子,BV1k7411m7nv,简单,单一食材,烧,一口大锅,\n清炖肉骨头,骨头,BV1SQ4y1m73v,普通,单一食材,炖,一口大锅,\n薯球,土豆,BV1bY4y1H7L9,普通,单一食材,炸,一口大锅,\n蒜蓉西兰花,花菜,BV1fy4y1W7z5,简单,单一食材,炒,一口大锅,\n土豆派,土豆,BV1bY4y1H7L9,简单,单一食材,烘,一口大锅,\n炸厚土豆片,土豆,BV1mt4y1i7Zr,简单,单一食材,煎,一口大锅,\n蒸腊肠,腊肠、米,BV1YK4y1e7bM,简单,单一食材,蒸,一口大锅,\n蒜香牛奶焗胡萝卜,胡萝卜、洋葱、鸡蛋,BV1yF411u7Me,普通,法餐,焗,一口大锅,\n胡萝卜炖牛肉,胡萝卜、牛肉、洋葱,BV1UR4y1V7nV,困难,法式,炖,一口大锅,\n葱油黄瓜,黄瓜,BV1954y1b7dv,简单,国宴菜,,一口大锅,\n黄瓜翡翠羹,黄瓜、鸡肉,BV1y3411W7Mz,困难,国宴菜,煮,一口大锅,\n烧汁牛肉粒,牛肉、洋葱、菌菇,BV1NU4y1d7ot,普通,国宴菜,炒,一口大锅,\n火烧云油焖鸡,土豆、番茄、鸡肉,BV1fY41187Xo,普通,好吃,煮,一口大锅,\n蒜香口蘑牛肉粒,菌菇、牛肉,BV1Nm4y197ev,简单,荤素搭配,炒,一口大锅,\n6阶番茄炒蛋,番茄、鸡蛋,BV1Py4y1S7EF,困难,家常菜,炒,一口大锅,\n白萝卜烧鸡块,白萝卜、鸡肉,BV1U3411e7Jv,普通,家常菜,烧,一口大锅,\n番茄炒蛋,番茄、鸡蛋,BV1rf4y1872R,普通,家常菜,炒,一口大锅,\n番茄鸡蛋,番茄、鸡蛋、米,BV11y4y167Wa,简单,家常菜,炒,一口大锅,\n番茄土豆炖牛腩,番茄、土豆、牛肉,BV1344y1n73V,普通,家常菜,炖,一口大锅,\n回锅肉炒包菜,包菜、猪肉,BV1EK411s75a,普通,家常菜,炒,一口大锅,\n腊肠蒸蛋,腊肠、鸡蛋,BV1Zp4y1Q7wu,简单,家常菜,蒸,一口大锅,\n拍辣椒炒午餐肉,午餐肉,BV1K4411Z7do,简单,家常菜,炒,一口大锅,\n青椒炒腊肠,腊肠、米,BV1ZK4y1L7pp,普通,家常菜,炒,一口大锅,\n热炒莴笋叶,莴笋,BV1xJ411M7bc,简单,家常菜,炒,一口大锅,\n素蟹粉,胡萝卜、土豆,BV1Zu411e7fp,简单,家常菜,炒,一口大锅,\n酸辣土豆丝,土豆,BV1wq4y1W7QA,普通,家常菜,炒,一口大锅,\n蒜蓉开背虾,虾,BV1DR4y1p7j4,普通,家常菜,炒,一口大锅,\n土豆烧包菜,包菜、土豆,BV1Ug4y1B7Uj,普通,家常菜,烧,一口大锅,\n土豆烧红肉,土豆、猪肉,BV1fY41187Xo,普通,家常菜,炖,一口大锅,\n莴笋炒蛋,莴笋、鸡蛋、胡萝卜,BV1C7411J7cb,普通,家常菜,炒,一口大锅,\n油焖大虾,虾、米,BV1DP4y1s7zu,普通,家常菜,焖,一口大锅,\n陈氏醋卤面,面食、猪肉,BV1tQ4y1M76f,普通,家传秘方,拌,一口大锅,\n白灼西兰花,花菜,BV1o4411A7dC,简单,健康,煮,一口大锅,\n芹菜牛肉卷,芹菜、牛肉,BV1Tv411L7V9,,健康餐,,一口大锅,\n减脂胡萝卜丝,胡萝卜,BV1UQ4y1i7JY,普通,减脂,蒸,一口大锅,\n醋溜西葫芦,西葫芦,BV1Rv411G7fD,普通,开胃,炒,一口大锅,\n餐蛋面,午餐肉、鸡蛋、方便面,BV1pT4y1F7p5,简单,快手菜,煮,一口大锅,\n鸡蛋煎午餐肉,午餐肉、鸡蛋,BV1aK4y177Xm,简单,快手菜,煎,一口大锅,\n金针菇菜卷,菌菇、胡萝卜、包菜,BV1aW411H7fZ,简单,快手菜,,一口大锅,\n口蘑鸡胸肉,菌菇、鸡肉,BV1AP4y1w7y6,简单,快手菜,煎,一口大锅,\n茄汁西葫芦,西葫芦、番茄,BV17W411g71Q,普通,快手菜,炒,一口大锅,\n番茄浓汤面,面食、番茄、鸡蛋,BV1P54y1H7oG,简单,懒人,煮,一口大锅,\n茄汁汤面,面食、番茄、鸡蛋,BV1Z7411G7AW,简单,懒人,煮,一口大锅,\n清汤面,面食、鸡蛋,BV1Yb411T7ED,简单,懒人,煮,一口大锅,\n鸡蛋拌包菜,包菜、鸡蛋,BV1Q64y1B7eq,简单,凉菜,拌,一口大锅,\n酱萝卜,白萝卜,BV1BK4y1Z7rF,简单,凉菜,腌,一口大锅,\n凉拌包菜,包菜,BV1HY411n7dV,简单,凉菜,拌,一口大锅,\n凉拌芹菜叶,芹菜,av976520411,简单,凉菜,拌,一口大锅,\n凉拌莴笋丝,莴笋,BV1Es411j7oy,简单,凉菜,凉拌,一口大锅,\n凉拌西葫芦,西葫芦,BV175411U7LU,简单,凉菜,凉拌,一口大锅,\n麻酱拌西葫芦丝,西葫芦,BV1Th411U7nW,简单,凉菜,凉拌,一口大锅,\n炝拌芹菜,芹菜,av845641799,简单,凉菜,拌,一口大锅,\n土豆拌茄子,土豆、茄子,BV1VT4y1J7Y8,简单,凉菜,蒸,一口大锅,\n香菜拌牛肉,牛肉,BV1144y1n7ZV,简单,凉菜,凉拌,一口大锅,\n小菜炝拌萝卜条,白萝卜,BV1nS4y1X7tM,简单,凉菜,拌,一口大锅,\n海苔虾饼,虾、胡萝卜,BV16T4y1i78D,困难,零食,炸/煎,一口大锅,\n灵魂土豆丸子,土豆,BV1Z64y1674r,,零食,蒸,一口大锅,\n薯塔,土豆,BV1uF411v7SQ,普通,零食,炸,一口大锅,\n盖码莴笋,莴笋、猪肉,BV1aV411v7xw,简单,清爽,盖浇,一口大锅,\n大根烧,白萝卜,BV1TW411e7Ln,普通,日式,烧,一口大锅,\n名古屋鸡翅,鸡肉,BV1ET4y1A7Xd,普通,日式,炸,一口大锅,\n日式炖白萝,白萝卜,BV17b411B7H1,简单,日式,炖,一口大锅,\n炸虾天妇罗,虾,BV1e5411t7LY,困难,日式,炸,一口大锅,\n可乐饼,土豆、洋葱、肉、鸡蛋,BV17x411U75q,普通,日式菜,炸,一口大锅,\n清炒莴笋丝,莴笋、胡萝卜,BV1qK411H7RL,简单,爽口,炒,一口大锅,\n莴笋泡菜,莴笋,BV1h741127rS,简单,爽口,泡菜,一口大锅,\n口蘑汤,菌菇,BV1e64y1h776,简单,汤,煎、炖,一口大锅,\n蘑菇浓汤,菌菇、洋葱、芹菜,BV1Ut411C7Cu,普通,汤,炖,一口大锅,\nKFC同款香辣鸡翅,鸡肉,BV1A7411u7Ji,普通,同款,炸,一口大锅,\nM记同款薯条,土豆,BV1Jx411x7iU,,同款,,一口大锅,\n乐事同款原味薯片,土豆、黄瓜,BV1vR4y1N7fN,简单,同款,,一口大锅,\nM记同款麦乐鸡,鸡肉、鸡蛋,BV1KA411e7Gr,,同款,,一口大锅,\n洋芋擦擦,土豆、胡萝卜,BV1fY41187Xo,普通,西北特色,蒸,一口大锅,\n白萝卜炒肉片,白萝卜、猪肉,BV1qL4y1i7xD,普通,下饭,炒,一口大锅,\n爆汁茄子,茄子、米,BV1rL4y1L751,简单,下饭,烧,一口大锅,\n番茄肉酱,番茄、芹菜、洋葱、猪肉、米、面食,BV1hF411t7hS,普通,下饭,烧,一口大锅,\n番茄土豆炖牛腩,牛肉、土豆、番茄、洋葱,BV1344y1n73V,普通,下饭,煮,一口大锅,\n干锅土豆片,土豆、猪肉、米,BV1ZZ4y1A7HK,简单,下饭,炒,一口大锅,\n干锅土豆五花肉,猪肉、土豆、洋葱,BV1CV411W7AD,简单,下饭,炒,一口大锅,\n红烧肉,猪肉,BV1vk4y127ZB,简单,下饭,煮,一口大锅,\n胡萝卜炒里脊肉,胡萝卜、猪肉、米,BV1Ff4y1x7FX,普通,下饭,炒,一口大锅,\n胡萝卜炒肉,猪肉、胡萝卜,BV1Ff4y1x7FX,简单,下饭,炒,一口大锅,\n胡萝卜炒五花肉,胡萝卜、猪肉、米,BV1jW411Z7yd,普通,下饭,炒,一口大锅,\n红烧肉,猪肉、鸡肉,BV1MJ411t7pT,,,,一口大锅,\n可乐鸡翅,鸡肉,BV1pJ411v7S9,,,,一口大锅,\n可乐鸡翅包虾滑,虾、鸡肉、米,BV1Pb4y1p7mi,困难,下饭,煎,一口大锅,\n腊肠烧茄子,腊肠、茄子、米,BV1bW41127MW,普通,下饭,烧,一口大锅,\n腊肠烧土豆,腊肠、土豆、米,BV1TL411V7Cp,普通,下饭,烧,一口大锅,\n萝卜泡菜,白萝卜,BV1VE411y7dr,普通,下饭,腌,一口大锅,\n麻辣凉拌包菜,包菜,BV18f4y1e7cy,简单,下饭,拌,一口大锅,\n炝炒西葫芦,西葫芦、番茄,BV1QU4y1f7sY,普通,下饭,炒,一口大锅,\n芹菜炒肉,猪肉、芹菜,BV1v7411T7is,简单,下饭,炒,一口大锅,\n芹菜肥牛,芹菜、牛肉,BV1ML4y1G7ba,,下饭,,一口大锅,\n芹菜粉,芹菜、面食,BV1Eh411Y7Y3,,下饭,,一口大锅,\n芹菜鸡翅焖锅,芹菜、鸡肉,av761877317,普通,下饭,烧,一口大锅,\n芹菜木耳炒蛋,芹菜、鸡蛋,BV1Dt411o7hq,,下饭,,一口大锅,\n芹菜油豆腐,芹菜、豆腐,BV15J411G7Dx,,下饭,,一口大锅,\n肉末土豆,土豆、肉、米,BV1ZS4y127wm,,下饭,炒,一口大锅,\n莴笋炒肉,莴笋、猪肉,BV1d3411H7G5,普通,下饭,炒,一口大锅,\n西葫芦炒蛋,西葫芦、鸡蛋、番茄,BV1wT4y1Q7Db,简单,下饭,炒,一口大锅,\n西葫芦炒五花肉,西葫芦、猪肉,BV1TL411T7eX,普通,下饭,炒,一口大锅,\n香辣土豆片,土豆、米,BV1Sq4y137uw,,下饭,煮,一口大锅,\n小炒拆骨肉,骨头、米,BV14p411o7zm,困难,下饭,炒,一口大锅,\n孜然土豆火腿,土豆、香肠、米,BV1k44y147kM,简单,下饭,炸,一口大锅,\n芹菜猪肉馅料,芹菜、猪肉,BV18b411r7CP,,馅料,,一口大锅,\n包菜厚蛋烧,包菜、鸡蛋,BV11g411A7QE,普通,小吃,烧,一口大锅,\n风味茄子,茄子,BV1tZ4y1U7iN,普通,小吃,炸,一口大锅,\n油炸白萝卜,白萝卜,BV1J54y1x7ie,简单,小吃,炸,一口大锅,\n炸包菜串,包菜,BV1Ts411F7q3,简单,小吃,炸,一口大锅,\n日式炒胡萝卜丝,胡萝卜,BV1mY411774Z,简单,消耗囤货,炒,一口大锅,\n番茄肉酱面,番茄、猪肉、面食,BV1Cm4y1Q7RS,简单,意式,烧,一口大锅,\n胡萝卜炒蛋,胡萝卜、鸡蛋,BV11E411Q7WB,简单,营养,炒,一口大锅,\n番茄酸汤火锅,番茄、胡萝卜、菌菇、洋葱、豆腐、面食、白萝卜、土豆、白菜、猪肉、牛肉、午餐肉,BV1yz4y1C7Qu,困难,杂烩,,一口大锅,\n骨头汤火锅锅底做法（全鸡版）,骨头、土豆、胡萝卜、花菜、白萝卜、西葫芦、芹菜、菌菇、豆腐、包菜、白菜、午餐肉、鸡肉、猪肉、虾、牛肉、面食、方便面,BV1bi4y187ro,困难,杂烩,,一口大锅,\n乱炖鸡胸,鸡肉、洋葱、菌菇、胡萝卜、土豆,BV1Qb411u7Vu,普通,杂烩,,一口大锅,\n清汤锅万能高汤做法（鸡蛋+猪肉）,猪肉、鸡蛋、土豆、胡萝卜、花菜、白萝卜、西葫芦、芹菜、菌菇、豆腐、包菜、白菜、午餐肉、鸡肉、虾、牛肉、面食、方便面,BV1zD4y197Us,困难,杂烩,,一口大锅,\n无米炒饭,花菜、黄瓜、胡萝卜、鸡蛋、豆腐、虾,BV1Cf4y1V7sv,,杂烩,,一口大锅,\n胡萝卜蛋饼,胡萝卜、鸡蛋,BV1Nh411Z7hZ,简单,早餐,煎,一口大锅,\n土豆丝饼,土豆,BV1fY41187Xo,简单,早餐,煎,一口大锅,\n午餐肉蛋饼,午餐肉、鸡蛋、面包,BV1d34y1X7A5,简单,早餐,煎,一口大锅,\n午餐肉饭团,午餐肉、米,BV1v7411t7uS,简单,早餐,煎,一口大锅,\n午餐肉米汉堡,午餐肉、米、鸡蛋、包菜,BV1n5411U78R,简单,早餐,煎,一口大锅,\n萝卜砂锅粥,白萝卜、米,BV1mz4y1C7H9,普通,早饭,煮,一口大锅,\n麻辣豆腐包,面食、豆腐,BV1y64y1U7jY,普通,早饭,蒸,一口大锅,\n棉花馒头,面食,BV1SK4y1r7dr,普通,早饭,蒸,一口大锅,\n奶黄包,面食、鸡蛋,BV1et4y127jQ,普通,早饭,蒸,一口大锅,\n蒸花卷,面食,BV1w64y1X7cT,困难,早饭,蒸,一口大锅,\n茄子焖面,茄子、番茄、面食,BV1BA411H771,简单,主食,烧,一口大锅,\n芹菜肉饺子,芹菜、猪肉、面食,av546464255,普通,主食,蒸,一口大锅,\n芹菜蒸面卷,芹菜、胡萝卜、面食,BV1JZ4y1s7Sh,,主食,,一口大锅,\n蒸芹菜叶,芹菜、面食,BV1Rg411c72w,,主食,,一口大锅,\n白菜炒土豆片,白菜、土豆,BV17y4y1e76x,普通,,,一口大锅,\n白菜炒香菇,白菜、菌菇,BV1MS4y117Kp,普通,,,一口大锅,\n白菜荷包蛋,白菜、鸡蛋、猪肉,BV16W411a7V8,,,,一口大锅,\n白菜鸡蛋烙,白菜、鸡蛋,BV1rb411F7en,,,,一口大锅,\n白菜鸡胸肉,白菜、鸡肉,BV1nf4y127Cx,,,,一口大锅,\n白菜鸡胸肉卷,白菜、鸡肉、鸡蛋,BV1nf4y127Cx,,,,一口大锅,\n白菜卷火锅,白菜、菌菇、胡萝卜、香肠,BV1wY411n7Vk,,,,一口大锅,\n白菜酿肉,白菜、猪肉、胡萝卜、菌菇,BV1Q5411J7oT,,,,一口大锅,\n白菜肉卷,白菜、番茄、洋葱、猪肉、米、鸡蛋,BV1Ru411C7Hn,困难,,,一口大锅,\n白菜三丝饼,白菜、胡萝卜、西葫芦、鸡蛋,BV1oF411B7Ys,普通,,,一口大锅,\n白萝卜煲,白萝卜、骨头,BV1dU4y1u7eb,普通,,煲,一口大锅,\n白萝卜炒鸡蛋,白萝卜、鸡蛋,BV1PK4y157u2,普通,,炒,一口大锅,\n白萝卜大骨头汤,骨头、白萝卜,BV1HE41137ZY,普通,,炖,一口大锅,\n白萝卜炖猪脚,白萝卜、猪肉,BV1m7411c7pD,困难,,炖,一口大锅,\n白萝卜骨头汤,白萝卜、骨头,BV1gf4y1u7ap,普通,,煮,一口大锅,\n白萝卜焖鸡翅,白萝卜、鸡肉,BV1fs41187XG,普通,,,一口大锅,\n白萝卜烧肉,白萝卜、猪肉,BV1x54y147mz,普通,,烧,一口大锅,\n白切肉,猪肉,BV15h411s7Ze,,,,一口大锅,\n班尼迪克蛋,鸡蛋、面包,BV1TT4y127vu,,,,一口大锅,\n包菜炒蛋,包菜、鸡蛋,BV1w34y1k7wu,简单,,炒,一口大锅,\n包菜炒香肠,包菜、腊肠,BV1AW411m77S,普通,,炒,一口大锅,\n包菜大阪烧,包菜、鸡蛋,BV1tA411F7Eq,普通,,煎,一口大锅,\n包菜豆腐煲,包菜、豆腐,BV1Cq4y157s1,普通,,煲,一口大锅,\n包菜烧肉,包菜、猪肉,BV1Wp4y1a7YR,普通,,烧,一口大锅,\n包菜五花肉,猪肉、包菜,BV1La411F7H4,普通,,炒,一口大锅,\n抱蛋肥牛饭,洋葱、牛肉、鸡蛋、米,BV1Kb4y147m2,,,,一口大锅,\n爆汁肉饼,猪肉、鸡蛋,BV1Cm4y1X7ej,,,,一口大锅,\n爆汁香脆蛋,鸡蛋,BV1yR4y1575Y,,,,一口大锅,\nbiangbiang面,面食,BV1844y157GL,简单,,油泼,一口大锅,\n菜包鸡胸,鸡肉,BV1h54y1Q7pf,,,,一口大锅,\n茶叶蛋,鸡蛋,BV1TT4y127vu,,,卤,一口大锅,\n娼妇意面,番茄、洋葱,BV1hF411t7hS,普通,,烧,一口大锅,\n炒白萝卜丝,白萝卜,BV1ph411p7Pm,简单,,炒,一口大锅,\n炒方便面,方便面、鸡蛋、香肠,BV1Q54y1p7n8,普通,,炒,一口大锅,\n潮汕杂菇煲,菌菇、猪肉,BV15E411K7B5,简单,,炒,一口大锅,\n川味泡面,方便面、鸡蛋,BV1G7411f7FA,简单,,煮,一口大锅,\n葱油拌面,面食,BV1H7411c7Xe,简单,,拌,一口大锅,\n葱油饼,面食,BV1gb411J7Xe,普通,,烙,一口大锅,\n葱油焖鸡翅,鸡肉、洋葱、菌菇,BV1uP4y1P7oQ,普通,,煎,一口大锅,\n醋溜白菜,白菜、米,BV12K4y157im,,,,一口大锅,\n醋卤面,面食、猪肉,BV1XC4y1p7Yn,普通,,拌,一口大锅,\n脆皮糖醋豆腐,豆腐,BV1dU4y1d7hz,普通,,,一口大锅,\n脆皮土豆虾,虾、土豆,BV1Kq4y1q7PM,普通,,炸,一口大锅,\n大餐口味方便面,方便面,BV1s34y1X74a,,,,一口大锅,\n大虾烧白菜,白菜、虾,BV1yT4y157x5,,,,一口大锅,\n地三鲜,土豆、茄子,BV1st4y1Y73H,,,,一口大锅,\n东北鸡蛋酱,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,\n豆腐饭（蛋炒饭）,豆腐、鸡蛋、米,BV1dU4y1d7hz,普通,,,一口大锅,\n法式炒蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,\n法式胡萝卜炖牛肉,胡萝卜、牛肉、洋葱,BV1UR4y1V7nV,困难,,炖,一口大锅,\n番茄肥牛,番茄、牛肉,BV1hF411t7hS,普通,,烧,一口大锅,\n番茄肥牛面,番茄、牛肉、面食,BV1xb4y167LH,普通,,煮,一口大锅,\n番茄虾滑汤,番茄、虾、面食,BV1eZ4y1o7TK,困难,,煮,一口大锅,\n番茄鸡翅焖锅,鸡肉、胡萝卜、土豆、洋葱,BV1Ap4y147Ac,普通,,煮,一口大锅,\n番茄鸡蛋面,方便面、番茄、鸡蛋,BV1tL4y1b7SM,简单,,煮,一口大锅,\n番茄鸡蛋油泼面,番茄、鸡蛋、面食,BV1hF411t7hS,普通,,煮,一口大锅,\n番茄焖面,番茄、面食、香肠、鸡蛋,BV1NL4y1571X,,,,一口大锅,\n番茄排骨汤,番茄、骨头,BV11y4y167Wa,普通,,煮,一口大锅,\n番茄烧茄子,茄子、番茄,BV11b4y1i7ck,普通,,烧,一口大锅,\n番茄土豆牛腩饭,牛肉、土豆、番茄、胡萝卜、米,BV1La411i73b,普通,,煮,一口大锅,\n番茄芝士蛋饼,番茄、鸡蛋,BV1PQ4y1d7ew,普通,,煎,一口大锅,\n方便面炒饭,方便面、米、香肠、鸡蛋,BV1Uf4y157RG,简单,,炒,一口大锅,\n翡翠玉带虾仁,黄瓜、虾、鸡蛋,BV1E4411w7wC,复杂,,,一口大锅,\n富贵金钱蛋（湖南口味辣）,鸡蛋,BV15q4y1A784,,,,一口大锅,\n干锅花菜,花菜、洋葱、米,BV1Rb4y147bZ,普通,,,一口大锅,\n干锅莴笋,莴笋,BV1TC4y1W76c,,,,一口大锅,\n干锅西葫芦,西葫芦、洋葱,BV1eP4y1T76T,普通,,炒,一口大锅,\n干锅西葫芦,西葫芦、猪肉,BV1Vu411B7o6,,,,一口大锅,\n宫保鸡丁,鸡肉、黄瓜,BV1wE411T7X5,,,,一口大锅,\n骨汤面,方便面、鸡蛋、牛肉,BV1Uf4y157RG,简单,,煮,一口大锅,\n骨头炖肉,骨头、猪肉,BV1bS4y1K7BD,普通,,炖,一口大锅,\n海鲜泡面,方便面、虾、鸡蛋,BV1G7411f7FA,简单,,煮,一口大锅,\n黑椒鸡胸肉,鸡肉,BV1PX4y1V7S3,,,,一口大锅,\n红烧大骨头,骨头,BV1hg4y1i7Ss,普通,,烧,一口大锅,\n厚蛋烧,鸡蛋,BV1TT4y127vu,,,煎,一口大锅,\n胡辣莴笋,莴笋,BV1NW411T7Xb,,,,一口大锅,\n花菜虾仁炒饭,花菜、虾仁、米、洋葱、胡萝卜,BV1z4411a7dy,普通,,,一口大锅,\n滑蛋牛肉,牛肉、鸡蛋,BV12u411Z72s,简单,,炒,一口大锅,\n黄瓜炒鸡蛋,黄瓜、鸡蛋,BV1Fi4y1E7pt,简单,,炒,一口大锅,\n黄瓜鸡蛋卷,黄瓜、鸡蛋,BV1GW411L7Sb,普通,,,一口大锅,\n黄瓜鸡蛋汤,黄瓜、鸡蛋,BV1zi4y1w7C8,简单,,,一口大锅,\n黄瓜鸡胸肉,鸡肉、黄瓜、胡萝卜,BV1BF41137JV,普通,,,一口大锅,\n黄瓜钱炒肉,黄瓜、猪肉,BV1WS4y1r7Tx,普通,,炒,一口大锅,\n火腿白菜豆腐煲,白菜、豆腐、香肠,BV1o44y1r7hX,,,,一口大锅,\n鸡蛋饼,鸡蛋,BV1a34y117iz,,,,一口大锅,\n鸡蛋番茄拌面,方便面、鸡蛋、番茄,BV1SS4y1G7fp,普通,,拌,一口大锅,\n鸡蛋焖面,方面、鸡蛋,BV1SS4y1G7fp,普通,,焖,一口大锅,\n鸡蛋蒜,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,\n鸡蛋西兰花豆腐羹,花菜、豆腐、鸡蛋,BV164411g77n,简单,,炖,一口大锅,\n鸡蛋洋葱饭,洋葱、鸡蛋、米,BV1TU4y1H7Cr,,,,一口大锅,\n鸡蛋芝士泡面,方便面、鸡蛋、香肠,BV1SS4y1G7fp,普通,,煮,一口大锅,\n鸡蛋芝士培根吐司,鸡蛋、面包,BV1hv411E7RW,,,,一口大锅,\n鸡胸肉蔬菜小饼,鸡肉、胡萝卜、花菜,BV1i7411d7Rd,,,,一口大锅,\n家常黄焖鸡（多调料版）,鸡肉、菌菇、土豆、胡萝卜,BV17u411X7rR,,,,一口大锅,\n煎蛋方便面,方便面,BV1s34y1X74a,,,,一口大锅,\n煎蛋焖面,面食、鸡蛋,BV1d54y1v7JE,简单,,煮,一口大锅,\n煎方便面饼,方便面、鸡蛋、胡萝卜,BV1Ey4y1g7p8,普通,,煎,一口大锅,\n煎土豆,土豆,BV15s411A7GB,,,炸,一口大锅,\n酱爆西葫芦,西葫芦,BV175411U7LU,简单,,炒,一口大锅,\n酱黄瓜,黄瓜,BV1y4411T78H,简单,,腌制,一口大锅,\n酱鸡蛋,鸡蛋,BV19L411u7H4,,,,一口大锅,\n椒盐鸡翅,鸡肉,BV1HK4y1o77T,简单,,煎,一口大锅,\n金丝午餐肉,土豆、午餐肉,BV1W34y1k7Bg,普通,,炸,一口大锅,\n开胃莴笋叶,莴笋,BV16441147Dc,,,,一口大锅,\n空气土豆,土豆,BV1RL411E7Uv,困难,,炸,一口大锅,\n腊肠炒黄瓜,腊肠、黄瓜、米,BV1zA411x7oK,普通,,炒,一口大锅,\n腊肠炒鸡蛋,腊肠、鸡蛋,BV1x54y127CC,普通,,炒,一口大锅,\n腊肠豆腐煲,腊肠、豆腐、米,BV1Sf4y1r7mS,普通,,煲,一口大锅,\n腊肠茄子煲,腊肠、茄子、米,BV1vW411g7td,普通,,煲,一口大锅,\n腊肠烧麦,腊肠、面食,BV1VV411C7o2,普通,,烧,一口大锅,\n辣味牛肉凉拌小黄瓜,黄瓜、牛肉、洋葱,BV1qr4y1Y792,简单,,,一口大锅,\n辣子鸡丁,鸡肉,BV1Ma4y147MA,,,,一口大锅,\n烂糊白菜,白菜、鸡蛋,BV1x3411y7eN,,,,一口大锅,\n莲花洋葱（消耗洋葱！）,洋葱、鸡蛋,BV1MW411T7Cv,,,,一口大锅,\n凉拌黄瓜虾滑,黄瓜、虾,BV1RF411W7v7,复杂,,,一口大锅,\n凉拌莴苣,莴笋,BV1z3411Y7Bs,,,,一口大锅,\n凉拌胸丝,鸡肉,BV1h54y1Q7pf,,,,一口大锅,\n凉拌洋葱,洋葱,BV1BS4y1G7XD,,,,一口大锅,\n流心蛋,鸡蛋,BV1Yt411G7Ut,,,,一口大锅,\n萝卜牛肉煲,白萝卜、牛肉,BV1a7411L733,困难,,煲,一口大锅,\n萝卜烧牛腩,白萝卜、牛肉,BV1nK41137Lb,普通,,烧,一口大锅,\n萝卜烧排骨,白萝卜、骨头,BV1YV411f7L8,普通,,烧,一口大锅,\n萝卜丝炒牛肉,白萝卜、牛肉,BV1jA411P7LJ,普通,,炒,一口大锅,\n萝卜鲜虾煲,白萝卜、虾,BV1Mp4y1z7Yr,困难,,煲,一口大锅,\n罗宋汤,胡萝卜、土豆、番茄、牛肉、包菜,BV1394y1d7f6,普通,,煮,一口大锅,\n马拉糕,面食,BV1fa4y1a7Xy,困难,,蒸,一口大锅,\n麻辣烫方便面,方便面、香肠、鸡蛋、包菜,BV1Yp411o7MB,简单,,煮,一口大锅,\n焖泡面,方便面、猪肉、茄子,BV1s34y1X74a,,,,一口大锅,\n秘制洋葱炒肥牛,洋葱、牛肉,BV1vT4y1E7fZ,,,,一口大锅,\n牧羊人派,土豆、猪肉,BV1fY41187Xo,普通,,,一口大锅,\n奶汤方便面,方便面,BV1s34y1X74a,,,,一口大锅,\n牛肉蒸黄瓜条,黄瓜、牛肉、鸡蛋,BV1V5411S7Uv,普通,,,一口大锅,\n拍黄瓜,黄瓜,BV1RV41167WV,简单,,凉拌,一口大锅,\n铺盖打蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,\n蒲烧茄子,茄子,BV1q44y1n78y,普通,,烧,一口大锅,\n千层土豆,土豆,BV1nb4y147D6,困难,,炸,一口大锅,\n茄汁大虾,虾、番茄、米、面食,BV1aJ411K75X,简单,,烧,一口大锅,\n茄汁蛋黄浓汤面,方便面、番茄、鸡蛋,BV1s34y1X74a,,,,一口大锅,\n茄汁豆腐,番茄、豆腐,BV1Xb4y1p7mv,简单,,烧,一口大锅,\n茄汁花菜,花菜、番茄,BV1J541187QQ,普通,,,一口大锅,\n茄汁鸡胸肉,鸡肉、番茄,BV1L54y1b7Xq,,,,一口大锅,\n茄子卷,茄子、猪肉,BV1L3411H7EK,普通,,炸/煎,一口大锅,\n芹菜炒腊肉,芹菜、猪肉,av209779329,普通,,炒,一口大锅,\n芹菜炒肉丝,芹菜、猪肉,av806554209,普通,,炒,一口大锅,\n芹菜炒香干,芹菜,av671691480,普通,,炒,一口大锅,\n芹菜煎饼,芹菜、鸡蛋,av247131174,简单,,煎,一口大锅,\n清炖牛肉,牛肉、白萝卜,BV1CL4y1n76E,普通,,炖,一口大锅,\n青瓜炒火腿肠,黄瓜、香肠,BV1u7411Z79s,简单,,,一口大锅,\n青椒炒荷包蛋,鸡蛋,BV1wJ41167uH,,,,一口大锅,\n热拌莴笋叶,莴笋,BV1xJ411M7bc,,,,一口大锅,\n日式汉堡排（废手）,面包、洋葱、猪肉、牛肉、鸡蛋,BV1UV411Y7HM,,,,一口大锅,\n肉炒莴苣,莴笋、猪肉,BV14E411t7wh,,,,一口大锅,\n肉末土豆茄子煲,茄子、土豆、猪肉,BV1Fu411S78s,普通,,煲,一口大锅,\n肉丸萝卜煲,白萝卜、虾、猪肉,BV1gT4y1m7UK,普通,,煲,一口大锅,\n赛螃蟹,鸡蛋,BV1ab4y1s7wk,,,,一口大锅,\n三杯鸡（无九层塔版）,鸡肉,BV1dp4y1e73R,,,,一口大锅,\n生煎白萝卜,白萝卜,BV1Yu41127Fa,简单,,煎,一口大锅,\n薯条,土豆,BV1fY41187Xo,普通,,炸,一口大锅,\n爽口腌莴笋,莴笋,BV1Vv411V7SE,,,,一口大锅,\n水波蛋,鸡蛋,BV1TT4y127vu,简单,,煮,一口大锅,\n水煮牛肉,牛肉、菌菇、莴笋、豆腐,BV11q4y1z7dD,复杂,,,一口大锅,\n苏格兰蛋,鸡蛋、猪肉,BV1TT4y127vu,,,炸,一口大锅,\n素烧白萝卜,白萝卜,BV1Z7411b7xo,简单,,烧,一口大锅,\n蒜香蜂胸扒,鸡肉,BV1h54y1Q7pf,,,,一口大锅,\n蒜香鸡翅,鸡肉,BV1sy4y1273W,普通,,煎,一口大锅,\n蒜香鸡胸肉,鸡肉,BV1tf4y1A7xR,,,,一口大锅,\n蓑衣黄瓜,黄瓜,BV1Wa411C7Gc,,,,一口大锅,\n泰式卤蛋,鸡蛋,BV1TT4y127vu,,,卤,一口大锅,\n糖醋荷包蛋,鸡蛋,BV1TT4y127vu,,,煎,一口大锅,\n糖醋鸡胸肉,鸡肉,BV1h7411C7PZ,,,,一口大锅,\n糖醋里脊,猪肉,BV1Dt411B78h,普通,,炸,一口大锅,\n糖醋茄子,茄子、米,BV1TE411L7Gp,普通,,烧,一口大锅,\n铁板豆腐,豆腐,BV1dU4y1d7hz,简单,,,一口大锅,\n土豆拌饭,土豆、香肠、米,BV1YD4y1c7ip,简单,,炒,一口大锅,\n土豆炖茄子,茄子、土豆,BV1uP4y1M7rt,普通,,炖,一口大锅,\n土豆胡萝卜炖牛腩,牛肉、胡萝卜、土豆、,BV1R54y1E7JX,,,,一口大锅,\n土豆腊肠煲饭,腊肠、土豆、米,BV1wV411o7Sq,困难,,煲,一口大锅,\n土豆泥厚蛋烧,土豆、鸡蛋,BV1DL4y1j7Nu,困难,,煎,一口大锅,\n土豆烧牛腩,土豆、牛肉,BV1qs411u7gj,普通,,炒,一口大锅,\n吐司进阶吃法,面包、鸡蛋、虾、包菜、香肠,BV1Tq4y1t7DC,简单,,,一口大锅,\n丸子面,方便面、猪肉、牛肉,BV1s34y1X74a,,,,一口大锅,\n莴笋菜饭,莴笋,BV1wC4y1Y7iY,,,,一口大锅,\n莴笋炒虾仁,莴笋、虾、胡萝卜,BV1ZT4y1g7Y2,简单,,炒,一口大锅,\n莴笋炒鸡丁,莴笋、鸡肉,BV1hb411i7X2,普通,,,一口大锅,\n莴笋炒腊肉,莴笋、香肠,BV1Ms411F7da,简单,,炒,一口大锅,\n莴笋胡萝卜酿肉,莴笋、胡萝卜,BV1754y1y7eV,,,,一口大锅,\n莴笋三吃,莴笋,BV1Bf4y1E7BR,,,,一口大锅,\n午餐肉蛋炒饭,午餐肉、鸡蛋、米,BV1HE411X7xR,普通,,炒,一口大锅,\n午餐肉土豆锅,午餐肉、土豆、胡萝卜,BV1gT4y1Q7R8,普通,,煮,一口大锅,\n西班牙土豆鸡蛋饼,鸡蛋、土豆、洋葱,BV1Vz4y1D7nF,,,,一口大锅,\n西葫芦鸡蛋饼,西葫芦、鸡蛋,BV1Mt411T7pF,普通,,煎,一口大锅,\n西葫芦蒸蛋,西葫芦、鸡蛋,BV1R94y1d7dd,普通,,,一口大锅,\n西兰花炒虾仁,花菜、虾,BV1nv411B78i,简单,,炒,一口大锅,\n香菇白菜汤,白菜、菌菇,BV1Cs411G7bw,普通,,,一口大锅,\n香煎鸡胸肉,鸡肉,BV1W4411j71M,,,,一口大锅,\n香辣鸡胸肉,鸡肉,BV1M34y187r2,,,,一口大锅,\n小炒鸡胸肉,鸡肉,BV1pF411a7nW,,,,一口大锅,\n小炒牛肉,黄瓜、牛肉、米,BV1MK411T7NX,普通,,,一口大锅,\n雪碧拌面（要雪碧+老干妈）,方便面、黄瓜,BV1az4y117d1,,,,一口大锅,\n阳春面,面食,BV11E411C7e7,简单,,煮,一口大锅,\n洋葱拌鸡丝,洋葱、鸡肉,BV1Ki4y1P7tN,,,,一口大锅,\n洋葱拌木耳,洋葱、木耳,BV1n5411H7Yo,,,,一口大锅,\n洋葱炒鸡蛋,洋葱、鸡蛋,BV1Dp4y1k7Mu,,,,一口大锅,\n洋葱炒肉,洋葱、鸡肉,BV1QV41127ES,,,,一口大锅,\n洋葱炒土豆,土豆、洋葱,BV1mg4y1b7z3,,,,一口大锅,\n洋葱虾仁滑蛋,鸡蛋、牛肉、洋葱、虾,BV1uE411N7AN,,,,一口大锅,\n洋葱排骨,洋葱、猪肉,BV1Q3411j7p6,,,,一口大锅,\n洋葱烧鸡肉,洋葱、鸡肉,BV1hW411y7Dp,,,,一口大锅,\n一口酥豆腐,豆腐,BV1dU4y1d7hz,普通,,,一口大锅,\n一只鸡蛋糕,鸡蛋,BV1X7411a7EQ,,,,一口大锅,\n英式炒蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,\n油墩子/腰子饼,白萝卜、芹菜、香肠、面食、鸡蛋,BV12b4y1i7DP,,,,一口大锅,\n鱼香茄子,茄子、鸡肉,BV1yL411E7oP,普通,,烧,一口大锅,\n杂粮蒸糕,面食,BV1vf4y1T7gY,普通,,蒸,一口大锅,\n炸蛋,鸡蛋,BV1TT4y127vu,简单,,炸,一口大锅,\n炸胡萝卜块,葫芦卜、鸡蛋,BV11a411i7f4,普通,,炸,一口大锅,\n炸胡萝卜丸,胡萝卜、鸡蛋、香肠,BV12r4y1i7ud,普通,,炸,一口大锅,\n炸茄盒,茄子,BV1df4y177NB,普通,,炸,一口大锅,\n炸薯卷,土豆、香肠,BV1Na4y1a7LA,简单,,炸,一口大锅,\n炸洋葱,洋葱,BV1ib411V7H2,,,,一口大锅,\n照烧鸡胸肉,鸡肉,BV12Q4y1K7CA,,,,一口大锅,\n照烧牛肉,牛肉,BV1mq4y1x7o3,普通,,炒,一口大锅,\n朝鲜冷面（方便面版）,方便面,BV1s34y1X74a,,,,一口大锅,\n蒸蛋羹,鸡蛋,BV1Wt411Z7td,,,,一口大锅,\n蒸蛋羹（硬核0失败版）,鸡蛋,BV1x441117r4,,,,一口大锅,\n蒸卤面,面食,BV1sZ4y137QG,困难,,蒸,一口大锅,\n蒸芹菜饼,芹菜、鸡蛋,av809152626,普通,,蒸,一口大锅,\n正确煮方便面,方便面,BV1s34y1X74a,,,,一口大锅,\n孜然土豆牛肉,牛肉、土豆,BV1qX4y1T7ax,普通,,炒,一口大锅,\n自制牛肉干,牛肉,BV1Ht411d71Q,复杂,,,一口大锅,\n电饭煲番茄牛肉焖饭,番茄、牛肉、米,BV1Bv411C7X3,普通,,,一口大锅、电饭煲,\n电饭煲排骨土豆焖饭,猪肉、土豆、米,BV1Bv411C7X3,普通,,,一口大锅、电饭煲,\n米布丁,米、鸡蛋,BV1hr4y1k7A5,简单,,,一口大锅、电饭煲,\n麻婆豆腐,豆腐,BV1it4y1X75m,简单,,,一口大锅,\n"
  },
  {
    "path": "app/pages/404.vue",
    "content": "<script setup lang=\"ts\">\n// const router = useRouter()\n</script>\n\n<template>\n  <main p=\"x4 y10\" text=\"center green-700 dark:gray-200\">\n    <div text-4xl>\n      <div i-ri-error-warning-line inline-block />\n    </div>\n    <div>菜谱消失了</div>\n    <div>\n      <NuxtLink btn text-sm m=\"3 t8\" to=\"/\">\n        返回主页\n      </NuxtLink>\n    </div>\n  </main>\n</template>\n"
  },
  {
    "path": "app/pages/about/acknowledgements.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PersonalAcknowledgement } from '~/constants/acknowledgements'\nimport { acknowledgements, personalAcknowledgements } from '~/constants/acknowledgements'\n\n// 图标映射表（提取为常量以提升性能）\nconst ICON_MAP: Record<string, string> = {\n  github: 'i-ri-github-line',\n  bilibili: 'i-ri-bilibili-line',\n  weibo: 'i-ri-weibo-line',\n  twitter: 'i-ri-twitter-x-line',\n  wechat: 'i-ri-wechat-2-line',\n  blog: 'i-ri-article-line',\n  website: 'i-ri-global-line',\n  email: 'i-ri-mail-line',\n}\n\n/**\n * 根据链接类型返回对应的图标类名\n */\nfunction getIconClass(type?: string): string {\n  return ICON_MAP[type || 'website'] || 'i-ri-links-line'\n}\n\nfunction getLinkKey(ackIndex: number, linkIndex: number): string {\n  return `link-${ackIndex}-${linkIndex}`\n}\n\nfunction getPersonKey(person: PersonalAcknowledgement, index: number): string {\n  return person.link ? `${person.name}-${person.link}` : `${person.name}-${index}`\n}\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/about\" />\n        </ion-buttons>\n        <ion-title>致谢</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <!-- 个人致谢名单 -->\n      <ion-list-header>\n        <ion-label class=\"text-sm op-50\">\n          感谢以下朋友在项目早期的支持与帮助\n        </ion-label>\n      </ion-list-header>\n      <ion-list v-if=\"personalAcknowledgements.length > 0\" :inset=\"true\">\n        <!-- 使用 ion-item 展示每个个人 -->\n        <ion-item\n          v-for=\"(person, index) in personalAcknowledgements\"\n          :key=\"getPersonKey(person, index)\"\n          :href=\"person.link\"\n          :target=\"person.link ? '_blank' : undefined\"\n          :button=\"!!person.link\"\n          :detail=\"!!person.link\"\n          lines=\"full\"\n        >\n          <div slot=\"start\" class=\"h-6 w-6 inline-flex items-center justify-center text-5 color-[var(--ion-color-medium)]\" i-ri-user-3-line />\n          <ion-label>\n            <h3 class=\"m-0 text-[17px] color-[var(--ion-text-color)] font-normal\">\n              {{ person.name }}\n            </h3>\n          </ion-label>\n        </ion-item>\n      </ion-list>\n\n      <!-- 团队/组织致谢 -->\n      <template v-for=\"(ack, index) in acknowledgements\" :key=\"index\">\n        <ion-list-header>\n          <ion-label class=\"tracking-wide uppercase opacity-50 text-[13px]! font-medium!\">\n            {{ ack.description }}\n          </ion-label>\n        </ion-list-header>\n        <ion-list :inset=\"true\">\n          <!-- 链接列表 -->\n          <ion-item\n            v-for=\"(link, linkIndex) in ack.links\"\n            :key=\"getLinkKey(index, linkIndex)\"\n            :href=\"link.href\"\n            :target=\"link.target || '_blank'\"\n            :detail=\"true\"\n            button\n          >\n            <div v-if=\"link.type\" slot=\"start\" class=\"inline-flex\" :class=\"getIconClass(link.type)\" />\n            <ion-label>{{ link.label }}</ion-label>\n          </ion-item>\n        </ion-list>\n      </template>\n\n      <!-- 底部说明 -->\n      <div class=\"px-4 pb-8 pt-4\">\n        <p class=\"m-0 text-center text-[13px] leading-[1.4] opacity-50\">\n          感谢所有支持和帮助过本项目的人们 ❤️\n        </p>\n      </div>\n    </ion-content>\n  </ion-page>\n</template>\n\n<style lang=\"scss\" scoped>\nion-list-header {\n  ion-label {\n    margin-top: 0px;\n    margin-bottom: 0px;\n  }\n}\n\nion-list {\n  margin-top: 8px !important;\n  margin-bottom: 8px !important;\n}\n</style>\n"
  },
  {
    "path": "app/pages/about/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { links } from '~/constants'\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>关于</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <TheAboutList />\n\n      <ion-list :inset=\"true\">\n        <ion-item :href=\"links.cook\" target=\"_blank\">\n          <ion-label>网页版本</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            <div class=\"inline-flex\" i-ri-global-line />\n            <span m=\"l-1\" class=\"inline-flex\">cook.yunyoujun.cn</span>\n          </ion-text>\n        </ion-item>\n\n        <ion-item :href=\"links.yyj.bilibiliOpus\" target=\"_blank\">\n          <ion-label>旅程的起点</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            <div class=\"inline-flex\" i-ri-bilibili-line />\n            <span m=\"l-1\" class=\"inline-flex\">动态</span>\n          </ion-text>\n        </ion-item>\n\n        <ion-item :href=\"links.yyj.mpArticle\" target=\"_blank\">\n          <ion-label>关于食用手册的前世今生</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            <div class=\"inline-flex\" i-ri-wechat-2-line />\n            <span m=\"l-1\" class=\"inline-flex\">文章</span>\n          </ion-text>\n        </ion-item>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/about/me.vue",
    "content": "<script setup lang=\"ts\">\nimport { links } from '~/constants'\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>关于作者</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <ion-list :inset=\"true\">\n        <ion-item :href=\"links.yyj.bilibili\" target=\"_blank\">\n          <div slot=\"start\" class=\"inline-flex\" i-ri-bilibili-line />\n          <ion-label>哔哩哔哩</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            <span m=\"l-1\" class=\"inline-flex\">云游君</span>\n          </ion-text>\n        </ion-item>\n\n        <ion-item :href=\"links.yyj.github\" target=\"_blank\">\n          <div slot=\"start\" class=\"inline-flex\" i-ri-github-line />\n          <ion-label>GitHub</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            <span m=\"l-1\" class=\"inline-flex\">YunYouJun</span>\n          </ion-text>\n        </ion-item>\n\n        <ion-item :href=\"links.yyj.blog\" target=\"_blank\">\n          <div slot=\"start\" class=\"inline-flex\" i-ri-article-line />\n          <ion-label>博客</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            云游君的小站\n          </ion-text>\n        </ion-item>\n\n        <ion-item :href=\"links.yyj.mp\" target=\"_blank\">\n          <div slot=\"start\" class=\"inline-flex\" i-ri-wechat-2-line />\n          <ion-label>公众号</ion-label>\n          <ion-text class=\"inline-flex items-center justify-center\">\n            云游君\n          </ion-text>\n        </ion-item>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/apps/random/index.vue",
    "content": "<script setup lang=\"ts\">\ndefinePageMeta({\n  alias: ['/random'],\n})\n\nuseHead({\n  title: '今天吃什么',\n})\n</script>\n\n<template>\n  <ion-page flex flex-col>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/apps\" />\n        </ion-buttons>\n\n        <ion-title>\n          今天吃什么\n        </ion-title>\n      </ion-toolbar>\n    </ion-header>\n    <ion-content>\n      <div class=\"flex flex-grow flex-col items-center justify-center\">\n        <RandomRecipe />\n      </div>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/changelog.vue",
    "content": "<script setup lang=\"ts\">\nimport { links } from '~/constants'\n\n// help\n\n// :href=\"links.changelog\" target=\"_blank\"\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>帮助</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <ion-list-header>功能日志</ion-list-header>\n      <ion-list :inset=\"true\">\n        <ion-item>\n          <ion-label>\n            <h2>v2.0.0-beta (2025-10-07)</h2>\n            <p>Beta App 功能</p>\n            <p>全新原生界面 UI 适配</p>\n            <p>新增历史记录和收藏功能</p>\n          </ion-label>\n        </ion-item>\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item :href=\"links.changelog\" target=\"_blank\">\n          <ion-label>开发日志</ion-label>\n        </ion-item>\n        <ion-item :href=\"links.releaseNotes\" target=\"_blank\">\n          <ion-label>Release Notes</ion-label>\n        </ion-item>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/cookbooks/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { defaultCookbook } from '~/utils'\n\ndefinePageMeta({\n  layout: 'child',\n  title: '自定义菜谱',\n})\n</script>\n\n<template>\n  <div>\n    <h3>\n      开发中，敬请期待\n    </h3>\n\n    <div grid=\"~ cols-3\" gap=\"4\" p=\"4\">\n      <CookbookCard :cookbook=\"defaultCookbook\">\n        默认菜谱\n      </CookbookCard>\n\n      <NewCookbookCard />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "app/pages/cookbooks/new.vue",
    "content": "<template>\n  <div>\n    新建 Cookbook\n  </div>\n</template>\n"
  },
  {
    "path": "app/pages/help.vue",
    "content": "<script setup lang=\"ts\">\n// help\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>帮助</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content class=\"w-full\">\n      <div class=\"mx-auto max-w-md w-full rounded-2xl p-2\" text-left>\n        <FAQItem title=\"未来计划？\">\n          计划增加新功能，如自定义菜谱，与使用其他用户分享的菜谱。\n        </FAQItem>\n\n        <FAQItem title=\"什么是模式？\">\n          <ul>\n            <li><b>模糊匹配</b>：展示所有含当前选中任意食材的菜谱</li>\n            <li><b>精准匹配</b>：展示所有含当前选中所有食材的菜谱</li>\n            <li><b>生存模式</b>：展示当前选中食材即可制作的所有菜谱</li>\n          </ul>\n        </FAQItem>\n\n        <FAQItem title=\"如何快速清空所选食材和工具?\">\n          <div inline-flex items-center justify-center>\n            点击顶部 <div i-mdi-pot-steam-outline mx-1 inline-block /> 图标即可。\n          </div>\n        </FAQItem>\n\n        <FAQItem title=\"是否有微信小程序?\">\n          因不可抗力（小程序因跳转 B 站视频而被判定为导流违规）下架。\n          将不再提供小程序版本。\n          <br>\n          <br>\n          搜索微信公众号<b>「云游君」</b>并发送<b>「做菜」</b>，也可以快速找到本网站。\n        </FAQItem>\n\n        <FAQItem title=\"是否有 APP?\">\n          <b>正在开发中，尽请期待。</b>\n        </FAQItem>\n\n        <FAQItem title=\"未来是否会收费？\">\n          该项目将以免费开源的形式运营。\n          <br>\n          您可以考虑赞助本项目，以支持我们的开发。\n          我会将其投入在周边的服务器、域名、CDN 等费用上。\n          <ul mt-1>\n            <li>\n              <a href=\"https://afdian.net/a/yunyoujun\" target=\"_blank\">爱发电赞助</a>\n            </li>\n            <li>\n              <a href=\"https://sponsors.yunyoujun.cn/\" target=\"_blank\">我要直接打钱！</a>\n            </li>\n          </ul>\n        </FAQItem>\n\n        <FAQItem title=\"页面无法点击、资源加载失败？\">\n          <blockquote>\n            试试「无痕模式」是否正常？\n          </blockquote>\n          <br>\n          <ol>\n            <li>\n              <b>清除 Cookie</b>\n              <ol>\n                <li>\n                  点击浏览器网址前方的 🔒 图标\n                </li>\n                <li>\n                  点击「Cookie」并清除\n                </li>\n              </ol>\n            </li>\n            <li>\n              <b>强制刷新缓存</b>\n              <ul>\n                <li>Windows: <code>Ctrl + F5</code></li>\n                <li>macOS: <code>Cmd + Shift + R</code></li>\n              </ul>\n            </li>\n          </ol>\n        </FAQItem>\n\n        <hr h=\"1\" my=\"4\" bg-black>\n\n        <HelpAbout />\n\n        <FAQItem title=\"关于我\">\n          <div text-left>\n            我的个人微信公众号「云游君」，会分享一些生活和写的<a href=\"https://sponsors.yunyoujun.cn/projects\" target=\"_blank\">\n              小玩具们\n            </a>。\n\n            <a inline-flex py-4 href=\"https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg\" target=\"_blank\">\n              <img src=\"https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg\">\n            </a>\n          </div>\n          <AboutMe />\n        </FAQItem>\n\n        <FAQItem title=\"致谢\">\n          <p>\n            感谢以下小伙伴为本项目提供的数据支持和 QA ！\n          </p>\n\n          <ul mt-2 text-left text-sm>\n            <li>\n              <a href=\"https://weibo.com/runny\" target=\"_blank\">Runny</a>\n            </li>\n            <li>\n              麒麟\n            </li>\n            <li>\n              晴方啾\n            </li>\n            <li>\n              课代表阿伟\n            </li>\n          </ul>\n        </FAQItem>\n\n        <FAQItem title=\"赞助者们\">\n          <div>\n            感谢至今以来所有的<a href=\"https://afdian.net/a/yunyoujun\" class=\"text-purple\" target=\"_blank\">赞助者</a>们，你们的支持是我持续维护和开发新项目的动力！\n          </div>\n          <div pt-2>\n            <a href=\"https://sponsors.yunyoujun.cn\" target=\"_blank\">\n              <img src=\"https://sponsors.yunyoujun.cn/sponsors.svg\">\n            </a>\n          </div>\n        </FAQItem>\n      </div>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/recipes/favorites.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { IonSearchbarCustomEvent, SearchbarInputEventDetail } from '@ionic/core'\nimport type { DbRecipeItem } from '~/utils/db'\nimport { Dialog } from '@capacitor/dialog'\nimport { getFavoriteTime, isFavorited, toggleFavorite } from '~/composables/store/favorite'\nimport { db } from '~/utils/db'\n\ndefinePageMeta({\n  layout: 'child',\n  title: '我的收藏',\n})\n\nconst loading = ref(false)\nconst recipes = ref<DbRecipeItem[]>([])\nconst keyword = ref('')\nconst sortKey = ref<'time' | 'name'>('time')\n\nconst displayed = computed(() => {\n  const text = keyword.value.trim()\n  let list = recipes.value\n  if (text)\n    list = list.filter(r => r.name.includes(text))\n\n  if (sortKey.value === 'name')\n    return [...list].sort((a, b) => a.name.localeCompare(b.name))\n\n  // time: sort by recorded favorite timestamp (recent first)\n  return [...list].sort((a, b) => {\n    const ta = getFavoriteTime(a) ?? -Infinity\n    const tb = getFavoriteTime(b) ?? -Infinity\n    return tb - ta\n  })\n})\n\nasync function loadFavorites() {\n  loading.value = true\n  try {\n    const all = await db.recipes.toArray()\n    recipes.value = all.filter(r => isFavorited(r))\n  }\n  finally {\n    loading.value = false\n  }\n}\n\nonMounted(loadFavorites)\n\nasync function clearAllFavorites() {\n  const result = await Dialog.confirm({\n    title: '清空收藏',\n    message: '确定要取消所有收藏吗？',\n    okButtonTitle: '确认',\n    cancelButtonTitle: '取消',\n  })\n  if (result.value) {\n    for (const item of recipes.value)\n      toggleFavorite(item)\n    await loadFavorites()\n  }\n}\n\nfunction onToggleFavorite(item: DbRecipeItem) {\n  toggleFavorite(item)\n  // update list immediately\n  recipes.value = recipes.value.filter(r => isFavorited(r))\n}\n\nfunction openDishLink(item: DbRecipeItem) {\n  const href = item.link || `https://www.bilibili.com/video/${item.bv}`\n  window.open(href, '_blank')\n}\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>我的收藏</ion-title>\n\n        <ion-buttons slot=\"end\">\n          <ion-button title=\"清空收藏\" @click=\"clearAllFavorites\">\n            <ion-icon slot=\"icon-only\" :icon=\"ioniconsTrashOutline\" />\n          </ion-button>\n        </ion-buttons>\n      </ion-toolbar>\n\n      <ion-toolbar>\n        <ion-searchbar\n          animated\n          placeholder=\"搜索收藏\"\n          :debounce=\"300\"\n          show-clear-button=\"focus\"\n          @ion-input=\"(ev: IonSearchbarCustomEvent<SearchbarInputEventDetail>) => (keyword = ev.detail.value ?? '')\"\n          @ion-clear=\"keyword = ''\"\n        />\n      </ion-toolbar>\n\n      <ion-toolbar class=\"pb-1.5 -mt-2\">\n        <ion-segment\n          :value=\"sortKey\"\n          @ion-change=\"e => (sortKey = (e.detail.value as 'time' | 'name') ?? 'time')\"\n        >\n          <ion-segment-button value=\"time\">\n            <ion-label>按收藏时间</ion-label>\n          </ion-segment-button>\n          <ion-segment-button value=\"name\">\n            <ion-label>按名称</ion-label>\n          </ion-segment-button>\n        </ion-segment>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <div v-if=\"loading\" class=\"ion-padding text-center\">\n        <ion-spinner name=\"crescent\" />\n      </div>\n\n      <ion-list v-else-if=\"displayed.length\">\n        <ion-item-sliding v-for=\"item in displayed\" :key=\"item.id ?? item.name\">\n          <ion-item @click=\"openDishLink(item)\">\n            <ion-label class=\"truncate\">\n              <DishLabel class=\"text-sm\" :dish=\"item\" />\n            </ion-label>\n            <ion-icon slot=\"end\" :icon=\"ioniconsStar\" color=\"warning\" />\n          </ion-item>\n\n          <ion-item-options>\n            <ion-item-option color=\"medium\" @click=\"onToggleFavorite(item)\">\n              取消收藏\n            </ion-item-option>\n          </ion-item-options>\n        </ion-item-sliding>\n      </ion-list>\n\n      <div v-else class=\"ion-padding text-center\">\n        <ion-note>还没有收藏的菜谱</ion-note>\n      </div>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/recipes/history.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RecipeItem } from '~/types'\nimport { Dialog } from '@capacitor/dialog'\nimport dayjs from 'dayjs'\nimport { recipeHistories } from '~/composables/store/history'\n\ndefinePageMeta({\n  layout: 'child',\n  title: '历史记录',\n})\n\n// todo\n// clear one history\nasync function clearAllHistory() {\n  await Dialog.confirm({\n    title: '清空历史记录',\n    message: '确定要清空所有历史记录吗？此操作不可撤销。',\n    okButtonTitle: '确认',\n    cancelButtonTitle: '取消',\n  }).then((result) => {\n    if (result.value) {\n      recipeHistories.value = []\n    }\n  })\n}\n\nfunction clearOneHistory(history: typeof recipeHistories.value[0]) {\n  recipeHistories.value = recipeHistories.value.filter(h => h !== history)\n}\n\nfunction openDishLink(dish: RecipeItem) {\n  const href = dish.link || `https://www.bilibili.com/video/${dish.bv}`\n  window.open(href, '_blank')\n}\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>历史记录</ion-title>\n\n        <ion-buttons slot=\"end\">\n          <ion-button title=\"清空记录\" @click=\"clearAllHistory\">\n            <ion-icon slot=\"icon-only\" :icon=\"ioniconsTrashOutline\" />\n          </ion-button>\n        </ion-buttons>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <ion-list>\n        <template v-for=\"history in recipeHistories\" :key=\"history.recipe.name\">\n          <ion-item-sliding>\n            <ion-item @click=\"openDishLink(history.recipe)\">\n              <ion-label class=\"truncate\">\n                <DishLabel class=\"text-sm\" :dish=\"history.recipe\" />\n              </ion-label>\n              <ion-text class=\"text-xs\">\n                {{ dayjs(history.time).format('YYYY-MM-DD HH:mm:ss') }}\n              </ion-text>\n            </ion-item>\n\n            <ion-item-options>\n              <!-- TODO -->\n              <!-- <ion-item-option>\n                收藏\n              </ion-item-option> -->\n              <ion-item-option color=\"danger\" @click=\"clearOneHistory(history)\">\n                删除\n              </ion-item-option>\n            </ion-item-options>\n          </ion-item-sliding>\n        </template>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/recipes/index.vue",
    "content": "<script lang=\"ts\" setup>\ndefinePageMeta({\n  layout: 'child',\n  title: '菜谱 - ?',\n})\n</script>\n\n<template>\n  <div>\n    asd\n  </div>\n</template>\n"
  },
  {
    "path": "app/pages/recipes/new.vue",
    "content": "<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>添加菜谱</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      asdad\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/settings.vue",
    "content": "<script lang=\"ts\" setup>\nconst { isDark, toggleDark } = useDarkMode()\n\nconst app = useAppStore()\n\ndefinePageMeta({\n  layout: 'child',\n})\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-buttons slot=\"start\">\n          <ion-back-button default-href=\"/my\" />\n        </ion-buttons>\n        <ion-title>设置</ion-title>\n        <!-- <ion-buttons slot=\"end\">\n          <ion-button color=\"dark\">\n            <ion-icon slot=\"icon-only\" :icon=\"ioniconsPersonCircleOutline\" />\n          </ion-button>\n        </ion-buttons> -->\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <ion-list-header>外观</ion-list-header>\n      <ion-list :inset=\"true\">\n        <ion-item>\n          <ion-toggle :checked=\"isDark\" justify=\"space-between\" @ion-change=\"toggleDark\">\n            暗色模式\n          </ion-toggle>\n        </ion-item>\n      </ion-list>\n\n      <ion-list-header>数据</ion-list-header>\n      <ion-list :inset=\"true\">\n        <ion-item>\n          <ion-toggle v-model:checked=\"app.settings.keepLocalData\" justify=\"space-between\">\n            离开网页后保留选中数据\n          </ion-toggle>\n        </ion-item>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n\n<style>\n/* (Optional) This is added to prevent the flashing that happens when toggling between palettes */\nion-item {\n  --transition: none;\n}\n</style>\n"
  },
  {
    "path": "app/pages/tabs/apps/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { links } from '~/constants'\n\ndefinePageMeta({\n  alias: ['/apps'],\n})\n\nuseHead({\n  title: '发现',\n})\n</script>\n\n<template>\n  <ion-page flex flex-col>\n    <ion-header>\n      <ion-toolbar>\n        <ion-title>\n          发现\n        </ion-title>\n      </ion-toolbar>\n    </ion-header>\n    <ion-content>\n      <ion-list :inset=\"true\">\n        <ion-item router-link=\"/apps/random\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsShuffleOutline\" />\n          <ion-label>\n            今天吃什么\n          </ion-label>\n        </ion-item>\n\n        <!-- <ion-item router-link=\"/apps/random\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsChatboxEllipsesOutline\" />\n          <ion-label>\n            AI 菜谱\n          </ion-label>\n        </ion-item> -->\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item :href=\"links.githubIssue\" target=\"_blank\">\n          <ion-label>\n            新功能建议\n          </ion-label>\n        </ion-item>\n      </ion-list>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/tabs/home/index.vue",
    "content": "<script lang=\"ts\" setup>\ndefinePageMeta({\n  alias: ['/', '/home', '/tabs'],\n})\n\nuseHead({\n  title: '食用手册',\n})\n\nconst ionContentRef = ref<HTMLElement>()\n\nconst rStore = useRecipeStore()\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-title>\n          <button\n            class=\"m-auto flex cursor-pointer items-center gap-2 transition active:text-green-800 hover:(text-green-600)\"\n            title=\"重置\"\n            @click=\"rStore.reset\"\n          >\n            <div v-if=\"rStore.selectedStuff.length\" i-mdi-pot-steam-outline />\n            <div v-else i-mdi-pot-mix-outline />\n\n            <span>\n              好的，今天我们来做菜！\n            </span>\n          </button>\n        </ion-title>\n      </ion-toolbar>\n    </ion-header>\n    <ion-content ref=\"ionContentRef\" class=\"text-center\">\n      <ChooseFood />\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/tabs/library/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { IonSearchbarCustomEvent, SearchbarInputEventDetail } from '@ionic/core'\nimport type { DbRecipeItem } from '~/utils/db'\nimport { watchDebounced } from '@vueuse/core'\nimport { computed, onMounted, ref } from 'vue'\nimport { useIndexedDB } from '~/composables/db'\nimport { isFavorited, toggleFavorite } from '~/composables/store/favorite'\nimport { recipeHistories } from '~/composables/store/history'\nimport { db } from '~/utils/db'\n\ndefinePageMeta({\n  alias: ['/library'],\n})\n\nconst keyword = ref('')\nconst loading = ref(false)\nconst recipes = ref<DbRecipeItem[]>([])\nconst view = ref<'all' | 'fav'>('all')\n\nconst displayed = computed(() => {\n  return view.value === 'fav' ? recipes.value.filter(r => isFavorited(r)) : recipes.value\n})\n\nconst favCount = computed(() => recipes.value.filter(r => isFavorited(r)).length)\n\nconst showToast = ref(false)\nconst toastMessage = ref('')\n\nasync function loadAll() {\n  loading.value = true\n  try {\n    recipes.value = await db.recipes.toArray()\n  }\n  finally {\n    loading.value = false\n  }\n}\n\nasync function runSearch(q: string) {\n  const text = (q || '').trim()\n  if (!text)\n    return loadAll()\n  loading.value = true\n  try {\n    recipes.value = await db.recipes\n      .filter(r => r.name.includes(text))\n      .toArray()\n  }\n  finally {\n    loading.value = false\n  }\n}\n\nonMounted(async () => {\n  // ensure IndexedDB has data\n  const { init } = useIndexedDB()\n  await init()\n  await loadAll()\n})\n\nwatchDebounced(keyword, (q) => {\n  runSearch(q)\n}, { debounce: 200, maxWait: 500 })\n\nfunction onInput(ev: IonSearchbarCustomEvent<SearchbarInputEventDetail>) {\n  // Ionic emits detail.value\n  keyword.value = ev?.detail?.value ?? ''\n}\n\nfunction onClear() {\n  keyword.value = ''\n}\n\nfunction openDishLink(dish: DbRecipeItem) {\n  // keep history like DishTag did\n  recipeHistories.value.push({ recipe: dish, time: Date.now() })\n  const href = dish.link || `https://www.bilibili.com/video/${dish.bv}`\n  window.open(href, '_blank')\n}\n\nfunction onToggleFavorite(item: DbRecipeItem) {\n  toggleFavorite(item)\n  toastMessage.value = isFavorited(item) ? '已添加到收藏' : '已从收藏移除'\n  showToast.value = true\n}\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-title>菜谱列表</ion-title>\n\n        <ion-buttons slot=\"end\">\n          <ion-button title=\"添加菜谱\" router-link=\"/recipes/new\">\n            <ion-icon slot=\"icon-only\" :icon=\"ioniconsAddCircleOutline\" />\n          </ion-button>\n        </ion-buttons>\n      </ion-toolbar>\n\n      <ion-toolbar>\n        <ion-searchbar\n          animated\n          placeholder=\"搜索菜谱\"\n          :debounce=\"300\"\n          show-clear-button=\"focus\"\n          @ion-input=\"onInput\"\n          @ion-clear=\"onClear\"\n        />\n      </ion-toolbar>\n\n      <ion-toolbar class=\"pb-1.5 -mt-2\">\n        <ion-segment\n          :value=\"view\"\n          @ion-change=\"e => (view = (e.detail.value as 'all' | 'fav') ?? 'all')\"\n        >\n          <ion-segment-button value=\"all\">\n            <ion-label>全部</ion-label>\n          </ion-segment-button>\n          <ion-segment-button value=\"fav\">\n            <ion-label>收藏 ({{ favCount }})</ion-label>\n          </ion-segment-button>\n        </ion-segment>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <div v-if=\"loading\" class=\"ion-padding text-center\">\n        <ion-spinner name=\"crescent\" />\n      </div>\n\n      <ion-list v-else-if=\"displayed.length\">\n        <ion-item-sliding v-for=\"item in displayed\" :key=\"item.id ?? item.name\">\n          <ion-item @click=\"openDishLink(item)\">\n            <ion-label class=\"truncate\">\n              <DishLabel class=\"text-sm\" :dish=\"item\" />\n            </ion-label>\n            <ion-button slot=\"end\" fill=\"clear\" @click.stop=\"onToggleFavorite(item)\">\n              <ion-icon :icon=\"isFavorited(item) ? ioniconsStar : ioniconsStarOutline\" color=\"warning\" />\n            </ion-button>\n          </ion-item>\n\n          <ion-item-options>\n            <ion-item-option color=\"warning\" @click=\"onToggleFavorite(item)\">\n              {{ isFavorited(item) ? '取消收藏' : '添加收藏' }}\n            </ion-item-option>\n          </ion-item-options>\n        </ion-item-sliding>\n      </ion-list>\n\n      <div\n        v-else-if=\"(keyword || view === 'fav') && displayed.length === 0\"\n        class=\"ion-padding text-center\"\n      >\n        <ion-note>没有找到相关菜谱</ion-note>\n      </div>\n    </ion-content>\n    <ion-toast\n      :is-open=\"showToast\"\n      :message=\"toastMessage\"\n      duration=\"1200\"\n      position=\"top\"\n      @did-dismiss=\"showToast = false\"\n    />\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/tabs/my/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { links } from '~/constants'\n\ndefinePageMeta({\n  alias: ['/my'],\n})\n</script>\n\n<template>\n  <ion-page>\n    <ion-header>\n      <ion-toolbar>\n        <ion-title>我的</ion-title>\n      </ion-toolbar>\n    </ion-header>\n\n    <ion-content>\n      <!-- TODO -->\n      <ion-list :inset=\"true\">\n        <ion-item router-link=\"/recipes/history\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsTimeOutline\" />\n          <ion-label>历史记录</ion-label>\n        </ion-item>\n        <ion-item router-link=\"/recipes/favorites\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsStarOutline\" />\n          <ion-label>我的收藏</ion-label>\n        </ion-item>\n        <!-- <ion-item router-link=\"/cookbooks\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsBookOutline\" />\n          <ion-label>自定义菜谱</ion-label>\n        </ion-item> -->\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item :href=\"links.githubIssue\" target=\"_blank\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsChatbubbleEllipsesOutline\" />\n          <ion-label>问题反馈</ion-label>\n        </ion-item>\n        <ion-item :href=\"links.githubDiscussions\" target=\"_blank\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsChatbubblesOutline\" />\n          <ion-label>参与讨论</ion-label>\n        </ion-item>\n        <ion-item :href=\"links.contribute\" target=\"_blank\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsMailOutline\" />\n          <ion-label>菜谱投稿</ion-label>\n        </ion-item>\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item router-link=\"/changelog\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsDocumentTextOutline\" />\n          <ion-label>更新日志</ion-label>\n        </ion-item>\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item router-link=\"/settings\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsSettingsOutline\" />\n          <ion-label>设置</ion-label>\n        </ion-item>\n      </ion-list>\n\n      <ion-list :inset=\"true\">\n        <ion-item router-link=\"/help\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsHelpCircleOutline\" />\n          <ion-label>帮助</ion-label>\n        </ion-item>\n        <ion-item router-link=\"/about\">\n          <ion-icon slot=\"start\" :icon=\"ioniconsInformationCircleOutline\" />\n          <ion-label>关于</ion-label>\n        </ion-item>\n      </ion-list>\n\n      <!-- <YlfForm>\n        <YlfFormItem icon=\"i-ri-article-line\" label=\"自定义菜谱 TODO\" to=\"/cookbooks/\" />\n      </YlfForm> -->\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/pages/tabs.vue",
    "content": "<script lang=\"ts\" setup>\nimport { App } from '@capacitor/app'\nimport { Capacitor } from '@capacitor/core'\nimport { useBackButton } from '@ionic/vue'\nimport { tabRootPaths } from '../config'\n\nuseHead({\n  title: 'Cook Tabs',\n})\n\nconst router = useRouter()\nconst ionRouter = useIonRouter()\n\nfunction isTabRootPath(path: string) {\n  return tabRootPaths.includes(path)\n}\n\nonMounted(() => {\n  if (Capacitor.getPlatform() === 'android') {\n    useBackButton(10, () => {\n      if (isTabRootPath(router.currentRoute.value.path)) {\n        App.minimizeApp()\n      }\n      else {\n        ionRouter.back()\n      }\n    })\n  }\n})\n</script>\n\n<template>\n  <ion-page>\n    <ion-content>\n      <ion-tabs>\n        <ion-router-outlet />\n\n        <ion-tab-bar slot=\"bottom\">\n          <ion-tab-button tab=\"home\" href=\"/home\">\n            <ion-icon :icon=\"ioniconsBookOutline\" />\n            <ion-label>做菜</ion-label>\n          </ion-tab-button>\n\n          <IonTabButton tab=\"library\" href=\"/library\">\n            <ion-icon :icon=\"ioniconsLibraryOutline\" />\n            <ion-label>菜谱</ion-label>\n          </IonTabButton>\n\n          <IonTabButton tab=\"apps\" href=\"/apps\">\n            <ion-icon :icon=\"ioniconsRestaurantOutline\" />\n            <ion-label>发现</ion-label>\n          </IonTabButton>\n\n          <!-- <IonTabButton tab=\"tab3\" href=\"/tabs/tab3\">\n            <ion-icon :icon=\"ioniconsBulbOutline\" />\n            <ion-label>Tab 3</ion-label>\n          </IonTabButton> -->\n\n          <IonTabButton tab=\"my\" href=\"/my\">\n            <ion-icon :icon=\"ioniconsPersonCircleOutline\" />\n            <ion-label>我的</ion-label>\n          </IonTabButton>\n        </ion-tab-bar>\n      </ion-tabs>\n    </ion-content>\n  </ion-page>\n</template>\n"
  },
  {
    "path": "app/styles/animation.scss",
    "content": "/* we will explain what these classes do next! */\n.v-enter-active,\n.v-leave-active {\n  transition: opacity 0.3s ease;\n}\n\n.v-enter-from,\n.v-leave-to {\n  opacity: 0;\n}\n\n// scrollbar\n::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n::-webkit-scrollbar-track {\n  border-radius: 2px;\n  background-color: rgba(255, 255, 255, 0.1);\n}\n\n::-webkit-scrollbar-thumb {\n  border-radius: 2px;\n  background-color: rgba(122, 122, 122, 0.3);\n\n  &:window-inactive {\n    background-color: rgba(122, 122, 122, 0.3);\n  }\n\n  &:hover {\n    background-color: rgba(122, 122, 122, 0.7);\n  }\n\n  &:active {\n    background-color: rgba(122, 122, 122, 0.9);\n  }\n}\n"
  },
  {
    "path": "app/styles/css-vars.scss",
    "content": ":root {\n  --c-primary: #5fc178;\n  --c-text: #333;\n\n  --c-bg: white;\n  --c-bg-alt: #f9fbfd;\n}\n\n.dark {\n  --c-text: #fafafa;\n\n  --c-bg: #121212;\n  --c-bg-alt: #333;\n}\n\n// ylf\n:root {\n  --ylf-c-bg-alt: var(--c-bg-alt);\n  --ylf-c-border: #eaeaea;\n}\n\n.dark {\n  --ylf-c-border: #666;\n}\n\n// cook custom\n:root {\n  --cook-bottom-menu-padding-bottom: 20px;\n  --cook-bottom-menu-height: calc(64px + var(--cook-bottom-menu-padding-bottom));\n}\n"
  },
  {
    "path": "app/styles/index.scss",
    "content": "@use './animation.scss';\n// markdown\n@use './markdown.scss';\n\nhtml,\nbody,\n#app {\n  height: 100%;\n  margin: 0;\n  padding: 0;\n\n  background: var(--c-bg);\n}\n\ninput:focus {\n  outline: none;\n}\n\n#nprogress {\n  pointer-events: none;\n}\n\n#nprogress .bar {\n  background: rgb(13, 148, 136);\n  opacity: 0.75;\n  position: fixed;\n  z-index: 1031;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 2px;\n}\n\nhtml {\n  color: var(--c-text);\n  background-color: var(--c-bg);\n\n  scroll-behavior: smooth;\n\n  -webkit-font-smoothing: antialiased;\n  -webkit-tap-highlight-color: transparent;\n}\n\na {\n  color: var(--c-text);\n}\n\nbutton {\n  outline: none;\n\n  &:focus,\n  &:active {\n    outline: none;\n  }\n}\n\nhr {\n  opacity: 0.1;\n}\n\n.tag,\n.tagSize {\n  margin: 4px;\n  padding: 2px 4px;\n  // border: 1px solid var(--c-text);\n}\n"
  },
  {
    "path": "app/styles/index.ts",
    "content": "import '@ionic/vue/css/palettes/dark.class.css'\nimport './ionic.css'\n"
  },
  {
    "path": "app/styles/ionic.css",
    "content": ":root {\n  /* --ion-color-primary: #3880ff; */\n}\n\n/* Ionic Variables and Theming\n * ---------------------------------------------------------------\n * Any overrides to theme variables should be placed in this file.\n * For more information, please see:\n * http://ionicframework.com/docs/theming/\n */\n\n/* This sets a different item border color for the default theme on ios and md */\n:root {\n  --ion-item-border-color: var(--ion-background-color-step-200);\n}\n\n/* This sets a different background and item background for the default theme on ios */\n:root.ios {\n  --ion-background-color: var(--ion-background-color-step-50, #f2f2f6);\n  --ion-toolbar-background: var(--ion-background-color);\n  --ion-item-background: #fff;\n}\n\n/* This sets a different background and item background for the default theme on md */\n:root.md {\n  --ion-background-color: var(--ion-background-color-step-100, #f9f9f9);\n  --ion-toolbar-background: var(--ion-background-color);\n  --ion-item-background: #fff;\n}\n\n/* This sets a different item background when dark mode is enabled on ios and md */\n.ion-palette-dark.ios,\n.ion-palette-dark.md {\n  --ion-item-background: #1c1c1d;\n}\n\n/* custom */\nhtml {\n  /* --ion-safe-area-top: 0px; */\n}\n"
  },
  {
    "path": "app/styles/markdown.scss",
    "content": "@use 'star-markdown-css/src/scss/theme/yun.scss';\n\n.markdown-body {\n  li {\n    list-style: disc;\n  }\n}\n\nblockquote {\n  border-left: 0.25em solid #ddd;\n  padding: 0 1em;\n  color: #777;\n  quotes: '\\\\201C' '\\\\201D' '\\\\2018' '\\\\2019';\n}\n\nol {\n  list-style: decimal;\n  padding-left: 2em;\n\n  li {\n    line-height: 2;\n  }\n}\n\nul {\n  list-style: disc;\n  padding-left: 2em;\n\n  li {\n    line-height: 2;\n  }\n}\n\na {\n  border-bottom: 1px dashed #ddd;\n}\n"
  },
  {
    "path": "app/types/cookbook.ts",
    "content": "import type { Recipes } from './recipe'\n\nexport interface Cookbook {\n  /**\n   * 菜谱 ID，自定义，唯一标识符\n   */\n  id: string\n  cover?: string\n  /**\n   * 菜谱名称\n   */\n  title: string\n  description: string\n  author: string | string[]\n  /**\n   * 菜谱\n   */\n  recipes: Recipes\n\n  createdAt: string\n  updatedAt: string\n}\n"
  },
  {
    "path": "app/types/incompatible-foods.ts",
    "content": "/**\n * 食物相克规则\n */\nexport interface IncompatibleRule {\n  foodA: string\n  foodB: string\n  reason: string\n}\n"
  },
  {
    "path": "app/types/index.ts",
    "content": "export * from './cookbook'\nexport * from './incompatible-foods'\nexport * from './recipe'\n"
  },
  {
    "path": "app/types/recipe.ts",
    "content": "export type Difficulty = '简单' | '普通' | '困难'\n\nexport interface RecipeItem {\n  /**\n   * 菜名\n   */\n  name: string\n  /**\n   * 链接\n   */\n  link?: string\n  /**\n   * BiliBili video id\n   */\n  bv?: string\n  /**\n   * 材料\n   */\n  stuff: string[]\n  /**\n   * 根据材料生成\n   */\n  emojis?: string[]\n  /**\n   * 难度\n   */\n  difficulty?: Difficulty | ''\n  /**\n   * 标签\n   */\n  tags?: string[]\n  /**\n   * 方式\n   */\n  methods?: ('炒' | '煎' | '烘' | '炸')[]\n  /**\n   * 工具\n   */\n  tools: string[]\n}\n\nexport type Recipes = RecipeItem[]\n\nexport interface StuffItem {\n  /**\n   * 食材名称\n   */\n  name: string\n  /**\n   * 例如：🥔\n   */\n  emoji: string\n  /**\n   * 图片链接\n   */\n  image?: string\n  /**\n   * 别名，譬如：西红柿/番茄\n   */\n  alias?: string\n  /**\n   * 图标名称\n   */\n  icon?: string\n  /**\n   * 显示标签\n   */\n  label?: string\n}\n"
  },
  {
    "path": "app/utils/cookbook.ts",
    "content": "import type { Cookbook } from '~/types'\n\nexport const defaultCookbook: Cookbook = {\n  id: 'default',\n  title: '默认菜谱',\n  description: '记录了一些特殊时期常用的菜谱',\n  author: [''],\n  recipes: [],\n  updatedAt: '',\n  createdAt: '2021-04-04',\n}\n"
  },
  {
    "path": "app/utils/db.ts",
    "content": "import type { Table } from 'dexie'\nimport type { RecipeItem } from '~/types'\n\nimport Dexie from 'dexie'\n\nexport interface DbRecipeItem extends RecipeItem {\n  id?: number\n}\n\nexport class MySubClassedDexie extends Dexie {\n  recipes!: Table<DbRecipeItem>\n\n  constructor() {\n    super('cook-db')\n    this.version(1).stores({\n      recipes: '++id, name, stuff, bv, difficulty, tags, methods, tools, link, description', // Primary key and indexed props\n    })\n  }\n}\n\nexport const db = new MySubClassedDexie()\n\nexport async function initDb() {\n  const { default: recipeData } = await import('../data/recipe.json')\n\n  return db.recipes.bulkPut(\n    (recipeData as RecipeItem[]).map((item, i) => ({\n      id: i,\n      ...item,\n    })),\n  )\n}\n"
  },
  {
    "path": "app/utils/index.ts",
    "content": "import { meat, staple, vegetable } from '~/data/food'\n\nexport * from './cookbook'\n\nconst foodItems = [...vegetable, ...meat, ...staple]\nconst foodEmojiMap = new Map()\nfoodItems.forEach((item) => {\n  foodEmojiMap.set(item.name, item.emoji)\n})\n\n/**\n * get emojis from stuff name array\n * @param stuff\n */\nexport function getEmojisFromStuff(stuff: string[]) {\n  const emojis: string[] = stuff.map(name => foodEmojiMap.get(name)).filter(item => !!item)\n  return emojis\n}\n"
  },
  {
    "path": "app/utils/pwa.ts",
    "content": "import { isClient } from '@vueuse/core'\n\n/**\n * - https://web.dev/customize-install/#detect-install\n * - [Trigger installation from your PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Trigger_install_prompt)\n */\nexport function installPrompt() {\n  if (!isClient)\n    return\n\n  const app = useAppStore()\n\n  window.addEventListener('beforeinstallprompt', (e) => {\n    // Prevent the mini-infobar from appearing on mobile\n    e.preventDefault()\n    // Stash the event so it can be triggered later.\n    app.deferredPrompt = e\n\n    // Update UI notify the user they can install the PWA\n    // showInstallPromotion()\n    // Optionally, send analytics event that PWA install promo was shown.\n    // eslint-disable-next-line no-console\n    console.log('\\'beforeinstallprompt\\' event was fired.')\n  })\n\n  window.addEventListener('appinstalled', () => {\n    // Hide the app-provided install promotion\n    // hideInstallPromotion()\n    // Clear the deferredPrompt so it can be garbage collected\n    app.deferredPrompt = null\n    // Optionally, send analytics event to indicate successful install\n    // eslint-disable-next-line no-console\n    console.log('PWA was installed')\n  })\n}\n"
  },
  {
    "path": "app/utils/random.ts",
    "content": "/**\n * 生成随机数组\n */\nexport function generateRandomArray(length: number, total = 1) {\n  const randomArr: number[] = []\n  for (let i = 0; i < total; i++) {\n    const randomIndex = Math.floor(Math.random() * length)\n    if (randomArr.includes(randomIndex)) {\n      i--\n      continue\n    }\n    randomArr.push(randomIndex)\n  }\n  return randomArr\n}\n"
  },
  {
    "path": "app/utils/settings.ts",
    "content": "export interface UserSettings {\n  /**\n   * 保留本地数据\n   */\n  keepLocalData: boolean\n}\n\nexport const defaultSettings: UserSettings = {\n  keepLocalData: true,\n}\n"
  },
  {
    "path": "app.config.ts",
    "content": "export default defineAppConfig({\n  theme: {\n    primaryColor: '#ababab',\n  },\n})\n"
  },
  {
    "path": "bump.config.ts",
    "content": "import { defineConfig } from 'bumpp'\n\nexport default defineConfig({\n  release: 'prompt',\n  all: true,\n  commit: true,\n  tag: true,\n  push: true,\n  files: [\n    'package.json',\n  ],\n})\n"
  },
  {
    "path": "capacitor.config.ts",
    "content": "/// <reference types=\"@capacitor/status-bar\" />\n\nimport type { CapacitorConfig } from '@capacitor/cli'\n\nconst config: CapacitorConfig = {\n  appId: 'cn.yunyoujun.cook',\n  appName: 'cook',\n  webDir: 'dist',\n\n  plugins: {\n    StatusBar: {\n      overlaysWebView: false,\n      style: 'DARK',\n      backgroundColor: '#ffffffff',\n    },\n  },\n}\n\nexport default config\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "node_modules\ndist\n\n.vitepress/cache\n"
  },
  {
    "path": "docs/.vitepress/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\n// biome-ignore lint: disable\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    DocsProjectCard: typeof import('./../../node_modules/.pnpm/@yunyoujun+docs@0.1.13/node_modules/@yunyoujun/docs/client/components/DocsProjectCard.vue')['default']\n    DocsProjectList: typeof import('./../../node_modules/.pnpm/@yunyoujun+docs@0.1.13/node_modules/@yunyoujun/docs/client/components/DocsProjectList.vue')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n  }\n}\n"
  },
  {
    "path": "docs/.vitepress/config/index.ts",
    "content": "import { getVitepressConfig } from '@yunyoujun/docs'\nimport { defineConfig } from 'vitepress'\n\nconst vpConfig = getVitepressConfig({\n  repo: 'https://github.com/YunYouJun/cook',\n})\n\nexport default defineConfig({\n  ...vpConfig,\n  title: 'Cook',\n  description: '食用手册',\n\n  themeConfig: {\n    ...vpConfig.themeConfig,\n  },\n})\n"
  },
  {
    "path": "docs/dev/app.md",
    "content": "# APP\n\n考虑到跨平台和开发效率，计划使用：\n\n- [Capacitor](https://capacitorjs.com/)\n- [Ionic](https://ionicframework.com/)\n- [Nuxt Ionic](https://ionic.nuxtjs.org/)\n"
  },
  {
    "path": "docs/guide/getting-started.md",
    "content": "# 使用说明\n\nTODO\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"Cook\"\n  text: \"食用手册\"\n  tagline: 今天吃什么\n  actions:\n    - theme: brand\n      text: 开始使用\n      link: https://cook.yunyoujun.cn\n    - theme: alt\n      text: 使用说明\n      link: /guide/getting-started\n\nfeatures:\n  - title: Feature A\n    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit\n  - title: Feature B\n    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit\n  - title: Feature C\n    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit\n---\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@cook/docs\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"pnpm run predocs && pnpm run docs:build\",\n    \"predocs\": \"typedoc\",\n    \"docs:dev\": \"vitepress dev --host\",\n    \"docs:build\": \"vitepress build\",\n    \"docs:preview\": \"vitepress preview\"\n  },\n  \"dependencies\": {\n    \"@yunyoujun/docs\": \"^0.1.14\"\n  },\n  \"devDependencies\": {\n    \"@shikijs/vitepress-twoslash\": \"^3.23.0\",\n    \"sass\": \"^1.97.3\",\n    \"typedoc\": \"^0.28.17\",\n    \"typedoc-plugin-markdown\": \"^4.10.0\",\n    \"typedoc-vitepress-theme\": \"^1.1.2\",\n    \"unocss\": \"^66.6.4\",\n    \"unplugin-vue-components\": \"^31.0.0\",\n    \"vite-plugin-vue-devtools\": \"^8.0.7\",\n    \"vitepress\": \"^2.0.0-alpha.16\",\n    \"vitepress-plugin-group-icons\": \"^1.7.1\"\n  }\n}\n"
  },
  {
    "path": "docs/public/CNAME",
    "content": "docs.cook.yunyoujun.cn\n"
  },
  {
    "path": "docs/vite.config.ts",
    "content": "import { getViteConfig } from '@yunyoujun/docs'\n\nimport { defineConfig } from 'vite'\n\nconst viteConfig = getViteConfig()\n\nexport default defineConfig({\n  ...viteConfig,\n})\n"
  },
  {
    "path": "edgeone.json",
    "content": "{\n  \"installCommand\": \"corepack enable && pnpm install\",\n  \"buildCommand\": \"pnpm build\",\n  \"outputDirectory\": \"dist\",\n  \"nodeVersion\": \"22.20.0\"\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "// @ts-check\nimport antfu from '@antfu/eslint-config'\nimport nuxt from './.nuxt/eslint.config.mjs'\n\nexport default nuxt(\n  antfu(\n    {\n      unocss: true,\n      formatters: true,\n    },\n    {\n      ignores: [\n        'app/data/*.json',\n        'ios/**/*',\n        'android/**/*',\n        'dist/**/*',\n        'public/**/*',\n        'node_modules/**/*',\n        '.nuxt/**/*',\n        '.output/**/*',\n      ],\n    },\n    {\n      rules: {\n        'vue/no-deprecated-slot-attribute': 'off',\n      },\n    },\n  ),\n)\n"
  },
  {
    "path": "ionic.config.json",
    "content": "{\n  \"name\": \"cook\",\n  \"integrations\": {\n    \"capacitor\": {}\n  },\n  \"type\": \"vue\"\n}\n"
  },
  {
    "path": "ios/.gitignore",
    "content": "App/build\nApp/Pods\nApp/output\nApp/App/public\nDerivedData\nxcuserdata\n\n# Cordova plugins for Capacitor\ncapacitor-cordova-ios-plugins\n\n# Generated Config files\nApp/App/capacitor.config.json\nApp/App/config.xml\n"
  },
  {
    "path": "ios/App/App/AppDelegate.swift",
    "content": "import UIKit\nimport Capacitor\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        // Override point for customization after application launch.\n        return true\n    }\n\n    func applicationWillResignActive(_ application: UIApplication) {\n        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.\n        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.\n    }\n\n    func applicationDidEnterBackground(_ application: UIApplication) {\n        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.\n        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.\n    }\n\n    func applicationWillEnterForeground(_ application: UIApplication) {\n        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.\n    }\n\n    func applicationDidBecomeActive(_ application: UIApplication) {\n        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.\n    }\n\n    func applicationWillTerminate(_ application: UIApplication) {\n        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.\n    }\n\n    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {\n        // Called when the app was launched with a url. Feel free to add additional processing here,\n        // but if you want the App API to support tracking app url opens, make sure to keep this call\n        return ApplicationDelegateProxy.shared.application(app, open: url, options: options)\n    }\n\n    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {\n        // Called when the app was launched with an activity, including Universal Links.\n        // Feel free to add additional processing here, but if you want the App API to support\n        // tracking app url opens, make sure to keep this call\n        return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)\n    }\n\n}\n"
  },
  {
    "path": "ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\": [\n    {\n      \"filename\": \"AppIcon-512@2x.png\",\n      \"idiom\": \"universal\",\n      \"platform\": \"ios\",\n      \"size\": \"1024x1024\"\n    }\n  ],\n  \"info\": {\n    \"author\": \"xcode\",\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "ios/App/App/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\": {\n    \"version\": 1,\n    \"author\": \"xcode\"\n  }\n}\n"
  },
  {
    "path": "ios/App/App/Assets.xcassets/Splash.imageset/Contents.json",
    "content": "{\n  \"images\": [\n    {\n      \"idiom\": \"universal\",\n      \"filename\": \"splash-2732x2732-2.png\",\n      \"scale\": \"1x\"\n    },\n    {\n      \"idiom\": \"universal\",\n      \"filename\": \"splash-2732x2732-1.png\",\n      \"scale\": \"2x\"\n    },\n    {\n      \"idiom\": \"universal\",\n      \"filename\": \"splash-2732x2732.png\",\n      \"scale\": \"3x\"\n    }\n  ],\n  \"info\": {\n    \"version\": 1,\n    \"author\": \"xcode\"\n  }\n}\n"
  },
  {
    "path": "ios/App/App/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"17132\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina4_7\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"17105\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <imageView key=\"view\" userInteractionEnabled=\"NO\" contentMode=\"scaleAspectFill\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" image=\"Splash\" id=\"snD-IY-ifK\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </imageView>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"Splash\" width=\"1366\" height=\"1366\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "ios/App/App/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"14111\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" colorMatched=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <device id=\"retina4_7\" orientation=\"portrait\">\n        <adaptation id=\"fullscreen\"/>\n    </device>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"14088\"/>\n    </dependencies>\n    <scenes>\n        <!--Bridge View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"CAPBridgeViewController\" customModule=\"Capacitor\" sceneMemberID=\"viewController\"/>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "ios/App/App/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>cook</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "ios/App/App.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };\n\t\t50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };\n\t\t504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };\n\t\t504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };\n\t\t504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };\n\t\t504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };\n\t\t50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };\n\t\tA084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = \"<group>\"; };\n\t\t50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = \"<group>\"; };\n\t\t504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = \"<group>\"; };\n\t\tAF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tAF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-App.release.xcconfig\"; path = \"Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tFC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-App.debug.xcconfig\"; path = \"Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t504EC3011FED79650016851F /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t504EC2FB1FED79650016851F = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t504EC3061FED79650016851F /* App */,\n\t\t\t\t504EC3051FED79650016851F /* Products */,\n\t\t\t\t7F8756D8B27F46E3366F6CEA /* Pods */,\n\t\t\t\t27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t504EC3051FED79650016851F /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t504EC3041FED79650016851F /* App.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t504EC3061FED79650016851F /* App */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50379B222058CBB4000EE86E /* capacitor.config.json */,\n\t\t\t\t504EC3071FED79650016851F /* AppDelegate.swift */,\n\t\t\t\t504EC30B1FED79650016851F /* Main.storyboard */,\n\t\t\t\t504EC30E1FED79650016851F /* Assets.xcassets */,\n\t\t\t\t504EC3101FED79650016851F /* LaunchScreen.storyboard */,\n\t\t\t\t504EC3131FED79650016851F /* Info.plist */,\n\t\t\t\t2FAD9762203C412B000D30F8 /* config.xml */,\n\t\t\t\t50B271D01FEDC1A000F3C39B /* public */,\n\t\t\t);\n\t\t\tpath = App;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7F8756D8B27F46E3366F6CEA /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tFC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,\n\t\t\t\tAF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t504EC3031FED79650016851F /* App */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget \"App\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t504EC3001FED79650016851F /* Sources */,\n\t\t\t\t504EC3011FED79650016851F /* Frameworks */,\n\t\t\t\t504EC3021FED79650016851F /* Resources */,\n\t\t\t\t9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = App;\n\t\t\tproductName = App;\n\t\t\tproductReference = 504EC3041FED79650016851F /* App.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t504EC2FC1FED79650016851F /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 0920;\n\t\t\t\tLastUpgradeCheck = 0920;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t504EC3031FED79650016851F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.2;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject \"App\" */;\n\t\t\tcompatibilityVersion = \"Xcode 8.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 504EC2FB1FED79650016851F;\n\t\t\tpackageReferences = (\n\t\t\t);\n\t\t\tproductRefGroup = 504EC3051FED79650016851F /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t504EC3031FED79650016851F /* App */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t504EC3021FED79650016851F /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t50B271D11FEDC1A000F3C39B /* public in Resources */,\n\t\t\t\t504EC30F1FED79650016851F /* Assets.xcassets in Resources */,\n\t\t\t\t50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,\n\t\t\t\t504EC30D1FED79650016851F /* Main.storyboard in Resources */,\n\t\t\t\t2FAD9763203C412B000D30F8 /* config.xml in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t504EC3001FED79650016851F /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t504EC3081FED79650016851F /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t504EC30B1FED79650016851F /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t504EC30C1FED79650016851F /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t504EC3111FED79650016851F /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t504EC3141FED79650016851F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t504EC3151FED79650016851F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Owholemodule\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t504EC3171FED79650016851F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = N3885VFNCL;\n\t\t\t\tINFOPLIST_FILE = App/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tOTHER_SWIFT_FLAGS = \"$(inherited) \\\"-D\\\" \\\"COCOAPODS\\\" \\\"-DDEBUG\\\"\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = cn.yunyoujun.cook;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t504EC3181FED79650016851F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = N3885VFNCL;\n\t\t\t\tINFOPLIST_FILE = App/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = cn.yunyoujun.cook;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t504EC2FF1FED79650016851F /* Build configuration list for PBXProject \"App\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t504EC3141FED79650016851F /* Debug */,\n\t\t\t\t504EC3151FED79650016851F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget \"App\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t504EC3171FED79650016851F /* Debug */,\n\t\t\t\t504EC3181FED79650016851F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 504EC2FC1FED79650016851F /* Project object */;\n}\n"
  },
  {
    "path": "ios/App/App.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:App.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "ios/App/Podfile",
    "content": "require_relative '../../node_modules/.pnpm/@capacitor+ios@7.4.3_@capacitor+core@7.4.3/node_modules/@capacitor/ios/scripts/pods_helpers'\n\nplatform :ios, '14.0'\nuse_frameworks!\n\n# workaround to avoid Xcode caching of Pods that requires\n# Product -> Clean Build Folder after new Cordova plugins installed\n# Requires CocoaPods 1.6 or newer\ninstall! 'cocoapods', :disable_input_output_paths => true\n\ndef capacitor_pods\n  pod 'Capacitor', :path => '../../node_modules/.pnpm/@capacitor+ios@7.4.3_@capacitor+core@7.4.3/node_modules/@capacitor/ios'\n  pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@7.4.3_@capacitor+core@7.4.3/node_modules/@capacitor/ios'\n  pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.1.0_@capacitor+core@7.4.3/node_modules/@capacitor/app'\n  pod 'CapacitorHaptics', :path => '../../node_modules/.pnpm/@capacitor+haptics@7.0.2_@capacitor+core@7.4.3/node_modules/@capacitor/haptics'\n  pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.3_@capacitor+core@7.4.3/node_modules/@capacitor/keyboard'\n  pod 'CapacitorStatusBar', :path => '../../node_modules/.pnpm/@capacitor+status-bar@7.0.3_@capacitor+core@7.4.3/node_modules/@capacitor/status-bar'\n  pod 'CapacitorDialog', :path => '../../node_modules/.pnpm/@capacitor+dialog@7.0.2_@capacitor+core@7.4.3/node_modules/@capacitor/dialog'\nend\n\ntarget 'App' do\n  capacitor_pods\n  # Add your Pods here\nend\n\npost_install do |installer|\n  assertDeploymentTarget(installer)\nend\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\npublish = \"dist\"\ncommand = \"pnpm run build\"\n\n[build.environment]\nNODE_VERSION = \"16\"\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.html\"\nstatus = 200\n"
  },
  {
    "path": "nuxt.config.ts",
    "content": "import process from 'node:process'\nimport { defineNuxtConfig } from 'nuxt/config'\n\n// import { pwa } from './app/config/pwa'\nimport { appDescription } from './app/constants/index'\n\n// for cloudflare\n// Object.assign(process.env, {\n//   VITE_COMMIT_REF: process.env.CF_PAGES_COMMIT_SHA || '',\n// })\n\ntry {\n  const { getLatestCommit } = await import('./scripts/git')\n  const latestCommit = await getLatestCommit()\n  /**\n   * CF_PAGES_COMMIT_SHA is Cloudflare Pages env\n   */\n  import.meta.env.VITE_COMMIT_REF = process.env.CF_PAGES_COMMIT_SHA || latestCommit?.hash || ''\n  // add build date string to env\n  import.meta.env.VITE_APP_BUILD_DATE = latestCommit?.date || new Date().toString()\n}\ncatch (e) {\n  console.error('Not in git repo, get latest commit failed:', e)\n  import.meta.env.VITE_APP_BUILD_DATE = new Date().toString()\n  import.meta.env.VITE_COMMIT_REF = ''\n}\n\nexport default defineNuxtConfig({\n  modules: [\n    '@vueuse/nuxt',\n    '@unocss/nuxt',\n    '@pinia/nuxt',\n    '@nuxtjs/color-mode',\n    // '@vite-pwa/nuxt',\n    '@nuxt/eslint',\n    '@nuxt/test-utils/module',\n    '@yunlefun/vue/nuxt',\n    // fix QQ in iOS, Done\n    // See https://github.com/unjs/ofetch/pull/366\n    // 'nuxt-fix-ofetch',\n    '@nuxt/scripts',\n    '@nuxtjs/ionic',\n  ],\n  ssr: false,\n\n  components: [\n    { path: '~/components', pathPrefix: false },\n  ],\n\n  devtools: {\n    enabled: true,\n  },\n\n  app: {\n    head: {\n      viewport: 'width=device-width,initial-scale=1',\n      link: [\n        { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },\n        { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },\n      ],\n      meta: [\n        { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=cover' },\n        { name: 'description', content: appDescription },\n        { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },\n        { name: 'color-scheme', content: 'light dark' },\n        { name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },\n        { name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },\n      ],\n    },\n  },\n\n  css: [\n    '@unocss/reset/tailwind.css',\n    '~/styles/css-vars.scss',\n    '~/styles/index.scss',\n    '~/styles/index.ts',\n  ],\n\n  router: {\n    options: {\n      scrollBehaviorType: 'smooth',\n    },\n  },\n\n  colorMode: {\n    classSuffix: '',\n  },\n\n  future: {\n    compatibilityVersion: 4,\n  },\n\n  experimental: {\n    // when using generate, payload js assets included in sw precache manifest\n    // but missing on offline, disabling extraction it until fixed\n    payloadExtraction: false,\n    renderJsonPayloads: true,\n    typedPages: true,\n  },\n  compatibilityDate: '2025-05-15',\n\n  nitro: {\n    esbuild: {\n      options: {\n        target: 'esnext',\n      },\n    },\n    prerender: {\n      crawlLinks: false,\n      routes: ['/', '/random', '/help', '/user', '/404', '/settings'],\n      ignore: ['/hi'],\n    },\n  },\n\n  eslint: {\n    config: {\n      standalone: false,\n      nuxt: {\n        sortConfigKeys: true,\n      },\n    },\n  },\n\n  ionic: {\n    css: {\n      core: true,\n      basic: true,\n      utilities: true,\n    },\n    config: {\n      mode: 'ios',\n      backButtonText: '返回',\n    },\n  },\n\n  // pwa,\n\n  /**\n   * @see https://scripts.nuxt.com/scripts/tracking/google-tag-manager\n   */\n  scripts: {\n    registry: {\n      googleTagManager: {\n        id: 'GTM-5FJSV46',\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"cook\",\n  \"type\": \"module\",\n  \"version\": \"2.0.0-beta.12\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.30.3\",\n  \"engines\": {\n    \"node\": \"^20.19.0 || >=22.12.0\"\n  },\n  \"scripts\": {\n    \"build\": \"npm run convert && npm run generate\",\n    \"build:nuxt\": \"nuxt build\",\n    \"build:android\": \"bash ./scripts/android/build.sh\",\n    \"convert\": \"pnpm -C scripts run convert\",\n    \"dev\": \"nuxt dev\",\n    \"dev:pwa\": \"VITE_PLUGIN_PWA=true nuxi dev\",\n    \"dev:android\": \"bash ./scripts/android/dev.sh\",\n    \"bash:ios\": \"bash ./scripts/ios.sh\",\n    \"dev:ios\": \"cap run ios -l\",\n    \"ios\": \"cap run ios\",\n    \"android\": \"cap run android\",\n    \"open:ios\": \"cap open ios\",\n    \"open:android\": \"cap open android\",\n    \"docs:dev\": \"pnpm -C docs run docs:dev\",\n    \"generate\": \"nuxt generate\",\n    \"start:generate\": \"npx serve dist\",\n    \"start\": \"node .output/server/index.mjs\",\n    \"lint\": \"eslint .\",\n    \"postinstall\": \"nuxt prepare\",\n    \"preview\": \"nuxt preview\",\n    \"release\": \"bumpp\",\n    \"sync\": \"cap sync\",\n    \"test\": \"vitest\",\n    \"typecheck\": \"nuxt typecheck\"\n  },\n  \"dependencies\": {\n    \"@capacitor/app\": \"7.1.0\",\n    \"@capacitor/core\": \"7.4.3\",\n    \"@capacitor/haptics\": \"7.0.2\",\n    \"@capacitor/ios\": \"7.4.3\",\n    \"@capacitor/keyboard\": \"7.0.3\",\n    \"@capacitor/status-bar\": \"7.0.3\",\n    \"dayjs\": \"^1.11.19\",\n    \"vue-about-me\": \"^1.4.0\",\n    \"vue-virtual-scroller\": \"2.0.0-beta.8\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"^7.6.1\",\n    \"@capacitor/android\": \"^8.1.0\",\n    \"@capacitor/cli\": \"7.4.3\",\n    \"@capacitor/dialog\": \"^8.0.1\",\n    \"@headlessui/vue\": \"^1.7.23\",\n    \"@iconify-json/carbon\": \"^1.2.19\",\n    \"@iconify-json/fe\": \"^1.2.4\",\n    \"@iconify-json/gg\": \"^1.2.2\",\n    \"@iconify-json/ic\": \"^1.2.4\",\n    \"@iconify-json/mdi\": \"^1.2.3\",\n    \"@iconify-json/ri\": \"^1.2.10\",\n    \"@iconify-json/twemoji\": \"^1.2.5\",\n    \"@nuxt/devtools\": \"^3.2.2\",\n    \"@nuxt/eslint\": \"^1.15.2\",\n    \"@nuxt/scripts\": \"^0.13.2\",\n    \"@nuxt/test-utils\": \"^4.0.0\",\n    \"@nuxtjs/color-mode\": \"^4.0.0\",\n    \"@nuxtjs/ionic\": \"1.0.2\",\n    \"@pinia/nuxt\": \"^0.11.3\",\n    \"@pinia/testing\": \"^1.0.3\",\n    \"@types/node\": \"^25.3.3\",\n    \"@unhead/vue\": \"^2.1.10\",\n    \"@unocss/eslint-config\": \"^66.6.4\",\n    \"@unocss/nuxt\": \"^66.6.4\",\n    \"@vite-pwa/nuxt\": \"^1.1.1\",\n    \"@vue/test-utils\": \"^2.4.6\",\n    \"@vueuse/nuxt\": \"^14.2.1\",\n    \"@yunlefun/vue\": \"^0.1.1\",\n    \"baseline-browser-mapping\": \"^2.10.0\",\n    \"bumpp\": \"^10.4.1\",\n    \"consola\": \"^3.4.2\",\n    \"dexie\": \"^4.3.0\",\n    \"eslint\": \"^10.0.2\",\n    \"eslint-plugin-format\": \"^2.0.1\",\n    \"fake-indexeddb\": \"^6.2.5\",\n    \"jsdom\": \"^28.1.0\",\n    \"lint-staged\": \"^16.3.1\",\n    \"nuxt\": \"^4.3.1\",\n    \"pinia\": \"^3.0.4\",\n    \"sass\": \"^1.97.3\",\n    \"serve\": \"^14.2.5\",\n    \"simple-git-hooks\": \"^2.13.1\",\n    \"star-markdown-css\": \"^0.5.3\",\n    \"tsx\": \"^4.21.0\",\n    \"typescript\": \"^5.9.3\",\n    \"unocss\": \"^66.6.4\",\n    \"vitest\": \"^4.0.18\",\n    \"vue-tsc\": \"^3.2.5\"\n  },\n  \"resolutions\": {\n    \"unplugin\": \"^2.3.11\",\n    \"vite\": \"^7.3.1\"\n  },\n  \"simple-git-hooks\": {\n    \"pre-commit\": \"pnpm lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*\": \"eslint --fix\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "shellEmulator: true\n\ntrustPolicy: no-downgrade\n\npackages:\n  - docs\n  - scripts\n\nonlyBuiltDependencies:\n  - '@parcel/watcher'\n  - esbuild\n  - simple-git-hooks\n  - unrs-resolver\n"
  },
  {
    "path": "public/_headers",
    "content": "/assets/*\n  cache-control: max-age=31536000\n  cache-control: immutable\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nAllow: /\n"
  },
  {
    "path": "scripts/android/build.sh",
    "content": "npx cap build android\n"
  },
  {
    "path": "scripts/android/dev.sh",
    "content": "#!/bin/bash\nLIP=$(ipconfig getifaddr en0)\n\necho \"🍦 Starting local development to android device - ensure local dev server is running already\"\necho \"🏗️ Type checking and building for development...\"\npnpm run build:dev\necho \"🔃 Capacitor installation, podfile installation, sync and copy to app distribution folders...\"\nnpx @ionic/cli capacitor sync android --no-build\necho \"🏃 Select an Android device to run the build at local ip address ${LIP} on...\"\neval \"npx @ionic/cli capacitor run android --livereload-url=http://${LIP}:3000  --external --mode development\"\n"
  },
  {
    "path": "scripts/config.ts",
    "content": "import path, { dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst recipeCsvFile = path.resolve(__dirname, '../app/data/recipe.csv')\nconst recipeJsonFile = path.resolve(__dirname, '../app/data/recipe.json')\nconst incompatibleFoodsCsvFile = path.resolve(__dirname, '../app/data/incompatible-foods.csv')\nconst incompatibleFoodsJsonFile = path.resolve(__dirname, '../app/data/incompatible-foods.json')\n\nexport const config = {\n  recipeCsvFile,\n  recipeJsonFile,\n  incompatibleFoodsCsvFile,\n  incompatibleFoodsJsonFile,\n}\n"
  },
  {
    "path": "scripts/convert.ts",
    "content": "import type { IncompatibleRule, RecipeItem, Recipes } from '../app/types'\n// convert csv to json\nimport fs from 'node:fs'\nimport consola from 'consola'\n\nimport { config } from './config'\n\nfunction run() {\n  const csvData = fs.readFileSync(config.recipeCsvFile, 'utf-8')\n  const lines = csvData.split(/\\r?\\n/)\n\n  const headers = 'name,stuff,bv,difficulty,tags,methods,tools,'\n  if (lines.length < 2) {\n    throw new Error('No data in csv file')\n  }\n\n  if (lines[0]?.trim() !== headers) {\n    consola.warn(`Headers Changed: ${lines[0]}`)\n    return\n  }\n\n  const recipeJson: Recipes = []\n  const sep = '、'\n\n  lines.slice(1).forEach((line) => {\n    if (line) {\n      const attrs = line.split(',')\n      if (attrs.length < 7) {\n        consola.warn(`Invalid line: ${line}`)\n        return\n      }\n      const stuff = attrs[1]?.trim().split(sep) || []\n      recipeJson.push({\n        name: attrs[0]?.trim() || '',\n        stuff,\n        // link: attrs[2].trim(),\n        // bv id\n        bv: attrs[2]?.trim().replace('https://www.bilibili.com/video/', ''),\n        difficulty: attrs[3] && attrs[3].trim() as RecipeItem['difficulty'],\n        tags: attrs[4] ? attrs[4].trim().split(sep) : [],\n        methods: attrs[5] ? (attrs[5].trim().split(sep)) as RecipeItem['methods'] : [],\n        tools: attrs[6] ? attrs[6].trim().split(sep) : [],\n      })\n    }\n  })\n\n  fs.writeFileSync(config.recipeJsonFile, JSON.stringify(recipeJson))\n  consola.success(`Generate file: ${config.recipeJsonFile}`)\n}\n\n/**\n * 转换食物相克数据\n */\nfunction convertIncompatibleFoods() {\n  consola.info('---')\n  consola.info('Convert Incompatible Foods Data...')\n\n  try {\n    const csvData = fs.readFileSync(config.incompatibleFoodsCsvFile, 'utf-8')\n    const lines = csvData.split(/\\r?\\n/)\n\n    const headers = 'foodA,foodB,reason'\n    if (lines.length < 2) {\n      throw new Error('No data in incompatible foods csv file')\n    }\n\n    if (lines[0]?.trim() !== headers) {\n      consola.warn(`Headers Changed: ${lines[0]}`)\n      return\n    }\n\n    const incompatibleRules: IncompatibleRule[] = []\n\n    lines.slice(1).forEach((line) => {\n      if (line.trim()) {\n        const attrs = line.split(',')\n        if (attrs.length < 3) {\n          consola.warn(`Invalid line: ${line}`)\n          return\n        }\n\n        const foodA = attrs[0]?.trim()\n        const foodB = attrs[1]?.trim()\n        const reason = attrs[2]?.trim()\n\n        if (!foodA || !foodB || !reason) {\n          consola.warn(`Missing required field(s) in line: ${line}`)\n          return\n        }\n\n        incompatibleRules.push({\n          foodA,\n          foodB,\n          reason,\n        })\n      }\n    })\n\n    fs.writeFileSync(config.incompatibleFoodsJsonFile, JSON.stringify(incompatibleRules, null, 2))\n    consola.success(`Generate file: ${config.incompatibleFoodsJsonFile}`)\n  }\n  catch (error) {\n    consola.error('Failed to convert incompatible foods data:', error)\n  }\n}\n\nfunction main() {\n  run()\n  convertIncompatibleFoods()\n}\n\nmain()\n"
  },
  {
    "path": "scripts/git.ts",
    "content": "import { simpleGit } from 'simple-git'\n\n/**\n * get git repo latest commit\n */\nexport async function getLatestCommit() {\n  const git = simpleGit()\n\n  try {\n    const log = await git.log({ maxCount: 1 })\n    return log.latest\n  }\n  catch (error) {\n    console.error('Error fetching latest commit:', error)\n    return null\n  }\n}\n"
  },
  {
    "path": "scripts/ios.sh",
    "content": "#!/bin/bash\nLIP=$(ipconfig getifaddr en0)\n\necho \"🍦 Starting local development to ios device - ensure local dev server is running already\"\necho \"🏗️ Type checking and building for development...\"\npnpm run build:dev\necho \"🔃 Capacitor installation, podfile installation, sync and copy to app distribution folders...\"\nnpx @ionic/cli capacitor sync ios --no-build\necho \"🏃 Select an iOS device to run the build at local ip address ${LIP} on...\"\neval \"npx @ionic/cli capacitor run ios --livereload-url=http://${LIP}:3000  --external --mode development\"\n"
  },
  {
    "path": "scripts/package.json",
    "content": "{\n  \"name\": \"@cook/scripts\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"convert\": \"tsx convert.ts\"\n  },\n  \"devDependencies\": {\n    \"simple-git\": \"^3.32.3\",\n    \"tsx\": \"^4.21.0\"\n  }\n}\n"
  },
  {
    "path": "shims.d.ts",
    "content": "declare interface Window {\n  // extend the window\n  wx: any\n  /**\n   * pwa install prompt event\n   */\n  deferredPrompt: Event | any\n}\n\n// with vite-plugin-vue-markdown, markdowns can be treat as Vue components\ndeclare module '*.md' {\n  import type { DefineComponent } from 'vue'\n\n  const component: DefineComponent<object, object, any>\n  export default component\n}\n\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n\n  const component: DefineComponent<object, object, any>\n  export default component\n}\n"
  },
  {
    "path": "test/component.test.ts",
    "content": "// import { mount } from '@vue/test-utils'\n// import { describe, expect, it, vi } from 'vitest'\nimport { describe, it } from 'vitest'\n\n// import { createTestingPinia } from '@pinia/testing'\n// import ChooseFood from '../src/components/ChooseFood.vue'\n\ndescribe('chooseFood.vue', () => {\n  it('should render', async () => {\n    //     const pinia = createTestingPinia({\n    //       createSpy: vi.fn,\n    //     })\n    //     const wrapper = mount(ChooseFood, {\n    //       global: {\n    //         plugins: [\n    //           pinia,\n    //         ],\n    //       },\n    //     })\n\n    //     const rStore = useRecipeStore()\n\n    //     rStore.reset()\n    //     rStore.addStuff('黄瓜')\n    //     rStore.addStuff('黄瓜')\n\n    //     expect(rStore.selectedStuff).toEqual(['黄瓜'])\n\n    //     expect(wrapper.find('.vegetable-tag').exists()).toBe(true)\n    //     expect(wrapper.find('.cook-filter-recipes').exists()).toBe(true)\n\n    //     await wrapper.find('.vegetable-tag').trigger('click')\n\n    //     const tags = wrapper.find('.cook-filter-recipes').findAll('.dish-tag')\n\n    //     expect(tags.length > 0).toBe(true)\n\n  //     tags.forEach((tag) => {\n  //       const result = tag.text().includes('🥒') || tag.text().includes('🍲')\n  //       expect(result).toBe(true)\n  //     })\n  })\n\n  //   it('should be interactive', async () => {\n  //     // const wrapper = mount(ChooseFood)\n  //     // expect(wrapper.text()).toContain('0')\n\n  //     // expect(wrapper.find('.inc').exists()).toBe(true)\n\n  //     // expect(wrapper.find('.dec').exists()).toBe(true)\n\n  //     // await wrapper.get('.inc').trigger('click')\n\n  //     // expect(wrapper.text()).toContain('1')\n\n  //     // await wrapper.get('.dec').trigger('click')\n\n//     // expect(wrapper.text()).toContain('0')\n//   })\n})\n"
  },
  {
    "path": "test/recipe.test.ts",
    "content": "import { createPinia, setActivePinia } from 'pinia'\nimport { beforeEach, describe, expect, it } from 'vitest'\nimport { useRecipeStore } from '../app/composables/store'\n\ndescribe('recipe interaction', () => {\n  beforeEach(() => {\n    // creates a fresh pinia and make it active so it's automatically picked\n    // up by any useStore() call without having to pass it to it:\n    // `useStore(pinia)`\n    setActivePinia(createPinia())\n  })\n\n  it('toggle stuff', () => {\n    const rStore = useRecipeStore()\n    rStore.toggleStuff('土豆')\n    expect(rStore.selectedStuff.includes('土豆')).toBe(true)\n    rStore.toggleStuff('土豆')\n    expect(rStore.selectedStuff.includes('土豆')).toBe(false)\n  })\n\n  it('toggle tool', () => {\n    const rStore = useRecipeStore()\n    rStore.toggleTools('电饭煲')\n    expect(rStore.curTool === '电饭煲').toBe(true)\n    rStore.toggleTools('微波炉')\n    expect(rStore.curTool === '微波炉').toBe(true)\n  })\n})\n\ndescribe('recipe mode', () => {\n  beforeEach(() => {\n    setActivePinia(createPinia())\n  })\n\n  it('loose mode', () => {\n    const rStore = useRecipeStore()\n\n    rStore.reset()\n    rStore.addStuff('土豆')\n    rStore.addStuff('腊肠')\n\n    rStore.curTool = '电饭煲'\n\n    expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])\n    rStore.setMode('strict')\n\n    rStore.displayedRecipe.forEach((item) => {\n      expect(item.stuff.includes('土豆') || item.stuff.includes('腊肠')).toBe(true)\n      expect(item.tools?.includes('电饭煲')).toBe(true)\n    })\n  })\n\n  it('strict mode', () => {\n    const rStore = useRecipeStore()\n\n    rStore.reset()\n    rStore.addStuff('土豆')\n    rStore.addStuff('腊肠')\n\n    rStore.curTool = '电饭煲'\n\n    expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])\n    rStore.setMode('strict')\n\n    rStore.displayedRecipe.forEach((item) => {\n      expect(item.stuff.includes('土豆') && item.stuff.includes('腊肠')).toBe(true)\n      expect(item.tools?.includes('电饭煲')).toBe(true)\n    })\n  })\n\n  it('survival mode', () => {\n    const rStore = useRecipeStore()\n\n    rStore.reset()\n    rStore.addStuff('土豆')\n    rStore.addStuff('腊肠')\n\n    expect(rStore.selectedStuff).toStrictEqual(['土豆', '腊肠'])\n    rStore.setMode('survival')\n\n    rStore.displayedRecipe.forEach((item) => {\n      const filtered = item.stuff.every(stuff => rStore.selectedStuff.includes(stuff))\n      expect(filtered).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "test/setup.ts",
    "content": "import { vi } from 'vitest'\nimport 'fake-indexeddb/auto'\n\n// Mock useScriptGoogleTagManager globally\nvi.stubGlobal('useScriptGoogleTagManager', () => ({\n  proxy: {\n    dataLayer: {\n      push: vi.fn(),\n    },\n  },\n}))\n\n// Mock onMounted to prevent Vue warnings in tests\nvi.mock('vue', async () => {\n  const actual = await vi.importActual('vue')\n  return {\n    ...actual,\n    onMounted: vi.fn(),\n  }\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  // https://nuxt.com/docs/guide/concepts/typescript\n  \"references\": [\n    {\n      \"path\": \"./.nuxt/tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.server.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.shared.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.node.json\"\n    }\n  ],\n  \"files\": []\n}\n"
  },
  {
    "path": "uno.config.ts",
    "content": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetTypography,\n  presetWind3,\n  transformerDirectives,\n  transformerVariantGroup,\n} from 'unocss'\n\nimport { tools } from './app/data/food'\n\nconst safelist: string[] = ['text-left']\n\ntools.forEach((item) => {\n  if (item.icon)\n    safelist.push(item.icon)\n})\n\nexport default defineConfig({\n  shortcuts: [\n    ['tag', 'text-sm cursor-pointer inline-flex justify-center items-center transition shadow hover:shadow-md'],\n    ['pureTag', 'text-sm inline-flex justify-center items-center transition shadow'],\n    ['btn', 'text-sm px-4 py-1 rounded inline-block bg-blue-600 text-white cursor-pointer hover:bg-blue-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],\n  ],\n  presets: [\n    // wind4 :host style override bug, so use wind3\n    presetWind3(),\n    presetAttributify(),\n    presetIcons({\n      scale: 1.2,\n    }),\n    presetTypography(),\n  ],\n  transformers: [\n    transformerDirectives(),\n    transformerVariantGroup(),\n  ],\n  safelist,\n})\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    include: ['test/**/*.test.ts'],\n    environment: 'jsdom',\n    server: {\n      deps: {\n        inline: ['@vue', '@vueuse', 'vue-demi'],\n      },\n    },\n\n    setupFiles: ['test/setup.ts'],\n\n    alias: {\n      '~': './',\n    },\n  },\n})\n"
  },
  {
    "path": "vitest.nuxt.config.ts",
    "content": "import path from 'node:path'\nimport { defineVitestConfig } from '@nuxt/test-utils/config'\n\nexport default defineVitestConfig({\n  resolve: {\n    alias: {\n      '~/': path.resolve(__dirname, './'),\n    },\n  },\n\n  test: {\n    include: ['test/**/*.test.ts'],\n    environment: 'nuxt',\n\n    setupFiles: ['test/setup.ts'],\n  },\n})\n"
  }
]