[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: antfu\nopencollective: slidev\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: \"\\U0001F41E Bug report\"\nabout: Create a report to help us improve\ntitle: ''\ntype: Bug\nlabels: ['pending triage']\nassignees: ''\n---\n\n<!-- ⚠️ Please DON'T ignore the issue template -->\n\n<!-- 💡 Consider upgrading to the latest version before sending the issue -->\n\n**Describe the bug**\n\nA clear and concise description of what the bug is.\n\n**Minimal reproduction**\n\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See the error\n\nYou can use https://sli.dev/new to create a new project to reproduce the issue.\n\n**Environment**\n\n- Slidev version:\n- Browser:\n- OS:\n\nIf you are using Slidev globally (i.e. `npx slidev` or `npm i -g slidev`), please try to reproduce the issue in a local project (i.e. `npm create slidev@latest`).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: Questions & Discussions\n    url: https://github.com/slidevjs/slidev/discussions\n    about: Use GitHub discussions for message-board style questions and discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: \"\\U00002728 Feature request\"\nabout: Suggest an idea for this project\ntitle: ''\ntype: Feature\nassignees: ''\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 60\ndaysUntilClose: 7\nexemptLabels:\n  - pinned\n  - security\n  - no-stale\n  - no stale\n  - pr welcome\n  - help wanted\nstaleLabel: stale\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/autofix.yml",
    "content": "name: autofix.ci\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\npermissions:\n  contents: read\n\njobs:\n  autofix:\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Use Node.js lts/*\n        uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n\n      - name: Setup\n        run: npm i -g @antfu/ni\n\n      - name: Install\n        run: nci\n        env:\n          CYPRESS_INSTALL_BINARY: 0\n\n      - name: Lint\n        run: nr lint --fix\n\n      - uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a\n"
  },
  {
    "path": ".github/workflows/cr.yml",
    "content": "# Continuous Releases provided by https://pkg.pr.new\nname: CR (Continuous Releases)\non: [push, pull_request]\n\njobs:\n  cr:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n\n      - run: npm i -g @antfu/ni\n      - run: nci\n\n      - name: Build\n        run: nr build\n\n      - run: nlx pkg-pr-new publish './packages/create-app' './packages/client' './packages/create-theme' './packages/parser' './packages/slidev' './packages/types' --pnpm\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    permissions:\n      id-token: write\n      contents: write\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - uses: pnpm/action-setup@v4\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          registry-url: https://registry.npmjs.org/\n\n      - run: npx changelogithub\n        env:\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n\n      - run: npm i -g npm@latest\n      - run: pnpm i\n      - run: pnpm run ci:publish\n\n      - name: Publish to VSCE & OVSX\n        run: pnpm run publish\n        working-directory: ./packages/vscode\n        env:\n          VSCE_TOKEN: ${{secrets.VSCE_PAT}}\n          OVSX_TOKEN: ${{secrets.OVSX_PAT}}\n"
  },
  {
    "path": ".github/workflows/smoke.yml",
    "content": "name: Production Smoke Test\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - master\n\n  workflow_dispatch:\n\njobs:\n  pack:\n    timeout-minutes: 10\n    runs-on: ubuntu-latest\n    steps:\n      - name: Set git to use LF\n        run: |\n          git config --global core.autocrlf false\n          git config --global core.eol lf\n\n      - uses: actions/checkout@v6\n\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n\n      - name: Setup\n        run: npm i -g @antfu/ni\n\n      - name: Install\n        run: nci\n        env:\n          CYPRESS_INSTALL_BINARY: 0\n\n      - name: Build\n        run: nr build\n\n      - name: Pack\n        run: node ./scripts/pack.mjs /tmp/slidev-pkgs\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: slidev-packages\n          path: /tmp/slidev-pkgs\n\n  test:\n    needs: pack\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n        pm: [npm, pnpm] # yarn not working in this CI\n        hoist: [true, false]\n\n    steps:\n      - name: Set git to use LF\n        run: |\n          git config --global core.autocrlf false\n          git config --global core.eol lf\n\n      - uses: actions/checkout@v6\n\n      - name: Use Node.js lts/*\n        uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n\n      - name: Setup\n        run: npm i -g @antfu/ni\n\n      - name: Setup PNPM\n        uses: pnpm/action-setup@v4\n\n      - name: Install\n        run: nci\n\n      - name: Download artifacts\n        uses: actions/download-artifact@v7\n        with:\n          name: slidev-packages\n          path: /tmp/slidev-pkgs\n\n      - name: Create new project\n        run: |\n          npm i -g /tmp/slidev-pkgs/create-app.tgz\n          echo \"N\" | create-slidev ../temp/slidev-project\n\n      - name: Remove npmrc\n        run: pnpx del-cli ./.npmrc\n        working-directory: ../temp/slidev-project\n        if: ${{ ! matrix.hoist }}\n\n      - name: Remove overridden dependencies\n        run: node ${{ github.workspace }}/scripts/remove-overridden-deps.mjs\n        working-directory: ../temp/slidev-project\n\n      - name: Install project (npm, pnpm)\n        run: ${{ matrix.pm }} i /tmp/slidev-pkgs/cli.tgz playwright-chromium\n        working-directory: ../temp/slidev-project\n        if: ${{ matrix.pm != 'yarn' }}\n\n      - name: Install Playwright browsers\n        run: pnpx playwright install chromium\n        working-directory: ../temp/slidev-project\n\n      - name: Install project (yarn)\n        run: yarn add /tmp/slidev-pkgs/cli.tgz playwright-chromium\n        working-directory: ../temp/slidev-project\n        if: ${{ matrix.pm == 'yarn' }}\n\n      - name: Test build command in project\n        run: pnpm build\n        working-directory: ../temp/slidev-project\n\n      - name: E2E Smoke Test\n        uses: cypress-io/github-action@v7\n        if: ${{ matrix.os != 'windows-latest' }}\n        with:\n          install-command: echo\n          build: echo\n          start: pnpm --dir ../temp/slidev-project dev --port 3041\n          spec: cypress/e2e/examples/smoke.spec.ts\n\n      - name: Install globally\n        run: |\n          ${{ matrix.pm }} i -g /tmp/slidev-pkgs/cli.tgz playwright-chromium\n          ${{ matrix.pm }} i -g @slidev/theme-seriph\n        if: ${{ matrix.pm != 'yarn' }}\n\n      - name: Create slide file\n        run: pnpm --package=cpy-cli dlx cpy ./packages/slidev/template.md ../temp/ --flat\n        if: ${{ matrix.pm != 'yarn' }}\n\n      - name: Test build command in global mode\n        run: slidev build template.md\n        if: ${{ matrix.pm != 'yarn' }}\n        working-directory: ../temp\n\n      # Commented out because it's not working\n      # - name: E2E test in global mode\n      #   uses: cypress-io/github-action@v7\n      #   if: ${{ matrix.os != 'windows' }}\n      #   with:\n      #     project: ${{ github.workspace }}\n      #     install-command: echo\n      #     build: echo\n      #     start: ${{ 'bash -c \"slidev ../template.md\"' }}\n      #     spec: cypress/e2e/examples/noError.spec.ts\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - master\n\njobs:\n  test:\n    timeout-minutes: 10\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        node-version: [lts/*]\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        # os: [ubuntu-latest, macos-latest]\n      fail-fast: false\n\n    steps:\n      - name: Set git to use LF\n        run: |\n          git config --global core.autocrlf false\n          git config --global core.eol lf\n\n      - uses: actions/checkout@v6\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Setup\n        run: npm i -g @antfu/ni\n\n      - name: Install\n        run: nci\n        env:\n          CYPRESS_INSTALL_BINARY: 0\n\n      - name: Build\n        run: nr build\n\n      - name: Test\n        run: nr test\n\n  cypress:\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n\n      - name: Setup\n        run: npm i -g @antfu/ni\n\n      - name: Install\n        run: nci\n\n      - name: Build\n        run: nr build\n\n      - name: Hack Cypress\n        run: cp pnpm-lock.yaml package-lock.json\n\n      - name: Cypress\n        uses: cypress-io/github-action@v7\n        with:\n          install-command: echo\n          build: nr build\n          start: nr cy:fixture\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.eslintcache\n.idea\n.nuxt\n.output\n.slidev\n.vite-inspect\n*-export\n*.local\n*.pdf\n.env\nassets/demo\ncomponents.d.ts\ncomposable-vue-cn\ndist\ndist-ssr\ndocs/.vitepress/@slidev\ndocs/.vitepress/cache\nnode_modules\ncypress/downloads\npackages/create-app/template/pages\npackages/create-app/template/slides.md\npackages/create-app/template/snippets\npackages/slidev/README.md\npackages/slidev/skills\npackages/vscode/syntaxes/codeblock-patch.json\nslides-export.md\n*slides-export.pptx\n.agents\n.claude\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"antfu.vite\",\n    \"Vue.volar\",\n    \"antfu.iconify\",\n    \"dbaeumer.vscode-eslint\",\n    \"antfu.unocss\",\n    \"csstools.postcss\",\n    \"antfu.pnpm-catalog-lens\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Run Extension\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"autoAttachChildProcesses\": true,\n      \"args\": [\n        \"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode\",\n        \"--folder-uri=${workspaceRoot}/packages/vscode/syntaxes\"\n      ],\n      \"outFiles\": [\n        \"${workspaceFolder}/packages/vscode/dist/**/*.js\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  \"files.associations\": {\n    \"*.css\": \"postcss\"\n  },\n  \"unocss.root\": [\n    \"packages/client\"\n  ],\n\n  // Enable the flat config support\n  \"eslint.experimental.useFlatConfig\": true,\n\n  // Disable the default formatter\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n\n  // Auto fix\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\",\n    \"source.organizeImports\": \"never\"\n  },\n\n  // Silent the stylistic rules in you IDE, but still auto fix them\n  \"eslint.rules.customizations\": [\n    { \"rule\": \"@stylistic/*\", \"severity\": \"off\" },\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  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"typescript\",\n    \"typescriptreact\",\n    \"vue\",\n    \"html\",\n    \"markdown\",\n    \"json\",\n    \"jsonc\",\n    \"yaml\"\n  ],\n  \"vitest.disableWorkspaceWarning\": true,\n  \"slidev.include\": [\n    \"**/slides.md\",\n    \"packages/vscode/syntax/slidev.example.md\"\n  ],\n  \"vue.server.hybridMode\": \"typeScriptPluginOnly\"\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nslidev@antfu.me.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nExcited to hear that you are interested in contributing to this project! Thanks!\n\n## Documentation\n\nDocumentation is now being synced from the [`/docs`](https://github.com/slidevjs/slidev/tree/main/docs) folder to the [`slidevjs/docs`](https://github.com/slidevjs/docs) repo.\n\nAll Pull Requests for documentation changes should still be made to this repository. Any merged changes will be automatically mirrored to the new documentation repo.\n\nThe easiest way to contribute documentation to this project is to follow these steps:\n\n1. [Fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo), for example to `https://github.com/octocat/slidev`, where `octocat` is your GitHub username.\n2. Clone the newly forked repo from your GitHub account\n3. Create a new branch to add your work to, i.e. `git checkout -b docs/update-contributing-guidelines`\n4. Make your changes and commit them\n5. Push the branch to your fork\n6. Go to [https://github.com/slidevjs/slidev/pulls](https://github.com/slidevjs/slidev/pulls), there should be a \"Compare & Pull Request\" button, where you can create a PR.\n\n## Setup (in your browser)\n\nYou can contribute through a development environment in your browser by clicking the following button:\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/slidevjs/slidev)\n\n## Setup (locally)\n\nThis project uses [`pnpm`](https://pnpm.io/) to manage the dependencies, install it if you haven't via\n\n```bash\nnpm i -g pnpm\n```\n\nClone this repo to your local machine and install the dependencies.\n\n```bash\npnpm install\n```\n\n## Development\n\nTo build all the packages at once, run the following command on the project root\n\n```bash\npnpm build\n```\n\nBuild with watch mode\n\n```bash\npnpm dev\n```\n\n### Run Demo\n\nTo run Slidev locally, you can run\n\n```bash\npnpm demo:dev\n```\n\nOr with the real-world example `Composable Vue`:\n\n```bash\npnpm demo:composable-vue\n```\n\nThe server will restart automatically every time the builds get updated.\n\n## Project Structure\n\n### Monorepo\n\nWe use monorepo to manage multiple packages\n\n```\npackages\n  slidev/          - main package entry, holds the code on Node.js side\n  client/          - main frontend app\n  parser/          - parser for Slidev's extended Markdown format\n  create-app/      - scripts and template for `npm init slidev`\n  create-theme/    - scripts and template for `npm init slidev-theme`\n  vscode/          - the VSCode extension\n```\n\n## Code Style\n\nDon't worry about the code style as long as you install the dev dependencies. Git hooks will format and fix them for you on committing.\n\n## Thanks\n\nThank you again for being interested in this project! You are awesome!\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-PRESENT Anthony Fu\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": "<br>\n<p align=\"center\">\n<a href=\"https://sli.dev\" target=\"_blank\">\n<img src=\"https://sli.dev/logo-title.png\" alt=\"Slidev\" height=\"250\" width=\"250\"/>\n</a>\n</p>\n\n<p align=\"center\">\nPresentation <b>slide</b>s for <b>dev</b>elopers 🧑‍💻👩‍💻👨‍💻\n</p>\n\n<p align=\"center\">\n<a href=\"https://www.npmjs.com/package/@slidev/cli\" target=\"__blank\"><img src=\"https://img.shields.io/npm/v/@slidev/cli?color=2B90B6&label=\" alt=\"NPM version\"></a>\n<a href=\"https://www.npmjs.com/package/@slidev/cli\" target=\"__blank\"><img alt=\"NPM Downloads\" src=\"https://img.shields.io/npm/dm/@slidev/cli?color=349dbe&label=\"></a>\n<a href=\"https://sli.dev/\" target=\"__blank\"><img src=\"https://img.shields.io/static/v1?label=&message=docs%20%26%20demos&color=45b8cd\" alt=\"Docs & Demos\"></a>\n<a href=\"https://sli.dev/resources/theme-gallery\" target=\"__blank\"><img src=\"https://img.shields.io/static/v1?label=&message=themes&color=4ec5d4\" alt=\"Themes\"></a>\n<br>\n<a href=\"https://github.com/slidevjs/slidev/stargazers\" target=\"__blank\"><img alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/slidevjs/slidev?style=social\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://twitter.com/antfu7/status/1389604687502995457\">Video Preview</a> | <a href=\"https://sli.dev\">Documentation</a>\n</p>\n\n<div align=\"center\">\n<table>\n<tbody>\n<td align=\"center\">\n<img width=\"2000\" height=\"0\" alt=\"\" aria-hidden><br>\n<sub>Made possible by my <a href=\"https://github.com/sponsors/antfu\">Sponsor Program 💖</a></sub><br>\n<img width=\"2000\" height=\"0\" alt=\"\" aria-hidden>\n</td>\n</tbody>\n</table>\n</div>\n\n## Features\n\n- 📝 [**Markdown-based**](https://sli.dev/guide/syntax) - focus on content and use your favorite editor\n- 🧑‍💻 [**Developer Friendly**](https://sli.dev/guide/syntax#code-blocks) - built-in code highlighting, live coding, etc.\n- 🎨 [**Themable**](https://sli.dev/resources/theme-gallery) - theme can be shared and used with npm packages\n- 🌈 [**Stylish**](https://sli.dev/guide/syntax#embedded-styles) - on-demand utilities via [UnoCSS](https://github.com/unocss/unocss).\n- 🤹 [**Interactive**](https://sli.dev/custom/directory-structure#components) - embedding Vue components seamlessly\n- 🎙 [**Presenter Mode**](https://sli.dev/guide/ui#presenter-mode) - use another window, or even your phone to control your slides\n- 🎨 [**Drawing**](https://sli.dev/features/drawing) - draw and annotate on your slides\n- 🧮 [**LaTeX**](https://sli.dev/features/latex) - built-in LaTeX math equations support\n- 📰 [**Diagrams**](https://sli.dev/guide/syntax#diagrams) - creates diagrams using textual descriptions with [Mermaid](https://mermaid.js.org/)\n- 🌟 [**Icons**](https://sli.dev/features/icons) - access to icons from any icon set directly\n- 💻 [**Editor**](https://sli.dev/guide/index#editor) - integrated editor, or the [VSCode extension](https://sli.dev/features/vscode-extension)\n- 🎥 [**Recording**](https://sli.dev/features/recording) - built-in recording and camera view\n- 📤 [**Portable**](https://sli.dev/guide/exporting) - export into PDF, PNGs, or PPTX\n- ⚡️ [**Fast**](https://vitejs.dev) - instant reloading powered by [Vite](https://vitejs.dev)\n- 🛠 [**Hackable**](https://sli.dev/custom/) - using Vite plugins, Vue components, or any npm packages\n\n## Getting Started\n\n### Try it Online ⚡️\n\n[sli.dev/new](https://sli.dev/new)\n\n[![](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://sli.dev/new)\n\n### Init Project Locally\n\nInstall [Node.js >=18](https://nodejs.org/) and run the following command:\n\n```bash\nnpm init slidev\n```\n\nDocumentation:\n**[English](https://sli.dev)** | [中文文档](https://cn.sli.dev) | [Français](https://fr.sli.dev) | [Español](https://es.sli.dev) | [Русский](https://ru.sli.dev) | [Português-BR](https://br.sli.dev)\n\nDiscord: [chat.sli.dev](https://chat.sli.dev)\n\nFor a full example, you can check the [demo](https://github.com/slidevjs/slidev/blob/main/demo) folder, which is also the source file for [my previous talk](https://antfu.me/posts/composable-vue-vueday-2021).\n\n## Tech Stack\n\n- [Vite](https://vitejs.dev) - An extremely fast frontend tooling\n- [Vue 3](https://v3.vuejs.org/) powered [Markdown](https://daringfireball.net/projects/markdown/syntax) - Focus on the content while having the power of HTML and Vue components whenever needed\n- [UnoCSS](https://github.com/unocss/unocss) - On-demand utility-first CSS engine, style your slides at ease\n- [Shiki](https://github.com/shikijs/shiki), [Monaco Editor](https://github.com/Microsoft/monaco-editor) - First-class code snippets support with live coding capability\n- [RecordRTC](https://recordrtc.org) - Built-in recording and camera view\n- [VueUse](https://vueuse.org) family - [`@vueuse/core`](https://github.com/vueuse/vueuse), [`@vueuse/motion`](https://github.com/vueuse/motion), etc.\n- [Iconify](https://iconify.design/) - Icon sets collection.\n- [Drauu](https://github.com/antfu/drauu) - Drawing and annotations support\n- [KaTeX](https://katex.org/) - LaTeX math rendering.\n- [Mermaid](https://mermaid-js.github.io/mermaid) - Textual Diagrams.\n\n## Sponsors\n\nThis project is made possible by all the sponsors supporting my work:\n\n<p align=\"center\">\n  <a href=\"https://github.com/sponsors/antfu\">\n    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg' alt=\"Logos from Sponsors\" />\n  </a>\n</p>\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "cypress/e2e/examples/basic.spec.ts",
    "content": "export {}\ndeclare global {\n  // eslint-disable-next-line ts/no-namespace\n  namespace Cypress {\n    interface Chainable<Subject> {\n      rightArrow: (n?: number) => Chainable<Subject>\n    }\n  }\n}\n\nCypress.Commands.add('rightArrow', (n = 1) => {\n  cy.get('body').wait(500).type('{rightarrow}'.repeat(n)).wait(500)\n})\n\nconst BASE = 'http://localhost:3041'\n\ncontext('Basic', () => {\n  beforeEach(() => {\n    cy.visit('/')\n  })\n\n  function goPage(no: number) {\n    cy.get('body')\n      .wait(100)\n      .type('g')\n      .wait(100)\n      .get('#slidev-goto-input')\n      .type(`${no}`, { force: true })\n      .type('{enter}', { force: true })\n      .url()\n      .should('eq', `${BASE}/${no}`)\n      .wait(500)\n  }\n\n  it('basic nav', () => {\n    cy.url()\n      .should('eq', `${BASE}/1`)\n\n    cy.contains('Global Footer')\n      .should('exist')\n\n    cy.get('#page-root > #slide-container > #slide-content')\n\n    cy.rightArrow()\n      .url()\n      .should('eq', `${BASE}/2`)\n\n    cy.contains('Global Footer')\n      .should('not.exist')\n\n    cy.get('#page-root > #slide-container > #slide-content > #slideshow .slidev-page-2 > div > p')\n      .should('have.css', 'border-color', 'rgb(0, 128, 0)')\n      .should('not.have.css', 'color', 'rgb(128, 0, 0)')\n\n    goPage(5)\n\n    cy.get('#page-root > #slide-container > #slide-content > #slideshow .slidev-page-5 .slidev-code')\n      .should('have.text', '<div>{{$slidev.nav.currentPage}}</div>')\n      .get('#page-root > #slide-container > #slide-content > #slideshow .slidev-page-5 > div > p')\n      .should('have.text', 'Current Page: 5')\n  })\n\n  it('should nav correctly', () => {\n    goPage(5)\n\n    cy.get('body')\n      .type('{DownArrow}')\n      .url()\n      .should('eq', `${BASE}/6`)\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/6?clicks=1`)\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}{RightArrow}{RightArrow}{RightArrow}')\n      .url()\n      .should('eq', `${BASE}/7`)\n\n    cy.get('body')\n      .type('{LeftArrow}')\n      .url()\n      .should('eq', `${BASE}/6?clicks=6`)\n\n    cy.get('body')\n      .type('{DownArrow}')\n      .url()\n      .should('eq', `${BASE}/7`)\n\n    cy.get('body')\n      .type('{UpArrow}')\n      .url()\n      .should('eq', `${BASE}/6`)\n  })\n\n  it('named slots', () => {\n    goPage(8)\n\n    cy.get('.col-right')\n      .contains('Right')\n  })\n\n  it('clicks map', () => {\n    goPage(9)\n\n    cy\n      .url()\n      .should('eq', `${BASE}/9`)\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/9?clicks=1`)\n\n    cy.get('#slideshow .slidev-page-9 .cy-content .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'CD')\n\n    cy.rightArrow(2)\n\n    cy.get('#slideshow .slidev-page-9 .cy-content .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'ABCD')\n\n    // v-click.hide\n    cy.rightArrow()\n\n    cy.get('#slideshow .slidev-page-9 .cy-content .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'ABC')\n\n    cy\n      .url()\n      .should('eq', `${BASE}/9?clicks=4`)\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/10`)\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/10?clicks=1`)\n\n    cy.get('#slideshow .slidev-page-10 .cy-content-hide .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'BD')\n\n    cy.rightArrow()\n\n    cy.get('#slideshow .slidev-page-10 .cy-content-hide .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'D')\n\n    cy.rightArrow()\n\n    cy.get('#slideshow .slidev-page-10 .cy-content-hide .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'CD')\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/10?clicks=4`)\n\n    cy.rightArrow()\n\n    cy\n      .url()\n      .should('eq', `${BASE}/11`)\n  })\n\n  it('overview nav', () => {\n    goPage(2)\n\n    cy.get('body')\n      .type('o{RightArrow}{RightArrow}{Enter}')\n      .url()\n      .should('eq', `${BASE}/4`)\n\n    cy.get('body')\n      .type('o{LeftArrow}{LeftArrow}{LeftArrow}{Enter}')\n      .url()\n      .should('eq', `${BASE}/1`)\n\n    cy.get('body')\n      .type('o{DownArrow}{DownArrow}{DownArrow}{Enter}')\n      .url()\n      .should('not.eq', `${BASE}/1`)\n\n    cy.get('body')\n      .type('o{UpArrow}{UpArrow}{UpArrow}{Enter}')\n      .url()\n      .should('eq', `${BASE}/1`)\n  })\n\n  it('deep nested lists', () => {\n    goPage(11)\n\n    cy\n      .url()\n      .should('eq', `${BASE}/11`)\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}')\n\n    cy.get('#slideshow .slidev-page-11 .cy-depth .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'C')\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}')\n\n    cy.get('#slideshow .slidev-page-11 .cy-depth .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'CD')\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}')\n\n    cy.get('#slideshow .slidev-page-11 .cy-depth .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden) .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'CDGH')\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}')\n\n    cy.get('#slideshow .slidev-page-11 .cy-depth > ul > .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'A B CDEF GHIJKL')\n  })\n\n  it('slot in v-clicks', () => {\n    goPage(12)\n\n    cy\n      .url()\n      .should('eq', `${BASE}/12`)\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}{RightArrow}{RightArrow}{RightArrow}')\n      .url()\n      .should('eq', `${BASE}/12?clicks=6`) // we should still be on page 12\n\n    cy.rightArrow()\n      .url()\n      .should('eq', `${BASE}/13`)\n\n    cy.get('#slideshow .slidev-page-13 .cy-wrapdecorate > ul > .slidev-vclick-target.slidev-vclick-hidden')\n      .should('have.text', 'AEFZ')\n\n    cy.get('body')\n      .type('{RightArrow}{RightArrow}{RightArrow}')\n\n    cy.get('#slideshow .slidev-page-13 .cy-wrapdecorate > ul > .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'AEF')\n\n    cy.rightArrow()\n\n    cy.get('#slideshow .slidev-page-13 .cy-wrapdecorate > ul > .slidev-vclick-target:not(.slidev-vclick-hidden)')\n      .should('have.text', 'AEFZ')\n  })\n})\n"
  },
  {
    "path": "cypress/e2e/examples/presenter-resizer.spec.ts",
    "content": "const LAYOUT_KEY = 'slidev-presenter-layout'\n\nfunction visitPresenter(layout: 1 | 2 | 3) {\n  cy.visit('/presenter', {\n    onBeforeLoad(win) {\n      win.localStorage.setItem(LAYOUT_KEY, String(layout))\n    },\n  })\n}\n\ncontext('Presenter resizer', () => {\n  it('shows proper resizer handles per layout', () => {\n    // Start with a tall viewport so layout 1 is not in wide mode.\n    cy.viewport(900, 1200)\n    visitPresenter(1)\n\n    cy.get('.note .notes-resizer').should('exist')\n    cy.get('.note .notes-row-resizer').should('exist')\n    cy.get('.next .notes-row-resizer').should('not.exist')\n    cy.get('.notes-vertical-resizer').should('not.exist')\n\n    // Switch to wide mode: unified vertical resizer should exist.\n    cy.viewport(1400, 900)\n    cy.get('.notes-vertical-resizer').should('exist')\n    cy.get('.note .notes-resizer').should('not.exist')\n\n    visitPresenter(2)\n\n    cy.get('.note .notes-resizer').should('exist')\n    cy.get('.next .notes-row-resizer').should('exist')\n    cy.get('.note .notes-row-resizer').should('not.exist')\n    cy.get('.notes-vertical-resizer').should('not.exist')\n\n    visitPresenter(3)\n\n    cy.get('.note .notes-resizer').should('not.exist')\n    cy.get('.note .notes-row-resizer').should('exist')\n    cy.get('.next .notes-row-resizer').should('not.exist')\n    cy.get('.notes-vertical-resizer').should('not.exist')\n    cy.get('.notes-vertical-resizer-left').should('exist')\n  })\n\n  it('applies CSS variables for dynamic sizing', () => {\n    visitPresenter(1)\n\n    cy.get('.grid-container')\n      .invoke('attr', 'style')\n      .then((style) => {\n        expect(style).to.include('--slidev-presenter-notes-width')\n        expect(style).to.include('--slidev-presenter-notes-row-size')\n      })\n  })\n})\n"
  },
  {
    "path": "cypress/e2e/examples/smoke.spec.ts",
    "content": "context('Smoke test', () => {\n  async function testAllSlides() {\n    while (1) {\n      let oldUrl, newUrl\n      cy.get('body')\n        .url()\n        .then(url => (oldUrl = url))\n        .type(`{RightArrow}`)\n        .wait(1000)\n        .url()\n        .then(url => (newUrl = url))\n      if (oldUrl === newUrl)\n        break\n    }\n  }\n\n  it('should throw no error in Play mode', async () => {\n    cy.visit('/').wait(4000)\n    await testAllSlides()\n  })\n\n  it('should throw no error in Presenter mode', async () => {\n    cy.visit('/presenter').wait(4000)\n    await testAllSlides()\n  })\n\n  it('should throw no error in Overview page', async () => {\n    cy.visit('/overview').wait(4000)\n  })\n\n  it('should throw no error in Entry page', async () => {\n    cy.visit('/entry').wait(4000)\n  })\n})\n"
  },
  {
    "path": "cypress/fixtures/basic/components/DecorateWithLi.vue",
    "content": "<template>\n  <li>Step b</li>\n  <slot />\n  <li>Step y</li>\n</template>\n"
  },
  {
    "path": "cypress/fixtures/basic/components/WrapInClicks.vue",
    "content": "<template>\n  <div style=\"border: 1px solid blue\">\n    <v-clicks>\n      <slot />\n    </v-clicks>\n  </div>\n</template>\n"
  },
  {
    "path": "cypress/fixtures/basic/components/WrapInClicksDecorate.vue",
    "content": "<template>\n  <v-clicks>\n    <ul style=\"border: 1px solid red\">\n      <li>A</li>\n      <slot />\n      <li>Z</li>\n    </ul>\n  </v-clicks>\n</template>\n"
  },
  {
    "path": "cypress/fixtures/basic/components/WrapInComponentInClicks.vue",
    "content": "<template>\n  <v-clicks>\n    <ol style=\"border: 1px solid green\">\n      <li>Point a</li>\n      <decorate-with-li>\n        <slot />\n      </decorate-with-li>\n      <li>Point z</li>\n    </ol>\n  </v-clicks>\n</template>\n"
  },
  {
    "path": "cypress/fixtures/basic/global.vue",
    "content": "<template>\n  <div\n    v-if=\"$slidev.nav.currentPage % 2 === 1\"\n    class=\"absolute bottom-0 right-0 m-10 hover:text-red-400\"\n  >\n    Global Footer (appear only on odd page)\n  </div>\n</template>\n"
  },
  {
    "path": "cypress/fixtures/basic/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"nodemon -w '../../../packages/slidev/dist/*.js' --exec 'slidev --log=info --port=3041'\",\n    \"build\": \"slidev build\",\n    \"export\": \"slidev export\"\n  },\n  \"devDependencies\": {\n    \"@slidev/cli\": \"workspace:*\",\n    \"@slidev/theme-default\": \"catalog:themes\",\n    \"@slidev/theme-seriph\": \"catalog:themes\",\n    \"@slidev/types\": \"workspace:*\",\n    \"nodemon\": \"catalog:dev\"\n  }\n}\n"
  },
  {
    "path": "cypress/fixtures/basic/slides.md",
    "content": "# Page 1\n\nHello World\n\n---\n\n# Page 2\n\n```html\n<style>\np {\n  color: red;\n}\n</style>\n```\n\n`<p>` should have a green border, but no red text\n\n<style>\np {\n  border: 1px solid green;\n}\n</style>\n\n---\nsrc: sub/page1.md\n---\n\nThis will be ignored\n\n---\nsrc: sub/page2.md\nbackground: https://sli.dev/demo-cover.png\n---\n\n---\n\n# Page 5\n\n```html\n<div>{{$slidev.nav.currentPage}}</div>\n```\n\nCurrent Page: {{$slidev.nav.currentPage}}\n\n---\n\n# Page 6\n\n<v-clicks>\n\n- A\n- B\n- C\n\n</v-clicks>\n\n<v-clicks>\n\n1. C\n2. B\n3. A\n\n</v-clicks>\n\n---\n\n# Page 7\n\n$$\n\\begin{aligned}\n\\frac{D \\boldsymbol{v}}{D t}=&-\\frac{1}{\\rho} \\operatorname{grad} p+\\frac{\\mu}{\\rho} \\Delta \\boldsymbol{v}+\\frac{\\lambda+\\mu}{\\rho} \\operatorname{grad} \\Theta+\\frac{\\Theta}{\\rho} \\operatorname{grad}(\\lambda+\\mu) \\\\\n&+\\frac{1}{\\rho} \\operatorname{grad}(\\boldsymbol{v} \\cdot \\operatorname{grad} \\mu)+\\frac{1}{\\rho} \\operatorname{rot}(\\boldsymbol{v} \\times \\operatorname{grad} \\mu)-\\frac{1}{\\rho} \\boldsymbol{v} \\Delta \\mu+\\boldsymbol{g}\n\\end{aligned}\n$$\n\n---\nlayout: two-cols\n---\n\n::right::\n\n# Right\n\n<b>Right</b>\n\n:: default ::\n\n# Left\n\nLeft\n\n---\n\n# Page 9\n\n<div class=\"cy-content\">\n  <div v-click=\"3\">A</div>\n  <div v-click=\"2\">B</div>\n  <div v-click=\"1\">C</div>\n  <div v-click.hide=\"4\">D</div>\n  <v-click hide><div>E</div></v-click>\n</div>\n\n---\n\n# Page 10\n\n<div class=\"cy-content-hide\">\n  <div v-click-hide>A</div>\n  <div v-click-hide>B</div>\n  <div v-click>C</div>\n  <div v-click-hide>D</div>\n</div>\n\n---\n\n# Page 11\n\n<div class=\"cy-depth\">\n<v-clicks depth=\"3\">\n\n- A\n  - B\n    - C\n    - D\n  - E\n  - F\n    - G\n    - H\n- I\n\n</v-clicks>\n\n<v-clicks>\n\n- J\n- K\n- L\n\n</v-clicks>\n</div>\n\n---\n\n# Page 12\n\n<v-clicks>\n  <ul><li>A</li><li>B</li></ul>\n</v-clicks>\n\n<wrap-in-clicks>\n  <ul><li>A</li><li>B</li></ul>\n</wrap-in-clicks>\n\n<wrap-in-clicks>\n\n- A\n- B\n\n</wrap-in-clicks>\n\n---\n\n# Page 13\n\n<div class=\"cy-wrapdecorate\">\n<wrap-in-clicks-decorate>\n  <li>E</li>\n  <li>F</li>\n</wrap-in-clicks-decorate>\n\n(the next is kept for a future patch but not animating the nesting)\n\n<wrap-in-component-in-clicks>\n  <li>step i</li>\n  <li>step j</li>\n</wrap-in-component-in-clicks>\n</div>\n"
  },
  {
    "path": "cypress/fixtures/basic/sub/page1.md",
    "content": "# Sub page 1\n\n$x+2$\n"
  },
  {
    "path": "cypress/fixtures/basic/sub/page2.md",
    "content": "---\nlayout: cover\n---\n\n# Sub page 2\n\n<Tweet :id=\"20\"/>\n"
  },
  {
    "path": "cypress/fixtures/basic/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\n\nexport default defineConfig({\n  build: {\n    manifest: true,\n    minify: false,\n  },\n})\n"
  },
  {
    "path": "cypress/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"cypress\"],\n    \"noEmit\": true\n  },\n  \"include\": [\n    \"../node_modules/cypress\",\n    \"./**/*.ts\"\n  ]\n}\n"
  },
  {
    "path": "cypress.config.ts",
    "content": "import { defineConfig } from 'cypress'\n\nexport default defineConfig({\n  e2e: {\n    baseUrl: 'http://localhost:3041',\n    chromeWebSecurity: false,\n    specPattern: 'cypress/e2e/**/*.spec.*',\n    supportFile: false,\n  },\n})\n"
  },
  {
    "path": "demo/README.md",
    "content": "The best way of understanding Slidev is to try it, with the following command:\n\n```bash\nnpm init slidev\n```\n\nLearn more: https://sli.dev\n"
  },
  {
    "path": "demo/composable-vue/components/Connections.vue",
    "content": "<script setup lang=\"ts\">\nimport { autoResetRef } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\n\nconst a = ref(2)\nconst b = ref(4)\n\nconst a2 = computed(() => a.value ** 2)\nconst b2 = computed(() => b.value ** 2)\n\nconst c = computed(() => a2.value + b2.value)\n\nconst a2c = autoResetRef(false, 100)\nconst b2c = autoResetRef(false, 100)\nconst cc = autoResetRef(false, 100)\n\nwatch(a, () => a2c.value = true)\nwatch(b, () => b2c.value = true)\nwatch([a2, b2], () => cc.value = true)\n</script>\n\n<template>\n  <div>\n    <div class=\"px-6 pt-4 text-xl\">\n      <b>𝒛=𝒙²+𝒚²</b>={{ a }}x{{ a }}+{{ b }}x{{ b }}={{ c }}\n    </div>\n    <div class=\"relative w-100 h-100\">\n      <NumBox\n        v-model:value=\"a\"\n        label=\"𝒙\"\n        class=\"absolute left-5 top-5 from-green-400 to-cyan-500\"\n        :controls=\"true\"\n      />\n      <NumBox\n        v-model:value=\"b\"\n        label=\"𝒚\"\n        class=\"absolute left-5 top-30 from-green-400 to-cyan-500\"\n        :controls=\"true\"\n      />\n      <NumBox\n        :value=\"a2\"\n        label=\"𝒙²\"\n        class=\"absolute left-35 top-5 from-blue-400 to-purple-400\"\n        :active=\"a2c\"\n      />\n      <NumBox\n        :value=\"b2\"\n        label=\"𝒚²\"\n        class=\"absolute left-35 top-30 from-blue-400 to-purple-400\"\n        :active=\"b2c\"\n      />\n      <NumBox\n        :value=\"c\"\n        label=\"𝒛\"\n        class=\"absolute left-65 top-17.5 from-blue-400 to-purple-400\"\n        :active=\"cc\"\n      />\n      <!-- Line1 -->\n      <svg\n        class=\"absolute left-20 top-10 -z-1\"\n        width=\"62\"\n        height=\"1\"\n        viewBox=\"0 0 62 1\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <line\n          x1=\"-3.75678e-10\"\n          y1=\"0.5\"\n          x2=\"62\"\n          y2=\"0.5\"\n          stroke=\"#888888\"\n          stroke-dasharray=\"5 2\"\n        />\n      </svg>\n      <!-- Line2 -->\n      <svg\n        class=\"absolute left-20 top-40 -z-1\"\n        width=\"62\"\n        height=\"1\"\n        viewBox=\"0 0 62 1\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <line\n          x1=\"-3.75678e-10\"\n          y1=\"0.5\"\n          x2=\"62\"\n          y2=\"0.5\"\n          stroke=\"#888888\"\n          stroke-dasharray=\"5 2\"\n        />\n      </svg>\n      <!-- Arc1 -->\n      <svg\n        class=\"absolute left-50 top-10 -z-1\"\n        width=\"62\"\n        height=\"61\"\n        viewBox=\"0 0 62 61\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path d=\"M0 0.5C27.5 0.5 39.5 59 61.5 60\" stroke=\"#888888\" stroke-dasharray=\"5 2\" />\n      </svg>\n      <!-- Arc2 -->\n      <svg\n        class=\"absolute left-50 top-25 -z-1\"\n        width=\"62\"\n        height=\"61\"\n        viewBox=\"0 0 62 61\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path d=\"M0 60.5C27.5 60.5 39.5 2 61.5 1\" stroke=\"#888888\" stroke-dasharray=\"5 2\" />\n      </svg>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/DarkToggle.vue",
    "content": "<script setup lang=\"ts\">\nimport { useDarkMode } from '@slidev/client'\n\nconst { isDark, toggleDark } = useDarkMode()\n</script>\n\n<template>\n  <button\n    class=\"bg-primary rounded border-b-2 border-green-900 text-sm px-2 pt-1.5 pb-1 inline-block !outline-none hover:bg-opacity-85\"\n    @click=\"toggleDark()\"\n  >\n    <div class=\"flex\">\n      <div v-if=\"isDark\" class=\"i-carbon:moon\" />\n      <div v-else class=\"i-carbon:sun\" />\n      <span class=\"mr-1 ml-2\">{{ isDark ? 'Dark' : 'Light' }}</span>\n    </div>\n  </button>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/Marker.vue",
    "content": "<template>\n  <span class=\"bg-$slidev-code-background px-2 py-1 rounded font-mono inline-block text-0.4em line-height-1em transform translate-y-[-1.5em] translate-x-[-0.3em]\"><slot /></span>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/MarkerCore.vue",
    "content": "<template>\n  <Marker class=\"text-green-500\">\n    Core\n  </Marker>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/MarkerPattern.vue",
    "content": "<template>\n  <Marker class=\"text-pink-500\">\n    Pattern\n  </Marker>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/MarkerTips.vue",
    "content": "<template>\n  <Marker class=\"text-orange-400\">\n    Tips\n  </Marker>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/NumBox.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\n\nconst props = defineProps<{\n  value: number\n  label?: string\n  active?: boolean\n  controls?: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: any): void\n}>()\nconst num = useVModel(props, 'value', emit)\n</script>\n\n<template>\n  <div\n    class=\"w-16 h-16 rounded-xl text-white overflow-hidden\"\n    style=\"background-image: radial-gradient(farthest-corner at 0 0, var(--un-gradient-from) 30%, var(--un-gradient-to))\"\n  >\n    <div\n      style=\"background-image: radial-gradient(farthest-corner at 0 0, var(--un-gradient-from) 30%, var(--un-gradient-to))\"\n      class=\"absolute w-full h-full from-orange-400 to-red-400 opacity-0 transition duration-400 ease-in pointer-events-none\"\n      :class=\"active ? '!opacity-100 !duration-10' : ''\"\n    />\n    <slot>\n      <div class=\"absolute left-0 top-0 px-2 py-0.5 opacity-40\">\n        {{ label }}\n      </div>\n      <div class=\"absolute w-full h-full flex z-10\">\n        <div class=\"m-auto text-2xl leading-0.8em\" :class=\"controls ? 'pl-2' : ''\">\n          {{ value }}\n        </div>\n        <div v-if=\"controls\" class=\"grid grid-rows-2 text-xl\">\n          <mdi:menu-up-outline\n            class=\"mt-auto opacity-50 hover:opacity-100 pr-1 cursor-pointer\"\n            @click=\"num += 1\"\n          />\n          <mdi:menu-down-outline\n            class=\"opacity-50 hover:opacity-100 pr-1 cursor-pointer\"\n            @click=\"num -= 1\"\n          />\n        </div>\n      </div>\n    </slot>\n  </div>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/components/VueUse.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  name: string\n}>()\n</script>\n\n<template>\n  <div class=\"px-2 -mx-2 mt-4 py-2\">\n    <img src=\"https://vueuse.org/favicon.svg\" class=\"h-1em -mb-0.5 mr-2 inline-block align-baseline\" alt=\"\">\n    <span class=\"opacity-50\">Available in VueUse: </span><a class=\"font-mono opacity-75 hover:opacity-100\" :href=\"`https://vueuse.org/${name}`\">{{ name }}</a>\n  </div>\n</template>\n"
  },
  {
    "path": "demo/composable-vue/index.html",
    "content": "<head>\n  <link rel=\"icon\" type=\"image/svg\" href=\"https://antfu.me/favicon.svg\" />\n</head>\n"
  },
  {
    "path": "demo/composable-vue/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"nodemon -w '../../packages/slidev/dist/*.mjs' --exec 'slidev --log=info'\",\n    \"build\": \"slidev build\",\n    \"export\": \"slidev export\",\n    \"export:clicks\": \"slidev export --with-clicks\"\n  },\n  \"devDependencies\": {\n    \"@iconify-json/mdi\": \"catalog:icons\",\n    \"@iconify-json/ri\": \"catalog:icons\",\n    \"@slidev/cli\": \"workspace:*\",\n    \"@slidev/theme-default\": \"catalog:themes\",\n    \"@slidev/theme-seriph\": \"catalog:themes\",\n    \"@slidev/types\": \"workspace:*\",\n    \"nodemon\": \"catalog:dev\"\n  }\n}\n"
  },
  {
    "path": "demo/composable-vue/setup/monaco.ts",
    "content": "import { defineMonacoSetup } from '@slidev/types'\n\nexport default defineMonacoSetup((monaco) => {\n  monaco.languages.typescript.typescriptDefaults.addExtraLib(\n    `\n    import { InjectionKey } from 'vue'\n    export interface UserInfo { id: number; name: string }\n    export const injectKeyUser: InjectionKey<UserInfo> = Symbol()\n    `,\n    'file:///root/context.ts',\n  )\n})\n"
  },
  {
    "path": "demo/composable-vue/slides.md",
    "content": "---\nlayout: cover\ndownload: 'https://antfu.me/talks/2021-04-29'\nhighlighter: shiki\nmonaco: true\ninfo: |\n  ## Composable Vue\n\n  Patterns and tips for writing good composable logic in Vue\n\n  [Anthony Fu](https://antfu.me/) at [VueDay 2021](https://2021.vueday.it/)\n\n  - [Recording](https://www.youtube.com/watch?v=IMJjP6edHd0)\n  - [Transcript](https://antfu.me/posts/composable-vue-vueday-2021)\n  - [Source code](https://github.com/antfu/talks/tree/master/2021-04-29)\n---\n\n# Composable Vue\n\nPatterns and tips for writing good composable logic in Vue\n\n<div class=\"uppercase text-sm tracking-widest\">\nAnthony Fu\n</div>\n\n<div class=\"abs-bl mx-14 my-12 flex\">\n  <img src=\"https://2020.vueday.it/img/themes/vueday/vueday-logo.png\" class=\"h-8\" alt=\"\">\n  <div class=\"ml-3 flex flex-col text-left\">\n    <div><b>Vue</b>Day</div>\n    <div class=\"text-sm opacity-50\">Apr. 29th, 2021</div>\n  </div>\n</div>\n\n---\nlayout: 'intro'\n---\n\n# Anthony Fu\n\n<div class=\"leading-8 opacity-80\">\nVue core team member and Vite team member.<br>\nCreator of VueUse, i18n Ally and Type Challenges.<br>\nA fanatical full-time open sourceror.<br>\n</div>\n\n<div class=\"my-10 grid grid-cols-[40px_1fr] w-min gap-y-4\">\n  <ri-github-line class=\"opacity-50\"/>\n  <div><a href=\"https://github.com/antfu\" target=\"_blank\">antfu</a></div>\n  <ri-twitter-line class=\"opacity-50\"/>\n  <div><a href=\"https://twitter.com/antfu7\" target=\"_blank\">antfu7</a></div>\n  <ri-user-3-line class=\"opacity-50\"/>\n  <div><a href=\"https://antfu.me\" target=\"_blank\">antfu.me</a></div>\n</div>\n\n<img src=\"https://antfu.me/avatar.png\" class=\"rounded-full w-40 abs-tr mt-16 mr-12\" alt=\"A comic art image of Anthony Fu\"/>\n\n---\nname: Sponsors\nlayout: center\n---\n\n<img class=\"h-100 -mt-10\" src=\"https://cdn.jsdelivr.net/gh/antfu/static/sponsors.png\" alt=\"A list of the sponsor logos\" /><br>\n<div class=\"text-center text-xs opacity-50 -mt-8 hover:opacity-100\">\n  <a href=\"https://github.com/sponsors/antfu\" target=\"_blank\">\n    Sponsor me at GitHub\n  </a>\n</div>\n\n---\nlayout: center\n---\n\n# Composable Vue\n\n---\nname: VueUse\nlayout: center\n---\n\n<div class=\"grid grid-cols-[3fr_2fr] gap-4\">\n  <div class=\"text-center pb-4\">\n    <img class=\"h-50 inline-block\" src=\"https://vueuse.org/favicon.svg\" alt=\"\">\n    <div class=\"opacity-50 mb-2 text-sm\">\n      Collection of essential Vue Composition Utilities\n    </div>\n    <div class=\"text-center\">\n      <a class=\"!border-none\" href=\"https://www.npmjs.com/package/@vueuse/core\" target=\"__blank\"><img class=\"h-4 inline mx-0.5\" src=\"https://img.shields.io/npm/v/@vueuse/core?color=a1b858&label=\" alt=\"NPM version\"></a>\n      <a class=\"!border-none\" href=\"https://www.npmjs.com/package/@vueuse/core\" target=\"__blank\"><img class=\"h-4 inline mx-0.5\" alt=\"NPM Downloads\" src=\"https://img.shields.io/npm/dm/@vueuse/core?color=50a36f&label=\"></a>\n      <a class=\"!border-none\" href=\"https://vueuse.org\" target=\"__blank\"><img class=\"h-4 inline mx-0.5\" src=\"https://img.shields.io/static/v1?label=&message=docs%20%26%20demos&color=1e8a7a\" alt=\"Docs & Demos\"></a>\n      <img class=\"h-4 inline mx-0.5\" alt=\"Function Count\" src=\"https://img.shields.io/badge/-114%20functions-13708a\">\n      <br>\n      <a class=\"!border-none\" href=\"https://github.com/vueuse/vueuse\" target=\"__blank\"><img class=\"mt-2 h-4 inline mx-0.5\" alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/vueuse/vueuse?style=social\"></a>\n    </div>\n  </div>\n  <div class=\"border-l border-main !all:leading-12 !all:list-none my-auto\">\n\n  - Works for both Vue 2 and 3\n  - Tree-shakeable ESM\n  - CDN compatible\n  - TypeScript\n  - Rich ecosystems\n\n  </div>\n</div>\n\n---\nlayout: center\nclass: text-center\n---\n\n# Composition API\n\na brief go-through\n\n---\n\n<div class=\"grid grid-cols-2 gap-x-4\"><div>\n\n# Ref\n\n```ts {monaco}\nimport { ref } from 'vue'\n\nlet foo = 0\nlet bar = ref(0)\n\nfoo = 1\nbar = 1 // ts-error\n```\n\n<div v-click>\n\n### Pros\n\n- More explicit, with type checking\n- Less caveats\n\n### Cons\n\n- `.value`\n\n</div>\n\n</div><div>\n\n# Reactive\n\n```ts {monaco}\nimport { reactive } from 'vue'\n\nconst foo = { prop: 0 }\nconst bar = reactive({ prop: 0 })\n\nfoo.prop = 1\nbar.prop = 1\n```\n\n<div v-click>\n\n### Pros\n\n- Auto unwrapping (a.k.a `.value` free)\n\n### Cons\n\n- Same as plain objects on types\n- Destructure loses reactivity\n- Need to use callback for `watch`\n\n</div>\n</div></div>\n\n---\n\n# Ref Auto Unwrapping <MarkerCore />\n\nGet rid of `.value` for most of the time.\n\n<div class=\"grid grid-cols-2 gap-x-4\">\n\n<v-clicks :every='2'>\n\n- `watch` accepts ref as the watch target, and returns the unwrapped value in the callback\n\n```ts\nconst counter = ref(0)\n\nwatch(counter, (count) => {\n  console.log(count) // same as `counter.value`\n})\n```\n\n- Ref is auto unwrapped in the template\n\n```html\n<template>\n  <button @click=\"counter += 1\">\n    Counter is {{ counter }}\n  </button>\n</template>\n```\n\n- Reactive will auto-unwrap nested refs.\n\n<div>\n\n```ts {monaco}\nimport { reactive, ref } from 'vue'\n\nconst foo = ref('bar')\nconst data = reactive({ foo, id: 10 })\ndata.foo // 'bar'\n```\n\n</div>\n\n</v-clicks>\n\n</div>\n\n---\n\n# `unref` - Opposite of Ref <MarkerCore />\n\n- If it gets a Ref, returns the value of it.\n- Otherwise, returns as-is.\n\n<div class=\"grid grid-cols-2 gap-x-4 mt-4\">\n\n<div v-click>\n\n### Implementation\n\n```ts\nfunction unref<T>(r: Ref<T> | T): T {\n  return isRef(r) ? r.value : r\n}\n```\n\n</div><div v-click>\n\n### Usage\n\n```ts {monaco}\nimport { ref, unref } from 'vue'\n\nconst foo = ref('foo')\nunref(foo) // 'foo'\n\nconst bar = 'bar'\nunref(bar) // 'bar'\n```\n\n</div></div>\n\n---\nlayout: center\nclass: text-center\n---\n\n# Patterns & Tips\n\nof writing composable functions\n\n---\n\n# What's Composable Functions\n\nSets of reusable logic, separation of concerns.\n\n<div v-click class=\"grid grid-cols-[1fr_130px]\">\n\n```ts\nexport function useDark(options: UseDarkOptions = {}) {\n  const preferredDark = usePreferredDark() // <--\n  const store = useLocalStorage('vueuse-dark', 'auto') // <--\n\n  return computed<boolean>({\n    get() {\n      return store.value === 'auto'\n        ? preferredDark.value\n        : store.value === 'dark'\n    },\n    set(v) {\n      store.value = v === preferredDark.value\n        ? 'auto'\n        : v ? 'dark' : 'light'\n    },\n  })\n}\n```\n\n<div class=\"grid\">\n<DarkToggle class=\"m-auto\"/>\n</div>\n\n</div>\n\n<div v-click class=\"abs-b mx-14 my-12\">\n<VueUse name=\"useDark\"/>\n</div>\n\n---\n\n# Think as \"Connections\"\n\nThe `setup()` only runs **once** on component initialization, to construct the relations between your state and logic.\n\n- Input → Output<sup class=\"ml-1 opacity-50\">Effects</sup>\n- Output reflects to input's changes automatically\n\n<div class=\"grid grid-cols-[auto_1fr] gap-4\">\n  <Connections v-click class=\"mt-4\"/>\n  <div v-click class=\"p-4\">\n    <h3 class=\"pb-2\">SpreadSheet Formula</h3>\n    <img class=\"h-40\" src=\"https://cdn.wallstreetmojo.com/wp-content/uploads/2019/01/Division-Formula-in-Excel-Example-1-1.png\" alt=\"\">\n  </div>\n</div>\n\n---\n\n# One Thing at a Time\n\nJust the same as authoring JavaScript functions.\n\n- Extract duplicated logics into composable functions\n- Have meaningful names\n- Consistent naming conversions - `useXX` `createXX` `onXX`\n- Keep function small and simple\n- \"Do one thing, and do it well\"\n\n---\n\n# Passing Refs as Arguments <MarkerPattern />\n\n<div class=\"grid grid-cols-[160px_1fr_180px] gap-x-4\">\n\n<div />\n\n### Implementation\n\n### Usage\n\n<v-clicks :every='3'>\n\n<div class=\"my-auto leading-6 text-base opacity-75\">\nPlain function\n</div>\n\n```ts\nfunction add(a: number, b: number) {\n  return a + b\n}\n```\n\n```ts\nconst a = 1\nconst b = 2\n\nconst c = add(a, b) // 3\n```\n\n<div class=\"my-auto leading-6 text-base opacity-75\">\nAccepts refs,<br>\nreturns a reactive result.\n</div>\n\n```ts\nfunction add(a: Ref<number>, b: Ref<number>) {\n  return computed(() => a.value + b.value)\n}\n```\n\n```ts\nconst a = ref(1)\nconst b = ref(2)\n\nconst c = add(a, b)\nc.value // 3\n```\n\n<div class=\"my-auto leading-6 text-base opacity-75\">\nAccepts both refs and plain values.\n</div>\n\n```ts\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number,\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n\n```ts\nconst a = ref(1)\n\nconst c = add(a, 5)\nc.value // 6\n```\n\n</v-clicks>\n\n</div>\n\n---\n\n# MaybeRef <MarkerTips/>\n\nA custom type helper\n\n```ts\ntype MaybeRef<T> = Ref<T> | T\n```\n\n<v-click>\n\nIn VueUse, we use this helper heavily to support optional reactive arguments\n\n```ts\nexport function useTimeAgo(\n  time: Date | number | string | Ref<Date | number | string>,\n) {\n  return computed(() => someFormating(unref(time)))\n}\n```\n\n```ts {monaco}\nimport type { Ref } from 'vue'\nimport { computed, unref } from 'vue'\n\ntype MaybeRef<T> = Ref<T> | T\n\nexport function useTimeAgo(\n  time: MaybeRef<Date | number | string>,\n) {\n  return computed(() => someFormating(unref(time)))\n}\n```\n\n</v-click>\n\n---\n\n# Make it Flexible <MarkerPattern />\n\nMake your functions like LEGO, can be used with different components in different ways.\n\n<div class=\"grid grid-cols-2 gap-x-4\">\n\n<div v-click>\n\n### Create a \"Special\" Ref\n\n```ts {monaco}\nimport { useTitle } from '@vueuse/core'\n\nconst title = useTitle()\n\ntitle.value = 'Hello World'\n// now the page's title changed\n```\n\n</div><div v-click>\n\n### Binding an Existing Ref\n\n```ts {monaco}\nimport { useTitle } from '@vueuse/core'\nimport { computed, ref } from 'vue'\n\nconst name = ref('Hello')\nconst title = computed(() => {\n  return `${name.value} - World`\n})\n\nuseTitle(title) // Hello - World\n\nname.value = 'Hi' // Hi - World\n```\n\n</div></div>\n\n<div v-click class=\"abs-b mx-14 my-12\">\n<VueUse name=\"useTitle\"/>\n</div>\n\n---\n\n# `useTitle` <Marker class=\"text-blue-400\">Case</Marker>\n\nTake a look at `useTitle`'s implementation\n\n<div class=\"grid grid-cols-2 gap-4\">\n<v-clicks>\n\n```ts {monaco}\nimport type { MaybeRef } from '@vueuse/core'\nimport { ref, watch } from 'vue'\n\nexport function useTitle(\n  newTitle: MaybeRef<string | null | undefined>,\n) {\n  const title = ref(newTitle || document.title)\n\n  watch(title, (t) => {\n    if (t != null)\n      document.title = t\n  }, { immediate: true })\n\n  return title\n}\n```\n\n```html\n\n<-- 1. use the user provided ref or create a new one\n\n<-- 2. sync ref changes to the document title\n\n```\n\n</v-clicks>\n</div>\n\n---\n\n# \"Reuse\" Ref <MarkerCore />\n\n<v-clicks>\n\nIf you pass a `ref` into `ref()`, it will return the original ref as-is.\n\n```ts\nconst foo = ref(1) // Ref<1>\nconst bar = ref(foo) // Ref<1>\n\nfoo === bar // true\n```\n\n```ts\nfunction useFoo(foo: Ref<string> | string) {\n  // no need!\n  const bar = isRef(foo) ? foo : ref(foo)\n\n  // they are the same\n  const bar = ref(foo)\n\n  /* ... */\n}\n```\n\nExtremely useful in composable functions that take uncertain argument types.\n\n</v-clicks>\n\n---\n\n# `ref` / `unref` <MarkerTips />\n\n<div v-click>\n\n- `MaybeRef<T>` works well with `ref` and `unref`.\n- Use `ref()` when you want to normalized it as a Ref.\n- Use `unref()` when you want to have the value.\n\n<br>\n\n```ts\ntype MaybeRef<T> = Ref<T> | T\n\nfunction useBala<T>(arg: MaybeRef<T>) {\n  const reference = ref(arg) // get the ref\n  const value = unref(arg) // get the value\n}\n```\n\n</div>\n\n---\n\n# Object of Refs <MarkerPattern />\n\nGetting benefits from both `ref` and `reactive` for authoring composable functions\n\n<div class=\"mt-1\" />\n<div class=\"grid grid-cols-2 gap-x-4\">\n<v-clicks>\n\n```ts {monaco}\nimport { reactive, ref } from 'vue'\n\nfunction useMouse() {\n  return {\n    x: ref(0),\n    y: ref(0),\n  }\n}\n\nconst { x, y } = useMouse()\nconst mouse = reactive(useMouse())\n\nmouse.x === x.value // true\n```\n\n<div class=\"px-2 py-4\">\n\n- Destructurable as Ref\n- Convert to reactive object to get the auto-unwrapping when needed\n\n</div>\n\n</v-clicks>\n</div>\n\n---\n\n# Async to \"Sync\" <MarkerTips />\n\nWith Composition API, we can actually turn async data into \"sync\"\n\n<div v-click>\n\n### Async\n\n```ts\nconst data = await fetch('https://api.github.com/').then(r => r.json())\n\n// use data\n```\n\n</div>\n<div v-click>\n\n### Composition API\n\n```ts\nconst { data } = useFetch('https://api.github.com/').json()\n\nconst user_url = computed(() => data.value?.user_url)\n```\n\n</div>\n<div v-click>\n\nEstablish the \"Connections\" first, then wait for data to be filled up. The idea is similar to SWR (stale-while-revalidate)\n\n</div>\n\n---\n\n# `useFetch` <Marker class=\"text-blue-400\">Case</Marker>\n\n<v-click>\n\n```ts\nexport function useFetch<R>(url: MaybeRef<string>) {\n  const data = shallowRef<T | undefined>()\n  const error = shallowRef<Error | undefined>()\n\n  fetch(unref(url))\n    .then(r => r.json())\n    .then(r => data.value = r)\n    .catch(e => error.value = e)\n\n  return {\n    data,\n    error,\n  }\n}\n```\n\n</v-click>\n\n<div v-click class=\"abs-b mx-14 my-12\">\n<VueUse name=\"useFetch\"/>\n</div>\n\n---\n\n# Side-effects Self Cleanup <MarkerPattern />\n\nThe `watch` and `computed` will stop themselves on components unmounted.<br>\nWe'd recommend following the same pattern for your custom composable functions.\n\n<div v-click>\n\n```ts {monaco}\nimport { onUnmounted } from 'vue'\n\nexport function useEventListener(target: EventTarget, name: string, fn: any) {\n  target.addEventListener(name, fn)\n\n  onUnmounted(() => {\n    target.removeEventListener(name, fn) // <--\n  })\n}\n```\n\n</div>\n\n<div v-click class=\"abs-b mx-14 my-12\">\n<VueUse name=\"useEventListener\"/>\n</div>\n\n<!--\nLower the mental burden\n-->\n\n---\n\n# `effectScope` RFC <Marker class=\"text-purple-400\">Upcoming</Marker>\n\nA new API to collect the side effects automatically. Likely to be shipped with Vue 3.1<br>\nhttps://github.com/vuejs/rfcs/pull/212\n\n```ts\n// effect, computed, watch, watchEffect created inside the scope will be collected\n\nconst scope = effectScope(() => {\n  const doubled = computed(() => counter.value * 2)\n\n  watch(doubled, () => console.log(double.value))\n\n  watchEffect(() => console.log('Count: ', double.value))\n})\n\n// dispose all effects in the scope\nstop(scope)\n```\n\n---\ndisabled: true\n---\n\n# Template Ref <MarkerTips />\n\nTo get DOM element, you can pass a ref to it, and it will be available after component mounted\n\n<div v-click>\n\n```ts {monaco}\nimport { defineComponent, onMounted, ref } from 'vue'\n\nexport default defineComponent({\n  setup() {\n    const element = ref<HTMLElement | undefined>()\n\n    onMounted(() => {\n      element.value // now you have it\n    })\n\n    return { element }\n  },\n})\n```\n\n```html {monaco}\n<template>\n  <div ref=\"element\"><!-- ... --></div>\n</template>\n```\n\n</div>\n\n---\ndisabled: true\n---\n\n# Template Ref <MarkerTips />\n\nUse `watch` instead of `onMounted` to unify the handling for template ref changes.\n\n<div>\n<v-click>\n\n```ts {monaco}\nimport { defineComponent, ref, watch } from 'vue'\n\nexport default defineComponent({\n  setup() {\n    const element = ref<HTMLElement | undefined>()\n\n    watch(element, (el) => {\n      // clean up previous side effect\n      if (el) {\n        // use the DOM element\n      }\n    })\n\n    return { element }\n  },\n})\n```\n\n</v-click>\n</div>\n\n---\n\n# Typed Provide / Inject <MarkerCore/>\n\nUse the `InjectionKey<T>` helper from Vue to share types across context.\n\n<div v-click>\n\n```ts {monaco}\n// context.ts\nimport type { InjectionKey } from 'vue'\n\nexport interface UserInfo {\n  id: number\n  name: string\n}\n\nexport const injectKeyUser: InjectionKey<UserInfo> = Symbol('user')\n```\n\n</div>\n\n---\n\n# Typed Provide / Inject <MarkerCore/>\n\nImport the key from the same module for `provide` and `inject`.\n\n<div class=\"grid grid-cols-2 gap-4\">\n<v-clicks>\n\n```ts {monaco}\n// parent.vue\nimport { provide } from 'vue'\nimport { injectKeyUser } from './context'\n\nexport default {\n  setup() {\n    provide(injectKeyUser, {\n      id: '7', // type error: should be number\n      name: 'Anthony',\n    })\n  },\n}\n```\n\n```ts {monaco}\n// child.vue\nimport { inject } from 'vue'\nimport { injectKeyUser } from './context'\n\nexport default {\n  setup() {\n    const user = inject(injectKeyUser)\n    // UserInfo | undefined\n\n    if (user)\n      console.log(user.name) // Anthony\n  },\n}\n```\n\n</v-clicks>\n</div>\n\n---\n\n# Shared State <MarkerPattern />\n\nBy the nature of Composition API, states can be created and used independently.\n\n<div class=\"grid grid-cols-2 gap-4\">\n\n<v-click>\n\n```ts\n// shared.ts\nimport { reactive } from 'vue'\n\nexport const state = reactive({\n  foo: 1,\n  bar: 'Hello',\n})\n```\n\n</v-click>\n\n<div>\n<v-clicks>\n\n```ts\n// A.vue\nimport { state } from './shared.ts'\n\nstate.foo += 1\n```\n\n```ts\n// B.vue\nimport { state } from './shared.ts'\n\nconsole.log(state.foo) // 2\n```\n\n</v-clicks>\n</div>\n</div>\n\n<h3 v-click class=\"opacity-100\">⚠️ But it's not SSR compatible!</h3>\n\n---\n\n# Shared State (SSR friendly) <MarkerPattern />\n\nUse `provide` and `inject` to share the app-level state\n\n<div class=\"grid grid-cols-[max-content_1fr] gap-4\">\n\n<v-click>\n\n```ts\nexport const myStateKey: InjectionKey<MyState> = Symbol('state')\n\nexport function createMyState() {\n  const state = {\n    /* ... */\n  }\n\n  return {\n    install(app: App) {\n      app.provide(myStateKey, state)\n    },\n  }\n}\n\nexport function useMyState(): MyState {\n  return inject(myStateKey)!\n}\n```\n\n</v-click>\n\n<div>\n<v-clicks>\n\n```ts\n// main.ts\nconst App = createApp(App)\n\napp.use(createMyState())\n```\n\n```ts\n// A.vue\n\n// use everywhere in your app\nconst state = useMyState()\n```\n\n<div class=\"my-3\">\n\n- [Vue Router v4](https://github.com/vuejs/vue-router-next) is using the similar approach\n\n</div>\n\n</v-clicks>\n</div>\n\n</div>\n\n---\n\n# useVModel <MarkerTips />\n\nA helper to make props/emit easier\n\n<div class=\"grid grid-cols-2 gap-4\">\n\n<v-click>\n\n```ts\nexport function useVModel(props, name) {\n  const emit = getCurrentInstance().emit\n\n  return computed({\n    get() {\n      return props[name]\n    },\n    set(v) {\n      emit(`update:${name}`, v)\n    },\n  })\n}\n```\n\n</v-click>\n\n<div>\n\n<v-click>\n\n```ts\nexport default defineComponent({\n  setup(props) {\n    const value = useVModel(props, 'value')\n\n    return { value }\n  },\n})\n```\n\n</v-click>\n<br>\n<v-click>\n\n```html\n<template>\n  <input v-model=\"value\" />\n</template>\n```\n\n</v-click>\n</div>\n</div>\n\n<div v-click class=\"abs-b mx-14 my-12\">\n<VueUse name=\"useVModel\"/>\n</div>\n\n---\ndisabled: true\n---\n\n# useVModel (Passive) <MarkerTips />\n\nMake the model able to be updated **independently** from the parent logic\n\n<v-click>\n\n```ts\nexport function usePassiveVModel(props, name) {\n  const emit = getCurrentInstance().emit\n  const data = ref(props[name]) // store the value in a ref\n\n  watch(() => props.value, v => data.value = v) // sync the ref whenever the prop changes\n\n  return computed({\n    get() {\n      return data.value\n    },\n    set(v) {\n      data.value = v // when setting value, update the ref directly\n      emit(`update:${name}`, v) // then emit out the changes\n    },\n  })\n}\n```\n\n</v-click>\n\n---\nlayout: center\n---\n\n# All of them work for both Vue 2 and 3\n\n---\n\n# `@vue/composition-api` <Marker class=\"text-teal-400\">Lib</Marker>\n\nComposition API support for Vue 2.<br><carbon-logo-github class=\"inline-block\"/> [vuejs/composition-api](https://github.com/vuejs/composition-api)\n\n```ts\nimport VueCompositionAPI from '@vue/composition-api'\nimport Vue from 'vue'\n\nVue.use(VueCompositionAPI)\n```\n\n```ts\nimport { reactive, ref } from '@vue/composition-api'\n```\n\n---\n\n# Vue 2.7 <Marker class=\"text-purple-400\">Upcoming</Marker>\n\n[Plans in Vue 2.7](https://github.com/vuejs/rfcs/blob/ie11/active-rfcs/0000-vue3-ie11-support.md#for-those-who-absolutely-need-ie11-support)\n\n- Backport `@vue/composition-api` into Vue 2's core.\n- `<script setup>` syntax in Single-File Components.\n- Migrate codebase to TypeScript.\n- IE11 support.\n- LTS.\n\n---\n\n# Vue Demi <Marker class=\"text-teal-400\">Lib</Marker>\n\nCreates Universal Library for Vue 2 & 3<br><carbon-logo-github class=\"inline-block\"/> [vueuse/vue-demi](https://github.com/vueuse/vue-demi)\n\n```ts\n// same syntax for both Vue 2 and 3\nimport { defineComponent, reactive, ref } from 'vue-demi'\n```\n\n<img class=\"h-50 mx-auto\" src=\"https://cdn.jsdelivr.net/gh/vueuse/vue-demi/assets/banner.png\" alt=\"\" />\n\n---\n\n# Recap\n\n- Think as \"Connections\"\n- One thing at a time\n- Accepting ref as arguments\n- Returns an object of refs\n- Make functions flexible\n- Async to \"sync\"\n- Side-effect self clean up\n- Shared state\n\n---\nlayout: center\nclass: 'text-center pb-5 :'\n---\n\n# Thank You!\n\nSlides can be found on [antfu.me](https://antfu.me)\n"
  },
  {
    "path": "demo/starter/README.md",
    "content": "See [../../packages/create-app/template/README.md](../../packages/create-app/template/README.md)\n"
  },
  {
    "path": "demo/starter/components/Counter.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nconst props = defineProps({\n  count: {\n    default: 0,\n  },\n})\n\nconst counter = ref(props.count)\n</script>\n\n<template>\n  <div flex=\"~\" w=\"min\" border=\"~ main rounded-md\">\n    <button\n      border=\"r main\"\n      p=\"2\"\n      font=\"mono\"\n      outline=\"!none\"\n      hover:bg=\"gray-400 opacity-20\"\n      @click=\"counter -= 1\"\n    >\n      -\n    </button>\n    <span m=\"auto\" p=\"2\">{{ counter }}</span>\n    <button\n      border=\"l main\"\n      p=\"2\"\n      font=\"mono\"\n      outline=\"!none\"\n      hover:bg=\"gray-400 opacity-20\"\n      @click=\"counter += 1\"\n    >\n      +\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "demo/starter/package.json",
    "content": "{\n  \"name\": \"slidev-demo\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"slidev build\",\n    \"dev\": \"nodemon -w '../../packages/slidev/dist/*.mjs' --exec \\\"slidev ./slides.md --open=false --log=info --inspect\\\"\",\n    \"export\": \"slidev export\",\n    \"export-notes\": \"slidev export-notes\"\n  },\n  \"devDependencies\": {\n    \"@slidev/cli\": \"workspace:*\",\n    \"@slidev/theme-default\": \"catalog:themes\",\n    \"@slidev/theme-seriph\": \"catalog:themes\",\n    \"nodemon\": \"catalog:dev\",\n    \"vue\": \"catalog:frontend\"\n  }\n}\n"
  },
  {
    "path": "demo/starter/pages/imported-slides.md",
    "content": "# Imported Slides\n\nYou can split your slides.md into multiple files and organize them as you want using the `src` attribute.\n\n#### `slides.md`\n\n```markdown\n# Page 1\n\nPage 2 from main entry.\n\n---\n\n## src: ./subpage.md\n```\n\n<br>\n\n#### `subpage.md`\n\n```markdown\n# Page 2\n\nPage 2 from another file.\n```\n\n[Learn more](https://sli.dev/guide/syntax.html#importing-slides)\n"
  },
  {
    "path": "demo/starter/slides.md",
    "content": "---\n# try also 'default' to start simple\ntheme: seriph\n# random image from a curated Unsplash collection by Anthony\n# like them? see https://unsplash.com/collections/94734566/slidev\nbackground: https://cover.sli.dev\n# some information about your slides (markdown enabled)\ntitle: Welcome to Slidev\ninfo: |\n  ## Slidev Starter Template\n  Presentation slides for developers.\n\n  Learn more at [Sli.dev](https://sli.dev)\n# apply UnoCSS classes to the current slide\nclass: text-center\n# https://sli.dev/features/drawing\ndrawings:\n  persist: false\n# slide transition: https://sli.dev/guide/animations.html#slide-transitions\ntransition: slide-left\n# enable Comark Syntax: https://comark.dev/syntax/markdown\ncomark: true\n# duration of the presentation\nduration: 35min\n---\n\n# Welcome to Slidev\n\nPresentation slides for developers\n\n<div @click=\"$slidev.nav.next\" class=\"mt-12 py-1\" hover:bg=\"white op-10\">\n  Press Space for next page <carbon:arrow-right />\n</div>\n\n<div class=\"abs-br m-6 text-xl\">\n  <button @click=\"$slidev.nav.openInEditor()\" title=\"Open in Editor\" class=\"slidev-icon-btn\">\n    <carbon:edit />\n  </button>\n  <a href=\"https://github.com/slidevjs/slidev\" target=\"_blank\" class=\"slidev-icon-btn\">\n    <carbon:logo-github />\n  </a>\n</div>\n\n<!--\nThe last comment block of each slide will be treated as slide notes. It will be visible and editable in Presenter Mode along with the slide. [Read more in the docs](https://sli.dev/guide/syntax.html#notes)\n-->\n\n---\ntransition: fade-out\n---\n\n# What is Slidev?\n\nSlidev is a slides maker and presenter designed for developers, consist of the following features\n\n- 📝 **Text-based** - focus on the content with Markdown, and then style them later\n- 🎨 **Themable** - themes can be shared and re-used as npm packages\n- 🧑‍💻 **Developer Friendly** - code highlighting, live coding with autocompletion\n- 🤹 **Interactive** - embed Vue components to enhance your expressions\n- 🎥 **Recording** - built-in recording and camera view\n- 📤 **Portable** - export to PDF, PPTX, PNGs, or even a hostable SPA\n- 🛠 **Hackable** - virtually anything that's possible on a webpage is possible in Slidev\n<br>\n<br>\n\nRead more about [Why Slidev?](https://sli.dev/guide/why)\n\n<!--\nYou can have `style` tag in markdown to override the style for the current page.\nLearn more: https://sli.dev/features/slide-scope-style\n-->\n\n<style>\nh1 {\n  background-color: #2B90B6;\n  background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%);\n  background-size: 100%;\n  -webkit-background-clip: text;\n  -moz-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  -moz-text-fill-color: transparent;\n}\n</style>\n\n<!--\nHere is another comment.\n-->\n\n---\ntransition: slide-up\nlevel: 2\n---\n\n# Navigation\n\nHover on the bottom-left corner to see the navigation's controls panel, [learn more](https://sli.dev/guide/ui#navigation-bar)\n\n## Keyboard Shortcuts\n\n|                                                     |                             |\n| --------------------------------------------------- | --------------------------- |\n| <kbd>right</kbd> / <kbd>space</kbd>                 | next animation or slide     |\n| <kbd>left</kbd>  / <kbd>shift</kbd><kbd>space</kbd> | previous animation or slide |\n| <kbd>up</kbd>                                       | previous slide              |\n| <kbd>down</kbd>                                     | next slide                  |\n\n<!-- https://sli.dev/guide/animations.html#click-animation -->\n<img\n  v-click\n  class=\"absolute -bottom-9 -left-7 w-80 opacity-50\"\n  src=\"https://sli.dev/assets/arrow-bottom-left.svg\"\n  alt=\"\"\n/>\n<p v-after class=\"absolute bottom-23 left-45 opacity-30 transform -rotate-10\">Here!</p>\n\n---\nlayout: two-cols\nlayoutClass: gap-16\n---\n\n# Table of contents\n\nYou can use the `Toc` component to generate a table of contents for your slides:\n\n```html\n<Toc minDepth=\"1\" maxDepth=\"1\" />\n```\n\nThe title will be inferred from your slide content, or you can override it with `title` and `level` in your frontmatter.\n\n::right::\n\n<Toc text-sm minDepth=\"1\" maxDepth=\"2\" />\n\n---\nlayout: image-right\nimage: https://cover.sli.dev\n---\n\n# Code\n\nUse code snippets and get the highlighting directly, and even types hover!\n\n```ts [filename-example.ts] {all|4|6|6-7|9|all} twoslash\n// TwoSlash enables TypeScript hover information\n// and errors in markdown code blocks\n// More at https://shiki.style/packages/twoslash\nimport { computed, ref } from 'vue'\n\nconst count = ref(0)\nconst doubled = computed(() => count.value * 2)\n\ndoubled.value = 2\n```\n\n<arrow v-click=\"[4, 5]\" x1=\"350\" y1=\"310\" x2=\"195\" y2=\"342\" color=\"#953\" width=\"2\" arrowSize=\"1\" />\n\n<!-- This allow you to embed external code blocks -->\n<<< @/snippets/external.ts#snippet\n\n<!-- Footer -->\n\n[Learn more](https://sli.dev/features/line-highlighting)\n\n<!-- Inline style -->\n<style>\n.footnotes-sep {\n  @apply mt-5 opacity-10;\n}\n.footnotes {\n  @apply text-sm opacity-75;\n}\n.footnote-backref {\n  display: none;\n}\n</style>\n\n<!--\nNotes can also sync with clicks\n\n[click] This will be highlighted after the first click\n\n[click] Highlighted with `count = ref(0)`\n\n[click:3] Last click (skip two clicks)\n-->\n\n---\nlevel: 2\n---\n\n# Shiki Magic Move\n\nPowered by [shiki-magic-move](https://shiki-magic-move.netlify.app/), Slidev supports animations across multiple code snippets.\n\nAdd multiple code blocks and wrap them with <code>````md magic-move</code> (four backticks) to enable the magic move. For example:\n\n````md magic-move {lines: true}\n```ts {*|2|*}\n// step 1\nconst author = reactive({\n  name: 'John Doe',\n  books: [\n    'Vue 2 - Advanced Guide',\n    'Vue 3 - Basic Guide',\n    'Vue 4 - The Mystery'\n  ]\n})\n```\n\n```ts {*|1-2|3-4|3-4,8}\n// step 2\nexport default {\n  data() {\n    return {\n      author: {\n        name: 'John Doe',\n        books: [\n          'Vue 2 - Advanced Guide',\n          'Vue 3 - Basic Guide',\n          'Vue 4 - The Mystery'\n        ]\n      }\n    }\n  }\n}\n```\n\n```ts\n// step 3\nexport default {\n  data: () => ({\n    author: {\n      name: 'John Doe',\n      books: [\n        'Vue 2 - Advanced Guide',\n        'Vue 3 - Basic Guide',\n        'Vue 4 - The Mystery'\n      ]\n    }\n  })\n}\n```\n\nNon-code blocks are ignored.\n\n```vue\n<!-- step 4 -->\n<script setup>\nconst author = {\n  name: 'John Doe',\n  books: [\n    'Vue 2 - Advanced Guide',\n    'Vue 3 - Basic Guide',\n    'Vue 4 - The Mystery'\n  ]\n}\n</script>\n```\n````\n\n---\n\n# Components\n\n<div grid=\"~ cols-2 gap-4\">\n<div>\n\nYou can use Vue components directly inside your slides.\n\nWe have provided a few built-in components like `<Tweet/>` and `<Youtube/>` that you can use directly. And adding your custom components is also super easy.\n\n```html\n<Counter :count=\"10\" />\n```\n\n<!-- ./components/Counter.vue -->\n<Counter :count=\"10\" m=\"t-4\" />\n\nCheck out [the guides](https://sli.dev/builtin/components.html) for more.\n\n</div>\n<div>\n\n```html\n<Tweet id=\"1390115482657726468\" />\n```\n\n<Tweet id=\"1390115482657726468\" scale=\"0.65\" />\n\n</div>\n</div>\n\n<!--\nPresenter note with **bold**, *italic*, and ~~striked~~ text.\n\nAlso, HTML elements are valid:\n<div class=\"flex w-full\">\n  <span style=\"flex-grow: 1;\">Left content</span>\n  <span>Right content</span>\n</div>\n-->\n\n---\nclass: px-20\n---\n\n# Themes\n\nSlidev comes with powerful theming support. Themes can provide styles, layouts, components, or even configurations for tools. Switching between themes by just **one edit** in your frontmatter:\n\n<div grid=\"~ cols-2 gap-2\" m=\"t-2\">\n\n```yaml\n---\ntheme: default\n---\n```\n\n```yaml\n---\ntheme: seriph\n---\n```\n\n<img border=\"rounded\" src=\"https://github.com/slidevjs/themes/blob/main/screenshots/theme-default/01.png?raw=true\" alt=\"\">\n\n<img border=\"rounded\" src=\"https://github.com/slidevjs/themes/blob/main/screenshots/theme-seriph/01.png?raw=true\" alt=\"\">\n\n</div>\n\nRead more about [How to use a theme](https://sli.dev/guide/theme-addon#use-theme) and\ncheck out the [Awesome Themes Gallery](https://sli.dev/resources/theme-gallery).\n\n---\n\n# Clicks Animations\n\nYou can add `v-click` to elements to add a click animation.\n\n<div v-click>\n\nThis shows up when you click the slide:\n\n```html\n<div v-click>This shows up when you click the slide.</div>\n```\n\n</div>\n\n<br>\n\n<v-click>\n\nThe <span v-mark.red=\"3\"><code>v-mark</code> directive</span>\nalso allows you to add\n<span v-mark.circle.orange=\"4\">inline marks</span>\n, powered by [Rough Notation](https://roughnotation.com/):\n\n```html\n<span v-mark.underline.orange>inline markers</span>\n```\n\n</v-click>\n\n<div mt-20 v-click>\n\n[Learn more](https://sli.dev/guide/animations#click-animation)\n\n</div>\n\n---\n\n# Motions\n\nMotion animations are powered by [@vueuse/motion](https://motion.vueuse.org/), triggered by `v-motion` directive.\n\n```html\n<div\n  v-motion\n  :initial=\"{ x: -80 }\"\n  :enter=\"{ x: 0 }\"\n  :click-3=\"{ x: 80 }\"\n  :leave=\"{ x: 1000 }\"\n>\n  Slidev\n</div>\n```\n\n<div class=\"w-60 relative\">\n  <div class=\"relative w-40 h-40\">\n    <img\n      v-motion\n      :initial=\"{ x: 800, y: -100, scale: 1.5, rotate: -50 }\"\n      :enter=\"final\"\n      class=\"absolute inset-0\"\n      src=\"https://sli.dev/logo-square.png\"\n      alt=\"\"\n    />\n    <img\n      v-motion\n      :initial=\"{ y: 500, x: -100, scale: 2 }\"\n      :enter=\"final\"\n      class=\"absolute inset-0\"\n      src=\"https://sli.dev/logo-circle.png\"\n      alt=\"\"\n    />\n    <img\n      v-motion\n      :initial=\"{ x: 600, y: 400, scale: 2, rotate: 100 }\"\n      :enter=\"final\"\n      class=\"absolute inset-0\"\n      src=\"https://sli.dev/logo-triangle.png\"\n      alt=\"\"\n    />\n  </div>\n\n  <div\n    class=\"text-5xl absolute top-14 left-40 text-[#2B90B6] -z-1\"\n    v-motion\n    :initial=\"{ x: -80, opacity: 0}\"\n    :enter=\"{ x: 0, opacity: 1, transition: { delay: 2000, duration: 1000 } }\">\n    Slidev\n  </div>\n</div>\n\n<!-- vue script setup scripts can be directly used in markdown, and will only affects current page -->\n<script setup lang=\"ts\">\nconst final = {\n  x: 0,\n  y: 0,\n  rotate: 0,\n  scale: 1,\n  transition: {\n    type: 'spring',\n    damping: 10,\n    stiffness: 20,\n    mass: 2\n  }\n}\n</script>\n\n<div\n  v-motion\n  :initial=\"{ x:35, y: 30, opacity: 0}\"\n  :enter=\"{ y: 0, opacity: 1, transition: { delay: 3500 } }\">\n\n[Learn more](https://sli.dev/guide/animations.html#motion)\n\n</div>\n\n---\n\n# $\\LaTeX$\n\n$\\LaTeX$ is supported out-of-box. Powered by [$\\KaTeX$](https://katex.org/).\n\n<div h-3 />\n\nInline $\\sqrt{3x-1}+(1+x)^2$\n\nBlock\n$$ {1|3|all}\n\\begin{aligned}\n\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\varepsilon_0} \\\\\n\\nabla \\cdot \\vec{B} &= 0 \\\\\n\\nabla \\times \\vec{E} &= -\\frac{\\partial\\vec{B}}{\\partial t} \\\\\n\\nabla \\times \\vec{B} &= \\mu_0\\vec{J} + \\mu_0\\varepsilon_0\\frac{\\partial\\vec{E}}{\\partial t}\n\\end{aligned}\n$$\n\n[Learn more](https://sli.dev/features/latex)\n\n---\n\n# Diagrams\n\nYou can create diagrams / graphs from textual descriptions, directly in your Markdown.\n\n<div class=\"grid grid-cols-4 gap-5 pt-4 -mb-6\">\n\n```mermaid {scale: 0.5, alt: 'A simple sequence diagram'}\nsequenceDiagram\n    Alice->John: Hello John, how are you?\n    Note over Alice,John: A typical interaction\n```\n\n```mermaid {theme: 'neutral', scale: 0.8}\ngraph TD\nB[Text] --> C{Decision}\nC -->|One| D[Result 1]\nC -->|Two| E[Result 2]\n```\n\n```mermaid\nmindmap\n  root((mindmap))\n    Origins\n      Long history\n      ::icon(fa fa-book)\n      Popularisation\n        British popular psychology author Tony Buzan\n    Research\n      On effectiveness<br/>and features\n      On Automatic creation\n        Uses\n            Creative techniques\n            Strategic planning\n            Argument mapping\n    Tools\n      Pen and paper\n      Mermaid\n```\n\n```plantuml {scale: 0.7}\n@startuml\n\npackage \"Some Group\" {\n  HTTP - [First Component]\n  [Another Component]\n}\n\nnode \"Other Groups\" {\n  FTP - [Second Component]\n  [First Component] --> FTP\n}\n\ncloud {\n  [Example 1]\n}\n\ndatabase \"MySql\" {\n  folder \"This is my folder\" {\n    [Folder 3]\n  }\n  frame \"Foo\" {\n    [Frame 4]\n  }\n}\n\n[Another Component] --> [Example 1]\n[Example 1] --> [Folder 3]\n[Folder 3] --> [Frame 4]\n\n@enduml\n```\n\n</div>\n\nLearn more: [Mermaid Diagrams](https://sli.dev/features/mermaid) and [PlantUML Diagrams](https://sli.dev/features/plantuml)\n\n---\nfoo: bar\ndragPos:\n  square: 691,32,167,_,-16\n---\n\n# Draggable Elements\n\nDouble-click on the draggable elements to edit their positions.\n\n<br>\n\n###### Directive Usage\n\n```md\n<img v-drag=\"'square'\" src=\"https://sli.dev/logo.png\">\n```\n\n<br>\n\n###### Component Usage\n\n```md\n<v-drag text-3xl>\n  <div class=\"i-carbon:arrow-up\" />\n  Use the `v-drag` component to have a draggable container!\n</v-drag>\n```\n\n<v-drag pos=\"663,206,261,_,-15\">\n  <div text-center text-3xl border border-main rounded>\n    Double-click me!\n  </div>\n</v-drag>\n\n<img v-drag=\"'square'\" src=\"https://sli.dev/logo.png\">\n\n###### Draggable Arrow\n\n```md\n<v-drag-arrow two-way />\n```\n\n<v-drag-arrow pos=\"67,452,253,46\" two-way op70 />\n\n---\nsrc: ./pages/imported-slides.md\nhide: false\n---\n\n---\n\n# Monaco Editor\n\nSlidev provides built-in Monaco Editor support.\n\nAdd `{monaco}` to the code block to turn it into an editor:\n\n```ts {monaco}\nimport { ref } from 'vue'\nimport { emptyArray } from './external'\n\nconst arr = ref(emptyArray(10))\n```\n\nUse `{monaco-run}` to create an editor that can execute the code directly in the slide:\n\n```ts {monaco-run}\nimport { version } from 'vue'\nimport { emptyArray, sayHello } from './external'\n\nsayHello()\nconsole.log(`vue ${version}`)\nconsole.log(emptyArray<number>(10).reduce(fib => [...fib, fib.at(-1)! + fib.at(-2)!], [1, 1]))\n```\n\n---\nlayout: center\nclass: text-center\n---\n\n# Learn More\n\n[Documentation](https://sli.dev) · [GitHub](https://github.com/slidevjs/slidev) · [Showcases](https://sli.dev/resources/showcases)\n\n<PoweredBySlidev mt-10 />\n"
  },
  {
    "path": "demo/starter/snippets/external.ts",
    "content": "/* eslint-disable no-console */\n\n// #region snippet\n// Inside ./snippets/external.ts\nexport function emptyArray<T>(length: number) {\n  return Array.from<T>({ length })\n}\n// #endregion snippet\n\nexport function sayHello() {\n  console.log('Hello from snippets/external.ts')\n}\n"
  },
  {
    "path": "demo/starter/style.css",
    "content": "/* add any global style here */\n"
  },
  {
    "path": "demo/starter/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [],\n})\n"
  },
  {
    "path": "demo/vue-runner/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"slidev build\",\n    \"dev\": \"nodemon -w '../../packages/slidev/dist/*.mjs' --exec \\\"slidev ./slides.md --open=false --log=info --inspect\\\"\",\n    \"export\": \"slidev export\",\n    \"export-notes\": \"slidev export-notes\"\n  },\n  \"devDependencies\": {\n    \"@slidev/cli\": \"workspace:*\",\n    \"@slidev/theme-default\": \"catalog:themes\",\n    \"@slidev/theme-seriph\": \"catalog:themes\",\n    \"@vue/compiler-sfc\": \"catalog:demo\",\n    \"nodemon\": \"catalog:dev\",\n    \"vue\": \"catalog:frontend\"\n  }\n}\n"
  },
  {
    "path": "demo/vue-runner/setup/code-runners.ts",
    "content": "/* eslint-disable no-new-func */\nimport { defineCodeRunnersSetup } from '@slidev/types'\n\nexport default defineCodeRunnersSetup(() => {\n  return {\n    // Support Vue SFC\n    async vue(code) {\n      const Vue = await import('vue')\n      const { parse, compileScript } = await import('@vue/compiler-sfc')\n\n      // Compile the script, note this demo does not handle Vue styles\n      const sfc = parse(code)\n      let scripts = compileScript(sfc.descriptor, {\n        id: sfc.descriptor.filename,\n        genDefaultAs: '__Component',\n        inlineTemplate: true,\n      }).content\n\n      // Replace Vue imports to object destructuring\n      // Only for simple demo, it doesn't work with imports from other packages\n      scripts = scripts.replace(/import (\\{[^}]+\\}) from ['\"]vue['\"]/g, (_, imports) => `const ${imports.replace(/\\sas\\s/g, ':')} = Vue`)\n      scripts += '\\nreturn __Component'\n\n      // Create function to evaluate the script and get the component\n      // Note this is not sandboxed, it's NOT secure.\n      const component = new Function(`return (Vue) => {${scripts}}`)()(Vue)\n\n      // Mount the component\n      const app = Vue.createApp(component)\n      const el = document.createElement('div')\n      app.mount(el)\n\n      return {\n        element: el,\n      }\n    },\n  }\n})\n"
  },
  {
    "path": "demo/vue-runner/setup/shiki.ts",
    "content": "import type { ShikiSetupReturn } from '@slidev/types'\nimport { defineShikiSetup } from '@slidev/types'\n\nexport default defineShikiSetup((): ShikiSetupReturn => {\n  return {\n    langs: [\n      'ts',\n      'js',\n      'vue',\n      'html',\n    ],\n  }\n})\n"
  },
  {
    "path": "demo/vue-runner/slides.md",
    "content": "---\nlayout: default\n---\n\n# Simple Vue SFC Runner\n\n<!-- eslint-skip -->\n\n```vue {monaco-run}\n<script setup>\nimport { computed, ref } from 'vue'\nconst counter = ref(1)\nconst doubled = computed(() => counter.value * 2)\nfunction inc() { counter.value++ }\n</script>\n\n<template>\n  <div class=\"select-none text-lg flex gap-4 items-center\">\n    <span class=\"text-gray text-lg\">\n      <span class=\"text-orange\">{{ counter }}</span>\n      * 2 =\n      <span class=\"text-green\">{{ doubled }}</span>\n    </span>\n    <button class=\"border border-main p2 rounded\" @click=\"inc\">+1</button>\n    <button class=\"border border-main p2 rounded\" @click=\"counter -= 1\">-1</button>\n  </div>\n</template>\n```\n\n---\n\nThis is a demo to prove the extensibility of Slidev Code Runners.\n\nRefer to `./setup/monaco-runner.ts` for the implementation.\n\nNote that there is a lot of edge cases that this demo is not handling. Extra work is needed to make it production ready.\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "node_modules\ndist\n.vitepress/@slidev\n.vitepress/cache\n"
  },
  {
    "path": "docs/.vitepress/addons.ts",
    "content": "import type { ThemeInfo } from './themes'\n\nexport type AddonInfo = Omit<ThemeInfo, 'previews'>\n\nexport const official: AddonInfo[] = [\n  {\n    id: '',\n    link: '#',\n    name: 'Work in Progress',\n    description: '',\n    tags: [],\n    author: {\n      name: '',\n    },\n  },\n]\n\nexport const community: AddonInfo[] = [\n  {\n    id: 'slidev-addon-python-runner',\n    name: 'Python Runner',\n    description: 'Run actual Python code in your slides',\n    tags: ['Code runner'],\n    author: {\n      name: '_Kerman',\n      link: 'https://github.com/KermanX',\n    },\n    repo: 'https://github.com/KermanX/slidev-addon-python-runner',\n  },\n  {\n    id: 'slidev-addon-tldraw',\n    name: 'tldraw',\n    description: 'Embed tldraw diagrams directly in Slidev, with in-slide editing support',\n    tags: ['Integration', 'Diagram'],\n    author: {\n      name: 'Albert Brand',\n      link: 'https://github.com/AlbertBrand',\n    },\n    repo: 'https://github.com/AlbertBrand/slidev-addon-tldraw',\n  },\n  {\n    id: 'slidev-addon-react',\n    name: 'React Components',\n    description: 'Use React components with JSX/TSX in your Slidev presentations.',\n    tags: ['React', 'Component', 'Integration'],\n    repo: 'https://github.com/Ygilany/slidev-addon-react',\n    author: {\n      name: 'YGilany',\n      link: 'https://github.com/YGilany',\n    },\n  },\n  {\n    id: 'slidev-addon-typst',\n    name: 'Typst',\n    description: 'Typst addon for Slidev',\n    tags: ['Integration', 'Diagram'],\n    author: {\n      name: 'Shigma',\n      link: 'https://github.com/shigma',\n    },\n    repo: 'https://github.com/shigma/slidev-addon-typst',\n  },\n  {\n    id: 'slidev-addon-fancy-arrow',\n    name: 'Fancy Arrow',\n    description: 'Hand drawn arrows with various styling and positioning options',\n    tags: ['Component'],\n    author: {\n      name: 'whitphx',\n      link: 'https://github.com/whitphx',\n    },\n    repo: 'https://github.com/whitphx/slidev-addon-fancy-arrow',\n  },\n  {\n    id: 'slidev-addon-sync',\n    name: 'Remote Sync',\n    description: 'Sync component for Slidev static build that uses a SSE or WS server',\n    tags: ['Remote control', 'Navigation'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-addon-sync',\n  },\n  {\n    id: 'slidev-addon-tikzjax',\n    name: 'tikzjax',\n    description: 'Compile TikZ/Chemfig/... to SVG and display them in Slidev',\n    tags: ['Integration', 'Diagram'],\n    author: {\n      name: 'Ethan Goh',\n      link: 'https://github.com/7086cmd',\n    },\n    repo: 'https://github.com/7086cmd/slidev-addon-tikzjax',\n  },\n  {\n    id: 'slidev-component-pager',\n    name: 'Pager',\n    description: 'Show current page and total page number',\n    tags: ['Component', 'Navigation'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-pager',\n  },\n  {\n    id: 'slidev-component-poll',\n    name: 'Poll and Quiz',\n    description: 'Poll and Quiz components for Slidev',\n    tags: ['Component'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-poll',\n  },\n  {\n    id: 'slidev-component-progress',\n    name: 'Progress',\n    description: 'Show interactive progress bar for Slidev',\n    tags: ['Tool', 'Navigation'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-progress',\n  },\n  {\n    id: 'slidev-component-scroll',\n    name: 'Mouse Scroll',\n    description: 'Use mouse wheel for navigating',\n    tags: ['Navigation'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-scroll',\n  },\n  {\n    id: 'slidev-component-spotlight',\n    name: 'Spotlight',\n    description: 'Activate a spotlight to highlight a specific region by holding a key',\n    tags: ['Tool'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-spotlight',\n  },\n  {\n    id: 'slidev-component-zoom',\n    name: 'Zooming',\n    description: 'Allow zooming inside the slides',\n    tags: ['Tool'],\n    author: {\n      name: 'Tony Cabaye',\n      link: 'https://github.com/tonai',\n    },\n    repo: 'https://github.com/Smile-SA/slidev-component-zoom',\n  },\n  {\n    id: 'slidev-addon-rabbit',\n    name: 'Rabbit',\n    description: 'Presentation time management for Slidev inspired by Rabbit',\n    tags: ['Tool', 'Navigation'],\n    author: {\n      name: 'kaakaa',\n      link: 'https://github.com/kaakaa',\n    },\n    repo: 'https://github.com/kaakaa/slidev-addon-rabbit',\n  },\n  {\n    id: 'slidev-addon-stem',\n    name: 'STEM',\n    description: 'Slidev addon for scientific presentation',\n    tags: ['Component', 'Layout'],\n    author: {\n      name: 'yutaka-shoji',\n      link: 'https://github.com/yutaka-shoji',\n    },\n    repo: 'https://github.com/yutaka-shoji/slidev-addon-stem',\n  },\n  {\n    id: 'slidev-addon-naive',\n    name: 'Naive UI',\n    description: 'Brings Naive UI components into Slidev',\n    tags: ['Component'],\n    author: {\n      name: 'Samuel Huang',\n      link: 'https://sghuang.com',\n    },\n    repo: 'https://github.com/sghuang19/slidev-addon-naive',\n  },\n  {\n    id: 'slidev-addon-hls-player',\n    name: 'HLS player',\n    description: 'Add a basic hls.js powered video player on your slides to show HTTP Live Streaming videos',\n    tags: ['hls', 'video'],\n    author: {\n      name: 'Albert Brand',\n      link: 'https://github.com/AlbertBrand',\n    },\n    repo: 'https://github.com/AlbertBrand/slidev-addon-hls-player',\n  },\n  {\n    id: 'slidev-addon-window-mockup',\n    name: 'Window Mockup',\n    description: 'Styled window frames',\n    tags: ['Component'],\n    author: {\n      name: 'whitphx',\n      link: 'https://github.com/whitphx',\n    },\n    repo: 'https://github.com/whitphx/slidev-addon-window-mockup',\n  },\n  {\n    id: 'slidev-addon-bpmn',\n    name: 'BPMN viewer',\n    description: 'Visualize bpmn-files in your slidev',\n    tags: ['Component'],\n    author: {\n      name: 'emaarco',\n      link: 'https://github.com/emaarco',\n    },\n    repo: 'https://github.com/emaarco/slidev-addon-bpmn',\n  },\n  {\n    id: 'slidev-addon-p5',\n    name: 'Runner for p5js',\n    description: 'Display, edit and run p5js sketches in your slidev',\n    tags: ['Component', 'Code runner'],\n    author: {\n      name: 'mjvo',\n      link: 'https://github.com/mjvo',\n    },\n    repo: 'https://github.com/mjvo/slidev-addon-p5',\n  },\n  // Add yours here!\n  {\n    id: '',\n    link: 'https://github.com/slidevjs/slidev/edit/main/docs/.vitepress/addons.ts',\n    name: 'Yours?',\n    description: 'Click here to submit your addon :)',\n    tags: [],\n    author: {\n      name: '',\n    },\n  },\n]\n"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "content": "import type { DefaultTheme } from 'vitepress'\nimport { fileURLToPath } from 'node:url'\nimport { transformerTwoslash } from '@shikijs/vitepress-twoslash'\nimport { defineConfig } from 'vitepress'\nimport { groupIconMdPlugin } from 'vitepress-plugin-group-icons'\nimport { version } from '../package.json'\nimport Customizations from './customizations'\nimport { Advanced, BuiltIn, Guides, Resources } from './pages'\nimport { getSidebarObject } from './sidebar-gen'\n\nexport const slidebars: DefaultTheme.SidebarItem[] = [\n  {\n    text: 'Guide',\n    items: Guides,\n  },\n  {\n    text: 'Advanced',\n    items: Advanced,\n  },\n  {\n    text: 'Customizations',\n    items: Customizations,\n  },\n  {\n    text: 'Built-in',\n    items: BuiltIn,\n  },\n  {\n    text: 'Resources',\n    items: Resources,\n  },\n]\n\nexport default defineConfig({\n  title: 'Slidev',\n  description: 'Presentation slides for developers',\n  head: [\n    ['link', { rel: 'icon', type: 'image/png', href: '/favicon.png' }],\n    ['meta', { name: 'author', content: 'Anthony Fu' }],\n    ['meta', { property: 'og:title', content: 'Slidev' }],\n    ['meta', { property: 'og:image', content: 'https://sli.dev/og-image.png' }],\n    ['meta', { property: 'og:description', content: 'Presentation slides for developers' }],\n    ['meta', { name: 'twitter:card', content: 'summary_large_image' }],\n    ['meta', { name: 'twitter:creator', content: '@slidevjs' }],\n    ['meta', { name: 'twitter:image', content: 'https://sli.dev/og-image.png' }],\n    ['link', { rel: 'dns-prefetch', href: 'https://fonts.gstatic.com' }],\n    ['link', { rel: 'preconnect', crossorigin: 'anonymous', href: 'https://fonts.gstatic.com' }],\n    ['link', { href: 'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@200;400;500&family=Inter:wght@200;400;500;600', rel: 'stylesheet' }],\n  ],\n  markdown: {\n    theme: {\n      light: 'vitesse-light',\n      dark: 'vitesse-dark',\n    },\n    async shikiSetup(shiki) {\n      await shiki.loadLanguage(\n        'html',\n        'xml',\n        'vue',\n        'markdown',\n        'mermaid',\n        'latex',\n      )\n    },\n    codeTransformers: [\n      transformerTwoslash({\n        twoslashOptions: {\n          // The @slidev/* installed in docs package are very old and should only be used in the homepage demo\n          vfsRoot: fileURLToPath(import.meta.url),\n          compilerOptions: {\n            resolveJsonModule: true,\n            moduleResolution: /* Bundler */ 100,\n          },\n        },\n      }),\n    ],\n    config(md) {\n      md.use(groupIconMdPlugin)\n    },\n  },\n  cleanUrls: true,\n  themeConfig: {\n    logo: '/logo.svg',\n    editLink: {\n      pattern: 'https://github.com/slidevjs/slidev/edit/main/docs/:path',\n      text: 'Suggest changes to this page',\n    },\n\n    search: {\n      provider: 'local',\n    },\n\n    nav: [\n      {\n        text: '📖 Guide',\n        items: [\n          ...Guides,\n          {\n            text: 'Advanced',\n            items: Advanced,\n          },\n        ],\n      },\n      {\n        text: '✨ Features',\n        link: '/features/',\n      },\n      {\n        text: 'Reference',\n        items: [\n          {\n            text: 'Built-in',\n            items: BuiltIn,\n          },\n          {\n            text: 'Customize',\n            items: Customizations,\n          },\n        ],\n      },\n      {\n        text: 'Resources',\n        items: Resources,\n      },\n    ],\n\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/slidevjs/slidev' },\n      { icon: 'twitter', link: 'https://twitter.com/slidevjs' },\n      { icon: 'discord', link: 'https://chat.sli.dev' },\n    ],\n\n    sidebar: {\n      '/guide/': slidebars,\n      '/themes/': slidebars,\n      '/addons/': slidebars,\n      '/custom/': slidebars,\n      '/builtin/': slidebars,\n      '/resources/': slidebars,\n      // eslint-disable-next-line antfu/no-top-level-await\n      ...await getSidebarObject(),\n      '/features/': [],\n      '/': slidebars,\n    },\n\n    footer: {\n      message: 'Released under the MIT License.',\n      copyright: 'Copyright © 2020-2025 Anthony Fu.',\n    },\n  },\n\n  locales: {\n    root: {\n      label: `English (v${version})`,\n    },\n    zh: {\n      label: '简体中文',\n      link: 'https://cn.sli.dev/',\n    },\n    ja: {\n      label: '日本語',\n      link: 'https://ja.sli.dev/',\n    },\n  },\n})\n"
  },
  {
    "path": "docs/.vitepress/customizations.ts",
    "content": "export default [\n  {\n    text: 'Configurations',\n    link: '/custom/',\n  },\n  {\n    text: 'Directory Structure',\n    link: '/custom/directory-structure',\n  },\n  {\n    text: 'Configure Highlighter',\n    link: '/custom/config-highlighter',\n  },\n  {\n    text: 'Configure Vite and Plugins',\n    link: '/custom/config-vite',\n  },\n  {\n    text: 'Configure Vue App',\n    link: '/custom/config-vue',\n  },\n  {\n    text: 'Configure UnoCSS',\n    link: '/custom/config-unocss',\n  },\n  {\n    text: 'Configure Code Runners',\n    link: '/custom/config-code-runners',\n  },\n  {\n    text: 'Configure Transformers',\n    link: '/custom/config-transformers',\n  },\n  {\n    text: 'Configure Monaco',\n    link: '/custom/config-monaco',\n  },\n  {\n    text: 'Configure KaTeX',\n    link: '/custom/config-katex',\n  },\n  {\n    text: 'Configure Mermaid',\n    link: '/custom/config-mermaid',\n  },\n  {\n    text: 'Configure Mermaid Renderer',\n    link: '/custom/config-mermaid-renderer',\n  },\n  {\n    text: 'Configure Routes',\n    link: '/custom/config-routes',\n  },\n  {\n    text: 'Configure Shortcuts',\n    link: '/custom/config-shortcuts',\n  },\n  {\n    text: 'Configure Context Menu',\n    link: '/custom/config-context-menu',\n  },\n  {\n    text: 'Configure Fonts',\n    link: '/custom/config-fonts',\n  },\n  {\n    text: 'Configure Pre-Parser',\n    link: '/custom/config-parser',\n  },\n]\n"
  },
  {
    "path": "docs/.vitepress/pages.ts",
    "content": "export const Guides = [\n  {\n    text: 'Why Slidev',\n    link: '/guide/why',\n  },\n  {\n    text: 'Getting Started',\n    link: '/guide/',\n  },\n  {\n    text: 'Syntax Guide',\n    link: '/guide/syntax',\n  },\n  {\n    text: 'User Interface',\n    link: '/guide/ui',\n  },\n  {\n    text: 'Animations',\n    link: '/guide/animations',\n  },\n  {\n    text: 'Theme & Addons',\n    link: '/guide/theme-addon',\n  },\n  {\n    text: 'Components',\n    link: '/guide/component',\n  },\n  {\n    text: 'Layouts',\n    link: '/guide/layout',\n  },\n  {\n    text: 'Exporting',\n    link: '/guide/exporting',\n  },\n  {\n    text: 'Hosting',\n    link: '/guide/hosting',\n  },\n  {\n    text: 'Work with AI',\n    link: '/guide/work-with-ai',\n  },\n  {\n    text: 'FAQ',\n    link: '/guide/faq',\n  },\n]\n\nexport const BuiltIn = [\n  {\n    text: 'CLI',\n    link: '/builtin/cli',\n  },\n  {\n    text: 'Components',\n    link: '/builtin/components',\n  },\n  {\n    text: 'Layouts',\n    link: '/builtin/layouts',\n  },\n]\n\nexport const Advanced = [\n  {\n    text: 'Global Context',\n    link: '/guide/global-context',\n  },\n  {\n    text: 'Writing Layouts',\n    link: '/guide/write-layout',\n  },\n  {\n    text: 'Writing Themes',\n    link: '/guide/write-theme',\n  },\n  {\n    text: 'Writing Addons',\n    link: '/guide/write-addon',\n  },\n]\n\nexport const Resources = [\n  {\n    text: 'Showcases',\n    link: '/resources/showcases',\n  },\n  {\n    text: 'Theme Gallery',\n    link: '/resources/theme-gallery',\n  },\n  {\n    text: 'Addon Gallery',\n    link: '/resources/addon-gallery',\n  },\n  {\n    text: 'Learning Resources',\n    link: '/resources/learning',\n  },\n  {\n    text: 'Curated Covers',\n    link: '/resources/covers',\n  },\n  {\n    text: 'Release Notes',\n    link: 'https://github.com/slidevjs/slidev/releases',\n  },\n]\n"
  },
  {
    "path": "docs/.vitepress/showcases.ts",
    "content": "export interface ShowCaseInfo {\n  title: string\n  cover: string\n  slidesLink?: string\n  sourceLink?: string\n  videoLink?: string\n  at?: string\n  datetime: string\n  author: {\n    name: string\n    link?: string\n  }\n}\n\nexport const showcases: ShowCaseInfo[] = [\n  {\n    title: 'Composable Vue',\n    cover: `${import.meta.env.BASE_URL}showcases/composable-vue.png`,\n    author: {\n      name: 'Anthony Fu',\n      link: 'https://github.com/antfu',\n    },\n    slidesLink: 'https://talks.antfu.me/2021/composable-vue/',\n    sourceLink: 'https://github.com/antfu/talks/tree/main/2021-04-29',\n    at: 'VueDay 2021',\n    datetime: '2021-04-29',\n  },\n  {\n    title: 'Developer Seonglae',\n    cover: 'https://seonglae-slides.vercel.app/og.png',\n    author: {\n      name: 'Seonglae Cho',\n      link: 'https://github.com/seonglae',\n    },\n    slidesLink: 'https://seonglae-slides.vercel.app',\n    sourceLink: 'https://github.com/seonglae/seonglae-slides',\n    at: 'Seongland',\n    datetime: '2021-05-10',\n  },\n  {\n    title: 'Vue 3 > Vue 2 + 1',\n    cover: 'https://user-images.githubusercontent.com/11247099/122246420-1df97b80-cef9-11eb-9c57-7751c6999deb.png',\n    author: {\n      name: 'Thorsten Lünborg',\n      link: 'https://github.com/LinusBorg',\n    },\n    slidesLink: 'http://vueday-2021.linusb.org',\n    sourceLink: 'https://github.com/LinusBorg/vueday-enterjs-vue3',\n    at: 'Enter.js Vue Day',\n    datetime: '2021-06-15',\n  },\n  // {\n  //   title: 'Simply Publish Your Package to npm',\n  //   author: {\n  //     name: 'Lucky Dewa Satria',\n  //     link: 'https://github.com/lucky401',\n  //   },\n  //   at: 'Weekly sharing',\n  //   slidesLink: 'https://masukin.link/talks/simply-publish-your-package-to-npm',\n  //   cover: 'https://masukin.link/talks-cover-npm.png',\n  //   datetime: '2021-06-12',\n  // },\n  // {\n  //   title: 'Create Icon Package With Vue and Rollup',\n  //   author: {\n  //     name: 'Lucky Dewa Satria',\n  //     link: 'https://github.com/lucky401',\n  //   },\n  //   at: 'Weekly Sharing',\n  //   slidesLink: 'https://masukin.link/talks/create-icon-package-with-vue-and-rollup',\n  //   sourceLink: 'https://github.com/lucky401/Create-Icon-Package-With-Vue-and-Rollup',\n  //   cover: 'https://masukin.link/talks-cover-create-icon-package-with-vue-and-rollup.png',\n  //   datetime: '2021-06-19',\n  // },\n  {\n    title: 'BeAPT',\n    author: {\n      name: 'Daniel Sousa @TutoDS',\n      link: 'https://github.com/tutods',\n    },\n    at: 'Presentation of my college final project',\n    slidesLink: 'https://beapt-presentation.netlify.app',\n    sourceLink: 'https://github.com/TutoDS/lei-project/tree/master/presentation',\n    cover: 'https://raw.githubusercontent.com/TutoDS/lei-project/master/presentation/cover.png',\n    datetime: '2021-07-20',\n  },\n  {\n    title: 'Prisma as my ORM for PostgreSQL',\n    cover: 'https://raw.githubusercontent.com/cedric25/prisma-talk/main/cover-for-slidev.png',\n    author: {\n      name: 'Cedric Nicoloso',\n      link: 'https://github.com/cedric25',\n    },\n    slidesLink: 'https://prisma-talk.netlify.app/',\n    sourceLink: 'https://github.com/cedric25/prisma-talk',\n    at: 'LyonJS Meetup',\n    datetime: '2021-07-21',\n  },\n  {\n    title: 'Introduction to SVG',\n    cover: 'https://raw.githubusercontent.com/lyqht/intro-to-svg-slides/main/intro-to-svg-slides-cover.png',\n    author: {\n      name: 'Estee Tey',\n      link: 'https://github.com/lyqht',\n    },\n    slidesLink: 'https://lyqht.github.io/intro-to-svg-slides/',\n    sourceLink: 'https://github.com/lyqht/intro-to-svg-slides',\n    at: 'Thoughtworks Internal Lunch & Learn',\n    datetime: '2021-11-12',\n  },\n  {\n    title: 'Git\\'s Most Wanted',\n    cover: 'https://cdn.jsdelivr.net/gh/alexanderdavide/git-most-wanted@assets/slides-export/01.png',\n    author: {\n      name: 'Alexander Eble',\n      link: 'https://github.com/alexanderdavide',\n    },\n    slidesLink: 'https://alexeble.de/talks/git-most-wanted/',\n    sourceLink: 'https://github.com/alexanderdavide/git-most-wanted',\n    at: 'Internal Tech Talk',\n    datetime: '2022-03-11',\n  },\n  {\n    title: 'OpenFunction 202',\n    cover: 'https://s2.loli.net/2022/05/22/4zsCnkQRFoAU1E5.png',\n    author: {\n      name: 'Haili Zhang',\n      link: 'https://github.com/webup',\n    },\n    slidesLink: 'https://openfunction-talks.netlify.app/2022/202-node-async/',\n    sourceLink: 'https://github.com/webup/openfunction-talks/tree/main/202-node-async',\n    at: 'OpenFunction Tutorial Sharing',\n    datetime: '2022-05-08',\n  },\n  {\n    title: 'Is it Okay to Pursue Functional Programming on Frontend?',\n    author: {\n      name: 'Minsu Kim , Changhui Lee',\n    },\n    at: '2022 JSConf Korea',\n    slidesLink: 'https://moonlit-nougat-422445.netlify.app/1',\n    sourceLink: 'https://github.com/alstn2468/2022-jsconf-presentation',\n    cover: 'https://raw.githubusercontent.com/alstn2468/2022-jsconf-presentation/main/public/images/og.png',\n    datetime: '2022-09-16',\n  },\n  {\n    title: 'Blazing slidev ppt template with naive-ui',\n    author: {\n      name: 'godkun',\n    },\n    at: 'personal sharing',\n    slidesLink: 'https://ppt.godkun.top',\n    sourceLink: 'https://github.com/godkun/ppt-template',\n    cover: 'https://github.com/godkun/ppt-template/raw/main/public/show.gif',\n    datetime: '2022-10-24',\n  },\n  {\n    title: 'Building a Polite Popup with Nuxt 3',\n    author: {\n      name: 'Michael Hoffmann',\n      link: 'https://github.com/mokkapps',\n    },\n    at: 'Vue.js Nation 2023',\n    slidesLink: 'https://vuejsnation-2023-talk-polite-popup.netlify.app',\n    sourceLink: 'https://github.com/Mokkapps/vuejsnation-2023-lightning-talk-polite-popup-nuxt-3-slides',\n    cover: 'https://raw.githubusercontent.com/Mokkapps/vuejsnation-2023-lightning-talk-polite-popup-nuxt-3-slides/main/screenshots/001.png',\n    datetime: '2023-01-25',\n  },\n  {\n    title: 'Dev Environment as Code',\n    cover: 'https://cdn.jsdelivr.net/gh/alexanderdavide/dev-environment-as-code@assets/slides-export/001.png',\n    author: {\n      name: 'Alexander Eble',\n      link: 'https://github.com/alexanderdavide',\n    },\n    slidesLink: 'https://alexeble.de/talks/dev-environment-as-code/',\n    sourceLink: 'https://github.com/alexanderdavide/dev-environment-as-code',\n    at: 'Internal Tech Talk',\n    datetime: '2022-12-01',\n  },\n  {\n    title: 'Exploring Social Engineering',\n    cover: 'https://raw.githubusercontent.com/zyf722/exploring-social-engineering-slides/main/assets/Screenshot_Cover.png',\n    author: {\n      name: 'zyf722',\n      link: 'https://github.com/zyf722',\n    },\n    slidesLink: 'https://zyf722.github.io/exploring-social-engineering-slides/',\n    sourceLink: 'https://github.com/zyf722/exploring-social-engineering-slides',\n    at: 'Presentation on Social Engineering in Computers in Society class',\n    datetime: '2023-10-20',\n  },\n  {\n    title: 'Diablo Health Orb Shader',\n    author: {\n      name: 'SuneBear',\n      link: 'https://github.com/sunebear',\n    },\n    at: 'rctAI Sessions',\n    slidesLink: 'https://rct-ai.github.io/frontend-slides/diablo-health-orb-shader/',\n    sourceLink: 'https://github.com/rct-ai/frontend-slides',\n    cover: 'https://github-production-user-asset-6210df.s3.amazonaws.com/7693264/284304324-db973b4c-a043-4644-932c-826169a1b4d8.gif',\n    datetime: '2022-09-01',\n  },\n  {\n    title: 'Comparison of Packaging Tools in 2023',\n    author: {\n      name: 'Peacock (Yoichi Takai)',\n      link: 'https://p3ac0ck.net',\n    },\n    at: 'PyCon APAC 2023',\n    slidesLink: 'https://slides.p3ac0ck.net/pyconapac2023/1',\n    sourceLink: 'https://github.com/peacock0803sz/slidev-slides/blob/7d41aa5e89ad8627cb68ae2cdbfe1681017b0408/talks/pyconapac2023/pyconapac2023.md',\n    cover: 'https://slides.p3ac0ck.net/pyconapac2023/cover.png',\n    datetime: '2023-10-28',\n  },\n  {\n    title: 'How Rust error handling ease web development',\n    author: {\n      name: 'Nguyễn Hồng Quân',\n      link: 'https://quan.hoabinh.vn',\n    },\n    at: 'FOSSASIA Summit 2024',\n    slidesLink: 'https://talk.quan.hoabinh.vn/rust-error-handling-ease-web-dev/',\n    sourceLink: 'https://hongquan@bitbucket.org/hongquan/rust-error-handling-ease-web-dev',\n    cover: 'https://i.imgur.com/2eBJofY.png',\n    datetime: '2024-04-10',\n  },\n  {\n    title: 'Sit Back and Relax with Fault Awareness and Robust Instant Recovery for Large Scale AI Workloads',\n    author: {\n      name: 'Neko',\n      link: 'https://github.com/nekomeowww',\n    },\n    at: 'KubeCon 2024 China',\n    slidesLink: 'https://talks.ayaka.io/nekoayaka/2024-08-21-kubecon-hk/',\n    sourceLink: 'https://github.com/nekomeowww/talks/tree/main/packages/2024-08-21-kubecon-hk',\n    cover: 'https://raw.githubusercontent.com/BaizeAI/talks/main/packages/2024-08-21-kubecon-hk/public/screenshot.png',\n    datetime: '2024-08-21',\n  },\n  {\n    title: 'Hacker Numerology',\n    author: {\n      name: 'HD Moore',\n      link: 'https://hdm.io',\n    },\n    at: 'LASCON 2024',\n    slidesLink: 'https://hdm.io/decks/2024-LASCON-Numerology/',\n    sourceLink: 'https://github.com/hdm/decks-2024-lascon-numerology.git',\n    cover: 'https://raw.githubusercontent.com/hdm/decks-2024-lascon-numerology/refs/heads/main/screenshot.png',\n    datetime: '2024-10-25',\n  },\n  {\n    title: 'Python Zero To Hero - Episode 1',\n    author: {\n      name: 'Kareim Tarek',\n      link: 'https://kareimgazer.github.io/',\n    },\n    at: 'Kareem Kreates YouTube Channel',\n    slidesLink: 'https://kareimgazer.github.io/py-intro/',\n    sourceLink: 'https://github.com/KareimGazer/py-intro',\n    cover: 'https://i.ytimg.com/vi/hVMaPBrWvAo/hqdefault.jpg',\n    datetime: '2025-01-12',\n  },\n  {\n    title: 'Taming Dependency Chaos for LLM in K8s',\n    author: {\n      name: 'Neko',\n      link: 'https://github.com/nekomeowww',\n    },\n    at: 'KubeCon 2025 China',\n    slidesLink: 'https://baizeai.github.io/talks/2025-06-11-kubecon-hk/',\n    sourceLink: 'https://github.com/BaizeAI/talks/tree/main/packages/2025-06-11-kubecon-hk',\n    cover: 'https://raw.githubusercontent.com/BaizeAI/talks/main/packages/2025-06-11-kubecon-hk/public/screenshot.png',\n    datetime: '2025-06-11',\n  },\n  {\n    title: 'Single Image Super-Resolution Based on Capsule Neural Networks',\n    author: {\n      name: 'George Corrêa de Araújo',\n      link: 'https://george-gca.github.io/',\n    },\n    at: 'Brazilian Conference on Intelligent Systems 2023',\n    slidesLink: 'https://george-gca.github.io/bracis_2023_srcaps/',\n    sourceLink: 'https://github.com/george-gca/bracis_2023_srcaps',\n    cover: 'https://raw.githubusercontent.com/george-gca/bracis_2023_srcaps/refs/heads/main/cover.png',\n    datetime: '2023-09-27',\n  },\n  {\n    title: 'Threat Modeling',\n    author: {\n      name: 'guisso',\n      link: 'https://github.com/fguisso',\n    },\n    at: 'OWASP Meetup',\n    slidesLink: 'https://guisso.dev/talks/threat-modeling',\n    sourceLink: 'https://github.com/fguisso/talks/tree/main/slides/threat-modeling',\n    cover: 'https://guisso.dev/posts/threat-modeling-intro/featured-threat-modeling_hu12396ec5bf9ecba1dda33f1443a5eb10_76776_600x0_resize_box_3.png',\n    datetime: '2023-09-22',\n  },\n  {\n    title: 'A 14-year journey developing nCine, an open-source 2D game framework',\n    author: {\n      name: 'Angelo Theodorou',\n      link: 'https://encelo.github.io',\n    },\n    at: '/dev/games/2025',\n    slidesLink: 'https://encelo.github.io/nCine_14Years_Presentation/',\n    sourceLink: 'https://github.com/encelo/nCine_14Years_Presentation',\n    cover: 'https://i.imgur.com/AbTdfhg.png',\n    datetime: '2025-06-05',\n  },\n  {\n    title: 'Reverse Engineering Denuvo in Hogwarts Legacy',\n    author: {\n      name: 'Maurice Heumann',\n      link: 'https://momo5502.com',\n    },\n    at: 'Navaja Negra 2025',\n    slidesLink: 'https://momo5502.com/slides/denuvo',\n    sourceLink: 'https://github.com/momo5502/denuvo-slides',\n    cover: 'https://raw.githubusercontent.com/momo5502/denuvo-slides/refs/heads/master/images/preview.png',\n    datetime: '2025-10-03',\n  },\n  // Add yours here!\n  {\n    title: 'Yours?',\n    author: {\n      name: '',\n    },\n    at: 'Submit your talk/presentation to be list here!',\n    slidesLink: 'https://github.com/slidevjs/slidev/edit/main/docs/.vitepress/showcases.ts',\n    cover: `${import.meta.env.BASE_URL}theme-placeholder.png`,\n    datetime: '2020-1-1',\n  },\n].sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime())\n"
  },
  {
    "path": "docs/.vitepress/sidebar-gen.ts",
    "content": "import type { DefaultTheme } from 'vitepress'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport fg from 'fast-glob'\nimport graymatter from 'gray-matter'\n\nconst root = fileURLToPath(new URL('../../', import.meta.url))\n\ninterface ParsedFile {\n  filepath: string\n  path: string\n  matter: graymatter.GrayMatterFile<string>\n  title: string\n}\n\nfunction parseFile(file: string) {\n  const filepath = join(root, file)\n  const path = file.replace('docs/', '').replace('.md', '')\n  const matter = graymatter.read(filepath)\n  const title = matter.data.title || matter.content.match(/^#\\s+(.*)/m)?.[1] || path\n  return {\n    filepath,\n    path,\n    matter,\n    title,\n  }\n}\n\nexport async function getSidebarObject() {\n  const map: Record<string, DefaultTheme.SidebarItem[]> = {}\n\n  const parsedFeatures: ParsedFile[] = await fg([\n    'docs/features/*.md',\n  ], {\n    onlyFiles: true,\n    cwd: root,\n  })\n    .then(files => files.map(parseFile))\n\n  const parsedGuides: ParsedFile[] = await fg([\n    'docs/guide/*.md',\n  ], {\n    onlyFiles: true,\n    cwd: root,\n  })\n    .then(files => files.map(parseFile))\n\n  const parsedCustoms: ParsedFile[] = await fg([\n    'docs/custom/*.md',\n  ], {\n    onlyFiles: true,\n    cwd: root,\n  })\n    .then(files => files.map(parseFile))\n\n  parsedFeatures.forEach(({ matter, path }) => {\n    const items: DefaultTheme.SidebarItem[] = [\n      {\n        text: 'Back to',\n        items: [\n          {\n            text: 'All Features',\n            link: '/features',\n          },\n        ],\n      },\n    ]\n\n    function findParsed(related: string) {\n      related = related.replace(/#.*$/, '')\n      const feature = parsedFeatures.find(file => file.path === related)\n      if (feature) {\n        return {\n          type: 'features',\n          item: feature,\n        }\n      }\n      const guide = parsedGuides.find(file => file.path === related)\n      if (guide) {\n        return {\n          type: 'guide',\n          item: guide,\n        }\n      }\n      const custom = parsedCustoms.find(file => file.path === related)\n      if (custom) {\n        return {\n          type: 'custom',\n          item: custom,\n        }\n      }\n      return undefined\n    }\n\n    function frontmatterToSidebarItem(path: string | Record<string, string>): DefaultTheme.SidebarItem[] {\n      if (typeof path === 'string') {\n        const match = findParsed(path)\n        if (match?.type === 'features') {\n          return [{\n            text: `✨ ${match.item.title}`,\n            link: `/${path}`,\n          }]\n        }\n        if (match?.type === 'guide') {\n          return [{\n            text: `📖 ${match.item.title}`,\n            link: `/${path}`,\n          }]\n        }\n        if (match?.type === 'custom') {\n          return [{\n            text: `🛠️ ${match.item.title}`,\n            link: `/${path}`,\n          }]\n        }\n        console.warn(`Dependent file not found: ${path}`)\n        return [{\n          text: path,\n          link: `/${path}`,\n        }]\n      }\n      else {\n        return Object.entries(path).map(([text, link]) => ({\n          text,\n          link,\n        }))\n      }\n    }\n\n    if (matter.data.depends) {\n      items.push({\n        text: 'Depends on',\n        items: matter.data.depends.flatMap(frontmatterToSidebarItem),\n      })\n    }\n\n    if (matter.data.relates) {\n      items.push({\n        text: 'Related to',\n        items: matter.data.relates.flatMap(frontmatterToSidebarItem),\n      })\n    }\n\n    const derives = matter.data.derives\n      ?? parsedFeatures.filter(f => f.matter.data.depends?.includes(path)).map(f => f.path)\n\n    if (derives.length) {\n      items.push({\n        text: 'Derives',\n        items: derives.flatMap(frontmatterToSidebarItem),\n      })\n    }\n\n    map[`/${path}`] = items\n  })\n\n  return map\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/components/AddonGallery.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { community, official } from '../../addons'\n\nconst props = defineProps({\n  collection: {\n    default: 'official',\n  },\n})\n\nconst addons = computed(() => props.collection === 'official' ? official : community)\n</script>\n\n<template>\n  <div class=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4\">\n    <AddonInfo v-for=\"addon of addons\" :key=\"addon.id\" :addon=\"addon\" />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/AddonInfo.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AddonInfo } from '../../addons'\n\ndefineProps<{\n  addon: AddonInfo\n}>()\n</script>\n\n<template>\n  <a :href=\"addon.link || addon.repo\" class=\"block flex flex-col !decoration-none !text-unset !cursor-unset !hover:bg-gray-400/10 p-2 rounded-lg transition-all\">\n    <div class=\"flex flex-wrap\">\n      <a :href=\"addon.link || addon.repo\" class=\"font-bold text-lg !text-$vp-c-text-1 !decoration-none\">\n        {{ addon.name }}\n      </a>\n      <div class=\"flex-grow\" />\n      <div class=\"mb-1 select-none op-80\">\n        <span v-for=\"tag in addon.tags\" :key=\"tag\" class=\"text-xs mx-.5 px-1.5 py-.5 rounded-lg bg-gray-400/40\">\n          {{ tag }}\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"flex-grow text-current text-xs opacity-90\"\n      :title=\"addon.description\"\n    >\n      {{ addon.description }}\n    </div>\n    <div class=\"mt-2 flex\">\n      <a\n        v-if=\"addon.author.link\"\n        :href=\"addon.author.link\"\n        class=\"text-current text-sm opacity-60 hover:opacity-100\"\n        target=\"_blank\"\n      >{{ addon.author.name }}</a>\n      <div v-else class=\"text-current text-sm opacity-60 hover:opacity-100\">\n        {{ addon.author.name }}\n      </div>\n      <div class=\"flex-auto\" />\n      <a\n        v-if=\"addon.id\"\n        :href=\"`https://npmjs.com/package/${addon.id}`\"\n        class=\"ml-2 text-current opacity-40 text-sm hover:opacity-100\"\n        target=\"_blank\"\n      >\n        <simple-icons-npm />\n      </a>\n      <a\n        v-if=\"addon.repo\"\n        :href=\"addon.repo\"\n        class=\"ml-2 text-current opacity-40 text-sm hover:opacity-100\"\n        target=\"_blank\"\n      >\n        <simple-icons-github />\n      </a>\n    </div>\n  </a>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/Demo.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SlidevMarkdown } from '@slidev/types'\nimport Center from '@slidev/client/layouts/center.vue'\nimport Default from '@slidev/client/layouts/default.vue'\nimport { parseSync } from '@slidev/parser'\nimport Cover from '@slidev/theme-default/layouts/cover.vue'\nimport Markdown from 'markdown-it'\nimport TypeIt from 'typeit'\nimport { onMounted, ref, watch } from 'vue'\nimport DemoEditor from './DemoEditor.vue'\nimport DemoSlide from './DemoSlide.vue'\nimport SlideContainer from './SlideContainer.vue'\n\nimport '@slidev/client/styles/layouts-base.css'\nimport '@slidev/theme-default/styles/layouts.css'\n\nconst page = ref(0)\nconst paused = ref(false)\nconst completed = ref(false)\nconst code = ref('')\nconst info = ref<SlidevMarkdown>()\nconst block = ref<HTMLPreElement>()\n\nconst markdown = new Markdown()\n\nfunction getLayout(id: number) {\n  const name = info.value?.slides?.[id]?.frontmatter?.layout || 'default'\n  return name === 'cover'\n    ? Cover\n    : name === 'center'\n      ? Center\n      : Default\n}\n\nwatch([code, paused], () => {\n  if (paused.value)\n    return\n  try {\n    info.value = parseSync(code.value, '')\n  }\n  catch {\n  }\n})\n\nfunction getAttrs(id: number) {\n  return info.value?.slides?.[id]?.frontmatter\n}\n\nfunction getHTML(id: number) {\n  const content = info?.value?.slides?.[id]?.content\n  if (!content)\n    return ''\n  return markdown.render(content)\n}\n\nfunction pause() {\n  paused.value = true\n}\nfunction resume() {\n  paused.value = false\n}\n\nconst COVER_URL = 'https://sli.dev/demo-cover.png'\nif (typeof window !== 'undefined') {\n  const img1 = new Image()\n  img1.src = COVER_URL\n}\n\nfunction play() {\n  code.value = ''\n  block.value!.innerHTML = ''\n  completed.value = false\n  // @ts-expect-error wrong types provided by TypeIt\n  new TypeIt(block.value!, {\n    speed: 50,\n    startDelay: 900,\n    afterStep: () => {\n      // eslint-disable-next-line unicorn/prefer-dom-node-text-content\n      code.value = JSON.parse(JSON.stringify(block.value!.innerText.replace('|', '')))\n    },\n    afterComplete: () => {\n      setTimeout(() => completed.value = true, 300)\n    },\n  })\n    .type('<br><span class=\"token title\"># Welcome to Slidev!</span><br><br>', { delay: 400 })\n    .type('Presentation Slides for Developers', { delay: 400 })\n    .move(null, { to: 'START', speed: 0 })\n    .type('<br>')\n    .move(null, { to: 'START' })\n    .exec(pause)\n    .type('<span class=\"token punctuation\">---<br><br>---</span>')\n    .move(-4)\n    .type('<span class=\"token tag\">layout:</span> center')\n    .exec(resume)\n    .pause(1000)\n    .exec(pause)\n    .delete(6, { delay: 100, speed: 50 })\n    .type('cover')\n    .exec(resume)\n    .exec(pause)\n    .pause(1000)\n    .type('<br>')\n    .type('<span class=\"token tag\">background:</span> ', { delay: 200 })\n    .type(COVER_URL, { speed: 0 })\n    .exec(resume)\n    .pause(1000)\n    .move(null, { to: 'END', speed: 0 })\n    .exec(pause)\n    .type('<br><br><span class=\"token punctuation\">---</span><br><br>', { delay: 400 })\n    .exec(resume)\n    .exec(() => setTimeout(() => page.value = 1))\n    .type('<span class=\"token title\"># Page 2</span><br><br>', { delay: 400 })\n    .type('- 📄 Write slides in a single Markdown file<br>', { delay: 800 })\n    .type('- 🌈 Themes, code blocks, interactive components<br>', { delay: 800 })\n    .type('- 😎 Read the docs to learn more!', { delay: 800 })\n    .exec(() => setTimeout(() => page.value = 0))\n    .go()\n}\n\nonMounted(play)\n</script>\n\n<template>\n  <div>\n    <DemoEditor>\n      <div class=\"text-sm opacity-50 text-center\">\n        ./slides.md\n      </div>\n\n      <div v-if=\"completed\" class=\"absolute text-xs right-1 top-1 icon-btn opacity-50\" title=\"Replay\" @click=\"play()\">\n        <div class=\"i-carbon:reset\" />\n      </div>\n\n      <div class=\"language-md !bg-transparent px4 py1\">\n        <pre ref=\"block\" class=\"text-left whitespace-pre-wrap font-mono bg-transparent\" />\n      </div>\n    </DemoEditor>\n\n    <DemoSlide class=\"text-left\">\n      <div\n        class=\"flex h-full dark:bg-[#181819] transition-transform transform duration-500\"\n        style=\"width: 200%\"\n        :class=\"page === 1 ? '-translate-x-1/2' : ''\"\n      >\n        <SlideContainer class=\"w-full h-full\">\n          <component :is=\"getLayout(0)\" v-bind=\"getAttrs(0)\">\n            <div v-html=\"getHTML(0)\" />\n          </component>\n        </SlideContainer>\n        <SlideContainer class=\"w-full h-full\">\n          <component :is=\"getLayout(1)\" v-bind=\"getAttrs(1)\">\n            <div v-html=\"getHTML(1)\" />\n          </component>\n        </SlideContainer>\n      </div>\n      <div\n        class=\"absolute left-2 bottom-1 flex text-gray-200\"\n        opacity=\"0 hover:100\"\n      >\n        <div class=\"icon-btn\" :class=\"{ disabled: page === 0 }\" @click=\"page = 0\">\n          <div class=\"i-carbon:chevron-left\" />\n        </div>\n        <div class=\"icon-btn\" :class=\"{ disabled: page === 1 }\" @click=\"page = 1\">\n          <div class=\"i-carbon:chevron-right\" />\n        </div>\n      </div>\n    </DemoSlide>\n  </div>\n</template>\n\n<style>\n.slidev-layout ul {\n  padding: 0;\n}\n.slidev-layout li {\n  line-height: 2.4em;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/DemoEditor.vue",
    "content": "<template>\n  <div class=\"demo-editor\">\n    <slot />\n  </div>\n</template>\n\n<style lang=\"postcss\">\n.demo-editor {\n  font-size: 1em;\n  line-height: 1.2em;\n  min-height: 450px;\n  margin-top: 25px;\n  padding: 10px;\n  border-radius: 7px;\n  position: relative;\n  box-shadow:\n    0 0 0 1px rgba(0, 0, 0, 0.05),\n    0 0 30px 1px rgba(0, 0, 0, 0.15);\n}\n.dark .demo-editor {\n  @apply bg-[#161618] border border-gray-400 border-opacity-10;\n}\n.demo-editor::after {\n  content: '';\n  position: absolute;\n  top: 12px;\n  left: 10px;\n  width: 12px;\n  height: 12px;\n  background: #f95c5b;\n  border-radius: 100%;\n  box-shadow:\n    0 0 0 1px #da3d42,\n    22px 0 0 0 #fabe3b,\n    22px 0 0 1px #ecb03e,\n    44px 0 0 0 #38cd46,\n    44px 0 0 1px #2eae32;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/DemoSlide.vue",
    "content": "<template>\n  <div class=\"slide\">\n    <div class=\"aspect-16/9\" />\n    <div class=\"absolute top-0 left-0 right-0 bottom-0\">\n      <slot />\n    </div>\n  </div>\n</template>\n\n<style lang=\"postcss\">\n.slide {\n  background: var(--c-bg);\n  font-size: 1em;\n  line-height: 1.2em;\n  border-radius: 7px;\n  position: relative;\n  box-shadow:\n    0 0 0 1px rgba(0, 0, 0, 0.05),\n    0 0 30px 1px rgba(0, 0, 0, 0.15);\n  @apply mt-4 transform translate-x-20 -translate-y-20 overflow-hidden;\n}\n\n.dark .slide {\n  @apply bg-gray-400 bg-opacity-5 border border-gray-400 border-opacity-10;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/Environment.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ type: 'node' | 'client' | 'both' }>()\n</script>\n\n<template>\n  <details class=\"p4 mt-4 rounded-lg bg-gray-400 bg-opacity-10\">\n    <summary class=\"outline-none !m0 select-none\">\n      Environment:\n      <span class=\"capitalize font-bold\" :class=\"type === 'node' ? 'text-orange-400' : 'text-green-400'\">{{ type }}</span>\n    </summary>\n\n    <div class=\"pt2 opacity-75\">\n      <span v-if=\"type === 'both'\">\n        This setup function will run on <b>both</b> Node.js and client side. Avoid using Node.js or DOM API to prevent runtime errors.\n      </span>\n      <span v-else-if=\"type === 'node'\">\n        This setup function will only run on Node.js environment, you can have access to Node's API.\n      </span>\n      <span v-else-if=\"type === 'client'\">\n        This setup function will only run on client side. Make sure the browser compatibility when importing packages.\n      </span>\n    </div>\n  </details>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/FeatureTag.vue",
    "content": "<script setup lang=\"ts\">\nimport { useData, withBase } from 'vitepress'\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  tag: string\n  removable?: boolean\n  noclick?: boolean\n}>()\nconst emit = defineEmits(['remove'])\n\nconst { isDark } = useData()\n\nfunction getHashColorFromString(\n  name: string,\n  opacity: number | string = 1,\n) {\n  name += 'salt'\n  let hash = 0\n  for (let i = 0; i < name.length; i++)\n    hash = name.charCodeAt(i) + ((hash << 5) - hash)\n  const h = hash % 360\n  return getHsla(h, opacity)\n}\n\nfunction getHsla(\n  hue: number,\n  opacity: number | string = 1,\n) {\n  const saturation = isDark.value ? 50 : 65\n  const lightness = isDark.value ? 60 : 40\n  return `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`\n}\n\nconst formattedTag = computed(() => {\n  let tag = props.tag\n  if (tag === tag.toUpperCase()) {\n    return tag\n  }\n  tag = tag.replace(/\\b(API|CLI)\\b/gi, m => m.toUpperCase())\n    .replace(/-/g, ' ')\n  return tag[0].toUpperCase() + tag.slice(1)\n})\n\nconst colors = computed(() => {\n  return [\n    getHashColorFromString(props.tag),\n    getHashColorFromString(props.tag, 0.7),\n    getHashColorFromString(props.tag, 0.5),\n    getHashColorFromString(props.tag, 0.2),\n    getHashColorFromString(props.tag, 0.1),\n  ]\n})\n</script>\n\n<template>\n  <a v-if=\"props.removable\" class=\"feature-tag flex gap-1 items-center\">\n    {{ formattedTag }}\n    <button class=\"flex items-center op-70 hover:bg-gray-200/20 hover:op90 rounded-full mr--1\" @click=\"emit('remove')\">\n      <div class=\"i-carbon:close\" />\n    </button>\n  </a>\n  <span v-else-if=\"props.noclick\" class=\"feature-tag\">\n    {{ formattedTag }}\n  </span>\n  <a v-else class=\"feature-tag\" :href=\"withBase(`/features/#tags=${tag}`)\">\n    {{ formattedTag }}\n  </a>\n</template>\n\n<style scoped>\n.feature-tag {\n  --uno: 'text-sm px-2 py-.5 rounded-md select-none !decoration-none border border-solid h-max';\n  background-color: v-bind('colors[4]');\n  color: v-bind('colors[0]') !important;\n  border-color: v-bind('colors[3]');\n}\n\n.feature-tag:hover {\n  background-color: v-bind('colors[3]');\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/FeaturesAnimation.vue",
    "content": "<script setup lang=\"ts\">\nimport Inner from './FeaturesAnimationInner.vue'\n</script>\n\n<template>\n  <div class=\"outer\">\n    <Inner class=\"inner\" />\n  </div>\n</template>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/FeaturesAnimationInner.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Feature } from '../../../features/index.data'\nimport { withBase } from 'vitepress'\nimport { computed, ref } from 'vue'\nimport { data as features } from '../../../features/index.data'\n\nconst rows = 6\nconst offsetMap = Array.from({ length: rows }, () => Math.floor(Math.random() * 200))\nconst featuresArr = Object.values(features) as Feature[]\nconst groupedFeatures = computed(() => {\n  const res: Feature[][] = Array.from({ length: rows }, () => [])\n  for (let i = 0; i < featuresArr.length; i++) {\n    res[i % rows].push(featuresArr[i])\n  }\n  return res\n})\n\nconst round = ref(0)\n</script>\n\n<template>\n  <div class=\"relative w-full x-6 overflow-auto\">\n    <div class=\"flex flex-col gap-4 ml--50\">\n      <div v-for=\"group, i in groupedFeatures\" :key=\"i\" class=\"flex gap-4\">\n        <div :style=\"{ minWidth: `${offsetMap[i]}px` }\" />\n        <template v-for=\"r in [round, round + 1, round + 2]\" :key=\"r\">\n          <a\n            v-for=\"feature, j in group\" :key=\"j\" :href=\"withBase(feature.link)\"\n            class=\"px-3 py-2 rounded bg-$vp-c-bg-elv min-w-max !decoration-none\"\n          >\n            {{ feature.title }}\n          </a>\n        </template>\n      </div>\n    </div>\n    <div class=\"absolute left-0 top-0 bottom-0 w-10 backdrop-blur\" />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/FeaturesOverview.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Feature } from '../../../features/index.data'\nimport { withBase } from 'vitepress'\n\ndefineProps<{\n  features: Feature[]\n}>()\n</script>\n\n<template>\n  <div class=\"features-grid mt-4\">\n    <a\n      v-for=\"feature in features\"\n      :key=\"feature.name\"\n      flex flex-col h-full p4 gap-3 rounded-md\n      :href=\"withBase(feature.link)\"\n    >\n      <div font-bold text-wrap leading-5> {{ feature.title }} </div>\n      <div text-wrap leading-5 op-75 overflow-hidden text-sm> {{ feature.description }} </div>\n      <div flex-grow />\n      <div flex gap-1 pointer-events-auto>\n        <FeatureTag v-for=\"tag in feature.tags\" :key=\"tag\" :tag />\n      </div>\n    </a>\n  </div>\n</template>\n\n<style scoped>\n.features-grid {\n  display: grid;\n  grid-gap: 0.75rem;\n  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n}\n\n.features-grid > a {\n  color: var(--vp-c-text-1);\n  text-decoration: none;\n  transition:\n    background-color 0.25s,\n    color 0.25s;\n  background-color: var(--vp-c-bg-soft);\n}\n\n.features-grid > a:hover {\n  color: var(--vp-c-brand-1);\n  background-color: var(--vp-c-default-soft);\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/LandingPage.vue",
    "content": "<template>\n  <div class=\"xl:grid xl:grid-cols-[3fr_4fr] gap-4\" min-h-80vh px8 xl:px20 py5 max-w-100vw xl:max-w-450 mxa>\n    <div flex=\"~ col items-center justify-center\" min-h-150>\n      <h1 hidden>\n        Slidev\n      </h1>\n      <img src=\"/logo-title.png\" alt=\"Slidev\" w-80 xl:w-100 xl:mt--35>\n      <h2 text-3xl mt--5 op80 text-center>\n        Presentation Slides for Developers\n      </h2>\n      <div flex=\"~ gap-3 justify-center\" p4 mt-5>\n        <a href=\"/guide/\" class=\"bg-$vp-c-brand-3 text-white! no-underline! px5 py3 text-xl font-bold rounded-xl hover:bg-$vp-c-brand-1 h-max\">Get Started</a>\n        <a href=\"/guide/why\" class=\"bg-$vp-c-gray-1 text-white! no-underline! px5 py3 text-xl font-bold rounded-2xl hover:bg-$vp-c-brand-1 h-max\">Why</a>\n      </div>\n    </div>\n    <div flex>\n      <div w-180 ma>\n        <ClientOnly>\n          <Demo />\n        </ClientOnly>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/Layout.vue",
    "content": "<script setup lang=\"ts\">\nimport { Dropdown } from 'floating-vue'\nimport { useRoute } from 'vitepress'\nimport VPMenuLink from 'vitepress/dist/client/theme-default/components/VPMenuLink.vue'\nimport DefaultTheme from 'vitepress/theme'\nimport FeatureTag from './FeatureTag.vue'\n\nconst { Layout } = DefaultTheme\nconst route = useRoute()\n</script>\n\n<template>\n  <Layout>\n    <template #nav-bar-title-after>\n      <a @click.prevent>\n        <Dropdown :triggers=\"['hover', 'click']\" :popper-triggers=\"['hover']\" theme=\"twoslash\" popper-class=\"z-1000\">\n          <Badge class=\"scale-80 translate-x--1 select-none\">\n            New Docs!\n          </Badge>\n          <template #popper>\n            <div class=\"p3 text-sm\">\n              <p>\n                You are viewing the new Slidev documentation.\n              </p>\n              <p>\n                The old one is available\n                <a href=\"https://docs-legacy.sli.dev/\" class=\"underline text-primary\" target=\"_blank\">here</a>.\n              </p>\n            </div>\n          </template>\n        </Dropdown>\n      </a>\n    </template>\n    <template #sidebar-nav-after>\n      <div flex=\"~ col gap-2\">\n        <div v-if=\"route.data?.frontmatter?.tags\" class=\"bg-$vp-c-bg-soft p4 rounded-lg\" flex=\"~ col gap-2\">\n          <div font-bold text-sm op75>\n            Tags\n          </div>\n          <div flex=\"~ wrap gap-2\">\n            <FeatureTag v-for=\"tag in route.data.frontmatter.tags\" :key=\"tag\" :tag=\"tag\" />\n          </div>\n        </div>\n        <div v-if=\"route.data?.frontmatter?.since\" class=\"bg-$vp-c-bg-soft px2 pb2 rounded-lg\" flex=\"~ col gap-1\">\n          <div font-bold text-sm op75 px2 pt4>\n            Since\n          </div>\n          <VPMenuLink\n            :item=\"{\n              text: route.data.frontmatter.since,\n              link: `https://github.com/slidevjs/slidev/releases/tag/${route.data.frontmatter.since}`,\n            }\"\n          />\n        </div>\n      </div>\n    </template>\n  </Layout>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/LinkCard.vue",
    "content": "<script setup lang=\"ts\">\nimport { withBase } from 'vitepress'\nimport { computed } from 'vue'\nimport { resolveLink } from '../../utils'\n\nconst props = defineProps<{\n  link: string\n}>()\n\nconst resolved = computed(() => resolveLink(props.link))\n</script>\n\n<template>\n  <div class=\"sr-only\">\n    {{ resolved.title }}\n  </div>\n  <ClientOnly>\n    <a class=\"link-card\" :href=\"withBase(resolved.url)\">\n      <div class=\"title\">\n        <div>{{ resolved.title }}</div>\n        <div flex-grow />\n        <div flex gap-1>\n          <FeatureTag v-for=\"tag in resolved.tags\" :key=\"tag\" :tag />\n        </div>\n      </div>\n      <div class=\"description\">\n        {{ resolved.descripton }}\n      </div>\n    </a>\n  </ClientOnly>\n</template>\n\n<style scoped>\n.link-card {\n  --uno: 'block my-4 pl-8 pr-6 py-6 rounded-lg bg-$vp-c-bg-soft flex flex-col gap-2';\n  border: 2px solid var(--vp-c-bg-soft);\n  transition:\n    color 0.5s,\n    background-color 0.5s;\n  text-decoration: none;\n}\n\n.link-card:hover {\n  border-color: var(--vp-c-brand-soft);\n  transition: border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);\n}\n\n.title {\n  font-size: 18px;\n  line-height: 1.4;\n  letter-spacing: -0.02em;\n  display: flex;\n  color: var(--vp-c-brand-1);\n}\n\n.description {\n  margin-bottom: 0;\n  color: var(--vp-c-text-2);\n  transition: color 0.5s;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/LinkInline.vue",
    "content": "<script setup lang=\"ts\">\nimport { Tooltip } from 'floating-vue'\nimport { withBase } from 'vitepress'\nimport { computed } from 'vue'\nimport { resolveLink } from '../../utils'\n\nconst props = defineProps<{\n  link: string\n}>()\n\nconst resolved = computed(() => resolveLink(props.link))\n</script>\n\n<template>\n  <span class=\"sr-only\">{{ resolved.title }}</span>\n  <ClientOnly>\n    <Tooltip class=\"inline-block\" theme=\"twoslash\">\n      <a\n        :href=\"withBase(resolved.url)\"\n        target=\"_blank\"\n        class=\"bg-$vp-c-default-soft hover:bg-$vp-c-brand-soft rounded px2 py1 !decoration-none\"\n      >\n        {{ resolved.title }}\n      </a>\n\n      <template #popper>\n        <a\n          :href=\"withBase(resolved.url)\"\n          target=\"_blank\"\n          flex flex-col p4 gap-2 max-w-100\n        >\n          <div flex gap-2>\n            <div>\n              {{ resolved.title }}\n            </div>\n            <div flex-grow />\n            <FeatureTag v-for=\"tag in resolved.tags\" :key=\"tag\" :tag :noclick=\"resolved.kind !== 'features'\" text-xs />\n          </div>\n          <div op75 text-sm>\n            {{ resolved.descripton }}\n          </div>\n        </a>\n      </template>\n    </Tooltip>\n  </ClientOnly>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/SeeAlso.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  links: string[]\n}>()\n</script>\n\n<template>\n  <div class=\"op-90 mb--1\">\n    See also:\n  </div>\n  <LinkCard v-for=\"link in links\" :key=\"link\" :link=\"link\" />\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/ShowCaseInfo.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ShowCaseInfo } from '../../showcases'\n\ndefineProps<{\n  info: ShowCaseInfo\n}>()\n</script>\n\n<template>\n  <div class=\"flex flex-col gap-2\">\n    <div class=\"block w-full relative aspect-16/9\">\n      <a\n        class=\"absolute top-0 bottom-0 left-0 right-0 overflow-hidden\"\n        border=\"~ rounded gray-400 opacity-20\"\n        :href=\"info.slidesLink\"\n        target=\"_blank\"\n      >\n        <img :src=\"info.cover\">\n      </a>\n    </div>\n    <div class=\"font-bold line-clamp-2\">\n      {{ info.title }}\n    </div>\n    <div class=\"text-current text-xs opacity-75 whitespace-nowrap overflow-hidden overflow-ellipsis\">\n      {{ info.at }}\n    </div>\n    <div class=\"flex-auto\" />\n    <div class=\"flex\">\n      <a\n        v-if=\"info.author.link\"\n        :href=\"info.author.link\"\n        class=\"text-current! text-sm opacity-50 whitespace-nowrap overflow-hidden overflow-ellipsis\"\n        target=\"_blank\"\n      >{{ info.author.name }}</a>\n      <div v-else class=\"text-current! text-sm opacity-50 whitespace-nowrap overflow-hidden overflow-ellipsis\">\n        {{ info.author.name }}\n      </div>\n      <div class=\"flex-auto\" />\n      <a\n        v-if=\"info.videoLink\"\n        :href=\"info.videoLink\"\n        class=\"ml-2 text-current! opacity-20 hover:opacity-100 hover:text-[#cb3837]\"\n        target=\"_blank\"\n      >\n        <div class=\"i-carbon:video\" />\n      </a>\n      <a\n        v-if=\"info.slidesLink\"\n        :href=\"info.slidesLink\"\n        class=\"ml-2 text-current! opacity-20 hover:opacity-100 hover:text-[#cb3837]\"\n        target=\"_blank\"\n      >\n        <div class=\"i-carbon:presentation-file\" />\n      </a>\n      <a\n        v-if=\"info.sourceLink\"\n        :href=\"info.sourceLink\"\n        class=\"ml-2 text-current! opacity-20 hover:opacity-100\"\n        target=\"_blank\"\n      >\n        <div class=\"i-carbon:logo-github\" />\n      </a>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/ShowCases.vue",
    "content": "<script setup lang=\"ts\">\nimport { showcases } from '../../showcases'\n</script>\n\n<template>\n  <div class=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4\">\n    <ShowCaseInfo v-for=\"(info, idx) of showcases\" :key=\"idx\" :info=\"info\" />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/SlideContainer.vue",
    "content": "<script setup lang=\"ts\">\nimport { useElementSize } from '@vueuse/core'\nimport { computed, ref } from 'vue'\n\nconst slideAspect = 16 / 9\nconst slideWidth = 980\nconst slideHeight = 980 * 9 / 16\n\nconst root = ref<HTMLDivElement>()\nconst element = useElementSize(root)\n\nconst width = computed(() => element.width.value)\nconst height = computed(() => element.height.value)\n\nconst screenAspect = computed(() => width.value / height.value)\n\nconst scale = computed(() => {\n  if (screenAspect.value < slideAspect)\n    return width.value / slideWidth\n  return height.value * slideAspect / slideWidth\n})\n\nconst style = computed(() => ({\n  height: `${slideHeight}px`,\n  width: `${slideWidth}px`,\n  transform: `translate(-50%, -50%) scale(${scale.value})`,\n}))\n</script>\n\n<template>\n  <div ref=\"root\" class=\"slide-container\">\n    <div class=\"slide-content\" :style=\"style\">\n      <slot />\n    </div>\n    <slot name=\"controls\" />\n  </div>\n</template>\n\n<style lang=\"postcss\">\n.slide-container {\n  @apply relative overflow-hidden;\n}\n\n.slide-content {\n  @apply relative overflow-hidden bg-main absolute left-1/2 top-1/2;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/TheTweet.vue",
    "content": "<!--\nA simple wrapper for embedded Tweet\n\nUsage:\n\n<TheTweet id=\"20\" />\n-->\n\n<script setup lang=\"ts\">\nimport { isClient, useScriptTag } from '@vueuse/core'\nimport { getCurrentInstance, onMounted, ref } from 'vue'\nimport { isDark } from '../composables/dark'\n\nconst props = defineProps<{\n  id: string | number\n  scale?: string | number\n  conversation?: string\n}>()\n\nconst tweet = ref<HTMLElement | null>()\n\nconst vm = getCurrentInstance()!\nconst loaded = ref(false)\n\nasync function create() {\n  // @ts-expect-error Global variable\n  if (!window.twttr?.widgets?.createTweet)\n    return\n  // @ts-expect-error Global variable\n  await window.twttr.widgets.createTweet(\n    props.id.toString(),\n    tweet.value,\n    {\n      theme: isDark.value ? 'dark' : 'light',\n      conversation: props.conversation || 'none',\n    },\n  )\n  loaded.value = true\n}\n\nif (isClient) {\n  // @ts-expect-error Global variable\n  if (window?.twttr?.widgets?.createTweet) {\n    onMounted(create)\n  }\n  else {\n    useScriptTag(\n      'https://platform.twitter.com/widgets.js',\n      () => {\n        if (vm.isMounted)\n          create()\n        else\n          onMounted(create, vm)\n      },\n      { async: true },\n    )\n  }\n}\n</script>\n\n<template>\n  <div class=\"w-full flex\">\n    <div ref=\"tweet\" class=\"mx-auto w-140\">\n      <div v-if=\"!loaded\" class=\"w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50\">\n        <div class=\"m-auto animate-pulse text-4xl\">\n          <div class=\"i-carbon:logo-twitter\" />\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/ThemeGallery.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { community, official } from '../../themes'\n\nconst props = defineProps({\n  collection: {\n    default: 'official',\n  },\n})\n\nconst themes = computed(() => props.collection === 'official' ? official : community)\n</script>\n\n<template>\n  <div class=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4\">\n    <ThemeInfo v-for=\"theme of themes\" :key=\"theme.id\" :theme=\"theme\" />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/ThemeInfo.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ThemeInfo } from '../../themes'\nimport { isClient, useIntervalFn } from '@vueuse/core'\nimport { ref } from 'vue'\n\nconst props = defineProps<{\n  theme: ThemeInfo\n}>()\n\nconst index = ref(0)\n\nif (props.theme.previews.length > 1 && isClient) {\n  const { resume } = useIntervalFn(() => {\n    index.value = (index.value + 1) % props.theme.previews.length\n  }, 3000, { immediate: false })\n  // add random defer so they don't starts together\n  setTimeout(resume, Math.round(1000 * Math.random()))\n}\n</script>\n\n<template>\n  <div>\n    <a\n      :href=\"theme.link || theme.repo\"\n      target=\"_blank\"\n      class=\"block mb-1.5 w-full overflow-hidden relative aspect-16/9 transition duration-300\"\n      border=\"~ rounded gray-400 opacity-20\"\n      hover=\"shadow-xl\"\n    >\n      <img\n        v-for=\"url, idx in theme.previews\"\n        :key=\"idx\"\n        :src=\"url\"\n        class=\"absolute top-0 bottom-0 left-0 right-0 transition-transform transform duration-500\"\n        :style=\"{ transform: idx > index ? 'scale(1.05) translate(110%)' : 'scale(1.05) translate(0)' }\"\n      >\n    </a>\n    <a :href=\"theme.link || theme.repo\" class=\"font-bold !text-$vp-c-text-1 !decoration-none\">\n      {{ theme.name }}\n    </a>\n    <div\n      class=\"text-current text-xs opacity-75 whitespace-nowrap overflow-hidden text-ellipsis\"\n      :title=\"theme.description\"\n    >\n      {{ theme.description }}\n    </div>\n    <div class=\"mt-2 flex\">\n      <a\n        v-if=\"theme.author.link\"\n        :href=\"theme.author.link\"\n        class=\"text-current text-sm opacity-60 hover:opacity-100\"\n        target=\"_blank\"\n      >{{ theme.author.name }}</a>\n      <div v-else class=\"text-current text-sm opacity-60 hover:opacity-100\">\n        {{ theme.author.name }}\n      </div>\n      <div class=\"flex-auto\" />\n      <a\n        v-if=\"theme.id\"\n        :href=\"`https://npmjs.com/package/${theme.id}`\"\n        class=\"ml-2 text-current opacity-40 text-sm hover:opacity-100\"\n        target=\"_blank\"\n      >\n        <simple-icons-npm />\n      </a>\n      <a\n        v-if=\"theme.repo\"\n        :href=\"theme.repo\"\n        class=\"ml-2 text-current opacity-40 text-sm hover:opacity-100\"\n        target=\"_blank\"\n      >\n        <simple-icons-github />\n      </a>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/theme/composables/dark.ts",
    "content": "import { useDark } from '@vueuse/core'\n\nexport const isDark = useDark()\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "import type { EnhanceAppContext } from 'vitepress'\nimport TwoSlash from '@shikijs/vitepress-twoslash/client'\nimport Theme from 'vitepress/theme'\nimport Layout from './components/Layout.vue'\n\nimport '@shikijs/vitepress-twoslash/style.css'\nimport './styles/vars.css'\nimport './styles/demo.css'\nimport './styles/custom.css'\nimport 'uno.css'\nimport 'virtual:group-icons.css'\n\nexport default {\n  extends: Theme,\n  enhanceApp({ app }: EnhanceAppContext) {\n    app.use(TwoSlash as any)\n  },\n  Layout,\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/custom.css",
    "content": ".icon-btn {\n  --uno: inline-block cursor-pointer select-none important-outline-none;\n  --uno: opacity-75 transition duration-200 ease-in-out align-middle rounded p-2;\n  --uno: hover-(opacity-100 bg-gray-400 bg-opacity-10);\n}\n\n.icon-btn.disabled {\n  --uno: opacity-25 pointer-events-none;\n}\n\n.inline-icon-btn {\n  --uno: text-primary-deep;\n  --uno: inline-block rounded p-0.5 text-2xl align-middle;\n  --uno: border border-primary border-opacity-20 border-solid;\n}\n\nkbd {\n  --uno: border-rounded bg-$vp-c-gray-1 bg-opacity-10 px-1 py-.5;\n}\n\n[data-tweet-id] {\n  border-radius: 13px;\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/demo.css",
    "content": "html:not(.dark) {\n  --prism-foreground: #393a34;\n  --prism-background: #fafafa;\n  --prism-inline-background: #f5f5f5;\n  --prism-comment: #a0ada0;\n  --prism-string: #b56959;\n  --prism-literal: #2f8a89;\n  --prism-number: #296aa3;\n  --prism-keyword: #1c6b48;\n  --prism-function: #6c7834;\n  --prism-boolean: #296aa3;\n  --prism-constant: #a65e2b;\n  --prism-deleted: #a14f55;\n  --prism-class: #2993a3;\n  --prism-builtin: #ab5959;\n  --prism-property: #b58451;\n  --prism-namespace: #b05a78;\n  --prism-punctuation: #8e8f8b;\n  --prism-decorator: #bd8f8f;\n  --prism-regex: #ab5e3f;\n  --prism-json-property: #698c96;\n}\n\nhtml.dark {\n  --prism-scheme: dark;\n  --prism-foreground: #d4cfbf;\n  --prism-background: #181818;\n  --prism-comment: #758575;\n  --prism-string: #d48372;\n  --prism-literal: #429988;\n  --prism-keyword: #4d9375;\n  --prism-boolean: #6394bf;\n  --prism-number: #6394bf;\n  --prism-variable: #c2b36e;\n  --prism-function: #a1b567;\n  --prism-deleted: #bc6066;\n  --prism-class: #54b1bf;\n  --prism-builtin: #e0a569;\n  --prism-property: #dd8e6e;\n  --prism-namespace: #db889a;\n  --prism-punctuation: #858585;\n  --prism-decorator: #bd8f8f;\n  --prism-regex: #ab5e3f;\n  --prism-json-property: #6b8b9e;\n  --prism-line-number: #888888;\n  --prism-line-number-gutter: #eeeeee;\n  --prism-line-highlight-background: #444444;\n  --prism-selection-background: #444444;\n  --prism-inline-background: theme('colors.dark.300');\n}\n\n.token.title {\n  color: var(--prism-keyword);\n}\n\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: var(--prism-comment);\n  font-style: var(--prism-comment-style);\n}\n\n.token.namespace {\n  color: var(--prism-namespace);\n}\n\n.token.interpolation {\n  color: var(--prism-interpolation);\n}\n\n.token.string {\n  color: var(--prism-string);\n}\n\n.token.punctuation {\n  color: var(--prism-punctuation);\n}\n\n.token.operator {\n  color: var(--prism-operator);\n}\n\n.token.keyword.module,\n.token.keyword.control-flow {\n  color: var(--prism-keyword-control);\n}\n\n.token.url,\n.token.symbol,\n.token.inserted {\n  color: var(--prism-symbol);\n}\n\n.token.constant {\n  color: var(--prism-constant);\n}\n\n.token.string.url {\n  text-decoration: var(--prism-url-decoration);\n}\n\n.token.boolean,\n.language-json .token.boolean {\n  color: var(--prism-boolean);\n}\n\n.token.number,\n.language-json .token.number {\n  color: var(--prism-number);\n}\n\n.token.variable {\n  color: var(--prism-variable);\n}\n\n.token.keyword {\n  color: var(--prism-keyword);\n}\n\n.token.atrule,\n.token.attr-value,\n.token.selector {\n  color: var(--prism-selector);\n}\n\n.token.function {\n  color: var(--prism-function);\n}\n\n.token.deleted {\n  color: var(--prism-deleted);\n}\n\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n\n.token.italic {\n  font-style: italic;\n}\n\n.token.class-name {\n  color: var(--prism-class);\n}\n\n.token.tag,\n.token.builtin {\n  color: var(--prism-builtin);\n}\n\n.token.attr-name,\n.token.property,\n.token.entity {\n  color: var(--prism-property);\n}\n\n.language-json .token.property {\n  color: var(--prism-json-property);\n}\n\n.token.regex {\n  color: var(--prism-regex);\n}\n\n.token.decorator,\n.token.annotation {\n  color: var(--prism-decorator);\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/vars.css",
    "content": "/**\n * Customize default theme styling by overriding CSS variables:\n * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css\n */\n\n/**\n * Colors\n *\n * Each colors have exact same color scale system with 3 levels of solid\n * colors with different brightness, and 1 soft color.\n *\n * - `XXX-1`: The most solid color used mainly for colored text. It must\n *   satisfy the contrast ratio against when used on top of `XXX-soft`.\n *\n * - `XXX-2`: The color used mainly for hover state of the button.\n *\n * - `XXX-3`: The color for solid background, such as bg color of the button.\n *   It must satisfy the contrast ratio with pure white (#ffffff) text on\n *   top of it.\n *\n * - `XXX-soft`: The color used for subtle background such as custom container\n *   or badges. It must satisfy the contrast ratio when putting `XXX-1` colors\n *   on top of it.\n *\n *   The soft color must be semi transparent alpha channel. This is crucial\n *   because it allows adding multiple \"soft\" colors on top of each other\n *   to create a accent, such as when having inline code block inside\n *   custom containers.\n *\n * - `default`: The color used purely for subtle indication without any\n *   special meanings attched to it such as bg color for menu hover state.\n *\n * - `brand`: Used for primary brand colors, such as link text, button with\n *   brand theme, etc.\n *\n * - `tip`: Used to indicate useful information. The default theme uses the\n *   brand color for this by default.\n *\n * - `warning`: Used to indicate warning to the users. Used in custom\n *   container, badges, etc.\n *\n * - `danger`: Used to show error, or dangerous message to the users. Used\n *   in custom container, badges, etc.\n * -------------------------------------------------------------------------- */\n\n:root {\n  --vp-c-brand-1: #3ab9d4;\n  --vp-c-brand-2: #60c4db;\n  --vp-c-brand-3: #6fcce1;\n  --vp-c-brand-soft: #3ab9d450;\n  --vp-c-bg-alt: #f9f9f9;\n\n  --vp-font-family-mono: theme('fontFamily.mono');\n}\n\n.dark {\n  --vp-c-brand-1: #6fcce1;\n  --vp-c-brand-2: #60c4db;\n  --vp-c-brand-3: #3ab9d4;\n  --vp-c-brand-soft: #3ab9d450;\n  --vp-c-bg-alt: #18181b;\n  --vp-c-gutter: #8883;\n}\n\n:root {\n  --vp-c-default-1: var(--vp-c-gray-1);\n  --vp-c-default-2: var(--vp-c-gray-2);\n  --vp-c-default-3: var(--vp-c-gray-3);\n  --vp-c-default-soft: var(--vp-c-gray-soft);\n\n  --vp-c-tip-1: var(--vp-c-brand-1);\n  --vp-c-tip-2: var(--vp-c-brand-2);\n  --vp-c-tip-3: var(--vp-c-brand-3);\n  --vp-c-tip-soft: var(--vp-c-brand-soft);\n}\n\n:root {\n  -vp-c-text-1: rgba(42, 40, 47);\n  -vp-c-text-2: rgba(42, 40, 47, 0.78);\n  -vp-c-text-3: rgba(42, 40, 47, 0.56);\n  --black-text-1: rgba(42, 40, 47);\n}\n\n.dark {\n  --vp-c-text-1: rgba(255, 255, 245, 0.86);\n  --vp-c-text-2: rgba(235, 235, 245, 0.6);\n  --vp-c-text-3: rgba(235, 235, 245, 0.38);\n}\n\n/**\n * Component: Button\n * -------------------------------------------------------------------------- */\n\n:root {\n  --vp-button-brand-border: transparent;\n  --vp-button-brand-text: var(--vp-c-white);\n  --vp-button-brand-bg: var(--vp-c-brand-1);\n  --vp-button-brand-hover-border: transparent;\n  --vp-button-brand-hover-text: var(--vp-c-white);\n  --vp-button-brand-hover-bg: var(--vp-c-brand-2);\n  --vp-button-brand-active-border: transparent;\n  --vp-button-brand-active-text: var(--vp-c-white);\n  --vp-button-brand-active-bg: var(--vp-c-brand-1);\n}\n\n.dark {\n  --vp-button-brand-text: var(--black-text-1);\n  --vp-button-brand-bg: var(--vp-c-brand-2);\n  --vp-button-brand-hover-text: var(--black-text-1);\n  --vp-button-brand-hover-bg: var(--vp-c-brand-1);\n  --vp-button-brand-active-text: var(--black-text-1);\n  --vp-button-brand-active-bg: var(--vp-c-brand-3);\n}\n\n/**\n * Component: Home\n * -------------------------------------------------------------------------- */\n\n:root {\n  --vp-home-hero-name-color: var(--vp-c-brand-1);\n}\n\n@media (min-width: 640px) {\n  :root {\n    --vp-home-hero-image-filter: blur(56px);\n  }\n}\n\n@media (min-width: 960px) {\n  :root {\n    --vp-home-hero-image-filter: blur(72px);\n  }\n}\n\n/**\n * Component: Custom Block\n * -------------------------------------------------------------------------- */\n\n:root {\n  --vp-custom-block-tip-border: transparent;\n  --vp-custom-block-tip-text: var(--vp-c-text-1);\n  --vp-custom-block-tip-bg: var(--vp-c-brand-soft);\n  --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);\n}\n\n/**\n * Component: Algolia\n * -------------------------------------------------------------------------- */\n\n.DocSearch {\n  --docsearch-primary-color: var(--vp-c-brand-1) !important;\n}\n"
  },
  {
    "path": "docs/.vitepress/themes.ts",
    "content": "export interface ThemeInfo {\n  id: string\n  name: string\n  description: string\n  previews: string[]\n  repo?: string\n  author: {\n    name: string\n    link?: string\n  }\n  link?: string\n  tags?: string[]\n}\n\nexport const official: ThemeInfo[] = [\n  {\n    id: '@slidev/theme-default',\n    name: 'Default',\n    description: 'The minimalism default theme for Slidev',\n    author: {\n      name: 'Anthony Fu',\n      link: 'https://github.com/antfu',\n    },\n    repo: 'https://github.com/slidevjs/themes/tree/main/packages/theme-default',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-default/01.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-default/02.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-default/06.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-default/08.png',\n    ],\n    tags: [\n      'official',\n      'minimalism',\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: '@slidev/theme-seriph',\n    name: 'Seriph',\n    description: 'A more formal looking theme using Serif fonts',\n    author: {\n      name: 'Anthony Fu',\n      link: 'https://github.com/antfu',\n    },\n    repo: 'https://github.com/slidevjs/themes/tree/main/packages/theme-seriph',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-seriph/01.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-seriph/02.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-seriph/03.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-seriph/08.png',\n    ],\n    tags: [\n      'official',\n      'minimalism',\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: '@slidev/theme-apple-basic',\n    name: 'Apple Basic',\n    description: 'Inspired by the Basic Black/White theme from Apple Keynote',\n    author: {\n      name: 'Jeremy Meissner',\n      link: 'https://github.com/JeremyMeissner',\n    },\n    repo: 'https://github.com/slidevjs/themes/tree/main/packages/theme-apple-basic',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-apple-basic/01.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-apple-basic/02.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-apple-basic/03.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-apple-basic/09.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-apple-basic/11.png',\n    ],\n    tags: [\n      'minimalism',\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: '@slidev/theme-bricks',\n    name: 'Bricks',\n    description: 'Building bricks',\n    author: {\n      name: 'iiiiiiinès',\n      link: 'https://github.com/iiiiiiines',\n    },\n    repo: 'https://github.com/slidevjs/themes/tree/main/packages/theme-bricks',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-bricks/01.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-bricks/04.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-bricks/06.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-bricks/05.png',\n    ],\n    tags: [\n      'light',\n    ],\n  },\n  {\n    id: '@slidev/theme-shibainu',\n    name: 'Shibainu',\n    description: 'Meow!',\n    author: {\n      name: 'iiiiiiinès',\n      link: 'https://github.com/iiiiiiines',\n    },\n    repo: 'https://github.com/slidevjs/themes/tree/main/packages/theme-shibainu',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-shibainu/01.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-shibainu/03.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-shibainu/04.png',\n      'https://cdn.jsdelivr.net/gh/slidevjs/themes@main/screenshots/theme-shibainu/09.png',\n    ],\n    tags: [\n      'dark',\n    ],\n  },\n]\n\nexport const community: ThemeInfo[] = [\n  {\n    id: 'slidev-theme-geist',\n    name: 'Vercel',\n    description: 'A theme based on Vercel\\'s design system.',\n    author: {\n      name: 'Nico Bachner',\n      link: 'https://github.com/nico-bachner',\n    },\n    repo: 'https://github.com/nico-bachner/slidev-theme-geist',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/nico-bachner/slidev-theme-geist@main/example-export/01.png',\n      'https://cdn.jsdelivr.net/gh/nico-bachner/slidev-theme-geist@main/example-export/02.png',\n      'https://cdn.jsdelivr.net/gh/nico-bachner/slidev-theme-geist@main/example-export/03.png',\n      'https://cdn.jsdelivr.net/gh/nico-bachner/slidev-theme-geist@main/example-export/04.png',\n      'https://cdn.jsdelivr.net/gh/nico-bachner/slidev-theme-geist@main/example-export/05.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-light-icons',\n    name: 'Light Icons',\n    description: 'A simple, light and elegant theme for Slidev, combined together with creative layouts, custom components & fonts',\n    author: {\n      name: 'Pulkit Aggarwal',\n      link: 'https://github.com/BashCloud',\n    },\n    repo: 'https://github.com/lightvue/slidev-theme-light-icons',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/1-layout-intro.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/2-layout-image-header-intro-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/3-layout-dynamic-image-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/5-layout-dynamic-image-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/7-layout-dynamic-image-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/8-layout-center-image-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/9-layout-dynamic-image-light.png',\n      'https://cdn.jsdelivr.net/gh/lightvue/slidev-theme-light-icons@master/screenshot/10-layout-left-image-light.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-eloc',\n    name: 'Eloc',\n    description: 'Focus on writing, present in a concise style.',\n    author: {\n      name: 'Amio',\n      link: 'https://github.com/amio',\n    },\n    repo: 'https://github.com/zthxxx/slides/tree/master/packages/slidev-theme-eloc',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/zthxxx/slides@master/packages/slidev-theme-eloc/screenshot/01.png',\n      'https://cdn.jsdelivr.net/gh/zthxxx/slides@master/packages/slidev-theme-eloc/screenshot/02.png',\n      'https://cdn.jsdelivr.net/gh/zthxxx/slides@master/packages/slidev-theme-eloc/screenshot/03.png',\n      'https://cdn.jsdelivr.net/gh/zthxxx/slides@master/packages/slidev-theme-eloc/screenshot/04.png',\n      'https://cdn.jsdelivr.net/gh/zthxxx/slides@master/packages/slidev-theme-eloc/screenshot/05.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-purplin',\n    name: 'Purplin',\n    description: 'Theme with bar bottom component. Based on purple color',\n    author: {\n      name: 'Mauricio Martínez',\n      link: 'https://github.com/moudev',\n    },\n    repo: 'https://github.com/moudev/slidev-theme-purplin',\n    previews: [\n      'https://i.imgur.com/BX3TpEc.png',\n      'https://i.imgur.com/mqqRi1F.png',\n      'https://i.imgur.com/fwm2785.png',\n      'https://i.imgur.com/m8eemKt.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-unicorn',\n    name: 'Unicorn',\n    description: 'Based on Dawntraoz website design',\n    author: {\n      name: 'Alba Silvente',\n      link: 'https://github.com/dawntraoz',\n    },\n    repo: 'https://github.com/dawntraoz/slidev-theme-unicorn',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/Dawntraoz/slidev-theme-unicorn@master/screenshots/dark-theme-intro.png',\n      'https://cdn.jsdelivr.net/gh/Dawntraoz/slidev-theme-unicorn@master/screenshots/light-theme-cover.png',\n      'https://cdn.jsdelivr.net/gh/Dawntraoz/slidev-theme-unicorn@master/screenshots/dark-theme-image-centered.png',\n      'https://cdn.jsdelivr.net/gh/Dawntraoz/slidev-theme-unicorn@master/screenshots/dark-theme-center-without-header-footer.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-zhozhoba',\n    name: 'Zhozhoba',\n    description: 'A zhozhoba theme for Slidev',\n    author: {\n      name: 'Bogenbai Bayzharassov',\n      link: 'https://github.com/thatoranzhevyy',\n    },\n    repo: 'https://github.com/thatoranzhevyy/slidev-theme-zhozhoba',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/thatoranzhevyy/slidev-theme-zhozhoba@master/slides-export/01.png',\n      'https://cdn.jsdelivr.net/gh/thatoranzhevyy/slidev-theme-zhozhoba@master/.github/dark.png',\n      'https://cdn.jsdelivr.net/gh/thatoranzhevyy/slidev-theme-zhozhoba@master/slides-export/02.png',\n      'https://cdn.jsdelivr.net/gh/thatoranzhevyy/slidev-theme-zhozhoba@master/slides-export/03.png',\n      'https://cdn.jsdelivr.net/gh/thatoranzhevyy/slidev-theme-zhozhoba@master/slides-export/04.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-penguin',\n    name: 'Penguin',\n    description: 'A Penguin theme for Slidev',\n    author: {\n      name: 'Alvaro Saburido',\n      link: 'https://github.com/alvarosabu',\n    },\n    repo: 'https://github.com/alvarosabu/slidev-theme-penguin',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/alvarosaburido/slidev-theme-penguin@master/screenshots/dark/01.png',\n      'https://cdn.jsdelivr.net/gh/alvarosaburido/slidev-theme-penguin@master/screenshots/light/02.png',\n      'https://cdn.jsdelivr.net/gh/alvarosaburido/slidev-theme-penguin@master/screenshots/light/06.png',\n      'https://cdn.jsdelivr.net/gh/alvarosaburido/slidev-theme-penguin@master/screenshots/light/05.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-vuetiful',\n    name: 'Vuetiful',\n    description: 'A Vue-inspired theme for Slidev',\n    author: {\n      name: 'Thorsten Lünborg',\n      link: 'https://github.com/LinusBorg',\n    },\n    repo: 'https://github.com/LinusBorg/slidev-theme-vuetiful',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/LinusBorg/slidev-theme-vuetiful@main/screenshots/cover-alt.png',\n      'https://cdn.jsdelivr.net/gh/LinusBorg/slidev-theme-vuetiful@main/screenshots/section.png',\n      'https://cdn.jsdelivr.net/gh/LinusBorg/slidev-theme-vuetiful@main/screenshots/big-points.png',\n      'https://cdn.jsdelivr.net/gh/LinusBorg/slidev-theme-vuetiful@main/screenshots/quote.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-takahashi',\n    name: 'Takahashi',\n    description: 'A simple theme for Slidev',\n    author: {\n      name: 'Percy M.',\n      link: 'https://github.com/kecrily',\n    },\n    repo: 'https://github.com/kecrily/slidev-theme-takahashi',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/01.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/02.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/03.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/04.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/05.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/06.png',\n      'https://cdn.jsdelivr.net/gh/kecrily/slidev-theme-takahashi@master/screenshots/07.png',\n    ],\n    tags: [\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-academic',\n    name: 'Academic',\n    description: 'Academic presentations with Slidev made simple',\n    author: {\n      name: 'Alexander Eble',\n      link: 'https://github.com/alexanderdavide',\n    },\n    repo: 'https://github.com/alexanderdavide/slidev-theme-academic',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/01.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/02.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/08.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/04.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/05.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/06.png',\n      'https://cdn.jsdelivr.net/gh/alexanderdavide/slidev-theme-academic@assets/example-export/07.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-mokkapps',\n    name: 'Mokkapps',\n    description: 'A theme for my personal brand \"Mokkapps\"',\n    author: {\n      name: 'Michael Hoffmann',\n      link: 'https://github.com/mokkapps',\n    },\n    repo: 'https://github.com/mokkapps/slidev-theme-mokkapps',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/001.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/002.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/003.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/004.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/005.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/006.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/007.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/008.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/009.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/010.png',\n      'https://cdn.jsdelivr.net/gh/mokkapps/slidev-theme-mokkapps@master/screenshots/dark/011.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-the-unnamed',\n    name: 'The unnamed',\n    description: 'A theme based on The unnamed VS Code theme',\n    author: {\n      name: 'Elio Struyf',\n      link: 'https://elio.dev',\n    },\n    repo: 'https://github.com/estruyf/slidev-theme-the-unnamed',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/estruyf/slidev-theme-the-unnamed@main/assets/cover.png',\n      'https://cdn.jsdelivr.net/gh/estruyf/slidev-theme-the-unnamed@main/assets/about-me.png',\n      'https://cdn.jsdelivr.net/gh/estruyf/slidev-theme-the-unnamed@main/assets/default.png',\n      'https://cdn.jsdelivr.net/gh/estruyf/slidev-theme-the-unnamed@main/assets/section.png',\n    ],\n    tags: [\n      'dark',\n    ],\n  },\n  {\n    id: 'slidev-theme-dracula',\n    name: 'Dracula',\n    description: 'One the best dark theme meets slidev',\n    author: {\n      name: 'JD Solanki',\n      link: 'https://github.com/jd-solanki',\n    },\n    repo: 'https://github.com/jd-solanki/slidev-theme-dracula',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/jd-solanki/slidev-theme-dracula/screenshots/screenshot-1.png',\n      'https://cdn.jsdelivr.net/gh/jd-solanki/slidev-theme-dracula/screenshots/screenshot-2.png',\n      'https://cdn.jsdelivr.net/gh/jd-solanki/slidev-theme-dracula/screenshots/screenshot-3.png',\n      'https://cdn.jsdelivr.net/gh/jd-solanki/slidev-theme-dracula/screenshots/screenshot-4.png',\n      'https://cdn.jsdelivr.net/gh/jd-solanki/slidev-theme-dracula/screenshots/screenshot-5.png',\n    ],\n    tags: [\n      'dark',\n      'minimalism',\n    ],\n  },\n  {\n    id: 'slidev-theme-frankfurt',\n    name: 'Frankfurt',\n    description: 'Inspired by the Beamer theme Frankfurt',\n    author: {\n      name: 'Mu-Tsun Tsai',\n      link: 'https://github.com/MuTsunTsai',\n    },\n    repo: 'https://github.com/MuTsunTsai/slidev-theme-frankfurt',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/MuTsunTsai/slidev-theme-frankfurt/screenshots/01.png',\n      'https://cdn.jsdelivr.net/gh/MuTsunTsai/slidev-theme-frankfurt/screenshots/04.png',\n      'https://cdn.jsdelivr.net/gh/MuTsunTsai/slidev-theme-frankfurt/screenshots/06.png',\n      'https://cdn.jsdelivr.net/gh/MuTsunTsai/slidev-theme-frankfurt/screenshots/07.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-hep',\n    name: 'HEP',\n    description: 'Academic style for High Energy Physics',\n    author: {\n      name: 'Yulei ZHANG',\n      link: 'https://github.com/AvencastF',\n    },\n    repo: 'https://github.com/AvencastF/slidev-theme-hep',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/avencastf/slidev-theme-hep/screenshot/001.png',\n      'https://cdn.jsdelivr.net/gh/avencastf/slidev-theme-hep/screenshot/004.png',\n      'https://cdn.jsdelivr.net/gh/avencastf/slidev-theme-hep/screenshot/006.png',\n      'https://cdn.jsdelivr.net/gh/avencastf/slidev-theme-hep/screenshot/008.png',\n    ],\n    tags: [\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-excali-slide',\n    name: 'Excali-slide',\n    description: 'A theme based on Excalidraw with animated highlighter effect',\n    author: {\n      name: 'Filip Hric',\n      link: 'https://github.com/filiphric',\n    },\n    repo: 'https://github.com/filiphric/slidev-theme-excali-slide',\n    previews: [\n      'https://raw.githubusercontent.com/filiphric/excali-slide/main/images/default_slide.png',\n      'https://raw.githubusercontent.com/filiphric/excali-slide/main/images/intro_slide.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n    ],\n  },\n  {\n    id: 'slidev-theme-mint',\n    name: 'mint',\n    description: 'Slidev Theme Mint',\n    author: {\n      name: 'Alfatta Rezqa',\n      link: 'https://github.com/alfatta',\n    },\n    repo: 'https://github.com/alfatta/slidev-theme-mint',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/1.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/2.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/3.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/4.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/5.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/6.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/7.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/8.png',\n      'https://cdn.jsdelivr.net/gh/alfatta/slidev-theme-mint/screenshot/9.png',\n    ],\n    tags: [\n      'light',\n      'mint',\n      'green',\n      'cool',\n    ],\n  },\n  {\n    id: 'slidev-theme-neversink',\n    name: 'neversink',\n    description: 'Slidev Theme Neversink',\n    author: {\n      name: 'Todd M. Gureckis',\n      link: 'https://github.com/gureckis',\n    },\n    repo: 'https://github.com/gureckis/slidev-theme-neversink',\n    previews: [\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/2.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/6.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/8.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/15.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/18.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/22.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/26.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/34.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/36.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/38.png',\n      'https://gureckis.github.io/slidev-theme-neversink/screenshots/35.png',\n    ],\n    tags: [\n      'light',\n      'academic',\n      'education',\n    ],\n  },\n  {\n    id: 'slidev-theme-ktym4a',\n    name: 'ktym4a',\n    description: 'Based on ktym4a website design',\n    author: {\n      name: 'ktym4a',\n      link: 'https://github.com/ktym4a',\n    },\n    repo: 'https://github.com/ktym4a/slidev-theme-ktym4a',\n    previews: [\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/rotation/0.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/rotation/1.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/rotation/6.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/rotation/7.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/rotation/8.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/single/0.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/single/1.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/single/3.png',\n      'https://cdn.jsdelivr.net/gh/ktym4a/slidev-theme-ktym4a@main/example-export/single/4.png',\n    ],\n    tags: [\n      'dark',\n      'catppuccin',\n    ],\n  },\n  {\n    id: 'slidev-theme-nord',\n    name: 'Nord',\n    description: 'Based on the Nord theme',\n    author: {\n      name: 'David Ollerhead',\n      link: 'https://github.com/oller',\n    },\n    repo: 'https://github.com/oller/slidev-theme-nord',\n    previews: [\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/1.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/2.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/3.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/4.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/5.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/6.png',\n      'https://raw.githubusercontent.com/oller/slidev-theme-nord/HEAD/example-export/7.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n      'nord',\n    ],\n  },\n  {\n    id: 'slidev-theme-scholarly',\n    name: 'Scholarly',\n    description: 'Based on the Nord theme',\n    author: {\n      name: 'Jiaxin Peng',\n      link: 'https://github.com/jxpeng98',\n    },\n    repo: 'https://github.com/jxpeng98/slidev-theme-scholarly',\n    previews: [\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/classic-blue/1.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/oxford/1.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/cambridge/1.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/princeton/1.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/classic-blue/2.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/classic-blue/3.png',\n      'https://raw.githubusercontent.com/jxpeng98/slidev-theme-scholarly/HEAD/images/themes/classic-blue/4.png',\n    ],\n    tags: [\n      'dark',\n      'light',\n      'academic',\n      'oxford',\n      'cambridge',\n      'princeton',\n    ],\n  },\n  {\n    id: 'slidev-theme-field-manual',\n    name: 'Field Manual',\n    description: 'A 24-layout theme modeled on the style of vintage military field manuals',\n    author: {\n      name: 'PJ Doland',\n      link: 'https://github.com/pjdoland',\n    },\n    repo: 'https://github.com/pjdoland/slidev-theme-field-manual',\n    previews: [\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/1.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/2.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/3.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/4.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/5.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/6.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/7.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/8.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/9.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/10.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/11.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/12.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/13.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/14.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/15.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/16.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/17.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/18.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/19.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/20.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/21.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/22.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/23.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/24.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/25.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/26.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/27.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/28.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/29.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/30.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/31.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/32.jpg',\n      'https://raw.githubusercontent.com/pjdoland/slidev-theme-field-manual/main/screenshots/33.jpg',\n    ],\n    tags: [\n      'light',\n      'dark',\n      'vintage',\n      'military',\n    ],\n  },\n  // Add yours here!\n  {\n    id: '',\n    link: 'https://github.com/slidevjs/slidev/edit/main/docs/.vitepress/themes.ts',\n    name: 'Yours?',\n    description: 'Click here to submit your theme :)',\n    author: {\n      name: '',\n    },\n    previews: [\n      '/theme-placeholder.png',\n    ],\n  },\n]\n"
  },
  {
    "path": "docs/.vitepress/utils.ts",
    "content": "import { data as features } from '../features/index.data.js'\nimport { Advanced, Guides } from './pages'\n\nfunction removeHash(link: string) {\n  const idx = link.lastIndexOf('#')\n  return idx < 0 ? link : link.slice(0, idx)\n}\n\nfunction getGuideTitle(id: string) {\n  return Guides.find(g => g.link.endsWith(`/${id}`))?.text ?? Advanced.find(g => g.link.endsWith(id))?.text ?? id\n}\n\nexport function resolveLink(link: string): {\n  kind: 'external' | 'features' | 'guide'\n  url: string\n  title?: string\n  tags?: string[]\n  descripton?: string\n} {\n  const [kind, nameWithHash] = link.split('/')\n  const name = removeHash(nameWithHash)\n  switch (kind) {\n    case 'http:':\n    case 'https:':\n    case 'mailto:':\n      return { kind: 'external', url: link }\n    case 'features': {\n      const feature = features[name]\n      if (!feature)\n        throw new Error(`Feature \"${name}\" not found.`)\n      return {\n        kind: 'features',\n        title: `✨ ${feature.title}`,\n        tags: feature.tags,\n        descripton: feature.description,\n        url: `/features/${nameWithHash}`,\n      }\n    }\n    case 'guide': {\n      return {\n        kind: 'guide',\n        title: `📖  ${getGuideTitle(name)}`,\n        tags: ['guide'],\n        descripton: 'Click to read this guide',\n        url: `/guide/${nameWithHash}`,\n      }\n    }\n    default:\n      throw new Error(`Invalid link: ${link}`)\n  }\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "# [sli.dev](https://sli.dev)\n\nDocumentation for [Slidev](https://github.com/slidevjs/slidev)\n\n## Translations\n\n> [!WARNING]\n>\n> Translations with strikethroughs are no longer maintained. The content is outdated and not encouraged to refer.\n\n|                           | Repo                                           |                             Site | Maintainers                                                           |\n| ------------------------- | ---------------------------------------------- | -------------------------------: | --------------------------------------------------------------------- |\n| English                   | [docs](https://github.com/slidevjs/docs)       |       [sli.dev](https://sli.dev) | [@antfu](https://github.com/antfu)                                    |\n| 简体中文                  | [docs-cn](https://github.com/slidevjs/docs-cn) | [cn.sli.dev](https://cn.sli.dev) | [@QC-L](https://github.com/QC-L) [@Ivocin](https://github.com/Ivocin) |\n| <del>Français</del>       | [docs-fr](https://github.com/slidevjs/docs-fr) | [fr.sli.dev](https://fr.sli.dev) | [@ArthurDanjou](https://github.com/ArthurDanjou)                      |\n| <del>Español</del>        | [docs-es](https://github.com/slidevjs/docs-es) | [es.sli.dev](https://es.sli.dev) | [@owlnai](https://github.com/owlnai)                                  |\n| <del>Русский</del>        | [docs-ru](https://github.com/slidevjs/docs-ru) | [ru.sli.dev](https://ru.sli.dev) | [@xesjkeee](https://github.com/xesjkeee)                              |\n| <del>Việt Nam</del>       | [docs-vn](https://github.com/slidevjs/docs-vn) | [vn.sli.dev](https://vn.sli.dev) | [@bongudth](https://github.com/bongudth)                              |\n| <del>Deutsch</del>        | [docs-de](https://github.com/slidevjs/docs-de) | [de.sli.dev](https://de.sli.dev) | [@fabiankachlock](https://github.com/fabiankachlock)                  |\n| <del>Português (BR)</del> | [docs-br](https://github.com/slidevjs/docs-br) | [br.sli.dev](https://br.sli.dev) | [@luisfelipesdn12](https://github.com/luisfelipesdn12)                |\n| <del>Ελληνικά</del>       | [docs-el](https://github.com/slidevjs/docs-el) | [el.sli.dev](https://el.sli.dev) | [@GeopJr](https://github.com/GeopJr)                                  |\n| <del>日本語</del>         | [docs-ja](https://github.com/slidevjs/docs-ja) | [ja.sli.dev](https://ja.sli.dev) | [@IkumaTadokoro](https://github.com/IkumaTadokoro)                    |\n\n## Start Server Locally\n\n```\nnpm i -g pnpm\n\npnpm i\npnpm run dev\n```\n\nAnd then visit `http://localhost:3000`\n\nOr install the [Vite extension for VS Code](https://marketplace.visualstudio.com/items?itemName=antfu.vite) to edit side-by-side.\n\n## Help on Translating\n\nPlease join our [Discord Server](https://chat.sli.dev) and contact the maintainers.\n"
  },
  {
    "path": "docs/builtin/cli.md",
    "content": "# Slidev CLI\n\n`@slidev/cli` exposes a binary called `slidev` that you can use to develop, build, and export your slides.\n\n## Prerequisites\n\nTo use the CLI, you can either install `@slidev/cli` globally or install it locally in your Node.js project. If you created your project with `npm init slidev`, the CLI is already installed locally.\n\n::: warning\nUsually `npx slidev` is not supported because the package name is actually `@slidev/cli`.\n:::\n\nThe CLI options of the commands obey the following conventions:\n\n- the value of the option can be passed after a space or a `=` character:\n\n  Example: `slidev --port 8080` is equivalent to `slidev --port=8080`\n\n- `true` can be omitted for boolean options:\n\n  Example: `slidev --open` is equivalent to `slidev --open true`\n\n::: info\n\nIf you use npm, please don't forget to add `--` before the options to pass them to Slidev:\n\n```bash\nnpm run slidev -- --remote --port 8080 --open\n```\n\n:::\n\n## `slidev [entry]` {#dev}\n\nStart a local server for Slidev.\n\n- `[entry]` (`string`, default: `slides.md`): path to the markdown file containing your slides.\n\nOptions:\n\n- `--port`, `-p` (`number`, default: `3030`): port number.\n- `--base` (`string`, default: `/`): base URL (see https://vitejs.dev/config/shared-options.html#base).\n- `--open`, `-o` (`boolean`, default: `false`): open in the browser.\n- `--remote [password]` (`string`): listen to the public host and enable remote control, if a value is passed then the presenter mode is private and only accessible by passing the given password in the URL query `password` parameter.\n- `--bind` (`string`, default: `0.0.0.0`): specify which IP addresses the server should listen on in the remote mode.\n- `--log` (`'error', 'warn', 'info', 'silent'`, default: `'warn'`): Log level.\n- `--force`, `-f` (`boolean`, default: `false`): force the optimizer to ignore the cache and re-bundle.\n- `--theme`, `-t` (`string`): override theme.\n\n## `slidev build [entry]` {#build}\n\nBuild a hostable SPA. See <LinkInline link=\"guide/hosting\" /> for more details.\n\n- `[entry]` (`string`, default: `slides.md`): path to the slides markdown file.\n\nOptions:\n\n- `--out`, `-o` (`string`, default: `dist`): output directory\n- `--base` (`string`, default: `/`): base URL (see https://vitejs.dev/config/shared-options.html#base)\n- `--download` (`boolean`, default: `false`): allow the download of the slides as a PDF inside the SPA\n- `--theme`, `-t` (`string`): override theme\n- `--without-notes` (`boolean`, default: `false`): exclude speaker notes from the SPA\n\n## `slidev export [...entry]` {#export}\n\nExport slides to PDF (or other format). See <LinkInline link=\"guide/exporting\" /> for more details.\n\n- `[entry]` (`string`, default: `slides.md`): path to the slides markdown entry.\n\nOptions:\n\n- `--output` (`string`, default: use `exportFilename` (see https://sli.dev/custom/#frontmatter-configures) or use `[entry]-export`): path to the output.\n- `--format` (`'pdf', 'png', 'pptx', 'md'`, default: `'pdf'`): output format.\n- `--timeout` (`number`, default: `30000`): timeout for rendering the print page (see https://playwright.dev/docs/api/class-page#page-goto).\n- `--range` (`string`): page ranges to export (example: `'1,4-5,6'`).\n- `--dark` (`boolean`, default: `false`): export as dark theme.\n- `--with-clicks`, `-c` (`boolean`, default: `false`): export pages for every click animation (see https://sli.dev/guide/animations.html#click-animation).\n- `--theme`, `-t` (`string`): override theme.\n- `--omit-background` (`boolean`, default: `false`): remove the default browser background\n\n## `slidev format [entry]` {#format}\n\nFormat the markdown file. Note that this won't format the content of the slides, only the organization of the markdown file.\n\n- `[entry]` (`string`, default: `slides.md`): path to the slides markdown entry.\n\n## `slidev theme [subcommand]` {#theme}\n\nTheme-related operations.\n\nSubcommands:\n\n- `eject [entry]`: Eject the current theme into the local file system. See <LinkInline link=\"features/eject-theme\" />.\n  - `[entry]` (`string`, default: `slides.md`): path to the slides markdown entry.\n  - Options:\n    - `--dir` (`string`, default: `theme`): the output dir.\n    - `--theme`, `-t` (`string`): override theme.\n"
  },
  {
    "path": "docs/builtin/components.md",
    "content": "# Components\n\nThis page lists all the built-in components provided by Slidev. These components can be **directly** used in your slides.\n\nNote that <LinkInline link=\"guide/theme-addon\" /> can provide additional components. To add your own components, see <LinkInline link=\"guide/component#write\" />.\n\n## `Arrow`\n\nDraw an arrow.\n\n### Usage\n\n```md\n<Arrow x1=\"10\" y1=\"20\" x2=\"100\" y2=\"200\" />\n```\n\nOr:\n\n```md\n<Arrow v-bind=\"{ x1:10, y1:10, x2:200, y2:200 }\" />\n```\n\nProps:\n\n- `x1` (`string | number`, required): start point x position\n- `y1` (`string | number`, required): start point y position\n- `x2` (`string | number`, required): end point x position\n- `y2` (`string | number`, required): end point y position\n- `width` (`string | number`, default: `2`): line width\n- `color` (`string`, default: `'currentColor'`): line color\n- `two-way` (`boolean`, default: `false`): draw a two-way arrow\n\n## `VDragArrow`\n\nAn `Arrow` component that can be dragged.\n\n### Usage\n\n<LinkCard link=\"features/draggable#draggable-arrow\" />\n\nProps not related to position are the same as [the `Arrow` component](#arrow).\n\n## `AutoFitText`\n\n> Experimental\n\nBox inside which the font size will automatically adapt to fit the content. Similar to PowerPoint or Keynote TextBox.\n\n### Usage\n\n```md\n<AutoFitText :max=\"200\" :min=\"100\" modelValue=\"Some text\"/>\n```\n\nProps:\n\n- `max` (`string | number`, default `100`): Maximum font size\n- `min` (`string | number`, default `30`): Minimum font size\n- `modelValue` (`string`, default `''`): text content\n\n## `LightOrDark`\n\nUse it to display one thing or another depending on the active light or dark theme.\n\n### Usage\n\nUse it with the two named Slots `#dark` and `#light`:\n\n```md\n<LightOrDark>\n  <template #dark>Dark mode is on</template>\n  <template #light>Light mode is on</template>\n</LightOrDark>\n```\n\nProvided props on `LightOrDark` component will be available using scoped slot props:\n\n```md\n<LightOrDark width=\"100\" alt=\"some image\">\n  <template #dark=\"props\">\n    <img src=\"/dark.png\" v-bind=\"props\"/>\n  </template>\n  <template #light=\"props\">\n    <img src=\"/light.png\" v-bind=\"props\"/>\n  </template>\n</LightOrDark>\n```\n\nYou can provide markdown in the slots, but you will need to surround the content with blank lines:\n\n```md\n<LightOrDark>\n  <template #dark>\n\n![dark](/dark.png)\n\n  </template>\n  <template #light>\n\n![light](/light.png)\n\n  </template>\n</LightOrDark>\n```\n\n## `Link`\n\nInsert a link you can use to navigate to a given slide.\n\n### Usage\n\n```md\n<Link to=\"42\">Go to slide 42</Link>\n<Link to=\"42\" title=\"Go to slide 42\"/>\n<Link to=\"solutions\" title=\"Go to solutions\"/>\n```\n\nProps:\n\n- `to` (`string | number`): The path of the slide to navigate to (slides path starts from `1`)\n- `title` (`string`): The title to display\n\nOne can use a string as `to`, provided the corresponding route exists, e.g.\n\n```md\n---\nrouteAlias: solutions\n---\n\n# Now some solutions!\n```\n\n## `PoweredBySlidev`\n\nRenders \"Powered by Slidev\" with a link to the Slidev website.\n\n## `RenderWhen`\n\nRender slots depend on whether the context matches (for example whether we are in presenter view).\n\n### Usage\n\n```md\n<RenderWhen context=\"presenter\">This will only be rendered in presenter view.</RenderWhen>\n```\n\nContext type: `'main' | 'visible' | 'print' | 'slide' | 'overview' | 'presenter' | 'previewNext'`\n\nProps:\n\n- `context` (`Context | Context[]`): a context or array of contexts you want to check for\n  - `'main'`: Render in slides and presenter view (equivalent to ['slide', 'presenter']),\n  - `'visible'`: Render the content if it is visible\n  - `'print'`: Render in print mode\n  - `'slide'`: Render in slides\n  - `'overview'`: Render in overview\n  - `'presenter'`: Render in presenter view\n  - `'previewNext'`: Render in presenter's next slide view\n\nSlots:\n\n- `#default`: Rendered when the context matches\n- `#fallback`: Rendered when the context does not match\n\n## `SlideCurrentNo`\n\nCurrent slide number.\n\n### Usage\n\n```md\n<SlideCurrentNo />\n```\n\n## `SlidesTotal`\n\nTotal number of slides.\n\n### Usage\n\n```md\n<SlidesTotal />\n```\n\n## `TitleRenderer`\n\nInsert the main title from a slide parsed as HTML.\n\nTitles and title levels get automatically retrieved from the first title element of each slide.\n\nYou can override this automatic behavior for a slide by using the front matter syntax:\n\n```yml\n---\ntitle: Amazing slide title\nlevel: 2\n---\n```\n\n### Usage\n\nThe `<TitleRenderer>` component is a virtual component you can import with:\n\n```js\nimport TitleRenderer from '#slidev/title-renderer'\n```\n\nThen you can use it with:\n\n```md\n<TitleRenderer no=\"42\" />\n```\n\nProps:\n\n- `no` (`string | number`): The number of the slide to display the title from (slides starts from `1`)\n\n## `Toc`\n\nInsert a Table Of Content.\n\nIf you want a slide to not appear in the `<Toc>` component, you can use the `hideInToc` option in the frontmatter of the slide:\n\n```yml\n---\nhideInToc: true\n---\n```\n\nTitles are displayed using the [`<Titles>` component](#titles)\n\n### Usage\n\n```md\n<Toc />\n```\n\nProps:\n\n- `columns` (`string | number`, default: `1`): The number of columns of the display\n- `listClass` (`string | string[]`, default: `''`): Classes to apply to the table of contents list\n- `maxDepth` (`string | number`, default: `Infinity`): The maximum depth level of title to display\n- `minDepth` (`string | number`, default: `1`): The minimum depth level of title to display\n- `mode` (`'all' | 'onlyCurrentTree'| 'onlySiblings'`, default: `'all'`):\n  - `'all'`: Display all items\n  - `'onlyCurrentTree'`: Display only items that are in current tree (active item, parents and children of active item)\n  - `'onlySiblings'`: Display only items that are in current tree and their direct siblings\n\n## `Transform`\n\nApply scaling or transforming to elements.\n\n### Usage\n\n```md\n<Transform :scale=\"0.5\" origin=\"top center\">\n  <YourElements />\n</Transform>\n```\n\nProps:\n\n- `scale` (`number | string`, default `1`): transform scale value\n- `origin` (`string`, default `'top left'`): transform origin value\n\n## `Tweet`\n\nEmbed a tweet.\n\n### Usage\n\n```md\n<Tweet id=\"20\" />\n```\n\nProps:\n\n- `id` (`number | string`, required): id of the tweet\n- `scale` (`number | string`, default `1`): transform scale value\n- `conversation` (`string`, default `'none'`): [tweet embed parameter](https://developer.twitter.com/en/docs/twitter-for-websites/embedded-tweets/guides/embedded-tweet-parameter-reference)\n- `cards` (`'hidden' | 'visible'`, default `'visible'`): [tweet embed parameter](https://developer.twitter.com/en/docs/twitter-for-websites/embedded-tweets/guides/embedded-tweet-parameter-reference)\n\n## `VAfter`, `VClick` and `VClicks`\n\n<LinkCard link=\"guide/animations#click-animation\" />\n\n## `VSwitch`\n\nSwitch between multiple slots based on clicks.\n\n<LinkCard link=\"guide/animations#enter-leave\" />\n\n- If the `unmount` prop is set to `true`, the previous slot will be unmounted when switching to the next slot. Default is `false`.\n- Use the `tag` and `childTag` props to change the default tag of the component and its children. Default is `div`.\n- Use the `transition` prop to change the transition effect. Default is `false` (disabled).\n\n## `VDrag`\n\n<LinkCard link=\"features/draggable\" />\n\n## `SlidevVideo`\n\nEmbed a video.\n\n### Usage\n\n```md\n<SlidevVideo v-click autoplay controls>\n  <!-- Anything that can go in an HTML video element. -->\n  <source src=\"/myMovie.mp4\" type=\"video/mp4\" />\n  <source src=\"/myMovie.webm\" type=\"video/webm\" />\n  <p>\n    Your browser does not support videos. You may download it\n    <a href=\"/myMovie.mp4\">here</a>.\n  </p>\n</SlidevVideo>\n```\n\nCheck [HTML video element's doc](https://developer.mozilla.org/docs/Web/HTML/Element/Video) to see what can be included in this component's slot.\n\nProps:\n\n- `controls` (`boolean`, default: `false`): show the video controls\n- `autoplay` (`boolean | 'once'`, default: `false`):\n  - `true` or `'once'`: start the video only once and does not restart it once ended or paused\n  - `false`: never automatically start the video (rely on `controls` instead)\n- `autoreset` (`'slide' | 'click'`, default: `undefined`):\n  - `'slide'`: go back to the start of the video when going back to the slide\n  - `'click'`: go back to the start of the video when going back to the component's click turn\n- `poster` (`string | undefined`, default: `undefined`):\n  - The source of the image to print when the video is not playing.\n- `printPoster` (`string | undefined`, default: `undefined`):\n  - The override for `poster` when printing.\n- `timestamp` (`string | number`, default: `0`):\n  - The starting time of the video in seconds.\n- `printTimestamp` (`string | number | 'last' | undefined`, default: `undefined`):\n  - The override for `timestamp` when printing.\n\n::: warning\nWhen exporting, the video may fail to load because Chromium does not support some video formats. In this case, you can specify the executable path of the browser. See [Chromium executable path](/guide/exporting.html#executable-path) for more information.\n:::\n\n## `Youtube`\n\nEmbed a YouTube video.\n\n### Usage\n\n```md\n<Youtube id=\"luoMHjh-XcQ\" />\n```\n\nProps:\n\n- `id` (`string`, required): id of the YouTube video\n- `width` (`number`): width of the video\n- `height` (`number`): height of the video\n\nYou can also make the video start at a specific time if you add `?start=1234` to the id value (where `1234` is seconds),\n"
  },
  {
    "path": "docs/builtin/layouts.md",
    "content": "# Layouts\n\nThis page lists all the built-in layouts provided by Slidev. These layouts can be used via the `layout` option in the frontmatters of your slides.\n\nNote that <LinkInline link=\"guide/theme-addon\" /> may provide additional layouts or override the existing ones. To add your own layouts, see <LinkInline link=\"guide/write-layout\" />.\n\n## `center`\n\nDisplays the content in the middle of the screen.\n\n## `cover`\n\nUsed to display the cover page for the presentation, may contain the presentation title, contextualization, etc.\n\n## `default`\n\nThe most basic layout, to display any kind of content.\n\n## `end`\n\nThe final page for the presentation.\n\n## `fact`\n\nTo show some fact or data with a lot of prominence on the screen.\n\n## `full`\n\nUse all the space of the screen to display the content.\n\n## `image-left`\n\nShows an image on the left side of the screen, the content will be placed on the right side.\n\n### Usage\n\n```yaml\n---\nlayout: image-left\n\n# the image source\nimage: /path/to/the/image\n\n# a custom class name to the content\nclass: my-cool-content-on-the-right\n---\n```\n\n## `image-right`\n\nShows an image on the right side of the screen, the content will be placed on the left side.\n\n### Usage\n\n```yaml\n---\nlayout: image-right\n\n# the image source\nimage: /path/to/the/image\n\n# a custom class name to the content\nclass: my-cool-content-on-the-left\n---\n```\n\n## `image`\n\nShows an image as the main content of the page.\n\n### Usage\n\n```yaml\n---\nlayout: image\n\n# the image source\nimage: /path/to/the/image\n---\n```\n\nYou can change the default background size (`cover`) by adding the `backgroundSize` attribute:\n\n```yaml\n---\nlayout: image\nimage: /path/to/the/image\nbackgroundSize: contain\n---\n```\n\n```yaml\n---\nlayout: image-left\nimage: /path/to/the/image\nbackgroundSize: 20em 70%\n---\n```\n\n## `iframe-left`\n\nShows a web page on the left side of the screen, the content will be placed on the right side.\n\n### Usage\n\n```yaml\n---\nlayout: iframe-left\n\n# the web page source\nurl: https://github.com/slidevjs/slidev\n\n# a custom class name to the content\nclass: my-cool-content-on-the-right\n---\n```\n\n## `iframe-right`\n\nShows a web page on the right side of the screen, the content will be placed on the left side.\n\n### Usage\n\n```yaml\n---\nlayout: iframe-right\n\n# the web page source\nurl: https://github.com/slidevjs/slidev\n\n# a custom class name to the content\nclass: my-cool-content-on-the-left\n---\n```\n\n## `iframe`\n\nShows a web page as the main content of the page.\n\n### Usage\n\n```yaml\n---\nlayout: iframe\n\n# the web page source\nurl: https://github.com/slidevjs/slidev\n---\n```\n\n## `intro`\n\nTo introduce the presentation, usually with the presentation title, a short description, the author, etc.\n\n## `none`\n\nA layout without any existing styling.\n\n## `quote`\n\nTo display a quotation with prominence.\n\n## `section`\n\nUsed to mark the beginning of a new presentation section.\n\n## `statement`\n\nMake an affirmation/statement as the main page content.\n\n## `two-cols`\n\nSeparates the page content in two columns.\n\n### Usage\n\n```md\n---\nlayout: two-cols\n---\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right\n```\n\n## `two-cols-header`\n\nSeparates the upper and lower lines of the page content, and the second line separates the left and right columns.\n\n### Usage\n\n```md\n---\nlayout: two-cols-header\n---\n\nThis spans both\n\n::left::\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right\n\n<style>\n.two-cols-header {\n  column-gap: 20px; /* Adjust the gap size as needed */\n}\n</style>\n```\n"
  },
  {
    "path": "docs/custom/config-code-runners.md",
    "content": "# Configure Code Runners\n\n<Environment type=\"client\" />\n\nDefine code runners for custom languages in your Monaco Editor.\n\nBy default, JavaScript, TypeScript runners are supported built-in. They run in the browser **without** a sandbox environment. If you want more advanced integrations, you can provide your own code runner that sends the code to a remote server, runs in a Web Worker, or anything, up to you.\n\nCreate `./setup/code-runners.ts` with the following content:\n\n```ts twoslash [setup/code-runners.ts]\n/* eslint-disable import/first */\ndeclare const executePythonCodeRemotely: (code: string) => Promise<string>\ndeclare const sanitizeHtml: (html: string) => string\n// ---cut---\nimport { defineCodeRunnersSetup } from '@slidev/types'\n\nexport default defineCodeRunnersSetup(() => {\n  return {\n    async python(code, ctx) {\n      // Somehow execute the code and return the result\n      const result = await executePythonCodeRemotely(code)\n      return {\n        text: result\n      }\n    },\n    html(code, ctx) {\n      return {\n        html: sanitizeHtml(code)\n      }\n    },\n    // or other languages, key is the language id\n  }\n})\n```\n\n## Runner Context\n\nThe second argument `ctx` is the runner context, which contains the following properties:\n\n```ts twoslash\nimport type { CodeRunnerOutputs } from '@slidev/types'\nimport type { CodeToHastOptions } from 'shiki'\n// ---cut---\nexport interface CodeRunnerContext {\n  /**\n   * Options passed to runner via the `runnerOptions` prop.\n   */\n  options: Record<string, unknown>\n  /**\n   * Highlight code with shiki.\n   */\n  highlight: (code: string, lang: string, options?: Partial<CodeToHastOptions>) => string\n  /**\n   * Use (other) code runner to run code.\n   */\n  run: (code: string, lang: string) => Promise<CodeRunnerOutputs>\n}\n```\n\n## Runner Output\n\nThe runner can either return a text or HTML output, or an element to be mounted. Refer to https://github.com/slidevjs/slidev/blob/main/packages/types/src/code-runner.ts for more details.\n\n## Additional Runner Dependencies\n\nBy default, Slidev will scan the Markdown source and automatically import the necessary dependencies for the code runners. If you want to manually import dependencies, you can use the `monacoRunAdditionalDeps` option in the [headmatter](./index#headmatter):\n\n```yaml\nmonacoRunAdditionalDeps:\n  - ./path/to/dependency\n  - lodash-es\n```\n\n::: tip\nThe paths are resolved relative to the `snippets` directory. And the names of the deps should be exactly the same as the imported ones in the code.\n:::\n"
  },
  {
    "path": "docs/custom/config-context-menu.md",
    "content": "# Configure Context Menu\n\n<Environment type=\"client\" />\n\nCustomize the context menu items in Slidev.\n\nCreate `./setup/context-menu.ts` with the following content:\n\n```ts twoslash [./setup/context-menu.ts]\n// ---cut---\nimport { useNav } from '@slidev/client'\nimport { defineContextMenuSetup } from '@slidev/types'\nimport { computed } from 'vue'\n// ---cut-start---\n// @ts-expect-error missing types\n// ---cut-end---\nimport Icon3DCursor from '~icons/carbon/3d-cursor'\n\nexport default defineContextMenuSetup((items) => {\n  const { isPresenter } = useNav()\n  return computed(() => [\n    ...items.value,\n    {\n      small: false,\n      icon: Icon3DCursor, // if `small` is `true`, only the icon is shown\n      label: 'Custom Menu Item', // or a Vue component\n      action() {\n        alert('Custom Menu Item Clicked!')\n      },\n      disabled: isPresenter.value,\n    },\n  ])\n})\n```\n\nThis will append a new menu item to the context menu.\n\nTo disable context menu globally, set `contextMenu` to `false` in the frontmatter. `contextMenu` can also be set to `dev` or `build` to only enable the context menu in development or build mode.\n"
  },
  {
    "path": "docs/custom/config-fonts.md",
    "content": "# Configure Fonts\n\nWhile you can use HTML and CSS to customize the fonts and style for your slides as you want, Slidev also provides a convenient way to use them effortlessly.\n\nIn your frontmatter, configure as the following:\n\n```yaml\n---\nfonts:\n  # basically the text\n  sans: Robot\n  # use with `font-serif` css class from UnoCSS\n  serif: Robot Slab\n  # for code blocks, inline code, etc.\n  mono: Fira Code\n---\n```\n\nAnd that's all.\n\nFonts will be **imported automatically from a provider via CDN, by default it is [Google Fonts](https://fonts.google.com/)**. That means you can use any fonts available on Google Fonts directly.\n\n## Local Fonts\n\nBy default, Slidev assumes all the fonts specified via `fonts` configurations come from Google Fonts. If you want to use local fonts, specify the `fonts.local` to opt-out the auto-importing.\n\n```yaml\n---\nfonts:\n  # like font-family in css, you can use `,` to separate multiple fonts for fallback\n  sans: 'Helvetica Neue,Robot'\n  # mark 'Helvetica Neue' as local font\n  local: Helvetica Neue\n---\n```\n\n## Weights & Italic\n\nBy default, Slidev imports three weights `200`,`400`,`600` for each font. You can configure them by:\n\n```yaml\n---\nfonts:\n  sans: Robot\n  # default\n  weights: '200,400,600'\n  # import italic fonts, default `false`\n  italic: false\n---\n```\n\nThis configuration applies to all web fonts. For more fine-grained controls of each font's weights, you will need to manually import them with [HTML](/custom/directory-structure.html#index-html) and CSS.\n\n## Fallback Fonts\n\nFor most of the scenarios, you only need to specify the \"special font\" and Slidev will append the fallback fonts for you, for example:\n\n```yaml\n---\nfonts:\n  sans: Robot\n  serif: Robot Slab\n  mono: Fira Code\n---\n```\n\nwill result in\n\n<!-- eslint-skip -->\n\n```css\n.font-sans {\n  font-family: \"Robot\",ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";\n}\n.font-serif {\n  font-family: \"Robot Slab\",ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif;\n}\n.font-mono {\n  font-family: \"Fira Code\",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n}\n```\n\nIf you want to disable the fallback fonts, configure as the following:\n\n```yaml\n---\nfonts:\n  mono: 'Fira Code, monospace'\n  fallbacks: false\n---\n```\n\n## Providers\n\n- Options: `google` | `coollabs` | `none`\n- Default: `google`\n\nCurrently, only [Google Fonts](https://fonts.google.com/) and [coolLabs](https://fonts.coollabs.io/) supported, we are planning to add more providers in the future. Specify to `none` will disable the auto-importing feature entirely and treat all the fonts locally.\n\n```yaml\n---\nfonts:\n  provider: none\n---\n```\n"
  },
  {
    "path": "docs/custom/config-highlighter.md",
    "content": "# Configure Highlighter\n\nSlidev uses [Shiki](https://github.com/shikijs/shiki) as the code highlighter. It's a TextMate Grammar powered syntax highlighter as accurate as VS Code. It generates colored tokens so no additinal CSS is required. Shiki also comes with [a bunch of built-in themes](https://shiki.style/themes). In Slidev, we also provided the [TwoSlash](#twoslash-integration) support.\n\n## Configure Shiki\n\n<Environment type=\"both\" />\n\nCreate `./setup/shiki.ts` file with the following content:\n\n```ts twoslash [setup/shiki.ts]\nimport { defineShikiSetup } from '@slidev/types'\n\nexport default defineShikiSetup(() => {\n  return {\n    themes: {\n      dark: 'min-dark',\n      light: 'min-light',\n    },\n    transformers: [\n      // ...\n    ],\n  }\n})\n```\n\nIf you want to add custom theme or language (TextMate grammar/themes in JSON), you can import them in the setup file:\n\n```ts twoslash [setup/shiki.ts]\nimport { defineShikiSetup } from '@slidev/types'\n// ---cut-start---\n// @ts-expect-error missing types\n// ---cut-end---\nimport customLanguage from './customLanguage.tmLanguage.json'\n// ---cut-start---\n// @ts-expect-error missing types\n// ---cut-end---\nimport customTheme from './customTheme.tmTheme.json'\n\nexport default defineShikiSetup(() => {\n  return {\n    themes: {\n      dark: customTheme,\n      light: 'min-light',\n    },\n    langs: [\n      'js',\n      'typescript',\n      'cpp',\n      customLanguage,\n      // ...\n    ],\n    transformers: [\n      // ...\n    ],\n  }\n})\n```\n\nCheck [Built-in languages](https://shiki.style/languages) and [Built-in themes](https://shiki.style/themes), and refer to [Shiki's docs](https://shiki.style) for more details.\n\n:::info\nFor now, Shiki Magic Move does not support transformers.\n:::\n\n## Configure Prism\n\n:::warning\nPrism support has been removed since v0.50. Please use Shiki instead.\n:::\n"
  },
  {
    "path": "docs/custom/config-katex.md",
    "content": "# Configure KaTeX\n\n<Environment type=\"node\" />\n\nCreate `./setup/katex.ts` with the following content:\n\n```ts twoslash [setup/katex.ts]\nimport { defineKatexSetup } from '@slidev/types'\n\nexport default defineKatexSetup(() => {\n  return {\n    maxExpand: 2000,\n    /* ... */\n  }\n})\n```\n\nThe return value should be the custom options for KaTeX. Refer to [KaTeX's documentation](https://katex.org/docs/options.html) or the type definition for the full options list.\n"
  },
  {
    "path": "docs/custom/config-mermaid-renderer.md",
    "content": "# Configure Mermaid Renderer\n\n<Environment type=\"client\" />\n\n1. The user installs the Mermaid library they want to use. e.g.) `npm install beautiful-mermaid`\n2. Create `./setup/mermaid-renderer.ts` with the following content:\n\n```ts\n// setup/mermaid-renderer.ts\nimport { defineMermaidRendererSetup } from '@slidev/types'\n// example. https://github.com/lukilabs/beautiful-mermaid?tab=readme-ov-file#readme\nimport { renderMermaid } from 'beautiful-mermaid'\n\nexport default defineMermaidRendererSetup(() => {\n  return (code, _options) => renderMermaid(code)\n})\n```\n\nThis setting allows you to use the 3rd party Mermaid library. Replace the `renderMermaid()` part with the render function of the library.\n"
  },
  {
    "path": "docs/custom/config-mermaid.md",
    "content": "# Configure Mermaid\n\n<Environment type=\"client\" />\n\nCreate `./setup/mermaid.ts` with the following content:\n\n```ts twoslash [setup/mermaid.ts]\nimport { defineMermaidSetup } from '@slidev/types'\n\nexport default defineMermaidSetup(() => {\n  return {\n    theme: 'forest',\n  }\n})\n```\n\nThe return value should be the custom configs for [Mermaid](https://mermaid.js.org/). Refer to the [Mermaid documentation](https://mermaid.js.org/config/schema-docs/config.html) or the type definition for the full config list.\n\n## Custom theme/styles\n\nIn case you want to create your custom Mermaid themes or styles, you can do this by defining `themeVariables` like in the following example:\n\n```ts twoslash\nimport { defineMermaidSetup } from '@slidev/types'\n\nexport default defineMermaidSetup(() => {\n  return {\n    theme: 'base',\n    themeVariables: {\n      // General theme variables\n      noteBkgColor: '#181d29',\n      noteTextColor: '#F3EFF5cc',\n      noteBorderColor: '#404551',\n\n      // Sequence diagram variables\n      actorBkg: '#0E131F',\n      actorBorder: '#44FFD2',\n      actorTextColor: '#F3EFF5',\n      actorLineColor: '#F3EFF5',\n      signalColor: '#F3EFF5',\n      signalTextColor: '#F3EFF5',\n    }\n  }\n})\n```\n\nYou can find all theme variables on the [Mermaid Theme Configuration](https://mermaid.js.org/config/theming.html) page.\n"
  },
  {
    "path": "docs/custom/config-monaco.md",
    "content": "# Configure Monaco\n\n<Environment type=\"client\" />\n\nCreate `./setup/monaco.ts` with the following content:\n\n```ts twoslash [./setup/monaco.ts]\nimport { defineMonacoSetup } from '@slidev/types'\n\nexport default defineMonacoSetup(async (monaco) => {\n  // use `monaco` to configure\n})\n```\n\nLearn more about [configuring Monaco](https://github.com/Microsoft/monaco-editor).\n\n## TypeScript Types\n\nWhen using TypeScript with Monaco, types for dependencies will be installed to the client-side automatically.\n\n````md\n```ts {monaco}\nimport { ref } from 'vue'\nimport { useMouse } from '@vueuse/core'\n\nconst counter = ref(0)\n```\n````\n\nIn the example above, make sure `vue` and `@vueuse/core` are installed locally as dependencies / devDependencies, Slidev will handle the rest to get the types working for the editor automatically. When deployed as SPA, those types will also be bundled for static hosting.\n\n### Additional Types\n\nSlidev will scan all the Monaco code blocks in your slides and import the types for those used libraries for you. In case it missed some, you can explicitly specify extra packages to import the types for:\n\n```md\n---\nmonacoTypesAdditionalPackages:\n  - lodash-es\n  - foo\n---\n```\n\n### Auto Type Acquisition\n\nYou can optionally switch to load types from CDN by setting the following headmatter:\n\n```md\n---\nmonacoTypesSource: ata\n---\n```\n\nThis feature is powered by [`@typescript/ata`](https://github.com/microsoft/TypeScript-Website/tree/v2/packages/ata) and runs completely on the client-side.\n\n## Configure Themes\n\nSince v0.48.0, Monaco will reuse the Shiki theme you configured in [Shiki's setup file](/custom/config-highlighter#configure-shiki), powered by [`@shikijs/monaco`](https://shiki.style/packages/monaco). You don't need to worry about it anymore and it will have a consistent style with the rest of your code blocks.\n\n## Configure the Editor\n\n> Available since v0.43.0\n\nIf you would like to customize the Monaco editor you may pass an `editorOptions` object that matches the [Monaco IEditorOptions](https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IEditorOptions.html) definition.\n\n````md\n```ts {monaco} { editorOptions: { wordWrap:'on'} }\nconsole.log('HelloWorld')\n```\n````\n\nAlternatively if you would like these options to be applied to every Monaco instance, you can return them in the `defineMonacoSetup` function\n\n```ts twoslash [./setup/monaco.ts]\nimport { defineMonacoSetup } from '@slidev/types'\n\nexport default defineMonacoSetup(() => {\n  return {\n    editorOptions: {\n      wordWrap: 'on'\n    }\n  }\n})\n```\n\n## Disabling\n\nSince v0.48.0, the Monaco editor is enabled by default and only be bundled when you use it. If you want to disable it, you can set `monaco` to `false` in the frontmatter of your slide:\n\n```yaml\n---\nmonaco: false # can also be `dev` or `build` to conditionally enable it\n---\n```\n\n## Strict Mode {#strict-mode}\n\n> Available since v0.52.0\n\nBy default, Monaco runnable code executes in strict mode (`\"use strict\"`). You can disable this if your code relies on non-strict mode behavior:\n\n```yaml\n---\nmonacoRunUseStrict: false\n---\n```\n\n## Configure Code Runners\n\nTo configure how the Monaco Runner runs the code, or to add support for custom languages, please reference [Configure Code Runners](/custom/config-code-runners).\n"
  },
  {
    "path": "docs/custom/config-parser.md",
    "content": "# Configure Pre-Parser\n\n::: info\nCustom pre-parsers are not supposed to be used too often. Usually you can use [Transformers](./config-transformers) for custom syntaxes.\n:::\n\nSlidev parses your presentation file (e.g. `slides.md`) in three steps:\n\n1. A \"preparsing\" step is carried out: the file is split into slides using the `---` separator, and considering the possible frontmatter blocks.\n2. Each slide is parsed with an external library.\n3. Slidev resolves the special frontmatter property `src: ....`, which allows to include other md files.\n\n## Markdown Parser\n\nConfiguring the markdown parser used in step 2 can be done by [configuring Vite internal plugins](/custom/config-vite#configure-internal-plugins).\n\n## Preparser Extensions\n\n> Available since v0.37.0.\n\n::: warning\nImportant: when modifying the preparser configuration, you need to stop and start Slidev again (restart might not be sufficient).\n:::\n\nThe preparser (step 1 above) is highly extensible and allows you to implement custom syntaxes for your md files. Extending the preparser is considered **an advanced feature** and is susceptible to breaking [editor integrations](../features/side-editor) due to implicit changes in the syntax.\n\nTo customize it, create a `./setup/preparser.ts` file with the following content:\n\n```ts twoslash [./setup/preparser.ts]\nimport { definePreparserSetup } from '@slidev/types'\n\nexport default definePreparserSetup(({ filepath, headmatter, mode }) => {\n  return [\n    {\n      transformRawLines(lines) {\n        for (const i in lines) {\n          if (lines[i] === '@@@')\n            lines[i] = 'HELLO'\n        }\n      },\n    }\n  ]\n})\n```\n\nThis example systematically replaces any `@@@` line with a line with `hello`. It illustrates the structure of a preparser configuration file and some of the main concepts the preparser involves:\n\n- `definePreparserSetup` must be called with a function as parameter.\n- The function receives the file path (of the root presentation file), the headmatter (from the md file) and, since v0.48.0, a mode (dev, build or export). It could use this information (e.g., enable extensions based on the presentation file or whether we are exporting a PDF).\n- The function must return a list of preparser extensions.\n- An extension can contain:\n  - a `transformRawLines(lines)` function that runs just after parsing the headmatter of the md file and receives a list of all lines (from the md file). The function can mutate the list arbitrarily.\n  - a `transformSlide(content, frontmatter)` function that is called for each slide, just after splitting the file, and receives the slide content as a string and the frontmatter of the slide as an object. The function can mutate the frontmatter and must return the content string (possibly modified, possibly `undefined` if no modifications have been done).\n  - a `transformNote(note, frontmatter)` function that is called for each slide, just after splitting the file, and receives the slide note as a string or undefined and the frontmatter of the slide as an object. The function can mutate the frontmatter and must return the note string (possibly modified, possibly `undefined` if no modifications have been done).\n  - a `name`\n\n## Example Preparser Extensions\n\n### Use case 1: compact syntax top-level presentation\n\nImagine a situation where (part of) your presentation is mainly showing cover images and including other md files. You might want a compact notation where for instance (part of) `slides.md` is as follows:\n\n<!-- eslint-skip -->\n\n```md\n@cover: /nice.jpg\n# Welcome\n@src: page1.md\n@src: page2.md\n@cover: /break.jpg\n@src: pages3-4.md\n@cover: https://cover.sli.dev\n# Questions?\nsee you next time\n```\n\nTo allow these `@src:` and `@cover:` syntaxes, create a `./setup/preparser.ts` file with the following content:\n\n```ts twoslash [./setup/preparser.ts]\nimport { definePreparserSetup } from '@slidev/types'\n\nexport default definePreparserSetup(() => {\n  return [\n    {\n      transformRawLines(lines) {\n        let i = 0\n        while (i < lines.length) {\n          const l = lines[i]\n          if (l.match(/^@cover:/i)) {\n            lines.splice(\n              i,\n              1,\n              '---',\n              'layout: cover',\n              `background: ${l.replace(/^@cover: */i, '')}`,\n              '---',\n              ''\n            )\n            continue\n          }\n          if (l.match(/^@src:/i)) {\n            lines.splice(\n              i,\n              1,\n              '---',\n              `src: ${l.replace(/^@src: */i, '')}`,\n              '---',\n              ''\n            )\n            continue\n          }\n          i++\n        }\n      }\n    },\n  ]\n})\n```\n\nAnd that's it.\n\n### Use case 2: using custom frontmatter to wrap slides\n\nImagine a case where you often want to scale some of your slides but still want to use a variety of existing layouts so creating a new layout would not be suited.\nFor instance, you might want to write your `slides.md` as follows:\n\n<!-- eslint-skip -->\n\n```md\n---\nlayout: quote\n_scale: 0.75\n---\n\n# Welcome\n\n> great!\n\n---\n_scale: 4\n---\n# Break\n\n---\n\n# Ok\n\n---\nlayout: center\n_scale: 2.5\n---\n# Questions?\nsee you next time\n```\n\nHere we used an underscore in `_scale` to avoid possible conflicts with existing frontmatter properties (indeed, the case of `scale`, without underscore would cause potential problems).\n\nTo handle this `_scale: ...` syntax in the frontmatter, create a `./setup/preparser.ts` file with the following content:\n\n```ts twoslash [./setup/preparser.ts]\nimport { definePreparserSetup } from '@slidev/types'\n\nexport default definePreparserSetup(() => {\n  return [\n    {\n      async transformSlide(content, frontmatter) {\n        if ('_scale' in frontmatter) {\n          return [\n            `<Transform :scale=${frontmatter._scale}>`,\n            '',\n            content,\n            '',\n            '</Transform>'\n          ].join('\\n')\n        }\n      },\n    },\n  ]\n})\n```\n\nAnd that's it.\n\n### Use case 3: using custom frontmatter to transform note\n\nImagine a case where you want to replace the slides default notes with custom notes.\nFor instance, you might want to write your `slides.md` as follows:\n\n<!-- eslint-skip -->\n\n```md\n---\nlayout: quote\n_note: notes/note.md\n---\n\n# Welcome\n\n> great!\n\n<!--\nDefault slide notes\n-->\n```\n\nHere we used an underscore in `_note` to avoid possible conflicts with existing frontmatter properties.\n\nTo handle this `_note: ...` syntax in the frontmatter, create a `./setup/preparser.ts` file with the following content:\n\n```ts twoslash [./setup/preparser.ts]\nimport fs, { promises as fsp } from 'node:fs'\nimport { definePreparserSetup } from '@slidev/types'\n\nexport default definePreparserSetup(() => {\n  return [\n    {\n      async transformNote(note, frontmatter) {\n        if ('_note' in frontmatter && fs.existsSync(frontmatter._note)) {\n          try {\n            const newNote = await fsp.readFile(frontmatter._note, 'utf8')\n            return newNote\n          }\n          catch (err) {\n          }\n        }\n\n        return note\n      },\n    },\n  ]\n})\n```\n"
  },
  {
    "path": "docs/custom/config-routes.md",
    "content": "# Configure Routes\n\n<Environment type=\"client\" />\n\nAdd custom pages to the Slidev app.\n\n## Usage\n\nCreate `./setup/routes.ts` with the following content:\n\n```ts twoslash [./setup/routes.ts]\nimport { defineRoutesSetup } from '@slidev/types'\n\nexport default defineRoutesSetup((routes) => {\n  return [\n    ...routes,\n    {\n      path: '/my-page',\n      // ---cut-start---\n      // @ts-expect-error missing types\n      // ---cut-end---\n      component: () => import('../pages/my-page.vue'),\n    },\n  ]\n})\n```\n\nLearn more about routes in the [Vue Router documentation](https://router.vuejs.org/).\n"
  },
  {
    "path": "docs/custom/config-shortcuts.md",
    "content": "# Configure Shortcuts\n\n<Environment type=\"client\" />\n\n## Getting started\n\nCreate `./setup/shortcuts.ts` with the following content:\n\n```ts twoslash [./setup/shortcuts.ts]\nimport type { NavOperations, ShortcutOptions } from '@slidev/types'\nimport { defineShortcutsSetup } from '@slidev/types'\n\nexport default defineShortcutsSetup((nav: NavOperations, base: ShortcutOptions[]) => {\n  return [\n    ...base, // keep the existing shortcuts\n    {\n      key: 'enter',\n      fn: () => nav.next(),\n      autoRepeat: true,\n    },\n    {\n      key: 'backspace',\n      fn: () => nav.prev(),\n      autoRepeat: true,\n    },\n  ]\n})\n```\n\nIn the setup function, you can customize the keyboard shortcuts by returning a new array of shortcuts. The above example binds the `next` operation to <kbd>enter</kbd> and the `prev` operation to <kbd>backspace</kbd>.\n\nPlease refer to [Navigation Actions](../guide/ui#navigation-actions) section for the default shortcuts and navigation operations.\n\n## Key Binding Format\n\nThe `key` of each shortcut can be either a string (e.g. `'Shift+Ctrl+A'`) or a computed boolean. Please refer to [`useMagicKeys` from VueUse](https://vueuse.org/core/useMagicKeys/) for\n"
  },
  {
    "path": "docs/custom/config-transformers.md",
    "content": "# Configure Transformers\n\n<Environment type=\"node\" />\n\nThis setup function allows you to define custom transformers for the markdown content of **each slide**. This is useful when you want to add custom Markdown syntax and render custom code blocks. To start, create a `./setup/transformers.ts` file with the following content:\n\n````ts twoslash [setup/transformers.ts]\nimport type { MarkdownTransformContext } from '@slidev/types'\nimport { defineTransformersSetup } from '@slidev/types'\n\nfunction myCodeblock(ctx: MarkdownTransformContext) {\n  console.log('index in presentation', ctx.slide.index)\n  ctx.s.replace(\n    /^```myblock *(\\{[^\\n]*\\})?\\n([\\s\\S]+?)\\n```/gm,\n    (full, options = '', code = '') => {\n      return `...`\n    },\n  )\n}\n\nexport default defineTransformersSetup(() => {\n  return {\n    pre: [],\n    preCodeblock: [myCodeblock],\n    postCodeblock: [],\n    post: [],\n  }\n})\n````\n\nThe return value should be the custom options for the transformers. The `pre`, `preCodeblock`, `postCodeblock`, and `post` are arrays of functions that will be called in order to transform the markdown content. The order of the transformers is:\n\n1. `pre` from your project\n2. `pre` from addons and themes\n3. Import snippets syntax and Shiki magic move\n4. `preCodeblock` from your project\n5. `preCodeblock` from addons and themes\n6. Built-in special code blocks like Mermaid, Monaco and PlantUML\n7. `postCodeblock` from addons and themes\n8. `postCodeblock` from your project\n9. Other built-in transformers like code block wrapping\n10. `post` from addons and themes\n11. `post` from your project\n"
  },
  {
    "path": "docs/custom/config-unocss.md",
    "content": "# Configure UnoCSS\n\n<Environment type=\"node\" />\n\n[UnoCSS](https://unocss.dev) is now the default CSS framework for Slidev since v0.42.0. UnoCSS is a fast atomic CSS engine that has full flexibility and extensibility. Most of the Tailwind CSS classes are supported **out-of-box**, and you can also extend it with your own configurations.\n\nBy default, Slidev enables the following presets out-of-box:\n\n- [@unocss/preset-wind3](https://unocss.dev/presets/wind3) - Tailwind / Windi CSS compatible utilities\n- [@unocss/preset-attributify](https://unocss.dev/presets/attributify) - Attributify mode\n- [@unocss/preset-icons](https://unocss.dev/presets/icons) - Use any icons as class\n- [@unocss/preset-web-fonts](https://unocss.dev/presets/web-fonts) - Use web fonts at ease\n- [@unocss/transformer-directives](https://unocss.dev/transformers/directives) - Use `@apply` in CSS\n\nSlidev also adds shortcuts as can be seen in its [source code](https://github.com/slidevjs/slidev/blob/main/packages/client/uno.config.ts).\n\nYou can therefore style your content the way you want. For example:\n\n```html\n<div class=\"grid pt-4 gap-4 grid-cols-[100px,1fr]\">\n\n### Name\n\n- Item 1\n- Item 2\n\n</div>\n```\n\n## Configurations\n\nYou can create `uno.config.ts` under the root of your project to extend the builtin configurations\n\n```ts twoslash [uno.config.ts]\nimport { defineConfig } from 'unocss'\n\nexport default defineConfig({\n  shortcuts: {\n    // custom the default background\n    'bg-main': 'bg-white text-[#181818] dark:(bg-[#121212] text-[#ddd])',\n  },\n  // ...\n})\n```\n\nLearn more about [UnoCSS configurations](https://unocss.dev/guide/config-file)\n"
  },
  {
    "path": "docs/custom/config-vite.md",
    "content": "# Configure Vite and Plugins\n\n<Environment type=\"node\" />\n\nSlidev is powered by [Vite](https://vitejs.dev/) under the hood. This means you can leverage Vite's great plugin system to customize your slides even further.\n\nThe `vite.config.ts` will be respected if you have one, and will be merged with the Vite config provided by Slidev, your theme and the addons.\n\n## Configure Internal Plugins\n\nSlidev internally adds the following plugins to Vite:\n\n- [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue)\n- [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components)\n- [unplugin-icons](https://github.com/unplugin/unplugin-icons)\n- [vite-plugin-vue-markdown](https://github.com/unplugin/unplugin-vue-markdown)\n- [vite-plugin-remote-assets](https://github.com/antfu/vite-plugin-remote-assets)\n- [unocss/vite](https://github.com/unocss/unocss/tree/main/packages/vite)\n\nTo configure the built-in plugins listed above, create a `vite.config.ts` with the following content. Please note that Slidev has some [default configurations](https://github.com/slidevjs/slidev/blob/main/packages/slidev/node/vite/index.ts) for those plugins, this usage will override some of them, which may potentially cause the app to break. Please treat this as **an advanced feature**, and make sure you know what you are doing before moving on.\n\n```ts twoslash [vite.config.ts]\n/* eslint-disable import/first */\n/// <reference types=\"@slidev/types\" />\nimport type MarkdownExit from 'markdown-exit'\n\ndeclare const MyPlugin: (md: any) => void\n// ---cut---\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  slidev: {\n    vue: {\n      /* vue options */\n    },\n    markdown: {\n      /* markdown-exit options */\n      markdownSetup(md) {\n        /* custom markdown-exit plugins */\n        md.use(MyPlugin)\n      },\n    },\n    /* options for other plugins */\n  },\n})\n```\n\nSee the [type declarations](https://github.com/slidevjs/slidev/blob/main/packages/types/src/vite.ts#L11) for more options.\n\n::: warning\nIt is not allowed to re-add plugins that has been used internally be Slidev. For example, instead of\n\n```ts twoslash\nimport Vue from '@vitejs/plugin-vue'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    Vue({\n      /* vue options */\n    })\n  ],\n})\n```\n\nPlease pass the Vue options to the `slidev.vue` field as described above\n:::\n\n## Add Custom Plugins based on Slide data\n\nUsually you can add Vite plugins into your `vite.config.ts` (see above).\nHowever, if you want to add plugins based on the slide data, you need to add a `./setup/vite-plugins.ts` with the following content:\n\n```ts twoslash\nimport { defineVitePluginsSetup } from '@slidev/types'\n\nexport default defineVitePluginsSetup((options) => {\n  return [\n    // Your plugins here\n    // Slide data is available as options.data.slides\n  ]\n})\n```\n"
  },
  {
    "path": "docs/custom/config-vue.md",
    "content": "# Configure Vue App\n\n<Environment type=\"client\" />\n\nSlidev uses [Vue 3](https://v3.vuejs.org/) to render the application on the client side. You can extend the app to add custom plugins or configurations.\n\nCreate `./setup/main.ts` with the following content:\n\n```ts twoslash [setup/main.ts]\n/* eslint-disable import/first */\nimport type { Plugin } from 'vue'\n\ndeclare const YourPlugin: Plugin\n// ---cut---\nimport { defineAppSetup } from '@slidev/types'\n\nexport default defineAppSetup(({ app, router }) => {\n  // Vue App\n  app.use(YourPlugin)\n})\n```\n\nThis can also be used as the main entrance of your Slidev app to do some initializations before the app starts.\n\nLearn more: [Vue Application API](https://v3.vuejs.org/api/application-api.html#component).\n"
  },
  {
    "path": "docs/custom/directory-structure.md",
    "content": "# Directory Structure\n\nSlidev employs some directory structure conventions to minimize the configuration surface and to make the functionality extensions flexible and intuitive.\n\nThe conventional directory structure is:\n\n```bash\nyour-slidev/\n  ├── components/       # custom components\n  ├── layouts/          # custom layouts\n  ├── public/           # static assets\n  ├── setup/            # custom setup / hooks\n  ├── snippets/         # code snippets\n  ├── styles/           # custom style\n  ├── index.html        # injections to index.html\n  ├── slides.md         # the main slides entry\n  └── vite.config.ts    # extending vite config\n```\n\nAll of them are optional.\n\n## Components\n\nPattern: `./components/*.{vue,js,ts,jsx,tsx,md}`\n\n<LinkCard link=\"guide/component\" />\n\n## Layouts\n\nPattern: `./layouts/*.{vue,js,ts,jsx,tsx}`\n\n<LinkCard link=\"guide/layout\" />\n\n## Public\n\nPattern: `./public/*`\n\nAssets in this directory will be served at root path `/` during dev, and copied to the root of the dist directory as-is. Read more about [Assets Handling](../guide/faq#assets-handling).\n\n## Style\n\nPattern: `./style.css` | `./styles/index.{css,js,ts}`\n\nFiles following this convention will be injected to the App root. If you need to import multiple CSS entries, you can create the following structure and manage the import order yourself.\n\n:::warning\nGlobal CSS here also applies to the presenter UI. Prefer scoping styles to individual slides, or wrap your selectors under `.slidev-layout` to avoid leaking styles into presenter mode.\n\n**Example:** Use `.slidev-layout .grid { ... }` instead of just `.grid { ... }`.\n:::\n\n```bash\nyour-slidev/\n  ├── ...\n  └── styles/\n      ├── index.ts\n      ├── base.css\n      ├── code.css\n      └── layouts.css\n```\n\n```ts\n// styles/index.ts\n\nimport './base.css'\nimport './code.css'\nimport './layouts.css'\n```\n\nStyles will be processed by [UnoCSS](https://unocss.dev/) and [PostCSS](https://postcss.org/), so you can use CSS nesting and [at-directives](https://unocss.dev/transformers/directives#apply) and Nested CSS out-of-box. For example:\n\n<!-- eslint-skip -->\n\n```css\n.slidev-layout {\n  --uno: px-14 py-10 text-[1.1rem];\n\n  h1, h2, h3, h4, p, div {\n    --uno: select-none;\n  }\n\n  pre, code {\n    --uno: select-text;\n  }\n\n  a {\n    color: theme('colors.primary');\n  }\n}\n```\n\nLearn more about the syntax [here](https://unocss.dev/transformers/directives#apply).\n\n## `index.html`\n\nPattern: `index.html`\n\nThe `index.html` provides the ability to inject meta tags and/or scripts to the main `index.html`\n\nFor example, for the following custom `index.html`:\n\n```html [index.html]\n<head>\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n  <link href=\"https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Nunito+Sans:wght@200;400;600&display=swap\" rel=\"stylesheet\">\n</head>\n\n<body>\n  <script src=\"./your-scripts\"></script>\n</body>\n```\n\nThe final hosted `index.html` will be:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <link rel=\"icon\" type=\"image/png\" href=\"https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png\">\n  <!-- injected head -->\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n  <link href=\"https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Nunito+Sans:wght@200;400;600&display=swap\" rel=\"stylesheet\">\n</head>\n<body>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"__ENTRY__\"></script>\n  <!-- injected body -->\n  <script src=\"./your-scripts\"></script>\n</body>\n</html>\n```\n\n## Global Layers\n\nPattern: `global-top.vue` | `global-bottom.vue` | `custom-nav-controls.vue` | `slide-top.vue` | `slide-bottom.vue`\n\n<LinkCard link=\"features/global-layers\" />\n"
  },
  {
    "path": "docs/custom/index.md",
    "content": "# Customizations\n\nSlidev is fully customizable, from styling to tooling configurations. It allows you to configure the tools underneath ([Vite](/custom/config-vite), [UnoCSS](/custom/config-unocss), [Monaco](/custom/config-monaco), etc.)\n\n## Slides Deck Configs {#headmatter}\n\nYou can configure the whole slides project in the frontmatter of your **first** slide (i.e. headmatter). The following shows the default value for each option:\n\n```yaml\n---\n# theme id, package name, or local path\n# Learn more: https://sli.dev/guide/theme-addon.html#use-theme\ntheme: default\n# addons, can be a list of package names or local paths\n# Learn more: https://sli.dev/guide/theme-addon.html#use-addon\naddons: []\n# title of your slide, will inferred from the first header if not specified\ntitle: Slidev\n# titleTemplate for the webpage, `%s` will be replaced by the slides deck's title\ntitleTemplate: '%s - Slidev'\n# information for your slides, can be a Markdown string\ninfo: false\n# author field for exported PDF or PPTX\nauthor: Your Name Here\n# keywords field for exported PDF, comma-delimited\nkeywords: keyword1,keyword2\n\n# enable presenter mode, can be boolean, 'dev' or 'build'\npresenter: true\n# enable browser exporter, can be boolean, 'dev' or 'build'\nbrowserExporter: dev\n# enabled pdf downloading in SPA build, can also be a custom url\ndownload: false\n# filename of the export file\nexportFilename: slidev-exported\n# export options\n# use export CLI options in camelCase format\n# Learn more: https://sli.dev/guide/exporting.html\nexport:\n  format: pdf\n  timeout: 30000\n  dark: false\n  withClicks: false\n  withToc: false\n# enable twoslash, can be boolean, 'dev' or 'build'\ntwoslash: true\n# show line numbers in code blocks\nlineNumbers: false\n# enable monaco editor, can be boolean, 'dev' or 'build'\nmonaco: true\n# Where to load monaco types from, can be 'cdn', 'local' or 'none'\nmonacoTypesSource: local\n# explicitly specify extra local packages to import the types for\nmonacoTypesAdditionalPackages: []\n# explicitly specify extra local modules as dependencies of monaco runnable\nmonacoRunAdditionalDeps: []\n# download remote assets in local using vite-plugin-remote-assets, can be boolean, 'dev' or 'build'\nremoteAssets: false\n# controls whether texts in slides are selectable\nselectable: true\n# enable slide recording, can be boolean, 'dev' or 'build'\nrecord: dev\n# enable Slidev's context menu, can be boolean, 'dev' or 'build'\ncontextMenu: true\n# enable wake lock, can be boolean, 'dev' or 'build'\nwakeLock: true\n# take snapshot for each slide in the overview\noverviewSnapshots: false\n\n# force color schema for the slides, can be 'auto', 'light', or 'dark'\ncolorSchema: auto\n# router mode for vue-router, can be \"history\" or \"hash\"\nrouterMode: history\n# aspect ratio for the slides\naspectRatio: 16/9\n# real width of the canvas, unit in px\ncanvasWidth: 980\n# used for theme customization, will inject root styles as `--slidev-theme-x` for attribute `x`\nthemeConfig:\n  primary: '#5d8392'\n\n# favicon, can be a local file path or URL\nfavicon: 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png'\n# URL of PlantUML server used to render diagrams\n# Learn more: https://sli.dev/features/plantuml.html\nplantUmlServer: https://www.plantuml.com/plantuml\n# fonts will be auto-imported from Google fonts\n# Learn more: https://sli.dev/custom/config-fonts.html\nfonts:\n  sans: Roboto\n  serif: Roboto Slab\n  mono: Fira Code\n\n# default frontmatter applies to all slides\ndefaults:\n  layout: default\n  # ...\n\n# drawing options\n# Learn more: https://sli.dev/guide/drawing.html\ndrawings:\n  enabled: true\n  persist: false\n  presenterOnly: false\n  syncAll: true\n\n# HTML tag attributes\nhtmlAttrs:\n  dir: ltr\n  lang: en\n\n# SEO meta tags\nseoMeta:\n  ogTitle: Slidev Starter Template\n  ogDescription: Presentation slides for developers\n  ogImage: https://cover.sli.dev\n  ogUrl: https://example.com\n  twitterCard: summary_large_image\n  twitterTitle: Slidev Starter Template\n  twitterDescription: Presentation slides for developers\n  twitterImage: https://cover.sli.dev\n  twitterSite: username\n  twitterUrl: https://example.com\n---\n```\n\nCheck out the [type definitions](https://github.com/slidevjs/slidev/blob/main/packages/types/src/config.ts) for more details.\n\n## Per-slide Configs {#frontmatter}\n\nAlso every slide accepts the following configuration in its frontmatter block. The following shows the default value for each option:\n\n```yaml\n---\n# custom clicks count\n# Learn more: https://sli.dev/guide/animations#total\nclicks: 0\n# custom start clicks count\nclicksStart: 0\n# completely disable and hide the slide\ndisabled: false\n# the same as `disabled`\nhide: false\n# hide the slide for the <Toc> components\nhideInToc: false\n# defines the layout component applied to the slide\nlayout: <\"cover\" if the slide is the first slide, otherwise \"default\">\n# override the title level for the <TitleRenderer> and <Toc> components\n# only if `title` has also been declared\nlevel: 1\n# mount this slide before entering\npreload: true\n# create a route alias that can be used in the URL or with the <Link> component\nrouteAlias: undefined # or string\n# includes a markdown file\n# Learn more: https://sli.dev/guide/syntax.html#importing-slides\nsrc: undefined # or string\n# override the title for the <TitleRenderer> and <Toc> components\n# only if `title` has also been declared\ntitle: undefined # or string\n# defines the transition between the slide and the next one\n# Learn more: https://sli.dev/guide/animations.html#slide-transitions\ntransition: undefined # or BuiltinSlideTransition | string | TransitionGroupProps | null\n# custom zoom scale\n# useful for slides with a lot of content\nzoom: 1\n# used as positions of draggable elements\n# Learn more: https://sli.dev/features/draggable.html\ndragPos: {} # type: Record<string, string>\n---\n```\n\nCheck out the [type definition](https://github.com/slidevjs/slidev/blob/main/packages/types/src/frontmatter.ts#L260) for more details.\n\n## Directory Structure\n\nSlidev uses directory structure conventions to minimalize the configuration surface and make extensions in functionality flexible and intuitive.\n\nRefer to the [Directory Structure](/custom/directory-structure) section.\n\n## Config Tools\n\n<script setup>\nimport VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'\nimport customizations from '../.vitepress/customizations'\n</script>\n\n<li v-for=\"c of customizations.slice(2)\" :key=\"c.text\">\n  <VPLink :href=\"c.link\">\n    {{ c.text }}\n  </VPLink>\n</li>\n"
  },
  {
    "path": "docs/features/block-frontmatter.md",
    "content": "---\ndepends:\n  - guide/syntax\nrelates:\n  - features/prettier-plugin\ntags: [syntax]\ndescription: |\n  Use a YAML code block as the frontmatter.\n---\n\n# Block Frontmatter\n\nThe usual way to define frontmatters of slides is concise, but may lack of highlighting and formatter support. To solve this, you can use a YAML block at the very beginning of the slide content as the frontmatter of the slide:\n\n````md\n---\ntheme: default\n---\n\n# Slide 1\n\n---\n\n```yaml\nlayout: quote\n```\n\n# Slide 2\n\n---\n\n# Slide 3\n````\n\n::: warning About headmatter\n\nHeadmatter in Slidev is exactly the usual called \"frontmatter\" of the a Markdown file, which is supported by most of the Markdown editors and formatters. So you can't use a YAML block as the headmatter of the whole slide deck.\n\n:::\n"
  },
  {
    "path": "docs/features/build-with-pdf.md",
    "content": "---\ndepends:\n  - guide/exporting\n  - guide/hosting\nrelates:\n  - CLI export options: /builtin/cli#export\n  - Headmatter export options: /custom/#headmatter\ntags: [export, build]\ndescription: |\n  Generate a downloadable PDF along with your slides build.\n---\n\n# Generate PDF when Building\n\nYou can provide a downloadable PDF in your built slides with the following config in headmatter:\n\n```md\n---\ndownload: true\n---\n```\n\nSlidev will generate a PDF file along with the build, and a download button will be displayed in the build.\n\nYou can also provide a custom URL for the PDF. In that case, the rendering process will be skipped.\n\n```md\n---\ndownload: 'https://myside.com/my-talk.pdf'\n---\n```\n\nThis can also be done with the CLI option `--download` (`boolean` only).\n\n```bash\n$ slidev build --download\n```\n\nWhen using the download option, you can also provide the export options via:\n\n- [CLI export options](/builtin/cli#export)\n- [Headmatter export options](/custom/#frontmatter-configures)\n"
  },
  {
    "path": "docs/features/bundle-remote-assets.md",
    "content": "---\nrelates:\n  - vite-plugin-remote-assets: https://github.com/antfu/vite-plugin-remote-assets\ntags: [build]\ndescription: |\n  Download and bundle remote assets when building your slides.\n---\n\n# Bundle Remote Assets\n\nJust like you would do in markdown, you can use images pointing to a remote or local URL.\n\nFor remote assets, the built-in [`vite-plugin-remote-assets`](https://github.com/antfu/vite-plugin-remote-assets) will cache them onto the disk at first run, ensuring instant loading even for large images later on.\n\n```md\n![Remote Image](https://sli.dev/favicon.png)\n```\n\nFor local assets, put them into the [`public` folder](/custom/directory-structure.html#public) and reference them with a **leading slash** (i.e., `/pic.png`, NOT `./pic.png`, which is relative to the working file).\n\n```md\n![Local Image](/pic.png)\n```\n\nIf you want to apply custom sizes or styles, you can convert them to the `<img>` tag:\n\n```html\n<img src=\"/pic.png\" class=\"m-40 h-40 rounded shadow\" />\n```\n"
  },
  {
    "path": "docs/features/canvas-size.md",
    "content": "---\nrelates:\n  - guide/faq#adjust-size\n  - features/zoom-slide\n  - features/transform-component\ntags: [layout]\ndescription: |\n  Set the size for all your slides.\n---\n\n# Slide Canvas Size\n\nSlidev allows you to set the size of the slide canvas via the `canvasWidth` and `aspectRatio` options in the headmatter:\n\n```md\n---\n# aspect ratio for the slides\naspectRatio: 16/9\n# real width of the canvas, unit in px\ncanvasWidth: 980\n---\n\n# Your slides here\n```\n\nTo scale several slides in your presentation, you can use the `zoom` option:\n\n<LinkCard link=\"features/zoom-slide\" />\n\nTo adjust the size of some elements on your slides, you can use the `Transform` component:\n\n<LinkCard link=\"features/transform-component\" />\n"
  },
  {
    "path": "docs/features/click-marker.md",
    "content": "---\ndepends:\n  - guide/syntax#notes\n  - guide/animations\nsince: v0.48.0\ntags: [presenter, animation]\ndescription: |\n  Highlighting notes and auto-scrolling to the active section of notes.\n---\n\n# Click Markers\n\nFor some slides you may have longer notes that could be hard to find your place. Slidev supports click markers that allow highlighting and auto-scrolling to the section of notes from your corresponding content. Put `[click]` markers at the beginning of any line in your notes for the timing you need to go to another [click](/guide/animations#click-animation). You may skip `n` clicks by using `[click:{n+1}]`. For example:\n\n```md\n<!--\nContent before the first click\n\n[click] This will be highlighted after the first click\n\nAlso highlighted after the first click\n\n- [click] This list element will be highlighted after the second click\n\n[click:3] Last click (skip two clicks)\n-->\n```\n\nSlidev divides the content between the click markers and highlights it in presenter notes, synchronized with your slide progress.\n\n<video src=\"https://github.com/slidevjs/slidev/assets/11247099/40014e34-67cd-4830-8c8d-8431754a3672\" controls rounded shadow w-full></video>\n"
  },
  {
    "path": "docs/features/code-block-line-numbers.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\ntags: [codeblock]\ndescription: |\n  Enable line numbering for all code blocks across the slides or individually.\n---\n\n# Line Numbers\n\nYou can enable line numbering for all code blocks across the slides by setting `lineNumbers: true` in the headmatter, or enable each code block individually by setting `lines: true`.\n\nYou can also set the starting line for each code block and highlight the lines accordingly via `{startLine: number}`, which defaults to 1.\n\n````md\n```ts {6,7}{lines:true,startLine:5}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\nNote that you can use `{*}` as a placeholder of <LinkInline link=\"features/line-highlighting\" />:\n\n````md\n```ts {*}{lines:true,startLine:5}\n// ...\n```\n````\n"
  },
  {
    "path": "docs/features/code-block-max-height.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\ntags: [codeblock, layout]\ndescription: |\n  Set a maximum height for a code block and enable scrolling.\n---\n\n# Max Height\n\nIf the code doesn't fit into one slide, you use the `maxHeight` to set a fixed height and enable scrolling:\n\n````md\n```ts {2|3|7|12}{maxHeight:'100px'}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n/// ...as many lines as you want\nconst c = add(1, 2)\n```\n````\n\nNote that you can use `{*}` as a placeholder of <LinkInline link=\"features/line-highlighting\" />:\n\n````md\n```ts {*}{maxHeight:'100px'}\n// ...\n```\n````\n"
  },
  {
    "path": "docs/features/code-groups.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\ntags: [codeblock]\ndescription: |\n  Group multiple code blocks and automatically match icon by the title name.\n---\n\n# Code Groups\n\n> [!NOTE]\n> This feature requires [Comark Syntax](/features/comark#comark-syntax). Enable `comark: true` to use it.\n\nYou can group multiple code blocks like this:\n\n````md\n::code-group\n\n```sh [npm]\nnpm i @slidev/cli\n```\n\n```sh [yarn]\nyarn add @slidev/cli\n```\n\n```sh [pnpm]\npnpm add @slidev/cli\n```\n\n::\n````\n\n## Title Icon Matching\n\n`code groups`, `code block` and [`Shiki Magic Move`](/features/shiki-magic-move) also supports the automatically icon matching by the title name.\n\n![code-groups-demo](/assets/code-groups-demo.png)\n\n::: info\n\nBy default, we provide some built-in icons, you can use them by install [@iconify-json/vscode-icons](https://www.npmjs.com/package/@iconify-json/vscode-icons).\n\n:::\n\n::: details All built-in icons\n\n```js\nconst builtinIcons = {\n  // package managers\n  'pnpm': 'i-vscode-icons:file-type-light-pnpm',\n  'npm': 'i-vscode-icons:file-type-npm',\n  'yarn': 'i-vscode-icons:file-type-yarn',\n  'bun': 'i-vscode-icons:file-type-bun',\n  'deno': 'i-vscode-icons:file-type-deno',\n  // frameworks\n  'vue': 'i-vscode-icons:file-type-vue',\n  'svelte': 'i-vscode-icons:file-type-svelte',\n  'angular': 'i-vscode-icons:file-type-angular',\n  'react': 'i-vscode-icons:file-type-reactjs',\n  'next': 'i-vscode-icons:file-type-light-next',\n  'nuxt': 'i-vscode-icons:file-type-nuxt',\n  'solid': 'logos:solidjs-icon',\n  'astro': 'i-vscode-icons:file-type-light-astro',\n  // bundlers\n  'rollup': 'i-vscode-icons:file-type-rollup',\n  'webpack': 'i-vscode-icons:file-type-webpack',\n  'vite': 'i-vscode-icons:file-type-vite',\n  'esbuild': 'i-vscode-icons:file-type-esbuild',\n  // configuration files\n  'package.json': 'i-vscode-icons:file-type-node',\n  'tsconfig.json': 'i-vscode-icons:file-type-tsconfig',\n  '.npmrc': 'i-vscode-icons:file-type-npm',\n  '.editorconfig': 'i-vscode-icons:file-type-editorconfig',\n  '.eslintrc': 'i-vscode-icons:file-type-eslint',\n  '.eslintignore': 'i-vscode-icons:file-type-eslint',\n  'eslint.config': 'i-vscode-icons:file-type-eslint',\n  '.gitignore': 'i-vscode-icons:file-type-git',\n  '.gitattributes': 'i-vscode-icons:file-type-git',\n  '.env': 'i-vscode-icons:file-type-dotenv',\n  '.env.example': 'i-vscode-icons:file-type-dotenv',\n  '.vscode': 'i-vscode-icons:file-type-vscode',\n  'tailwind.config': 'vscode-icons:file-type-tailwind',\n  'uno.config': 'i-vscode-icons:file-type-unocss',\n  'unocss.config': 'i-vscode-icons:file-type-unocss',\n  '.oxlintrc': 'i-vscode-icons:file-type-oxlint',\n  'vue.config': 'i-vscode-icons:file-type-vueconfig',\n  // filename extensions\n  '.mts': 'i-vscode-icons:file-type-typescript',\n  '.cts': 'i-vscode-icons:file-type-typescript',\n  '.ts': 'i-vscode-icons:file-type-typescript',\n  '.tsx': 'i-vscode-icons:file-type-typescript',\n  '.mjs': 'i-vscode-icons:file-type-js',\n  '.cjs': 'i-vscode-icons:file-type-js',\n  '.json': 'i-vscode-icons:file-type-json',\n  '.js': 'i-vscode-icons:file-type-js',\n  '.jsx': 'i-vscode-icons:file-type-js',\n  '.md': 'i-vscode-icons:file-type-markdown',\n  '.py': 'i-vscode-icons:file-type-python',\n  '.ico': 'i-vscode-icons:file-type-favicon',\n  '.html': 'i-vscode-icons:file-type-html',\n  '.css': 'i-vscode-icons:file-type-css',\n  '.scss': 'i-vscode-icons:file-type-scss',\n  '.yml': 'i-vscode-icons:file-type-light-yaml',\n  '.yaml': 'i-vscode-icons:file-type-light-yaml',\n  '.php': 'i-vscode-icons:file-type-php',\n}\n```\n\n:::\n\n## Custom Icons\n\nYou can use any name from the [iconify](https://icones.js.org) collection by using the `~icon~` syntax, for example:\n\n````md\n```js [npm ~i-uil:github~]\nconsole.log('Hello, GitHub!')\n```\n````\n\nTo make it work, you need to:\n\n1. Install the icon's collection.\n\n:::code-group\n\n```sh [npm]\nnpm add @iconify-json/uil\n```\n\n```sh [yarn]\nyarn add @iconify-json/uil\n```\n\n```sh [pnpm]\npnpm add @iconify-json/uil\n```\n\n```sh [bun]\nbun add @iconify-json/uil\n```\n\n:::\n\n2. Add the icon to the `uno.config.ts` file.\n\n```ts [uno.config.ts] {4-6}\nimport { defineConfig } from 'unocss'\n\nexport default defineConfig({\n  safelist: [\n    'i-uil:github',\n  ],\n})\n```\n"
  },
  {
    "path": "docs/features/comark.md",
    "content": "---\nrelates:\n  - Comark Syntax: https://comark.dev/syntax/markdown\n  - '@comark/markdown-it': https://github.com/comarkdown/comark\nsince: v0.43.0\ntags: [syntax, styling]\ndescription: |\n  A powerful syntax to enhance your markdown content with components and styles.\n---\n\n# Comark Syntax\n\nSlidev supports optional [Comark Syntax](https://comark.dev/syntax/markdown) (formerly known as MDC, Markdown Components) powered by [`@comark/markdown-it`](https://github.com/comarkdown/comark).\n\nYou can enable it by adding `comark: true` to the frontmatter of your markdown file.\n\n```mdc\n---\ncomark: true\n---\n\nThis is a [red text]{style=\"color:red\"} :inline-component{prop=\"value\"}\n\n![](/image.png){width=500px lazy}\n\n::block-component{prop=\"value\"}\nThe **default** slot\n::\n```\n\nLearn more about [Comark Syntax](https://comark.dev/syntax/markdown).\n"
  },
  {
    "path": "docs/features/direction-variant.md",
    "content": "---\nrelates:\n  - UnoCSS Variants: https://unocss.dev/config/variants#variants\nsince: v0.48.0\ntags: [navigation, styling]\ndescription: |\n  Apply different styles and animations based on the navigation direction.\n---\n\n# Navigation Direction Variants\n\nYou may want to apply different classes based on whether it's navigating forward or backward. The `.slidev-nav-go-forward` or `.slidev-nav-go-backward` class will be applied to the slide container when navigating, and you can use them to apply different styles or animations:\n\n```css\n/* example: delay on only forward but not backward */\n.slidev-nav-go-forward .slidev-vclick-target {\n  transition-delay: 500ms;\n}\n.slidev-nav-go-backward .slidev-vclick-target {\n  transition-delay: 0;\n}\n```\n\nTo make it easier, we also provided some [UnoCSS variants](https://github.com/slidevjs/slidev/blob/6adcf2016b8fb0cab65cf150221f1f67a76a2dd8/packages/client/uno.config.ts#L32-L38) for this. You can use the `forward:` or `backward:` prefix to any UnoCSS classes to only enable them in the specific navigation direction:\n\n```html\n<div v-click class=\"transition delay-300\">Element</div> // [!code --]\n<div v-click class=\"transition forward:delay-300\">Element</div> // [!code ++]\n```\n\nIn the above example, the animation is only delayed when navigating forward.\n"
  },
  {
    "path": "docs/features/draggable.md",
    "content": "---\ntags: [layout]\ndescription: |\n  Move, resize, and rotate elements by dragging them with the mouse.\n---\n\n# Draggable Elements\n\nDraggable elements give you the ability to move, resize, and rotate elements by dragging them with the mouse. This is useful for creating floating elements in your slides.\n\n## Directive Usage\n\n### Data from the frontmatter\n\n```md\n---\ndragPos:\n  square: Left,Top,Width,Height,Rotate\n---\n\n<img v-drag=\"'square'\" src=\"https://sli.dev/logo.png\">\n```\n\n### Data from the directive value\n\n::: warning\nSlidev use regex to update the position value in the slide content. If you meet problems, please use the frontmatter to define the values instead.\n:::\n\n```md\n<img v-drag=\"[Left,Top,Width,Height,Rotate]\" src=\"https://sli.dev/logo.png\">\n```\n\n## Component Usage\n\n### Data from the frontmatter\n\n```md\n---\ndragPos:\n  foo: Left,Top,Width,Height,Rotate\n---\n\n<v-drag pos=\"foo\" text-3xl>\n  <div class=\"i-carbon:arrow-up\" />\n  Use the `v-drag` component to have a draggable container!\n</v-drag>\n```\n\n### Data from props\n\n```md\n<v-drag pos=\"Left,Top,Width,Height,Rotate\" text-3xl>\n  <div class=\"i-carbon:arrow-up\" />\n  Use the `v-drag` component to have a draggable container!\n</v-drag>\n```\n\n## Create a Draggable Element\n\nWhen you create a new draggable element, you don't need to specify the position value (but you need to specify the position name if you want to use the frontmatter). Slidev will automatically generate the initial position value for you.\n\n## Automatic Height\n\nYou can set `Height` to `NaN` (in) or `_` (if you use the component) to make the height of the draggable element automatically adjust to its content.\n\n## Controls\n\n- Double-click the draggable element to start dragging it.\n- You can also use the arrow keys to move the element.\n- Hold `Shift` while dragging to preserve its aspect ratio.\n- Click outside the draggable element to stop dragging it.\n\n## Draggable Arrow\n\nThe `<v-drag-arrow>` component creates a draggable arrow element. Simply use it like this:\n\n```md\n<v-drag-arrow />\n```\n\nAnd you will get a draggable arrow element. Other props are the same as [the `Arrow` component](/builtin/components#arrow).\n"
  },
  {
    "path": "docs/features/drawing.md",
    "content": "---\ndepends:\n  - guide/ui#navigation-bar\nrelates:\n  - drauu: https://github.com/antfu/drauu\ntags: [drawing]\ndescription: |\n  Draw and annotate your slides with ease.\n---\n\n# Drawing & Annotations\n\nSlidev comes with a built-in drawing and annotation feature powered by [drauu](https://github.com/antfu/drauu). It allows you to draw and annotate your slides with ease.\n\nTo start, click the <carbon-pen class=\"inline-icon-btn\"/> icon in the [navigation bar](../guide/ui#navigation-bar) to open the drawing toolbar. It's also available in the [Presenter Mode](/guide/ui#presenter-mode). Drawings and annotations you created will be **synced** automatically across all instances in real-time.\n\n<TheTweet id=\"1424027510342250499\" />\n\n## Use with Stylus Pen\n\nWhen using a stylus pen on a tablet (for example, iPad with Apple Pencil), Slidev will intelligently detect the input type. You can directly draw on your slides with the pen without turning on the drawing mode while having your fingers or mouse control the navigation.\n\n## Persist Drawings\n\nThe following frontmatter configuration allows you to persist your drawings as SVGs under `.slidev/drawings` directory and have them inside your exported PDF or hosted site.\n\n```md\n---\ndrawings:\n  persist: true\n---\n```\n\n## Disable Drawings\n\nEntirely:\n\n```md\n---\ndrawings:\n  enabled: false\n---\n```\n\nOnly in Development:\n\n```md\n---\ndrawings:\n  enabled: dev\n---\n```\n\nOnly in Presenter Mode:\n\n```md\n---\ndrawings:\n  presenterOnly: true\n---\n```\n\n## Drawing Syncing\n\nBy default, Slidev syncs up your drawings across all instances. If you are sharing your slides with others, you might want to disable the syncing via:\n\n```md\n---\ndrawings:\n  syncAll: false\n---\n```\n\nWith this config, only the drawing from the presenter instance will be able to sync with others.\n"
  },
  {
    "path": "docs/features/eject-theme.md",
    "content": "---\ndepends:\n  - guide/theme-addon\ntags: [theme, cli]\ndescription: |\n  Eject the installed theme from your project to customize it.\n---\n\n# Eject Theme\n\nIf you want to get full control of the current theme, you can **eject** it to your local file system and modify it as you want. By running the following command\n\n```bash\n$ slidev theme eject\n```\n\nIt will eject the theme you are using currently into `./theme`, and change your frontmatter to\n\n```yaml\n---\ntheme: ./theme\n---\n```\n\nThis could also be helpful if you want to make a theme based on an existing one. If you do, remember to mention the original theme and the author :)\n\nFor more options of the `theme` command, please refer to the [Theme Command](../builtin/cli#theme) section.\n"
  },
  {
    "path": "docs/features/frontmatter-merging.md",
    "content": "---\ndepends:\n  - guide/syntax#importing-slides\n  - features/importing-slides\ntags: [syntax]\ndescription: |\n  Merge frontmatter from multiple markdown files.\n---\n\n# Frontmatter Merging\n\nYou can provide frontmatter instructions from both your main entry and external markdown pages. If there are duplicate keys in them, the ones from the **main entry have the higher priority**. For example:\n\n::: code-group\n\n```md [./slides.md]\n---\nsrc: ./cover.md\nbackground: https://sli.dev/bar.png // [!code highlight]\nclass: text-center\n---\n```\n\n```md [./cover.md]\n---\nlayout: cover\nbackground: https://sli.dev/foo.png // [!code highlight]\n---\n\n# Cover\n\nCover Page\n```\n\n:::\n\nThey will end up being equivalent to the following page:\n\n```md\n---\nlayout: cover\nbackground: https://sli.dev/bar.png // [!code highlight]\nclass: text-center\n---\n\n# Cover\n\nCover Page\n```\n"
  },
  {
    "path": "docs/features/global-layers.md",
    "content": "---\ntags: [navigation, layout]\ndescription: |\n  Create custom components that persist across slides.\n---\n\n# Global Layers\n\nGlobal layers allow you to have custom components that **persist** across slides. This could be useful for having footers, cross-slide animations, global effects, etc.\n\nSlidev provides three layers for this usage, create `global-top.vue`, `global-bottom.vue`, or `custom-nav-controls.vue` under your project root and it will pick up automatically.\n\nThere are also layers for **each** slide: `slide-top.vue` and `slide-bottom.vue`. The usage is similar to the global layers, but they are applied to every slide, so there may be more than one instance of them.\n\n::: tip\nIf you are using `global-top.vue` or `global-bottom.vue` depending on the current navigation state, when exporting, the `--per-slide` option should be used to ensure the correct state is applied to each slide. Or you can use `slide-top.vue` and `slide-bottom.vue` instead.\n:::\n\n## Layers relationship\n\nAt z-axis, from top to bottom:\n\n- NavControls\n  - Customized Navigation Controls (`custom-nav-controls.vue`)\n- Global Top (`global-top.vue`) - single instance\n- Slide Top (`slide-top.vue`) - instance per slide\n- Slide Content\n- Slide Bottom (`slide-bottom.vue`) - instance per slide\n- Global Bottom (`global-bottom.vue`) - single instance\n\n## Example\n\n```html\n<!-- global-bottom.vue -->\n<template>\n  <footer class=\"absolute bottom-0 left-0 right-0 p-2\">Your Name</footer>\n</template>\n```\n\nThe text `Your Name` will appear on all your slides.\n\n```html\n<!-- custom-nav-controls -->\n<template>\n  <button class=\"icon-btn\" title=\"Next\" @click=\"$nav.next\">\n    <div class=\"i-carbon:arrow-right\" />\n  </button>\n</template>\n```\n\nThe button `Next` will appear in NavControls.\n\nTo enable it conditionally, you can use the <LinkInline link=\"guide/global-context\" />\n\n```html\n<!-- hide the footer from Page 4 -->\n<template>\n  <footer\n    v-if=\"$nav.currentPage !== 4\"\n    class=\"absolute bottom-0 left-0 right-0 p-2\"\n  >\n    Your Name\n  </footer>\n</template>\n```\n\n```html\n<!-- hide the footer from \"cover\" layout -->\n<template>\n  <footer\n    v-if=\"$nav.currentLayout !== 'cover'\"\n    class=\"absolute bottom-0 left-0 right-0 p-2\"\n  >\n    Your Name\n  </footer>\n</template>\n```\n\n```html\n<!-- an example footer for pages -->\n<template>\n  <footer\n    v-if=\"$nav.currentLayout !== 'cover'\"\n    class=\"absolute bottom-0 left-0 right-0 p-2\"\n  >\n    {{ $nav.currentPage }} / {{ $nav.total }}\n  </footer>\n</template>\n```\n\n```html\n<!-- custom-nav-controls -->\n<!-- hide the button in Presenter model -->\n<template>\n  <button v-if=\"!$nav.isPresenter\" class=\"icon-btn\" title=\"Next\" @click=\"$nav.next\">\n    <div class=\"i-carbon:arrow-right\" />\n  </button>\n</template>\n```\n"
  },
  {
    "path": "docs/features/icons.md",
    "content": "---\nrelates:\n  - Iconify: https://iconify.design/\n  - Icones: https://icones.js.org/\n  - unplugin-icons: https://github.com/antfu/unplugin-icons\ntags: [components]\ndescription: |\n  Use icons from virtually all open-source icon sets directly in your markdown.\n---\n\n# Icons\n\nSlidev allows you to have access to virtually all open-source icon sets **directly** in your markdown after installing the corresponding package. Powered by [`unplugin-icons`](https://github.com/antfu/unplugin-icons) and [Iconify](https://iconify.design/).\n\nThe naming follows [Iconify](https://iconify.design/)'s convention of `{collection-name}-{icon-name}`. For example:\n\n- `<mdi-account-circle />` - <mdi-account-circle /> from [Material Design Icons](https://github.com/Templarian/MaterialDesign) - [`@iconify-json/mdi`](https://npmjs.com/package/@iconify-json/mdi)\n- `<carbon-badge />` - <carbon-badge /> from [Carbon](https://github.com/carbon-design-system/carbon/tree/main/packages/icons) - [`@iconify-json/carbon`](https://npmjs.com/package/@iconify-json/carbon)\n- `<uim-rocket />` - <uim-rocket /> from [Unicons Monochrome](https://github.com/Iconscout/unicons) - [`@iconify-json/uim`](https://npmjs.com/package/@iconify-json/uim)\n- `<twemoji-cat-with-tears-of-joy />` - <twemoji-cat-with-tears-of-joy /> from [Twemoji](https://github.com/twitter/twemoji) - [`@iconify-json/twemoji`](https://npmjs.com/package/@iconify-json/twemoji)\n- `<logos-vue />` - <logos-vue /> from [SVG Logos](https://github.com/gilbarbara/logos) - [`@iconify-json/logos`](https://npmjs.com/package/@iconify-json/logos)\n- And much more...\n\n::: code-group\n\n```bash [pnpm]\npnpm add @iconify-json/[the-collection-you-want]\n```\n\n```bash [npm]\nnpm install @iconify-json/[the-collection-you-want]\n```\n\n```bash [yarn]\nyarn add @iconify-json/[the-collection-you-want]\n```\n\n```bash [bun]\nbun add @iconify-json/[the-collection-you-want]\n```\n\n```bash [deno]\ndeno add jsr:@iconify-json/[the-collection-you-want]\n```\n\n:::\n\nWe use [Iconify](https://iconify.design) as our data source of icons. You need to install the corresponding icon-set in `dependencies` by following the `@iconify-json/*` pattern. For example, `@iconify-json/mdi` for [Material Design Icons](https://materialdesignicons.com/), `@iconify-json/tabler` for [Tabler](https://tabler-icons.io/). You can refer to [Icônes](https://icones.js.org/) or [Iconify](https://icon-sets.iconify.design/) for all the collections available.\n\n### Styling Icons\n\nYou can style the icons just like other HTML elements. For example:\n\n```html\n<uim-rocket />\n<uim-rocket class=\"text-3xl text-red-400 mx-2\" />\n<uim-rocket class=\"text-3xl text-orange-400 animate-ping\" />\n```\n\n<uim-rocket />\n<uim-rocket class=\"text-3xl text-red-400 mx-2\" />\n<uim-rocket class=\"text-3xl text-orange-400 animate-ping ml-2\" />\n"
  },
  {
    "path": "docs/features/import-snippet.md",
    "content": "---\nrelates:\n  - features/monaco-write\n  - features/monaco-editor\nsince: v0.47.0\ntags: [codeblock, syntax]\ndescription: |\n  Import code snippets from existing files into your slides.\n---\n\n# Import Code Snippets\n\nYou can import code snippets from existing files via the following syntax:\n\n```md\n<<< @/snippets/snippet.js\n```\n\n::: tip\nThe value of `@` corresponds to your package's root directory. It's recommended to put snippets in `@/snippets` in order to be compatible with the Monaco editor. Alternatively, you can also import from relative paths.\n:::\n\nYou can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file:\n\n```md\n<<< @/snippets/snippet.js#region-name\n```\n\nTo explicitly specify the language of the imported code, you can add a language identifier after:\n\n```md\n<<< @/snippets/snippet.js ts\n```\n\nAny code block features like [line highlighting](#line-highlighting) and [Monaco editor](#monaco-editor) are also supported:\n\n```md\n<<< @/snippets/snippet.js {2,3|5}{lines:true}\n<<< @/snippets/snippet.js ts {monaco}{height:200px}\n```\n\nNote that you can use `{*}` as a placeholder of <LinkInline link=\"features/line-highlighting\" />:\n\n<!-- eslint-skip -->\n\n```md\n<<< @/snippets/snippet.js {*}{lines:true}\n```\n"
  },
  {
    "path": "docs/features/importing-slides.md",
    "content": "---\nrelates:\n  - features/frontmatter-merging\ntags: [syntax]\ndescription: |\n  Split your `slides.md` into multiple files for better reusability and organization.\n---\n\n# Importing Slides\n\nYou can split your `slides.md` into multiple files for better reusability and organization. To do this, you can use the `src` frontmatter option to specify the path to the external markdown file. For example:\n\n::: code-group\n\n<!-- eslint-skip -->\n\n```md [./slides.md]\n# Title\n\nThis is a normal page\n\n---\nsrc: ./pages/toc.md // [!code highlight]\n---\n\n<!-- this page will be loaded from './pages/toc.md' -->\n\nContents here are ignored\n\n---\n\n# Page 4\n\nAnother normal page\n\n---\nsrc: ./pages/toc.md   # Reuse the same file // [!code highlight]\n---\n```\n\n```md [./pages/toc.md]\n# Table of Contents\n\nPart 1\n\n---\n\n# Table of Contents\n\nPart 2\n```\n\n:::\n\n## Importing Specific Slides\n\nTo reuse some of the slides inside another Markdown file, you can use the hash part of the import path:\n\n```md\n---\nsrc: ./another-presentation.md#2,5-7\n---\n```\n\nThis will import the slides 2, 5, 6, and 7 from `./another-presentation.md`.\n\n## Frontmatter Merging\n\n<LinkCard link=\"features/frontmatter-merging\" />\n"
  },
  {
    "path": "docs/features/index.data.ts",
    "content": "import { basename } from 'node:path'\nimport { createContentLoader } from 'vitepress'\n\nexport interface Feature {\n  name: string\n  title: string\n  link: string\n  description: string\n  depends: string[]\n  relates: string[]\n  derives: string[]\n  tags: string[]\n  since?: string\n}\n\nexport default createContentLoader('features/*.md', {\n  includeSrc: true,\n  transform(data) {\n    const derivesMap: Record<string, string[]> = {}\n    for (const md of data) {\n      const name = basename(md.url, '.md')\n      if (name === 'index' || name === 'features')\n        continue\n      for (const depend of md.frontmatter.depends ?? []) {\n        const dependName = depend.match(/\\/([\\w-]+)($|#)/)?.[1]\n        if (dependName) {\n          derivesMap[dependName] ??= []\n          derivesMap[dependName].push(`features/${name}`)\n        }\n      }\n    }\n\n    const result: Record<string, Feature> = {}\n    for (const md of data) {\n      const name = basename(md.url, '.md')\n      if (name === 'index' || name === 'features')\n        continue\n      const title = md.src?.match(/^# (.*)$/m)?.[1]?.trim() ?? name\n      const derives = md.frontmatter.derives ?? []\n      for (const d of derivesMap[name] ?? []) {\n        if (!derives.includes(d)) {\n          derives.push(d)\n        }\n      }\n      result[name] = {\n        name,\n        title,\n        link: `/features/${name}.html`,\n        description: md.frontmatter.description ?? '',\n        depends: md.frontmatter.depends ?? [],\n        relates: md.frontmatter.relates ?? [],\n        derives,\n        tags: md.frontmatter.tags ?? [],\n        since: md.frontmatter.since,\n      }\n    }\n    return result\n  },\n})\n\nexport declare const data: Record<string, Feature>\n"
  },
  {
    "path": "docs/features/index.md",
    "content": "---\neditLink: false\nfooter: false\naside: false\noutline: false\nsidebar: false\npageClass: all-features-page\n---\n\n<script setup lang=\"ts\">\nimport { useUrlSearchParams } from '@vueuse/core'\nimport { computed, toRef, ref } from 'vue'\nimport { withBase } from 'vitepress'\nimport { data as features } from './index.data'\n\nconst query = useUrlSearchParams('hash-params', { removeFalsyValues: true })\nconst search = toRef(query, 'search') as Ref<string | null>\nconst tags = toRef(query, 'tags') as Ref<string | null>\nconst tagsArr = computed({\n  get: () => tags.value?.split(',').map(t => t.trim()).filter(Boolean) ?? [],\n  set: (val: string[]) => query.tags = val.join(','),\n})\n\nconst filteredFeatures = computed(() => {\n  const s = search.value?.toLowerCase().trim()\n  const t = tagsArr.value\n  return Object.values(features).filter(feature => {\n    const matchSearch = !s ||\n      feature.name.toLowerCase().includes(s) ||\n      feature.title.toLowerCase().includes(s) ||\n      feature.description.toLowerCase().includes(s)\n\n    const matchTags = !t?.length || t.every(tag =>\n      feature.tags?.some(featureTag =>\n        featureTag.toLowerCase() === tag.toLowerCase()\n      )\n    )\n    return matchSearch && matchTags\n  })\n})\n\nfunction resetFilters() {\n  query.search = null\n  query.tags = null\n}\n\nfunction removeTag(tag: string) {\n  tagsArr.value = tagsArr.value.filter(t => t !== tag)\n}\n</script>\n\n# Features\n\nThis is a list of all the individual features that Slidev provides. Each feature can be used independently and is optional.\n\nYou can also read <LinkInline link=\"guide/\" /> to learn the features by topic.\n\n<ClientOnly>\n<div flex items-center mt-6 gap-6>\n  <div\n    flex items-center rounded-md\n    px3 py2 gap-2 border-2 border-solid border-transparent\n    class=\"bg-$vp-c-bg-alt focus-within:border-color-$vp-c-brand\"\n  >\n    <div class=\"i-carbon:search\" text-sm op-80 />\n    <input\n      v-model=\"search\"\n      type=\"search\" text-base\n      placeholder=\"Search features...\"\n    />\n  </div>\n  <div\n    v-if=\"tagsArr.length\"\n    flex items-center gap-1\n  >\n    <div class=\"i-carbon:tag\" text-sm mr-1 op-80 />\n    <FeatureTag v-for=\"tag in tagsArr\" :key=\"tag\" :tag removable @remove=\"removeTag(tag)\"/>\n  </div>\n</div>\n\n<FeaturesOverview :features=\"filteredFeatures\" />\n\n<div v-if=\"filteredFeatures.length === 0\" class=\"w-full mt-6 op-80 flex flex-col items-center\">\n  No results found\n  <button class=\"block select-button flex-inline gap-1 items-center px-2 py-1 hover:bg-gray-400/10 rounded\" @click=\"resetFilters()\">\n    <div class=\"i-carbon:filter-remove\" />\n    Clear Filters\n  </button>\n</div>\n</ClientOnly>\n\n<style>\n.all-features-page .VPDoc > .container > .content {\n  max-width: 72vw !important;\n}\n</style>\n\n<style>\n:root {\n  overflow-y: scroll;\n}\n</style>\n"
  },
  {
    "path": "docs/features/latex.md",
    "content": "---\nrelates:\n  - Demo: /demo/starter/11\n  - KaTeX: https://katex.org/\ntags: [codeblock, syntax]\ndescription: |\n  Slidev comes with LaTeX support out-of-box, powered by KaTeX.\n---\n\n# LaTeX\n\nSlidev comes with LaTeX support out-of-box, powered by [KaTeX](https://katex.org/).\n\n## Inline\n\nSurround your LaTeX with a single `$` on each side for inline rendering.\n\n```md\n$\\sqrt{3x-1}+(1+x)^2$\n```\n\n## Block\n\nUse two (`$$`) for block rendering. This mode uses bigger symbols and centers\nthe result.\n\n```latex\n$$\n\\begin{aligned}\n\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\varepsilon_0} \\\\\n\\nabla \\cdot \\vec{B} &= 0 \\\\\n\\nabla \\times \\vec{E} &= -\\frac{\\partial\\vec{B}}{\\partial t} \\\\\n\\nabla \\times \\vec{B} &= \\mu_0\\vec{J} + \\mu_0\\varepsilon_0\\frac{\\partial\\vec{E}}{\\partial t}\n\\end{aligned}\n$$\n```\n\n## Line Highlighting\n\nTo highlight specific lines, simply add line numbers within bracket `{}`. Line numbers start counting from 1 by default.\n\n```latex\n$$ {1|3|all}\n\\begin{aligned}\n\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\varepsilon_0} \\\\\n\\nabla \\cdot \\vec{B} &= 0 \\\\\n\\nabla \\times \\vec{E} &= -\\frac{\\partial\\vec{B}}{\\partial t} \\\\\n\\nabla \\times \\vec{B} &= \\mu_0\\vec{J} + \\mu_0\\varepsilon_0\\frac{\\partial\\vec{E}}{\\partial t}\n\\end{aligned}\n$$\n```\n\nThe `at` and `finally` options of [code blocks](#line-highlighting) are also available for LaTeX blocks.\n\n## Chemical equations\n\nTo enable the rendering of chemical equations, the [mhchem](https://github.com/KaTeX/KaTeX/tree/main/contrib/mhchem)\nKaTeX extension needs to be loaded.\n\nCreate `vite.config.ts` with the following content:\n\n```ts\nimport 'katex/contrib/mhchem'\n\nexport default {}\n```\n\nNow chemical equations can be rendered properly.\n\n```latex\n$$\n\\displaystyle{\\ce{B(OH)3 + H2O <--> B(OH)4^- + H+}}\n$$\n```\n\nLearn more: [Syntax](https://mhchem.github.io/MathJax-mhchem)\n\n---\n\n<TheTweet id=\"1392246507793915904\" />\n"
  },
  {
    "path": "docs/features/line-highlighting.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\n  - guide/animations\ntags: [codeblock, animation]\ndescription: |\n  Highlight specific lines in code blocks based on clicks.\n---\n\n# Line Highlighting\n\nTo highlight specific lines, simply add line numbers within brackets `{}`. Line numbers start counting from 1 by default.\n\n````md\n```ts {2,3}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\n## Dynamic Line Highlighting\n\nTo change what's highlighted with multiple clicks, you can use `|` to separate each stage:\n\n````md\n```ts {2-3|5|all}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\nThis will first highlight `a: Ref<number> | number` and `b: Ref<number> | number`, and then `return computed(() => unref(a) + unref(b))` after one click, and lastly, the whole block.\n\nYou can set the line number to `hide` to hide the code block or `none` to not highlight any line:\n\n````md\n```ts {hide|none}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\n::: tip\nLearn more in the [click animations guide](/guide/animations#positioning).\n:::\n"
  },
  {
    "path": "docs/features/mermaid.md",
    "content": "---\nrelates:\n  - Mermaid: https://mermaid.js.org/\n  - Mermaid Live Editor: https://mermaid.live/\n  - Demo Slide: https://sli.dev/demo/starter/12\n  - features/plantuml\ntags: [diagram]\ndescription: |\n  Create diagrams/graphs from textual descriptions, powered by Mermaid.\n---\n\n# Mermaid Diagrams\n\nYou can also create diagrams/graphs from textual descriptions in your Markdown, powered by [Mermaid](https://mermaid.js.org/).\n\nCode blocks marked as `mermaid` will be converted to diagrams, for example:\n\n````md\n```mermaid\nsequenceDiagram\n  Alice->John: Hello John, how are you?\n  Note over Alice,John: A typical interaction\n```\n````\n\nYou can further pass an options object to it to specify the scaling and theming. The syntax of the object is a JavaScript object literal, you will need to add quotes (`'`) for strings and use comma (`,`) between keys.\n\n````md\n```mermaid {theme: 'neutral', scale: 0.8}\ngraph TD\nB[Text] --> C{Decision}\nC -->|One| D[Result 1]\nC -->|Two| E[Result 2]\n```\n````\n\nVisit the [Mermaid Website](https://mermaid.js.org/) for more information.\n"
  },
  {
    "path": "docs/features/monaco-editor.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\nrelates:\n  - Monaco Editor: https://microsoft.github.io/monaco-editor/\n  - Configure Monaco Editor: /custom/config-monaco\ntags: [codeblock, editor]\ndescription: |\n  Turn code blocks into fully-featured editors, or generate a diff between two code blocks.\n---\n\n# Monaco Editor\n\n<video src=\"https://github.com/slidevjs/slidev/assets/11247099/0c6ce681-80d3-4555-93bf-9288ee533462\" controls rounded shadow w-full></video>\n\nWhenever you want to do some modification in the presentation, simply add `{monaco}` after the language id — it turns the block into a fully-featured Monaco editor!\n\n````md\n```ts {monaco}\nconsole.log('HelloWorld')\n```\n````\n\nLearn more about [Configuring Monaco](/custom/config-monaco).\n\n## Diff Editor\n\nMonaco can also generate a diff between two code blocks. Use `{monaco-diff}` to turn the block into a [Monaco diff editor](https://microsoft.github.io/monaco-editor/playground.html?source=v0.36.1#example-creating-the-diffeditor-multi-line-example) and use `~~~` to separate the original and modified code!\n\n````md\n```ts {monaco-diff}\nconsole.log('Original text')\n~~~\nconsole.log('Modified text')\n```\n````\n\n## Editor Height\n\nBy default, the Monaco editor has a fixed height based on the initial content. If you start with an empty or small code block and want the editor to automatically grow as you type more code, you can set `{height:'auto'}`.\n\n````md\n```ts {monaco} {height:'auto'}\n// The editor will automatically grow as you type more code\nconsole.log('Hello, World!')\n```\n````\n\nYou can also set a specific height using CSS units like `{height:'300px'}` or `{height:'100%'}`.\n"
  },
  {
    "path": "docs/features/monaco-run.md",
    "content": "---\ndepends:\n  - features/monaco-editor\n  - guide/animations\nrelates:\n  - Custom Code Runners: /custom/config-code-runners\nsince: v0.48.0\ntags: [codeblock, editor]\ndescription: |\n  Run code directly in the editor and see the result.\n---\n\n# Monaco Runner\n\nSlidev also provides the Monaco Runner Editor, which allows you to run the code directly in the editor and see the result. Use `{monaco-run}` to turn the block into a Monaco Runner Editor.\n\n````md\n```ts {monaco-run}\nfunction distance(x: number, y: number) {\n  return Math.sqrt(x ** 2 + y ** 2)\n}\nconsole.log(distance(3, 4))\n```\n````\n\nIt provides the editor with a \"Run\" button, and shows the result of the code execution right below the code block. You may also modify the code and the result will be re-evaluated on the fly.\n\nBy default it will automatically run the code when the slide is loaded; if you want to instead explicitly trigger the run, you can set `{autorun:false}`.\n\n````md\n```ts {monaco-run} {autorun:false}\nconsole.log('Click the play button to run me')\n```\n````\n\nIf you want to only show the output in certain clicks, you can use the `showOutputAt` prop. The value is the same as `v-click`.\n\n````md\n```ts {monaco-run} {showOutputAt:'+1'}\nconsole.log('Shown after 1 click')\n```\n````\n\nCurrently, Slidev supports running JavaScript and TypeScript code out-of-box. Refer to [Custom Code Runners](/custom/config-code-runners) for custom language support.\n"
  },
  {
    "path": "docs/features/monaco-write.md",
    "content": "---\ndepends:\n  - features/monaco-editor\n  - features/import-snippet\nrelates:\n  - features/import-snippet\n  - Custom Code Runners: /custom/config-code-runners\nsince: v0.49.5\ntags: [codeblock, editor]\ndescription: |\n  A monaco editor that allows you to write code directly in the slides and save the changes back to the file.\n---\n\n# Writable Monaco Editor\n\nYou can also use the [Import Code Snippets](#import-code-snippets) syntax combined with the `{monaco-write}` directive, to link your Monaco Editor with a file on your filesystem. This will allow you to edit the code directly in the editor and save the changes back to the file.\n\n```md\n<<< ./some-file.ts {monaco-write}\n```\n\nWhen using this, be sure to back up your files beforehand, as the changes will be saved directly to the file.\n"
  },
  {
    "path": "docs/features/notes-auto-ruby.md",
    "content": "---\ntags: [notes, presenter]\ndescription: Automatically add `<ruby>` tags to your notes.\n---\n\n# Notes Auto Ruby\n\n> Available since v52.4.0\n\nWhen you write notes in your slides, you might want to add some ruby text to help pronouncing the some words. You can always add `<ruby>` tags to your notes manually, but Slidev also provides a convenient way to do this automatically by a global auto-replacement.\n\nIn the headmatter, you can set the `notesAutoRuby` option to a map of words to their ruby text:\n\n```md\n---\nnotesAutoRuby:\n  日本語: ni hon go\n  勉強: べんきょう\n---\n\n# Your slides here\n\n<!--\n私は日本語を勉強しています。\n-->\n```\n\nAnd the notes will be rendered as:\n\n<p>私は<ruby>日本語<rt>ni hon go</rt></ruby>を<ruby>勉強<rt>べんきょう</rt></ruby>しています。</p>\n"
  },
  {
    "path": "docs/features/og-image.md",
    "content": "---\nrelates:\n  - features/seo-meta\ntags: ['SEO', head]\ndescription: |\n  Set the Open Graph image for your slides.\n---\n\n# Open Graph Image\n\nSlidev allows you to set the Open Graph image via the `seoMeta.ogImage` option in the headmatter:\n\n```md\n---\nseoMeta:\n  ogImage: https://url.to.your.image.png\n---\n\n# Your slides here\n```\n\nLearn more about [SEO Meta Tags](./seo-meta).\n\n## Local Image\n\nIf you have `./og-image.png` in your project root, Slidev will grab it as the Open Graph image automatically without any configuration.\n\n## Auto-generate\n\nSince v52.1.0, Slidev supports auto-generating the Open Graph image from the first slide.\n\nYou can set `seoMeta.ogImage` to `auto` to enable this feature.\n\n```md\n---\nseoMeta:\n  ogImage: auto\n---\n```\n\nIt will use [playwright](https://playwright.dev/) to capture the first slide and save it as `./og-image.png` (same as `slidev export`). You may also commit the generated image to your repository to avoid the auto-generation. Or if you generate it on CI, you might also want to setup the playwright environment.\n"
  },
  {
    "path": "docs/features/plantuml.md",
    "content": "---\nrelates:\n  - Plant UML: https://plantuml.com/\n  - Plant UML Live Editor: https://plantuml.com/plantuml\n  - Example Slide: https://sli.dev/demo/starter/12\n  - features/mermaid\ntags: [diagram]\ndescription: |\n  Create diagrams from textual descriptions, powered by PlantUML.\n---\n\n# PlantUML Diagrams\n\nYou can create PlantUML diagrams easily in your slides, for example:\n\n````md\n```plantuml\n@startuml\nAlice -> Bob : Hello!\n@enduml\n```\n````\n\nThe source code will be sent to https://www.plantuml.com/plantuml to render the diagram by default. You can also set up your own server by setting the `plantUmlServer` in the [Slidev configuration](../custom/index#headmatter).\n\nVisit the [PlantUML Website](https://plantuml.com/) for more information.\n"
  },
  {
    "path": "docs/features/prettier-plugin.md",
    "content": "---\nrelates:\n  - features/block-frontmatter\n  - GitHub Repo: https://github.com/slidevjs/prettier-plugin\n  - Prettier: https://prettier.io/\ntags: [editor]\ndescription: |\n  Use the Prettier plugin to format your slides.\n---\n\n# Prettier Plugin\n\nThe Slidev's syntax may be incompatible with the default Markdown parser of [Prettier](https://prettier.io/). To solve this, Slidev provides a Prettier plugin to format your slides. You can use it with your favorite editor that supports Prettier.\n\n## 1. Install\n\n::: code-group\n\n```bash [npm]\nnpm i -D prettier prettier-plugin-slidev\n```\n\n```bash [pnpm]\npnpm i -D prettier prettier-plugin-slidev\n```\n\n```bash [yarn]\nyarn add -D prettier prettier-plugin-slidev\n```\n\n```bash [bun]\nbun add -D prettier prettier-plugin-slidev\n```\n\n```bash [deno]\ndeno add -D npm:prettier npm:prettier-plugin-slidev\n```\n\n:::\n\n## 2. Activate the plugin\n\nCreate or modify your [prettier configuration file](https://prettier.io/docs/en/configuration) to activate the plugin:\n\n```json\n{\n  \"overrides\": [\n    {\n      \"files\": [\"slides.md\", \"pages/*.md\"],\n      \"options\": {\n        \"parser\": \"slidev\",\n        \"plugins\": [\"prettier-plugin-slidev\"]\n      }\n    }\n  ]\n}\n```\n\nNote that only specifying `plugins` is not enough, because Slidev and common Markdown files share the same file extension `.md`.\n"
  },
  {
    "path": "docs/features/recording.md",
    "content": "---\ndepends:\n  - guide/ui#navigation-bar\nrelates:\n  - RecordRTC: https://github.com/muaz-khan/RecordRTC\n  - WebRTC API: https://webrtc.org/\ntags: [presenter, tool]\ndescription: |\n  Record your presentation with the built-in camera view and recording feature.\n---\n\n# Recording\n\nSlidev comes with a built-in camera view and recording feature. They make it simple for you to record your presentation without having to switch between other recording tools while delivering a presentation.\n\n## Camera View {#camera-view}\n\nClick the <carbon-user-avatar class=\"inline-icon-btn\"/> button in the [navigation bar](../guide/ui#navigation-bar) to show your camera view in the presentation. You can drag it to move it, and use the handler on the right bottom corner to resize it. The size and position will persist across reloads.\n\n<TheTweet id=\"1395006771027120133\" />\n\n## Start Recording {#start-recording}\n\nClicking the <carbon-video class=\"inline-icon-btn\"/> button in the [navigation bar](../guide/ui#navigation-bar) will bring up a dialog for you. Here you can choose to either record your camera output embedded in your slides or to separate them into two video files.\n\nThis feature is powered by [RecordRTC](https://github.com/muaz-khan/RecordRTC) and uses the [WebRTC API](https://webrtc.org/).\n\n![](/screenshots/recording.png)\n"
  },
  {
    "path": "docs/features/remote-access.md",
    "content": "---\nrelates:\n  - guide/ui\n  - CLI: builtin/cli\n  - Cloudflare Quick Tunnels: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/\ntags: [remote, tool]\ndescription: |\n  Access your presentation remotely with Slidev's remote access feature.\n---\n\n# Remote Access\n\nYou can run your presentation with remote access by using the `--remote` flag:\n\n::: code-group\n\n```bash [pnpm]\npnpm dev --remote\n# i.e. slidev --remote\n```\n\n```bash [npm]\nnpm run dev -- --remote\n# i.e. slidev --remote\n```\n\n```bash [yarn]\nyarn dev --remote\n# i.e. slidev --remote\n```\n\n```bash [bun]\nbun dev --remote\n# i.e. slidev --remote\n```\n\n```bash [deno]\ndeno run dev --remote\n# i.e. slidev --remote\n```\n\n:::\n\n## Password Protection\n\nIf you want to share your slides but don't want other people to access the presenter mode, you can pass a password to the option, i.e. `--remote=your_password`. Then the password is required when accessing the presenter mode.\n\n## Remote Tunnel\n\nYou can open a [Cloudflare Quick Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) to expose your local server to the internet. This way, you can share your slides with others without setting up a server.\n\n::: code-group\n\n```bash [pnpm]\npnpm dev --remote --tunnel\n# i.e. slidev --remote --tunnel\n```\n\n```bash [npm]\nnpm run dev -- --remote --tunnel\n# i.e. slidev --remote --tunnel\n```\n\n```bash [yarn]\nyarn dev --remote --tunnel\n# i.e. slidev --remote --tunnel\n```\n\n```bash [bun]\nbun dev --remote --tunnel\n# i.e. slidev --remote --tunnel\n```\n\n```bash [deno]\ndeno run dev --remote --tunnel\n# i.e. slidev --remote --tunnel\n```\n\n:::\n"
  },
  {
    "path": "docs/features/rough-marker.md",
    "content": "---\ndepends:\n  - guide/animations\nrelates:\n  - Rough Notation: https://github.com/slidevjs/rough-notation\nsince: v0.48.0\ntags: [drawing, animation]\ndescription: |\n  Integrate Rough Notation to allow marking or highlighting elements in your slides.\n---\n\n# Rough Markers\n\nSlidev integrates [Rough Notation](https://github.com/slidevjs/rough-notation) to allow marking or highlighting elements in your slides.\n\n---\n\n### `v-mark` directive\n\nRough Notation integration comes with the `v-mark` directive.\n\n#### Type\n\nUse `v-mark.underline` for the underline mark, `v-mark.circle` for the circle mark, etc. (defaults to `underline`).\n\n#### Color\n\n`v-mark.red` makes the notation `red`. Supported built-in color themes from UnoCSS. For custom colors, use object syntax `v-mark=\"{ color: '#234' }\"`.\n\n#### Clicks\n\n`v-mark` works like `v-click` and will trigger after a click. Same as `v-click`, it allows you to pass a custom click value, like `v-mark=\"5\"` or `v-mark=\"'+1'\"`.\n\n#### Options\n\nOptionally, you can pass an object to `v-mark` to specify the options, for example:\n\n```vue\n<span v-mark=\"{ at: 5, color: '#234', type: 'circle' }\">\nImportant text\n</span>\n```\n\n#### Preview\n\n<video src=\"https://github.com/slidevjs/slidev/assets/11247099/c840340c-0aa1-4cde-b228-e6c67e5f6879\" rounded-lg shadow controls></video>\n"
  },
  {
    "path": "docs/features/seo-meta.md",
    "content": "---\ndepends:\n  - custom/index#headmatter\nrelates:\n  - features/og-image\ntags: [SEO, head]\ndescription: |\n  Configure SEO meta tags for better social media sharing and search engine optimization.\n---\n\n# SEO Meta Tags\n\nSlidev allows you to configure SEO meta tags in the headmatter to improve social media sharing and search engine optimization. You can set up Open Graph and Twitter Card meta tags to control how your slides appear when shared on social platforms.\n\n## Configuration\n\nAdd the `seoMeta` configuration to your slides deck frontmatter:\n\n```yaml\n---\n# SEO meta tags\nseoMeta:\n  ogTitle: Slidev Starter Template\n  ogDescription: Presentation slides for developers\n  ogImage: https://cover.sli.dev\n  ogUrl: https://example.com\n  twitterCard: summary_large_image\n  twitterTitle: Slidev Starter Template\n  twitterDescription: Presentation slides for developers\n  twitterImage: https://cover.sli.dev\n  twitterSite: username\n  twitterUrl: https://example.com\n---\n```\n\nThis feature is powered by [unhead](https://unhead.unjs.io/)'s `useHead` hook, please refer to the [documentation](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta) for more details.\n"
  },
  {
    "path": "docs/features/shiki-magic-move.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\n  - guide/animations\nrelates:\n  - Shiki Magic Move: https://github.com/shikijs/shiki-magic-move\nsince: v0.48.0\ntags: [codeblock, animation]\ndescription: |\n  Enable granular transition between code changes, similar to Keynote's Magic Move.\n---\n\n# Shiki Magic Move\n\n[Shiki Magic Move](https://github.com/shikijs/shiki-magic-move) enables you to have a granular transition between code changes, similar to Keynote's Magic Move. You can check [the playground](https://shiki-magic-move.netlify.app/) to see how it works.\n\n<video src=\"https://github.com/slidevjs/slidev/assets/11247099/79927794-27ba-4342-9911-9996cec889d6\" controls rounded shadow w-full></video>\n\nIn Slidev, we bind the magic-move to the [clicks system](/guide/animations#click-animation). The syntax is to wrap multiple code blocks representing each step with <code>````md magic-move</code> (mind it's **4** backticks), this will be transformed into one code block, that morphs to each step as you click.\n\n`````md\n````md magic-move\n```js\nconsole.log(`Step ${1}`)\n```\n```js\nconsole.log(`Step ${1 + 1}`)\n```\n```ts\nconsole.log(`Step ${3}` as string)\n```\n````\n`````\n\nIt's also possible to mix Magic Move with <LinkInline link=\"features/line-highlighting\" /> and <LinkInline link=\"features/code-block-line-numbers\" />, for example:\n\n`````md\n````md magic-move {at:4, lines: true} // [!code hl]\n```js {*|1|2-5} // [!code hl]\nlet count = 1\nfunction add() {\n  count++\n}\n```\n\nNon-code blocks in between as ignored, you can put some comments.\n\n```js {*}{lines: false} // [!code hl]\nlet count = 1\nconst add = () => count += 1\n```\n````\n`````\n\n## Title Bar {#title-bar}\n\n> Available since v0.52.0\n\nYou can add a title bar to magic move blocks by specifying a filename in the opening fence of each step:\n\n`````md\n````md magic-move\n```js [app.js]\nconsole.log('Step 1')\n```\n```js [app.js]\nconsole.log('Step 2')\n```\n````\n`````\n\nThe title bar will also display an automatically matched icon based on the filename (see <LinkInline link=\"features/code-groups#title-icon-matching\" />).\n\n## Animation Duration {#duration}\n\n> Available since v0.52.0\n\nYou can customize the animation duration for magic move transitions globally via the headmatter:\n\n```yaml\n---\nmagicMoveDuration: 500 # duration in milliseconds, default is 800\n---\n```\n\nOr per-block by passing the `duration` option:\n\n`````md\n````md magic-move {duration:500}\n```js\nconsole.log('Step 1')\n```\n```js\nconsole.log('Step 2')\n```\n````\n`````\n\n## Copy Button {#copy-button}\n\n> Available since v0.52.0\n\nMagic move code blocks support a copy button that appears on hover. Configure this behavior globally with the `magicMoveCopy` headmatter option:\n\n<!-- eslint-skip -->\n\n```yaml\n---\n# Options: true | false | 'always' | 'final'\nmagicMoveCopy: true     # show copy button on all steps (default)\nmagicMoveCopy: false    # disable copy button\nmagicMoveCopy: 'final'  # only show copy button on the final step\n---\n```\n\nThe copy button respects the global `codeCopy` setting. If `codeCopy` is `false`, the magic move copy button will also be disabled.\n"
  },
  {
    "path": "docs/features/side-editor.md",
    "content": "---\ndepends:\n  - guide/ui#navigation-actions\nrelates:\n  - features/vscode-extension\ntags: [editor]\ndescription: |\n  Edit your slides source file alongside the presentation.\n---\n\n# Integrated Editor\n\nSlidev comes with an integrated editor that will instantly reload and save the changes to your file.\n\nClick the <carbon-edit class=\"inline-icon-btn\"/> button on the navigation panel to open it.\n\n![](/screenshots/integrated-editor.png)\n"
  },
  {
    "path": "docs/features/slide-hook.md",
    "content": "---\ndepends:\n  - guide/global-context\ntags: [client-api]\ndescription: |\n  Hooks to manage the slide lifecycle.\n---\n\n# Slide Hooks\n\nSlidev provides a set of hooks to help you manage the slide lifecycle:\n\n```ts twoslash\nimport { onSlideEnter, onSlideLeave, useIsSlideActive } from '@slidev/client'\n\nconst isActive = useIsSlideActive()\n\nonSlideEnter(() => {\n  /* Called whenever the slide becomes active */\n})\n\nonSlideLeave(() => {\n  /* Called whenever the slide becomes inactive */\n})\n```\n\nYou can also use <LinkInline link=\"guide/global-context\" /> to access other useful context information.\n\n::: warning\n\nIn the slide component, `onMounted` and `onUnmounted` hooks are not available, because the component instance is preserved even when the slide is not active. Use `onSlideEnter` and `onSlideLeave` instead.\n\n:::\n"
  },
  {
    "path": "docs/features/slide-scope-style.md",
    "content": "---\nrelates:\n  - Vue's Scoped CSS: https://vuejs.org/api/sfc-css-features.html#scoped-css\n  - UnoCSS directives: https://unocss.dev/transformers/directives\ntags: [styling, syntax]\ndescription: |\n  Define styles for only the current slide.\n---\n\n# Slide Scope Styles\n\nYou can use the `<style>` tag in your Markdown to define styles for **only the current slide**.\n\n```md\n# This is Red\n\n<style>\nh1 {\n  color: red;\n}\n</style>\n\n---\n\n# Other slides are not affected\n```\n\nThe `<style>` tag in Markdown is always [scoped](https://vuejs.org/api/sfc-css-features.html#scoped-css). As a result, a selector with a child combinator (`.a > .b`) is unusable as such; see the previous link. To have global styles, check out the [customization section](/custom/directory-structure#style).\n\nPowered by [UnoCSS](/custom/config-unocss), you can directly use nested css and [directives](https://unocss.dev/transformers/directives):\n\n```md\n# Slidev\n\n> Hello **world**\n\n<style>\nblockquote {\n  strong {\n    --uno: 'text-teal-500 dark:text-teal-400';\n  }\n}\n</style>\n```\n"
  },
  {
    "path": "docs/features/slot-sugar.md",
    "content": "---\nrelates:\n  - Vue's Named Slots: https://v3.vuejs.org/guide/component-slots.html\ntags: [layout, syntax]\ndescription: |\n  A syntax sugar for named slots in layouts.\n---\n\n# Slot Sugar for Layouts\n\nSome layouts can provide multiple contributing points using [Vue's named slots](https://vuejs.org/guide/components/slots.html).\n\nFor example, in [`two-cols` layout](https://github.com/slidevjs/slidev/blob/main/packages/client/layouts/two-cols.vue), you can have two columns left (`default` slot) and right (`right` slot) side by side.\n\n```md\n---\nlayout: two-cols\n---\n\n<template v-slot:default>\n\n# Left\n\nThis is shown on the left\n\n</template>\n<template v-slot:right>\n\n# Right\n\nThis is shown on the right\n\n</template>\n```\n\n<div class=\"grid grid-cols-2 rounded border border-gray-400 border-opacity-50 px-10 pb-4\">\n<div>\n<h3>Left</h3>\n<p>This shows on the left</p>\n</div>\n<div>\n<h3>Right</h3>\n<p>This shows on the right</p>\n</div>\n</div>\n\nWe also provide a shorthand syntactical sugar `::name::` for slot name. The following works exactly the same as the previous example.\n\n```md\n---\nlayout: two-cols\n---\n\n# Left\n\nThis is shown on the left\n\n::right::\n\n# Right\n\nThis is shown on the right\n```\n\nYou can also explicitly specify the default slot and provide it in the custom order.\n\n```md\n---\nlayout: two-cols\n---\n\n::right::\n\n# Right\n\nThis shows on the right\n\n::default::\n\n# Left\n\nThis is shown on the left\n```\n"
  },
  {
    "path": "docs/features/timer.md",
    "content": "---\ntags: [presenter]\ndescription: Timer for the presenter mode.\n---\n\n# Presenter Timer\n\nSlidev provides a timer for the presenter mode. You can start, pause, and reset the timer.\n\nIt will show a timer (in stopwatch or countdown mode), and a progress bar in the presenter mode.\n\n## Configuration\n\nYou can set the duration of the presentation in the headmatter. Default is `30min`.\n\n```yaml\n---\n# duration of the presentation, default is '30min'\nduration: 30min\n# timer mode, can be 'countdown' or 'stopwatch', default is 'stopwatch'\ntimer: stopwatch\n---\n```\n"
  },
  {
    "path": "docs/features/transform-component.md",
    "content": "---\nrelates:\n  - guide/faq#adjust-size\n  - features/canvas-size\n  - features/zoom-slide\ntags: [layout]\ndescription: |\n  A component for scaling some elements.\n---\n\n# The `Transform` Component\n\nThe `Transform` component allows you to scale the size of the elements on your slides:\n\n```md\n<Transform :scale=\"0.5\" origin=\"top center\">\n  <YourElements />\n</Transform>\n```\n\nThis is useful when you want to adjust the size of some elements on your slides without affecting the layout of the entire slide.\n\nTo scale all the slides in your presentation, you can set the slide canvas size:\n\n<LinkCard link=\"features/canvas-size\" />\n\nTo scale several slides in your presentation, you can use the `zoom` option:\n\n<LinkCard link=\"features/zoom-slide\" />\n"
  },
  {
    "path": "docs/features/twoslash.md",
    "content": "---\ndepends:\n  - guide/syntax#code-block\nrelates:\n  - TwoSlash: https://twoslash.netlify.app/\nsince: v0.46.0\ntags: [codeblock]\ndescription: |\n  A powerful tool for rendering TypeScript code blocks with type information on hover or inlined.\n---\n\n# TwoSlash Integration\n\n[TwoSlash](https://twoslash.netlify.app/) is a powerful tool for rendering TypeScript code blocks with type information on hover or inlined. It's quite useful for preparing slides for JavaScript/TypeScript-related topics.\n\nTo use it, you can add `twoslash` to the code block's language identifier:\n\n````md\n```ts twoslash\nimport { ref } from 'vue'\n\nconst count = ref(0)\n//            ^?\n```\n````\n\nIt will be rendered as:\n\n```ts twoslash\nimport { ref } from 'vue'\n\nconst count = ref(0)\n//            ^?\n```\n\n<!-- For the popup to not overlap the content below -->\n<div class=\"py-20\" />\n"
  },
  {
    "path": "docs/features/vscode-extension.md",
    "content": "---\nrelates:\n  - VS Code: https://code.visualstudio.com/\n  - View in Marketplace: https://marketplace.visualstudio.com/items?itemName=antfu.slidev\n  - View in OVSX: https://open-vsx.org/extension/antfu/slidev\ntags: [editor]\ndescription: |\n  Help you better organize your slides and have a quick overview of them.\n---\n\n# VS Code Extension\n\n<p align=\"center\">\n    <a href=\"https://github.com/slidevjs/slidev\" target=\"_blank\">\n        <img src=\"https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/logo-for-vscode.png\" alt=\"Slidev\" width=\"300\" />\n    </a>\n</p>\n\n<a href=\"https://marketplace.visualstudio.com/items?itemName=antfu.slidev\" target=\"__blank\">\n  <img inline src=\"https://img.shields.io/visual-studio-marketplace/v/antfu.slidev.svg?color=4EC5D4&amp;label=VS%20Code%20Marketplace&logo=visual-studio-code\" alt=\"Visual Studio Marketplace Version\" />\n</a> &nbsp;\n<a href=\"https://marketplace.visualstudio.com/items?itemName=antfu.slidev\" target=\"__blank\">\n  <img inline src=\"https://img.shields.io/visual-studio-marketplace/d/antfu.slidev.svg?color=2B90B6\" alt=\"Visual Studio Marketplace Downloads\" />\n</a>\n\nThe VS Code extension provides some features to help you better organize your slides and have a quick overview of them.\n\n### Features\n\n- Preview slides in the side panel\n- Slides tree view with slide numbers\n- Re-ordering slides via drag and drop\n- Folding for slide blocks\n- Multiple slides project support\n- Start the dev server with one click\n- AI/Copilot integration via Language Model Tools\n\n![](https://github.com/slidevjs/slidev/assets/63178754/2c9ba01a-d21f-4b33-b6b6-4e249873f865)\n\n<TheTweet id=\"1395333405345148930\" />\n\n<TheTweet id=\"1789684139152810151\" />\n\n### Installation\n\nYou can install the extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=antfu.slidev) or the [Open VSX Registry](https://open-vsx.org/extension/antfu/slidev).\n\n### Usage\n\nClick the `Slidev` icon in the activity bar to open the **Slidev panel**. In the Slidev panel, you can see the projects tree view, slides tree view, and the preview webview.\n\nIn the **projects tree view**, you can see all the Slidev projects in your workspace. You can click the item to open the corresponding file, and click the <codicon-eye /> icon over it to switch the active project. The <codicon-add /> icon allows you to load a slides project that wasn't scanned automatically.\n\nIn the **slides tree view**, you can see all the slides in the active project. You can click the item to move your cursor to the slide in the editor, and drag and drop to reorder the slides.\n\nIn the **preview webview**, you can click the <codicon-run-all /> icon to start the dev server and click the <codicon-globe /> icon to open the slides in the browser. Toggle <codicon-lock /> icon to sync/unsync the preview navigation with the editor cursor.\n\nThere are also some **commands** you can use. Type `Slidev` in the command palette to see them.\n\nYou can add glob patterns to the `slidev.include` configuration to include files as Slidev entries. The default value is `[\"**/*.md\"]`. Example:\n\n```json\n{\n  \"slidev.include\": [\"**/presentation.md\"]\n}\n```\n\n#### Dev Command {#dev-command}\n\nYou can customize the command to start dev server by setting the `slidev.dev-command` configuration. The default value is `npm exec -c 'slidev ${args}'`.\n\nThe configured command can contain placeholders:\n\n- `${args}`: All CLI arguments. e.g. `slides.md --port 3000 --remote`\n- `${port}`: The port number. e.g. `3000`\n\nExamples:\n\n- Global installation: `slidev ${args}`\n- For PNPM users, you can set it to `pnpm slidev ${args}`.\n- For [code-server](https://coder.com/docs/code-server/) users, you can set it to `pnpm slidev ${args} --base /proxy/${port}/`. This will make the dev server accessible at `http://localhost:8080/proxy/3000/`.\n\n#### Slides Tree View {#slides-tree}\n\n> Available since v0.52.0\n\nThe slides tree view shows all slides in your presentation with their slide numbers and titles. Each slide is displayed as `{slideNo}. {title}` making it easy to navigate to specific slides.\n\n#### AI/Copilot Integration {#ai-integration}\n\n> Available since v0.52.0\n\nThe extension provides Language Model Tools that allow VSCode's Copilot and other AI assistants to interact with your Slidev project. The following tools are available:\n\n- `slidev_getActiveSlide` - Get information about the current active slide and project\n- `slidev_getSlideContent` - Retrieve the content of a specific slide by number\n- `slidev_getAllSlideTitles` - List all slide titles in the presentation\n- `slidev_findSlideNoByTitle` - Find a slide number by its title\n- `slidev_listEntries` - List all loaded Slidev project entries\n- `slidev_getPreviewPort` - Get the preview server port for a project\n- `slidev_chooseEntry` - Switch the active Slidev entry\n\nThese tools enable AI assistants to help you navigate, edit, and understand your slides more effectively.\n"
  },
  {
    "path": "docs/features/zoom-slide.md",
    "content": "---\nrelates:\n  - guide/faq#adjust-size\n  - features/canvas-size\n  - features/transform-component\ntags: [layout]\ndescription: |\n  Zoom the content of a slide to a specific scale.\n---\n\n# Zoom Slides\n\nYou may find some slides in your presentation too spacious or too crowded. Slidev provides a `zoom` option for each slide that allows you to scale the content of a slide:\n\n```md\n---\nzoom: 0.8\n---\n\n# A Slide with lots of content\n\n---\n\n# Other slides aren't affected\n```\n\nTo scale all the slides in your presentation, you can set the slide canvas size:\n\n<LinkCard link=\"features/canvas-size\" />\n\nTo adjust the size of some elements on your slides, you can use the `Transform` component:\n\n<LinkCard link=\"features/transform-component\" />\n"
  },
  {
    "path": "docs/guide/animations.md",
    "content": "---\noutline: deep\n---\n\n# Animation\n\nAnimation is an essential part of slide presentations. Slidev provides a variety of ways to animate your slides, from the simple to the complex. This guide will show you how to use them effectively.\n\n## Click Animation {#click-animation}\n\nA \"**click**\" can be considered as the unit of animation steps in slides. A slide can have one or more clicks, and each click can trigger one or more animations - for example, revealing or hiding elements.\n\n> [!NOTE]\n> Since v0.48.0, we've rewritten the click animations system with much more consistent behaviors. It might change the behaviors of your existing slides in edge cases. While this page is showing the new click system, you can find more details about the refactor in [#1279](https://github.com/slidevjs/slidev/pull/1279).\n\n### `v-click` {#v-click}\n\nTo apply show/hide \"click animations\" for elements, you can use the `<v-click>` component or the `v-click` directive.\n\n<!-- eslint-skip -->\n\n```md\n<!-- Component usage:\n     this will be invisible until you press \"next\" -->\n<v-click> Hello World! </v-click>\n\n<!-- Directive usage:\n     this will be invisible until you press \"next\" the second time -->\n<div v-click class=\"text-xl\"> Hey! </div>\n```\n\n### `v-after` {#v-after}\n\n`v-after` will turn the element visible when the previous `v-click` is triggered.\n\n```md\n<div v-click> Hello </div>\n<div v-after> World </div>  <!-- or <v-after> World </v-after> -->\n```\n\nWhen you press \"next\", both `Hello` and `World` will show up together.\n\n### Hide after clicking {#hide-after-clicking}\n\nAdd a `.hide` modifier to `v-click` or `v-after` directives to make elements invisible after clicking, instead of showing up.\n\n```md\n<div v-click> Visible after 1 click </div>\n<div v-click.hide> Hidden after 2 clicks </div>\n<div v-after.hide> Hidden after 2 clicks </div>\n```\n\nFor the components, you can use the `hide` prop to achieve the same effect:\n\n```md\n<v-click> Visible after 1 click </v-click>\n<v-click hide> Hidden after 2 clicks </v-click>\n<v-after hide> Also hidden after 2 clicks </v-after>\n```\n\n### `v-clicks` {#v-clicks}\n\n`v-clicks` is only provided as a component. It's a shorthand to apply the `v-click` directive to all its child elements. It is especially useful when working with lists and tables.\n\n```md\n<v-clicks>\n\n- Item 1\n- Item 2\n- Item 3\n\n</v-clicks>\n```\n\nAn item will become visible each time you click \"next\".\nIt accepts a `depth` prop for nested list:\n\n```md\n<v-clicks depth=\"2\">\n\n- Item 1\n  - Item 1.1\n  - Item 1.2\n- Item 2\n  - Item 2.1\n  - Item 2.2\n\n</v-clicks>\n```\n\nAlso, you can use the `every` prop to specify the number of items to show after each click:\n\n```md\n<v-clicks every=\"2\">\n\n- Item 1.1\n- Item 1.2\n- Item 2.1\n- Item 2.2\n\n</v-clicks>\n```\n\n### Positioning {#positioning}\n\nBy default, the clicking animations are triggered one by one. You can customize the animation \"position\" of elements by using the `at` prop or the `v-click` directive with value.\n\nLike the CSS layout system, click-animated elements can be \"relative\" or \"absolute\":\n\n#### Relative Position {#relative-position}\n\nThis actual position of relative elements is calculated based on the previous relative elements:\n\n````md\n<div v-click> visible after 1 click </div>\n<v-click at=\"+2\"><div> visible after 3 clicks </div></v-click>\n<div v-click.hide=\"'-1'\"> hidden after 2 clicks </div>\n\n```js {none|1|2}{at:'+5'}\n1  // highlighted after 7 clicks\n2  // highlighted after 8 clicks\n```\n````\n\n> [!NOTE]\n> The default value of `v-click` is `'+1'` when you don't specify it.\n\nIn fact, `v-after` are just shortcuts for `v-click` with `at` prop:\n\n```md\n<!-- The following 2 usages are equivalent -->\n<img v-after />\n<img v-click=\"'+0'\" />\n\n<!-- The following 3 usages are equivalent -->\n<img v-click />\n<img v-click=\"'+1'\" />\n<v-click-gap size=\"1\" /><img v-after />\n```\n\n::: tip `at` prop value format\nOnly string values starting with `'+'` or `'-'` like `'+1'` are treated as relative positions:\n\n| Value          | Kind     |\n| -------------- | -------- |\n| `'-1'`, `'+1'` | Relative |\n| `+1` === `1`   | Absolute |\n| `'1'`          | Absolute |\n\nSo don't forget the single quotes for the relative values.\n:::\n\n#### Absolute Position {#absolute-position}\n\nThe given value is the exact click count to trigger this animation:\n\n````md\n<div v-click=\"3\"> visible after 3 clicks </div>\n<v-click at=\"2\"><div> visible after 2 clicks </div></v-click>\n<div v-click.hide=\"1\"> hidden after 1 click </div>\n\n```js {none|1|2}{at:3}\n1  // highlighted after 3 clicks\n2  // highlighted after 4 clicks\n```\n````\n\n#### Mixed Case {#mixed-case}\n\nYou can mix the absolute and relative positions:\n\n```md\n<div v-click> visible after 1 click </div>\n<div v-click=\"3\"> visible after 3 clicks </div>\n<div v-click> visible after 2 click </div>\n<div v-click=\"'-1'\"> visible after 1 click </div>\n<div v-click=\"4\"> visible after 4 clicks </div>\n```\n\nThe following example synchronizes the highlighting of the two code blocks:\n\n````md {1,6}\n```js {1|2}{at:1}\n1 + 1\n'a' + 'b'\n```\n\n```js {1|2}{at:1}\n= 2\n= 'ab'\n```\n````\n\n### Enter & Leave {#enter-leave}\n\nYou can also specify the enter and leave index for the `v-click` directive by passing an array. The end index is exclusive.\n\n```md\n<div v-click.hide=\"[2, 4]\">\n  This will be hidden at click 2 and 3 (and shown otherwise).\n</div>\n<div v-click />\n<div v-click=\"['+1', '+1']\">\n  This will be shown only at click 2 (and hidden otherwise).\n</div>\n```\n\nYou can also use `v-switch` to achieve the same effect:\n\n```md\n<v-switch>\n  <template #1> show at click 1, hide at click 2. </template>\n  <template #2> show at click 2, hide at click 5. </template>\n  <template #5-7> show at click 5, hide at click 7. </template>\n</v-switch>\n```\n\nSee [`VSwitch` Component](/builtin/components#vswitch) for more details.\n\n### Custom Total Clicks Count {#total}\n\nBy default, Slidev automatically calculates how many clicks are required before going to the next slide. You can override this via the `clicks` frontmatter option:\n\n```yaml\n---\n# 10 clicks in this slide, before going to the next slide\nclicks: 10\n---\n```\n\n### Element Transitions {#element-transitions}\n\nWhen you apply the `v-click` directive to your elements, it will attach the class name `slidev-vclick-target` to it. When the elements are hidden, the class name `slidev-vclick-hidden` will also be attached. For example:\n\n```html\n<div class=\"slidev-vclick-target slidev-vclick-hidden\">Text</div>\n```\n\nAfter a click, it may become:\n\n```html\n<div class=\"slidev-vclick-target\">Text</div>\n```\n\nBy default, a subtle opacity transition is applied to those classes:\n\n```css\n/* below shows the default style */\n\n.slidev-vclick-target {\n  transition: opacity 100ms ease;\n}\n\n.slidev-vclick-hidden {\n  opacity: 0;\n  pointer-events: none;\n}\n```\n\nYou can override them to customize the transition effects in your custom stylesheets. For example, you can achieve the scaling up transitions by:\n\n```css\n/* styles.css */\n\n.slidev-vclick-target {\n  transition: all 500ms ease;\n}\n\n.slidev-vclick-hidden {\n  transform: scale(0);\n}\n```\n\nTo specify animations for only certain slides or layouts:\n\n```scss\n.slidev-page-7,\n.slidev-layout.my-custom-layout {\n  .slidev-vclick-target {\n    transition: all 500ms ease;\n  }\n\n  .slidev-vclick-hidden {\n    transform: scale(0);\n  }\n}\n```\n\nLearn more about [customizing styles](/custom/directory-structure#style).\n\n## Motion {#motion}\n\nSlidev has [@vueuse/motion](https://motion.vueuse.org/) built-in. You can use the `v-motion` directive to any elements to apply motion to them. For example\n\n```html\n<div\n  v-motion\n  :initial=\"{ x: -80 }\"\n  :enter=\"{ x: 0 }\"\n  :leave=\"{ x: 80 }\"\n>\n  Slidev\n</div>\n```\n\nThe text `Slidev` will move from `-80px` to its original position when entering the slide. When leaving, it will move to `80px`.\n\n> Before v0.48.9, you need to add `preload: false` to the slide's frontmatter to enable motion.\n\n### Motion with Clicks {#motion-with-clicks}\n\n> Available since v0.48.9\n\nYou can also trigger the motion by clicks:\n\n```html\n<div\n  v-motion\n  :initial=\"{ x: -80 }\"\n  :enter=\"{ x: 0, y: 0 }\"\n  :click-1=\"{ x: 0, y: 30 }\"\n  :click-2=\"{ y: 60 }\"\n  :click-2-4=\"{ x: 40 }\"\n  :leave=\"{ y: 0, x: 80 }\"\n>\n  Slidev\n</div>\n```\n\nOr combine `v-click` with `v-motion`:\n\n```html\n<div v-click=\"[2, 4]\" v-motion\n  :initial=\"{ x: -50 }\"\n  :enter=\"{ x: 0 }\"\n  :leave=\"{ x: 50 }\"\n>\n  Shown at click 2 and hidden at click 4.\n</div>\n```\n\nThe meanings of variants:\n\n- `initial`: When `currentPage < thisPage`, or `v-click` hides the current element because `$clicks` is too small.\n- `enter`: When `currentPage === thisPage`, and `v-click` shows the element. _Priority: lowest_\n- `click-x`: `x` is a number representing the **absolute** click num. The variant will take effect if `$clicks >= x`. _Priority: `x`_\n- `click-x-y`: The variant will take effect if `x <= $clicks < y`. _Priority: `x`_\n- `leave`: `currentPage > thisPage`, or `v-click` hides the current element because `$clicks` is too large.\n\nThe variants will be combined according to the priority defined above.\n\n::: warning\nDue to a Vue internal [bug](https://github.com/vuejs/core/issues/10295), currently **only** `v-click` applied to the same element as `v-motion` can control the motion animation. As a workaround, you can use something like `v-if=\"3 < $clicks\"` to achieve the same effect.\n:::\n\nLearn more: [Demo](https://sli.dev/demo/starter/10) | [@vueuse/motion](https://motion.vueuse.org/) | [v-motion](https://motion.vueuse.org/features/directive-usage) | [Presets](https://motion.vueuse.org/features/presets)\n\n## Slide Transitions {#slide-transitions}\n\n<div id=\"pages-transitions\" />\n\nSlidev supports slide transitions out of the box. You can enable it by setting the `transition` frontmatter option:\n\n```md\n---\ntransition: slide-left\n---\n```\n\nThis will give you a nice sliding effects on slide switching. Setting it in the headmatter will apply this to all slides. You can also set different transitions per slide in frontmatters.\n\n### Builtin Transitions {#builtin-transitions}\n\n- `fade` - Crossfade in/out\n- `fade-out` - Fade out and then fade in\n- `slide-left` - Slides to the left (slide to right when going backward)\n- `slide-right` - Slides to the right (slide to left when going backward)\n- `slide-up` - Slides to the top (slide to bottom when going backward)\n- `slide-down` - Slides to the bottom (slide to top when going backward)\n- `view-transition` - Via the view transitions API\n\n### View Transition API {#view-transitions}\n\nThe View Transitions API provides a mechanism for easily creating animated transitions between different DOM states. Learn more about it in [View Transitions API - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API).\n\n:::warning\nExperimental: This is not supported by all browsers. Check the [Browser compatibility table](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API#browser_compatibility) carefully before using this.\n:::\n\nYou can use the `view-transition-name` CSS property to name view transitions, which creates connections between different page elements and smooth transitions when switching slides.\n\nYou can enable [Comark Syntax](/guide/syntax#comark-syntax) support to conveniently name view-transitions:\n\n```md\n---\ntransition: view-transition\ncomark: true\n---\n\n# View Transition {.inline-block.view-transition-title}\n\n---\n\n# View Transition {.inline-block.view-transition-title}\n```\n\n### Custom Transitions {#custom-transitions}\n\nSlidev's slide transitions are powered by [Vue Transition](https://vuejs.org/guide/built-ins/transition.html). You can provide your custom transitions by:\n\n```md\n---\ntransition: my-transition\n---\n```\n\nand then in your custom stylesheets:\n\n```css\n.my-transition-enter-active,\n.my-transition-leave-active {\n  transition: opacity 0.5s ease;\n}\n\n.my-transition-enter-from,\n.my-transition-leave-to {\n  opacity: 0;\n}\n```\n\nLearn more about how it works in [Vue Transition](https://vuejs.org/guide/built-ins/transition.html).\n\n### Forward & Backward Transitions {#forward-backward-transitions}\n\nYou can specify different transitions for forward and backward navigation using `|` as a separator in the transition name:\n\n```md\n---\ntransition: go-forward | go-backward\n---\n```\n\nWith this, when you go from slide 1 to slide 2, the `go-forward` transition will be applied. When you go from slide 2 to slide 1, the `go-backward` transition will be applied.\n\n### Advanced Usage {#advanced-usage}\n\nThe `transition` field accepts an option that will passed to the [`<TransitionGroup>`](https://vuejs.org/api/built-in-components.html#transition) component. For example:\n\n```md\n---\ntransition:\n  name: my-transition\n  enterFromClass: custom-enter-from\n  enterActiveClass: custom-enter-active\n---\n```\n"
  },
  {
    "path": "docs/guide/component.md",
    "content": "# Components in Slides\n\nOne of the most powerful features of Slidev is the ability to use Vue components directly in your slides. This allows you to create interactive and dynamic content with ease.\n\n## Using Components {#use}\n\nWith the help of [`unplugin-vue-components`](https://github.com/unplugin/unplugin-vue-components), Slidev allows you to use Vue components directly in your slides without importing them manually:\n\n```md\n# My Slide\n\n<MyComponent :count=\"4\"/>\n```\n\nThe components come from:\n\n- Built-in components. See [Built-in Components](../builtin/components) for reference.\n- Provided by the theme and addons. See <LinkInline link=\"guide/theme-addon\" />.\n- Custom components in the `components` directory. See the next section.\n\n## Writing Components {#write}\n\nTo create a custom component, simply create a new Vue file in the `components` directory:\n\n```bash\nyour-slidev/\n  ├── ...\n  ├── slides.md\n  └── components/\n      ├── ...\n      └── MyComponent.vue\n```\n\nRefer to the [Vue documentation](https://vuejs.org/guide/essentials/component-basics.html) for how to write Vue components.\n\nYou can also <LinkInline link=\"guide/write-addon\" /> to reuse and share your components with others.\n"
  },
  {
    "path": "docs/guide/exporting.md",
    "content": "---\noutline: deep\n---\n\n# Exporting\n\nUsually the slides are displayed in a web browser, but you can also export them to PDF, PPTX, PNG, or Markdown files for sharing or printing. This feature is available through the CLI command [`slidev export`](../builtin/cli#export).\n\nHowever, interactive features in your slides may not be available in the exported files. You can build and host your slides as a web application to keep the interactivity. See [Building and Hosting](./hosting) for more information.\n\n## The Browser Exporter <Badge> Recommended </Badge> {#browser}\n\n> Available since v0.50.0-beta.11\n\nSlidev provides a UI in the browser for exporting your slides. You can access it by clicking the \"Export\" button in \"More options\" menu in the [navigation bar](./ui#navigation-bar), or go to `http://localhost:<port>/export` directly.\n\nIn the UI, you can export the slides as PDF, or capture the slides as images and download them as a PPTX or zip file.\n\nNote that browsers other than **modern Chromium-based browsers** may not work well with the exporting UI. If you encounter any issues, please try use the CLI instead.\n\n> The following content of this page is for the CLI only.\n\n## The CLI {#cli}\n\nExporting to PDF, PPTX, or PNG relies on [Playwright](https://playwright.dev) for rendering the slides. Therefore [`playwright-chromium`](https://npmjs.com/package/playwright-chromium) is required to be installed in your project:\n\n::: code-group\n\n```bash [pnpm]\n$ pnpm add -D playwright-chromium\n```\n\n```bash [npm]\n$ npm i -D playwright-chromium\n```\n\n```bash [yarn]\n$ yarn add -D playwright-chromium\n```\n\n```bash [bun]\n$ bun add -D playwright-chromium\n```\n\n```bash [deno]\n$ deno add -D npm:playwright-chromium\n```\n\n:::\n\n## Formats\n\n### PDF\n\nAfter installing `playwright-chromium` as described above, you can export your slides to PDF using the following command:\n\n```bash\n$ slidev export\n```\n\nBy default, the PDF will be placed at `./slides-export.pdf`.\n\n### PPTX\n\nSlidev can also export your slides as a PPTX file:\n\n```bash\n$ slidev export --format pptx\n```\n\nNote that all the slides in the PPTX file will be exported as images, so the text will not be selectable. Presenter notes will be conveyed into the PPTX file on a per-slide basis.\n\nIn this mode, the `--with-clicks` option is enabled by default. To disable it, pass `--with-clicks false`.\n\n### PNGs and Markdown\n\nWhen passing in the `--format png` option, Slidev will export PNG images for each slide instead of a PDF:\n\n```bash\n$ slidev export --format png\n```\n\nYou can also compile a markdown file composed of compiled png using `--format md`:\n\n```bash\n$ slidev export --format md\n```\n\n## Options\n\nHere are some common options you can use with the `slidev export` command. For a full list of options, see the [CLI documentation](../builtin/cli#export).\n\n### Export Clicks Steps\n\nBy default, Slidev exports one page per slide with clicks animations disabled. If you want to export slides with multiple steps into multiple pages, pass the `--with-clicks` option:\n\n```bash\n$ slidev export --with-clicks\n```\n\n### Output Filename\n\nYou can specify the output filename with the `--output` option:\n\n```bash\n$ slidev export --output my-pdf-export\n```\n\nOr in the headmatter configuration:\n\n```yaml\n---\nexportFilename: my-pdf-export\n---\n```\n\n### Export with Range\n\nBy default, all slides in the presentation are exported. If you want to export a specific slide or a range of slides you can set the `--range` option and specify which slides you would like to export:\n\n```bash\n$ slidev export --range 1,6-8,10\n```\n\nThis option accepts both specific slide numbers and ranges. The example above would export slides 1,6,7,8 and 10.\n\n### Multiple Exports\n\nYou can also export multiple slides at once:\n\n```bash\n$ slidev export slides1.md slides2.md\n```\n\nOr (only available in certain shells):\n\n```bash\n$ slidev export *.md\n```\n\nIn this case, each input file will generate its own PDF file.\n\n### Dark Mode\n\nIn case you want to export your slides using the dark version of the theme, use the `--dark` option:\n\n```bash\n$ slidev export --dark\n```\n\n### Timeouts\n\nFor big presentations, you might want to increase the Playwright timeout with `--timeout`:\n\n```bash\n$ slidev export --timeout 60000\n```\n\n### Wait\n\nSome parts of your slides may require a longer time to render. You can use the `--wait` option to have an extra delay before exporting:\n\n```bash\n$ slidev export --wait 10000\n```\n\nThere is also a `--wait-until` option to wait for a state before exporting each slide. If you keep encountering timeout issues, you can try setting this option:\n\n```bash\n$ slidev export --wait-until none\n```\n\nPossible values:\n\n- `'networkidle'` - (_default_) consider operation to be finished when there are no network connections for at least `500` ms. This is the safest, but may cause timeouts.\n- `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.\n- `'load'` - consider operation to be finished when the `load` event is fired.\n- `'none'` - do not wait for any event.\n\n::: warning\nWhen specifying values other than `'networkidle'`, please make sure the printed slides are complete and correct. If some contents are missing, you may need to use the `--wait` option.\n:::\n\n### Executable Path\n\nChromium may miss some features like codecs that are required to decode some videos. You can set the browser executable path for Playwright to your Chrome or Edge using `--executable-path`:\n\n```bash\n$ slidev export --executable-path [path_to_chromium]\n```\n\n### PDF Outline\n\n> Available since v0.36.10\n\nYou can generate the PDF outline by passing the `--with-toc` option:\n\n```bash\n$ slidev export --with-toc\n```\n\n### Omit Background\n\nWhen exporting to PNGs, you can remove the default browser background by passing `--omit-background`:\n\n```bash\n$ slidev export --omit-background\n```\n\nThe default browser background is the white background visible on all browser windows and is different than other backgrounds applied throughout the application using CSS styling. [See Playwright docs](https://playwright.dev/docs/api/class-page#page-screenshot-option-omit-background). You will then need to apply additional CSS styling to the application to reveal the transparency.\n\nHere is a basic example that covers all backgrounds in the application:\n\n```css\n* {\n  background: transparent !important;\n}\n```\n\n## Troubleshooting\n\n### Missing Content or Animation not Finished\n\nIf you find that some content is missing or the animations are not finished in the exported PDF, you can try adding a wait time before exporting each slide:\n\n```bash\n$ slidev export --wait 1000\n```\n\n### Broken Emojis\n\nIf the PDF or PNG are missing Emojis, you are likely missing required fonts (such as. e.g. [Google's _Noto Emoji_](https://fonts.google.com/noto/specimen/Noto+Emoji)) in your environment.\n\nThis can affect e.g. CI/CD-like in-container sort of Linux environments. It can be fixed e.g. like this:\n\n```bash\n$ curl -L --output NotoColorEmoji.ttf https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf\n$ sudo mv NotoColorEmoji.ttf /usr/local/share/fonts/\n$ fc-cache -fv\n```\n\n### Wrong Context in Global Layers\n\nSee the tip in https://sli.dev/features/global-layers.\n"
  },
  {
    "path": "docs/guide/faq.md",
    "content": "---\noutline: deep\n---\n\n# FAQ\n\n## Assets Handling {#assets-handling}\n\nYou may use static assets like images and videos in your slides. Since Slidev is based on Vite, you can import them directly in your markdown files.\n\nURLs that can be statically analyzed as assets can use relative paths:\n\n```md\n![alt](./image.png)\n<img src=\"./image.png\" />\n```\n\nIn the above case, the URLs will be resolved to `/BASE_URL/assets/image.png` after build.\n\nHowever, relative paths in frontmatter and other components will be broken after build:\n\n```md\n---\nbackground: ./image.png  # Broken after build\n---\n\n<Comp src=\"./image.png\" />\n```\n\nIn the above case, the URLs are not statically analyzable and will be preserved as-is, which will result in 404 errors after build.\n\nTo solve this, you can place these assets in the [public folder](../custom/directory-structure#public) and use an absolute path to import them:\n\n```md\n---\nbackground: /image.png\n---\n\n<Comp src=\"/image.png\" />\n```\n\nFor more details, refer to [Vite's documentation](https://vitejs.dev/guide/assets.html).\n\n## Positioning {#positioning}\n\nSince Slidev is web-based, CSS is the primary way to position elements. Here are some useful tips for position elements:\n\n### Grids And Flexboxes\n\nYou can use CSS Grids to create complex layouts:\n\n::: code-group\n\n```md [Two columns]\n<div class=\"grid grid-cols-2 gap-4\">\n  <div>\n    The first column\n  </div>\n  <div>\n    The second column\n  </div>\n</div>\n```\n\n```md [Complex case]\n<div class=\"grid grid-cols-[200px_1fr_10%] gap-4\">\n  <div>\n    The first column (200px)\n  </div>\n  <div>\n    The second column (auto fit)\n  </div>\n  <div>\n    The third column (10% width to parent container)\n  </div>\n</div>\n```\n\n:::\n\nAnd use Flexboxes to create more responsive layouts:\n\n::: code-group\n\n```md [Horizontal]\n<div class=\"flex items-center\">\n  <div>\n    First block\n  </div>\n  <div>\n    Second block\n  </div>\n</div>\n```\n\n```md [Vertical]\n<div class=\"flex flex-col items-center\">\n  <div>\n    Centered content\n  </div>\n</div>\n```\n\n:::\n\nLearn more: [CSS Grids](https://css-tricks.com/snippets/css/complete-guide-grid/), [flexboxes](https://css-tricks.com/snippets/css/a-guide-to-flexbox/), or even [Masonry](https://css-tricks.com/native-css-masonry-layout-in-css-grid/).\n\n### Absolute Position\n\nYou can use UnoCSS to position elements absolutely:\n\n```md\n<div class=\"absolute left-30px bottom-30px\">\n  This is a left-bottom aligned footer\n</div>\n```\n\nOr use the draggable elements feature:\n\n<LinkCard link=\"features/draggable\" />\n\n## Adjust Sizes {#adjust-size}\n\n- Adjust all slides's size:\n\n<LinkCard link=\"features/canvas-size\" />\n\n- Adjust several slides' size:\n\n<LinkCard link=\"features/zoom-slide\" />\n\n- Adjust some elements' size:\n\n<LinkCard link=\"features/transform-component\" />\n"
  },
  {
    "path": "docs/guide/global-context.md",
    "content": "# Global Context\n\nSlidev injects several global context values for advanced navigation controls.\n\n## Direct Usage {#direct-usage}\n\nYou can access them directly in your slides or components:\n\n```md [slides.md]\n# Page 1\n\nCurrent page is: {{ $nav.currentPage }}\n```\n\n```vue [Foo.vue]\n<template>\n  <div>Title: {{ $slidev.configs.title }}</div>\n  <button @click=\"$nav.next\">\n    Next Click\n  </button>\n  <button @click=\"$nav.nextSlide\">\n    Next Slide\n  </button>\n</template>\n```\n\n## Composable Usage {#composable-usage}\n\n> Available since v0.48.0\n\nIf you want to get the context programmatically (also type-safely), you can import composables from `@slidev/client`:\n\n```vue\n<script setup>\nimport { onSlideEnter, onSlideLeave, useDarkMode, useIsSlideActive, useNav, useSlideContext } from '@slidev/client'\n\nconst { $slidev } = useSlideContext()\nconst { currentPage, currentLayout, currentSlideRoute } = useNav()\nconst { isDark } = useDarkMode()\nconst isActive = useIsSlideActive()\nonSlideEnter(() => { /* ... */ })\nonSlideLeave(() => { /* ... */ })\n// ...\n</script>\n```\n\n> [!NOTE]\n> Previously, you might see the usage of importing nested modules like `import { isDark } from '@slidev/client/logic/dark.ts'`, this is **NOT RECOMMENDED** as they are internal implementation details and may change in the future. Always use the public APIs from `@slidev/client` if possible.\n\n::: warning\n\nWhen the `useSlideContext` composable is used in a file, the automatic injection of `$slidev` will be disabled. You need to manually get the `$slidev` object to the `useSlideContext` function.\n\n:::\n\n<SeeAlso :links=\"['features/slide-hook']\" />\n\n## Properties {#properties}\n\n### `$slidev` {#slidev}\n\nThe global context object.\n\n### `$frontmatter` {#frontmatter}\n\nThe frontmatter object of the current slide. Note that this is empty for components out of the slides like <LinkInline link=\"features/global-layers\" />.\n\n### `$clicks` {#clicks}\n\n`$clicks` hold the number of clicks on the current slide. Can be used conditionally to show different content on clicks.\n\n```html\n<div v-if=\"$clicks > 3\">Content</div>\n```\n\nSee the <LinkInline link=\"guide/animations\" /> guide for more information.\n\n### `$nav` {#nav}\n\nA reactive object holding the properties and controls of the slide navigation. For examples:\n\n```js\n$nav.next() // go next step\n$nav.nextSlide() // go next slide (skip clicks)\n$nav.go(10) // go slide #10\n\n$nav.currentPage // current slide number\n$nav.currentLayout // current layout name\n```\n\nFor more properties available, refer to the [`SlidevContextNav` interface](https://github.com/slidevjs/slidev/blob/main/packages/client/composables/useNav.ts).\n\n### `$page` {#page}\n\n`$page` holds the number of the current page, 1-indexed.\n\n```md\nPage: {{ $page }}\n\nIs current page active: {{ $page === $nav.currentPage }}\n```\n\n> [!Note] > `$nav.clicks` is a global state while `$clicks` is the local clicks number for each slide.\n\n### `$renderContext` {#render-context}\n\n`$renderContext` holds the current render context, which can be `slide`, `overview`, `presenter` or `previewNext`\n\n```md\n<div v-if=\"['slide', 'presenter'].includes($renderContext)\">\n  This content will only be rendered in main slides view\n</div>\n```\n\nYou can also use the [`<RenderWhen>` component](../builtin/components#renderwhen).\n\n### `$slidev.configs` {#configs}\n\nA reactive object holding the configurations for the slide project. For example:\n\n```md\n---\ntitle: My First Slidev!\n---\n\n# Page 1\n\n---\n\n# Any Page\n\n{{ $slidev.configs.title }} // 'My First Slidev!'\n```\n\n### `$slidev.themeConfigs` {#theme-configs}\n\nA reactive object holding the parsed theme configurations:\n\n```yaml\n---\ntitle: My First Slidev!\nthemeConfig:\n  primary: '#213435'\n---\n```\n\nThen the theme can access the primary color like:\n\n```md\n{{ $slidev.themeConfigs.primary }} // '#213435'\n```\n\n## Types {#types}\n\nIf you want to get a type programmatically, you can import types like `TocItem` from `@slidev/types`:\n\n```vue\n<script setup>\nimport type { TocItem } from '@slidev/types'\n\nfunction tocFunc(tree: TocItem[]): TocItem[] {\n  // ...\n}\n</script>\n```\n"
  },
  {
    "path": "docs/guide/hosting.md",
    "content": "---\noutline: deep\n---\n\n# Building and Hosting\n\nSlidev is designed to run as a web server when you are editing or presenting your slides. However, after the presentation, you may still want to share your **interactive** slides with others. This guide will show you how to build and host your slides.\n\n## Build as a SPA {#spa}\n\nYou can build the slides into a static [Single-page application (SPA)](https://developer.mozilla.org/en-US/docs/Glossary/SPA) via the following command:\n\n```bash\n$ slidev build\n```\n\nBy default, the generated files are placed in the `dist` folder. You can test the built version of you slides by running: `npx vite preview` or any other static server.\n\n### Base Path {#base}\n\nTo deploy your slides under sub-routes, you need to pass the `--base` option. The `--base` path **must begin and end with a slash `/`**. For example:\n\n```bash\n$ slidev build --base /talks/my-cool-talk/\n```\n\nRefer to [Vite's documentation](https://vitejs.dev/guide/build.html#public-base-path) for more details.\n\n### Output directory {#output-directory}\n\nYou can change the output directory using `--out`.\n\n```bash\n$ slidev build --out my-build-folder\n```\n\n### Remove speaker notes {#without-notes}\n\nIf you are sharing the built slides publicly and don't want to include your speaker notes, run the build with `--without-notes`:\n\n```bash\n$ slidev build --without-notes\n```\n\n### Multiple Builds {#multiple-builds}\n\nYou can build multiple slide decks in one go by passing multiple markdown files as arguments:\n\n```bash\n$ slidev build slides1.md slides2.md\n```\n\nOr if your shell supports it, you can use a glob pattern:\n\n```bash\n$ slidev build *.md\n```\n\nIn this case, each input file will generate a folder containing the build in the output directory.\n\n### Examples {#examples}\n\nHere are a few examples of the exported SPA:\n\n- [Demo Slides](https://sli.dev/demo/starter)\n- [Composable Vue](https://talks.antfu.me/2021/composable-vue) by [Anthony Fu](https://github.com/antfu)\n- More in [Showcases](../resources/showcases)\n\n### Options {#options}\n\n<LinkCard link=\"features/build-with-pdf\" />\n<LinkCard link=\"features/bundle-remote-assets\" />\n\n## Hosting {#hosting}\n\nWe recommend using `npm init slidev@latest` to scaffold your project, which contains the necessary configuration files for hosting services out-of-the-box.\n\n### GitHub Pages {#github-pages}\n\nTo deploy your slides on [GitHub Pages](https://pages.github.com/) via GitHub Actions, follow these steps:\n\n1. In your repository, go to `Settings` > `Pages`. Under `Build and deployment`, select `GitHub Actions`. (Do not choose `Deploy from a branch` and upload the `dist` directory, which is not recommended.)\n2. Create `.github/workflows/deploy.yml` with the following content to deploy your slides to GitHub Pages via GitHub Actions.\n\n::: details deploy.yml\n\n```yaml\nname: Deploy pages\n\non:\n  workflow_dispatch:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\nconcurrency:\n  group: pages\n  cancel-in-progress: false\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n\n      - name: Setup @antfu/ni\n        run: npm i -g @antfu/ni\n\n      - name: Install dependencies\n        run: nci\n\n      - name: Build\n        run: nr build --base /${{github.event.repository.name}}/\n\n      - name: Setup Pages\n        uses: actions/configure-pages@v4\n\n      - uses: actions/upload-pages-artifact@v3\n        with:\n          path: dist\n\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    needs: build\n    runs-on: ubuntu-latest\n    name: Deploy\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n```\n\n:::\n\n3. Commit and push the changes to your repository. The GitHub Actions workflow will automatically deploy your slides to GitHub Pages every time you push to the `main` branch.\n4. You can access your slides at `https://<username>.github.io/<repository-name>/`.\n\n### Netlify\n\nCreate `netlify.toml` in your project root with the following content:\n\n::: details netlify.toml\n\n```toml\n[build]\npublish = 'dist'\ncommand = 'npm run build'\n\n[build.environment]\nNODE_VERSION = '20'\n\n[[redirects]]\nfrom = '/*'\nto = '/index.html'\nstatus = 200\n```\n\n:::\n\nThen go to your [Netlify dashboard](https://netlify.com/) and create a new site with the repository.\n\n### Vercel\n\nCreate `vercel.json` in your project root with the following content:\n\n::: details vercel.json\n\n```json\n{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/index.html\" }\n  ]\n}\n```\n\n:::\n\nThen go to your [Vercel dashboard](https://vercel.com/) and create a new site with the repository.\n\n### Zephyr Cloud {#zephyr-cloud}\n\nTo deploy your Slidev deck on [Zephyr Cloud](https://zephyr-cloud.io/), you can add Zephyr support to an existing Slidev project with:\n\n```bash\nnpx with-zephyr@latest\n```\n\nThis codemod detects your bundler (Slidev uses Vite) and updates your config for Zephyr Cloud.\n\nAfter setup, run your normal build command, for example:\n\n```bash\nnpm run build\n```\n\nWhen the build runs with Zephyr enabled, your app is deployed and Zephyr Cloud returns a preview URL.\n\n::: info\nZephyr Cloud is a bit different from most hosting providers: every `build` run triggers a deployment.\n:::\n\n### Host on Docker {#docker}\n\nIf you need a rapid way to run a presentation with containers, you can use the prebuilt [docker image](https://hub.docker.com/r/tangramor/slidev) maintained by [tangramor](https://github.com/tangramor), or build your own.\n\n::: details Use the Docker Image\n\nJust run the following command in your work folder:\n\n```bash\ndocker run --name slidev --rm -it \\\n    --user node \\\n    -v ${PWD}:/slidev \\\n    -p 3030:3030 \\\n    -e NPM_MIRROR=\"https://registry.npmmirror.com\" \\\n    tangramor/slidev:latest\n```\n\n**_Note_**: You can use `NPM_MIRROR` to specify a npm mirror to speed up the installation process.\n\nIf your work folder is empty, it will generate a template `slides.md` and other related files under your work folder, and launch the server on port `3030`.\n\nYou can access your slides from `http://localhost:3030/`\n\nTo create an Docker Image for your slides, you can use the following Dockerfile:\n\n```Dockerfile\nFROM tangramor/slidev:latest\n\nADD . /slidev\n```\n\nCreate the docker image: `docker build -t myslides .`\n\nAnd run the container: `docker run --name myslides --rm --user node -p 3030:3030 myslides`\n\nYou can visit your slides at `http://localhost:3030/`\n\n:::\n"
  },
  {
    "path": "docs/guide/index.md",
    "content": "---\noutline: deep\n---\n\n# Getting Started\n\nSlidev <sup>(slide + dev, **/slaɪdɪv/**)</sup> is a web-based slides maker and presenter. It's designed for developers to focus on writing content in Markdown. With the power of web technologies like Vue, you are able to deliver pixel-perfect designs with interactive demos to your presentation.\n\n::: tip\n\nYou can learn more about the rationale behind this project in <LinkInline link=\"guide/why\" />.\n\n:::\n\n<!--\n- 📝 [**Markdown-based**](/guide/syntax) - focus on content and use your favorite editor\n- 🧑‍💻 [**Developer Friendly**](/guide/syntax#code-blocks) - built-in code highlighting, live coding, etc.\n- 🎨 [**Themable**](/resources/theme-gallery) - theme can be shared and used with npm packages\n- 🌈 [**Stylish**](/guide/syntax#embedded-styles) - on-demand utilities via [UnoCSS](https://github.com/unocss/unocss).\n- 🤹 [**Interactive**](/custom/directory-structure#components) - embedding Vue components seamlessly\n- 🎙 [**Presenter Mode**](/guide/ui#presenter-mode) - use another window, or even your phone to control your slides\n- 🎨 [**Drawing**](/features/drawing) - draw and annotate on your slides\n- 🧮 [**LaTeX**](/guide/syntax#latex) - built-in LaTeX math equations support\n- 📰 [**Diagrams**](/guide/syntax#diagrams) - creates diagrams using textual descriptions with [Mermaid.js](https://mermaid.js.org/)\n- 🌟 [**Icons**](/guide/syntax#icons) - access to icons from any icon set directly\n- 💻 [**Editor**](/guide/index#editor) - integrated editor, or the [VSCode extension](/features/vscode-extension)\n- 🎥 [**Recording**](/features/recording) - built-in recording and camera view\n- 📤 [**Portable**](/guide/exporting) - export into PDF, PNGs, or PPTX\n- ⚡️ [**Fast**](https://vitejs.dev) - instant reloading powered by [Vite](https://vitejs.dev)\n- 🛠 [**Hackable**](/custom/) - using Vite plugins, Vue components, or any npm packages\n-->\n\n<!-- <FeaturesAnimation /> -->\n\n## Create Slides\n\n### Try it Online\n\nStart Slidev right in your browser with StackBlitz: [sli.dev/new](https://sli.dev/new)\n\n### Create Locally\n\n> Requires [Node.js](https://nodejs.org) >= 18.0 installed.\n\nRun the following command to create a new Slidev project locally:\n\n::: code-group\n\n```bash [pnpm]\n# If you haven't installed pnpm\nnpm i -g pnpm\n\npnpm create slidev\n```\n\n```bash [npm]\n# Not recommended -\n# NPM will download the packages each time you create a new project,\n# which is slow and takes up a lot of space\n\nnpm init slidev@latest\n```\n\n```bash [yarn]\nyarn create slidev\n```\n\n```bash [bun]\nbun create slidev\n```\n\n```bash [deno]\ndeno init --npm slidev\n```\n\n:::\n\nFollow the prompts to start your slides project. The slides content is in `slides.md`, which initially includes demos of most the Slidev features. For more information about the Markdown syntax, please check <LinkInline link=\"guide/syntax\" />.\n\n:::: details Single file usage (not recommended)\n\nIf you prefer to have a single Markdown file as your slides, you can install the Slidev CLI globally:\n\n::: code-group\n\n```bash [pnpm]\npnpm i -g @slidev/cli\n```\n\n```bash [npm]\nnpm i -g @slidev/cli\n```\n\n```bash [yarn]\nyarn global add @slidev/cli\n```\n\n```bash [bun]\nbun i -g @slidev/cli\n```\n\n```bash [deno]\ndeno i -g npm:@slidev/cli\n```\n\n:::\n\nThen, you can create and start a single file slides via:\n\n```bash\nslidev slides.md\n```\n\n::::\n\n## Basic Commands\n\nSlidev provides a set of commands in its CLI. Here are some common ones:\n\n- `slidev` - Start the dev server. See [the dev command](../builtin/cli#dev).\n- `slidev export` - Export the slides to PDF, PPTX, or PNGs. See <LinkInline link=\"guide/exporting\" />.\n- `slidev build` - Build the slides as a static web application. See <LinkInline link=\"guide/hosting\" />.\n- `slidev format` - Format the slides. See [the format command](../builtin/cli#format).\n- `slidev --help` - Show the help message\n\nTo run these commands, you can add them to your `package.json` scripts (which has been done for you if the project was created via `npm init slidev`):\n\n```json [package.json]\n{\n  \"scripts\": {\n    \"dev\": \"slidev --open\",\n    \"build\": \"slidev build\",\n    \"export\": \"slidev export\"\n  }\n}\n```\n\nThen, you can simply run `npm run dev`, `npm run build`, and `npm run export`.\n\nFor more information about the CLI, please check the [CLI guide](../builtin/cli).\n\n## Setup Your Editor {#editor}\n\nSince Slidev uses Markdown as the source entry, you can use any editor you prefer to create your slides. We also provide tools to help you edit you slides more conveniently:\n\n<LinkCard link=\"features/vscode-extension\" />\n<LinkCard link=\"features/side-editor\" />\n<LinkCard link=\"features/prettier-plugin\" />\n\n## Join the Community\n\nIt's recommended to join our official [Discord Server](https://chat.sli.dev/) to get help, share your slides, or discuss anything about Slidev.\n\nIf you're encountering bugs, feel free to open an issue on [GitHub](https://github.com/slidevjs/slidev/issues/new/choose).\n\n## Tech Stack\n\nSlidev is made possible by combining these tools and technologies.\n\n- [Vite](https://vitejs.dev) - An extremely fast frontend tooling\n- [Vue 3](https://v3.vuejs.org/) powered [Markdown](https://daringfireball.net/projects/markdown/syntax) - Focus on the content while having the power of HTML and Vue components whenever needed\n- [UnoCSS](https://github.com/unocss/unocss) - On-demand utility-first CSS framework, style your slides at ease\n- [Shiki](https://github.com/shikijs/shiki), [Monaco Editor](https://github.com/Microsoft/monaco-editor) - First-class code snippets support with live coding capability\n- [RecordRTC](https://recordrtc.org) - Built-in recording and camera view\n- [VueUse](https://vueuse.org) family - [`@vueuse/core`](https://github.com/vueuse/vueuse), [`@vueuse/head`](https://github.com/vueuse/head), [`@vueuse/motion`](https://github.com/vueuse/motion), etc.\n- [Iconify](https://iconify.design/) - Iconsets collection.\n- [Drauu](https://github.com/antfu/drauu) - Drawing and annotations support\n- [KaTeX](https://katex.org/) - LaTeX math rendering.\n- [Mermaid](https://mermaid-js.github.io/mermaid) - Textual Diagrams.\n"
  },
  {
    "path": "docs/guide/layout.md",
    "content": "# Slide Layout\n\nLayouts in Slidev are used to define the structure for each slide. They are Vue components that wrap the content of the slides.\n\n## Using Layouts {#use}\n\nTo use a layout, you can specify it in the frontmatter of the slide:\n\n```md\n---\nlayout: quote\n---\n\nA quote from someone\n```\n\nBy default, the layout of the first slide is `cover`, and the rest are `default`.\n\nThe layouts are loaded in the following order, and the last one loaded will override the previous ones:\n\n1. default layouts. See [Built-in Layouts](../builtin/layouts).\n2. layouts provided by the theme\n3. layouts provided by the addons\n4. custom layouts in the `layouts` directory\n\n<SeeAlso :links=\"[\n  'features/slot-sugar',\n]\" />\n\n## Writing Layouts {#write}\n\n<LinkCard link=\"guide/write-layout\" />\n"
  },
  {
    "path": "docs/guide/syntax.md",
    "content": "---\noutline: deep\n---\n\n# Syntax Guide\n\nSlidev's slides are written as Markdown files, which are called **Slidev Markdown**s. A presentation has a Slidev Markdown as its entry, which is `./slides.md` by default, but you can change it by passing the file path as an argument to [the CLI commands](../builtin/cli).\n\nIn a Slidev Markdown, not only [the basic Markdown features](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) can be used as usual, Slidev also provides additional features to enhance your slides. This section covers the syntax introduced by Slidev. Please make sure you know the basic Markdown syntax before reading this guide.\n\n## Slide Separators {#slide-separators}\n\nUse `---` padded with a new line to separate your slides.\n\n````md {5,15}\n# Title\n\nHello, **Slidev**!\n\n---\n\n# Slide 2\n\nUse code blocks for highlighting:\n\n```ts\nconsole.log('Hello, World!')\n```\n\n---\n\n# Slide 3\n\nUse UnoCSS classes and Vue components to style and enrich your slides:\n\n<div class=\"p-3\">\n  <Tweet id=\"...\" />\n</div>\n````\n\n## Frontmatter & Headmatter {#frontmatter}\n\nAt the beginning of each slide, you can add an optional [frontmatter](https://jekyllrb.com/docs/front-matter/) to configure the slide. The first frontmatter block is called **headmatter** and can configure the whole slide deck. The rest are **frontmatters** for individual slides. Texts in the headmatter or the frontmatter should be an object in [YAML](https://www.cloudbees.com/blog/yaml-tutorial-everything-you-need-get-started/) format. For example:\n\n<!-- eslint-skip -->\n\n```md {1-4,10-14,26-28}\n---\ntheme: seriph\ntitle: Welcome to Slidev\n---\n\n# Slide 1\n\nThe frontmatter of this slide is also the headmatter\n\n---\nlayout: center\nbackground: /background-1.png\nclass: text-white\n---\n\n# Slide 2\n\nA page with the layout `center` and a background image\n\n---\n\n# Slide 3\n\nA page without frontmatter\n\n---\nsrc: ./pages/4.md  # This slide only contains a frontmatter\n---\n\n---\n\n# Slide 5\n```\n\nConfigurations you can set are described in the [Slides deck configurations](/custom/#headmatter) and [Per slide configurations](/custom/#frontmatter) sections.\n\nTo make the headmatter more readable, you can install the VSCode extension:\n\n<LinkCard link=\"features/vscode-extension\" />\n\nAlso, there is another possible frontmatter format:\n\n<LinkCard link=\"features/block-frontmatter\" />\n\n## Notes {#notes}\n\nYou can also create presenter notes for each slide. They will show up in <LinkInline link=\"guide/ui#presenter-mode\" /> for you to reference during presentations.\n\nThe comment blocks at the end of each slide are treated as the note of the slide:\n\n```md {9,19-21}\n---\nlayout: cover\n---\n\n# Slide 1\n\nThis is the cover page.\n\n<!-- This is a **note** -->\n\n---\n\n# Slide 2\n\n<!-- This is NOT a note because it is not at the end of the slide -->\n\nThe second page\n\n<!--\nThis is _another_ note\n-->\n```\n\nBasic Markdown and HTML are also supported in notes and will be rendered.\n\n<SeeAlso :links=\"[\n  'features/click-marker',\n]\" />\n\n## Code Blocks {#code-block}\n\nOne big reason that led to the creation of Slidev was the need to perfectly display code in slides. Consequently, you can use Markdown-flavored code blocks to highlight your code.\n\n````md\n```ts\nconsole.log('Hello, World!')\n```\n````\n\nSlidev has [Shiki](https://github.com/shikijs/shiki) built in as the syntax highlighter. Refer to [Configure Shiki](/custom/config-highlighter) for more details.\n\nMore about code blocks:\n\n<LinkCard link=\"features/code-block-line-numbers\" />\n<LinkCard link=\"features/code-block-max-height\" />\n<LinkCard link=\"features/line-highlighting\" />\n<LinkCard link=\"features/monaco-editor\" />\n<LinkCard link=\"features/monaco-run\" />\n<LinkCard link=\"features/monaco-write\" />\n<LinkCard link=\"features/shiki-magic-move\" />\n<LinkCard link=\"features/twoslash\" />\n<LinkCard link=\"features/import-snippet\" />\n<LinkCard link=\"features/code-groups\" />\n\n## LaTeX Blocks {#latex-block}\n\nSlidev supports LaTeX blocks for mathematical and chemical formulas:\n\n<LinkCard link=\"features/latex\" />\n\n## Diagrams {#diagrams}\n\nSlidev supports [Mermaid](https://mermaid.js.org/) and [PlantUML](https://plantuml.com/) for creating diagrams from text:\n\n<LinkCard link=\"features/mermaid\" />\n<LinkCard link=\"features/plantuml\" />\n\n## Comark Syntax {#comark-syntax}\n\nComark Syntax is the easiest way to apply styles and classes to elements:\n\n<LinkCard link=\"features/comark\" />\n\n## Scoped CSS {#scoped-css}\n\nYou can use scoped CSS to style your slides:\n\n<LinkCard link=\"features/slide-scope-style\" />\n\n## Importing Slides {#importing-slides}\n\n<LinkCard link=\"features/importing-slides\" />\n"
  },
  {
    "path": "docs/guide/theme-addon.md",
    "content": "# Theme and Addons\n\nA slides project can have one theme and multiple addons. All of them can provide styles, components, layouts, and other configs to your slides project.\n\n## Use a Theme {#use-theme}\n\nChanging the theme in Slidev is surprisingly easy. All you need to do is to add the `theme` option in your [headmatter](../custom/index#headmatter):\n\n```md\n---\ntheme: seriph\n---\n\n# The first slide\n```\n\nYou can find the list of official themes and community themes in the [Themes Gallery](../resources/theme-gallery).\n\n::: info Theme name convention\n\n- You can also pass a relative or absolute path to a local theme folder, like `../my-theme`\n- You can always use the full package name as the theme name\n- If the theme is [official](../resources/theme-gallery#official-themes) or is named like `slidev-theme-name`, you can omit the `slidev-theme-` prefix\n- For scoped packages like `@org/slidev-theme-name`, the full package name is required\n\n:::\n\nYou can start the server and will be prompted to install the theme after a confirmation.\n\n<div class=\"language-md text-xs pl-6\">\n<pre style=\"overflow: hidden; text-wrap: pretty;\">\n<span class=\"token keyword\">?</span> The theme <span class=\"token string\">\"@slidev/theme-seriph\"</span> was not found in your project, do you want to install it now? › (Y/n)\n</pre>\n</div>\n\nor install the theme manually via:\n\n```bash\n$ npm install @slidev/theme-seriph\n```\n\nAnd that's all, enjoy the new theme! For more details about the usage, you can refer to the theme's README.\n\n<SeeAlso :links=\"[\n  'features/eject-theme',\n]\" />\n\n## Use an Addon {#use-addon}\n\nAddons are similar to themes, but they are more flexible and can be used to add extra features to your slides project. You can add multiple addons to your project, and they can be used to add extra features to your slides project.\n\nTo use an addon, you can add the `addons` option in your [headmatter](../custom/index#headmatter):\n\n```md\n---\naddons:\n  - excalidraw\n  - '@slidev/plugin-notes'\n---\n```\n\nYou can find the list of official addons and community addons in the [Addons Gallery](../resources/addon-gallery).\n"
  },
  {
    "path": "docs/guide/ui.md",
    "content": "---\noutline: deep\n---\n\n# User Interface\n\n## Navigation Bar {#navigation-bar}\n\nIn Play mode, move your mouse to the bottom left corner of the page, you can see the navigation bar.\n![](/screenshots/navbar.png)\n\n> You can extend the navigation bar via <LinkInline link=\"features/global-layers\" />.\n\n## Navigation Actions {#navigation-actions}\n\n| Keyboard Shortcut                   | Button in Navigation Bar                                                              | Description                                                     |\n| ----------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------- |\n| <kbd>f</kbd>                        | <carbon-maximize class=\"inline-icon-btn\"/> <carbon-minimize class=\"inline-icon-btn\"/> | Toggle fullscreen                                               |\n| <kbd>right</kbd> / <kbd>space</kbd> | <carbon-arrow-right class=\"inline-icon-btn\"/>                                         | Next animation or slide                                         |\n| <kbd>left</kbd>                     | <carbon-arrow-left class=\"inline-icon-btn\"/>                                          | Previous animation or slide                                     |\n| <kbd>up</kbd>                       | -                                                                                     | Previous slide                                                  |\n| <kbd>down</kbd>                     | -                                                                                     | Next slide                                                      |\n| <kbd>o</kbd>                        | <carbon-apps class=\"inline-icon-btn\"/>                                                | Toggle [Quick Overview](#quick-overview)                        |\n| <kbd>d</kbd>                        | <carbon-sun class=\"inline-icon-btn\"/> <carbon-moon class=\"inline-icon-btn\"/>          | Toggle dark mode                                                |\n| -                                   | <carbon-user-avatar class=\"inline-icon-btn\"/>                                         | Toggle [Camera View](../features/recording#camera-view)         |\n| -                                   | <carbon-video class=\"inline-icon-btn\"/>                                               | Start <LinkInline link=\"features/recording\" />                  |\n| -                                   | <carbon-user-speaker class=\"inline-icon-btn\"/>                                        | Enter [Presenter Mode](#presenter-mode)                         |\n| -                                   | <carbon-text-annotation-toggle class=\"inline-icon-btn\"/>                              | Toggle <LinkInline link=\"features/side-editor\" />               |\n| -                                   | <carbon-document-pdf class=\"inline-icon-btn\"/>                                        | Enter [Browser Exporter](#exporter)                             |\n| -                                   | <carbon-download class=\"inline-icon-btn\"/>                                            | Download PDF. See <LinkInline link=\"features/build-with-pdf\" /> |\n| -                                   | <carbon-information class=\"inline-icon-btn\"/>                                         | Show information about the slides                               |\n| -                                   | <carbon-settings-adjust class=\"inline-icon-btn\"/>                                     | More options                                                    |\n| <kbd>g</kbd>                        | -                                                                                     | Show goto...                                                    |\n\n> You can [configure the shortcuts](../custom/config-shortcuts).\n\n## Quick Overview {#quick-overview}\n\nBy pressing <kbd>o</kbd> or clicking the <carbon-apps class=\"inline-icon-btn\"/> button in the navigation bar, you can have an overview of your slides so you can jump between them easily.\n\n![](/screenshots/slides-overview.png)\n\n## Presenter Mode {#presenter-mode}\n\nTo enter the presenter mode, you can click the <carbon-user-speaker class=\"inline-icon-btn\"/> button in the navigation panel, or visit `http://localhost:<port>/presenter`.\n\nWhen giving a presentation, it's recommended to open two browser windows - one in the play mode for the audience, and another one in the presenter mode for you. Then you can share the first screen to the audience and keep the second screen for yourself.\n\nWhenever you navigate through the slides in the presenter mode, all other opened pages will automatically follow this navigation to stay in sync with the presenter.\n\n![](/screenshots/presenter-mode.png)\n\n### Presenter Layouts {#presenter-layouts}\n\n> Available since v0.50.0\n\nThe presenter view offers three different layouts that you can cycle through by clicking the layout toggle button <carbon-template class=\"inline-icon-btn\"/> in the navigation bar:\n\n- **Layout 1** (default): Current slide prominently displayed at the top, with notes and next slide preview below\n- **Layout 2**: Notes panel on the left, current slide and next slide stacked on the right\n- **Layout 3**: Notes and current slide on the left, larger next slide preview on the right\n\nEach layout is optimized for different screen sizes and presentation preferences.\n\n### Screen Mirror {#screen-mirror}\n\n> Available since v0.50.0\n\nIn the presenter view, you can switch the main slide area to \"Screen Mirror\" mode. This allows you to capture and display another monitor or window directly in the presenter view.\n\nClick the \"Screen Mirror\" option in the presenter view's segment control, then select the screen or window you want to mirror. This is useful when you want to see exactly what your audience sees on the projector or external display (e.g. live coding / live demo).\n\n## Slide Overview {#slides-overview}\n\n> Available since v0.48.0\n\n<video src=\"https://github.com/slidevjs/slidev/assets/11247099/01bbf5b3-f916-4646-9ea4-cf269c0567cb\"\ncontrols rounded shadow></video>\n\nYou can visit an overview of all of your slides by first opening the [Quick Overview panel](#quick-overview) and then clicking the <carbon-list-boxes class=\"inline-icon-btn\"/> on the top right, or by visiting `http://localhost:<port>/overview` directly.\n\nThe overview page gives you a linear list of all your slides, with all of your notes on the side. You can double-click on the notes to edit the notes directly, and drag the clicks sliders to preview the steps in your slides.\n\n## Notes Editor {#notes-editor}\n\n> Available since v0.52.0\n\nSlidev provides a batch notes editor at `http://localhost:<port>/notes-edit` where you can edit notes for all slides in a single text area.\n\nNotes for each slide are separated by `--- #[slide-number]` markers. Changes are automatically saved as you type with debouncing.\n\nThis is useful when you want to write or review all your speaker notes in one place without switching between slides.\n\n## Drawing UI {#drawing}\n\nSee:\n\n<LinkCard link=\"features/drawing\" />\n\n## Recording UI {#recording}\n\nSee:\n\n<LinkCard link=\"features/recording\"/>\n\n## Browser Exporter {#exporter}\n\nSee:\n\n<LinkCard link=\"guide/exporting#browser\"/>\n\n## Settings {#settings}\n\nClick the <carbon-settings-adjust class=\"inline-icon-btn\"/> button in the navigation bar to access additional settings.\n\n### CSS Filters {#css-filters}\n\n> Available since v0.50.0\n\nWhen presenting on different projectors or displays, colors may appear differently than expected. Slidev provides CSS filter controls to adjust the display in real-time:\n\n- **Invert**: Flip all colors\n- **Brightness**: Adjust overall brightness (0.5 - 1.5)\n- **Contrast**: Adjust contrast levels (0.5 - 1.5)\n- **Saturation**: Adjust color saturation (0.5 - 1.5)\n- **Sepia**: Add sepia tone effect\n- **Hue Rotate**: Shift all colors by degrees (-180 to 180)\n\nThese settings are stored locally and persist across sessions. A dot indicator appears on the settings button when any filter is active.\n\n### Hide Idle Cursor {#hide-idle-cursor}\n\n> Available since v0.50.0\n\nWhen enabled, the cursor will automatically hide after a period of inactivity during the presentation. This provides a cleaner viewing experience for your audience.\n\n### Slide Scale {#slide-scale}\n\nChoose between \"Fit\" mode (scales slides to fit the viewport) or \"1:1\" mode (displays slides at their native resolution).\n\n### Wake Lock {#wake-lock}\n\nWhen enabled, prevents the screen from dimming or locking during your presentation. Requires browser support for the Wake Lock API.\n\n## Global Layers {#global-layers}\n\nYou can add any custom UI below or above your slides for the whole presentation or per-slide:\n\n<LinkCard link=\"features/global-layers\" />\n"
  },
  {
    "path": "docs/guide/why.md",
    "content": "---\noutline: deep\n---\n\n# Why Slidev\n\nThere have been lots of feature-rich WYSIWYG slides makers like [Microsoft PowerPoint](https://www.microsoft.com/en-us/microsoft-365/powerpoint) and [Apple Keynote](https://www.apple.com/keynote/) _(see [Comparisons](#comparisons))_. They are intuitive and easy to learn. So why bother making Slidev?\n\nSlidev aims to provide flexibility and interactivity for **developers** to make their presentations much more interesting, expressive, and attractive by using technologies they are familiar with. Slidev is also open source with a strong community.\n\nSlidev is Markdown-based, which helps you **focus on the content**. Slidev is also Web-based, which means **nothing is impossible** - everything you can do in a web app can apply to your slides.\n\nSlidev is also **progressive**. You can start with a super simple Markdown file, and then use the [built-in features](../features/) when you need them without any configuration. There are also [themes and addons](./theme-addon) you can optionally install to enhance your slides.\n\n![demo slide](/screenshots/cover.png) {#welcome}\n\n## Features\n\n### 📝 Markdown-based\n\nSlidev uses an extended Markdown format to organize your slides in a single plain text file. This helps you focus on the content while allowing you to use Git and any editor you like.\n\n> Learn more: <LinkInline link=\"guide/syntax\"/>.\n\n### 🧑‍💻 Developer Friendly\n\nSlidev provides first-class support for code snippets for developers. It uses [Shiki](https://github.com/shikijs/shiki) to get the most accurate syntax highlighting. Slidev also supports <LinkInline link=\"features/shiki-magic-move\"/> and <LinkInline link=\"features/twoslash\"/>. These make Slidev the best choice for tech talks.\n\n### 🎨 Themable\n\nThemes for Slidev can be shared via npm packages. You apply a theme within one line of code.\n\nCheck out the [Theme Gallery](../resources/theme-gallery) for the beautiful themes made by the official team and the community.\n\n### ⚡ Fast\n\nEvery change you make in the editor will be updated to your slides in the browser **instantly** without reloading, thanks to [Vite's HMR feature](https://vitejs.dev/guide/features.html#hot-module-replacement).\n\n### 🤹 Interactive & Expressive\n\nYou can write Vue components and use them in your slides, which you can then interact with during the presentation to express your idea in a more interesting and intuitive way.\n\nSlidev also has built-in support of <LinkInline link=\"features/monaco-editor\"/>, which empowers you to do live coding in your presentation with auto-completion and hover messages.\n\n### 🎥 Recording Support\n\nSlidev provides built-in recording and camera view. You can share your presentation with your camera view inside, or record and save your screen and camera separately.\n\n> Learn more: <LinkInline link=\"features/recording\"/>.\n\n### 📤 Portable\n\nYou can export your slides into PDF, PPTX, PNGs, or even a single-page application (SPA) via a single command. Then you can share or host it anywhere you like.\n\n> Learn more: <LinkInline link=\"guide/exporting\"/> and <LinkInline link=\"guide/hosting\"/>.\n\n### 🛠 Hackable\n\nBecause Slidev is web-based, everything that can be done in a normal web app can be applied to your slides. For example, WebGL, API requests, iframes, or even live sharing. It's up to your imagination!\n\n> Learn more: [Customization](../custom/).\n\n## Comparisons\n\n::: details Slidev vs. Microsoft PowerPoint / Apple Keynote\n\n[Microsoft PowerPoint](https://www.microsoft.com/en-us/microsoft-365/powerpoint) and [Apple Keynote](https://www.apple.com/keynote/) are feature-rich WYSIWYG slides makers. They are intuitive and easy to learn, which makes them one of the best choices for non-developers.\n\nCompared to them, Slidev has the following advantages:\n\n- Developer-friendly: Code snippets are first-class citizens in Slidev.\n- Markdown-based: Focus on the content, and version control your slides with Git.\n- Web-based: Everything you can do in a web app can apply to your slides.\n- Hackable: Customize anything you like with web technologies.\n- Open source: Slidev is completely open source, and has a strong community.\n\n:::\n\n::: details Slidev vs. Reveal.js\n\n[Reveal.js](https://revealjs.com/) is a popular HTML presentation framework. It is also open source and supports Markdown.\n\nCompared to Reveal.js, Slidev has the following advantages:\n\n- More concise: Slidev uses an extended Markdown format, while Reveal.js encourages you to write HTML to organize your slides.\n- Vue support: You can use Vue components in Slidev to make your slides interactive.\n- Vite-based: Slidev is built on top of Vite, which provides instant HMR and flexible plugin API.\n- Atomatic CSS: You can [UnoCSS](https://unocss.dev/) out of the box to style your slides.\n\n:::\n\n::: details Slidev vs. Marp\n\n[Marp](https://marp.app/) is a Markdown presentation tool that focuses on simplicity and portability. It is also open source and supports Markdown.\n\nCompared to Marp, Slidev has the following advantages:\n\n- The same simplicity: Slidev's slides can start as simple as Marp's.\n- More features: Slidev supports many features that Marp doesn't.\n- Vue support: You can use Vue components in Slidev to make your slides interactive.\n- Vite-based: Slidev is built on top of Vite, which provides instant HMR and flexible plugin API.\n- Atomatic CSS: You can [UnoCSS](https://unocss.dev/) out of the box to style your slides.\n\n:::\n\n## Give it a Try\n\nPlaying around with Slidev will tell you more than thousands of words. Check the <LinkInline link=\"guide/\"/> guide to create your first Slidev project in one click or one command.\n\nOr you can have a quick preview of it:\n\n<iframe class=\"aspect-16/9 rounded-xl w-full shadow-md border-none\" src=\"https://www.youtube.com/embed/eW7v-2ZKZOU\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n"
  },
  {
    "path": "docs/guide/work-with-ai.md",
    "content": "# Work with AI\n\nThanks to Slidev being markdown-based, it works great with AI coding agents.\n\n## Skills\n\nSlidev provides official [skills](https://code.claude.com/docs/en/skills) for AI coding agents, enabling them to understand Slidev's syntax, features, and best practices when helping you create presentations.\n\n### Installation\n\nInstall the Slidev skill to your AI coding agent:\n\n```bash\nnpx skills add slidevjs/slidev\n```\n\nThe source code of the skill is [here](https://github.com/slidevjs/slidev/tree/main/skills/slidev).\n\n### Example Prompts\n\nOnce installed, you can ask agents to help with various Slidev tasks:\n\n```\nCreate a Slidev presentation about TypeScript generics with code examples\n```\n\n```\nAdd a two-column slide with code on the left and explanation on the right\n```\n\n```\nSet up click animations to reveal bullet points one by one\n```\n\n```\nConfigure the presentation for PDF export with speaker notes\n```\n\n### What's Included\n\nThe Slidev skill provides knowledge about:\n\n- Markdown syntax, slide separators, and frontmatter\n- Click animations and transitions\n- Code highlighting, Monaco editor, and magic-move\n- Diagrams (Mermaid, PlantUML) and LaTeX math\n- Built-in layouts and components\n- Exporting and hosting options\n\n## VS Code Extension\n\nThe <LinkInline link=\"features/vscode-extension\" /> provides Language Model Tools that allow VS Code's Copilot and other AI assistants to interact with your Slidev project directly. These tools enable AI to:\n\n- Get information about the active slide and project\n- Retrieve content of specific slides\n- List and search slides by title\n- Navigate between slides\n\nSee <LinkInline link=\"features/vscode-extension#ai-integration\" /> for more details.\n"
  },
  {
    "path": "docs/guide/write-addon.md",
    "content": "# Writing Addons\n\n> Please read <LinkInline link=\"guide/theme-addon\" /> and <LinkInline link=\"guide/write-theme\" /> first.\n\nEach slides project can only have one theme, but can have multiple addons.\n\n## Capability\n\nTheoretically, all the capabilities of a theme can be done in an addon. However, an addon is more like a plugin that extends the functionalities of Slidev.\n\nIt's recommended to implement one or more of the following points in an addon:\n\n- Provide custom components\n- Provide _new_ layouts\n- Provide new code snippets\n- Provide new code runners\n- Configure tools like UnoCSS, Vite, etc.\n\nHowever, the following points are **not** recommended to be done in an addon, and may be better [implemented as a theme](./write-theme):\n\n- Wildcard global styles\n- Overriding existing layouts\n- Overriding configurations\n- Other things that may be incompatible with the theme and other addons\n\nAn addon can also specify its required Slidev version in the same way as themes.\n\n## Previewing\n\nThe same as themes, you can preview your addon via a `./slides.md` like this:\n\n```md [slides.md]\n---\naddons:\n  - ./\n---\n```\n\n## Publishing\n\nWhen publishing the addon, non-JS files like `.vue` and `.ts` files can be published directly without compiling. Slidev will automatically compile them when using the addon.\n\nAddons should follow the following conventions:\n\n- Package name should start with `slidev-addon-`. For example, `slidev-addon-name` or `@scope/slidev-addon-name`\n- Add `\"slidev-addon\"` and `\"slidev\"` in the `keywords` field of your `package.json`\n\nTheme can be used locally without publishing to NPM. If your addon is only for personal use, you can simply use it as a local addon, or publish it as a private scoped package. However, it is recommended to publish it to the NPM registry if you want to share it with others.\n"
  },
  {
    "path": "docs/guide/write-layout.md",
    "content": "# Writing Layouts\n\n> Please read <LinkInline link=\"guide/layout\" /> first.\n\nTo create a custom layout, simply create a new Vue file in the `layouts` directory:\n\n```bash\nyour-slidev/\n  ├── ...\n  ├── slides.md\n  └── layouts/\n      ├── ...\n      └── MyLayout.vue\n```\n\nLayouts are Vue components, so you can use all the features of Vue in them.\n\nIn the layout component, use `<slot/>` (the default slot) for the slide content:\n\n```vue [default.vue]\n<template>\n  <div class=\"slidev-layout default\">\n    <slot />\n  </div>\n</template>\n```\n\nYou can also have [named slots](https://vuejs.org/guide/components/slots.html) for more complex layouts:\n\n```vue [split.vue]\n<template>\n  <div class=\"slidev-layout split\">\n    <div class=\"left\">\n      <slot name=\"left\" />\n    </div>\n    <div class=\"right\">\n      <slot name=\"right\" />\n    </div>\n  </div>\n</template>\n```\n\nAnd then use it with <LinkInline link=\"features/slot-sugar\" />.\n"
  },
  {
    "path": "docs/guide/write-theme.md",
    "content": "# Writing Themes\n\n> Please read <LinkInline link=\"guide/theme-addon\" /> first.\n\nEach slides project can only have one theme. Themes should focus on providing the appearance of slides. If the feature isn't related to the appearance and can be used separately, it should be implemented as an [addon](./write-addon).\n\nTo get started, we recommend you use our generator for scaffolding your first theme\n\n::: code-group\n\n```bash [pnpm]\n$ pnpm create slidev-theme\n```\n\n```bash [npm]\n$ npm init slidev-theme@latest\n```\n\n```bash [yarn]\n$ yarn create slidev-theme\n```\n\n```bash [bun]\n$ bun create slidev-theme\n```\n\n```bash [deno]\n$ deno init --npm slidev-theme\n```\n\n:::\n\nThen you can modify and play with it. You can also refer to the [official themes](../resources/theme-gallery#official-themes) as examples.\n\n## Capability\n\nA theme can contribute to the following points:\n\n- Global styles\n- Provide default configurations\n- Provide custom layouts or override the existing ones\n- Provide custom components\n- Configure tools like UnoCSS, Shiki, etc.\n\nHowever, the following points are **not** recommended to be done in a theme, and may be better implemented as an [addon](./write-addon):\n\n- New code snippets\n- New code runners\n- Other things that can be used separately\n\nBasically, the way to provide global styles, layouts, components and configure tools is the same as doing these in a slides project. For example, to configure Shiki, you can create a `./setup/shiki.ts` as described in [Configure Highlighter](../custom/config-highlighter). You can refer to the [customization guide](/custom/) for more information.\n\nTo provide default Slidev configurations, you can add a `slidev.defaults` field in the `package.json` file, which will be merged with the user's configurations:\n\n```json [package.json]\n{\n  \"slidev\": {\n    \"defaults\": {\n      \"transition\": \"slide-left\",\n      \"aspectRatio\": \"4/3\"\n    }\n  }\n}\n```\n\n### Require Slidev Version\n\nIf the theme is relying on a specific feature of Slidev that is newly introduced, you can set the minimal Slidev version required to have your theme working properly:\n\n```json\n{\n  \"engines\": {\n    \"slidev\": \">=0.48.0\"\n  }\n}\n```\n\nAn error message will be shown when the an incompatible version is used.\n\n### Theme Metadata\n\nBy default, Slidev assumes themes support both light mode and dark mode. If you only want your theme to be presented in a specific color schema, you need to specify it explicitly in the `package.json`:\n\n```json [package.json]\n{\n  \"slidev\": {\n    \"colorSchema\": \"light\" // or \"dark\" or \"both\"\n  }\n}\n```\n\n## Previewing\n\nYou can preview your theme when developing by using a demo slide deck. To do so, create a `./slides.md` file with the following headmatter:\n\n```md [slides.md]\n---\ntheme: ./  # Use the theme in the current directory\n---\n```\n\nThen you can start the demo slides as usual.\n\n## Publishing\n\nWhen publishing the theme, non-JS files like `.vue` and `.ts` files can be published directly without compiling. Slidev will automatically compile them when using the theme.\n\nThemes should follow the following conventions:\n\n- Package name should start with `slidev-theme-`. For example, `slidev-theme-name` or `@scope/slidev-theme-name`\n- Add `\"slidev-theme\"` and `\"slidev\"` in the `keywords` field of your `package.json`\n\nTheme can be used locally without publishing to NPM. If your theme is only for personal use, you can simply use it as a local theme, or publish it as a private scoped package. However, it is recommended to publish it to the NPM registry if you want to share it with others.\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\nmarkdownStyles: false\n---\n\n<LandingPage />\n"
  },
  {
    "path": "docs/netlify.toml",
    "content": "[build]\npublish = \".vitepress/dist\"\ncommand = \"pnpm run build\"\n\n[build.environment]\nNODE_VERSION = \"20\"\nPLAYWRIGHT_BROWSERS_PATH = \"0\"\n\n[[redirects]]\nfrom = \"/new\"\nto = \"https://stackblitz.com/github/slidevjs/new?file=slides.md\"\nstatus = 302\nforce = true\n\n[[redirects]]\nfrom = \"https://slidev.antfu.me/*\"\nto = \"https://sli.dev/:splat\"\nstatus = 301\nforce = true\n\n[[redirects]]\nfrom = \"/demo/composable-vue/*\"\nto = \"https://demo.sli.dev/composable-vue\"\nstatus = 301\nforce = true\n\n[[redirects]]\nfrom = \"/demo/starter/*\"\nto = \"https://demo.sli.dev/starter\"\nstatus = 301\nforce = true\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.html\"\nstatus = 200\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@slidev/docs\",\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\",\n    \"directory\": \"docs\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"files\": [\n    \"**/*.md\",\n    \"!.vitepress/**\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vitepress\",\n    \"build\": \"vitepress build\",\n    \"preview\": \"vitepress preview\"\n  },\n  \"devDependencies\": {\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@iconify/json\": \"catalog:icons\",\n    \"@shikijs/vitepress-twoslash\": \"catalog:prod\",\n    \"@slidev/client\": \"workspace:*\",\n    \"@slidev/parser\": \"workspace:*\",\n    \"@slidev/types\": \"workspace:*\",\n    \"@types/node\": \"catalog:types\",\n    \"@unocss/reset\": \"catalog:frontend\",\n    \"@vueuse/core\": \"catalog:frontend\",\n    \"fast-glob\": \"catalog:prod\",\n    \"gray-matter\": \"catalog:prod\",\n    \"shiki\": \"catalog:frontend\",\n    \"typeit\": \"catalog:docs\",\n    \"typescript\": \"catalog:dev\",\n    \"unocss\": \"catalog:prod\",\n    \"unplugin-icons\": \"catalog:prod\",\n    \"unplugin-vue-components\": \"catalog:prod\",\n    \"vite-plugin-inspect\": \"catalog:prod\",\n    \"vitepress\": \"catalog:docs\",\n    \"vitepress-plugin-group-icons\": \"catalog:docs\",\n    \"vitepress-plugin-llms\": \"catalog:docs\",\n    \"vue\": \"catalog:frontend\"\n  }\n}\n"
  },
  {
    "path": "docs/resources/addon-gallery.md",
    "content": "---\naside: false\n---\n\n<script setup>\nimport AddonGallery from '../.vitepress/theme/components/AddonGallery.vue'\n</script>\n\n# Addon Gallery\n\nBrowse awesome addons available for Slidev here.\n\nRead more about <LinkInline link=\"guide/theme-addon#use-addon\" /> to use them, and <LinkInline link=\"guide/write-addon\" /> to create your own addon.\n\n## Official Addons\n\n<ClientOnly>\n  <AddonGallery collection=\"official\"/>\n</ClientOnly>\n\n## Community Addons\n\nHere are the curated addons made by the community.\n\n<!-- Edit in ./docs/.vitepress/addons.ts -->\n<ClientOnly>\n  <AddonGallery collection=\"community\"/>\n</ClientOnly>\n\n## More Addons\n\nFind all the [addons available on NPM](https://www.npmjs.com/search?q=keywords%3Aslidev-addon).\n"
  },
  {
    "path": "docs/resources/covers.md",
    "content": "# Curated Covers\n\nWe curated a few cover images to demonstrate our starter template.\n\n![](/screenshots/covers.png)\n\n```yaml\n---\n# random image from the curated collection\nbackground: https://cover.sli.dev\n---\n```\n\nIf you enjoy any of them, check out our [Unsplash collection](https://unsplash.com/collections/94734566/slidev) and find out their authors.\n\n[cover.sli.dev](https://cover.sli.dev) is hosted from [`slidevjs/slidev-covers`](https://github.com/slidevjs/slidev-covers).\n"
  },
  {
    "path": "docs/resources/learning.md",
    "content": "# Learning Resources\n\n## English\n\n### Videos\n\n- [Slidev - one of the best presentation software and it is free!](https://www.youtube.com/watch?v=oSgM6GoSwyY) - by [Federico Tartarini](https://www.youtube.com/@FedericoTartarini)\n- [Slides + developers = slidev](https://www.youtube.com/watch?v=nleqgO38pPU) by Murilo Cunha\n\n### Articles\n\n- [Tips To Turn R Markdown Into Slidev Presentation](https://yutani.rbind.io/post/2021-06-05-tips-to-turn-r-markdown-into-slidev-presentation/) by Hiroaki Yutani\n\n## 中文\n\n- [Slidev：一个用Markdown写slides的神器](https://zhuanlan.zhihu.com/p/372729473) by [梦里风林](https://www.zhihu.com/people/meng-li-feng-lin)\n- [神器！这款开源项目可以让你使用 Markdown 来做 PPT！](https://zhuanlan.zhihu.com/p/377567327) by [Github掘金计划](https://www.zhihu.com/people/github-stars)\n\n## 日本語\n\n- [開発者のためのスライド作成ツール Slidev がすごい](https://zenn.dev/ryo_kawamata/articles/introduce-slidev) by [ryo_kawamata](https://zenn.dev/ryo_kawamata)\n- [Markdownでオシャレなスライドを作るSli.dev](https://qiita.com/e99h2121/items/a115f8865a0dc21bb462) by [Nobuko YAMADA](https://qiita.com/e99h2121)\n- [【Slidev 超入門】エンジニアだからこそ作れるつよつよスライドの作り方！](https://zenn.dev/takumaru/articles/3faa75c2f09493) by [takuma-ru](https://zenn.dev/takumaru)\n"
  },
  {
    "path": "docs/resources/showcases.md",
    "content": "---\naside: false\n---\n\n# Showcases\n\nTalks / Presentations using Slidev.\n\n<!-- Edit in ./docs/.vitepress/showcases.ts -->\n<ShowCases />\n"
  },
  {
    "path": "docs/resources/theme-gallery.md",
    "content": "---\naside: false\n---\n\n<script setup>\nimport ThemeGallery from '../.vitepress/theme/components/ThemeGallery.vue'\n</script>\n\n# Theme Gallery\n\nBrowse awesome themes available for Slidev here.\n\nRead more about <LinkInline link=\"guide/theme-addon#use-theme\" /> to use them, and <LinkInline link=\"guide/write-theme\" /> to create your own theme.\n\n## Official Themes {#official-themes}\n\n<ClientOnly>\n  <ThemeGallery collection=\"official\"/>\n</ClientOnly>\n\n## Community Themes {#community-themes}\n\nHere are the curated themes made by the community.\n\n<!-- Edit in ./docs/.vitepress/themes.ts -->\n<ClientOnly>\n  <ThemeGallery collection=\"community\"/>\n</ClientOnly>\n\n## More Themes {#more-themes}\n\nFind all the [themes available on NPM](https://www.npmjs.com/search?q=keywords%3Aslidev-theme).\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"jsx\": \"preserve\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"baseUrl\": \".\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"vite/client\",\n      \"node\"\n    ],\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"noUnusedLocals\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\n    \"./*.ts\",\n    \"./.vitepress/**/*.ts\",\n    \"./.vitepress/**/*.vue\"\n  ],\n  \"exclude\": [\"**/dist/**\", \"node_modules\"]\n}\n"
  },
  {
    "path": "docs/uno.config.ts",
    "content": "import { defineConfig, presetAttributify, presetIcons, presetWebFonts, presetWind3, transformerDirectives } from 'unocss'\n\nexport default defineConfig({\n  presets: [\n    presetWind3(),\n    presetAttributify(),\n    presetWebFonts({\n      fonts: {\n        mono: ['IBM Plex Mono', 'monospace'],\n      },\n    }),\n    presetIcons(),\n  ],\n  transformers: [\n    transformerDirectives(),\n  ],\n  shortcuts: {\n    'bg-main': 'bg-white dark:bg-[#111]',\n  },\n  theme: {\n    colors: {\n      primary: {\n        DEFAULT: '#3AB9D4',\n        deep: '#2082A6',\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "docs/vite.config.ts",
    "content": "import { slidebars } from '.vitepress/config'\nimport UnoCSS from 'unocss/vite'\nimport IconsResolver from 'unplugin-icons/resolver'\nimport Icons from 'unplugin-icons/vite'\nimport Components from 'unplugin-vue-components/vite'\nimport { defineConfig } from 'vite'\nimport Inspect from 'vite-plugin-inspect'\nimport { groupIconVitePlugin } from 'vitepress-plugin-group-icons'\nimport llmstxt from 'vitepress-plugin-llms'\nimport config from './.vitepress/config'\n\nconst IS_ROOT_ENGLISH_DOC = config.locales?.root.label.includes('English') || false\n\nexport default defineConfig({\n  optimizeDeps: {\n    exclude: [\n      'vue-demi',\n      '@vueuse/shared',\n      '@vueuse/core',\n    ],\n  },\n  server: {\n    hmr: {\n      overlay: false,\n    },\n  },\n  plugins: [\n    IS_ROOT_ENGLISH_DOC && llmstxt({\n      ignoreFiles: [\n        'index.md',\n        'README.md',\n      ],\n      sidebar: slidebars,\n    }),\n    Components({\n      dirs: [\n        './.vitepress/theme/components',\n        './node_modules/@slidev/client/builtin',\n      ],\n      extensions: ['vue', 'md'],\n      include: [/\\.vue$/, /\\.vue\\?vue/, /\\.md$/, /\\.md\\?vue/],\n      resolvers: [\n        IconsResolver({\n          prefix: '',\n        }),\n      ],\n    }),\n    Icons({\n      defaultStyle: 'display: inline-block;',\n    }),\n    Inspect(),\n    UnoCSS(),\n    groupIconVitePlugin(),\n  ],\n})\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import antfu from '@antfu/eslint-config'\n\nexport default antfu({\n  pnpm: true,\n  formatters: {\n    markdown: true,\n    css: true,\n    slidev: {\n      files: [\n        '**/slides.md',\n        '**/template.md',\n        '**/example.md',\n        'test/fixtures/markdown/**/*.md',\n        'packages/vscode/syntaxes/slidev.example.md',\n      ],\n    },\n  },\n  ignores: [\n    'skills/**/*.md',\n  ],\n})\n  .removeRules(\n    'vue/no-v-text-v-html-on-component',\n    'vue/component-name-in-template-casing',\n    'jsonc/sort-array-values',\n    'pnpm/yaml-no-duplicate-catalog-item',\n  )\n  .override('antfu/pnpm/package-json', {\n    ignores: [\n      'packages/create-theme/template/package.json',\n      'packages/create-app/template/package.json',\n      // VSCE and OVSX do not support pnpm catalog when reading `@types/vscode`'s version.\n      'packages/vscode/package.json',\n    ],\n  })\n  .remove('antfu/markdown/rules')\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\npublish = \"docs/.vitepress/dist\"\ncommand = \"pnpm run build && pnpm run docs:build\"\n\n[build.environment]\nNODE_VERSION = \"24\"\nNODE_OPTIONS = \"--max-old-space-size=8192\"\nPLAYWRIGHT_BROWSERS_PATH = \"0\"\n\n[[redirects]]\nfrom = \"/new\"\nto = \"https://stackblitz.com/github/slidevjs/new?file=slides.md\"\nstatus = 302\nforce = true\n\n[[redirects]]\nfrom = \"/demo/composable-vue/*\"\nto = \"/demo/composable-vue/index.html\"\nstatus = 200\n\n[[redirects]]\nfrom = \"/demo/starter/*\"\nto = \"/demo/starter/index.html\"\nstatus = 200\n\n[[redirects]]\nfrom = \"/demo/vue-runner/*\"\nto = \"/demo/vue-runner/index.html\"\nstatus = 200\n\n[[redirects]]\nfrom = \"https://slidev.antfu.me/*\"\nto = \"https://sli.dev/:splat\"\nstatus = 301\nforce = true\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.html\"\nstatus = 200\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.30.3\",\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"pnpm -r --filter=\\\"./packages/**\\\" --parallel run build\",\n    \"ci:publish\": \"zx scripts/publish.mjs\",\n    \"cy\": \"cypress open\",\n    \"cy:fixture\": \"pnpm -C cypress/fixtures/basic run dev\",\n    \"demo:build\": \"zx ./scripts/demo.mjs\",\n    \"demo:composable-vue\": \"pnpm -C demo/composable-vue run dev\",\n    \"demo:vue-runner\": \"pnpm -C demo/vue-runner run dev\",\n    \"demo:dev\": \"pnpm -C demo/starter run dev\",\n    \"vscode:dev\": \"pnpm -C packages/vscode run dev\",\n    \"play\": \"pnpm demo:dev\",\n    \"dev\": \"pnpm -r --filter=\\\"./packages/**\\\" --parallel run dev\",\n    \"lint\": \"eslint . --cache\",\n    \"lint:fix\": \"nr lint --fix\",\n    \"typecheck\": \"vue-tsc --noEmit\",\n    \"docs\": \"pnpm -C docs run dev\",\n    \"docs:build\": \"pnpm run --filter=\\\"./docs...\\\" build && pnpm demo:build\",\n    \"release\": \"bumpp package.json packages/*/package.json docs/package.json --all -x \\\"zx scripts/update-versions.mjs\\\"\",\n    \"test\": \"vitest test\",\n    \"prepare\": \"simple-git-hooks\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"catalog:dev\",\n    \"@antfu/ni\": \"catalog:prod\",\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@shikijs/markdown-it\": \"catalog:frontend\",\n    \"@slidev/cli\": \"workspace:*\",\n    \"@slidev/parser\": \"workspace:*\",\n    \"@slidev/types\": \"workspace:*\",\n    \"@types/cli-progress\": \"catalog:types\",\n    \"@types/connect\": \"catalog:types\",\n    \"@types/file-saver\": \"catalog:types\",\n    \"@types/js-yaml\": \"catalog:types\",\n    \"@types/katex\": \"catalog:types\",\n    \"@types/node\": \"catalog:types\",\n    \"@types/prompts\": \"catalog:types\",\n    \"@types/recordrtc\": \"catalog:types\",\n    \"@types/resolve\": \"catalog:types\",\n    \"@types/semver\": \"catalog:types\",\n    \"@types/yargs\": \"catalog:types\",\n    \"@vueuse/core\": \"catalog:frontend\",\n    \"bumpp\": \"catalog:dev\",\n    \"cypress\": \"catalog:dev\",\n    \"eslint\": \"catalog:dev\",\n    \"eslint-plugin-format\": \"catalog:dev\",\n    \"katex\": \"catalog:frontend\",\n    \"lint-staged\": \"catalog:dev\",\n    \"mermaid\": \"catalog:frontend\",\n    \"playwright-chromium\": \"catalog:dev\",\n    \"prettier\": \"catalog:dev\",\n    \"prettier-plugin-slidev\": \"catalog:dev\",\n    \"rimraf\": \"catalog:dev\",\n    \"shiki\": \"catalog:frontend\",\n    \"simple-git-hooks\": \"catalog:dev\",\n    \"taze\": \"catalog:dev\",\n    \"tinyexec\": \"catalog:prod\",\n    \"tsdown\": \"catalog:dev\",\n    \"tsx\": \"catalog:dev\",\n    \"typescript\": \"catalog:dev\",\n    \"vite\": \"catalog:prod\",\n    \"vitest\": \"catalog:dev\",\n    \"vue-tsc\": \"catalog:dev\",\n    \"zx\": \"catalog:dev\"\n  },\n  \"resolutions\": {\n    \"chokidar\": \"catalog:prod\",\n    \"semver\": \"catalog:dev\",\n    \"typescript\": \"catalog:dev\",\n    \"undici\": \"catalog:dev\",\n    \"undici-types\": \"catalog:dev\",\n    \"vite\": \"catalog:prod\"\n  },\n  \"simple-git-hooks\": {\n    \"pre-commit\": \"npx lint-staged\"\n  },\n  \"lint-staged\": {\n    \"**/*.{js,ts,vue,json}\": [\n      \"eslint --fix --cache\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/client/.generated/unocss-tokens.ts",
    "content": "/* eslint-disable eslint-comments/no-unlimited-disable */\n/* eslint-disable */\nexport default [\n  \"!backdrop-blur-0px\",\n  \"!bg-opacity-75\",\n  \"!bg-transparent\",\n  \"!border-none\",\n  \"!hidden\",\n  \"!opacity-0\",\n  \"!opacity-100\",\n  \"!p-4\",\n  \"!px-0\",\n  \"!text-current\",\n  \"!text-sm\",\n  \"-mt-0.5\",\n  \"-mt-1\",\n  \"-rotate-45\",\n  \"-top-15px\",\n  \"-top-20\",\n  \"-translate-y-1/2\",\n  \"-z-1\",\n  \"<md:hidden\",\n  \"[absolute=\\\"\\\"]\",\n  \"[align-top=\\\"\\\"]\",\n  \"[b=\\\"\\\"]\",\n  \"[bg-cyan:10=\\\"\\\"]\",\n  \"[bg-gray:4=\\\"\\\"]\",\n  \"[bg-gray:5=\\\"\\\"]\",\n  \"[bg-gray=\\\"\\\"]\",\n  \"[bg-opacity-30=\\\"\\\"]\",\n  \"[bg-primary=\\\"\\\"]\",\n  \"[bg~=\\\"black\\\"]\",\n  \"[bg~=\\\"opacity-80\\\"]\",\n  \"[border-collapse=\\\"\\\"]\",\n  \"[border-gray=\\\"\\\"]\",\n  \"[border-main=\\\"\\\"]\",\n  \"[border-t=\\\"\\\"]\",\n  \"[border=\\\"\\\"]\",\n  \"[border~=\\\"0\\\"]\",\n  \"[border~=\\\"1\\\"]\",\n  \"[border~=\\\"b\\\"]\",\n  \"[border~=\\\"dark:main\\\"]\",\n  \"[border~=\\\"main\\\"]\",\n  \"[border~=\\\"r\\\"]\",\n  \"[border~=\\\"red\\\"]\",\n  \"[border~=\\\"rounded\\\"]\",\n  \"[border~=\\\"rounded-lg\\\"]\",\n  \"[border~=\\\"rounded-md\\\"]\",\n  \"[border~=\\\"t\\\"]\",\n  \"[border~=\\\"transparent\\\"]\",\n  \"[border~=\\\"y\\\"]\",\n  \"[border~=\\\"~\\\"]\",\n  \"[bottom-0=\\\"\\\"]\",\n  \"[bottom-1=\\\"\\\"]\",\n  \"[bottom-5=\\\"\\\"]\",\n  \"[b~=\\\"100%\\\"]\",\n  \"[b~=\\\"2\\\"]\",\n  \"[b~=\\\"50%\\\"]\",\n  \"[b~=\\\"x\\\"]\",\n  \"[b~=\\\"y\\\"]\",\n  \"[color~=\\\"green\\\"]\",\n  \"[container=\\\"\\\"]\",\n  \"[cursor-pointer=\\\"\\\"]\",\n  \"[dark:bg-gray-800=\\\"\\\"]\",\n  \"[dark:border-gray-500=\\\"\\\"]\",\n  \"[dark:border~=\\\"main\\\"]\",\n  \"[dark:border~=\\\"~\\\"]\",\n  \"[fixed=\\\"\\\"]\",\n  \"[flex-auto=\\\"\\\"]\",\n  \"[flex=\\\"\\\"]\",\n  \"[flex~=\\\"col\\\"]\",\n  \"[flex~=\\\"gap-1\\\"]\",\n  \"[flex~=\\\"gap-2\\\"]\",\n  \"[flex~=\\\"gap-3\\\"]\",\n  \"[flex~=\\\"gap-4\\\"]\",\n  \"[flex~=\\\"items-center\\\"]\",\n  \"[flex~=\\\"justify-center\\\"]\",\n  \"[flex~=\\\"row\\\"]\",\n  \"[flex~=\\\"~\\\"]\",\n  \"[font-bold=\\\"\\\"]\",\n  \"[font-mono=\\\"\\\"]\",\n  \"[grid=\\\"\\\"]\",\n  \"[h-1.5=\\\"\\\"]\",\n  \"[h-1.5em=\\\"\\\"]\",\n  \"[h-1px=\\\"\\\"]\",\n  \"[h-22px=\\\"\\\"]\",\n  \"[h-2=\\\"\\\"]\",\n  \"[h-5=\\\"\\\"]\",\n  \"[h-8=\\\"\\\"]\",\n  \"[h-full=\\\"\\\"]\",\n  \"[h1=\\\"\\\"]\",\n  \"[h5=\\\"\\\"]\",\n  \"[hidden=\\\"\\\"]\",\n  \"[hover:bg-active=\\\"\\\"]\",\n  \"[hover~=\\\"op100\\\"]\",\n  \"[inline-flex=\\\"\\\"]\",\n  \"[inset-0=\\\"\\\"]\",\n  \"[italic=\\\"\\\"]\",\n  \"[items-center=\\\"\\\"]\",\n  \"[justify-center=\\\"\\\"]\",\n  \"[left-0=\\\"\\\"]\",\n  \"[m--1=\\\"\\\"]\",\n  \"[m0=\\\"\\\"]\",\n  \"[marker-start~=\\\"none\\\"]\",\n  \"[max-w-90=\\\"\\\"]\",\n  \"[min-w-30=\\\"\\\"]\",\n  \"[min-w-90=\\\"\\\"]\",\n  \"[my2=\\\"\\\"]\",\n  \"[of-hidden=\\\"\\\"]\",\n  \"[op0=\\\"\\\"]\",\n  \"[op25=\\\"\\\"]\",\n  \"[op40=\\\"\\\"]\",\n  \"[op50=\\\"\\\"]\",\n  \"[op75=\\\"\\\"]\",\n  \"[opacity-10=\\\"\\\"]\",\n  \"[p0.5=\\\"\\\"]\",\n  \"[p1=\\\"\\\"]\",\n  \"[p2=\\\"\\\"]\",\n  \"[p3=\\\"\\\"]\",\n  \"[pl-4=\\\"\\\"]\",\n  \"[pl1=\\\"\\\"]\",\n  \"[pl2=\\\"\\\"]\",\n  \"[pointer-events-none=\\\"\\\"]\",\n  \"[pr-3=\\\"\\\"]\",\n  \"[pr-4=\\\"\\\"]\",\n  \"[print:hidden=\\\"\\\"]\",\n  \"[pt-2px=\\\"\\\"]\",\n  \"[px2=\\\"\\\"]\",\n  \"[px3=\\\"\\\"]\",\n  \"[px4=\\\"\\\"]\",\n  \"[px=\\\"\\\"]\",\n  \"[py-2=\\\"\\\"]\",\n  \"[py0=\\\"\\\"]\",\n  \"[py2=\\\"\\\"]\",\n  \"[p~=\\\"l-1\\\"]\",\n  \"[p~=\\\"r-2\\\"]\",\n  \"[p~=\\\"t-0.5\\\"]\",\n  \"[p~=\\\"x-4\\\"]\",\n  \"[p~=\\\"y-2\\\"]\",\n  \"[relative=\\\"\\\"]\",\n  \"[resize=\\\"\\\"]\",\n  \"[right--2=\\\"\\\"]\",\n  \"[right-0.5=\\\"\\\"]\",\n  \"[right-0=\\\"\\\"]\",\n  \"[right-1=\\\"\\\"]\",\n  \"[right-5=\\\"\\\"]\",\n  \"[rounded-full=\\\"\\\"]\",\n  \"[rounded=\\\"\\\"]\",\n  \"[scale~=\\\"0.5\\\"]\",\n  \"[scale~=\\\"1\\\"]\",\n  \"[select-none=\\\"\\\"]\",\n  \"[shadow=\\\"\\\"]\",\n  \"[shadow~=\\\"$event\\\"]\",\n  \"[shadow~=\\\"~\\\"]\",\n  \"[slidev-glass-effect=\\\"\\\"]\",\n  \"[start~=\\\"1\\\"]\",\n  \"[stroke-width~=\\\"1\\\"]\",\n  \"[stroke-width~=\\\"2\\\"]\",\n  \"[stroke-width~=\\\"20\\\"]\",\n  \"[stroke-width~=\\\"3\\\"]\",\n  \"[stroke~=\\\"transparent\\\"]\",\n  \"[table-cell=\\\"\\\"]\",\n  \"[table-row=\\\"\\\"]\",\n  \"[table=\\\"\\\"]\",\n  \"[text-cyan:75=\\\"\\\"]\",\n  \"[text-main=\\\"\\\"]\",\n  \"[text-primary=\\\"\\\"]\",\n  \"[text-right=\\\"\\\"]\",\n  \"[text-sm=\\\"\\\"]\",\n  \"[text-xs=\\\"\\\"]\",\n  \"[text~=\\\"sm\\\"]\",\n  \"[top-0.5=\\\"\\\"]\",\n  \"[top-0=\\\"\\\"]\",\n  \"[vertical-middle=\\\"\\\"]\",\n  \"[visible=\\\"\\\"]\",\n  \"[w-1.5=\\\"\\\"]\",\n  \"[w-1px=\\\"\\\"]\",\n  \"[w-20=\\\"\\\"]\",\n  \"[w-2=\\\"\\\"]\",\n  \"[w-30=\\\"\\\"]\",\n  \"[w-5=\\\"\\\"]\",\n  \"[w-60=\\\"\\\"]\",\n  \"[w-90=\\\"\\\"]\",\n  \"[w-full=\\\"\\\"]\",\n  \"[w1=\\\"\\\"]\",\n  \"[ws-nowrap=\\\"\\\"]\",\n  \"[z-10=\\\"\\\"]\",\n  \"[z-1=\\\"\\\"]\",\n  \"[z-label=\\\"\\\"]\",\n  \"absolute\",\n  \"align-middle\",\n  \"align-top\",\n  \"animate-duration-100\",\n  \"animate-fade-in\",\n  \"animate-pulse\",\n  \"aspect-initial\",\n  \"aspect-ratio-initial\",\n  \"auto-rows-fr\",\n  \"b\",\n  \"b-dark\",\n  \"b-dashed\",\n  \"backdrop-blur-5px\",\n  \"bg-active\",\n  \"bg-black\",\n  \"bg-blue-400\",\n  \"bg-current\",\n  \"bg-cyan:10\",\n  \"bg-gray\",\n  \"bg-gray-400\",\n  \"bg-gray/10\",\n  \"bg-gray/20\",\n  \"bg-gray/5\",\n  \"bg-gray:10\",\n  \"bg-gray:4\",\n  \"bg-gray:5\",\n  \"bg-main\",\n  \"bg-opacity-10\",\n  \"bg-opacity-30\",\n  \"bg-opacity-50\",\n  \"bg-orange/10\",\n  \"bg-primary\",\n  \"bg-transparent\",\n  \"block\",\n  \"border\",\n  \"border-2\",\n  \"border-b\",\n  \"border-b-2\",\n  \"border-blue-600\",\n  \"border-current\",\n  \"border-dashed\",\n  \"border-gray\",\n  \"border-gray-300/50\",\n  \"border-gray-400\",\n  \"border-l\",\n  \"border-main\",\n  \"border-opacity-20\",\n  \"border-orange\",\n  \"border-primary\",\n  \"border-r\",\n  \"border-r-2\",\n  \"border-t\",\n  \"border-transparent\",\n  \"border-white\",\n  \"bottom-0\",\n  \"bottom-1\",\n  \"bottom-10\",\n  \"bottom-5\",\n  \"break-after-page\",\n  \"break-all\",\n  \"break-inside-avoid-page\",\n  \"caret-black\",\n  \"children:my-auto\",\n  \"container\",\n  \"cursor-default\",\n  \"cursor-pointer\",\n  \"cursor-text\",\n  \"dark:b-gray-400\",\n  \"dark:bg-gray-800\",\n  \"dark:border\",\n  \"dark:border-gray-500\",\n  \"dark:border-true-gray-700\",\n  \"dark:caret-white\",\n  \"dark:stroke-black\",\n  \"dark:text-gray-200\",\n  \"dark:text-green\",\n  \"dark:text-red-500\",\n  \"duration-150\",\n  \"duration-200\",\n  \"duration-300\",\n  \"duration-400\",\n  \"duration-500\",\n  \"ease-in\",\n  \"ease-out\",\n  \"filter\",\n  \"fixed\",\n  \"flex\",\n  \"flex-1\",\n  \"flex-auto\",\n  \"flex-col\",\n  \"flex-grow\",\n  \"flex-none\",\n  \"flex-nowrap\",\n  \"flex-wrap\",\n  \"flex-wrap-reverse\",\n  \"focus-visible:opacity-100\",\n  \"focus-within:opacity-100\",\n  \"focus:outline-none\",\n  \"font-$slidev-code-font-family\",\n  \"font-500\",\n  \"font-bold\",\n  \"font-light\",\n  \"font-mono\",\n  \"gap-0.2\",\n  \"gap-0.5\",\n  \"gap-1\",\n  \"gap-2\",\n  \"gap-3\",\n  \"gap-4\",\n  \"gap-5\",\n  \"gap-x-8\",\n  \"gap-y-4\",\n  \"grid\",\n  \"grid-cols-2\",\n  \"grid-cols-[1fr_max-content]\",\n  \"grid-cols-[35px_1fr]\",\n  \"grid-rows-[1fr_max-content]\",\n  \"grid-rows-[1fr_min-content]\",\n  \"grid-rows-[auto_max-content]\",\n  \"grid-rows-[max-content_1fr]\",\n  \"group-hover:hidden\",\n  \"group-hover:op100\",\n  \"group-hover:op80\",\n  \"group-hover:opacity-20\",\n  \"group-not-hover:hidden\",\n  \"h-0.7\",\n  \"h-1.5\",\n  \"h-10px\",\n  \"h-1px\",\n  \"h-2\",\n  \"h-22px\",\n  \"h-3.5\",\n  \"h-30\",\n  \"h-3px\",\n  \"h-40\",\n  \"h-40px\",\n  \"h-5\",\n  \"h-6\",\n  \"h-8\",\n  \"h-9\",\n  \"h-[40px]\",\n  \"h-[calc(var(--vh,1vh)*100)]\",\n  \"h-full\",\n  \"h-max\",\n  \"h-screen\",\n  \"h1\",\n  \"h2\",\n  \"h5\",\n  \"hidden\",\n  \"hover:!opacity-100\",\n  \"hover:bg-active\",\n  \"hover:bg-gray-400\",\n  \"hover:bg-gray/20\",\n  \"hover:bg-opacity-10\",\n  \"hover:border-primary\",\n  \"hover:border-solid\",\n  \"hover:op-100\",\n  \"hover:op100\",\n  \"hover:opacity-10\",\n  \"hover:opacity-100\",\n  \"hover:opacity-90\",\n  \"hover:text-primary\",\n  \"important-text-op-50\",\n  \"important:[&_*]:select-none\",\n  \"important:op0\",\n  \"inline\",\n  \"inline-block\",\n  \"inline-flex\",\n  \"inset-0\",\n  \"inset-0.5\",\n  \"inset-y-2\",\n  \"italic\",\n  \"items-center\",\n  \"items-end\",\n  \"justify-center\",\n  \"justify-items-start\",\n  \"leading-1.6em\",\n  \"leading-1em\",\n  \"leading-2\",\n  \"leading-5\",\n  \"leading-[.8rem]\",\n  \"left-0\",\n  \"left-1\",\n  \"left-1/2\",\n  \"left-110%\",\n  \"left-3\",\n  \"lg:m-2\",\n  \"lg:p-2\",\n  \"lg:p-4\",\n  \"list-disc\",\n  \"lt-md:flex-col\",\n  \"lt-md:hidden\",\n  \"m-1\",\n  \"m-4\",\n  \"m-auto\",\n  \"m0\",\n  \"ma\",\n  \"max-h-full\",\n  \"max-w-100\",\n  \"max-w-150\",\n  \"max-w-250\",\n  \"max-w-300\",\n  \"max-w-90\",\n  \"max-w-full\",\n  \"max-w-xs\",\n  \"mb--.5\",\n  \"mb-10\",\n  \"mb-2\",\n  \"mb-4\",\n  \"mb-8\",\n  \"mb2\",\n  \"md:flex-col\",\n  \"md:flex-nowrap\",\n  \"md:flex-row\",\n  \"md:gap-8\",\n  \"md:my-4\",\n  \"md:of-y-auto\",\n  \"min-h-50\",\n  \"min-h-full\",\n  \"min-w-16\",\n  \"min-w-30\",\n  \"min-w-40\",\n  \"min-w-90\",\n  \"min-w-fit\",\n  \"min-w-full\",\n  \"min-w-max\",\n  \"ml--4\",\n  \"ml-1\",\n  \"mr--3\",\n  \"mr--8\",\n  \"mr-1\",\n  \"mr-2\",\n  \"mr-4\",\n  \"mr1\",\n  \"mt-0.5\",\n  \"mt-1\",\n  \"mt-2\",\n  \"mt-3\",\n  \"mt1\",\n  \"mx--1.2\",\n  \"mx-auto\",\n  \"my-1\",\n  \"my-10px\",\n  \"my-4\",\n  \"my-auto\",\n  \"my1\",\n  \"my2\",\n  \"my4\",\n  \"my5\",\n  \"mya\",\n  \"object-contain\",\n  \"object-cover\",\n  \"of-auto\",\n  \"of-hidden\",\n  \"of-x-visible\",\n  \"of-y-auto\",\n  \"op-40\",\n  \"op-60\",\n  \"op-70\",\n  \"op-80\",\n  \"op0\",\n  \"op100\",\n  \"op15\",\n  \"op20\",\n  \"op25\",\n  \"op30\",\n  \"op35\",\n  \"op40\",\n  \"op50\",\n  \"op75\",\n  \"op80\",\n  \"opacity-0\",\n  \"opacity-10\",\n  \"opacity-25\",\n  \"opacity-40\",\n  \"opacity-5\",\n  \"opacity-50\",\n  \"opacity-80\",\n  \"origin-tl\",\n  \"outline\",\n  \"outline-none\",\n  \"overflow-auto\",\n  \"overflow-hidden\",\n  \"overflow-visible\",\n  \"overflow-x-hidden\",\n  \"overflow-y-auto\",\n  \"overflow-y-hidden\",\n  \"p-1\",\n  \"p-16\",\n  \"p-2\",\n  \"p-6\",\n  \"p0.5\",\n  \"p1\",\n  \"p2\",\n  \"p3\",\n  \"p4\",\n  \"pa-3\",\n  \"pb-2\",\n  \"pb2\",\n  \"pie\",\n  \"pl-0\",\n  \"pl-4\",\n  \"pl1\",\n  \"pl2\",\n  \"place-content-center\",\n  \"pointer-events-none\",\n  \"pr-12\",\n  \"pr-3\",\n  \"pr-4\",\n  \"print-container\",\n  \"print:block\",\n  \"print:hidden\",\n  \"print:inset-0\",\n  \"print:min-h-max\",\n  \"print:position-unset\",\n  \"prose\",\n  \"pt-.5\",\n  \"pt-1\",\n  \"pt-15%\",\n  \"pt-2\",\n  \"pt-4\",\n  \"pt5\",\n  \"px\",\n  \"px-1.5\",\n  \"px-2\",\n  \"px-3\",\n  \"px-4\",\n  \"px-5\",\n  \"px-6\",\n  \"px1\",\n  \"px2\",\n  \"px3\",\n  \"px4\",\n  \"py-1\",\n  \"py-10\",\n  \"py-2\",\n  \"py-20\",\n  \"py-3\",\n  \"py-4\",\n  \"py0\",\n  \"py0.5\",\n  \"py1\",\n  \"py1.5\",\n  \"py2\",\n  \"py3\",\n  \"relative\",\n  \"resize\",\n  \"resize-none\",\n  \"right--2\",\n  \"right-0\",\n  \"right-0.5\",\n  \"right-1\",\n  \"right-3\",\n  \"right-4\",\n  \"right-5\",\n  \"rounded\",\n  \"rounded-1/2\",\n  \"rounded-b\",\n  \"rounded-full\",\n  \"rounded-l\",\n  \"rounded-lb\",\n  \"rounded-lg\",\n  \"rounded-md\",\n  \"rounded-r\",\n  \"rounded-tl\",\n  \"scale-102\",\n  \"scale-85\",\n  \"scale-x-80\",\n  \"select-none\",\n  \"select-text\",\n  \"shadow\",\n  \"shadow-xl\",\n  \"slidev-glass-effect\",\n  \"sr-only\",\n  \"stroke-white\",\n  \"tab\",\n  \"table\",\n  \"table-cell\",\n  \"table-row\",\n  \"tabular-nums\",\n  \"text-$slidev-controls-foreground\",\n  \"text-1.2em\",\n  \"text-11px\",\n  \"text-2xl\",\n  \"text-3em\",\n  \"text-3xl\",\n  \"text-4xl\",\n  \"text-9xl\",\n  \"text-base\",\n  \"text-blue-500\",\n  \"text-center\",\n  \"text-current\",\n  \"text-cyan:75\",\n  \"text-gray-400\",\n  \"text-gray-500\",\n  \"text-green\",\n  \"text-green-500\",\n  \"text-green6\",\n  \"text-lg\",\n  \"text-main\",\n  \"text-nowrap\",\n  \"text-opacity-50!\",\n  \"text-opacity-85\",\n  \"text-orange-500\",\n  \"text-orange/100\",\n  \"text-primary\",\n  \"text-red-400\",\n  \"text-red-500\",\n  \"text-red-700\",\n  \"text-right\",\n  \"text-sm\",\n  \"text-teal-700\",\n  \"text-transparent\",\n  \"text-white\",\n  \"text-wrap\",\n  \"text-xl\",\n  \"text-xs\",\n  \"top-0\",\n  \"top-0.5\",\n  \"top-1\",\n  \"top-1/2\",\n  \"top-10\",\n  \"top-4\",\n  \"top-5\",\n  \"top-50%\",\n  \"touch-none\",\n  \"tracking-widest\",\n  \"transform\",\n  \"transition\",\n  \"transition-all\",\n  \"transition-opacity\",\n  \"translate-0\",\n  \"translate-y--50%\",\n  \"underline\",\n  \"uppercase\",\n  \"view-transition-name\",\n  \"visible\",\n  \"w-0\",\n  \"w-1.5\",\n  \"w-10px\",\n  \"w-13\",\n  \"w-1px\",\n  \"w-2\",\n  \"w-20\",\n  \"w-200\",\n  \"w-22px\",\n  \"w-250\",\n  \"w-3.5\",\n  \"w-30\",\n  \"w-5\",\n  \"w-6\",\n  \"w-60\",\n  \"w-7\",\n  \"w-8\",\n  \"w-9\",\n  \"w-90\",\n  \"w-[40px]\",\n  \"w-full\",\n  \"w-screen\",\n  \"w1\",\n  \"whitespace-nowrap\",\n  \"ws-nowrap\",\n  \"z-1\",\n  \"z-10\",\n  \"z-camera\",\n  \"z-context-menu\",\n  \"z-dragging\",\n  \"z-label\",\n  \"z-menu\",\n  \"z-modal\",\n  \"z-nav\"\n]"
  },
  {
    "path": "packages/client/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { watchEffect } from 'vue'\nimport { themeVars } from './env'\nimport setupRoot from './setup/root'\n\nsetupRoot()\n\nwatchEffect(() => {\n  for (const [key, value] of Object.entries(themeVars.value))\n    document.body.style.setProperty(key, value.toString())\n})\n</script>\n\n<template>\n  <RouterView />\n</template>\n"
  },
  {
    "path": "packages/client/README.md",
    "content": "# @slidev/client\n\n[![NPM version](https://img.shields.io/npm/v/@slidev/client?color=3AB9D4&label=)](https://www.npmjs.com/package/@slidev/client)\n\nClient code for [Slidev](https://sli.dev). Shipped with [`@slidev/cli`](https://www.npmjs.com/package/@slidev/cli).\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/client/builtin/Arrow.vue",
    "content": "<!--\n\nSimple Arrow\n\n<arrow x1=\"10\" y1=\"20\" x2=\"100\" y2=\"200\" color=\"green\" width=\"3\" />\n\n<arrow v-bind=\"{ x1:10, y1:10, x2:200, y2:200 }\"/>\n\n-->\n\n<script setup lang=\"ts\">\nimport { onClickOutside } from '@vueuse/core'\nimport { ref } from 'vue'\nimport { makeId } from '../logic/utils'\n\ndefineProps<{\n  x1: number | string\n  y1: number | string\n  x2: number | string\n  y2: number | string\n  width?: number | string\n  color?: string\n  twoWay?: boolean\n}>()\n\nconst emit = defineEmits(['dblclick', 'clickOutside'])\n\nconst id = makeId()\n\nconst markerAttrs = {\n  markerUnits: 'strokeWidth',\n  markerHeight: 7,\n  refY: 3.5,\n  orient: 'auto',\n}\n\nconst clickArea = ref<HTMLElement>()\nonClickOutside(clickArea, () => emit('clickOutside'))\n</script>\n\n<template>\n  <svg\n    class=\"absolute left-0 top-0\"\n    :width=\"Math.max(+x1, +x2) + 50\"\n    :height=\"Math.max(+y1, +y2) + 50\"\n  >\n    <defs>\n      <marker :id=\"id\" markerWidth=\"10\" refX=\"9\" v-bind=\"markerAttrs\">\n        <polygon points=\"0 0, 10 3.5, 0 7\" :fill=\"color || 'currentColor'\" @dblclick=\"emit('dblclick')\" />\n      </marker>\n      <marker v-if=\"twoWay\" :id=\"`${id}-rev`\" markerWidth=\"20\" refX=\"11\" v-bind=\"markerAttrs\">\n        <polygon points=\"20 0, 10 3.5, 20 7\" :fill=\"color || 'currentColor'\" @dblclick=\"emit('dblclick')\" />\n      </marker>\n    </defs>\n    <line\n      :x1 :y1 :x2 :y2\n      :stroke=\"color || 'currentColor'\"\n      :stroke-width=\"width || 2\"\n      :marker-end=\"`url(#${id})`\"\n      :marker-start=\"twoWay ? `url(#${id}-rev)` : 'none'\"\n      @dblclick=\"emit('dblclick')\"\n    />\n    <line\n      ref=\"clickArea\"\n      :x1 :y1 :x2 :y2\n      stroke=\"transparent\"\n      stroke-linecap=\"round\"\n      :stroke-width=\"20\"\n      @dblclick=\"emit('dblclick')\"\n    />\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/AutoFitText.vue",
    "content": "<!--\n[Experimental]\n\nThink this component as the TextBox you that will see\nin PowerPoint or Keynote. It will automatically resize\nthe font size based on it's content to fit them in.\n\nUsage:\n\n<AutoFitText modelValue=\"text\"/>\n\nor\n\n<AutoFitText :max=\"80\" :min=\"100\" v-model=\"text\"/>\n-->\n\n<script setup lang=\"ts\">\nimport { useElementSize, useVModel } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\n\nconst props = defineProps({\n  modelValue: {\n    default: '',\n  },\n  max: {\n    default: 100,\n  },\n  min: {\n    default: 30,\n  },\n})\n\nconst emit = defineEmits<{\n  (e: any): void\n}>()\nconst container = ref<HTMLDivElement>()\nconst inner = ref<HTMLDivElement>()\nconst size = ref(100)\nconst fontSize = computed(() => `${size.value}px`)\nconst value = useVModel(props, 'modelValue', emit)\n\nconst containerSize = useElementSize(container)\nconst innerSize = useElementSize(inner)\n\nconst wrapLen = ref(0)\nconst wrap = ref('nowrap')\n\nwatch([container, value, containerSize.width, innerSize.width], async () => {\n  if (!container.value || innerSize.width.value <= 0)\n    return\n  const ratio = containerSize.width.value / innerSize.width.value\n  if (Number.isNaN(ratio) || ratio <= 0)\n    return\n  let newSize = size.value * (containerSize.width.value / innerSize.width.value)\n  if (newSize < props.min) {\n    wrapLen.value = value.value.length\n    wrap.value = ''\n  }\n  else {\n    if (value.value.length < wrapLen.value)\n      wrap.value = 'nowrap'\n  }\n  newSize = Math.max(props.min, Math.min(props.max, newSize))\n  size.value = newSize\n})\n</script>\n\n<template>\n  <div ref=\"container\" class=\"slidev-auto-fit-text\">\n    <div ref=\"inner\" class=\"slidev-auto-fit-text-inner\">\n      <slot>\n        {{ value }}\n      </slot>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.slidev-auto-fit-text {\n  overflow: auto;\n  font-size: v-bind(fontSize);\n  white-space: v-bind(wrap);\n}\n\n.slidev-auto-fit-text-inner {\n  display: inline-block;\n}\n</style>\n"
  },
  {
    "path": "packages/client/builtin/CodeBlockWrapper.vue",
    "content": "<!--\nLine highlighting for code blocks.\n(auto transformed, you don't need to use this component directly)\n\nUsage:\n\n```ts {1,3-5|2,4}\nconst your_code = 'here'\n```\n\nLearn more: https://sli.dev/guide/syntax.html#line-highlighting\n-->\n\n<script setup lang=\"ts\">\nimport type { PropType, Ref } from 'vue'\nimport { useClipboard } from '@vueuse/core'\nimport { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'\nimport { CLASS_VCLICK_HIDDEN, CLICKS_MAX } from '../constants'\nimport { useSlideContext } from '../context'\nimport { configs } from '../env'\nimport TitleIcon from '../internals/TitleIcon.vue'\nimport { makeId, updateCodeHighlightRange } from '../logic/utils'\n\nconst props = defineProps({\n  ranges: {\n    type: Array as PropType<string[]>,\n    default: () => [],\n  },\n  finally: {\n    type: [String, Number],\n    default: 'last',\n  },\n  startLine: {\n    type: Number,\n    default: 1,\n  },\n  lines: {\n    type: Boolean,\n    default: configs.lineNumbers,\n  },\n  at: {\n    type: [String, Number],\n    default: '+1',\n  },\n  maxHeight: {\n    type: String,\n    default: undefined,\n  },\n  title: {\n    type: String,\n    default: undefined,\n  },\n})\n\nconst { $clicksContext: clicks } = useSlideContext()\nconst el = ref<HTMLDivElement>()\nconst id = makeId()\n\nonUnmounted(() => {\n  clicks!.unregister(id)\n})\n\nwatchEffect(() => {\n  el.value?.classList.toggle('slidev-code-line-numbers', props.lines)\n})\n\nonMounted(() => {\n  if (!clicks || !props.ranges?.length)\n    return\n\n  const clicksInfo = clicks.calculateSince(props.at, props.ranges.length - 1)\n  clicks.register(id, clicksInfo)\n\n  const index = computed(() => clicksInfo ? Math.max(0, clicks.current - clicksInfo.start + 1) : CLICKS_MAX)\n\n  const finallyRange = computed(() => {\n    return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()\n  })\n\n  watchEffect(() => {\n    if (!el.value)\n      return\n\n    let rangeStr = props.ranges[index.value] ?? finallyRange.value\n    const hide = rangeStr === 'hide'\n    el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)\n    if (hide)\n      rangeStr = props.ranges[index.value + 1] ?? finallyRange.value\n\n    const pre = el.value.querySelector('.shiki')!\n    const lines = Array.from(pre.querySelectorAll('code > .line'))\n    const linesCount = lines.length\n\n    updateCodeHighlightRange(\n      rangeStr,\n      linesCount,\n      props.startLine,\n      no => [lines[no]],\n    )\n\n    // Scroll to the highlighted line if `maxHeight` is set\n    if (props.maxHeight) {\n      const highlightedEls = Array.from(pre.querySelectorAll('.line.highlighted')) as HTMLElement[]\n      const height = highlightedEls.reduce((acc, el) => el.offsetHeight + acc, 0)\n      if (height > el.value.offsetHeight) {\n        highlightedEls[0].scrollIntoView({ behavior: 'smooth', block: 'start' })\n      }\n      else if (highlightedEls.length > 0) {\n        const middleEl = highlightedEls[Math.round((highlightedEls.length - 1) / 2)]\n        middleEl.scrollIntoView({ behavior: 'smooth', block: 'center' })\n      }\n    }\n  })\n})\n\nconst { copied, copy } = useClipboard()\n\nfunction copyCode() {\n  const code = el.value?.querySelector('.slidev-code')?.textContent\n  if (code)\n    copy(code)\n}\n\n// code block title\nconst activeTitle = inject<Ref<string> | null>('activeTitle', null)\n\nconst isBlockTitleShow = computed(() => {\n  return activeTitle === null && props.title\n})\n</script>\n\n<template>\n  <div\n    ref=\"el\"\n    class=\"slidev-code-wrapper relative group\"\n    :class=\"{\n      'slidev-code-line-numbers': props.lines,\n      'active': activeTitle === title,\n    }\"\n    :style=\"{\n      'max-height': props.maxHeight,\n      'overflow-y': props.maxHeight ? 'scroll' : undefined,\n      '--start': props.startLine,\n    }\"\n    :data-title=\"title\"\n  >\n    <div v-if=\"isBlockTitleShow\" class=\"slidev-code-block-title\">\n      <TitleIcon :title=\"title\" />\n      <div class=\"leading-1em\">\n        {{ title.replace(/~([^~]+)~/g, '').trim() }}\n      </div>\n    </div>\n    <slot />\n    <button\n      v-if=\"configs.codeCopy\"\n      class=\"slidev-code-copy absolute right-0 transition opacity-0 group-hover:opacity-20 hover:!opacity-100\"\n      :class=\"isBlockTitleShow ? 'top-10' : 'top-0'\"\n      :title=\"copied ? 'Copied' : 'Copy'\" @click=\"copyCode()\"\n    >\n      <ph-check-circle v-if=\"copied\" class=\"p-2 w-8 h-8\" />\n      <ph-clipboard v-else class=\"p-2 w-8 h-8\" />\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/CodeGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport { onMounted, provide, ref, useTemplateRef } from 'vue'\nimport TitleIcon from '../internals/TitleIcon.vue'\n\nconst codeGroupBlocksRef = useTemplateRef('codeGroupBlocksRef')\nconst activeTitle = ref('')\n\nprovide('activeTitle', activeTitle)\nconst tabs = ref<string[]>([])\n\nonMounted(() => {\n  const codeGroupBlocks = codeGroupBlocksRef.value\n  let isActiveSet = false\n\n  codeGroupBlocks?.querySelectorAll('.slidev-code-wrapper')?.forEach((block) => {\n    const title = block.getAttribute('data-title') || ''\n    if (title) {\n      if (!isActiveSet) {\n        activeTitle.value = title\n        isActiveSet = true\n      }\n      tabs.value.push(title)\n    }\n  })\n})\n</script>\n\n<template>\n  <div class=\"slidev-code-group\">\n    <div class=\"slidev-code-group-tabs\">\n      <div v-for=\"tab in tabs\" :key=\"tab\" class=\"flex items-center\">\n        <div\n          class=\"slidev-code-tab\"\n          :style=\"{\n            borderColor: activeTitle === tab ? 'var(--slidev-theme-primary)' : 'transparent',\n            color: activeTitle === tab ? 'var(--slidev-code-tab-active-text-color)' : 'var(--slidev-code-tab-text-color)',\n          }\"\n          @click=\"activeTitle = tab\"\n        >\n          <TitleIcon :title=\"tab\" />\n\n          <div>\n            {{ tab.replace(/~([^~]+)~/g, '').trim() }}\n          </div>\n        </div>\n      </div>\n    </div>\n    <div ref=\"codeGroupBlocksRef\" class=\"slidev-code-group-blocks\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/KaTexBlockWrapper.vue",
    "content": "<!--\nLine highlighting for KaTex blocks/\n(auto transformed, you don't need to use this component directly)\n\nUsage:\n$$ {1|3|all}\n\\begin{array}{c}\n\n\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} &\n= \\frac{4\\pi}{c}\\vec{\\mathbf{j}}    \\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n\n\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n\n\\nabla \\cdot \\vec{\\mathbf{B}} & = 0\n\n\\end{array}\n$$\n\nLearn more: https://sli.dev/guide/syntax.html#latex-line-highlighting\n-->\n\n<script setup lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { parseRangeString } from '@slidev/parser/utils'\nimport { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'\nimport { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, CLICKS_MAX } from '../constants'\nimport { useSlideContext } from '../context'\nimport { makeId } from '../logic/utils'\n\nconst props = defineProps({\n  ranges: {\n    type: Array as PropType<string[]>,\n    default: () => [],\n  },\n  finally: {\n    type: [String, Number],\n    default: 'last',\n  },\n  startLine: {\n    type: Number,\n    default: 1,\n  },\n  at: {\n    type: [String, Number],\n    default: '+1',\n  },\n})\n\nconst { $clicksContext: clicks } = useSlideContext()\nconst el = ref<HTMLDivElement>()\nconst id = makeId()\n\nonUnmounted(() => {\n  clicks!.unregister(id)\n})\n\nonMounted(() => {\n  if (!clicks || !props.ranges?.length)\n    return\n\n  const clicksInfo = clicks.calculateSince(props.at, props.ranges.length - 1)\n  clicks.register(id, clicksInfo)\n\n  const index = computed(() => clicksInfo ? Math.max(0, clicks.current - clicksInfo.start + 1) : CLICKS_MAX)\n\n  const finallyRange = computed(() => {\n    return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()\n  })\n\n  watchEffect(() => {\n    if (!el.value)\n      return\n\n    let rangeStr = props.ranges[index.value] ?? finallyRange.value\n    const hide = rangeStr === 'hide'\n    el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)\n    if (hide)\n      rangeStr = props.ranges[index.value + 1] ?? finallyRange.value\n\n    // KaTeX equations have col-align-XXX as parent\n    const equationParents = el.value.querySelectorAll('.mtable > [class*=col-align]')\n    if (!equationParents)\n      return\n\n    // For each row we extract the individual equation rows\n    const equationRowsOfEachParent = Array.from(equationParents)\n      .map(item => Array.from(item.querySelectorAll(':scope > .vlist-t > .vlist-r > .vlist > span > .mord')))\n    // This list maps rows from different parents to line them up\n    const lines: Element[][] = []\n    for (const equationRowParent of equationRowsOfEachParent) {\n      equationRowParent.forEach((equationRow, idx) => {\n        if (!equationRow)\n          return\n        if (Array.isArray(lines[idx]))\n          lines[idx].push(equationRow)\n        else\n          lines[idx] = [equationRow]\n      })\n    }\n\n    const startLine = props.startLine\n    const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr)\n    lines.forEach((line, idx) => {\n      const highlighted = highlights.includes(idx + startLine)\n      line.forEach((node) => {\n        node.classList.toggle(CLASS_VCLICK_TARGET, true)\n        node.classList.toggle('highlighted', highlighted)\n        node.classList.toggle('dishonored', !highlighted)\n      })\n    })\n  })\n})\n</script>\n\n<template>\n  <div ref=\"el\" class=\"slidev-katex-wrapper\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/LightOrDark.vue",
    "content": "<script setup lang=\"ts\">\nimport { isDark } from '../logic/dark'\n</script>\n\n<template>\n  <div>\n    <slot v-if=\"isDark\" name=\"dark\" />\n    <slot v-else name=\"light\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/Link.vue",
    "content": "<!--\nCreate a link in the presentation\n\nUsage:\n\n<Link :to=\"5\" >Go to slide 5</Link>\n\n<Link :to=\"5\" title=\"Go to slide 5\" />\n-->\n<script setup lang=\"ts\">\nimport { useNav } from '../composables/useNav'\n\ndefineProps<{\n  to: number | string\n  title?: string\n}>()\n\nconst { isPrintMode } = useNav()\n</script>\n\n<template>\n  <RouterLink v-if=\"!isPrintMode && title\" :to=\"String(to)\" @click=\"$event.target.blur()\" v-html=\"title\" />\n  <RouterLink v-else-if=\"!isPrintMode && !title\" :to=\"String(to)\" @click=\"$event.target.blur()\">\n    <slot />\n  </RouterLink>\n  <a v-else-if=\"isPrintMode && title\" :href=\"`#${to}`\" v-html=\"title\" />\n  <a v-else :href=\"`#${to}`\"><slot /></a>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/Mermaid.vue",
    "content": "<!--\nMermaid\n(auto transformed, you don't need to use this component directly)\n\nUsage:\n\n```mermaid\npie\n\"Dogs\" : 386\n\"Cats\" : 85\n\"Rats\" : 15\n```\n-->\n\n<script setup lang=\"ts\">\nimport { getCurrentInstance, ref, watch, watchEffect } from 'vue'\nimport ShadowRoot from '../internals/ShadowRoot.vue'\nimport { isDark } from '../logic/dark'\nimport { renderMermaid } from '../modules/mermaid'\n\nconst props = defineProps<{\n  codeLz: string\n  scale?: number\n  theme?: string\n}>()\n\nconst vm = getCurrentInstance()\nconst el = ref<ShadowRoot>()\nconst error = ref<string | null>(null)\nconst html = ref('')\n\nwatchEffect(async (onCleanup) => {\n  let disposed = false\n  onCleanup(() => {\n    disposed = true\n  })\n  error.value = null\n  try {\n    const svg = await renderMermaid(\n      props.codeLz || '',\n      {\n        theme: props.theme || (isDark.value ? 'dark' : undefined),\n        ...vm!.attrs,\n      },\n    )\n    if (!disposed)\n      html.value = svg\n  }\n  catch (e) {\n    error.value = `${e}`\n    console.warn(e)\n  }\n})\n\nconst actualHeight = ref<number>()\n\nwatch(html, () => {\n  actualHeight.value = undefined\n})\n\nwatchEffect(() => {\n  const svgEl = el.value?.children?.[0] as SVGElement | undefined\n  if (svgEl && svgEl.hasAttribute('viewBox') && actualHeight.value == null) {\n    const v = Number.parseFloat(svgEl.getAttribute('viewBox')?.split(' ')[3] || '')\n    actualHeight.value = Number.isNaN(v) ? undefined : v\n  }\n}, { flush: 'post' })\n\nwatchEffect(() => {\n  const svgEl = el.value?.children?.[0] as SVGElement | undefined\n  if (svgEl != null && props.scale != null && actualHeight.value != null) {\n    svgEl.setAttribute('height', `${actualHeight.value * props.scale}`)\n    svgEl.removeAttribute('width')\n    svgEl.removeAttribute('style')\n  }\n}, { flush: 'post' })\n</script>\n\n<template>\n  <pre v-if=\"error\" border=\"1 red rounded\" class=\"pa-3 text-wrap\">{{ error }}</pre>\n  <ShadowRoot v-else class=\"mermaid\" :inner-html=\"html\" @shadow=\"el = $event\" />\n</template>\n"
  },
  {
    "path": "packages/client/builtin/Monaco.vue",
    "content": "<!--\nMonaco Editor\n(auto transformed, you don't need to use this component directly)\n\nUsage:\n\n```ts {monaco}\nconst your_code = 'here'\n```\n\nLearn more: https://sli.dev/guide/syntax.html#monaco-editor\n-->\n\n<script setup lang=\"ts\">\nimport type { RawAtValue } from '@slidev/types'\nimport type * as monaco from 'monaco-editor'\nimport { debounce } from '@antfu/utils'\nimport { whenever } from '@vueuse/core'\nimport lz from 'lz-string'\nimport { computed, defineAsyncComponent, nextTick, onMounted, ref } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useSlideContext } from '../context'\nimport { makeId } from '../logic/utils'\n\nconst props = withDefaults(\n  defineProps<{\n    codeLz?: string\n    diffLz?: string\n    lang?: string\n    readonly?: boolean\n    lineNumbers?: 'on' | 'off' | 'relative' | 'interval'\n    height?: number | string // Posible values: 'initial', 'auto', '100%', '200px', etc.\n    editorOptions?: monaco.editor.IEditorOptions\n    ata?: boolean\n    runnable?: boolean\n    writable?: string\n    autorun?: boolean | 'once'\n    showOutputAt?: RawAtValue\n    outputHeight?: string\n    highlightOutput?: boolean\n    runnerOptions?: Record<string, unknown>\n  }>(),\n  {\n    codeLz: '',\n    lang: 'typescript',\n    readonly: false,\n    lineNumbers: 'off',\n    height: 'initial',\n    ata: true,\n    runnable: false,\n    autorun: true,\n    highlightOutput: true,\n  },\n)\n\nconst CodeRunner = defineAsyncComponent(() => import('../internals/CodeRunner.vue').then(r => r.default))\n\nconst code = ref(lz.decompressFromBase64(props.codeLz).trimEnd())\nconst diff = props.diffLz && ref(lz.decompressFromBase64(props.diffLz).trimEnd())\nconst isWritable = computed(() => props.writable && !props.readonly && __DEV__)\n\nconst langMap: Record<string, string> = {\n  ts: 'typescript',\n  js: 'javascript',\n}\nconst lang = langMap[props.lang] ?? props.lang\nconst extMap: Record<string, string> = {\n  typescript: 'mts',\n  javascript: 'mjs',\n  ts: 'mts',\n  js: 'mjs',\n}\nconst ext = extMap[props.lang] ?? props.lang\n\nconst outer = ref<HTMLDivElement>()\nconst container = ref<HTMLDivElement>()\n\nconst contentHeight = ref(0)\nconst initialHeight = ref<number>()\nconst height = computed(() => {\n  if (props.height === 'auto')\n    return `${contentHeight.value}px`\n  if (props.height === 'initial')\n    return `${initialHeight.value}px`\n  return props.height\n})\n\nconst loadTypes = ref<() => void>()\nconst { $page: thisSlideNo, $renderContext: renderContext } = useSlideContext()\nconst { currentSlideNo } = useNav()\nconst stopWatchTypesLoading = whenever(\n  () => Math.abs(thisSlideNo.value - currentSlideNo.value) <= 1 && loadTypes.value,\n  (loadTypes) => {\n    if (['slide', 'presenter'].includes(renderContext.value))\n      loadTypes()\n    else\n      setTimeout(loadTypes, 5000)\n  },\n)\n\nonMounted(async () => {\n  // Lazy load monaco, so it will be bundled in async chunk\n  const { default: setup } = await import('../setup/monaco')\n  const { ata, monaco, editorOptions } = await setup()\n  const model = monaco.editor.createModel(code.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))\n  model.onDidChangeContent(() => code.value = model.getValue())\n  const commonOptions = {\n    automaticLayout: true,\n    readOnly: props.readonly,\n    lineNumbers: props.lineNumbers,\n    minimap: { enabled: false },\n    overviewRulerBorder: false,\n    overviewRulerLanes: 0,\n    padding: { top: 10, bottom: 10 },\n    lineNumbersMinChars: 3,\n    bracketPairColorization: { enabled: false },\n    tabSize: 2,\n    fontSize: 11.5,\n    fontFamily: 'var(--slidev-code-font-family)',\n    scrollBeyondLastLine: false,\n    useInlineViewWhenSpaceIsLimited: false,\n    ...editorOptions,\n    ...props.editorOptions,\n  } satisfies monaco.editor.IStandaloneEditorConstructionOptions & monaco.editor.IDiffEditorConstructionOptions\n\n  let editableEditor: monaco.editor.IStandaloneCodeEditor\n  if (diff) {\n    const diffModel = monaco.editor.createModel(diff.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))\n    diffModel.onDidChangeContent(() => code.value = model.getValue())\n    const editor = monaco.editor.createDiffEditor(container.value!, {\n      renderOverviewRuler: false,\n      ...commonOptions,\n    })\n    editor.setModel({\n      original: model,\n      modified: diffModel,\n    })\n    const originalEditor = editor.getOriginalEditor()\n    const modifiedEditor = editor.getModifiedEditor()\n    const onContentSizeChange = () => {\n      const newHeight = Math.max(originalEditor.getContentHeight(), modifiedEditor.getContentHeight()) + 4\n      initialHeight.value ??= newHeight\n      contentHeight.value = newHeight\n      nextTick(() => editor.layout())\n    }\n    originalEditor.onDidContentSizeChange(onContentSizeChange)\n    modifiedEditor.onDidContentSizeChange(onContentSizeChange)\n    editableEditor = modifiedEditor\n  }\n  else {\n    const editor = monaco.editor.create(container.value!, {\n      model,\n      lineDecorationsWidth: 0,\n      ...commonOptions,\n    })\n    editor.onDidContentSizeChange((e) => {\n      const newHeight = e.contentHeight + 4\n      initialHeight.value ??= newHeight\n      contentHeight.value = newHeight\n      nextTick(() => editableEditor.layout())\n    })\n\n    editableEditor = editor\n  }\n  loadTypes.value = () => {\n    stopWatchTypesLoading()\n    import('#slidev/monaco-types')\n    if (props.ata) {\n      ata(editableEditor.getValue())\n      editableEditor.onDidChangeModelContent(debounce(1000, () => {\n        ata(editableEditor.getValue())\n      }))\n    }\n  }\n  const originalLayoutContentWidget = editableEditor.layoutContentWidget.bind(editableEditor)\n  editableEditor.layoutContentWidget = (widget: any) => {\n    originalLayoutContentWidget(widget)\n    const id = widget.getId()\n    if (id === 'editor.contrib.resizableContentHoverWidget') {\n      widget._resizableNode.domNode.style.transform = widget._positionPreference === 1\n        ? /* ABOVE */ `translateY(calc(100% * (var(--slidev-slide-scale) - 1)))`\n        : /* BELOW */ `` // reset\n    }\n  }\n\n  editableEditor.addAction({\n    id: 'slidev-save',\n    label: 'Save',\n    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],\n    run: () => {\n      if (!isWritable.value || !import.meta.hot?.send) {\n        console.warn('[Slidev] this monaco editor is not writable, save action is ignored.')\n        return\n      }\n      import.meta.hot.send('slidev:monaco-write', {\n        file: props.writable!,\n        content: editableEditor.getValue(),\n      })\n    },\n  })\n\n  nextTick(() => monaco.editor.remeasureFonts())\n  setTimeout(() => monaco.editor.remeasureFonts(), 1000)\n})\n</script>\n\n<template>\n  <div class=\"relative slidev-monaco-container\">\n    <div ref=\"outer\" class=\"relative slidev-monaco-container-inner\" :style=\"{ height }\">\n      <div ref=\"container\" class=\"absolute inset-0.5\" />\n    </div>\n    <CodeRunner\n      v-if=\"props.runnable\"\n      v-model=\"code\"\n      :lang=\"lang\"\n      :autorun=\"props.autorun\"\n      :show-output-at=\"props.showOutputAt\"\n      :height=\"props.outputHeight\"\n      :highlight-output=\"props.highlightOutput\"\n      :runner-options=\"props.runnerOptions\"\n    />\n  </div>\n</template>\n\n<style>\ndiv[widgetid='messageoverlay'] {\n  transform: translateY(calc(100% * (var(--slidev-slide-scale) - 1)));\n}\n\n.slidev-monaco-container {\n  position: relative;\n  margin: var(--slidev-code-margin);\n  line-height: var(--slidev-code-line-height);\n  border-radius: var(--slidev-code-radius);\n  background: var(--slidev-code-background);\n}\n\n.slidev-monaco-container-inner {\n  padding: var(--slidev-code-padding);\n}\n\n.slidev-monaco-container .monaco-editor {\n  --monaco-monospace-font: var(--slidev-code-font-family);\n  --vscode-editor-background: var(--slidev-code-background);\n  --vscode-editorGutter-background: var(--slidev-code-background);\n}\n\n/** Revert styles */\n.slidev-monaco-container .monaco-editor a {\n  border-bottom: none;\n}\n\n.slidev-monaco-container .monaco-editor a:hover {\n  border-bottom: none;\n}\n</style>\n"
  },
  {
    "path": "packages/client/builtin/PlantUml.vue",
    "content": "<!--\nPlantUML\n(auto transformed, you don't need to use this component directly)\n\nUsage:\n\n```plantuml\n@startuml\nAlice -> Bob : Hello!\n@enduml\n```\n-->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = withDefaults(defineProps<{\n  code: string\n  server: string\n  scale?: number\n  alt?: string\n}>(), {\n  alt: 'PlantUML diagram',\n})\n\nconst uri = computed(() => `${props.server}/svg/${props.code}`)\n</script>\n\n<template>\n  <img :src=\"uri\" :style=\"{ scale }\" :alt=\"alt\">\n</template>\n"
  },
  {
    "path": "packages/client/builtin/PoweredBySlidev.vue",
    "content": "<template>\n  <span inline-flex items-center>\n    <span text-main>Powered by</span>\n    <a href=\"https://sli.dev\" class=\"!border-none\">\n      <img alt=\"Slidev logo\" src=\"../assets/logo-title-horizontal.png\" h-1.5em>\n    </a>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/RenderWhen.vue",
    "content": "<script setup lang=\"ts\">\nimport type { RenderContext } from '@slidev/types'\nimport { useElementVisibility } from '@vueuse/core'\nimport { computed, ref } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useSlideContext } from '../context'\n\ntype Context = 'main' | 'visible' | 'print' | RenderContext\n\nconst props = defineProps<{\n  context: Context | Context[]\n}>()\nconst { context } = props\nconst target = ref(null)\nconst targetVisible = useElementVisibility(target)\n\n// When context has `visible`, we need to wrap the content with a div to track the visibility\nconst needsDomWrapper = Array.isArray(context) ? context.includes('visible') : context === 'visible'\n\nconst { $renderContext: currentContext } = useSlideContext()\nconst { isPrintMode } = useNav()\nconst shouldRender = computed(() => {\n  const anyContext = Array.isArray(context) ? context.some(contextMatch) : contextMatch(context)\n  const allConditions = Array.isArray(context) ? context.every(conditionsMatch) : conditionsMatch(context)\n  return anyContext && allConditions\n})\n\nfunction contextMatch(context: Context) {\n  if (context === currentContext?.value)\n    return true\n  if (context === 'main' && (currentContext?.value === 'slide' || currentContext?.value === 'presenter'))\n    return true\n  if (context === 'visible')\n    return true\n  if (context === 'print' && isPrintMode.value)\n    return true\n  return false\n}\n\nfunction conditionsMatch(context: Context) {\n  if (context === 'visible')\n    return targetVisible.value\n  return true\n}\n</script>\n\n<template>\n  <div v-if=\"needsDomWrapper\" ref=\"target\">\n    <slot v-if=\"shouldRender\" />\n    <slot v-else name=\"fallback\" />\n  </div>\n  <slot v-else-if=\"shouldRender\" />\n  <slot v-else name=\"fallback\" />\n</template>\n"
  },
  {
    "path": "packages/client/builtin/ShikiMagicMove.vue",
    "content": "<script setup lang=\"ts\">\nimport type { KeyedTokensInfo } from 'shiki-magic-move/types'\nimport type { PropType } from 'vue'\nimport { sleep } from '@antfu/utils'\nimport { useClipboard } from '@vueuse/core'\nimport lz from 'lz-string'\nimport { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'\nimport { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { CLICKS_MAX } from '../constants'\nimport { useSlideContext } from '../context'\nimport { configs } from '../env'\nimport TitleIcon from '../internals/TitleIcon.vue'\nimport { makeId, updateCodeHighlightRange } from '../logic/utils'\n\nconst props = defineProps({\n  at: {\n    type: [String, Number],\n    default: '+1',\n  },\n  stepsLz: {\n    type: String,\n    required: true,\n  },\n  stepRanges: {\n    type: Array as PropType<string[][]>,\n    required: true,\n  },\n  lines: {\n    type: Boolean,\n    default: configs.lineNumbers,\n  },\n  title: {\n    type: String,\n    default: '',\n  },\n  duration: {\n    type: Number,\n    default: configs.magicMoveDuration,\n  },\n})\n\nconst steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]\nconst { $clicksContext: clicks, $scale: scale, $zoom: zoom } = useSlideContext()\nconst { isPrintMode } = useNav()\nconst id = makeId()\n\nconst stepIndex = ref(0)\n// Used to skip the animation on the first tick.\nconst isFirstTick = ref(true)\nconst container = ref<HTMLElement>()\n\nconst showCopyButton = computed(() => {\n  if (!configs.codeCopy)\n    return false\n\n  const magicCopy = configs.magicMoveCopy\n  if (!magicCopy)\n    return false\n\n  if (magicCopy === true || magicCopy === 'always')\n    return true\n\n  if (magicCopy === 'final')\n    return stepIndex.value === steps.length - 1\n\n  return false\n})\nconst { copied, copy } = useClipboard()\n\nfunction copyCode() {\n  // Use the code property directly from KeyedTokensInfo\n  const currentStep = steps[stepIndex.value]\n  if (!currentStep || !currentStep.code)\n    return\n\n  copy(currentStep.code.trim())\n}\n\n// Normalized the ranges, to at least have one range\nconst ranges = computed(() => props.stepRanges.map(i => i.length ? i : ['all']))\n\nonUnmounted(() => {\n  clicks?.unregister(id)\n})\n\nonMounted(() => {\n  if (!clicks)\n    return\n\n  if (ranges.value.length !== steps.length)\n    throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')\n\n  const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)\n  const clickInfo = clicks.calculateSince(props.at, clickCounts - 1)\n  clicks.register(id, clickInfo)\n\n  let cancelTick: () => void = () => { }\n  watch(\n    () => clicks.current,\n    () => {\n      // Calculate the step and rangeStr based on the current click count\n      const clickCount = clickInfo ? clicks.current - clickInfo.start : CLICKS_MAX\n      let step = steps.length - 1\n      let currentClickSum = 0\n      let rangeStr = 'all'\n      for (let i = 0; i < ranges.value.length; i++) {\n        const current = ranges.value[i]\n        if (clickCount < currentClickSum + current.length - 1) {\n          step = i\n          rangeStr = current[clickCount - currentClickSum + 1]\n          break\n        }\n        currentClickSum += current.length || 1\n      }\n\n      // It seems ticks may not be executed in order. Cancel previous ones, because\n      // clicks.current is first 0 then immediately updated when refreshing the slide.\n      cancelTick()\n      let isCanceled = false\n      cancelTick = () => {\n        isCanceled = true\n      }\n\n      nextTick(async () => {\n        if (isCanceled) {\n          return\n        }\n        stepIndex.value = step\n        if (isFirstTick.value) {\n          nextTick(() => {\n            isFirstTick.value = false\n          })\n        }\n        await sleep(0)\n\n        const pre = container.value?.querySelector('.shiki') as HTMLElement\n        if (!pre)\n          return\n\n        const children = (Array.from(pre.children) as HTMLElement[])\n          .slice(1) // Remove the first anchor\n          .filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements\n\n        // Group to lines between `<br>`\n        const lines = children.reduce((acc, el) => {\n          if (el.tagName === 'BR')\n            acc.push([])\n          else\n            acc[acc.length - 1].push(el)\n          return acc\n        }, [[]] as HTMLElement[][])\n\n        // Update highlight range\n        updateCodeHighlightRange(\n          rangeStr,\n          lines.length,\n          1,\n          no => lines[no],\n        )\n      })\n    },\n    { immediate: true },\n  )\n})\n</script>\n\n<template>\n  <div ref=\"container\" class=\"slidev-code-wrapper slidev-code-magic-move relative group\">\n    <div v-if=\"title\" class=\"slidev-code-block-title\">\n      <TitleIcon :title=\"title\" />\n      <div class=\"leading-1em\">\n        {{ title.replace(/~([^~]+)~/g, '').trim() }}\n      </div>\n    </div>\n    <ShikiMagicMovePrecompiled\n      class=\"slidev-code relative shiki overflow-visible\"\n      :steps=\"steps\"\n      :step=\"stepIndex\"\n      :animate=\"!isPrintMode\"\n      :options=\"{\n        globalScale: scale * zoom,\n        // Use duration 0 to skip animation instead of using the animate prop,\n        // because moving from non-animated to animated causes issues with\n        // new elements. Unfortunately, this causes a flash.\n        duration: isFirstTick ? 0 : $props.duration,\n        stagger: 1,\n      }\"\n    />\n    <button\n      v-if=\"showCopyButton\"\n      class=\"slidev-code-copy absolute right-0 transition opacity-0 group-hover:opacity-20 hover:!opacity-100\"\n      :class=\"title ? 'top-10' : 'top-0'\"\n      :title=\"copied ? 'Copied' : 'Copy'\" @click=\"copyCode()\"\n    >\n      <ph-check-circle v-if=\"copied\" class=\"p-2 w-8 h-8\" />\n      <ph-clipboard v-else class=\"p-2 w-8 h-8\" />\n    </button>\n  </div>\n</template>\n\n<style>\n.slidev-code-magic-move .shiki-magic-move-enter-from,\n.slidev-code-magic-move .shiki-magic-move-leave-to {\n  opacity: 0;\n}\n</style>\n"
  },
  {
    "path": "packages/client/builtin/SlideCurrentNo.vue",
    "content": "<script setup lang=\"ts\">\nimport { useSlideContext } from '../context'\n\nconst { $page } = useSlideContext()\n</script>\n\n<template>\n  <span>{{ $page }}</span>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/SlidesTotal.vue",
    "content": "<script setup lang=\"ts\">\nimport { useSlideContext } from '../context'\n\nconst { $nav } = useSlideContext()\n</script>\n\n<template>\n  <span>{{ $nav.total }}</span>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/SlidevVideo.vue",
    "content": "<script setup lang=\"ts\">\nimport { and } from '@vueuse/math'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useSlideContext } from '../context'\nimport { resolvedClickMap } from '../modules/v-click'\n\nconst props = defineProps<{\n  autoplay?: boolean | 'once'\n  autoreset?: 'slide' | 'click'\n  poster?: string\n  printPoster?: string\n  timestamp?: string | number\n  printTimestamp?: string | number | 'last'\n  controls?: boolean\n}>()\n\nconst printPoster = computed(() => props.printPoster ?? props.poster)\nconst printTimestamp = computed(() => props.printTimestamp ?? props.timestamp ?? 0)\n\nconst { $slidev, $renderContext, $route } = useSlideContext()\nconst { isPrintMode } = useNav()\n\nconst noPlay = computed(() => isPrintMode.value || !['slide', 'presenter'].includes($renderContext.value))\n\nconst video = ref<HTMLMediaElement>()\nconst played = ref(false)\n\nonMounted(() => {\n  if (noPlay.value)\n    return\n\n  const timestamp = +(props.timestamp ?? 0)\n  video.value!.currentTime = timestamp\n\n  const matchRoute = computed(() => !!$route && $route.no === $slidev?.nav.currentSlideNo)\n  const matchClick = computed(() => !!video.value && (resolvedClickMap.get(video.value)?.isShown?.value ?? true))\n  const matchRouteAndClick = and(matchRoute, matchClick)\n\n  watch(matchRouteAndClick, () => {\n    if (matchRouteAndClick.value) {\n      if (props.autoplay === true || (props.autoplay === 'once' && !played.value))\n        video.value!.play()\n    }\n    else {\n      video.value!.pause()\n      if (props.autoreset === 'click' || (props.autoreset === 'slide' && !matchRoute.value))\n        video.value!.currentTime = timestamp\n    }\n  }, { immediate: true })\n})\n\nfunction onLoadedMetadata(ev: Event) {\n  // The video may be loaded before component mounted\n  const element = ev.target as HTMLMediaElement\n  if (noPlay.value && (!printPoster.value || props.printTimestamp)) {\n    element.currentTime = printTimestamp.value === 'last'\n      ? element.duration\n      : +printTimestamp.value\n  }\n}\n</script>\n\n<template>\n  <video\n    ref=\"video\"\n    :poster=\"noPlay ? printPoster : props.poster\"\n    :controls=\"!noPlay && props.controls\"\n    @play=\"played = true\"\n    @loadedmetadata=\"onLoadedMetadata\"\n  >\n    <slot />\n  </video>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/Toc.vue",
    "content": "<!--\nTable Of content\n\n`mode` can be either 'all', 'onlyCurrentTree' or 'onlySiblings'\n\nUsage:\n\n<Toc columns='2' maxDepth='3' mode='onlySiblings'/>\n-->\n<script setup lang='ts'>\nimport type { TocItem } from '@slidev/types'\nimport { computed } from 'vue'\nimport { useSlideContext } from '../context'\nimport TocList from './TocList.vue'\n\nconst props = withDefaults(\n  defineProps<{\n    columns?: string | number\n    listClass?: string | string[]\n    start?: string | number\n    listStyle?: string | string[]\n    maxDepth?: string | number\n    minDepth?: string | number\n    mode?: 'all' | 'onlyCurrentTree' | 'onlySiblings'\n  }>(),\n  {\n    columns: 1,\n    listClass: '',\n    start: 1,\n    listStyle: '',\n    maxDepth: Number.POSITIVE_INFINITY,\n    minDepth: 1,\n    mode: 'all',\n  },\n)\n\nconst { $slidev } = useSlideContext()\n\nfunction filterTreeDepth(tree: TocItem[], level = 1): TocItem[] {\n  if (level > Number(props.maxDepth)) {\n    return []\n  }\n  else if (level < Number(props.minDepth)) {\n    const activeItem = tree.find((item: TocItem) => item.active || item.activeParent)\n    return activeItem ? filterTreeDepth(activeItem.children, level + 1) : []\n  }\n  return tree\n    .map((item: TocItem) => ({\n      ...item,\n      children: filterTreeDepth(item.children, level + 1),\n    }))\n}\n\nfunction filterOnlyCurrentTree(tree: TocItem[]): TocItem[] {\n  return tree\n    .filter(\n      (item: TocItem) => item.active || item.activeParent || item.hasActiveParent,\n    )\n    .map((item: TocItem) => ({\n      ...item,\n      children: filterOnlyCurrentTree(item.children),\n    }))\n}\n\nfunction filterOnlySiblings(tree: TocItem[]): TocItem[] {\n  const treehasActiveItem = tree.some(\n    (item: TocItem) => item.active || item.activeParent || item.hasActiveParent,\n  )\n  return tree\n    .filter(() => treehasActiveItem)\n    .map((item: TocItem) => ({\n      ...item,\n      children: filterOnlySiblings(item.children),\n    }))\n}\n\nconst toc = computed(() => {\n  const tree = $slidev?.nav.tocTree\n  if (!tree)\n    return []\n  let tocTree = filterTreeDepth(tree)\n  if (props.mode === 'onlyCurrentTree')\n    tocTree = filterOnlyCurrentTree(tocTree)\n  else if (props.mode === 'onlySiblings')\n    tocTree = filterOnlySiblings(tocTree)\n  return tocTree\n})\n</script>\n\n<template>\n  <div class=\"slidev-toc\" :style=\"`column-count:${columns}`\">\n    <TocList\n      :level=\"1\"\n      :start=\"start\"\n      :list-style=\"listStyle\"\n      :list=\"toc\"\n      :list-class=\"listClass\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/TocList.vue",
    "content": "<!--\nTOC list\n(used by Toc component, you don't need to use this component directly)\n\nUsage:\n\n<TocList :list=\"list\"/>\n-->\n<script setup lang=\"ts\">\nimport type { TocItem } from '@slidev/types'\nimport TitleRenderer from '#slidev/title-renderer'\nimport { toArray } from '@antfu/utils'\nimport { computed } from 'vue'\nimport { useNav } from '../composables/useNav'\n\nconst props = withDefaults(\n  defineProps<{\n    level?: number\n    start?: string | number\n    listStyle?: string | string[]\n    list: TocItem[]\n    listClass?: string | string[]\n  }>(),\n  { level: 1 },\n)\n\nconst { isPresenter } = useNav()\n\nconst classes = computed(() => {\n  return [\n    ...toArray(props.listClass || []),\n    'slidev-toc-list',\n    `slidev-toc-list-level-${props.level}`,\n  ]\n})\n\nconst styles = computed(() => {\n  return [\n    ...toArray(props.listStyle || []),\n  ]\n})\n</script>\n\n<template>\n  <ol\n    v-if=\"list && list.length > 0\"\n    :class=\"classes\"\n    :start=\"level === 1 ? start : undefined\"\n    :style=\"styles.length >= level ? `list-style:${styles[level - 1]}` : undefined\"\n  >\n    <li\n      v-for=\"item of list\"\n      :key=\"item.path\" class=\"slidev-toc-item\"\n      :class=\"[{ 'slidev-toc-item-active': item.active }, { 'slidev-toc-item-parent-active': item.activeParent }]\"\n    >\n      <Link :to=\"isPresenter ? `/presenter${item.path}` : item.path\">\n        <TitleRenderer :no=\"item.no\" />\n      </Link>\n      <TocList\n        v-if=\"item.children.length > 0\"\n        :level=\"level + 1\"\n        :list-style=\"listStyle\"\n        :list=\"item.children\"\n        :list-class=\"listClass\"\n      />\n    </li>\n  </ol>\n</template>\n\n<style>\n.slidev-layout .slidev-toc-item p {\n  margin: 0;\n}\n.slidev-layout .slidev-toc-item div,\n.slidev-layout .slidev-toc-item div p {\n  display: initial;\n}\n</style>\n"
  },
  {
    "path": "packages/client/builtin/Transform.vue",
    "content": "<!--\nApply scaling or transforming to elements.\n\nUsage:\n\n<Transform :scale=\"0.5\">\n  <YourElements />\n</Transform>\n-->\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  scale?: number | string\n  origin?: string\n}>()\n\nconst style = computed(() => {\n  const transforms = []\n  if (props.scale != null)\n    transforms.push(`scale(${props.scale || 1})`)\n\n  return {\n    'transform': transforms.join(' '),\n    'transform-origin': props.origin || 'top left',\n  }\n})\n</script>\n\n<template>\n  <div :style=\"style\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/Tweet.vue",
    "content": "<!--\nA simple wrapper for embedded Tweet\n\nUsage:\n\n<Tweet id=\"20\" />\n-->\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { isDark } from '../logic/dark'\n\nconst props = defineProps<{\n  id: string | number\n  scale?: string | number\n  conversation?: string\n  cards?: 'hidden' | 'visible'\n}>()\n\nconst tweet = ref<HTMLElement | null>()\n\nconst loaded = ref(false)\nconst tweetNotFound = ref(false)\n\nasync function create(retries = 10) {\n  // @ts-expect-error global\n  if (!window.twttr?.widgets?.createTweet) {\n    if (retries <= 0)\n      return console.error('Failed to load Twitter widget after 10 retries.')\n    setTimeout(() => create(retries - 1), 1000)\n    return\n  }\n  // @ts-expect-error global\n  const element = await window.twttr.widgets.createTweet(\n    props.id.toString(),\n    tweet.value,\n    {\n      theme: isDark.value ? 'dark' : 'light',\n      conversation: props.conversation || 'none',\n      cards: props.cards,\n    },\n  )\n  loaded.value = true\n  if (element === undefined)\n    tweetNotFound.value = true\n}\n\nonMounted(() => {\n  create()\n})\n</script>\n\n<template>\n  <Transform :scale=\"scale || 1\">\n    <div ref=\"tweet\" class=\"tweet slidev-tweet\">\n      <div v-if=\"!loaded || tweetNotFound\" class=\"w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50\">\n        <div class=\"m-auto animate-pulse text-4xl\">\n          <div class=\"i-carbon:logo-twitter\" />\n          <span v-if=\"tweetNotFound\">Could not load tweet with id=\"{{ props.id }}\"</span>\n        </div>\n      </div>\n    </div>\n  </Transform>\n</template>\n\n<style>\n.slidev-tweet iframe {\n  border-radius: 12px;\n  overflow: hidden;\n}\n</style>\n"
  },
  {
    "path": "packages/client/builtin/VAfter.ts",
    "content": "/**\n * <v-after/> click animations component\n *\n * Learn more: https://sli.dev/guide/animations.html#click-animation\n */\n\nimport type { Directive, VNode } from 'vue'\nimport { toArray } from '@antfu/utils'\nimport { defineComponent, h, resolveDirective, withDirectives } from 'vue'\n\nexport default defineComponent({\n  render() {\n    const after = resolveDirective('after')!\n\n    function applyDirective(node: VNode, directive: Directive) {\n      return withDirectives(node, [[directive]])\n    }\n\n    let defaults = this.$slots.default?.()\n\n    if (!defaults)\n      return\n\n    defaults = toArray(defaults)\n\n    return defaults.map(i => applyDirective(h(i), after))\n  },\n})\n"
  },
  {
    "path": "packages/client/builtin/VClick.ts",
    "content": "/**\n * <v-click/> click animations component\n *\n * Learn more: https://sli.dev/guide/animations.html#click-animation\n */\n\nimport type { PropType, VNode } from 'vue'\nimport { defineComponent, h, Text } from 'vue'\nimport { CLICKS_MAX } from '../constants'\nimport VClicks from './VClicks'\n\nexport default defineComponent({\n  props: {\n    at: {\n      type: [Number, String],\n      default: '+1',\n    },\n    hide: {\n      type: Boolean,\n      default: false,\n    },\n    fade: {\n      type: Boolean,\n      default: false,\n    },\n    wrapText: {\n      type: Function as PropType<(text: VNode) => VNode>,\n      default: (text: VNode) => h('span', text),\n    },\n  },\n  render() {\n    return h(\n      VClicks,\n      {\n        every: CLICKS_MAX,\n        at: this.at,\n        hide: this.hide,\n        fade: this.fade,\n        handleSpecialElements: false,\n      },\n      {\n        default: () =>\n          this.$slots.default?.().map(v =>\n            v.type === Text\n              ? this.wrapText(v)\n              : v,\n          ),\n      },\n    )\n  },\n})\n"
  },
  {
    "path": "packages/client/builtin/VClickGap.vue",
    "content": "<script setup lang=\"ts\">\nimport { Fragment, onMounted, onUnmounted } from 'vue'\nimport { useSlideContext } from '../context'\nimport { makeId } from '../logic/utils'\n\nconst props = defineProps({\n  size: {\n    type: [String, Number],\n    default: 1,\n  },\n})\n\nconst { $clicksContext: clicks } = useSlideContext()\nconst id = makeId()\n\nlet delta = +props.size\nif (Number.isNaN(delta)) {\n  console.warn(`[slidev] Invalid size for VClickGap: ${props.size}`)\n  delta = 1\n}\n\nonMounted(() => {\n  const max = clicks.currentOffset + delta - 1\n  clicks.register(id, { max, delta })\n})\n\nonUnmounted(() => {\n  clicks.unregister(id)\n})\n</script>\n\n<template>\n  <Fragment />\n</template>\n"
  },
  {
    "path": "packages/client/builtin/VClicks.ts",
    "content": "/**\n * <v-clicks/> click animations component\n *\n * Learn more: https://sli.dev/guide/animations.html#click-animation\n */\n\nimport type { VNode, VNodeArrayChildren } from 'vue'\nimport { toArray } from '@antfu/utils'\nimport { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'\nimport { normalizeSingleAtValue } from '../composables/useClicks'\nimport VClickGap from './VClickGap.vue'\n\nconst listTags = ['ul', 'ol']\n\nexport default defineComponent({\n  props: {\n    depth: {\n      type: [Number, String],\n      default: 1,\n    },\n    every: {\n      type: [Number, String],\n      default: 1,\n    },\n    at: {\n      type: [Number, String],\n      default: '+1',\n    },\n    hide: {\n      type: Boolean,\n      default: false,\n    },\n    fade: {\n      type: Boolean,\n      default: false,\n    },\n    handleSpecialElements: {\n      type: Boolean,\n      default: true,\n    },\n  },\n  render() {\n    const every = +this.every\n    const at = normalizeSingleAtValue(this.at)\n    const isRelative = typeof at === 'string'\n\n    let elements = this.$slots.default?.()\n\n    if (at == null || !elements) {\n      return elements\n    }\n\n    const click = resolveDirective('click')!\n\n    const applyDirective = (node: VNode, value: number | string) => {\n      return withDirectives(node, [[\n        click,\n        value,\n        '',\n        {\n          hide: this.hide,\n          fade: this.fade,\n        },\n      ]])\n    }\n\n    const openAllTopLevelSlots = <T extends VNodeArrayChildren | VNode[]>(children: T): T => {\n      return children.flatMap((i) => {\n        if (isVNode(i) && typeof i.type === 'symbol' && Array.isArray(i.children))\n          return openAllTopLevelSlots(i.children)\n        else\n          return [i]\n      }) as T\n    }\n\n    elements = openAllTopLevelSlots(toArray(elements))\n\n    const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {\n      const vNodes = openAllTopLevelSlots(children).map((i) => {\n        if (!isVNode(i))\n          return i\n        if (listTags.includes(i.type as string) && Array.isArray(i.children)) {\n          // eslint-disable-next-line ts/no-use-before-define\n          const vNodes = mapChildren(i.children, depth + 1)\n          return h(i, {}, vNodes)\n        }\n        return h(i)\n      })\n      return vNodes\n    }\n\n    let globalIdx = 1\n    let execIdx = 0\n    const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {\n      const vNodes = openAllTopLevelSlots(children).map((i) => {\n        if (!isVNode(i) || i.type === Comment)\n          return i\n\n        const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1\n\n        let vNode\n        if (depth < +this.depth && Array.isArray(i.children))\n          vNode = h(i, {}, mapSubList(i.children, depth))\n        else\n          vNode = h(i)\n\n        const delta = thisShowIdx - execIdx\n        execIdx = thisShowIdx\n\n        return applyDirective(\n          vNode,\n          isRelative\n            ? delta >= 0 ? `+${delta}` : `${delta}`\n            : thisShowIdx,\n        )\n      })\n      return vNodes\n    }\n\n    const lastGap = () => createVNode(VClickGap, {\n      size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,\n    })\n\n    if (this.handleSpecialElements) {\n      // handle ul, ol list\n      if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))\n        return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])\n\n      if (elements.length === 1 && elements[0].type as string === 'table') {\n        const tableNode = elements[0]\n        if (Array.isArray(tableNode.children)) {\n          return h(tableNode, {}, tableNode.children.map((i) => {\n            if (!isVNode(i))\n              return i\n            else if (i.type === 'tbody' && Array.isArray(i.children))\n              return h(i, {}, [...mapChildren(i.children), lastGap()])\n            else\n              return h(i)\n          }))\n        }\n      }\n    }\n\n    return [...mapChildren(elements), lastGap()]\n  },\n})\n"
  },
  {
    "path": "packages/client/builtin/VDrag.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DragElementMarkdownSource } from '../composables/useDragElements'\nimport { onMounted, onUnmounted } from 'vue'\nimport { useDragElement } from '../composables/useDragElements'\n\nconst props = defineProps<{\n  pos?: string\n  markdownSource?: DragElementMarkdownSource\n}>()\n\nconst { dragId, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)\n\nonMounted(mounted)\nonUnmounted(unmounted)\n</script>\n\n<template>\n  <div\n    ref=\"container\"\n    :data-drag-id=\"dragId\"\n    :style=\"containerStyle\"\n    class=\"p-1\"\n    @dblclick=\"startDragging\"\n  >\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/builtin/VDragArrow.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DragElementMarkdownSource } from '../composables/useDragElements'\nimport { computed, onMounted, onUnmounted } from 'vue'\nimport { useDragElement } from '../composables/useDragElements'\nimport Arrow from './Arrow.vue'\n\nconst props = defineProps<{\n  pos?: string\n  markdownSource?: DragElementMarkdownSource\n  width?: number | string\n  color?: string\n  twoWay?: boolean\n}>()\n\nconst { dragId, mounted, unmounted, startDragging, stopDragging, x0, y0, width, height } = useDragElement(null, props.pos ?? '100,100,300,300', props.markdownSource, true)\n\nonMounted(mounted)\nonUnmounted(unmounted)\n\nconst x1 = computed(() => x0.value - width.value / 2)\nconst y1 = computed(() => y0.value - height.value / 2)\nconst x2 = computed(() => x0.value + width.value / 2)\nconst y2 = computed(() => y0.value + height.value / 2)\n</script>\n\n<template>\n  <Arrow\n    :x1 :y1 :x2 :y2\n    :data-drag-id=\"dragId\"\n    :width=\"props.width\"\n    :color=\"props.color\"\n    :two-way=\"props.twoWay\"\n    @dblclick=\"startDragging\"\n    @click-outside=\"stopDragging\"\n  />\n</template>\n"
  },
  {
    "path": "packages/client/builtin/VSwitch.ts",
    "content": "import type { PropType, Ref, Slot, TransitionGroupProps, VNode } from 'vue'\nimport { recomputeAllPoppers } from 'floating-vue'\nimport { defineComponent, h, onMounted, onUnmounted, ref, TransitionGroup, watchEffect } from 'vue'\nimport { CLASS_VCLICK_CURRENT, CLASS_VCLICK_DISPLAY_NONE, CLASS_VCLICK_PRIOR, CLASS_VCLICK_TARGET, CLICKS_MAX } from '../constants'\nimport { useSlideContext } from '../context'\nimport { resolveTransition } from '../logic/transition'\nimport { makeId } from '../logic/utils'\nimport { hmrSkipTransition } from '../state'\n\nexport default defineComponent({\n  props: {\n    at: {\n      type: [Number, String],\n      default: '+1',\n    },\n    /**\n     * unmount or hide the content when it's not visible\n     */\n    unmount: {\n      type: Boolean,\n      default: false,\n    },\n    transition: {\n      type: [Object, String, Boolean] as PropType<TransitionGroupProps | string | false>,\n      default: false,\n    },\n    tag: {\n      type: String,\n      default: 'div',\n    },\n    childTag: {\n      type: String,\n      default: 'div',\n    },\n  },\n  setup({ at, unmount, transition, tag, childTag }, { slots }) {\n    const slotEntries = Object.entries(slots).sort((a, b) => -a[0].split('-')[0] + +b[0].split('-')[0])\n    const contents: [start: number, end: number, slot: Slot<any> | undefined, elRef: Ref<HTMLElement | undefined>][] = []\n\n    let lastStart: number | undefined\n    for (const [range, slot] of slotEntries) {\n      const elRef = ref<HTMLElement>()\n      if (Number.isFinite(+range)) {\n        contents.push([+range, lastStart ?? +range + 1, slot, elRef])\n        lastStart = +range\n      }\n      else {\n        const [start, end] = range.split('-').map(Number)\n        if (!Number.isFinite(start) || !Number.isFinite(end))\n          throw new Error(`Invalid range for v-switch: ${range}`)\n        contents.push([start, end, slot, elRef])\n        lastStart = start\n      }\n    }\n\n    const size = Math.max(...contents.map(c => c[1])) - 1\n    const id = makeId()\n    const offset = ref(0)\n\n    const { $clicksContext: clicks, $nav: nav } = useSlideContext()\n\n    onMounted(() => {\n      const clicksInfo = clicks.calculateSince(at, size)\n      if (!clicksInfo) {\n        offset.value = CLICKS_MAX\n        return\n      }\n      clicks.register(id, clicksInfo)\n      watchEffect(() => {\n        offset.value = clicksInfo.currentOffset.value + 1\n      })\n    })\n\n    onUnmounted(() => {\n      clicks.unregister(id)\n    })\n\n    function onAfterLeave() {\n      // Refer to SlidesShow.vue\n      hmrSkipTransition.value = true\n      recomputeAllPoppers()\n    }\n    const transitionProps = transition && {\n      ...resolveTransition(transition, nav.value.navDirection < 0),\n      tag,\n      onAfterLeave,\n    }\n\n    return () => {\n      const children: VNode[] = []\n      for (let i = contents.length - 1; i >= 0; i--) {\n        const [start, end, slot, ref] = contents[i]\n        const visible = start <= offset.value && offset.value < end\n        if (unmount && !visible)\n          continue\n        children.push(h(childTag, {\n          'key': i,\n          ref,\n          'class': [\n            CLASS_VCLICK_TARGET,\n            offset.value === start && CLASS_VCLICK_CURRENT,\n            offset.value >= end && CLASS_VCLICK_PRIOR,\n            !visible && CLASS_VCLICK_DISPLAY_NONE,\n          ].filter(Boolean),\n          'data-slidev-clicks-start': start,\n          'data-slidev-clicks-end': end,\n        }, slot?.()))\n      }\n      return transitionProps\n        ? h(TransitionGroup, hmrSkipTransition.value ? {} : transitionProps, () => children)\n        : h(tag, children)\n    }\n  },\n})\n"
  },
  {
    "path": "packages/client/builtin/Youtube.vue",
    "content": "<!--\nA simple wrapper for embeded YouTube videos\n\nUsage:\n\n<Youtube id=\"luoMHjh-XcQ\" />\n-->\n\n<script setup lang=\"ts\">\ndefineProps<{\n  id: string\n  width?: number\n  height?: number\n}>()\n</script>\n\n<template>\n  <iframe\n    class=\"youtube\"\n    :width=\"width\"\n    :height=\"height\"\n    :src=\"`https://www.youtube.com/embed/${id}`\"\n    title=\"YouTube\"\n    frameborder=\"0\"\n    allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n    allowfullscreen\n  />\n</template>\n"
  },
  {
    "path": "packages/client/composables/useClicks.ts",
    "content": "import type { ClicksContext, NormalizedRangeClickValue, NormalizedSingleClickValue, RawAtValue, RawSingleAtValue, SlideRoute } from '@slidev/types'\nimport type { MaybeRefOrGetter, Ref } from 'vue'\nimport { clamp, sum } from '@antfu/utils'\nimport { computed, isReadonly, onMounted, onUnmounted, ref, shallowReactive, toValue, watch } from 'vue'\n\nexport function normalizeSingleAtValue(at: RawSingleAtValue): NormalizedSingleClickValue {\n  if (at === false || at === 'false')\n    return null\n  if (at == null || at === true || at === 'true')\n    return '+1'\n  if (typeof at === 'string' && '+-'.includes(at[0]))\n    return at\n  const v = +at\n  if (Number.isNaN(v)) {\n    console.error(`Invalid \"at\" prop value: ${at}`)\n    return null\n  }\n  if (v <= 0) {\n    console.warn(`[Slidev] \"at\" prop value must be greater than 0, but got ${at}, has been set to 1`)\n    return 1\n  }\n  return v\n}\n\nexport function normalizeRangeAtValue(at: RawAtValue): NormalizedRangeClickValue {\n  if (Array.isArray(at))\n    return [normalizeSingleAtValue(at[0])!, normalizeSingleAtValue(at[1])!]\n  return null\n}\n\nexport function createClicksContextBase(\n  current: Ref<number>,\n  clicksStart = 0,\n  clicksTotalOverrides?: number,\n): ClicksContext {\n  const isMounted = ref(false)\n  let relativeSizeMap: ClicksContext['relativeSizeMap'] = new Map()\n  let maxMap: ClicksContext['maxMap'] = new Map()\n  const context: ClicksContext = {\n    get current() {\n      return clamp(+current.value, clicksStart, context.total)\n    },\n    set current(value) {\n      current.value = isMounted.value\n        ? clamp(value, clicksStart, context.total)\n        : value /* context.total is not available yet */\n    },\n    clicksStart,\n    get relativeSizeMap() {\n      if (__DEV__ && isMounted.value)\n        console.warn('[slidev] ClicksContext: Unexpected access to relativeSizeMap after mounted')\n      return relativeSizeMap\n    },\n    get maxMap() {\n      return maxMap\n    },\n    get isMounted() {\n      return isMounted.value\n    },\n    setup() {\n      onMounted(() => {\n        isMounted.value = true\n        // Convert maxMap to reactive\n        maxMap = shallowReactive(maxMap)\n        // Make sure the query is not greater than the total\n        if (!isReadonly(current))\n          context.current = current.value\n      })\n      onUnmounted(() => {\n        isMounted.value = false\n        relativeSizeMap = new Map()\n        maxMap = new Map()\n      })\n    },\n    calculateSince(rawAt, size = 1) {\n      const at = normalizeSingleAtValue(rawAt)\n      if (at == null)\n        return null\n      let start: number, max: number, delta: number\n      if (typeof at === 'string') {\n        const offset = context.currentOffset\n        const value = +at\n        start = offset + value\n        max = offset + value + size - 1\n        delta = value + size - 1\n      }\n      else {\n        start = at\n        max = at + size - 1\n        delta = 0\n      }\n      return {\n        start,\n        end: +Number.POSITIVE_INFINITY,\n        max,\n        delta,\n        currentOffset: computed(() => context.current - start),\n        isCurrent: computed(() => context.current === start),\n        isActive: computed(() => context.current >= start),\n      }\n    },\n    calculateRange(rawAt) {\n      const at = normalizeRangeAtValue(rawAt)\n      if (at == null)\n        return null\n      const [a, b] = at\n      let start: number, end: number, delta: number\n      if (typeof a === 'string') {\n        const offset = context.currentOffset\n        start = offset + +a\n        delta = +a\n      }\n      else {\n        start = a\n        delta = 0\n      }\n      if (typeof b === 'string') {\n        end = start + +b\n        delta += +b\n      }\n      else {\n        end = b\n      }\n      return {\n        start,\n        end,\n        max: end,\n        delta,\n        currentOffset: computed(() => context.current - start),\n        isCurrent: computed(() => context.current === start),\n        isActive: computed(() => start <= context.current && context.current < end),\n      }\n    },\n    calculate(at) {\n      if (Array.isArray(at))\n        return context.calculateRange(at)\n      return context.calculateSince(at)\n    },\n    register(el, info) {\n      if (!info)\n        return\n      if (__DEV__ && isMounted.value)\n        console.warn('[slidev] ClicksContext: Unexpected register after mounted')\n      const { delta, max } = info\n      relativeSizeMap.set(el, delta)\n      maxMap.set(el, max)\n    },\n    unregister(el) {\n      relativeSizeMap.delete(el)\n      maxMap.delete(el)\n    },\n    get currentOffset() {\n      return sum(...relativeSizeMap.values())\n    },\n    get total() {\n      return clicksTotalOverrides\n        ?? (isMounted.value\n          ? Math.max(0, ...maxMap.values())\n          : 0 /* fallback value */\n        )\n    },\n  }\n  return context\n}\n\nexport function createFixedClicks(\n  route?: SlideRoute | undefined,\n  currentInit: MaybeRefOrGetter<number> = 0,\n): ClicksContext {\n  const clicksStart = route?.meta.slide?.frontmatter.clicksStart ?? 0\n  const clicks = ref(Math.max(toValue(currentInit), clicksStart))\n  watch(() => toValue(currentInit), (v) => {\n    clicks.value = Math.max(v, clicksStart)\n  })\n  return createClicksContextBase(\n    clicks,\n    clicksStart,\n    route?.meta?.clicks,\n  )\n}\n"
  },
  {
    "path": "packages/client/composables/useDarkMode.ts",
    "content": "import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'\n\nexport function useDarkMode() {\n  return {\n    isColorSchemaConfigured,\n    isDark,\n    toggleDark,\n  }\n}\n"
  },
  {
    "path": "packages/client/composables/useDragElements.ts",
    "content": "import type { SlidePatch } from '@slidev/types'\nimport type { CSSProperties, DirectiveBinding, InjectionKey, WatchStopHandle } from 'vue'\nimport { debounce, ensureSuffix } from '@antfu/utils'\nimport { injectLocal, onClickOutside, useWindowFocus } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport { injectionCurrentPage, injectionFrontmatter, injectionRenderContext, injectionSlideElement, injectionSlideScale, injectionSlideZoom } from '../constants'\nimport { makeId } from '../logic/utils'\nimport { activeDragElement } from '../state'\nimport { directiveInject } from '../utils'\nimport { useNav } from './useNav'\nimport { useSlideBounds } from './useSlideBounds'\nimport { useDynamicSlideInfo } from './useSlideInfo'\n\nexport type DragElementDataSource = 'frontmatter' | 'prop' | 'directive'\n/**\n * Markdown source position, injected by markdown-it plugin\n */\nexport type DragElementMarkdownSource = [startLine: number, endLine: number, index: number]\n\nexport type DragElementsUpdater = (id: string, posStr: string, type: DragElementDataSource, markdownSource?: DragElementMarkdownSource) => void\n\nconst map: Record<number, DragElementsUpdater> = {}\n\nexport function useDragElementsUpdater(no: number) {\n  if (!(__DEV__ && __SLIDEV_FEATURE_EDITOR__))\n    return () => {}\n\n  if (map[no])\n    return map[no]\n\n  const { info, update } = useDynamicSlideInfo(no)\n\n  let newPatch: SlidePatch | null = null\n  async function save() {\n    if (newPatch) {\n      await update({\n        ...newPatch,\n        skipHmr: true,\n      })\n      newPatch = null\n    }\n  }\n  const debouncedSave = debounce(500, save)\n\n  return map[no] = (id, posStr, type, markdownSource) => {\n    if (!info.value)\n      return\n\n    if (type === 'frontmatter') {\n      const frontmatter = info.value.frontmatter\n      frontmatter.dragPos ||= {}\n      if (frontmatter.dragPos[id] === posStr)\n        return\n      frontmatter.dragPos[id] = posStr\n      newPatch = {\n        frontmatter: {\n          dragPos: frontmatter.dragPos,\n        },\n      }\n    }\n    else {\n      if (!markdownSource)\n        throw new Error(`[Slidev] VDrag Element ${id} is missing markdown source`)\n\n      const [startLine, endLine, idx] = markdownSource\n      const lines = info.value.content.split(/\\r?\\n/g)\n\n      let section = lines.slice(startLine, endLine).join('\\n')\n      let replaced = false\n\n      section = type === 'prop'\n      // eslint-disable-next-line regexp/no-super-linear-backtracking\n        ? section.replace(/<(v-?drag-?\\w*)(.*?)(\\/)?>/gi, (full, tag, attrs, selfClose = '', index) => {\n            if (index === idx) {\n              replaced = true\n              const posMatch = attrs.match(/pos=\".*?\"/)\n              if (!posMatch)\n                return `<${tag}${ensureSuffix(' ', attrs)}pos=\"${posStr}\"${selfClose}>`\n              const start = posMatch.index\n              const end = start + posMatch[0].length\n              return `<${tag}${attrs.slice(0, start)}pos=\"${posStr}\"${attrs.slice(end)}${selfClose}>`\n            }\n            return full\n          })\n        : section.replace(/(?<![</\\w])v-drag(?:=\".*?\")?/gi, (full, index) => {\n            if (index === idx) {\n              replaced = true\n              return `v-drag=\"${posStr}\"`\n            }\n            return full\n          })\n\n      if (!replaced)\n        throw new Error(`[Slidev] VDrag Element ${id} is not found in the markdown source`)\n\n      lines.splice(\n        startLine,\n        endLine - startLine,\n        section,\n      )\n\n      const newContent = lines.join('\\n')\n      if (info.value.content === newContent)\n        return\n      newPatch = {\n        content: newContent,\n      }\n      info.value = {\n        ...info.value,\n        content: newContent,\n      }\n    }\n    debouncedSave()\n  }\n}\n\nexport function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource, isArrow = false) {\n  function inject<T>(key: InjectionKey<T> | string): T | undefined {\n    return directive\n      ? directiveInject(directive, key)\n      : injectLocal(key)\n  }\n\n  const renderContext = inject(injectionRenderContext)!\n  const frontmatter = inject(injectionFrontmatter) ?? {}\n  const page = inject(injectionCurrentPage)!\n  const updater = computed(() => useDragElementsUpdater(page.value))\n  const scale = inject(injectionSlideScale) ?? ref(1)\n  const zoom = inject(injectionSlideZoom) ?? ref(1)\n  const { left: slideLeft, top: slideTop, stop: stopWatchBounds } = useSlideBounds(inject(injectionSlideElement) ?? ref())\n  const { isPrintMode } = useNav()\n  const enabled = ['slide', 'presenter'].includes(renderContext.value) && !isPrintMode.value\n\n  let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'\n  let dragId: string = makeId()\n  let pos: number[] | undefined\n  if (Array.isArray(posRaw)) {\n    pos = posRaw\n  }\n  else if (typeof posRaw === 'string' && posRaw.includes(',')) {\n    pos = posRaw.split(',').map(Number)\n  }\n  else if (posRaw != null) {\n    dataSource = 'frontmatter'\n    dragId = `${posRaw}`\n    posRaw = frontmatter?.dragPos?.[dragId]\n    pos = (posRaw as string)?.split(',').map(Number)\n  }\n\n  if (dataSource !== 'frontmatter' && !markdownSource)\n    throw new Error('[Slidev] Can not identify the source position of the v-drag element, please provide an explicit `id` prop.')\n\n  const watchStopHandles: WatchStopHandle[] = [stopWatchBounds]\n\n  const autoHeight = !isArrow && posRaw != null && !Number.isFinite(pos?.[3])\n  pos ??= [Number.NaN, Number.NaN, 0]\n  const width = ref(pos[2])\n  const x0 = ref(pos[0] + pos[2] / 2)\n\n  const rotate = ref(isArrow ? 0 : (pos[4] ?? 0))\n  const rotateRad = computed(() => rotate.value * Math.PI / 180)\n  const rotateSin = computed(() => Math.sin(rotateRad.value))\n  const rotateCos = computed(() => Math.cos(rotateRad.value))\n\n  const container = ref<HTMLElement>()\n  const bounds = ref({ left: 0, top: 0, width: 0, height: 0 })\n  const actualHeight = ref(0)\n  function updateBounds() {\n    if (!container.value)\n      return\n    const rect = container.value.getBoundingClientRect()\n    bounds.value = {\n      left: rect.left / zoom.value,\n      top: rect.top / zoom.value,\n      width: rect.width / zoom.value,\n      height: rect.height / zoom.value,\n    }\n    actualHeight.value = ((bounds.value.width + bounds.value.height) / scale.value / (Math.abs(rotateSin.value) + Math.abs(rotateCos.value)) - width.value)\n  }\n  watchStopHandles.push(watch(width, updateBounds, { flush: 'post' }))\n\n  const configuredHeight = ref(pos[3] ?? 0)\n  const height = autoHeight\n    ? computed({\n        get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,\n        set: v => !autoHeight && (configuredHeight.value = v),\n      })\n    : configuredHeight\n  const configuredY0 = autoHeight ? ref(pos[1]) : ref(pos[1] + pos[3] / 2)\n  const y0 = autoHeight\n    ? computed({\n        get: () => configuredY0.value + height.value / 2,\n        set: v => configuredY0.value = v - height.value / 2,\n      })\n    : configuredY0\n\n  const containerStyle = computed(() => {\n    return Number.isFinite(x0.value)\n      ? {\n        position: 'absolute',\n        zIndex: 100,\n        left: `${x0.value - width.value / 2}px`,\n        top: `${y0.value - height.value / 2}px`,\n        width: `${width.value}px`,\n        height: autoHeight ? undefined : `${height.value}px`,\n        transformOrigin: 'center center',\n        transform: `rotate(${rotate.value}deg)`,\n      } satisfies CSSProperties\n      : {\n        position: 'absolute',\n        zIndex: 100,\n      } satisfies CSSProperties\n  })\n\n  watchStopHandles.push(\n    watch(\n      [x0, y0, width, height, rotate],\n      ([x0, y0, w, h, r]) => {\n        let posStr = [x0 - w / 2, y0 - h / 2, w].map(Math.round).join()\n        if (autoHeight)\n          posStr += dataSource === 'directive' ? ',NaN' : ',_'\n        else\n          posStr += `,${Math.round(h)}`\n        if (Math.round(r) !== 0)\n          posStr += `,${Math.round(r)}`\n\n        if (dataSource === 'directive')\n          posStr = `[${posStr}]`\n\n        updater.value(dragId, posStr, dataSource, markdownSource)\n      },\n    ),\n  )\n\n  const state = {\n    dragId,\n    dataSource,\n    markdownSource,\n    isArrow,\n    zoom,\n    autoHeight,\n    x0,\n    y0,\n    width,\n    height,\n    rotate,\n    container,\n    containerStyle,\n    watchStopHandles,\n    dragging: computed((): boolean => activeDragElement.value === state),\n    mounted() {\n      if (!enabled)\n        return\n      updateBounds()\n      if (!posRaw) {\n        setTimeout(() => {\n          updateBounds()\n          x0.value = (bounds.value.left + bounds.value.width / 2 - slideLeft.value) / scale.value\n          y0.value = (bounds.value.top - slideTop.value) / scale.value\n          width.value = bounds.value.width / scale.value\n          height.value = bounds.value.height / scale.value\n        }, 100)\n      }\n    },\n    unmounted() {\n      if (!enabled)\n        return\n      state.stopDragging()\n    },\n    startDragging(): void {\n      if (!enabled)\n        return\n      updateBounds()\n      activeDragElement.value = state\n    },\n    stopDragging(): void {\n      if (!enabled)\n        return\n      if (activeDragElement.value === state)\n        activeDragElement.value = null\n    },\n  }\n\n  watchStopHandles.push(\n    onClickOutside(container, (ev) => {\n      const container = document.querySelector('#drag-control-container')\n      if (container && ev.target && container.contains(ev.target as HTMLElement))\n        return\n      state.stopDragging()\n    }),\n    watch(useWindowFocus(), (focused) => {\n      if (!focused)\n        state.stopDragging()\n    }),\n  )\n\n  return state\n}\n\nexport type DragElementState = ReturnType<typeof useDragElement>\n"
  },
  {
    "path": "packages/client/composables/useDrawings.ts",
    "content": "import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'\nimport { createSharedComposable, toReactive, useLocalStorage } from '@vueuse/core'\nimport { createDrauu } from 'drauu'\nimport { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'\nimport { configs } from '../env'\nimport { isInputting } from '../state'\nimport { drawingState, onPatchDrawingState, patchDrawingState } from '../state/drawings'\nimport { useNav } from './useNav'\n\nexport const useDrawings = createSharedComposable(() => {\n  const { currentSlideNo, isPresenter } = useNav()\n\n  const brushColors = [\n    '#ff595e',\n    '#ffca3a',\n    '#8ac926',\n    '#1982c4',\n    '#6a4c93',\n    '#ffffff',\n    '#000000',\n  ]\n\n  const drawingEnabled = useLocalStorage('slidev-drawing-enabled', false)\n  const drawingPinned = useLocalStorage('slidev-drawing-pinned', false)\n  const brush = toReactive(useLocalStorage<Brush>('slidev-drawing-brush', {\n    color: brushColors[0],\n    size: 4,\n    mode: 'stylus',\n  }))\n\n  const isDrawing = ref(false)\n  const canUndo = ref(false)\n  const canRedo = ref(false)\n  const canClear = ref(false)\n\n  const _mode = ref<DrawingMode | 'arrow'>('stylus')\n  const syncUp = computed(() => configs.drawings.syncAll || isPresenter.value)\n  let disableDump = false\n\n  const drawingMode = computed({\n    get() {\n      return _mode.value\n    },\n    set(v: DrawingMode | 'arrow') {\n      _mode.value = v\n      if (v === 'arrow') {\n        // eslint-disable-next-line ts/no-use-before-define\n        drauu.mode = 'line'\n        brush.arrowEnd = true\n      }\n      else {\n        // eslint-disable-next-line ts/no-use-before-define\n        drauu.mode = v\n        brush.arrowEnd = false\n      }\n    },\n  })\n\n  const drauuOptions: DrauuOptions = reactive({\n    brush,\n    acceptsInputTypes: computed(() => (drawingEnabled.value && (!configs.drawings.presenterOnly || isPresenter.value)) ? undefined : ['pen' as const]),\n    coordinateTransform: false,\n  })\n\n  const drauu = markRaw(createDrauu(drauuOptions))\n\n  function clearDrauu() {\n    drauu.clear()\n    if (syncUp.value)\n      patchDrawingState(currentSlideNo.value, '')\n  }\n\n  function updateState() {\n    canRedo.value = drauu.canRedo()\n    canUndo.value = drauu.canUndo()\n    canClear.value = !!drauu.el?.children.length\n  }\n\n  function loadCanvas(page?: number) {\n    disableDump = true\n    const data = drawingState[page || currentSlideNo.value]\n    if (data != null)\n      drauu.load(data)\n    else\n      drauu.clear()\n    updateState()\n    disableDump = false\n  }\n\n  drauu.on('changed', () => {\n    updateState()\n    if (!disableDump) {\n      const dump = drauu.dump()\n      const key = currentSlideNo.value\n      if ((drawingState[key] || '') !== dump && syncUp.value)\n        patchDrawingState(key, drauu.dump())\n    }\n  })\n\n  onPatchDrawingState((state) => {\n    disableDump = true\n    if (state[currentSlideNo.value] != null)\n      drauu.load(state[currentSlideNo.value] || '')\n    disableDump = false\n    updateState()\n  })\n\n  nextTick(() => {\n    watch(currentSlideNo, () => {\n      if (!drauu.mounted)\n        return\n      loadCanvas()\n    }, { immediate: true })\n  })\n\n  drauu.on('start', () => isDrawing.value = true)\n  drauu.on('end', () => isDrawing.value = false)\n\n  window.addEventListener('keydown', (e) => {\n    if (!drawingEnabled.value || isInputting.value)\n      return\n\n    const noModifier = !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey\n    let handled = true\n    if (e.code === 'KeyZ' && (e.ctrlKey || e.metaKey)) {\n      if (e.shiftKey)\n        drauu.redo()\n      else\n        drauu.undo()\n    }\n    else if (e.code === 'Escape') {\n      drawingEnabled.value = false\n    }\n    else if (e.code === 'KeyL' && noModifier) {\n      drawingMode.value = 'line'\n    }\n    else if (e.code === 'KeyA' && noModifier) {\n      drawingMode.value = 'arrow'\n    }\n    else if (e.code === 'KeyS' && noModifier) {\n      drawingMode.value = 'stylus'\n    }\n    else if (e.code === 'KeyR' && noModifier) {\n      drawingMode.value = 'rectangle'\n    }\n    else if (e.code === 'KeyE' && noModifier) {\n      drawingMode.value = 'ellipse'\n    }\n    else if (e.code === 'KeyC' && noModifier) {\n      clearDrauu()\n    }\n    else if (e.code.startsWith('Digit') && noModifier && +e.code[5] <= brushColors.length) {\n      brush.color = brushColors[+e.code[5] - 1]\n    }\n    else {\n      handled = false\n    }\n\n    if (handled) {\n      e.preventDefault()\n      e.stopPropagation()\n    }\n  }, false)\n\n  return {\n    brush,\n    brushColors,\n    canClear,\n    canRedo,\n    canUndo,\n    clear: clearDrauu,\n    drauu,\n    drauuOptions,\n    drawingEnabled,\n    drawingMode,\n    drawingPinned,\n    drawingState,\n    isDrawing,\n    loadCanvas,\n  }\n})\n"
  },
  {
    "path": "packages/client/composables/useEmbeddedCtrl.ts",
    "content": "import { throttledWatch } from '@vueuse/core'\nimport { useNav } from '../composables/useNav'\nimport { isDark } from '../logic/dark'\n\nexport function useEmbeddedControl() {\n  const nav = useNav()\n  const clientId = `${Date.now()}`\n\n  window.addEventListener('message', ({ data }) => {\n    if (data && data.target === 'slidev') {\n      if (data.type === 'navigate') {\n        if (data.no || data.clicks) {\n          nav.go(+data.no, +data.clicks || 0)\n        }\n        else if (typeof data.operation === 'string') {\n          const fn = nav[data.operation as keyof typeof nav]\n          if (typeof fn === 'function')\n            (fn as any)(...(data.args ?? []))\n        }\n      }\n      else if (data.type === 'css-vars') {\n        const root = document.documentElement\n        for (const [key, value] of Object.entries(data.vars || {}))\n          root.style.setProperty(key, value as any)\n      }\n      else if (data.type === 'color-schema') {\n        isDark.value = data.color === 'dark'\n      }\n    }\n  })\n\n  if (nav.isEmbedded.value) {\n    throttledWatch(\n      [nav.currentSlideNo, nav.clicks, nav.hasNext, nav.hasPrev],\n      ([no, clicks, hasNext, hasPrev]) => {\n        window.parent.postMessage(\n          {\n            target: 'slidev',\n            clientId,\n            navState: {\n              no,\n              clicks,\n              hasNext,\n              hasPrev,\n            },\n          },\n          '*',\n        )\n      },\n      {\n        throttle: 50,\n        immediate: true,\n      },\n    )\n  }\n}\n"
  },
  {
    "path": "packages/client/composables/useHideCursorIdle.ts",
    "content": "import type { Ref } from 'vue'\nimport { useEventListener } from '@vueuse/core'\nimport { computed, onScopeDispose, watch } from 'vue'\nimport { hideCursorIdle } from '../state'\n\nconst TIMEOUT = 2000\n\nexport function useHideCursorIdle(\n  enabled: Ref<boolean>,\n) {\n  const shouldHide = computed(() => enabled.value && hideCursorIdle.value)\n\n  function hide() {\n    document.body.style.cursor = 'none'\n  }\n  function show() {\n    document.body.style.cursor = ''\n  }\n\n  let timer: ReturnType<typeof setTimeout> | null = null\n\n  // If disabled, immediately show the cursor and stop the timer\n  watch(\n    shouldHide,\n    (value) => {\n      if (!value) {\n        show()\n        if (timer) {\n          clearTimeout(timer)\n        }\n        timer = null\n      }\n    },\n  )\n\n  onScopeDispose(() => {\n    show()\n    if (timer) {\n      clearTimeout(timer)\n    }\n    timer = null\n  })\n\n  useEventListener(\n    document.body,\n    ['pointermove', 'pointerdown'],\n    () => {\n      show()\n      if (timer)\n        clearTimeout(timer)\n      if (shouldHide.value) {\n        timer = setTimeout(hide, TIMEOUT)\n      }\n      else {\n        timer = null\n      }\n    },\n    { passive: true },\n  )\n}\n"
  },
  {
    "path": "packages/client/composables/useIME.ts",
    "content": "import type { ModelRef } from 'vue'\nimport { ref, watch } from 'vue'\n\nexport function useIME(content: ModelRef<string>) {\n  const composingContent = ref(content.value)\n  watch(content, (v) => {\n    if (v !== composingContent.value) {\n      composingContent.value = v\n    }\n  })\n\n  function onInput(e: Event) {\n    if (!(e instanceof InputEvent) || !(e.target instanceof HTMLTextAreaElement)) {\n      return\n    }\n\n    if (e.isComposing) {\n      composingContent.value = e.target.value\n    }\n    else {\n      content.value = e.target.value\n    }\n  }\n\n  function onCompositionEnd() {\n    content.value = composingContent.value\n  }\n\n  return { composingContent, onInput, onCompositionEnd }\n}\n"
  },
  {
    "path": "packages/client/composables/useNav.ts",
    "content": "import type { ClicksContext, SlideRoute, TocItem } from '@slidev/types'\nimport type { ComputedRef, Ref, TransitionGroupProps, WritableComputedRef } from 'vue'\nimport type { RouteLocationNormalized, Router } from 'vue-router'\nimport { slides } from '#slidev/slides'\nimport { clamp } from '@antfu/utils'\nimport { parseRangeString } from '@slidev/parser/utils'\nimport { createSharedComposable } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { CLICKS_MAX } from '../constants'\nimport { configs } from '../env'\nimport { useRouteQuery } from '../logic/route'\nimport { getSlide, getSlidePath } from '../logic/slides'\nimport { getCurrentTransition } from '../logic/transition'\nimport { hmrSkipTransition } from '../state'\nimport { createClicksContextBase } from './useClicks'\nimport { useTocTree } from './useTocTree'\n\nexport interface SlidevContextNav {\n  slides: Ref<SlideRoute[]>\n  total: ComputedRef<number>\n\n  currentPath: ComputedRef<string>\n  currentPage: ComputedRef<number>\n  currentSlideNo: ComputedRef<number>\n  currentSlideRoute: ComputedRef<SlideRoute>\n  currentTransition: ComputedRef<TransitionGroupProps | undefined>\n  currentLayout: ComputedRef<string>\n\n  nextRoute: ComputedRef<SlideRoute>\n  prevRoute: ComputedRef<SlideRoute>\n  hasNext: ComputedRef<boolean>\n  hasPrev: ComputedRef<boolean>\n\n  clicksContext: ComputedRef<ClicksContext>\n  clicks: ComputedRef<number>\n  clicksStart: ComputedRef<number>\n  clicksTotal: ComputedRef<number>\n\n  /** The table of content tree */\n  tocTree: ComputedRef<TocItem[]>\n  /** The direction of the navigation, 1 for forward, -1 for backward */\n  navDirection: Ref<number>\n  /** The direction of the clicks, 1 for forward, -1 for backward */\n  clicksDirection: Ref<number>\n  /** Utility function for open file in editor, only avaible in dev mode  */\n  openInEditor: (url?: string) => Promise<boolean>\n\n  /** Go to next click */\n  next: () => Promise<void>\n  /** Go to previous click */\n  prev: () => Promise<void>\n  /** Go to next slide */\n  nextSlide: (lastClicks?: boolean) => Promise<void>\n  /** Go to previous slide */\n  prevSlide: (lastClicks?: boolean) => Promise<void>\n  /** Go to slide */\n  go: (no: number | string, clicks?: number, force?: boolean) => Promise<void>\n  /** Go to the first slide */\n  goFirst: () => Promise<void>\n  /** Go to the last slide */\n  goLast: () => Promise<void>\n\n  /** Enter presenter mode */\n  enterPresenter: () => void\n  /** Exit presenter mode */\n  exitPresenter: () => void\n}\n\nexport interface SlidevContextNavState {\n  router: Router\n  currentRoute: ComputedRef<RouteLocationNormalized>\n  isPrintMode: ComputedRef<boolean>\n  isPrintWithClicks: Ref<boolean>\n  isEmbedded: ComputedRef<boolean>\n  isPlaying: ComputedRef<boolean>\n  isPresenter: ComputedRef<boolean>\n  isNotesViewer: ComputedRef<boolean>\n  isPresenterAvailable: ComputedRef<boolean>\n  hasPrimarySlide: ComputedRef<boolean>\n  currentSlideNo: ComputedRef<number>\n  currentSlideRoute: ComputedRef<SlideRoute>\n  clicksContext: ComputedRef<ClicksContext>\n  queryClicksRaw: Ref<string>\n  queryClicks: WritableComputedRef<number>\n  printRange: Ref<number[]>\n  getPrimaryClicks: (route: SlideRoute) => ClicksContext\n}\n\nexport interface SlidevContextNavFull extends SlidevContextNav, SlidevContextNavState { }\n\nexport function useNavBase(\n  currentSlideRoute: ComputedRef<SlideRoute>,\n  clicksContext: ComputedRef<ClicksContext>,\n  queryClicks: Ref<number> = ref(0),\n  isPresenter: Ref<boolean>,\n  isPrint: Ref<boolean>,\n  router?: Router,\n): SlidevContextNav {\n  const total = computed(() => slides.value.length)\n\n  const navDirection = ref(0)\n  const clicksDirection = ref(0)\n\n  const currentPath = computed(() => getSlidePath(currentSlideRoute.value, isPresenter.value))\n  const currentSlideNo = computed(() => currentSlideRoute.value.no)\n  const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))\n\n  const clicks = computed(() => clicksContext.value.current)\n  const clicksStart = computed(() => clicksContext.value.clicksStart)\n  const clicksTotal = computed(() => clicksContext.value.total)\n  const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])\n  const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])\n  const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)\n  const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)\n\n  const currentTransition = computed(() => isPrint.value ? undefined : getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))\n\n  watch(currentSlideRoute, (next, prev) => {\n    navDirection.value = next.no - prev.no\n  })\n\n  async function openInEditor(url?: string) {\n    if (!__DEV__)\n      return false\n    if (url == null) {\n      const slide = currentSlideRoute.value?.meta?.slide\n      if (!slide)\n        return false\n      url = `${slide.filepath}:${slide.start}`\n    }\n    await fetch(`/__open-in-editor?file=${encodeURIComponent(url)}`)\n    return true\n  }\n\n  const tocTree = useTocTree(\n    slides,\n    currentSlideNo,\n    currentSlideRoute,\n  )\n\n  async function next() {\n    clicksDirection.value = 1\n    if (clicksTotal.value <= queryClicks.value)\n      await nextSlide()\n    else\n      queryClicks.value += 1\n  }\n\n  async function prev() {\n    clicksDirection.value = -1\n    if (queryClicks.value <= clicksStart.value)\n      await prevSlide(true)\n    else\n      queryClicks.value -= 1\n  }\n\n  async function nextSlide(lastClicks = false) {\n    clicksDirection.value = 1\n    if (currentSlideNo.value < slides.value.length) {\n      await go(\n        currentSlideNo.value + 1,\n        lastClicks && !isPrint.value ? CLICKS_MAX : undefined,\n      )\n    }\n  }\n\n  async function prevSlide(lastClicks = false) {\n    clicksDirection.value = -1\n    if (currentSlideNo.value > 1) {\n      await go(\n        currentSlideNo.value - 1,\n        lastClicks && !isPrint.value ? CLICKS_MAX : undefined,\n      )\n    }\n  }\n\n  function goFirst() {\n    return go(1)\n  }\n\n  function goLast() {\n    return go(total.value)\n  }\n\n  async function go(no: number | string, clicks: number = 0, force = false) {\n    hmrSkipTransition.value = false\n    const pageChanged = currentSlideNo.value !== no\n    const clicksChanged = clicks !== queryClicks.value\n    const meta = getSlide(no)?.meta\n    const clicksStart = meta?.slide?.frontmatter.clicksStart ?? 0\n    clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)\n    if (force || pageChanged || clicksChanged) {\n      await router?.push({\n        path: getSlidePath(no, isPresenter.value, router.currentRoute.value.name === 'export'),\n        query: {\n          ...router.currentRoute.value.query,\n          clicks: clicks === 0 ? undefined : clicks.toString(),\n          embedded: location.search.includes('embedded') ? 'true' : undefined,\n        },\n      })\n    }\n  }\n\n  function enterPresenter() {\n    router?.push({\n      path: getSlidePath(currentSlideNo.value, true),\n      query: { ...router.currentRoute.value.query },\n    })\n  }\n  function exitPresenter() {\n    router?.push({\n      path: getSlidePath(currentSlideNo.value, false),\n      query: { ...router.currentRoute.value.query },\n    })\n  }\n\n  return {\n    slides,\n    total,\n    currentPath,\n    currentSlideNo,\n    currentPage: currentSlideNo,\n    currentSlideRoute,\n    currentLayout,\n    currentTransition,\n    clicksDirection,\n    nextRoute,\n    prevRoute,\n    clicksContext,\n    clicks,\n    clicksStart,\n    clicksTotal,\n    hasNext,\n    hasPrev,\n    tocTree,\n    navDirection,\n    openInEditor,\n    next,\n    prev,\n    go,\n    goLast,\n    goFirst,\n    nextSlide,\n    prevSlide,\n    enterPresenter,\n    exitPresenter,\n  }\n}\n\nexport function useFixedNav(\n  currentSlideRoute: SlideRoute,\n  clicksContext: ClicksContext,\n): SlidevContextNav {\n  const noop = async () => { }\n  return {\n    ...useNavBase(\n      computed(() => currentSlideRoute),\n      computed(() => clicksContext),\n      ref(CLICKS_MAX),\n      ref(false),\n      ref(false),\n    ),\n    next: noop,\n    prev: noop,\n    nextSlide: noop,\n    prevSlide: noop,\n    goFirst: noop,\n    goLast: noop,\n    go: noop,\n  }\n}\n\nconst useNavState = createSharedComposable((): SlidevContextNavState => {\n  const router = useRouter()\n  const currentRoute = useRoute()\n\n  const query = computed(() => {\n    // eslint-disable-next-line ts/no-unused-expressions\n    router?.currentRoute?.value?.query\n    return new URLSearchParams(location.search)\n  })\n  const isPrintMode = computed(() => query.value.has('print') || currentRoute.name === 'export')\n  const isPrintWithClicks = ref(query.value.get('print') === 'clicks')\n  const isEmbedded = computed(() => query.value.has('embedded'))\n  const isPlaying = computed(() => currentRoute.name === 'play')\n  const isPresenter = computed(() => currentRoute.name === 'presenter')\n  const isNotesViewer = computed(() => currentRoute.name === 'notes')\n  const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || query.value.get('password') === configs.remote))\n  const hasPrimarySlide = computed(() => !!currentRoute.params.no)\n  const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.params.no as string)?.no ?? 1 : 1)\n  const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])\n  const printRange = ref(parseRangeString(slides.value.length, currentRoute?.query?.range as string | undefined))\n\n  const queryClicksRaw = useRouteQuery<string>('clicks', '0')\n\n  const clicksContext = computed(() => getPrimaryClicks(currentSlideRoute.value))\n\n  const queryClicks = computed({\n    get() {\n      let v = +(queryClicksRaw.value || 0)\n      if (Number.isNaN(v))\n        v = 0\n      return v\n    },\n    set(v) {\n      hmrSkipTransition.value = false\n      queryClicksRaw.value = v.toString()\n    },\n  })\n\n  function getPrimaryClicks(\n    route: SlideRoute,\n  ): ClicksContext {\n    if (route?.meta?.__clicksContext)\n      return route.meta.__clicksContext\n\n    const thisNo = route.no\n    const context = createClicksContextBase(\n      computed({\n        get() {\n          if (currentSlideNo.value === thisNo)\n            return Math.max(+(queryClicksRaw.value ?? 0), context.clicksStart)\n          else if (currentSlideNo.value > thisNo)\n            return CLICKS_MAX\n          else\n            return context.clicksStart\n        },\n        set(v) {\n          if (currentSlideNo.value === thisNo)\n            queryClicksRaw.value = v.toString()\n        },\n      }),\n      route?.meta.slide?.frontmatter.clicksStart ?? 0,\n      route?.meta.clicks,\n    )\n\n    if (route?.meta)\n      route.meta.__clicksContext = context\n\n    return context\n  }\n\n  return {\n    router,\n    currentRoute: computed(() => currentRoute),\n    isPrintMode,\n    isPrintWithClicks,\n    isEmbedded,\n    isPlaying,\n    isPresenter,\n    isNotesViewer,\n    isPresenterAvailable,\n    hasPrimarySlide,\n    currentSlideNo,\n    currentSlideRoute,\n    clicksContext,\n    queryClicksRaw,\n    queryClicks,\n    printRange,\n    getPrimaryClicks,\n  }\n})\n\nexport const useNav = createSharedComposable((): SlidevContextNavFull => {\n  const state = useNavState()\n  const router = useRouter()\n\n  const nav = useNavBase(\n    state.currentSlideRoute,\n    state.clicksContext,\n    state.queryClicks,\n    state.isPresenter,\n    state.isPrintMode,\n    router,\n  )\n\n  watch(\n    [nav.total, state.currentRoute],\n    async () => {\n      const no = state.currentRoute.value.params.no as string\n      if (state.hasPrimarySlide.value && !getSlide(no)) {\n        if (no && no !== 'index.html') {\n          // The current slide may has been removed. Redirect to the last slide.\n          await nav.go(nav.total.value, 0, true)\n        }\n        else {\n          // Redirect to the first slide\n          await nav.go(1, 0, true)\n        }\n      }\n    },\n    { flush: 'pre', immediate: true },\n  )\n\n  return {\n    ...nav,\n    ...state,\n  }\n})\n"
  },
  {
    "path": "packages/client/composables/usePreloadImages.ts",
    "content": "import type { SlideRoute } from '@slidev/types'\nimport type { ComputedRef, Ref } from 'vue'\nimport configs from '#slidev/configs'\nimport { watchEffect } from 'vue'\n\nconst loaded = new Set<string>()\nconst loading = new Set<string>()\n\nfunction resolveUrl(url: string): string {\n  if (url.startsWith('http') || url.startsWith('//'))\n    return url\n  const base = (import.meta.env.BASE_URL || '/').replace(/\\/$/, '')\n  return `${base}${url.startsWith('/') ? url : `/${url}`}`\n}\n\nfunction preloadImage(url: string): void {\n  const resolved = resolveUrl(url)\n  if (loaded.has(resolved) || loading.has(resolved))\n    return\n  loading.add(resolved)\n  const img = new Image()\n  img.onload = () => {\n    loading.delete(resolved)\n    loaded.add(resolved)\n  }\n  img.onerror = () => {\n    loading.delete(resolved)\n  }\n  img.src = resolved\n}\n\nfunction preloadSlideImages(route: SlideRoute): void {\n  const images = route.meta?.slide?.images\n  if (images?.length) {\n    for (const url of images)\n      preloadImage(url)\n  }\n}\n\nexport function usePreloadImages(\n  currentRoute: ComputedRef<SlideRoute>,\n  prevRoute: ComputedRef<SlideRoute>,\n  nextRoute: ComputedRef<SlideRoute>,\n  slides: Ref<SlideRoute[]>,\n): void {\n  const config = configs.preloadImages\n  if (config === false)\n    return\n\n  const ahead = (typeof config === 'object' && config?.ahead) || 3\n\n  // Preload current + prev + next + look-ahead window\n  watchEffect(() => {\n    const current = currentRoute.value\n    const all = slides.value\n    if (!current || !all?.length)\n      return\n\n    preloadSlideImages(current)\n    preloadSlideImages(prevRoute.value)\n    preloadSlideImages(nextRoute.value)\n\n    // Preload ahead window\n    const currentIdx = current.no - 1\n    for (let i = 1; i <= ahead; i++) {\n      const idx = currentIdx + i\n      if (idx < all.length)\n        preloadSlideImages(all[idx])\n    }\n  })\n\n  // Preload all remaining slides after 3s\n  watchEffect((onCleanup) => {\n    const all = slides.value\n    const timeout = setTimeout(() => {\n      if (all?.length) {\n        for (const route of all)\n          preloadSlideImages(route)\n      }\n    }, 3000)\n    onCleanup(() => clearTimeout(timeout))\n  })\n}\n"
  },
  {
    "path": "packages/client/composables/usePrintStyles.ts",
    "content": "import { useStyleTag } from '@vueuse/core'\nimport { computed } from 'vue'\nimport { slideHeight, slideWidth } from '../env'\nimport { useNav } from './useNav'\n\nexport function usePrintStyles() {\n  const { isPrintMode } = useNav()\n\n  useStyleTag(computed(() => isPrintMode.value\n    ? `\n@page {\n  size: ${slideWidth.value}px ${slideHeight.value}px;\n  margin: 0px;\n}\n\n* {\n  transition: none !important;\n  transition-duration: 0s !important;\n}`\n    : ''))\n}\n\n// Monaco uses `<style media=\"screen\" class=\"monaco-colors\">` to apply colors, which will be ignored in print mode.\nexport function patchMonacoColors() {\n  document.querySelectorAll<HTMLStyleElement>('style.monaco-colors').forEach((el) => {\n    el.media = ''\n  })\n}\n"
  },
  {
    "path": "packages/client/composables/useSlideBounds.ts",
    "content": "import { useElementBounding } from '@vueuse/core'\nimport { inject, ref, watch } from 'vue'\nimport { injectionSlideElement } from '../constants'\nimport { editorHeight, editorWidth, isEditorVertical, showEditor, slideScale, windowSize } from '../state'\n\nexport function useSlideBounds(slideElement = inject(injectionSlideElement, ref())) {\n  const bounding = useElementBounding(slideElement)\n  const stop = watch(\n    [\n      showEditor,\n      isEditorVertical,\n      editorWidth,\n      editorHeight,\n      slideScale,\n      windowSize.width,\n      windowSize.height,\n    ],\n    () => {\n      setTimeout(bounding.update, 300)\n    },\n    {\n      flush: 'post',\n      immediate: true,\n    },\n  )\n  return {\n    ...bounding,\n    stop,\n  }\n}\n"
  },
  {
    "path": "packages/client/composables/useSlideInfo.ts",
    "content": "import type { SlideInfo, SlidePatch } from '@slidev/types'\nimport type { MaybeRef } from '@vueuse/core'\nimport type { Ref } from 'vue'\nimport { useFetch } from '@vueuse/core'\nimport { computed, ref, unref } from 'vue'\nimport { getSlide } from '../logic/slides'\n\nexport interface UseSlideInfo {\n  info: Ref<SlideInfo | null>\n  update: (data: SlidePatch) => Promise<SlideInfo | void>\n}\n\nexport function useSlideInfo(no: number): UseSlideInfo {\n  if (!__SLIDEV_HAS_SERVER__) {\n    return {\n      info: ref(getSlide(no)?.meta.slide ?? null) as Ref<SlideInfo | null>,\n      update: async () => {},\n    }\n  }\n  const url = `/__slidev/slides/${no}.json`\n  const { data: info, execute } = useFetch(url).json<SlideInfo>().get()\n\n  execute()\n\n  const update = async (data: SlidePatch) => {\n    return await fetch(\n      url,\n      {\n        method: 'POST',\n        headers: {\n          'Accept': 'application/json',\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(data),\n      },\n    ).then(r => r.json())\n  }\n\n  if (__DEV__) {\n    import.meta.hot?.on('slidev:update-slide', (payload) => {\n      if (payload.no === no)\n        info.value = payload.data\n    })\n    import.meta.hot?.on('slidev:update-note', (payload) => {\n      if (payload.no === no && info.value && info.value.note?.trim() !== payload.note?.trim())\n        info.value = { ...info.value, ...payload }\n    })\n  }\n\n  return {\n    info,\n    update,\n  }\n}\n\nconst map: Record<number, UseSlideInfo> = {}\n\nexport function useDynamicSlideInfo(no: MaybeRef<number>) {\n  function get(no: number) {\n    return map[no] ??= useSlideInfo(no)\n  }\n\n  return {\n    info: computed({\n      get() {\n        return get(unref(no)).info.value\n      },\n      set(newInfo) {\n        get(unref(no)).info.value = newInfo\n      },\n    }),\n    update: async (data: SlidePatch, newId?: number) => {\n      const info = get(newId ?? unref(no))\n      const newData = await info.update(data)\n      if (newData)\n        info.info.value = newData\n      return newData\n    },\n  }\n}\n"
  },
  {
    "path": "packages/client/composables/useSwipeControls.ts",
    "content": "import type { Ref } from 'vue'\nimport { timestamp, usePointerSwipe } from '@vueuse/core'\nimport { ref } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useDrawings } from './useDrawings'\n\nexport function useSwipeControls(root: Ref<HTMLElement | undefined>) {\n  const { next, nextSlide, prev, prevSlide } = useNav()\n  const { isDrawing } = useDrawings()\n\n  const swipeBegin = ref(0)\n  const { direction, distanceX, distanceY } = usePointerSwipe(root, {\n    pointerTypes: ['touch'],\n    onSwipeStart() {\n      if (isDrawing.value)\n        return\n      swipeBegin.value = timestamp()\n    },\n    onSwipeEnd() {\n      if (!swipeBegin.value)\n        return\n      if (isDrawing.value)\n        return\n\n      const x = Math.abs(distanceX.value)\n      const y = Math.abs(distanceY.value)\n      if (x / window.innerWidth > 0.3 || x > 75) {\n        if (direction.value === 'left')\n          next()\n\n        else\n          prev()\n      }\n      else if (y / window.innerHeight > 0.4 || y > 200) {\n        if (direction.value === 'down')\n          prevSlide()\n\n        else\n          nextSlide()\n      }\n    },\n  })\n}\n"
  },
  {
    "path": "packages/client/composables/useTimer.ts",
    "content": "import { parseTimeString } from '@slidev/parser/utils'\nimport { useInterval } from '@vueuse/core'\nimport { computed, toRef } from 'vue'\nimport { configs } from '../env'\nimport { sharedState } from '../state/shared'\n\nexport function useTimer() {\n  const mode = computed(() => configs.timer || 'stopwatch')\n  const duration = computed(() => parseTimeString(configs.duration).seconds)\n  const interval = useInterval(100, { controls: true })\n\n  const state = toRef(sharedState, 'timer')\n  const status = computed(() => state.value?.status)\n  const passedMs = computed(() => {\n    // eslint-disable-next-line ts/no-unused-expressions\n    interval.counter.value\n    if (state.value.status === 'stopped' || !state.value.startedAt)\n      return 0\n    if (state.value.status === 'paused')\n      return state.value.pausedAt - state.value.startedAt\n    return Date.now() - state.value.startedAt\n  })\n  const passed = computed(() => passedMs.value / 1000)\n  const percentage = computed(() => passed.value / duration.value * 100)\n\n  const timer = computed(() => {\n    if (mode.value === 'stopwatch') {\n      if (state.value.status === 'stopped' || !state.value.startedAt)\n        return { h: '', m: '-', s: '--', ms: '-' }\n    }\n\n    const total = mode.value === 'countdown'\n      ? duration.value * 1000 - passedMs.value\n      : passedMs.value\n\n    let h = Math.floor(total / 1000 / 60 / 60).toString()\n    if (h === '0')\n      h = ''\n    let min = Math.floor(total / 1000 / 60 % 60).toString()\n    if (h)\n      min = min.padStart(2, '0')\n    const sec = Math.floor(total / 1000 % 60).toString().padStart(2, '0')\n    const ms = Math.floor(total % 1000 / 100).toString()\n\n    return {\n      h,\n      m: min,\n      s: sec,\n      ms,\n    }\n  })\n\n  function reset() {\n    interval.pause()\n    state.value = {\n      status: 'stopped',\n      slides: {},\n      startedAt: 0,\n      pausedAt: 0,\n    }\n  }\n\n  function resume() {\n    if (!state.value)\n      return\n    if (state.value?.status === 'stopped') {\n      state.value.status = 'running'\n      state.value.startedAt = Date.now()\n    }\n    else if (state.value.status === 'paused') {\n      state.value.status = 'running'\n      state.value.startedAt = Date.now() - (state.value.pausedAt - state.value.startedAt)\n    }\n    interval.resume()\n  }\n\n  function pause() {\n    state.value.status = 'paused'\n    state.value.pausedAt = Date.now()\n    interval.pause()\n  }\n\n  function toggle() {\n    if (state.value.status === 'running') {\n      pause()\n    }\n    else {\n      resume()\n    }\n  }\n\n  return {\n    state,\n    status,\n    timer,\n    reset,\n    toggle,\n    resume,\n    pause,\n    passed,\n    percentage,\n    duration,\n    mode,\n  }\n}\n"
  },
  {
    "path": "packages/client/composables/useTocTree.ts",
    "content": "import type { SlideRoute, TocItem } from '@slidev/types'\nimport type { ComputedRef, Ref } from 'vue'\nimport { computed } from 'vue'\nimport { getSlidePath } from '../logic/slides'\n\nfunction addToTree(tree: TocItem[], route: SlideRoute, level = 1) {\n  const titleLevel = route.meta.slide.level ?? level\n  if (titleLevel && titleLevel > level && tree.length > 0) {\n    addToTree(tree[tree.length - 1].children, route, level + 1)\n  }\n  else {\n    tree.push({\n      no: route.no,\n      children: [],\n      level,\n      titleLevel,\n      path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no, false),\n      hideInToc: Boolean(route.meta?.slide?.frontmatter?.hideInToc),\n      title: route.meta?.slide?.title,\n    })\n  }\n}\n\nfunction getTreeWithActiveStatuses(\n  tree: TocItem[],\n  currentRoute?: SlideRoute,\n  hasActiveParent = false,\n  parent?: TocItem,\n  currentSlideNo?: Ref<number>,\n): TocItem[] {\n  return tree.map((item: TocItem) => {\n    const clone = {\n      ...item,\n      active: item.no === currentSlideNo?.value,\n      hasActiveParent,\n    }\n    if (clone.children.length > 0) {\n      clone.children = getTreeWithActiveStatuses(\n        clone.children,\n        currentRoute,\n        clone.active || clone.hasActiveParent,\n        clone,\n        currentSlideNo,\n      )\n    }\n    if (parent && (clone.active || clone.activeParent))\n      parent.activeParent = true\n    return clone\n  })\n}\n\nfunction filterTree(tree: TocItem[], level = 1): TocItem[] {\n  return tree\n    .filter((item: TocItem) => !item.hideInToc)\n    .map((item: TocItem) => ({\n      ...item,\n      children: filterTree(item.children, level + 1),\n    }))\n}\n\nexport function useTocTree(\n  slides: Ref<SlideRoute[]>,\n  currentSlideNo: Ref<number>,\n  currentSlideRoute: Ref<SlideRoute>,\n): ComputedRef<TocItem[]> {\n  const rawTree = computed(() => slides.value\n    .filter((route: SlideRoute) => route.meta?.slide?.title)\n    .reduce((acc: TocItem[], route: SlideRoute) => {\n      addToTree(acc, route)\n      return acc\n    }, []))\n\n  const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(\n    rawTree.value,\n    currentSlideRoute.value,\n    undefined,\n    undefined,\n    currentSlideNo,\n  ))\n\n  return computed(() => filterTree(treeWithActiveStatuses.value))\n}\n"
  },
  {
    "path": "packages/client/composables/useViewTransition.ts",
    "content": "import { ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { configs } from '../env'\nimport { getSlide } from '../logic/slides'\n\nexport function useViewTransition() {\n  const router = useRouter()\n  const isViewTransition = ref(false)\n\n  let viewTransitionFinish: undefined | (() => void)\n  let viewTransitionAbort: undefined | (() => void)\n\n  const supportViewTransition = typeof document !== 'undefined' && 'startViewTransition' in document\n\n  router.beforeResolve((to, from) => {\n    const fromMeta = getSlide(from.params.no as string)?.meta\n    const toMeta = getSlide(to.params.no as string)?.meta\n    const fromNo = fromMeta?.slide?.no\n    const toNo = toMeta?.slide?.no\n    const transitionType = fromNo != null && toNo != null && fromNo !== toNo\n      && ((fromNo < toNo ? fromMeta?.transition : toMeta?.transition) ?? configs.transition)\n    if (transitionType !== 'view-transition') {\n      isViewTransition.value = false\n      return\n    }\n\n    if (!supportViewTransition) {\n      isViewTransition.value = false\n      console.warn('View transition is not supported in your browser, fallback to normal transition.')\n      return\n    }\n\n    isViewTransition.value = true\n    const promise = new Promise<void>((resolve, reject) => {\n      viewTransitionFinish = resolve\n      viewTransitionAbort = reject\n    })\n\n    let changeRoute: () => void\n    const ready = new Promise<void>(resolve => (changeRoute = resolve))\n\n    // Wait for `TransitionGroup` to become normal `div`\n    setTimeout(() => {\n      document.startViewTransition(() => {\n        changeRoute()\n        return promise\n      })\n    }, 50)\n\n    return ready\n  })\n\n  if (supportViewTransition) {\n    router.afterEach(() => {\n      viewTransitionFinish?.()\n      viewTransitionAbort?.()\n    })\n  }\n\n  return isViewTransition\n}\n"
  },
  {
    "path": "packages/client/composables/useWakeLock.ts",
    "content": "import { useWakeLock as useVueUseWakeLock } from '@vueuse/core'\nimport { watch } from 'vue'\nimport { wakeLockEnabled } from '../state'\n\nexport function useWakeLock() {\n  const { request, release } = useVueUseWakeLock()\n\n  watch(\n    wakeLockEnabled,\n    (enabled) => {\n      if (enabled)\n        request('screen')\n      else\n        release()\n    },\n    { immediate: true },\n  )\n}\n"
  },
  {
    "path": "packages/client/constants.ts",
    "content": "import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'\nimport type { ComputedRef, InjectionKey, Ref, UnwrapNestedRefs } from 'vue'\nimport type { SlidevContext } from './modules/context'\n\n// Here we use string literal instead of symbols to make HMR more stable\n// The value of the injections keys are implementation details, you should always use them with the reference to the constant instead of the value\nexport const injectionClicksContext = '$$slidev-clicks-context' as unknown as InjectionKey<Ref<ClicksContext>>\nexport const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>\nexport const injectionSlideElement = '$$slidev-slide-element' as unknown as InjectionKey<Ref<HTMLElement | null>>\nexport const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>\nexport const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>\nexport const injectionRoute = '$$slidev-route' as unknown as InjectionKey<SlideRoute>\nexport const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey<Ref<RenderContext>>\nexport const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>\nexport const injectionSlideZoom = '$$slidev-slide-zoom' as unknown as InjectionKey<ComputedRef<number>>\n\nexport const CLASS_VCLICK_TARGET = 'slidev-vclick-target'\nexport const CLASS_VCLICK_HIDDEN = 'slidev-vclick-hidden'\nexport const CLASS_VCLICK_FADE = 'slidev-vclick-fade'\nexport const CLASS_VCLICK_GONE = 'slidev-vclick-gone'\nexport const CLASS_VCLICK_HIDDEN_EXP = 'slidev-vclick-hidden-explicitly'\nexport const CLASS_VCLICK_CURRENT = 'slidev-vclick-current'\nexport const CLASS_VCLICK_PRIOR = 'slidev-vclick-prior'\nexport const CLASS_VCLICK_DISPLAY_NONE = 'slidev-vclick-display-none'\n\nexport const CLICKS_MAX = 999999\n\nexport const TRUST_ORIGINS = [\n  'localhost',\n  '127.0.0.1',\n]\n\nexport const FRONTMATTER_FIELDS = [\n  'clicks',\n  'clicksStart',\n  'disabled',\n  'hide',\n  'hideInToc',\n  'layout',\n  'level',\n  'preload',\n  'routeAlias',\n  'src',\n  'title',\n  'transition',\n  'zoom',\n  'dragPos',\n  'lang',\n]\n\nexport const HEADMATTER_FIELDS = [\n  ...FRONTMATTER_FIELDS,\n  'theme',\n  'titleTemplate',\n  'info',\n  'author',\n  'keywords',\n  'presenter',\n  'browserExporter',\n  'download',\n  'exportFilename',\n  'export',\n  'highlighter',\n  'lineNumbers',\n  'monaco',\n  'monacoTypesSource',\n  'monacoTypesAdditionalPackages',\n  'monacoRunAdditionalDeps',\n  'monacoRunUseStrict',\n  'remoteAssets',\n  'selectable',\n  'record',\n  'colorSchema',\n  'routerMode',\n  'aspectRatio',\n  'canvasWidth',\n  'themeConfig',\n  'favicon',\n  'plantUmlServer',\n  'fonts',\n  'defaults',\n  'drawings',\n  'htmlAttrs',\n  'mdc',\n  'comark',\n  'contextMenu',\n  'wakeLock',\n  'seoMeta',\n  'notesAutoRuby',\n  'magicMoveDuration',\n  'preloadImages',\n]\n"
  },
  {
    "path": "packages/client/context.ts",
    "content": "import { injectLocal, objectOmit } from '@vueuse/core'\nimport { computed, ref, toRef } from 'vue'\nimport {\n  FRONTMATTER_FIELDS,\n  HEADMATTER_FIELDS,\n  injectionClicksContext,\n  injectionCurrentPage,\n  injectionFrontmatter,\n  injectionRenderContext,\n  injectionRoute,\n  injectionSlideScale,\n  injectionSlidevContext,\n  injectionSlideZoom,\n} from './constants'\n\n/**\n * Get the current slide context, should be called inside the setup function of a component inside slide\n */\nexport function useSlideContext() {\n  const $slidev = injectLocal(injectionSlidevContext)!\n  const $nav = toRef($slidev, 'nav')\n  const $clicksContext = injectLocal(injectionClicksContext)!.value\n  const $clicks = toRef($clicksContext, 'current')\n  const $page = injectLocal(injectionCurrentPage)!\n  const $renderContext = injectLocal(injectionRenderContext)!\n  const $frontmatter = injectLocal(injectionFrontmatter, {})\n  const $route = injectLocal(injectionRoute, undefined)\n  const $scale = injectLocal(injectionSlideScale, ref(1))\n  const $zoom = injectLocal(injectionSlideZoom, computed(() => 1))\n\n  return {\n    $slidev,\n    $nav,\n    $clicksContext,\n    $clicks,\n    $page,\n    $route,\n    $renderContext,\n    $frontmatter,\n    $scale,\n    $zoom,\n  }\n}\n\nexport type SlideContext = ReturnType<typeof useSlideContext>\n\n/**\n * Convert frontmatter options to props for v-bind\n * It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter\n *\n * @internal\n */\nexport function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {\n  return {\n    ...objectOmit(frontmatter, pageNo === 0 ? HEADMATTER_FIELDS : FRONTMATTER_FIELDS),\n    frontmatter,\n  }\n}\n"
  },
  {
    "path": "packages/client/env.ts",
    "content": "import configs from '#slidev/configs'\nimport { objectMap } from '@antfu/utils'\nimport { computed } from 'vue'\n\nexport { configs }\n\nexport const mode = __DEV__ ? 'dev' : 'build'\n\nexport const slideAspect = computed(() => configs.aspectRatio)\nexport const slideWidth = computed(() => configs.canvasWidth)\n\n// To honor the aspect ratio more as possible, we need to approximate the height to the next integer.\n// Doing this, we will prevent on print, to create an additional empty white page after each page.\nexport const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspect.value))\n\nexport const themeVars = computed(() => {\n  return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])\n})\n\nexport const slidesTitle = configs.slidesTitle\n\nexport const pathPrefix = import.meta.env.BASE_URL + (__SLIDEV_HASH_ROUTE__ ? '#/' : '')\n"
  },
  {
    "path": "packages/client/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"__ENTRY__\"></script>\n  <div id=\"mermaid-rendering-container\"></div>\n  <!-- body -->\n</body>\n</html>\n"
  },
  {
    "path": "packages/client/index.ts",
    "content": "/**\n * This file is the public APIs that you might use in your app.\n *\n * The other files despite they are accessable, are not meant to be used directly, breaking changes might happen.\n */\n\nexport { useDarkMode } from './composables/useDarkMode'\nexport { useDrawings } from './composables/useDrawings'\nexport { useNav } from './composables/useNav'\n\nexport { useSlideContext } from './context'\n\nexport * from './env'\nexport * from './layoutHelper'\n\nexport {\n  onSlideEnter,\n  onSlideLeave,\n  useIsSlideActive,\n} from './logic/slides'\n\nexport type { DrawingsState } from './state/drawings'\nexport { drawingState, onDrawingUpdate } from './state/drawings'\nexport type { SharedState } from './state/shared'\nexport { onSharedUpdate, sharedState } from './state/shared'\nexport { lockShortcuts } from './state/storage'\n\nexport {\n  addSyncMethod,\n  createSyncState,\n  disableBuiltinSync,\n} from './state/syncState'\n"
  },
  {
    "path": "packages/client/internals/Badge.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport {\n  getHashColorFromString,\n  getHsla,\n} from '../logic/color'\n\nconst props = withDefaults(\n  defineProps<{\n    text?: string\n    color?: boolean | number\n    as?: string\n    size?: string\n  }>(),\n  {\n    color: true,\n  },\n)\n\nconst style = computed(() => {\n  if (!props.text || props.color === false)\n    return {}\n  return {\n    color: typeof props.color === 'number'\n      ? getHsla(props.color)\n      : getHashColorFromString(props.text),\n    background: typeof props.color === 'number'\n      ? getHsla(props.color, 0.1)\n      : getHashColorFromString(props.text, 0.1),\n  }\n})\n\nconst sizeClasses = computed(() => {\n  switch (props.size || 'sm') {\n    case 'sm':\n      return 'px-1.5 text-11px leading-1.6em'\n  }\n  return ''\n})\n</script>\n\n<template>\n  <component :is=\"as || 'span'\" ws-nowrap rounded :class=\"sizeClasses\" :style>\n    <slot>\n      <span v-text=\"props.text\" />\n    </slot>\n  </component>\n</template>\n"
  },
  {
    "path": "packages/client/internals/ClicksSlider.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext } from '@slidev/types'\nimport { clamp, range } from '@antfu/utils'\nimport { computed } from 'vue'\nimport { CLICKS_MAX } from '../constants'\n\nconst props = withDefaults(defineProps<{\n  clicksContext: ClicksContext\n  readonly?: boolean\n  active?: boolean\n}>(), {\n  active: true,\n})\n\nconst total = computed(() => props.clicksContext.total)\nconst start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))\nconst length = computed(() => total.value - start.value + 1)\nconst current = computed({\n  get() {\n    return props.clicksContext.current > total.value ? -1 : props.clicksContext.current\n  },\n  set(value: number) {\n    // eslint-disable-next-line vue/no-mutating-props\n    props.clicksContext.current = value\n  },\n})\n\nconst clicksRange = computed(() => range(start.value, total.value + 1))\n\nfunction onMousedown() {\n  if (props.readonly)\n    return\n  if (current.value < 0 || current.value > total.value)\n    current.value = 0\n}\n</script>\n\n<template>\n  <div\n    class=\"flex gap-1 items-center select-none\"\n    :title=\"`Clicks in this slide: ${length}`\"\n    :class=\"length && props.clicksContext.isMounted ? '' : 'op50'\"\n  >\n    <div class=\"flex gap-0.2 items-center min-w-16 font-mono mr1\">\n      <div class=\"i-carbon:cursor-1 text-sm op50\" />\n      <template v-if=\"current >= 0 && current !== CLICKS_MAX && active\">\n        <div flex-auto />\n        <span text-primary>{{ current }}</span>\n        <span op25 text-sm>/</span>\n        <span op50 text-sm>{{ total }}</span>\n      </template>\n      <div\n        v-else\n        op50 flex-auto pl1\n      >\n        {{ total }}\n      </div>\n    </div>\n    <div\n      relative flex-auto h5 font-mono flex=\"~\"\n    >\n      <div\n        v-for=\"i of clicksRange\" :key=\"i\"\n        border=\"y main\" of-hidden relative\n        :class=\"[\n          i === 0 ? 'rounded-l border-l' : '',\n          i === total ? 'rounded-r border-r' : '',\n        ]\"\n        :style=\"{ width: length > 0 ? `${1 / length * 100}%` : '100%' }\"\n      >\n        <div\n          absolute inset-0\n          :class=\"(i <= current && active) ? 'bg-primary op15' : ''\"\n        />\n        <div\n          :class=\"[\n            (+i === +current && active) ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',\n            i === 0 ? 'rounded-l' : '',\n            i === total ? 'rounded-r' : 'border-r-2',\n          ]\"\n          w-full h-full text-xs flex items-center justify-center z-1\n        >\n          {{ i }}\n        </div>\n      </div>\n      <input\n        v-model=\"current\"\n        class=\"range\"\n        type=\"range\" :min=\"start\" :max=\"total\" :step=\"1\"\n        absolute inset-0 z-label op0\n        :class=\"readonly ? 'pointer-events-none' : ''\"\n        :style=\"{ '--thumb-width': `${1 / (length + 1) * 100}%` }\"\n        @mousedown=\"onMousedown\"\n        @focus=\"event => (event.currentTarget as HTMLElement)?.blur()\"\n      >\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.range {\n  -webkit-appearance: none;\n  appearance: none;\n  background: transparent;\n}\n.range::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  height: 100%;\n  width: var(--thumb-width, 0.5rem);\n}\n\n.range::-moz-range-thumb {\n  height: 100%;\n  width: var(--thumb-width, 0.5rem);\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/CodeRunner.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CodeRunnerOutputs, RawAtValue } from '@slidev/types'\nimport { debounce, toArray } from '@antfu/utils'\nimport { useVModel } from '@vueuse/core'\nimport { computed, onMounted, onUnmounted, ref, shallowRef, toValue, watch, watchSyncEffect } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useSlideContext } from '../context'\nimport { makeId } from '../logic/utils'\nimport setupCodeRunners from '../setup/code-runners'\nimport DomElement from './DomElement.vue'\nimport IconButton from './IconButton.vue'\n\nconst props = defineProps<{\n  modelValue: string\n  lang: string\n  autorun: boolean | 'once'\n  height?: string\n  showOutputAt?: RawAtValue\n  highlightOutput: boolean\n  runnerOptions?: Record<string, unknown>\n}>()\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst { isPrintMode } = useNav()\n\nconst code = useVModel(props, 'modelValue', emit)\n\nconst { $renderContext, $clicksContext } = useSlideContext()\nconst disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value))\n\nconst autorun = isPrintMode.value ? 'once' : props.autorun\nconst isRunning = ref(autorun)\nconst outputs = shallowRef<CodeRunnerOutputs>()\nconst runCount = ref(0)\nconst highlightFn = ref<(code: string, lang: string) => string>()\n\nconst hidden = ref(props.showOutputAt)\nif (props.showOutputAt) {\n  const id = makeId()\n  onMounted(() => {\n    const info = $clicksContext.calculate(props.showOutputAt)\n    if (info) {\n      $clicksContext.register(id, info)\n      watchSyncEffect(() => {\n        hidden.value = !info.isActive.value\n      })\n    }\n    else {\n      hidden.value = false\n    }\n  })\n  onUnmounted(() => $clicksContext.unregister(id))\n}\n\nconst triggerRun = debounce(200, async () => {\n  if (disabled.value)\n    return\n\n  const { highlight, run } = await setupCodeRunners()\n  highlightFn.value = highlight\n\n  const setAsRunning = setTimeout(() => {\n    isRunning.value = true\n  }, 500)\n\n  outputs.value = await run(code.value, props.lang, props.runnerOptions ?? {})\n  runCount.value += 1\n  isRunning.value = false\n\n  clearTimeout(setAsRunning)\n})\n\nif (autorun === 'once')\n  triggerRun()\nelse if (autorun)\n  watch(code, triggerRun, { immediate: true })\n</script>\n\n<template>\n  <div\n    v-show=\"!hidden\"\n    class=\"relative flex flex-col rounded-b border-t border-main\"\n    :style=\"{ height: props.height }\"\n    data-waitfor=\".slidev-runner-output\"\n  >\n    <div v-if=\"disabled\" class=\"text-sm text-center opacity-50\">\n      Code is disabled in the \"{{ $renderContext }}\" mode\n    </div>\n    <div v-else-if=\"isRunning\" class=\"text-sm text-center opacity-50\">\n      Running...\n    </div>\n    <div v-else-if=\"!outputs\" class=\"text-sm text-center opacity-50\">\n      Click the play button to run the code\n    </div>\n    <div v-else :key=\"`run-${runCount}`\" class=\"slidev-runner-output\">\n      <template v-for=\"output, _idx1 of toArray(toValue(outputs))\" :key=\"_idx1\">\n        <div v-if=\"'html' in output\" v-html=\"output.html\" />\n        <div v-else-if=\"'error' in output\" class=\"text-red-500\">\n          {{ output.error }}\n        </div>\n        <DomElement v-else-if=\"'element' in output\" :element=\"output.element\" />\n        <div v-else class=\"output-line\">\n          <template\n            v-for=\"item, idx2 in toArray(output)\"\n            :key=\"idx2\"\n          >\n            <span\n              v-if=\"item.highlightLang && highlightFn\"\n              class=\"highlighted\"\n              v-html=\"highlightFn(item.text, item.highlightLang)\"\n            />\n            <span v-else :class=\"item.class\">{{ item.text }}</span>\n            <span v-if=\"idx2 < toArray(output).length - 1\" class=\"separator\">,</span>\n          </template>\n        </div>\n      </template>\n    </div>\n  </div>\n  <div v-if=\"code.trim()\" class=\"absolute right-1 top-1 max-h-full flex gap-1\">\n    <IconButton class=\"w-8 h-8 max-h-full flex justify-center items-center\" title=\"Run code\" @click=\"triggerRun\">\n      <div class=\"i-carbon:play\" />\n    </IconButton>\n  </div>\n</template>\n\n<style lang=\"postcss\">\n.slidev-runner-output {\n  @apply px-5 py-3 flex-grow text-xs leading-[.8rem] font-$slidev-code-font-family select-text;\n}\n\n.slidev-runner-output .log-type {\n  @apply font-bold op-70;\n\n  &.DBG {\n    @apply text-gray-500;\n  }\n\n  &.LOG {\n    @apply text-blue-500;\n  }\n\n  &.WRN {\n    @apply text-orange-500;\n  }\n\n  &.ERR {\n    @apply text-red-500;\n  }\n}\n\n.slidev-runner-output .output-line {\n  @apply flex my-1 w-full;\n}\n\n.slidev-runner-output .separator {\n  @apply op-40 mr-1;\n}\n\n.slidev-runner-output .highlighted > pre {\n  @apply inline text-wrap !bg-transparent;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/ContextMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport { onClickOutside, useElementBounding, useEventListener, useWindowFocus } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport { useDynamicSlideInfo } from '../composables/useSlideInfo'\nimport { configs } from '../env'\nimport { closeContextMenu, currentContextMenu } from '../logic/contextMenu'\nimport { windowSize } from '../state'\n\nconst container = ref<HTMLElement>()\n\nonClickOutside(container, closeContextMenu)\nuseEventListener(document, 'mousedown', (ev) => {\n  if (ev.buttons & 2)\n    closeContextMenu()\n}, {\n  passive: true,\n  capture: true,\n})\n\nconst isExplicitEnabled = computed(() => configs.contextMenu != null)\n\nconst windowFocus = useWindowFocus()\nwatch(windowFocus, (hasFocus) => {\n  if (!hasFocus)\n    closeContextMenu()\n})\n\nconst firstSlide = useDynamicSlideInfo(1)\nfunction disableContextMenu() {\n  const info = firstSlide.info.value\n  if (!info)\n    return\n  firstSlide.update({\n    frontmatter: {\n      contextMenu: false,\n    },\n  })\n}\n\nconst { width, height } = useElementBounding(container)\nconst left = computed(() => {\n  const x = currentContextMenu.value?.x\n  if (!x)\n    return 0\n  if (x + width.value > windowSize.width.value)\n    return windowSize.width.value - width.value\n  return x\n})\nconst top = computed(() => {\n  const y = currentContextMenu.value?.y\n  if (!y)\n    return 0\n  if (y + height.value > windowSize.height.value)\n    return windowSize.height.value - height.value\n  return y\n})\n</script>\n\n<template>\n  <div\n    v-if=\"currentContextMenu\"\n    ref=\"container\"\n    :style=\"`left:${left}px;top:${top}px`\"\n    class=\"slidev-glass-effect fixed z-context-menu w-60 flex flex-wrap justify-items-start p-1 animate-fade-in animate-duration-100 rounded-md shadow overflow-hidden select-none\"\n    @contextmenu.prevent=\"\"\n    @click=\"closeContextMenu\"\n  >\n    <template v-for=\"item, index of currentContextMenu.items.value\" :key=\"index\">\n      <div v-if=\"item === 'separator'\" :key=\"index\" class=\"w-full my1 border-t border-main\" />\n      <template v-else-if=\"item.show ?? true\">\n        <div\n          v-if=\"item.small\"\n          class=\"p-2 w-[40px] h-[40px] inline-block text-center cursor-pointer rounded flex\"\n          :class=\"item.disabled ? `op40` : `hover:bg-active`\"\n          :title=\"(item.label as string)\"\n          @click=\"item.action\"\n        >\n          <div v-if=\"typeof item.icon === 'string'\" :class=\"item.icon\" class=\"text-1.2em ma\" />\n          <component :is=\"item.icon\" v-else />\n        </div>\n        <div\n          v-else\n          class=\"w-full grid grid-cols-[35px_1fr] p-2 pl-0 cursor-pointer rounded\"\n          :class=\"item.disabled ? `op40` : `hover:bg-active`\"\n          @click=\"item.action\"\n        >\n          <div class=\"mx-auto flex\">\n            <div v-if=\"typeof item.icon === 'string'\" :class=\"item.icon\" class=\"text-1.2em ma\" />\n            <component :is=\"item.icon\" v-else />\n          </div>\n          <div v-if=\"typeof item.label === 'string'\">\n            {{ item.label }}\n          </div>\n          <component :is=\"item.label\" v-else />\n        </div>\n      </template>\n    </template>\n    <template v-if=\"!isExplicitEnabled\">\n      <div class=\"w-full my1 border-t border-main\" />\n      <div class=\"w-full text-xs p2\">\n        <div class=\"text-main text-opacity-50!\">\n          Hold <kbd class=\"border px1 py0.5 border-main rounded text-primary\">Shift</kbd> and right click to open the native context menu\n          <button\n            v-if=\"__DEV__\"\n            class=\"underline op50 hover:op100 mt1 block\"\n            @click=\"disableContextMenu()\"\n          >\n            Disable custom context menu\n          </button>\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/Controls.vue",
    "content": "<script setup lang=\"ts\">\nimport { shallowRef } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { configs } from '../env'\nimport { showInfoDialog, showRecordingDialog } from '../state'\nimport ContextMenu from './ContextMenu.vue'\nimport Goto from './Goto.vue'\nimport InfoDialog from './InfoDialog.vue'\nimport QuickOverview from './QuickOverview.vue'\n\nconst { isEmbedded } = useNav()\nconst drawingEnabled = __SLIDEV_FEATURE_DRAWINGS__ && !configs.drawings.presenterOnly && !isEmbedded.value\nconst DrawingControls = shallowRef<any>()\nif (drawingEnabled)\n  import('../internals/DrawingControls.vue').then(v => DrawingControls.value = v.default)\n\nconst WebCamera = shallowRef<any>()\nconst RecordingDialog = shallowRef<any>()\nif (__SLIDEV_FEATURE_RECORD__) {\n  import('./WebCamera.vue').then(v => WebCamera.value = v.default)\n  import('./RecordingDialog.vue').then(v => RecordingDialog.value = v.default)\n}\n</script>\n\n<template>\n  <DrawingControls v-if=\"DrawingControls\" />\n  <QuickOverview />\n  <Goto />\n  <WebCamera v-if=\"WebCamera\" />\n  <RecordingDialog v-if=\"RecordingDialog\" v-model=\"showRecordingDialog\" />\n  <InfoDialog v-if=\"configs.info\" v-model=\"showInfoDialog\" />\n  <ContextMenu />\n</template>\n"
  },
  {
    "path": "packages/client/internals/CurrentProgressBar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext } from '@slidev/types'\nimport { computed } from 'vue'\nimport { useNav } from '../composables/useNav'\n\nconst props = defineProps<{\n  clicksContext?: ClicksContext\n  current?: number\n}>()\n\nconst nav = useNav()\nconst clicksContext = computed(() => props.clicksContext ?? nav.clicksContext.value)\nconst current = computed(() => props.current ?? nav.currentSlideNo.value)\nconst { total } = nav\n</script>\n\n<template>\n  <div class=\"relative flex gap-px\">\n    <div\n      v-for=\"i of total - 1\"\n      :key=\"i\" class=\"border-x border-b border-main h-4px transition-all\"\n      :style=\"{ width: `${(1 / (total - 1) * 100)}%` }\"\n      :class=\"i < current ? 'bg-primary border-primary' : ''\"\n    >\n      <Transition name=\"fade\">\n        <div\n          v-if=\"i === current\"\n          class=\"h-full bg-primary op75 transition-all\"\n          :style=\"{ width: `${clicksContext.total === 0 ? 0 : clicksContext.current / (clicksContext.total + 1) * 100}%` }\"\n        />\n      </Transition>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/DevicesSelectors.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectionItem } from './types'\nimport { computed } from 'vue'\nimport {\n  cameras,\n  ensureDevicesListPermissions,\n  microphones,\n  mimeExtMap,\n  mimeType,\n  supportedMimeTypes,\n} from '../logic/recording'\nimport { currentCamera, currentMic } from '../state'\nimport SelectList from './SelectList.vue'\n\nconst camerasItems = computed<SelectionItem<string>[]>(() => [\n  {\n    value: 'none',\n    display: 'None',\n  },\n  ...cameras.value.map(i => ({\n    value: i.deviceId,\n    display: i.label,\n  })),\n])\n\nconst microphonesItems = computed<SelectionItem<string>[]>(() => [\n  {\n    value: 'none',\n    display: 'None',\n  },\n  ...microphones.value.map(i => ({\n    value: i.deviceId,\n    display: i.label,\n  })),\n])\n\nconst mimeTypeItems = supportedMimeTypes.map(mime => ({\n  value: mime,\n  display: mimeExtMap[mime].toUpperCase(),\n}))\n\nensureDevicesListPermissions()\n</script>\n\n<template>\n  <div text-sm flex=\"~ col gap-2\">\n    <SelectList\n      v-model=\"currentCamera\"\n      title=\"Camera\"\n      :items=\"camerasItems\"\n    />\n    <div class=\"h-1px opacity-10 bg-current w-full\" />\n    <SelectList\n      v-model=\"currentMic\"\n      title=\"Microphone\"\n      :items=\"microphonesItems\"\n    />\n    <div class=\"h-1px opacity-10 bg-current w-full\" />\n    <SelectList\n      v-if=\"mimeTypeItems.length\"\n      v-model=\"mimeType\"\n      title=\"Video Format\"\n      :items=\"mimeTypeItems\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/DomElement.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, watchEffect } from 'vue'\n\nconst props = defineProps<{\n  element: HTMLElement\n}>()\n\nconst container = ref<HTMLElement>()\n\nwatchEffect(() => {\n  if (container.value)\n    container.value.appendChild(props.element)\n})\n</script>\n\n<template>\n  <div ref=\"container\" />\n</template>\n"
  },
  {
    "path": "packages/client/internals/DragControl.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Pausable } from '@vueuse/core'\nimport type { DragElementState } from '../composables/useDragElements'\nimport { clamp } from '@antfu/utils'\nimport { useIntervalFn } from '@vueuse/core'\nimport { computed, inject, ref, watchEffect } from 'vue'\nimport { useSlideBounds } from '../composables/useSlideBounds'\nimport { injectionSlideScale } from '../constants'\nimport { slideHeight, slideWidth } from '../env'\nimport { magicKeys } from '../state'\n\nconst { data } = defineProps<{ data: DragElementState }>()\nconst { dragId, zoom, autoHeight, x0, y0, width, height, rotate, isArrow } = data\n\nconst slideScale = inject(injectionSlideScale, ref(1))\nconst scale = computed(() => slideScale.value * zoom.value)\nconst { left: slideLeft, top: slideTop } = useSlideBounds()\n\nconst ctrlSize = 10\nconst minSize = isArrow ? Number.NEGATIVE_INFINITY : 40\nconst minRemain = 10\n\nconst rotateRad = computed(() => rotate.value * Math.PI / 180)\nconst rotateSin = computed(() => Math.sin(rotateRad.value))\nconst rotateCos = computed(() => Math.cos(rotateRad.value))\n\nconst boundingWidth = computed(() => width.value * rotateCos.value + height.value * rotateSin.value)\nconst boundingHeight = computed(() => width.value * rotateSin.value + height.value * rotateCos.value)\n\nconst boundingLeft = computed(() => x0.value - boundingWidth.value / 2)\nconst boundingTop = computed(() => y0.value - boundingHeight.value / 2)\nconst boundingRight = computed(() => x0.value + boundingWidth.value / 2)\nconst boundingBottom = computed(() => y0.value + boundingHeight.value / 2)\n\nconst arrowRevX = computed(() => isArrow && width.value < 0)\nconst arrowRevY = computed(() => isArrow && height.value < 0)\n\nlet currentDrag: {\n  x0: number\n  y0: number\n  width: number\n  height: number\n  rotate: number\n  dx0: number\n  dy0: number\n  ltx: number\n  lty: number\n  rtx: number\n  rty: number\n  lbx: number\n  lby: number\n  rbx: number\n  rby: number\n} | null = null\n\nfunction onPointerdown(ev: PointerEvent) {\n  if (ev.buttons !== 1)\n    return\n\n  ev.preventDefault()\n  ev.stopPropagation()\n  const el = ev.target as HTMLElement\n  const elBounds = el.getBoundingClientRect()\n\n  const cross1x = width.value * rotateCos.value - height.value * rotateSin.value\n  const cross1y = width.value * rotateSin.value + height.value * rotateCos.value\n  const cross2x = width.value * rotateCos.value + height.value * rotateSin.value\n  const cross2y = -width.value * rotateSin.value + height.value * rotateCos.value\n\n  currentDrag = {\n    x0: x0.value,\n    y0: y0.value,\n    width: width.value,\n    height: height.value,\n    rotate: rotate.value,\n    dx0: ev.clientX - (elBounds.left + elBounds.right) / 2,\n    dy0: ev.clientY - (elBounds.top + elBounds.bottom) / 2,\n    ltx: x0.value - cross1x / 2,\n    lty: y0.value - cross1y / 2,\n    rtx: x0.value + cross2x / 2,\n    rty: y0.value - cross2y / 2,\n    lbx: x0.value - cross2x / 2,\n    lby: y0.value + cross2y / 2,\n    rbx: x0.value + cross1x / 2,\n    rby: y0.value + cross1y / 2,\n  };\n\n  (ev.currentTarget as HTMLElement).setPointerCapture(ev.pointerId)\n}\n\nfunction onPointermove(ev: PointerEvent) {\n  if (!currentDrag || ev.buttons !== 1)\n    return\n\n  ev.preventDefault()\n  ev.stopPropagation()\n\n  const x = (ev.clientX - slideLeft.value - currentDrag.dx0) / scale.value\n  const y = (ev.clientY - slideTop.value - currentDrag.dy0) / scale.value\n\n  x0.value = clamp(x, -boundingWidth.value / 2 + minRemain, slideWidth.value + boundingWidth.value / 2 - minRemain)\n  y0.value = clamp(y, -boundingHeight.value / 2 + minRemain, slideHeight.value + boundingHeight.value / 2 - minRemain)\n}\n\nfunction onPointerup(ev: PointerEvent) {\n  if (!currentDrag)\n    return\n\n  ev.preventDefault()\n  ev.stopPropagation()\n\n  currentDrag = null\n}\n\nconst ctrlClasses = `absolute border border-gray bg-gray dark:border-gray-500 dark:bg-gray-800 bg-opacity-30 `\n\nfunction getCornerProps(isLeft: boolean, isTop: boolean) {\n  return {\n    onPointerdown,\n    onPointermove: (ev: PointerEvent) => {\n      if (!currentDrag || ev.buttons !== 1)\n        return\n\n      ev.preventDefault()\n      ev.stopPropagation()\n\n      let x = (ev.clientX - slideLeft.value) / scale.value\n      let y = (ev.clientY - slideTop.value) / scale.value\n\n      const { ltx, lty, rtx, rty, lbx, lby, rbx, rby } = currentDrag\n\n      const ratio = currentDrag.width / currentDrag.height\n      const wMin = Math.max(minSize, minSize * ratio)\n      function getSize(w1: number, h1: number) {\n        if (ev.shiftKey) {\n          const w = Math.max(w1, h1 * ratio, wMin)\n          const h = w / ratio\n          return { w, h }\n        }\n        else {\n          return { w: Math.max(w1, minSize), h: Math.max(h1, minSize) }\n        }\n      }\n\n      if (isLeft) {\n        if (isTop) {\n          const w1 = (rbx - x) * rotateCos.value + (rby - y) * rotateSin.value\n          const h1 = -(rbx - x) * rotateSin.value + (rby - y) * rotateCos.value\n          const { w, h } = getSize(w1, h1)\n          x = rbx - w * rotateCos.value + h * rotateSin.value\n          y = rby - w * rotateSin.value - h * rotateCos.value\n        }\n        else {\n          const w1 = (rtx - x) * rotateCos.value - (y - rty) * rotateSin.value\n          const h1 = (rtx - x) * rotateSin.value + (y - rty) * rotateCos.value\n          const { w, h } = getSize(w1, h1)\n          x = rtx - w * rotateCos.value - h * rotateSin.value\n          y = rty - w * rotateSin.value + h * rotateCos.value\n        }\n      }\n      else {\n        if (isTop) {\n          const w1 = (x - lbx) * rotateCos.value - (lby - y) * rotateSin.value\n          const h1 = (x - lbx) * rotateSin.value + (lby - y) * rotateCos.value\n          const { w, h } = getSize(w1, h1)\n          x = lbx + w * rotateCos.value + h * rotateSin.value\n          y = lby + w * rotateSin.value - h * rotateCos.value\n        }\n        else {\n          const w1 = (x - ltx) * rotateCos.value + (y - lty) * rotateSin.value\n          const h1 = -(x - ltx) * rotateSin.value + (y - lty) * rotateCos.value\n          const { w, h } = getSize(w1, h1)\n          x = ltx + w * rotateCos.value - h * rotateSin.value\n          y = lty + w * rotateSin.value + h * rotateCos.value\n        }\n      }\n\n      if (isLeft) {\n        if (isTop) {\n          x0.value = (x + rbx) / 2\n          y0.value = (y + rby) / 2\n          width.value = (rbx - x) * rotateCos.value + (rby - y) * rotateSin.value\n          height.value = -(rbx - x) * rotateSin.value + (rby - y) * rotateCos.value\n        }\n        else {\n          x0.value = (x + rtx) / 2\n          y0.value = (y + rty) / 2\n          width.value = (rtx - x) * rotateCos.value - (y - rty) * rotateSin.value\n          height.value = (rtx - x) * rotateSin.value + (y - rty) * rotateCos.value\n        }\n      }\n      else {\n        if (isTop) {\n          x0.value = (x + lbx) / 2\n          y0.value = (y + lby) / 2\n          width.value = (x - lbx) * rotateCos.value - (lby - y) * rotateSin.value\n          height.value = (x - lbx) * rotateSin.value + (lby - y) * rotateCos.value\n        }\n        else {\n          x0.value = (x + ltx) / 2\n          y0.value = (y + lty) / 2\n          width.value = (x - ltx) * rotateCos.value + (y - lty) * rotateSin.value\n          height.value = -(x - ltx) * rotateSin.value + (y - lty) * rotateCos.value\n        }\n      }\n    },\n    onPointerup,\n    style: {\n      width: `${ctrlSize}px`,\n      height: `${ctrlSize}px`,\n      margin: `-${ctrlSize / 2}px`,\n      left: isLeft !== arrowRevX.value ? '0' : undefined,\n      right: isLeft !== arrowRevX.value ? undefined : '0',\n      top: isTop !== arrowRevY.value ? '0' : undefined,\n      bottom: isTop !== arrowRevY.value ? undefined : '0',\n      cursor: isArrow ? 'move' : +isLeft + +isTop === 1 ? 'nesw-resize' : 'nwse-resize',\n      borderRadius: isArrow ? '50%' : undefined,\n    },\n    class: ctrlClasses,\n  }\n}\n\nfunction getBorderProps(dir: 'l' | 'r' | 't' | 'b') {\n  return {\n    onPointerdown,\n    onPointermove: (ev: PointerEvent) => {\n      if (!currentDrag || ev.buttons !== 1)\n        return\n\n      ev.preventDefault()\n      ev.stopPropagation()\n\n      const x = (ev.clientX - slideLeft.value) / scale.value\n      const y = (ev.clientY - slideTop.value) / scale.value\n\n      const { ltx, lty, rtx, rty, lbx, lby, rbx, rby } = currentDrag\n\n      if (dir === 'l') {\n        const rx = (rtx + rbx) / 2\n        const ry = (rty + rby) / 2\n        width.value = Math.max((rx - x) * rotateCos.value + (ry - y) * rotateSin.value, minSize)\n        x0.value = rx - width.value * rotateCos.value / 2\n        y0.value = ry - width.value * rotateSin.value / 2\n      }\n      else if (dir === 'r') {\n        const lx = (ltx + lbx) / 2\n        const ly = (lty + lby) / 2\n        width.value = Math.max((x - lx) * rotateCos.value + (y - ly) * rotateSin.value, minSize)\n        x0.value = lx + width.value * rotateCos.value / 2\n        y0.value = ly + width.value * rotateSin.value / 2\n      }\n      else if (dir === 't') {\n        const bx = (lbx + rbx) / 2\n        const by = (lby + rby) / 2\n        height.value = Math.max((by - y) * rotateCos.value - (bx - x) * rotateSin.value, minSize)\n        x0.value = bx + height.value * rotateSin.value / 2\n        y0.value = by - height.value * rotateCos.value / 2\n      }\n      else if (dir === 'b') {\n        const tx = (ltx + rtx) / 2\n        const ty = (lty + rty) / 2\n        height.value = Math.max((y - ty) * rotateCos.value - (x - tx) * rotateSin.value, minSize)\n        x0.value = tx - height.value * rotateSin.value / 2\n        y0.value = ty + height.value * rotateCos.value / 2\n      }\n    },\n    onPointerup,\n    style: {\n      width: `${ctrlSize}px`,\n      height: `${ctrlSize}px`,\n      margin: `-${ctrlSize / 2}px`,\n      left: dir === 'l' ? '0' : dir === 'r' ? `100%` : `50%`,\n      top: dir === 't' ? '0' : dir === 'b' ? `100%` : `50%`,\n      cursor: 'lr'.includes(dir) ? 'ew-resize' : 'ns-resize',\n      borderRadius: '50%',\n    },\n    class: ctrlClasses,\n  }\n}\n\nfunction getRotateProps() {\n  return {\n    onPointerdown,\n    onPointermove: (ev: PointerEvent) => {\n      if (!currentDrag || ev.buttons !== 1)\n        return\n\n      ev.preventDefault()\n      ev.stopPropagation()\n\n      const x = (ev.clientX - slideLeft.value - currentDrag.dx0) / scale.value - ctrlSize / 4\n      const y = (ev.clientY - slideTop.value - currentDrag.dy0) / scale.value - ctrlSize / 4\n\n      let angle = Math.atan2(y - y0.value, x - x0.value) * 180 / Math.PI + 90\n\n      const commonAngles = [0, 90, 180, 270, 360]\n      for (const a of commonAngles) {\n        if (Math.abs(angle - a) < 5) {\n          angle = a % 360\n          break\n        }\n      }\n\n      rotate.value = angle\n    },\n    onPointerup,\n    style: {\n      width: `${ctrlSize}px`,\n      height: `${ctrlSize}px`,\n      margin: `-${ctrlSize / 2}px`,\n      left: '50%',\n      top: '-20px',\n      cursor: 'grab',\n      borderRadius: '50%',\n    },\n    class: ctrlClasses,\n  }\n}\n\nconst moveInterval = 20\nconst intervalFnOptions = {\n  immediate: false,\n  immediateCallback: false,\n}\nconst moveLeft = useIntervalFn(() => {\n  if (boundingRight.value <= minRemain)\n    return\n  x0.value--\n}, moveInterval, intervalFnOptions)\nconst moveRight = useIntervalFn(() => {\n  if (boundingLeft.value >= slideWidth.value - minRemain)\n    return\n  x0.value++\n}, moveInterval, intervalFnOptions)\nconst moveUp = useIntervalFn(() => {\n  if (boundingBottom.value <= minRemain)\n    return\n  y0.value--\n}, moveInterval, intervalFnOptions)\nconst moveDown = useIntervalFn(() => {\n  if (boundingTop.value >= slideHeight.value - minRemain)\n    return\n  y0.value++\n}, moveInterval, intervalFnOptions)\n\nwatchEffect(() => {\n  function shortcut(key: string, fn: Pausable) {\n    if (magicKeys[key].value)\n      fn.resume()\n    else fn.pause()\n  }\n  shortcut('left', moveLeft)\n  shortcut('right', moveRight)\n  shortcut('up', moveUp)\n  shortcut('down', moveDown)\n})\n</script>\n\n<template>\n  <div\n    v-if=\"Number.isFinite(x0)\"\n    id=\"drag-control-container\"\n    :data-drag-id=\"dragId\"\n    :style=\"{\n      position: 'absolute',\n      zIndex: 100,\n      left: `${zoom * (x0 - Math.abs(width) / 2)}px`,\n      top: `${zoom * (y0 - Math.abs(height) / 2)}px`,\n      width: `${zoom * Math.abs(width)}px`,\n      height: `${zoom * Math.abs(height)}px`,\n      transformOrigin: 'center center',\n      transform: `rotate(${rotate}deg)`,\n    }\"\n    @pointerdown=\"onPointerdown\"\n    @pointermove=\"onPointermove\"\n    @pointerup=\"onPointerup\"\n  >\n    <div class=\"absolute inset-0 z-nav dark:b-gray-400\" :class=\"isArrow ? '' : 'b b-dark'\">\n      <template v-if=\"!autoHeight\">\n        <div v-bind=\"getCornerProps(true, true)\" />\n        <div v-bind=\"getCornerProps(false, false)\" />\n        <template v-if=\"!isArrow\">\n          <div v-bind=\"getCornerProps(true, false)\" />\n          <div v-bind=\"getCornerProps(false, true)\" />\n        </template>\n      </template>\n      <template v-if=\"!isArrow\">\n        <div v-bind=\"getBorderProps('l')\" />\n        <div v-bind=\"getBorderProps('r')\" />\n        <template v-if=\"!autoHeight\">\n          <div v-bind=\"getBorderProps('t')\" />\n          <div v-bind=\"getBorderProps('b')\" />\n        </template>\n        <div v-bind=\"getRotateProps()\" />\n        <div\n          class=\"absolute -top-15px w-0 b b-dashed b-dark dark:b-gray-400\"\n          :style=\"{\n            left: 'calc(50% - 1px)',\n            height: autoHeight ? '14px' : '10px',\n          }\"\n        />\n      </template>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/Draggable.vue",
    "content": "<script setup lang=\"ts\">\nimport { useDraggable, useLocalStorage } from '@vueuse/core'\nimport { ref } from 'vue'\n\nconst props = defineProps<{\n  storageKey?: string\n  initial?: { x: number, y: number }\n}>()\n\nconst el = ref<HTMLElement | null>(null)\nconst initial = props.initial ?? { x: 0, y: 0 }\nconst point = props.storageKey\n  ? useLocalStorage(props.storageKey, initial)\n  : ref(initial)\nconst { style } = useDraggable(el, { initialValue: point })\n</script>\n\n<template>\n  <div ref=\"el\" class=\"fixed\" :style=\"style\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/DrawingControls.vue",
    "content": "<script setup lang=\"ts\">\r\nimport { Menu } from 'floating-vue'\r\nimport { useDrawings } from '../composables/useDrawings'\r\nimport Draggable from './Draggable.vue'\r\nimport IconButton from './IconButton.vue'\r\nimport VerticalDivider from './VerticalDivider.vue'\r\n\r\nconst {\r\n  brush,\r\n  canClear,\r\n  canRedo,\r\n  canUndo,\r\n  clear,\r\n  drauu,\r\n  drawingEnabled,\r\n  drawingMode,\r\n  drawingPinned,\r\n  brushColors,\r\n} = useDrawings()\r\n\r\nfunction undo() {\r\n  drauu.undo()\r\n}\r\nfunction redo() {\r\n  drauu.redo()\r\n}\r\n\r\nlet lastDrawingMode: typeof drawingMode.value = 'stylus'\r\nfunction setDrawingMode(mode: typeof drawingMode.value) {\r\n  drawingMode.value = mode\r\n  drawingEnabled.value = true\r\n  if (mode !== 'eraseLine')\r\n    lastDrawingMode = mode\r\n}\r\nfunction setBrushColor(color: typeof brush.color) {\r\n  brush.color = color\r\n  drawingEnabled.value = true\r\n  drawingMode.value = lastDrawingMode\r\n}\r\n</script>\r\n\r\n<template>\r\n  <Draggable\r\n    v-if=\"drawingEnabled || drawingPinned\"\r\n    class=\"flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-nav  border border-main\"\r\n    :class=\"!drawingEnabled && drawingPinned ? 'opacity-40 hover:opacity-90' : ''\"\r\n    storage-key=\"slidev-drawing-pos\"\r\n    :initial-x=\"10\"\r\n    :initial-y=\"10\"\r\n  >\r\n    <IconButton title=\"Draw with stylus\" :class=\"{ shallow: drawingMode !== 'stylus' }\" @click=\"setDrawingMode('stylus')\">\r\n      <div class=\"i-carbon:pen\" />\r\n    </IconButton>\r\n    <IconButton title=\"Draw a line\" :class=\"{ shallow: drawingMode !== 'line' }\" @click=\"setDrawingMode('line')\">\r\n      <svg width=\"1em\" height=\"1em\" class=\"-mt-0.5\" preserveAspectRatio=\"xMidYMid meet\" viewBox=\"0 0 24 24\">\r\n        <path d=\"M21.71 3.29a1 1 0 0 0-1.42 0l-18 18a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l18-18a1 1 0 0 0 0-1.42z\" fill=\"currentColor\" />\r\n      </svg>\r\n    </IconButton>\r\n    <IconButton title=\"Draw an arrow\" :class=\"{ shallow: drawingMode !== 'arrow' }\" @click=\"setDrawingMode('arrow')\">\r\n      <div class=\"i-carbon:arrow-up-right\" />\r\n    </IconButton>\r\n    <IconButton title=\"Draw an ellipse\" :class=\"{ shallow: drawingMode !== 'ellipse' }\" @click=\"setDrawingMode('ellipse')\">\r\n      <div class=\"i-carbon:radio-button\" />\r\n    </IconButton>\r\n    <IconButton title=\"Draw a rectangle\" :class=\"{ shallow: drawingMode !== 'rectangle' }\" @click=\"setDrawingMode('rectangle')\">\r\n      <div class=\"i-carbon:checkbox\" />\r\n    </IconButton>\r\n    <IconButton title=\"Erase\" :class=\"{ shallow: drawingMode !== 'eraseLine' }\" @click=\"setDrawingMode('eraseLine')\">\r\n      <div class=\"i-carbon:erase\" />\r\n    </IconButton>\r\n\r\n    <VerticalDivider />\r\n\r\n    <Menu>\r\n      <IconButton title=\"Adjust stroke width\" :class=\"{ shallow: drawingMode === 'eraseLine' }\">\r\n        <svg viewBox=\"0 0 32 32\" width=\"1.2em\" height=\"1.2em\">\r\n          <line x1=\"2\" y1=\"15\" x2=\"22\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1\" stroke-linecap=\"round\" />\r\n          <line x1=\"2\" y1=\"24\" x2=\"28\" y2=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\r\n          <line x1=\"7\" y1=\"31\" x2=\"29\" y2=\"19\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\r\n        </svg>\r\n      </IconButton>\r\n      <template #popper>\r\n        <div class=\"flex bg-main p-2\">\r\n          <div class=\"inline-block w-7 text-center\">\r\n            {{ brush.size }}\r\n          </div>\r\n          <div class=\"pt-.5\">\r\n            <input v-model=\"brush.size\" type=\"range\" min=\"1\" max=\"15\" @change=\"drawingMode = lastDrawingMode\">\r\n          </div>\r\n        </div>\r\n      </template>\r\n    </Menu>\r\n    <IconButton\r\n      v-for=\"color of brushColors\"\r\n      :key=\"color\"\r\n      title=\"Set brush color\"\r\n      :class=\"brush.color === color && drawingMode !== 'eraseLine' ? 'active' : 'shallow'\"\r\n      @click=\"setBrushColor(color)\"\r\n    >\r\n      <div\r\n        class=\"w-6 h-6 transition-all transform border\"\r\n        :class=\"brush.color !== color ? 'rounded-1/2 scale-85 border-white' : 'rounded-md border-gray-300/50'\"\r\n        :style=\"drawingEnabled ? { background: color } : { borderColor: color }\"\r\n      />\r\n    </IconButton>\r\n\r\n    <VerticalDivider />\r\n\r\n    <IconButton title=\"Undo\" :class=\"{ disabled: !canUndo }\" @click=\"undo()\">\r\n      <div class=\"i-carbon:undo\" />\r\n    </IconButton>\r\n    <IconButton title=\"Redo\" :class=\"{ disabled: !canRedo }\" @click=\"redo()\">\r\n      <div class=\"i-carbon:redo\" />\r\n    </IconButton>\r\n    <IconButton title=\"Delete\" :class=\"{ disabled: !canClear }\" @click=\"clear()\">\r\n      <div class=\"i-carbon:trash-can\" />\r\n    </IconButton>\r\n\r\n    <VerticalDivider />\r\n    <IconButton :title=\"drawingPinned ? 'Unpin drawing' : 'Pin drawing'\" :class=\"{ shallow: !drawingPinned }\" @click=\"drawingPinned = !drawingPinned\">\r\n      <div v-show=\"drawingPinned\" class=\"i-carbon:pin-filled transform -rotate-45\" />\r\n      <div v-show=\"!drawingPinned\" class=\"i-carbon:pin\" />\r\n    </IconButton>\r\n    <IconButton\r\n      v-if=\"drawingEnabled\"\r\n      :title=\"drawingPinned ? 'Drawing pinned' : 'Drawing unpinned'\"\r\n      :class=\"{ shallow: !drawingEnabled }\"\r\n      @click=\"drawingEnabled = !drawingEnabled\"\r\n    >\r\n      <div v-show=\"drawingPinned\" class=\"i-carbon:error\" />\r\n      <div v-show=\"!drawingPinned\" class=\"i-carbon:close-outline\" />\r\n    </IconButton>\r\n  </Draggable>\r\n</template>\r\n\r\n<style>\r\n.v-popper--theme-menu .v-popper__arrow-inner {\r\n  --uno: border-main;\r\n}\r\n</style>\r\n"
  },
  {
    "path": "packages/client/internals/DrawingLayer.vue",
    "content": "<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useSlideContext } from '../context'\n\nconst { drauu, drawingEnabled, loadCanvas } = useDrawings()\n\nconst scale = useSlideContext().$scale\nconst svg = ref<SVGSVGElement>()\n\nonMounted(() => {\n  drauu.mount(svg.value!, svg.value!.parentElement!)\n  watch(scale, scale => drauu.options.coordinateScale = 1 / scale, { immediate: true })\n  loadCanvas()\n})\n\nonBeforeUnmount(() => {\n  drauu.unmount()\n})\n</script>\n\n<template>\n  <svg\n    ref=\"svg\"\n    class=\"w-full h-full absolute top-0\"\n    :class=\"{ 'pointer-events-none': !drawingEnabled, 'touch-none': drawingEnabled }\"\n  />\n</template>\n"
  },
  {
    "path": "packages/client/internals/DrawingPreview.vue",
    "content": "<script setup lang=\"ts\">\nimport { useDrawings } from '../composables/useDrawings'\n\ndefineProps<{ page: number }>()\n\nconst { drawingState } = useDrawings()\n</script>\n\n<template>\n  <svg\n    v-if=\"drawingState[page]\"\n    class=\"w-full h-full absolute top-0 pointer-events-none\"\n    v-html=\"drawingState[page]\"\n  />\n</template>\n"
  },
  {
    "path": "packages/client/internals/ExportPdfTip.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport { skipExportPdfTip } from '../state'\nimport Modal from './Modal.vue'\n\nconst props = defineProps({\n  modelValue: {\n    default: false,\n  },\n})\n\nconst emit = defineEmits(['update:modelValue', 'print'])\nconst value = useVModel(props, 'modelValue', emit)\n\nfunction print() {\n  value.value = false\n  emit('print')\n}\n</script>\n\n<template>\n  <Modal v-model=\"value\" class=\"px-6 py-4 flex flex-col gap-2\">\n    <div class=\"flex gap-2 text-xl\">\n      <div class=\"i-carbon:information my-auto\" /> Tips\n    </div>\n    <div>\n      Slidev will open your browser's built-in print dialog to export the slides as PDF. <br>\n      In the print dialog, please:\n      <ul class=\"list-disc my-4 pl-4\">\n        <li>\n          Choose \"Save as PDF\" as the Destination.\n          <span class=\"op-70 text-xs\"> (Not \"Microsoft Print to PDF\") </span>\n        </li>\n        <li> Choose \"Default\" as the Margin. </li>\n        <li> Toggle on \"Print backgrounds\". </li>\n      </ul>\n      <div class=\"mb-2 op-70 text-sm\">\n        If you're encountering problems, please try\n        <a href=\"https://sli.dev/builtin/cli#export\"> the CLI </a>\n        or\n        <a href=\"https://github.com/slidevjs/slidev/issues/new\"> open an issue</a>.\n      </div>\n      <div class=\"form-check op-70\">\n        <input\n          v-model=\"skipExportPdfTip\"\n          name=\"record-camera\"\n          type=\"checkbox\"\n        >\n        <label for=\"record-camera\" @click=\"skipExportPdfTip = !skipExportPdfTip\">Don't show this dialog next time.</label>\n      </div>\n    </div>\n    <div class=\"flex my-1\">\n      <button class=\"cancel\" @click=\"value = false\">\n        Cancel\n      </button>\n      <div class=\"flex-auto\" />\n      <button @click=\"print\">\n        Start\n      </button>\n    </div>\n  </Modal>\n</template>\n\n<style scoped>\nbutton {\n  @apply bg-blue-400 text-white px-4 py-1 rounded border-b-2 border-blue-600;\n  @apply hover:(bg-blue-500 border-blue-700);\n}\n\nbutton.cancel {\n  @apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;\n  @apply hover:(bg-opacity-75 border-opacity-75);\n}\n\na {\n  @apply border-current border-b border-dashed hover:text-primary hover:border-solid;\n}\n\n.form-check {\n  @apply leading-5;\n\n  * {\n    @apply my-auto align-middle;\n  }\n\n  label {\n    @apply ml-1 text-sm select-none;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/FormCheckbox.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  disabled?: boolean\n}>()\n\nconst value = defineModel<boolean>('modelValue', {\n  type: Boolean,\n})\n</script>\n\n<template>\n  <div border=\"~ main rounded\" flex=\"~ gap-2 items-center\" relative h-5 w-5 p0.5 hover:bg-active p1>\n    <div i-ri-check-line :class=\"value ? '' : 'op0'\" />\n    <input v-model=\"value\" type=\"checkbox\" absolute inset-0 opacity-10 :disabled=\"disabled\">\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/FormItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Tooltip } from 'floating-vue'\n\ndefineProps<{\n  title: string\n  nested?: boolean | number\n  div?: boolean\n  description?: string\n  dot?: boolean\n}>()\n\nconst emit = defineEmits<{\n  (event: 'reset'): void\n}>()\n\nfunction reset() {\n  emit('reset')\n}\n</script>\n\n<template>\n  <component :is=\"div ? 'div' : 'label'\" flex=\"~ row gap-2 items-center\" select-none>\n    <div w-30 h-8 flex=\"~ gap-1 items-center\">\n      <div\n        v-if=\"nested\" i-ri-corner-down-right-line op40\n        :style=\"typeof nested === 'number' ? { marginLeft: `${nested * 0.5 + 0.5}rem` } : { marginLeft: '0.25rem' }\"\n      />\n      <div v-if=\"!description\" op75 relative @dblclick=\"reset\">\n        {{ title }}\n        <div v-if=\"dot\" w-1.5 h-1.5 bg-primary rounded absolute top-0 right--2 />\n      </div>\n      <Tooltip v-else distance=\"10\">\n        <div op75 text-right relative @dblclick=\"reset\">\n          {{ title }}\n          <div v-if=\"dot\" w-1.5 h-1.5 bg-primary rounded absolute top-0 right--2 />\n        </div>\n        <template #popper>\n          <div text-sm min-w-90 v-html=\"description\" />\n        </template>\n      </Tooltip>\n    </div>\n    <slot />\n  </component>\n</template>\n"
  },
  {
    "path": "packages/client/internals/FormSlider.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  max: number\n  min: number\n  step: number\n  unit?: string\n  default?: number\n}>()\n\nconst value = defineModel<number>('modelValue', {\n  type: Number,\n})\n</script>\n\n<template>\n  <div relative h-22px w-60 flex-auto @dblclick=\"props.default !== undefined ? value = props.default : null\">\n    <input\n      v-model.number=\"value\" type=\"range\" class=\"slider\"\n      v-bind=\"props\"\n      absolute bottom-0 left-0 right-0 top-0 z-10 w-full align-top\n    >\n    <span\n      v-if=\"props.default != null\"\n      border=\"r main\" absolute bottom-0 top-0 h-full w-1px op75\n      :style=\"{\n        left: `${(props.default - min) / (max - min) * 100}%`,\n      }\"\n    />\n  </div>\n  <div relative h-22px>\n    <input v-model.number=\"value\" type=\"number\" v-bind=\"props\" border=\"~ main rounded\" m0 w-20 bg-gray:5 pl2 align-top text-sm>\n    <span v-if=\"props.unit\" pointer-events-none absolute right-1 top-0.5 text-xs op25>{{ props.unit }}</span>\n  </div>\n</template>\n\n<style>\n.slider {\n  appearance: none;\n  height: 22px;\n  outline: none;\n  opacity: 0.7;\n  -webkit-transition: 0.2s;\n  transition: opacity 0.2s;\n  --uno: border border-main rounded of-hidden bg-gray/5;\n}\n\n.slider:hover {\n  opacity: 1;\n}\n\n.slider::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 5px;\n  height: 22px;\n  background: var(--slidev-theme-primary);\n  cursor: pointer;\n  z-index: 10;\n}\n\n.slider::-moz-range-thumb {\n  width: 5px;\n  height: 22px;\n  background: var(--slidev-theme-primary);\n  cursor: pointer;\n  z-index: 10;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/Goto.vue",
    "content": "<script setup lang=\"ts\">\nimport TitleRenderer from '#slidev/title-renderer'\nimport Fuse from 'fuse.js'\nimport { computed, ref, watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { activeElement, showGotoDialog } from '../state'\n\nconst container = ref<HTMLDivElement>()\nconst input = ref<HTMLInputElement>()\nconst list = ref<HTMLUListElement>()\nconst items = ref<HTMLLIElement[]>()\nconst text = ref('')\nconst selectedIndex = ref(0)\n\nconst { go, slides } = useNav()\n\nfunction notNull<T>(value: T | null | undefined): value is T {\n  return value !== null && value !== undefined\n}\n\nconst fuse = computed(() => new Fuse(slides.value.map(i => i.meta?.slide).filter(notNull), {\n  keys: ['no', 'title'],\n  threshold: 0.3,\n  shouldSort: true,\n  minMatchCharLength: 1,\n}))\n\nconst path = computed(() => text.value.startsWith('/') ? text.value.substring(1) : text.value)\nconst result = computed(() => fuse.value.search(path.value).map(result => result.item))\nconst valid = computed(() => !!result.value.length)\n\nfunction goTo() {\n  if (valid.value) {\n    const item = result.value.at(selectedIndex.value || 0)\n    if (item)\n      go(item.no)\n  }\n  close()\n}\n\nfunction close() {\n  text.value = ''\n  showGotoDialog.value = false\n}\n\nfunction focusDown(event: Event) {\n  event.preventDefault()\n  selectedIndex.value++\n  if (selectedIndex.value >= result.value.length)\n    selectedIndex.value = 0\n  scroll()\n}\n\nfunction focusUp(event: Event) {\n  event.preventDefault()\n  selectedIndex.value--\n  if (selectedIndex.value <= -2)\n    selectedIndex.value = result.value.length - 1\n  scroll()\n}\n\nfunction scroll() {\n  const item = items.value?.[selectedIndex.value]\n  if (item && list.value) {\n    if (item.offsetTop + item.offsetHeight > list.value.offsetHeight + list.value.scrollTop) {\n      list.value.scrollTo({\n        behavior: 'smooth',\n        top: item.offsetTop + item.offsetHeight - list.value.offsetHeight + 1,\n      })\n    }\n    else if (item.offsetTop < list.value.scrollTop) {\n      list.value.scrollTo({\n        behavior: 'smooth',\n        top: item.offsetTop,\n      })\n    }\n  }\n}\n\nfunction updateText(event: Event) {\n  selectedIndex.value = 0\n  text.value = (event.target as HTMLInputElement).value\n}\n\nfunction select(no: number) {\n  go(no)\n  close()\n}\n\nwatch(showGotoDialog, async (show) => {\n  if (show) {\n    text.value = ''\n    selectedIndex.value = 0\n    // delay the focus to avoid the g character coming from the key that triggered showGotoDialog\n    setTimeout(() => input.value?.focus(), 0)\n  }\n  else {\n    input.value?.blur()\n  }\n})\n\nwatch(activeElement, () => {\n  if (!container.value?.contains(activeElement.value as Node))\n    close()\n})\n</script>\n\n<template>\n  <div\n    id=\"slidev-goto-dialog\"\n    ref=\"container\"\n    class=\"fixed right-5 transition-all\"\n    w-90 max-w-90 min-w-90\n    :class=\"showGotoDialog ? 'top-5' : '-top-20'\"\n  >\n    <div\n      class=\"bg-main transform\"\n      shadow=\"~\"\n      p=\"x-4 y-2\"\n      border=\"~ transparent rounded dark:main\"\n    >\n      <input\n        id=\"slidev-goto-input\"\n        ref=\"input\"\n        :value=\"text\"\n        type=\"text\"\n        :disabled=\"!showGotoDialog\"\n        class=\"outline-none bg-transparent\"\n        placeholder=\"Goto...\"\n        :class=\"{ 'text-red-400': !valid && text }\"\n        @keydown.enter=\"goTo\"\n        @keydown.escape=\"close\"\n        @keydown.down=\"focusDown\"\n        @keydown.up=\"focusUp\"\n        @input=\"updateText\"\n      >\n    </div>\n    <div\n      v-if=\"result.length > 0\"\n      ref=\"list\"\n      class=\"autocomplete-list\"\n      shadow=\"~\"\n      border=\"~ transparent rounded dark:main\"\n    >\n      <ul table w-full border-collapse>\n        <li\n          v-for=\"(item, index) of result\"\n          ref=\"items\"\n          :key=\"item.id\"\n          role=\"button\"\n          tabindex=\"0\"\n          cursor-pointer\n          hover=\"op100\"\n          table-row\n          items-center\n          :border=\"index === 0 ? undefined : 't main'\"\n          :class=\"selectedIndex === index ? 'bg-active op100' : 'op80'\"\n          @click.stop.prevent=\"select(item.no)\"\n        >\n          <div text-right op50 text-sm table-cell py-2 pl-4 pr-3 vertical-middle>\n            {{ item.no }}\n          </div>\n          <TitleRenderer table-cell py-2 pr-4 w-full :no=\"item.no\" />\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.autocomplete-list {\n  --uno: bg-main mt-1;\n  overflow: auto;\n  max-height: calc(100vh - 100px);\n}\n\n.autocomplete {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/IconButton.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  title: string\n  icon?: string\n  as?: string\n  to?: string\n}>()\n\nconst type = computed(() => props.as || (props.to ? 'router-link' : 'button'))\n</script>\n\n<template>\n  <component :is=\"type\" class=\"slidev-icon-btn\" :title=\"title\" :to=\"to\">\n    <span class=\"sr-only\">{{ title }}</span>\n    <slot>\n      <div :class=\"icon\" />\n    </slot>\n  </component>\n</template>\n"
  },
  {
    "path": "packages/client/internals/InfoDialog.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport { computed } from 'vue'\nimport { configs } from '../env'\nimport Modal from './Modal.vue'\n\nconst props = defineProps({\n  modelValue: {\n    default: false,\n  },\n})\n\nconst emit = defineEmits(['update:modelValue'])\nconst value = useVModel(props, 'modelValue', emit)\nconst hasInfo = computed(() => typeof configs.info === 'string')\n</script>\n\n<template>\n  <Modal v-model=\"value\" class=\"px-6 py-4\">\n    <div class=\"slidev-info-dialog slidev-layout flex flex-col gap-4 text-base\">\n      <div\n        v-if=\"hasInfo\"\n        class=\"mb-4\"\n        v-html=\"configs.info\"\n      />\n      <a\n        href=\"https://github.com/slidevjs/slidev\"\n        target=\"_blank\"\n        class=\"!opacity-100 !border-none !text-current\"\n      >\n        <div class=\"flex gap-1 children:my-auto\">\n          <div class=\"opacity-50 text-sm mr-2\">Powered by</div>\n          <img\n            class=\"w-5 h-5\"\n            src=\"../assets/logo.png\"\n            alt=\"Slidev logo\"\n          >\n          <div style=\"color: #2082A6\">\n            <b>Sli</b>dev\n          </div>\n        </div>\n      </a>\n    </div>\n  </Modal>\n</template>\n\n<style lang=\"postcss\">\n.slidev-info-dialog {\n  @apply !p-4 max-w-150;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/MenuButton.vue",
    "content": "<script setup lang=\"ts\">\nimport { onClickOutside, useVModel } from '@vueuse/core'\nimport { ref } from 'vue'\n\nconst props = defineProps({\n  modelValue: {\n    default: false,\n  },\n  disabled: {\n    default: false,\n  },\n})\n\nconst emit = defineEmits<{\n  (e: any): void\n}>()\nconst value = useVModel(props, 'modelValue', emit, { passive: true })\nconst el = ref<HTMLDivElement>()\n\nonClickOutside(el, () => {\n  value.value = false\n})\n</script>\n\n<template>\n  <div ref=\"el\" class=\"flex relative\">\n    <button :class=\"{ disabled }\" @click=\"value = !value\">\n      <slot name=\"button\" :class=\"{ disabled }\" />\n    </button>\n    <KeepAlive>\n      <div\n        v-if=\"value\"\n        class=\"bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu py2\"\n        border=\"~ main rounded-md\"\n      >\n        <slot name=\"menu\" />\n      </div>\n    </KeepAlive>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/Modal.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\n\nconst props = defineProps({\n  modelValue: {\n    default: false,\n  },\n  class: {\n    default: '',\n  },\n})\n\nconst emit = defineEmits(['update:modelValue'])\nconst value = useVModel(props, 'modelValue', emit)\n\nfunction onClick() {\n  value.value = false\n}\n</script>\n\n<template>\n  <KeepAlive>\n    <div v-if=\"value\" class=\"fixed top-0 bottom-0 left-0 right-0 grid z-modal\">\n      <div\n        bg=\"black opacity-80\"\n        class=\"absolute top-0 bottom-0 left-0 right-0 -z-1\"\n        @click=\"onClick()\"\n      />\n      <div\n        class=\"m-auto rounded-md bg-main shadow\"\n        dark:border=\"~ main\"\n        :class=\"props.class\"\n      >\n        <slot />\n      </div>\n    </div>\n  </KeepAlive>\n</template>\n"
  },
  {
    "path": "packages/client/internals/NavControls.vue",
    "content": "<script setup lang=\"ts\">\nimport CustomNavControls from '#slidev/custom-nav-controls'\nimport { computed, ref, shallowRef } from 'vue'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useNav } from '../composables/useNav'\nimport { configs } from '../env'\nimport { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'\nimport { activeElement, breakpoints, fullscreen, hasViewerCssFilter, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'\nimport { downloadPDF } from '../utils'\nimport IconButton from './IconButton.vue'\nimport MenuButton from './MenuButton.vue'\nimport Settings from './Settings.vue'\nimport SyncControls from './SyncControls.vue'\n\nimport VerticalDivider from './VerticalDivider.vue'\n\nconst props = defineProps({\n  persist: {\n    default: false,\n  },\n})\n\nconst {\n  currentSlideNo,\n  hasNext,\n  hasPrev,\n  isEmbedded,\n  isPresenter,\n  isPresenterAvailable,\n  next,\n  prev,\n  total,\n  enterPresenter,\n  exitPresenter,\n} = useNav()\nconst {\n  brush,\n  drawingEnabled,\n} = useDrawings()\n\nconst md = breakpoints.smaller('md')\nconst { isFullscreen, toggle: toggleFullscreen } = fullscreen\n\nconst root = ref<HTMLDivElement>()\nfunction onMouseLeave() {\n  if (root.value && activeElement.value && root.value.contains(activeElement.value))\n    activeElement.value.blur()\n}\n\nconst barStyle = computed(() => props.persist\n  ? 'text-$slidev-controls-foreground bg-transparent'\n  : 'rounded-md bg-main shadow-xl border border-main')\n\nconst RecordingControls = shallowRef<any>()\nif (__SLIDEV_FEATURE_RECORD__)\n  import('./RecordingControls.vue').then(v => RecordingControls.value = v.default)\n</script>\n\n<template>\n  <nav ref=\"root\" class=\"flex flex-col\">\n    <div\n      class=\"flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:p-2\"\n      :class=\"barStyle\"\n      @mouseleave=\"onMouseLeave\"\n    >\n      <IconButton v-if=\"!isEmbedded\" :title=\"isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'\" @click=\"toggleFullscreen\">\n        <div v-if=\"isFullscreen\" class=\"i-carbon:minimize\" />\n        <div v-else class=\"i-carbon:maximize\" />\n      </IconButton>\n      <IconButton :class=\"{ disabled: !hasPrev }\" title=\"Go to previous slide\" @click=\"prev\">\n        <div class=\"i-carbon:arrow-left\" />\n      </IconButton>\n      <IconButton :class=\"{ disabled: !hasNext }\" title=\"Go to next slide\" @click=\"next\">\n        <div class=\"i-carbon:arrow-right\" />\n      </IconButton>\n      <IconButton v-if=\"!isEmbedded\" title=\"Show slide overview\" @click=\"toggleOverview()\">\n        <div class=\"i-carbon:apps\" />\n      </IconButton>\n      <IconButton\n        v-if=\"!isColorSchemaConfigured\"\n        :title=\"isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'\"\n        @click=\"toggleDark()\"\n      >\n        <carbon-moon v-if=\"isDark\" />\n        <carbon-sun v-else />\n      </IconButton>\n\n      <VerticalDivider />\n\n      <template v-if=\"!isEmbedded\">\n        <template v-if=\"!isPresenter && !md && RecordingControls\">\n          <RecordingControls />\n          <VerticalDivider />\n        </template>\n\n        <IconButton\n          v-if=\"isPresenter\"\n          :title=\"showPresenterCursor ? 'Hide presenter cursor' : 'Show presenter cursor'\"\n          @click=\"showPresenterCursor = !showPresenterCursor\"\n        >\n          <ph-cursor-fill v-if=\"showPresenterCursor\" />\n          <ph-cursor-duotone v-else />\n        </IconButton>\n      </template>\n\n      <template v-if=\"__SLIDEV_FEATURE_DRAWINGS__ && (!configs.drawings.presenterOnly || isPresenter) && !isEmbedded\">\n        <IconButton class=\"relative\" :title=\"drawingEnabled ? 'Hide drawing toolbar' : 'Show drawing toolbar'\" @click=\"drawingEnabled = !drawingEnabled\">\n          <div class=\"i-carbon:pen\" />\n          <div\n            v-if=\"drawingEnabled\"\n            class=\"absolute left-1 right-1 bottom-0 h-0.7 rounded-full\"\n            :style=\"{ background: brush.color }\"\n          />\n        </IconButton>\n        <VerticalDivider />\n      </template>\n\n      <template v-if=\"!isEmbedded\">\n        <IconButton v-if=\"isPresenter\" title=\"Play Mode\" @click=\"exitPresenter\">\n          <div class=\"i-carbon:presentation-file\" />\n        </IconButton>\n        <IconButton v-if=\"__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable\" title=\"Presenter Mode\" @click=\"enterPresenter\">\n          <div class=\"i-carbon:user-speaker\" />\n        </IconButton>\n\n        <IconButton\n          v-if=\"__DEV__ && __SLIDEV_FEATURE_EDITOR__\"\n          :title=\"showEditor ? 'Hide editor' : 'Show editor'\"\n          class=\"lt-md:hidden\"\n          @click=\"showEditor = !showEditor\"\n        >\n          <div class=\"i-carbon:text-annotation-toggle\" />\n        </IconButton>\n      </template>\n\n      <template v-if=\"!__DEV__\">\n        <IconButton v-if=\"configs.download\" title=\"Download as PDF\" @click=\"downloadPDF\">\n          <div class=\"i-carbon:download\" />\n        </IconButton>\n      </template>\n\n      <template v-if=\"__SLIDEV_FEATURE_BROWSER_EXPORTER__ && !isEmbedded && !isPresenter\">\n        <IconButton title=\"Browser Exporter\" to=\"/export\">\n          <div class=\"i-carbon:document-pdf\" />\n        </IconButton>\n      </template>\n\n      <IconButton\n        v-if=\"!isPresenter && configs.info && !isEmbedded\"\n        title=\"Show info\"\n        @click=\"showInfoDialog = !showInfoDialog\"\n      >\n        <div class=\"i-carbon:information\" />\n      </IconButton>\n\n      <template v-if=\"!isEmbedded\">\n        <VerticalDivider />\n\n        <IconButton v-if=\"isPresenter\" title=\"Toggle Presenter Layout\" class=\"aspect-ratio-initial flex items-center\" @click=\"togglePresenterLayout\">\n          <div class=\"i-carbon:template\" />\n          {{ presenterLayout }}\n        </IconButton>\n\n        <SyncControls v-if=\"__SLIDEV_FEATURE_PRESENTER__\" />\n\n        <MenuButton>\n          <template #button>\n            <IconButton title=\"More Options\">\n              <div class=\"i-carbon:settings-adjust\" />\n              <div v-if=\"hasViewerCssFilter\" w-2 h-2 bg-primary rounded-full absolute top-0.5 right-0.5 />\n            </IconButton>\n          </template>\n          <template #menu>\n            <Settings />\n          </template>\n        </MenuButton>\n      </template>\n\n      <VerticalDivider v-if=\"!isEmbedded\" />\n\n      <div class=\"px2 my-auto\">\n        <span class=\"text-lg\">{{ currentSlideNo }}</span>\n        <span class=\"opacity-50 text-sm\"> / {{ total }}</span>\n      </div>\n\n      <CustomNavControls />\n    </div>\n  </nav>\n</template>\n"
  },
  {
    "path": "packages/client/internals/NoteDisplay.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext } from '@slidev/types'\nimport { computed, nextTick, onMounted, ref, watch, watchEffect } from 'vue'\nimport { CLICKS_MAX } from '../constants'\n\nconst props = withDefaults(\n  defineProps<{\n    class?: string | string[]\n    noteHtml?: string\n    note?: string\n    highlight?: boolean\n    placeholder?: string\n    clicksContext?: ClicksContext\n    autoScroll?: boolean\n  }>(),\n  {\n    highlight: true,\n  },\n)\n\nconst emit = defineEmits<{\n  (type: 'markerDblclick', e: MouseEvent, clicks: number): void\n  (type: 'markerClick', e: MouseEvent, clicks: number): void\n}>()\n\nconst CLASS_FADE = 'slidev-note-fade'\nconst CLASS_MARKER = 'slidev-note-click-mark'\n\nconst withClicks = computed(() => props.clicksContext != null && props.noteHtml?.includes(CLASS_MARKER))\nconst noteDisplay = ref<HTMLElement | null>(null)\n\nfunction processNote() {\n  if (!noteDisplay.value || !withClicks.value)\n    return\n\n  const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]\n\n  const markersMap = new Map<HTMLElement, number>()\n  const parentsMap = new Map<HTMLElement, [divider: HTMLElement | null, dividerClicks: number][]>()\n  let lastClicks = 0\n  for (const marker of markers) {\n    const clicks = Number(marker.dataset!.clicks)\n    markersMap.set(marker, clicks)\n\n    // Set parent clicks map\n    let n = marker\n    let p = marker.parentElement\n    while (p && n !== noteDisplay.value) {\n      if (!parentsMap.has(p))\n        parentsMap.set(p, [[null, lastClicks]])\n      parentsMap.get(p)!.push([n, clicks])\n      n = p\n      p = p.parentElement\n    }\n\n    lastClicks = clicks\n  }\n\n  const siblingsMap = new Map<HTMLElement, number>()\n  for (const [parent, dividers] of parentsMap) {\n    let hasPrefix = false\n    let dividerIdx = 0\n    for (const sibling of Array.from(parent.childNodes)) {\n      let skip = false\n      while (sibling === dividers[dividerIdx + 1]?.[0]) {\n        skip = true\n        dividerIdx++\n      }\n      if (skip)\n        continue\n\n      // Convert sibling text nodes to spans\n      let siblingEl = sibling as HTMLElement\n      if (sibling.nodeType === 3 /* text node */) {\n        if (!sibling.textContent?.trim())\n          continue\n        siblingEl = document.createElement('span')\n        siblingEl.textContent = sibling.textContent\n        parent.insertBefore(siblingEl, sibling)\n        sibling.remove()\n      }\n\n      hasPrefix ||= dividerIdx === 0\n      siblingsMap.set(siblingEl, dividers[dividerIdx][1])\n    }\n    if (!hasPrefix)\n      dividers[0][1] = -1\n  }\n\n  // Apply\n  return (current: number) => {\n    const enabled = props.highlight\n    for (const [parent, clicks] of parentsMap)\n      parent.classList.toggle(CLASS_FADE, enabled && !clicks.some(([_, c]) => c === current))\n    for (const [parent, clicks] of siblingsMap)\n      parent.classList.toggle(CLASS_FADE, enabled && clicks !== current)\n    for (const [marker, clicks] of markersMap) {\n      marker.classList.remove(CLASS_FADE)\n      marker.classList.toggle(`${CLASS_MARKER}-past`, enabled && clicks < current)\n      marker.classList.toggle(`${CLASS_MARKER}-active`, enabled && clicks === current)\n      marker.classList.toggle(`${CLASS_MARKER}-next`, enabled && clicks === current + 1)\n      marker.classList.toggle(`${CLASS_MARKER}-future`, enabled && clicks > current + 1)\n      marker.ondblclick = (e) => {\n        if (!enabled)\n          return\n        emit('markerDblclick', e, clicks)\n        if (e.defaultPrevented)\n          return\n        props.clicksContext!.current = clicks\n        e.stopPropagation()\n        e.stopImmediatePropagation()\n      }\n      marker.onclick = (e) => {\n        if (enabled) {\n          emit('markerClick', e, clicks)\n        }\n      }\n\n      if (enabled && props.autoScroll && clicks === current)\n        marker.scrollIntoView({ block: 'center', behavior: 'smooth' })\n    }\n  }\n}\n\nconst applyHighlight = ref<ReturnType<typeof processNote>>()\n\nwatch(\n  () => [props.noteHtml, props.highlight],\n  () => {\n    nextTick(() => {\n      applyHighlight.value = processNote()\n    })\n  },\n  { immediate: true },\n)\n\nonMounted(() => {\n  processNote()\n})\n\nwatchEffect(() => {\n  const current = props.clicksContext?.current ?? CLICKS_MAX\n  applyHighlight.value?.(current)\n})\n</script>\n\n<template>\n  <div\n    v-if=\"noteHtml\"\n    ref=\"noteDisplay\"\n    class=\"prose dark:prose-invert overflow-auto outline-none slidev-note\"\n    :class=\"[props.class, withClicks ? 'slidev-note-with-clicks' : '']\"\n    v-html=\"noteHtml\"\n  />\n  <div\n    v-else-if=\"note\"\n    class=\"prose dark:prose-invert overflow-auto outline-none slidev-note\"\n    :class=\"props.class\"\n  >\n    <p v-text=\"note\" />\n  </div>\n  <div\n    v-else\n    class=\"prose dark:prose-invert overflow-auto outline-none opacity-50 italic select-none slidev-note\"\n    :class=\"props.class\"\n  >\n    <p v-text=\"props.placeholder || 'No notes.'\" />\n  </div>\n</template>\n\n<style>\n.slidev-note :first-child {\n  margin-top: 0;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/NoteEditable.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext } from '@slidev/types'\nimport type { PropType } from 'vue'\nimport { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'\nimport { nextTick, ref, toRef, watch, watchEffect } from 'vue'\nimport { useDynamicSlideInfo } from '../composables/useSlideInfo'\nimport NoteDisplay from './NoteDisplay.vue'\n\nconst props = defineProps({\n  no: {\n    type: Number,\n    required: true,\n  },\n  class: {\n    default: '',\n  },\n  editing: {\n    default: false,\n  },\n  style: {\n    default: () => ({}),\n  },\n  placeholder: {\n    default: 'No notes for this slide',\n  },\n  clicksContext: {\n    type: Object as PropType<ClicksContext>,\n  },\n  highlight: {\n    default: true,\n  },\n  autoHeight: {\n    default: false,\n  },\n})\n\nconst emit = defineEmits<{\n  (type: 'update:editing', value: boolean): void\n  (type: 'markerDblclick', e: MouseEvent, clicks: number): void\n  (type: 'markerClick', e: MouseEvent, clicks: number): void\n}>()\n\nconst editing = useVModel(props, 'editing', emit, { passive: true })\n\nconst { info, update } = useDynamicSlideInfo(toRef(props, 'no'))\n\nconst note = ref('')\nlet timer: any\n\n// Send back the note on changes\nconst { ignoreUpdates } = ignorableWatch(\n  note,\n  (v) => {\n    if (!editing.value)\n      return\n    const id = props.no\n    clearTimeout(timer)\n    timer = setTimeout(() => {\n      update({ note: v }, id)\n    }, 500)\n  },\n)\n\n// Update note value when info changes\nwatch(\n  () => info.value?.note,\n  (value = '') => {\n    if (editing.value)\n      return\n    clearTimeout(timer)\n    ignoreUpdates(() => {\n      note.value = value\n    })\n  },\n  { immediate: true, flush: 'sync' },\n)\n\nconst inputEl = ref<HTMLTextAreaElement>()\nconst inputHeight = ref<number | null>()\n\nwatchEffect(() => {\n  if (editing.value)\n    inputEl.value?.focus()\n})\n\nonClickOutside(inputEl, () => {\n  editing.value = false\n})\n\nfunction calculateEditorHeight() {\n  if (!props.autoHeight || !inputEl.value || !editing.value)\n    return\n  if (inputEl.value.scrollHeight > inputEl.value.clientHeight)\n    inputEl.value.style.height = `${inputEl.value.scrollHeight}px`\n}\n\nfunction onKeyDown(e: KeyboardEvent) {\n  // Override save shortcut on editing mode\n  if (editing.value && e.metaKey && e.key === 's') {\n    e.preventDefault()\n    update({ note: note.value }, props.no)\n  }\n}\n\nwatch(\n  [note, editing],\n  () => {\n    nextTick(() => {\n      calculateEditorHeight()\n    })\n  },\n  { flush: 'post', immediate: true },\n)\n</script>\n\n<template>\n  <NoteDisplay\n    v-if=\"!editing\"\n    class=\"border-transparent border-2\"\n    :class=\"[props.class, note ? '' : 'opacity-25 italic select-none']\"\n    :style=\"props.style\"\n    :note=\"note || placeholder\"\n    :note-html=\"info?.noteHTML\"\n    :clicks-context=\"clicksContext\"\n    :auto-scroll=\"!autoHeight\"\n    :highlight=\"props.highlight\"\n    @marker-click=\"(e, clicks) => emit('markerClick', e, clicks)\"\n    @marker-dblclick=\"(e, clicks) => emit('markerDblclick', e, clicks)\"\n  />\n  <textarea\n    v-else\n    ref=\"inputEl\"\n    v-model=\"note\"\n    class=\"prose dark:prose-invert resize-none overflow-auto outline-none bg-transparent block border-primary border-2\"\n    style=\"line-height: 1.75;\"\n    :style=\"[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]\"\n    :class=\"props.class\"\n    :placeholder=\"placeholder\"\n    @keydown.esc=\"editing = false\"\n    @keydown=\"onKeyDown\"\n  />\n</template>\n"
  },
  {
    "path": "packages/client/internals/NoteStatic.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext } from '@slidev/types'\nimport { useSlideInfo } from '../composables/useSlideInfo'\nimport NoteDisplay from './NoteDisplay.vue'\n\nconst props = defineProps<{\n  no: number\n  class?: string\n  clicksContext?: ClicksContext\n}>()\n\nconst { info } = useSlideInfo(props.no)\n</script>\n\n<template>\n  <NoteDisplay\n    :class=\"props.class\"\n    :note=\"info?.note\"\n    :note-html=\"info?.noteHTML\"\n    :clicks-context=\"clicksContext\"\n  />\n</template>\n"
  },
  {
    "path": "packages/client/internals/PresenterMouse.vue",
    "content": "<script setup lang=\"ts\">\nimport { sharedState } from '../state/shared'\n</script>\n\n<template>\n  <div\n    v-if=\"sharedState.cursor\"\n    class=\"absolute top-0 left-0 right-0 bottom-0 pointer-events-none text-xl\"\n  >\n    <ph-cursor-fill\n      class=\"absolute stroke-white dark:stroke-black\"\n      :style=\"{\n        left: `${sharedState.cursor.x}%`,\n        top: `${sharedState.cursor.y}%`,\n        strokeWidth: 16,\n      }\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/PrintContainer.vue",
    "content": "<script setup lang=\"ts\">\nimport { provideLocal } from '@vueuse/core'\nimport { computed } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { injectionSlideScale } from '../constants'\nimport { configs, slideAspect, slideWidth } from '../env'\nimport PrintSlide from './PrintSlide.vue'\n\nconst props = defineProps<{\n  width: number\n}>()\n\nconst { slides, printRange } = useNav()\n\nconst width = computed(() => props.width)\nconst height = computed(() => props.width / slideAspect.value)\n\nconst screenAspect = computed(() => width.value / height.value)\n\nconst scale = computed(() => {\n  if (screenAspect.value < slideAspect.value)\n    return width.value / slideWidth.value\n  return (height.value * slideAspect.value) / slideWidth.value\n})\n\nconst className = computed(() => ({\n  'select-none': !configs.selectable,\n}))\n\nprovideLocal(injectionSlideScale, scale)\n</script>\n\n<template>\n  <div id=\"print-container\" :class=\"className\">\n    <div id=\"print-content\">\n      <PrintSlide v-for=\"no of printRange\" :key=\"no\" :route=\"slides[no - 1]\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/PrintSlide.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SlideRoute } from '@slidev/types'\nimport { createFixedClicks } from '../composables/useClicks'\nimport { useFixedNav, useNav } from '../composables/useNav'\nimport { CLICKS_MAX } from '../constants'\nimport PrintSlideClick from './PrintSlideClick.vue'\n\nconst { route } = defineProps<{ hidden?: boolean, route: SlideRoute }>()\nconst { isPrintWithClicks } = useNav()\nconst clicks0 = createFixedClicks(route, () => isPrintWithClicks.value ? 0 : CLICKS_MAX)\n</script>\n\n<template>\n  <PrintSlideClick\n    v-show=\"!hidden\"\n    :nav=\"useFixedNav(route, clicks0)\"\n  />\n  <template v-if=\"isPrintWithClicks\">\n    <!--\n      clicks0.total can be any number >=0 when rendering.\n      So total-clicksStart can be negative in intermediate states.\n    -->\n    <PrintSlideClick\n      v-for=\"i in Math.max(0, clicks0.total - clicks0.clicksStart)\"\n      v-show=\"!hidden\"\n      :key=\"i\"\n      :nav=\"useFixedNav(route, createFixedClicks(route, i + clicks0.clicksStart))\"\n    />\n  </template>\n</template>\n"
  },
  {
    "path": "packages/client/internals/PrintSlideClick.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SlidevContextNav } from '../composables/useNav'\nimport { GlobalBottom, GlobalTop } from '#slidev/global-layers'\nimport { provideLocal } from '@vueuse/core'\nimport { computed, reactive, shallowRef, useTemplateRef } from 'vue'\nimport { injectionSlideElement, injectionSlidevContext } from '../constants'\nimport { configs, slideHeight, slideWidth } from '../env'\nimport { getSlideClass } from '../utils'\nimport SlideWrapper from './SlideWrapper.vue'\n\nconst { nav } = defineProps<{\n  nav: SlidevContextNav\n}>()\n\nconst route = computed(() => nav.currentSlideRoute.value)\n\nconst style = computed(() => ({\n  height: `${slideHeight.value}px`,\n  width: `${slideWidth.value}px`,\n}))\n\nconst DrawingPreview = shallowRef<any>()\nif (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)\n  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))\n\nconst id = computed(() =>\n  `${route.value.no.toString().padStart(3, '0')}-${(nav.clicks.value + 1).toString().padStart(2, '0')}`,\n)\n\nprovideLocal(injectionSlidevContext, reactive({\n  nav,\n  configs,\n  themeConfigs: computed(() => configs.themeConfig),\n}))\n\nprovideLocal(injectionSlideElement, useTemplateRef('slide-element'))\n</script>\n\n<template>\n  <div :id=\"id\" ref=\"slide-element\" class=\"print-slide-container\" :style=\"style\">\n    <GlobalBottom />\n\n    <SlideWrapper\n      :clicks-context=\"nav.clicksContext.value\"\n      :class=\"getSlideClass(route)\"\n      :route=\"route\"\n    />\n    <template\n      v-if=\"\n        (__SLIDEV_FEATURE_DRAWINGS__\n          || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)\n          && DrawingPreview\n      \"\n    >\n      <DrawingPreview :page=\"route.no\" />\n    </template>\n\n    <GlobalTop />\n  </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.print-slide-container {\n  @apply relative overflow-hidden break-after-page translate-0 bg-main;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/QuickOverview.vue",
    "content": "<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { computed, ref, watchEffect } from 'vue'\nimport { createFixedClicks } from '../composables/useClicks'\nimport { useNav } from '../composables/useNav'\nimport { CLICKS_MAX } from '../constants'\nimport { pathPrefix } from '../env'\nimport { currentOverviewPage, overviewRowCount } from '../logic/overview'\nimport { isScreenshotSupported } from '../logic/screenshot'\nimport { snapshotManager } from '../logic/snapshot'\nimport { breakpoints, showOverview, windowSize } from '../state'\nimport DrawingPreview from './DrawingPreview.vue'\nimport IconButton from './IconButton.vue'\nimport SlideContainer from './SlideContainer.vue'\nimport SlideWrapper from './SlideWrapper.vue'\n\nconst nav = useNav()\nconst { currentSlideNo, go: goSlide, slides } = nav\n\nfunction close() {\n  showOverview.value = false\n}\n\nfunction go(page: number) {\n  goSlide(page)\n  close()\n}\n\nfunction focus(page: number) {\n  if (page === currentOverviewPage.value)\n    return true\n  return false\n}\n\nconst xs = breakpoints.smaller('xs')\nconst sm = breakpoints.smaller('sm')\n\nconst padding = 4 * 16 * 2\nconst gap = 2 * 16\nconst cardWidth = computed(() => {\n  if (xs.value)\n    return windowSize.width.value - padding\n  else if (sm.value)\n    return (windowSize.width.value - padding - gap) / 2\n  return 300\n})\n\nconst rowCount = computed(() => {\n  return Math.floor((windowSize.width.value - padding) / (cardWidth.value + gap))\n})\n\nconst keyboardBuffer = ref<string>('')\n\nasync function captureSlidesOverview() {\n  showOverview.value = false\n  await snapshotManager.startCapturing(nav)\n  showOverview.value = true\n}\n\nuseEventListener('keypress', (e) => {\n  if (!showOverview.value) {\n    keyboardBuffer.value = ''\n    return\n  }\n  if (e.key === 'Enter') {\n    e.preventDefault()\n    if (keyboardBuffer.value) {\n      go(+keyboardBuffer.value)\n      keyboardBuffer.value = ''\n    }\n    else {\n      go(currentOverviewPage.value)\n    }\n    return\n  }\n  const num = Number.parseInt(e.key.replace(/\\D/g, ''))\n  if (Number.isNaN(num)) {\n    keyboardBuffer.value = ''\n    return\n  }\n  if (!keyboardBuffer.value && num === 0)\n    return\n\n  keyboardBuffer.value += String(num)\n\n  // beyond the number of slides, reset\n  if (+keyboardBuffer.value > slides.value.length) {\n    keyboardBuffer.value = ''\n    return\n  }\n\n  const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)\n  if (extactMatch !== -1)\n    currentOverviewPage.value = extactMatch + 1\n\n  // When the input number is the largest at the number of digits, we go to that page directly.\n  if (+keyboardBuffer.value * 10 > slides.value.length) {\n    go(+keyboardBuffer.value)\n    keyboardBuffer.value = ''\n  }\n})\n\nwatchEffect(() => {\n  // Watch currentPage, make sure every time we open overview,\n  // we focus on the right page.\n  currentOverviewPage.value = currentSlideNo.value\n  // Watch rowCount, make sure up and down shortcut work correctly.\n  overviewRowCount.value = rowCount.value\n})\n</script>\n\n<template>\n  <Transition\n    enter-active-class=\"duration-150 ease-out\"\n    enter-from-class=\"opacity-0 scale-102 !backdrop-blur-0px\"\n    leave-active-class=\"duration-200 ease-in\"\n    leave-to-class=\"opacity-0 scale-102 !backdrop-blur-0px\"\n  >\n    <div\n      v-if=\"showOverview\"\n      class=\"fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)] z-modal bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px select-none\"\n      @click=\"close\"\n    >\n      <div\n        class=\"grid gap-y-4 gap-x-8 w-full\"\n        :style=\"`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`\"\n      >\n        <div\n          v-for=\"(route, idx) of slides\"\n          :key=\"route.no\"\n          class=\"relative\"\n        >\n          <div\n            class=\"inline-block border rounded overflow-hidden bg-main hover:border-primary transition\"\n            :class=\"(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'\"\n            @click=\"go(route.no)\"\n          >\n            <SlideContainer\n              :key=\"route.no\"\n              :no=\"route.no\"\n              :use-snapshot=\"true\"\n              :width=\"cardWidth\"\n              class=\"pointer-events-none\"\n            >\n              <SlideWrapper\n                :clicks-context=\"createFixedClicks(route, CLICKS_MAX)\"\n                :route=\"route\"\n                render-context=\"overview\"\n              />\n              <DrawingPreview :page=\"route.no\" />\n            </SlideContainer>\n          </div>\n          <div\n            class=\"absolute top-0\"\n            :style=\"`left: ${cardWidth + 5}px`\"\n          >\n            <template v-if=\"keyboardBuffer && String(idx + 1).startsWith(keyboardBuffer)\">\n              <span class=\"text-green font-bold\">{{ keyboardBuffer }}</span>\n              <span class=\"opacity-50\">{{ String(idx + 1).slice(keyboardBuffer.length) }}</span>\n            </template>\n            <span v-else class=\"opacity-50\">\n              {{ idx + 1 }}\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </Transition>\n  <div\n    v-show=\"showOverview\"\n    class=\"fixed top-4 right-4 z-modal text-gray-400 flex flex-col items-center gap-2\"\n  >\n    <IconButton title=\"Close\" class=\"text-2xl\" @click=\"close\">\n      <div class=\"i-carbon:close\" />\n    </IconButton>\n    <IconButton\n      v-if=\"__SLIDEV_FEATURE_PRESENTER__\"\n      as=\"a\"\n      title=\"Slides Overview\"\n      target=\"_blank\"\n      :href=\"`${pathPrefix}overview`\"\n      tab-index=\"-1\"\n      class=\"text-2xl\"\n    >\n      <div class=\"i-carbon:list-boxes\" />\n    </IconButton>\n    <IconButton\n      v-if=\"__DEV__ && isScreenshotSupported\"\n      title=\"Capture slides as images\"\n      class=\"text-2xl\"\n      @click=\"captureSlidesOverview\"\n    >\n      <div class=\"i-carbon:drop-photo\" />\n    </IconButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/README.md",
    "content": "# Internal Components\n\nComponents for built-in UIs.\n\nNot exposed to auto-importing. Will need to be imported to use in other components.\n"
  },
  {
    "path": "packages/client/internals/RecordingControls.vue",
    "content": "<script setup lang=\"ts\">\nimport { useLocalStorage } from '@vueuse/core'\nimport { onMounted, watch } from 'vue'\nimport { recorder } from '../logic/recording'\nimport { currentCamera, showRecordingDialog } from '../state'\nimport DevicesSelectors from './DevicesSelectors.vue'\nimport IconButton from './IconButton.vue'\nimport MenuButton from './MenuButton.vue'\n\nconst {\n  recording,\n  showAvatar,\n  streamCamera,\n  stopRecording,\n  toggleAvatar,\n} = recorder\n\nconst previousAvatar = useLocalStorage('slidev-webcam-show', false)\nwatch(showAvatar, () => {\n  previousAvatar.value = showAvatar.value\n})\n\nfunction toggleRecording() {\n  if (recording.value)\n    stopRecording()\n  else\n    showRecordingDialog.value = true\n}\n\nonMounted(() => {\n  if (previousAvatar.value && !showAvatar.value)\n    toggleAvatar()\n})\n</script>\n\n<template>\n  <IconButton\n    v-if=\"currentCamera !== 'none'\"\n    class=\"<md:hidden\"\n    :class=\"{ 'text-green-500': Boolean(showAvatar && streamCamera) }\"\n    title=\"Toggle camera view\"\n    @click=\"toggleAvatar\"\n  >\n    <div class=\"i-carbon:user-avatar\" />\n  </IconButton>\n\n  <IconButton\n    :class=\"{ 'text-red-500': recording }\"\n    :title=\"recording ? 'Stop record video' : 'Record video'\"\n    @click=\"toggleRecording\"\n  >\n    <div v-if=\"recording\" class=\"i-carbon:stop-outline\" />\n    <div v-else class=\"i-carbon:video\" />\n  </IconButton>\n  <MenuButton :disabled=\"recording\">\n    <template #button>\n      <IconButton title=\"Select recording device\" class=\"h-full !text-sm !px-0 aspect-initial\">\n        <div class=\"i-carbon:chevron-up opacity-50\" />\n      </IconButton>\n    </template>\n    <template #menu>\n      <DevicesSelectors />\n    </template>\n  </MenuButton>\n</template>\n"
  },
  {
    "path": "packages/client/internals/RecordingDialog.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport { nextTick } from 'vue'\nimport { bitRate, frameRate, getFilename, mimeType, recordCamera, recorder, recordingName, resolution } from '../logic/recording'\nimport DevicesSelectors from './DevicesSelectors.vue'\nimport Modal from './Modal.vue'\n\nconst props = defineProps({\n  modelValue: {\n    default: false,\n  },\n})\n\nconst emit = defineEmits<{\n  (e: any): void\n}>()\nconst value = useVModel(props, 'modelValue', emit)\n\nconst { startRecording } = recorder\n\nconst frameRateOptions = [15, 24, 30, 60]\nconst resolutionOptions = [\n  { value: '1280x720', label: '720p (1280x720)' },\n  { value: '1920x1080', label: '1080p (1920x1080)' },\n  { value: '2560x1440', label: '1440p (2560x1440)' },\n  { value: '3840x2160', label: '2160p (3840x2160)' },\n]\n\nfunction close() {\n  value.value = false\n}\n\nasync function start() {\n  close()\n  await nextTick()\n  startRecording({\n    mimeType: mimeType.value,\n    bitsPerSecond: bitRate.value * 1024,\n  })\n}\n</script>\n\n<template>\n  <Modal v-model=\"value\" class=\"px-6 py-4 recording-dialog flex flex-col gap-2\">\n    <div class=\"flex gap-2 text-xl\">\n      <div class=\"i-carbon:video my-auto\" />Recording\n    </div>\n    <div class=\"grid grid-cols-2 gap-4\">\n      <div class=\"flex flex-col gap-2 py-2\">\n        <div class=\"form-text\">\n          <label for=\"title\">Recording Name</label>\n          <input\n            v-model=\"recordingName\"\n            class=\"bg-transparent text-current\"\n            name=\"title\"\n            type=\"text\"\n            placeholder=\"Enter the title...\"\n          >\n          <div class=\"text-xs w-full opacity-50 py-2\">\n            <div>This will be used in the output filename that might <br>help you better organize your recording chips.</div>\n          </div>\n        </div>\n\n        <div class=\"form-text\">\n          <label for=\"framerate\">Frame Rate</label>\n          <select\n            v-model=\"frameRate\"\n            class=\"bg-transparent text-current border border-main rounded px-2 py-1\"\n            name=\"framerate\"\n          >\n            <option v-for=\"rate in frameRateOptions\" :key=\"rate\" :value=\"rate\">\n              {{ rate }} fps\n            </option>\n          </select>\n        </div>\n\n        <div class=\"form-text\">\n          <label for=\"resolution\">Resolution</label>\n          <select\n            v-model=\"resolution\"\n            class=\"bg-transparent text-current border border-main rounded px-2 py-1\"\n            name=\"resolution\"\n          >\n            <option v-for=\"res in resolutionOptions\" :key=\"res.value\" :value=\"res.value\">\n              {{ res.label }}\n            </option>\n          </select>\n        </div>\n\n        <div class=\"form-text\">\n          <label for=\"bitrate\">Bitrate</label>\n          <div class=\"relative\">\n            <input\n              v-model.number=\"bitRate\"\n              type=\"number\"\n              min=\"1000\"\n              step=\"1000\"\n              class=\"bg-transparent text-current border border-main rounded px-2 py-1 w-full pr-12\"\n              name=\"bitrate\"\n            >\n            <span class=\"absolute right-3 top-1/2 transform -translate-y-1/2 text-sm opacity-50\">kbps</span>\n          </div>\n        </div>\n\n        <div class=\"form-check\">\n          <input\n            v-model=\"recordCamera\"\n            name=\"record-camera\"\n            type=\"checkbox\"\n          >\n          <label for=\"record-camera\" @click=\"recordCamera = !recordCamera\">Record camera separately</label>\n        </div>\n\n        <div class=\"text-xs w-full opacity-50\">\n          <div class=\"mt-2 opacity-50\">\n            Enumerated filenames\n          </div>\n          <div class=\"font-mono\">\n            {{ getFilename('screen', mimeType) }}\n          </div>\n          <div v-if=\"recordCamera\" class=\"font-mono\">\n            {{ getFilename('camera', mimeType) }}\n          </div>\n        </div>\n      </div>\n      <DevicesSelectors />\n    </div>\n    <div class=\"flex my-1\">\n      <button class=\"slidev-form-button\" @click=\"close\">\n        Cancel\n      </button>\n      <div class=\"flex-auto\" />\n      <button class=\"slidev-form-button primary\" @click=\"start\">\n        Start\n      </button>\n    </div>\n  </Modal>\n</template>\n\n<style lang=\"postcss\">\n.recording-dialog {\n  .form-text {\n    @apply flex flex-col;\n\n    label {\n      @apply text-xs uppercase opacity-50 tracking-widest py-1;\n    }\n  }\n\n  .form-check {\n    @apply leading-5;\n\n    * {\n      @apply my-auto align-middle;\n    }\n\n    label {\n      @apply ml-1 text-sm select-none;\n    }\n  }\n\n  input[type='text'] {\n    @apply border border-main rounded px-2 py-1;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/ScreenCaptureMirror.vue",
    "content": "<script setup lang=\"ts\">\nimport { shallowRef, useTemplateRef } from 'vue'\n\nconst video = useTemplateRef('video')\nconst stream = shallowRef<MediaStream | null>(null)\nconst started = shallowRef(false)\n\nasync function startCapture() {\n  stream.value = await navigator.mediaDevices.getDisplayMedia({\n    video: {\n      // @ts-expect-error missing types\n      cursor: 'always',\n    },\n    audio: false,\n    selfBrowserSurface: 'include',\n    preferCurrentTab: false,\n  })\n  video.value!.srcObject = stream.value\n  video.value!.play()\n  started.value = true\n  stream.value.addEventListener('inactive', () => {\n    video.value!.srcObject = null\n    started.value = false\n  })\n  stream.value.addEventListener('ended', () => {\n    video.value!.srcObject = null\n    started.value = false\n  })\n}\n</script>\n\n<template>\n  <div h-full w-full>\n    <video v-show=\"started\" ref=\"video\" class=\"w-full h-full object-contain\" />\n    <div v-if=\"!started\" w-full h-full flex=\"~ col gap-4 items-center justify-center\">\n      <div op50>\n        Use screen capturing to mirror your main screen back to presenter view.<br>\n        Click the button below and <b>select your other monitor or window</b>.\n      </div>\n      <button class=\"slidev-form-button\" @click=\"startCapture\">\n        Start Screen Mirroring\n      </button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/SegmentControl.vue",
    "content": "<script setup lang=\"ts\">\nimport Badge from './Badge.vue'\n\ndefineProps<{\n  options: { label: string, value: any }[]\n  modelValue: any\n}>()\n\ndefineEmits<{\n  (event: 'update:modelValue', newValue: any): void\n}>()\n</script>\n\n<template>\n  <div flex=\"~ gap-1 items-center\" rounded bg-gray:4 p1 m--1>\n    <Badge\n      v-for=\"option in options\"\n      :key=\"option.value\"\n      class=\"px-2 py-1 text-xs font-mono\"\n      :class=\"option.value === modelValue ? '' : 'op50'\"\n      :color=\"option.value === modelValue\"\n      :aria-pressed=\"option.value === modelValue\"\n      size=\"none\"\n      :text=\"option.label\"\n      as=\"button\"\n      @click=\"$emit('update:modelValue', option.value)\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/SelectList.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PropType } from 'vue'\nimport type { SelectionItem } from './types'\nimport { useVModel } from '@vueuse/core'\n\nconst props = defineProps({\n  modelValue: {\n    type: [Object, String, Number, Boolean] as PropType<any>,\n  },\n  title: {\n    type: String,\n  },\n  items: {\n    type: Array as PropType<SelectionItem<any>[]>,\n  },\n})\n\nconst emit = defineEmits<{\n  (e: any): void\n}>()\nconst value = useVModel(props, 'modelValue', emit, { passive: true })\n</script>\n\n<template>\n  <div class=\"select-list\">\n    <div class=\"title\">\n      {{ title }}\n    </div>\n    <div class=\"items\">\n      <div\n        v-for=\"item of items\"\n        :key=\"item.value\"\n        class=\"item\"\n        :class=\"{ active: value === item.value }\"\n        @click=\"() => { value = item.value; item.onClick?.() }\"\n      >\n        <div class=\"i-carbon:checkmark text-green-500 mya\" :class=\"{ 'opacity-0': value !== item.value }\" />\n        <div :class=\"{ 'opacity-50': value !== item.value }\">\n          {{ item.display || item.value }}\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"postcss\" scoped>\n.item {\n  @apply flex rounded whitespace-nowrap py-1 gap-1 px-2 cursor-default hover:bg-gray-400 hover:bg-opacity-10;\n}\n\n.title {\n  @apply text-sm op75 px3 py1 select-none text-nowrap font-bold;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/Settings.vue",
    "content": "<script setup lang=\"ts\">\nimport { useWakeLock } from '@vueuse/core'\nimport { useNav } from '../composables/useNav'\nimport { hideCursorIdle, slideScale, viewerCssFilter, viewerCssFilterDefaults, wakeLockEnabled } from '../state'\nimport FormCheckbox from './FormCheckbox.vue'\nimport FormItem from './FormItem.vue'\nimport FormSlider from './FormSlider.vue'\nimport SegmentControl from './SegmentControl.vue'\n\nconst { isPresenter } = useNav()\nconst { isSupported } = useWakeLock()\n</script>\n\n<template>\n  <div text-sm select-none flex=\"~ col gap-1\" min-w-30 px4>\n    <FormItem\n      title=\"Invert\"\n      :dot=\"viewerCssFilter.invert !== viewerCssFilterDefaults.invert\"\n      @reset=\"viewerCssFilter.invert = viewerCssFilterDefaults.invert\"\n    >\n      <FormCheckbox v-model=\"viewerCssFilter.invert\" />\n    </FormItem>\n    <FormItem\n      title=\"Brightness\"\n      :dot=\"viewerCssFilter.brightness !== viewerCssFilterDefaults.brightness\"\n      @reset=\"viewerCssFilter.brightness = viewerCssFilterDefaults.brightness\"\n    >\n      <FormSlider\n        v-model=\"viewerCssFilter.brightness\"\n        :max=\"1.5\"\n        :min=\"0.5\"\n        :step=\"0.02\"\n        :default=\"viewerCssFilterDefaults.brightness\"\n      />\n    </FormItem>\n    <FormItem\n      title=\"Contrast\"\n      :dot=\"viewerCssFilter.contrast !== viewerCssFilterDefaults.contrast\"\n      @reset=\"viewerCssFilter.contrast = viewerCssFilterDefaults.contrast\"\n    >\n      <FormSlider\n        v-model=\"viewerCssFilter.contrast\"\n        :max=\"1.5\"\n        :min=\"0.5\"\n        :step=\"0.02\"\n        :default=\"viewerCssFilterDefaults.contrast\"\n      />\n    </FormItem>\n    <FormItem\n      title=\"Saturation\"\n      :dot=\"viewerCssFilter.saturate !== viewerCssFilterDefaults.saturate\"\n      @reset=\"viewerCssFilter.saturate = viewerCssFilterDefaults.saturate\"\n    >\n      <FormSlider\n        v-model=\"viewerCssFilter.saturate\"\n        :max=\"1.5\"\n        :min=\"0.5\"\n        :step=\"0.02\"\n        :default=\"viewerCssFilterDefaults.saturate\"\n      />\n    </FormItem>\n    <FormItem\n      title=\"Sepia\"\n      :dot=\"viewerCssFilter.sepia !== viewerCssFilterDefaults.sepia\"\n      @reset=\"viewerCssFilter.sepia = viewerCssFilterDefaults.sepia\"\n    >\n      <FormSlider\n        v-model=\"viewerCssFilter.sepia\"\n        :max=\"2\"\n        :min=\"-2\"\n        :step=\"0.02\"\n        :default=\"viewerCssFilterDefaults.sepia\"\n      />\n    </FormItem>\n    <FormItem\n      title=\"Hue Rotate\"\n      :dot=\"viewerCssFilter.hueRotate !== viewerCssFilterDefaults.hueRotate\"\n      @reset=\"viewerCssFilter.hueRotate = viewerCssFilterDefaults.hueRotate\"\n    >\n      <FormSlider\n        v-model=\"viewerCssFilter.hueRotate\"\n        :max=\"180\"\n        :min=\"-180\"\n        :step=\"0.1\"\n        :default=\"viewerCssFilterDefaults.hueRotate\"\n      />\n    </FormItem>\n    <div class=\"h-1px opacity-5 bg-current w-full my2\" />\n    <FormItem\n      v-if=\"!isPresenter\"\n      title=\"Slide Scale\"\n    >\n      <SegmentControl\n        v-model=\"slideScale\"\n        :options=\"[\n          { label: 'Fit', value: 0 },\n          { label: '1:1', value: 1 },\n        ]\"\n      />\n    </FormItem>\n    <FormItem\n      v-if=\"__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported\"\n      title=\"Wake Lock\"\n    >\n      <FormCheckbox v-model=\"wakeLockEnabled\" />\n    </FormItem>\n    <FormItem\n      v-if=\"!isPresenter\"\n      title=\"Hide Idle Cursor\"\n    >\n      <FormCheckbox v-model=\"hideCursorIdle\" />\n    </FormItem>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/ShadowRoot.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed, ref, watchEffect } from 'vue'\n\nconst props = defineProps<{\n  innerHtml: string\n}>()\n\nconst emit = defineEmits<{\n  (event: 'shadow', div: ShadowRoot): void\n}>()\n\nconst el = ref<HTMLDivElement>()\nconst shadow = computed(() => el.value ? (el.value.shadowRoot || el.value.attachShadow({ mode: 'open' })) : null)\nwatchEffect(() => {\n  if (shadow.value && props.innerHtml) {\n    emit('shadow', shadow.value)\n    shadow.value.innerHTML = props.innerHtml\n  }\n})\n</script>\n\n<template>\n  <div ref=\"el\" />\n</template>\n"
  },
  {
    "path": "packages/client/internals/ShikiEditor.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, shallowRef } from 'vue'\nimport { useIME } from '../composables/useIME'\n\nconst props = defineProps<{\n  placeholder?: string\n}>()\nconst content = defineModel<string>({ required: true })\nconst { composingContent, onInput, onCompositionEnd } = useIME(content)\n\nconst textareaEl = ref<HTMLTextAreaElement | null>(null)\n\nconst highlight = shallowRef<((code: string) => string) | null>(null)\nimport('../setup/shiki').then(async (m) => {\n  const { getEagerHighlighter, defaultHighlightOptions } = await m.default()\n  const highlighter = await getEagerHighlighter()\n  highlight.value = (code: string) => highlighter.codeToHtml(code, {\n    ...defaultHighlightOptions,\n    lang: 'markdown',\n  })\n})\n</script>\n\n<template>\n  <div class=\"absolute left-3 right-0 inset-y-2 font-mono overflow-x-hidden overflow-y-auto cursor-text\">\n    <div v-if=\"highlight\" class=\"relative w-full h-max min-h-full\">\n      <div class=\"relative w-full h-max\" v-html=\"`${highlight(composingContent)}&nbsp;`\" />\n      <textarea\n        ref=\"textareaEl\" v-model=\"composingContent\" :placeholder=\"props.placeholder\"\n        class=\"absolute inset-0 resize-none text-transparent bg-transparent focus:outline-none caret-black dark:caret-white overflow-y-hidden\"\n        @input=\"onInput\"\n        @compositionend=\"onCompositionEnd\"\n      />\n    </div>\n  </div>\n</template>\n\n<style scoped>\n:deep(code),\ntextarea {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n  font-feature-settings: normal;\n  font-variation-settings: normal;\n  font-size: 1em;\n  text-wrap: wrap;\n  word-break: normal;\n  overflow-wrap: break-word;\n  display: block;\n  width: 100%;\n}\n:deep(pre.shiki) {\n  background-color: transparent;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/SideEditor.vue",
    "content": "<script setup lang=\"ts\">\nimport { throttledWatch, useEventListener } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useDynamicSlideInfo } from '../composables/useSlideInfo'\nimport { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'\nimport IconButton from './IconButton.vue'\nimport ShikiEditor from './ShikiEditor.vue'\n\nconst props = defineProps<{\n  resize?: boolean\n}>()\n\nconst { currentSlideNo, openInEditor } = useNav()\n\nconst tab = ref<'content' | 'note'>('content')\nconst content = ref('')\nconst note = ref('')\nconst dirty = ref(false)\n\nconst { info, update } = useDynamicSlideInfo(currentSlideNo)\n\nwatch(\n  info,\n  (v) => {\n    if (!isInputting.value) {\n      note.value = (v?.note || '').trim()\n      const frontmatterPart = v?.frontmatterRaw?.trim() ? `---\\n${v.frontmatterRaw.trim()}\\n---\\n\\n` : ''\n      content.value = frontmatterPart + (v?.source.contentRaw || '').trim()\n      dirty.value = false\n    }\n  },\n  { immediate: true },\n)\n\nasync function save() {\n  dirty.value = false\n\n  let frontmatterRaw: string | undefined\n  const contentOnly = content.value.trim().replace(/^---\\n([\\s\\S]*?)\\n---\\n/, (_, f) => {\n    frontmatterRaw = f\n    return ''\n  })\n\n  await update({\n    note: note.value || undefined,\n    content: contentOnly,\n    frontmatterRaw,\n  })\n}\n\nfunction close() {\n  showEditor.value = false\n}\n\nuseEventListener('keydown', (e) => {\n  if (activeElement.value?.tagName === 'TEXTAREA' && e.code === 'KeyS' && (e.ctrlKey || e.metaKey)) {\n    save()\n    e.preventDefault()\n  }\n})\n\nconst contentRef = computed({\n  get() { return content.value },\n  set(v) {\n    if (content.value.trim() !== v.trim())\n      dirty.value = true\n    content.value = v\n  },\n})\n\nconst noteRef = computed({\n  get() { return note.value },\n  set(v) {\n    note.value = v\n    dirty.value = true\n  },\n})\n\nconst handlerDown = ref(false)\nfunction onHandlerDown() {\n  handlerDown.value = true\n}\nfunction updateSize(v?: number) {\n  if (vertical.value)\n    editorHeight.value = Math.min(Math.max(300, v ?? editorHeight.value), window.innerHeight - 200)\n  else\n    editorWidth.value = Math.min(Math.max(318, v ?? editorWidth.value), window.innerWidth - 200)\n}\nfunction switchTab(newTab: typeof tab.value) {\n  tab.value = newTab\n  // @ts-expect-error force cast\n  document.activeElement?.blur?.()\n}\n\nif (props.resize) {\n  useEventListener('pointermove', (e) => {\n    if (handlerDown.value) {\n      updateSize(vertical.value\n        ? window.innerHeight - e.pageY\n        : window.innerWidth - e.pageX)\n    }\n  }, { passive: true })\n  useEventListener('pointerup', () => {\n    handlerDown.value = false\n  })\n  useEventListener('resize', () => {\n    updateSize()\n  })\n}\n\nthrottledWatch(\n  [content, note],\n  () => {\n    if (dirty.value)\n      save()\n  },\n  { throttle: 500 },\n)\n</script>\n\n<template>\n  <div\n    v-if=\"resize\" class=\"fixed bg-gray-400 select-none opacity-0 hover:opacity-10 z-dragging\"\n    :class=\"vertical ? 'left-0 right-0 w-full h-10px' : 'top-0 bottom-0 w-10px h-full'\" :style=\"{\n      opacity: handlerDown ? '0.3' : undefined,\n      bottom: vertical ? `${editorHeight - 5}px` : undefined,\n      right: !vertical ? `${editorWidth - 5}px` : undefined,\n      cursor: vertical ? 'row-resize' : 'col-resize',\n    }\" @pointerdown=\"onHandlerDown\"\n  />\n  <div\n    class=\"shadow bg-main p-2 pt-4 grid grid-rows-[max-content_1fr] h-full overflow-hidden\"\n    :class=\"resize ? 'border-l border-gray-400 border-opacity-20' : ''\"\n    :style=\"resize ? {\n      height: vertical ? `${editorHeight}px` : undefined,\n      width: !vertical ? `${editorWidth}px` : undefined,\n    } : {}\"\n  >\n    <div class=\"flex pb-2 text-xl -mt-1\">\n      <div class=\"mr-4 rounded flex\">\n        <IconButton\n          title=\"Switch to content tab\" :class=\"tab === 'content' ? 'text-primary' : ''\"\n          @click=\"switchTab('content')\"\n        >\n          <div class=\"i-carbon:account\" />\n        </IconButton>\n        <IconButton\n          title=\"Switch to notes tab\" :class=\"tab === 'note' ? 'text-primary' : ''\"\n          @click=\"switchTab('note')\"\n        >\n          <div class=\"i-carbon:align-box-bottom-right\" />\n        </IconButton>\n      </div>\n      <span class=\"text-2xl pt-1\">\n        {{ tab === 'content' ? 'Slide' : 'Notes' }}\n      </span>\n      <div class=\"flex-auto\" />\n      <template v-if=\"resize\">\n        <IconButton v-if=\"vertical\" title=\"Dock to right\" @click=\"vertical = false\">\n          <div class=\"i-carbon:open-panel-right\" />\n        </IconButton>\n        <IconButton v-else title=\"Dock to bottom\" @click=\"vertical = true\">\n          <div class=\"i-carbon:open-panel-bottom\" />\n        </IconButton>\n      </template>\n      <IconButton title=\"Open in editor\" @click=\"openInEditor()\">\n        <div class=\"i-carbon:launch\" />\n      </IconButton>\n      <IconButton title=\"Close\" @click=\"close\">\n        <div class=\"i-carbon:close\" />\n      </IconButton>\n    </div>\n    <div class=\"relative overflow-hidden rounded\" style=\"background-color: var(--slidev-code-background)\">\n      <ShikiEditor v-show=\"tab === 'content'\" v-model=\"contentRef\" placeholder=\"Create slide content...\" />\n      <ShikiEditor v-show=\"tab === 'note'\" v-model=\"noteRef\" placeholder=\"Write some notes...\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/SlideContainer.vue",
    "content": "<script setup lang=\"ts\">\nimport { provideLocal, useElementSize } from '@vueuse/core'\nimport { computed, onUnmounted, ref, watchEffect } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { injectionSlideElement, injectionSlideScale } from '../constants'\nimport { slideAspect, slideHeight, slideWidth } from '../env'\nimport { isDark } from '../logic/dark'\nimport { snapshotManager } from '../logic/snapshot'\nimport { slideScale } from '../state'\n\nconst props = defineProps({\n  width: {\n    type: Number,\n  },\n  meta: {\n    default: () => ({}) as any,\n  },\n  isMain: {\n    type: Boolean,\n    default: false,\n  },\n  no: {\n    type: Number,\n    required: false,\n  },\n  useSnapshot: {\n    type: Boolean,\n    default: false,\n  },\n  contentStyle: {\n    type: Object,\n    default: () => ({}),\n  },\n})\n\nconst { isPrintMode } = useNav()\n\nconst container = ref<HTMLDivElement | null>(null)\nconst containerSize = useElementSize(container)\nconst slideElement = ref<HTMLElement | null>(null)\n\nconst width = computed(() => props.width ?? containerSize.width.value)\nconst height = computed(() => props.width ? props.width / slideAspect.value : containerSize.height.value)\n\nconst scale = computed(() => {\n  if (slideScale.value && !isPrintMode.value)\n    return +slideScale.value\n  return Math.min(width.value / slideWidth.value, height.value / slideHeight.value)\n})\n\nconst contentStyle = computed(() => ({\n  ...props.contentStyle,\n  'height': `${slideHeight.value}px`,\n  'width': `${slideWidth.value}px`,\n  '--slidev-slide-scale': scale.value,\n}))\n\nconst containerStyle = computed(() => props.width\n  ? {\n      width: `${props.width}px`,\n      height: `${props.width / slideAspect.value}px`,\n    }\n  : {},\n)\n\nif (props.isMain) {\n  const rootStyle = document.documentElement.style\n  watchEffect(() => rootStyle.setProperty('--slidev-slide-scale', scale.value.toString()))\n  onUnmounted(() => rootStyle.removeProperty('--slidev-slide-scale'))\n}\n\nprovideLocal(injectionSlideScale, scale)\nprovideLocal(injectionSlideElement, slideElement)\n\nconst snapshot = computed(() => {\n  if (props.no == null || !props.useSnapshot)\n    return undefined\n  return snapshotManager.getSnapshot(props.no, isDark.value)\n})\n</script>\n\n<template>\n  <div\n    v-if=\"!snapshot\"\n    :id=\"isMain ? 'slide-container' : undefined\"\n    ref=\"container\"\n    class=\"slidev-slide-container\"\n    :style=\"containerStyle\"\n  >\n    <div\n      :id=\"isMain ? 'slide-content' : undefined\"\n      ref=\"slideElement\"\n      class=\"slidev-slide-content\"\n      :style=\"contentStyle\"\n    >\n      <slot />\n    </div>\n    <slot name=\"controls\" />\n  </div>\n  <!-- Image Snapshot -->\n  <div v-else class=\"slidev-slide-container w-full h-full relative\">\n    <img\n      :src=\"snapshot\"\n      class=\"w-full h-full object-cover\"\n      :style=\"containerStyle\"\n    >\n    <div absolute bottom-1 right-1 p0.5 text-cyan:75 bg-cyan:10 rounded title=\"Snapshot\">\n      <div class=\"i-carbon-camera\" />\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.slidev-slide-container {\n  @apply relative w-full h-full overflow-hidden;\n}\n\n.slidev-slide-content {\n  --slidev-slide-container-scale: var(--slidev-slide-scale);\n  transform: translate(-50%, -50%) scale(var(--slidev-slide-scale));\n  @apply absolute left-1/2 top-1/2 overflow-hidden bg-main;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/SlideLoading.vue",
    "content": "<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\nconst timeout = ref(false)\nonMounted(() => {\n  setTimeout(() => {\n    timeout.value = true\n  }, 200)\n})\n</script>\n\n<template>\n  <div class=\"h-full w-full flex items-center justify-center gap-2 slidev-slide-loading\">\n    <template v-if=\"timeout\">\n      <div class=\"i-svg-spinners-90-ring-with-bg text-xl\" />\n      <div>Loading slide...</div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/SlideWrapper.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'\nimport type { CSSProperties, PropType } from 'vue'\nimport { SlideBottom, SlideTop } from '#slidev/global-layers'\nimport { provideLocal } from '@vueuse/core'\nimport { computed, ref, toRef } from 'vue'\nimport { injectionClicksContext, injectionCurrentPage, injectionFrontmatter, injectionRenderContext, injectionRoute, injectionSlideZoom } from '../constants'\nimport { configs } from '../env'\nimport { getSlideClass } from '../utils'\n\nconst props = defineProps({\n  clicksContext: {\n    type: Object as PropType<ClicksContext>,\n    required: true,\n  },\n  renderContext: {\n    type: String as PropType<RenderContext>,\n    default: 'slide',\n  },\n  route: {\n    type: Object as PropType<SlideRoute>,\n    required: true,\n  },\n})\n\nconst zoom = computed(() => props.route.meta?.slide?.frontmatter.zoom ?? 1)\n\nprovideLocal(injectionRoute, props.route)\nprovideLocal(injectionFrontmatter, props.route.meta.slide.frontmatter)\nprovideLocal(injectionCurrentPage, ref(props.route.no))\nprovideLocal(injectionRenderContext, ref(props.renderContext))\nprovideLocal(injectionClicksContext, toRef(props, 'clicksContext'))\nprovideLocal(injectionSlideZoom, zoom)\n\nconst style = computed<CSSProperties>(() => ({\n  'user-select': configs.selectable ? undefined : 'none',\n  '--slidev-slide-zoom-scale': zoom.value === 1 ? undefined : zoom.value,\n}))\n</script>\n\n<template>\n  <div\n    :data-slidev-no=\"props.route.no\"\n    :class=\"getSlideClass(route, ['slide', 'presenter'].includes(props.renderContext) ? '' : 'disable-view-transition')\"\n    :style=\"style\"\n    :lang=\"props.route.meta.slide.frontmatter.lang\"\n  >\n    <SlideBottom />\n    <component :is=\"props.route.component\" />\n    <SlideTop />\n  </div>\n</template>\n\n<style scoped>\n.disable-view-transition:deep(*) {\n  view-transition-name: none !important;\n}\n\n.slidev-page {\n  position: absolute;\n  inset: 0;\n\n  /* Zoom handling */\n  --slidev-slide-zoom-scale: 1;\n  width: calc(100% / var(--slidev-slide-zoom-scale));\n  height: calc(100% / var(--slidev-slide-zoom-scale));\n  transform-origin: top left;\n  scale: var(--slidev-slide-zoom-scale);\n  /* slide scale = container scale * zoom scale */\n  --slidev-slide-scale: calc(var(--slidev-slide-container-scale) * var(--slidev-slide-zoom-scale));\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/SlidesShow.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SlideRoute } from '@slidev/types'\nimport { GlobalBottom, GlobalTop } from '#slidev/global-layers'\nimport { recomputeAllPoppers } from 'floating-vue'\nimport { computed, shallowRef, TransitionGroup, watchEffect } from 'vue'\nimport { createFixedClicks } from '../composables/useClicks'\nimport { useNav } from '../composables/useNav'\nimport { usePreloadImages } from '../composables/usePreloadImages'\nimport { useViewTransition } from '../composables/useViewTransition'\nimport { CLICKS_MAX } from '../constants'\nimport { activeDragElement, disableTransition, hmrSkipTransition } from '../state'\nimport DragControl from './DragControl.vue'\nimport SlideWrapper from './SlideWrapper.vue'\n\ndefineProps<{\n  renderContext: 'slide' | 'presenter'\n}>()\n\nconst {\n  currentSlideRoute,\n  currentTransition,\n  getPrimaryClicks,\n  prevRoute,\n  nextRoute,\n  slides,\n  isPrintMode,\n  isPrintWithClicks,\n  clicksDirection,\n  printRange,\n} = useNav()\n\nfunction preloadRoute(route: SlideRoute) {\n  if (route.meta.preload !== false) {\n    route.meta.__preloaded = true\n    route.load()\n  }\n}\n// preload current, prev and next slides\nwatchEffect(() => {\n  preloadRoute(currentSlideRoute.value)\n  preloadRoute(prevRoute.value)\n  preloadRoute(nextRoute.value)\n})\n// preload all slides after 3s\nwatchEffect((onCleanup) => {\n  const routes = slides.value\n  const timeout = setTimeout(() => {\n    routes.forEach(preloadRoute)\n  }, 3000)\n  onCleanup(() => clearTimeout(timeout))\n})\n\n// preload images for nearby slides\nusePreloadImages(currentSlideRoute, prevRoute, nextRoute, slides)\n\nconst hasViewTransition = useViewTransition()\n\nconst DrawingLayer = shallowRef<any>()\nif (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)\n  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)\n\nconst loadedRoutes = computed(() => isPrintMode.value\n  ? printRange.value.map(no => slides.value[no - 1])\n  : slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value),\n)\n\nfunction onAfterLeave() {\n  // After transition, we disable it so HMR won't trigger it again\n  // We will turn it back on `nav.go` so the normal navigation would still work\n  hmrSkipTransition.value = true\n  // recompute poppers after transition\n  recomputeAllPoppers()\n}\n</script>\n\n<template>\n  <!-- Global Bottom -->\n  <GlobalBottom />\n\n  <!-- Slides -->\n  <component\n    :is=\"(hasViewTransition && !isPrintMode && !hmrSkipTransition && !disableTransition) ? 'div' : TransitionGroup\"\n    v-bind=\"(hmrSkipTransition || disableTransition || isPrintMode) ? {} : currentTransition\"\n    id=\"slideshow\"\n    tag=\"div\"\n    :class=\"{\n      'slidev-nav-go-forward': clicksDirection > 0,\n      'slidev-nav-go-backward': clicksDirection < 0,\n    }\"\n    @after-leave=\"onAfterLeave\"\n  >\n    <template v-for=\"route of loadedRoutes\" :key=\"route.no\">\n      <SlideWrapper\n        v-show=\"route === currentSlideRoute\"\n        :clicks-context=\"isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)\"\n        :route=\"route\"\n        :render-context=\"renderContext\"\n      />\n    </template>\n  </component>\n\n  <DragControl v-if=\"activeDragElement\" :data=\"activeDragElement\" />\n\n  <!-- Global Top -->\n  <GlobalTop />\n\n  <template v-if=\"(__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__) && DrawingLayer\">\n    <DrawingLayer />\n  </template>\n</template>\n\n<style scoped>\n#slideshow {\n  height: 100%;\n}\n</style>\n"
  },
  {
    "path": "packages/client/internals/SyncControls.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { syncDirections } from '../state'\nimport IconButton from './IconButton.vue'\nimport MenuButton from './MenuButton.vue'\nimport SelectList from './SelectList.vue'\n\nconst { isPresenter } = useNav()\n\nconst shouldReceive = computed({\n  get: () => isPresenter.value\n    ? syncDirections.value.presenterReceive\n    : syncDirections.value.viewerReceive,\n  set(v) {\n    if (isPresenter.value) {\n      syncDirections.value.presenterReceive = v\n    }\n    else {\n      syncDirections.value.viewerReceive = v\n    }\n  },\n})\n\nconst shouldSend = computed({\n  get: () => isPresenter.value\n    ? syncDirections.value.presenterSend\n    : syncDirections.value.viewerSend,\n  set(v) {\n    if (isPresenter.value) {\n      syncDirections.value.presenterSend = v\n    }\n    else {\n      syncDirections.value.viewerSend = v\n    }\n  },\n})\n\nconst state = computed({\n  get: () => {\n    if (shouldReceive.value && shouldSend.value) {\n      return 'bidirectional'\n    }\n    if (shouldReceive.value && !shouldSend.value) {\n      return 'receive-only'\n    }\n    if (!shouldReceive.value && shouldSend.value) {\n      return 'send-only'\n    }\n    return 'off'\n  },\n  set(v) {\n    switch (v) {\n      case 'bidirectional':\n        shouldReceive.value = true\n        shouldSend.value = true\n        break\n      case 'receive-only':\n        shouldReceive.value = true\n        shouldSend.value = false\n        break\n      case 'send-only':\n        shouldReceive.value = false\n        shouldSend.value = true\n        break\n      case 'off':\n        shouldReceive.value = false\n        shouldSend.value = false\n        break\n    }\n  },\n})\n</script>\n\n<template>\n  <MenuButton>\n    <template #button>\n      <IconButton title=\"Change sync settings\">\n        <div class=\"i-ph:arrow-up-bold mx--1.2 scale-x-80\" :class=\"shouldSend ? 'text-green6 dark:text-green' : 'op30'\" />\n        <div class=\"i-ph:arrow-down-bold mx--1.2 scale-x-80\" :class=\"shouldReceive ? 'text-green6 dark:text-green' : 'op30'\" />\n      </IconButton>\n    </template>\n    <template #menu>\n      <div text-sm flex=\"~ col gap-2\">\n        <div px3 ws-nowrap>\n          <span op75>Slides navigation syncing for </span>\n          <span font-bold text-primary>{{ isPresenter ? 'presenter' : 'viewer' }}</span>\n        </div>\n        <div class=\"h-1px opacity-10 bg-current w-full\" />\n        <SelectList\n          v-model=\"state\"\n          title=\"Sync Mode\"\n          :items=\"[\n            { value: 'bidirectional', display: 'Bidirectional Sync' },\n            { value: 'receive-only', display: 'Receive Only' },\n            { value: 'send-only', display: 'Send Only' },\n            { value: 'off', display: 'Disable' },\n          ]\"\n        />\n      </div>\n    </template>\n  </MenuButton>\n</template>\n"
  },
  {
    "path": "packages/client/internals/TimerBar.vue",
    "content": "<script setup lang=\"ts\">\n// import { parseTimesplits } from '@slidev/parser/utils'\nimport { computed, reactive } from 'vue'\n// import { useNav } from '../composables/useNav'\nimport { useTimer } from '../composables/useTimer'\n\n// const { slides } = useNav()\n\nconst timer = reactive(useTimer())\n// TODO: timesplit\n// const slidesWithTimesplits = computed(() => slides.value.filter(i => i.meta.slide?.frontmatter.timesplit))\n\n// const _timesplits = computed(() => {\n//   const parsed = parseTimesplits(\n//     slidesWithTimesplits.value\n//       .map(i => ({ no: i.no, timesplit: i.meta.slide?.frontmatter.timesplit as string })),\n//   )\n//   return parsed\n// })\n\n// TODO: maybe make it configurable, or somehow more smart\nconst color = computed(() => {\n  if (timer.status === 'stopped')\n    return 'op50'\n  if (timer.status === 'paused')\n    return 'bg-blue'\n\n  if (timer.percentage > 100)\n    return 'bg-red'\n  else if (timer.percentage > 80)\n    return 'bg-yellow'\n  else\n    return 'bg-green'\n})\n</script>\n\n<template>\n  <div\n    class=\"border-b mt-px border-main relative flex h-4px\"\n  >\n    <div\n      v-if=\"timer.status !== 'stopped'\"\n      class=\"h-4px\"\n      :class=\"color\"\n      :style=\"{ width: `${timer.percentage}%` }\"\n    />\n    <!-- {{ timesplits }} -->\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/TimerInlined.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTimer } from '../composables/useTimer'\n\nconst { status, percentage, mode, timer, reset, toggle } = useTimer()\n\nconst color = computed(() => {\n  if (status.value === 'stopped')\n    return 'op50'\n  if (status.value === 'paused')\n    return 'text-blue6 dark:text-blue3'\n\n  if (percentage.value > 100)\n    return 'text-red6 dark:text-red3'\n  else if (percentage.value > 80)\n    return 'text-yellow6 dark:text-yellow3'\n  else\n    return 'text-green6 dark:text-green3'\n})\n</script>\n\n<template>\n  <div\n    class=\"group flex items-center justify-center pl-4 select-none\"\n    :class=\"color\"\n  >\n    <div class=\"w-22px cursor-pointer\">\n      <div\n        class=\"group-hover:hidden text-2xl\"\n        :class=\"mode === 'countdown' ? 'i-carbon:timer' : 'i-carbon:time'\"\n      />\n      <div class=\"group-not-hover:hidden flex flex-col items-center\">\n        <div class=\"relative op-80 hover:op-100\" @click=\"toggle\">\n          <div v-if=\"status === 'running'\" class=\"i-carbon:pause text-lg\" />\n          <div v-else class=\"i-carbon:play\" />\n        </div>\n        <div class=\"op-80 hover:op-100\" @click=\"reset\">\n          <div class=\"i-carbon:renew\" />\n        </div>\n      </div>\n    </div>\n    <div class=\"text-3xl px-3 my-auto font-mono\">\n      <template v-if=\"timer.h\">\n        <span>{{ timer.h }}</span>\n        <span op50>:</span>\n      </template>\n      <span>{{ timer.m }}</span>\n      <span op50>:</span>\n      <span>{{ timer.s }}</span>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/TitleIcon.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title: string\n}>()\n\nconst builtinIcons: Record<string, string> = {\n  // package managers\n  'pnpm': 'i-vscode-icons:file-type-light-pnpm',\n  'npm': 'i-vscode-icons:file-type-npm',\n  'yarn': 'i-vscode-icons:file-type-yarn',\n  'bun': 'i-vscode-icons:file-type-bun',\n  'deno': 'i-vscode-icons:file-type-deno',\n  // frameworks\n  'vue': 'i-vscode-icons:file-type-vue',\n  'svelte': 'i-vscode-icons:file-type-svelte',\n  'angular': 'i-vscode-icons:file-type-angular',\n  'react': 'i-vscode-icons:file-type-reactjs',\n  'next': 'i-vscode-icons:file-type-light-next',\n  'nuxt': 'i-vscode-icons:file-type-nuxt',\n  'solid': 'logos:solidjs-icon',\n  'astro': 'i-vscode-icons:file-type-light-astro',\n  // bundlers\n  'rollup': 'i-vscode-icons:file-type-rollup',\n  'webpack': 'i-vscode-icons:file-type-webpack',\n  'vite': 'i-vscode-icons:file-type-vite',\n  'esbuild': 'i-vscode-icons:file-type-esbuild',\n  // configuration files\n  'package.json': 'i-vscode-icons:file-type-node',\n  'tsconfig.json': 'i-vscode-icons:file-type-tsconfig',\n  '.npmrc': 'i-vscode-icons:file-type-npm',\n  '.editorconfig': 'i-vscode-icons:file-type-editorconfig',\n  '.eslintrc': 'i-vscode-icons:file-type-eslint',\n  '.eslintignore': 'i-vscode-icons:file-type-eslint',\n  'eslint.config': 'i-vscode-icons:file-type-eslint',\n  '.gitignore': 'i-vscode-icons:file-type-git',\n  '.gitattributes': 'i-vscode-icons:file-type-git',\n  '.env': 'i-vscode-icons:file-type-dotenv',\n  '.env.example': 'i-vscode-icons:file-type-dotenv',\n  '.vscode': 'i-vscode-icons:file-type-vscode',\n  'tailwind.config': 'vscode-icons:file-type-tailwind',\n  'uno.config': 'i-vscode-icons:file-type-unocss',\n  'unocss.config': 'i-vscode-icons:file-type-unocss',\n  '.oxlintrc': 'i-vscode-icons:file-type-oxlint',\n  'vue.config': 'i-vscode-icons:file-type-vueconfig',\n  // filename extensions\n  '.mts': 'i-vscode-icons:file-type-typescript',\n  '.cts': 'i-vscode-icons:file-type-typescript',\n  '.ts': 'i-vscode-icons:file-type-typescript',\n  '.tsx': 'i-vscode-icons:file-type-typescript',\n  '.mjs': 'i-vscode-icons:file-type-js',\n  '.cjs': 'i-vscode-icons:file-type-js',\n  '.json': 'i-vscode-icons:file-type-json',\n  '.js': 'i-vscode-icons:file-type-js',\n  '.jsx': 'i-vscode-icons:file-type-js',\n  '.md': 'i-vscode-icons:file-type-markdown',\n  '.py': 'i-vscode-icons:file-type-python',\n  '.ico': 'i-vscode-icons:file-type-favicon',\n  '.html': 'i-vscode-icons:file-type-html',\n  '.css': 'i-vscode-icons:file-type-css',\n  '.scss': 'i-vscode-icons:file-type-scss',\n  '.yml': 'i-vscode-icons:file-type-light-yaml',\n  '.yaml': 'i-vscode-icons:file-type-light-yaml',\n  '.php': 'i-vscode-icons:file-type-php',\n  '.svg': 'i-vscode-icons:file-type-svg',\n}\n\nfunction matchIcon(title: string) {\n  const colonMatch = title.match(/~([^~]+)~/g)\n  if (colonMatch && colonMatch.length > 0) {\n    const icon = colonMatch[0].slice(1, -1)\n    return icon\n  }\n\n  const sortedKeys = Object.keys(builtinIcons).sort((a, b) => b.length - a.length)\n  for (const key of sortedKeys) {\n    if (title.toLowerCase().includes(key.toLowerCase())) {\n      return builtinIcons[key]\n    }\n  }\n  return ''\n}\n</script>\n\n<template>\n  <div v-if=\"matchIcon(title)\" :class=\"`${matchIcon(title)} w-3.5 h-3.5 relative`\" />\n</template>\n"
  },
  {
    "path": "packages/client/internals/VerticalDivider.vue",
    "content": "<template>\n  <div class=\"w-1px opacity-10 bg-current m-1 lg:m-2\" />\n</template>\n"
  },
  {
    "path": "packages/client/internals/WebCamera.vue",
    "content": "<script setup lang=\"ts\">\nimport { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'\nimport { computed, onMounted, ref, watchEffect } from 'vue'\nimport { recorder } from '../logic/recording'\nimport { currentCamera } from '../state'\n\nconst size = useLocalStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))\nconst position = useLocalStorage('slidev-webcam-pos', {\n  x: window.innerWidth - size.value - 30,\n  y: window.innerHeight - size.value - 30,\n}, { deep: true })\n\nconst frame = ref<HTMLDivElement | undefined>()\nconst handler = ref<HTMLDivElement | undefined>()\nconst video = ref<HTMLVideoElement | undefined>()\n\nconst { streamCamera, showAvatar } = recorder\n\nconst { style: containerStyle } = useDraggable(frame, {\n  initialValue: position,\n  onMove({ x, y }) {\n    position.value.x = x\n    position.value.y = y\n  },\n})\nconst { isDragging: handlerDown } = useDraggable(handler, {\n  onMove({ x, y }) {\n    size.value = Math.max(10, Math.min(x - position.value.x, y - position.value.y) / 0.8536)\n  },\n})\n\nwatchEffect(() => {\n  if (video.value)\n    video.value.srcObject = streamCamera.value!\n}, { flush: 'post' })\n\nconst frameStyle = computed(() => ({\n  width: `${size.value}px`,\n  height: `${size.value}px`,\n}))\n\nconst handleStyle = computed(() => ({\n  width: '14px',\n  height: '14px',\n  // 0.5 + 0.5 / sqrt(2)\n  top: `${size.value * 0.8536 - 7}px`,\n  left: `${size.value * 0.8536 - 7}px`,\n  cursor: 'nwse-resize',\n}))\n\nfunction fixPosition() {\n  // move back if the camera is outside of the canvas\n  if (position.value.x >= window.innerWidth)\n    position.value.x = window.innerWidth - size.value - 30\n  if (position.value.y >= window.innerHeight)\n    position.value.y = window.innerHeight - size.value - 30\n}\n\nuseEventListener('resize', fixPosition)\nonMounted(fixPosition)\n</script>\n\n<template>\n  <div\n    v-if=\"streamCamera && showAvatar && currentCamera !== 'none'\"\n    class=\"fixed z-camera\"\n    :style=\"containerStyle\"\n  >\n    <div\n      ref=\"frame\"\n      class=\"rounded-full shadow bg-gray-400 bg-opacity-10 overflow-hidden object-cover\"\n      :style=\"frameStyle\"\n    >\n      <video\n        ref=\"video\"\n        autoplay\n        muted\n        volume=\"0\"\n        class=\"object-cover min-w-full min-h-full rounded-full\"\n        style=\"transform: rotateY(180deg);\"\n      />\n    </div>\n\n    <div\n      ref=\"handler\"\n      class=\"absolute bottom-0 right-0 rounded-full bg-main shadow opacity-0 shadow z-dragging hover:opacity-100 dark:border dark:border-true-gray-700\"\n      :style=\"handleStyle\"\n      :class=\"handlerDown ? '!opacity-100' : ''\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/internals/types.ts",
    "content": "export interface SelectionItem<T> {\n  value: T\n  display: string\n  onClick?: () => void\n}\n"
  },
  {
    "path": "packages/client/layoutHelper.ts",
    "content": "import type { CSSProperties } from 'vue'\n\n/**\n * Resolve urls from frontmatter and append with the base url\n */\nexport function resolveAssetUrl(url: string) {\n  if (url.startsWith('/'))\n    return import.meta.env.BASE_URL + url.slice(1)\n  return url\n}\n\nexport function handleBackground(background?: string, dim = false, backgroundSize = 'cover'): CSSProperties {\n  const isColor = background && (background[0] === '#' || background.startsWith('rgb'))\n\n  const style = {\n    background: isColor\n      ? background\n      : undefined,\n    color: (background && !isColor)\n      ? 'white'\n      : undefined,\n    backgroundImage: isColor\n      ? undefined\n      : background\n        ? dim\n          ? `linear-gradient(#0005, #0008), url(${resolveAssetUrl(background)})`\n          : `url(\"${resolveAssetUrl(background)}\")`\n        : undefined,\n    backgroundRepeat: 'no-repeat',\n    backgroundPosition: 'center',\n    backgroundSize,\n  }\n\n  if (!style.background)\n    delete style.background\n\n  return style\n}\n"
  },
  {
    "path": "packages/client/layouts/404.vue",
    "content": "<template>\n  <div class=\"px-4 py-10 text-center text-teal-700 dark:text-gray-200\">\n    404\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/center.vue",
    "content": "<template>\n  <div class=\"slidev-layout center h-full grid place-content-center\">\n    <div class=\"my-auto\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/cover.vue",
    "content": "<template>\n  <div class=\"slidev-layout cover\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/default.vue",
    "content": "<template>\n  <div class=\"slidev-layout default\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/end.vue",
    "content": "<template>\n  <div class=\"slidev-layout end\">\n    <slot>END</slot>\n  </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.slidev-layout.end {\n  @apply text-white text-opacity-85 text-xl tracking-widest bg-black h-full text-center grid place-content-center select-none;\n}\n</style>\n"
  },
  {
    "path": "packages/client/layouts/error.vue",
    "content": "<template>\n  <div class=\"px-4 py-10 text-center text-red-700 dark:text-red-500 font-bold font-mono\">\n    {{\n      __SLIDEV_HAS_SERVER__\n        ? 'An error occurred on this slide. Check the terminal for more information.'\n        : 'Failed to fetch this slide. Please check your network connection.'\n    }}\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/fact.vue",
    "content": "<template>\n  <div class=\"slidev-layout fact\">\n    <div class=\"my-auto\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/full.vue",
    "content": "<template>\n  <div class=\"slidev-layout full w-full h-full\">\n    <slot class=\"w-full h-full\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/iframe-left.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  url: string\n  scale?: number\n}>()\n\nconst scaleInvertPercent = computed(() => `${(1 / (props.scale || 1)) * 100}%`)\n</script>\n\n<template>\n  <div class=\"grid grid-cols-2 w-full h-full\">\n    <div relative :style=\"{ width: scaleInvertPercent, height: scaleInvertPercent }\">\n      <iframe\n        id=\"frame\" class=\"w-full h-full\"\n        :src=\"url\"\n        :style=\"scale ? { transform: `scale(${scale})`, transformOrigin: 'top left' } : {}\"\n      />\n    </div>\n    <div class=\"slidev-layout default\" v-bind=\"$attrs\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/iframe-right.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  url: string\n  scale?: number\n}>()\n\nconst scaleInvertPercent = computed(() => `${(1 / (props.scale || 1)) * 100}%`)\n</script>\n\n<template>\n  <div class=\"grid grid-cols-2 w-full h-full\">\n    <div class=\"slidev-layout default\" v-bind=\"$attrs\">\n      <slot />\n    </div>\n    <div relative :style=\"{ width: scaleInvertPercent, height: scaleInvertPercent }\">\n      <iframe\n        id=\"frame\" class=\"w-full h-full\"\n        :src=\"url\"\n        :style=\"scale ? { transform: `scale(${scale})`, transformOrigin: 'top left' } : {}\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/iframe.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n  url: string\n  scale?: number\n}>()\n\nconst scaleInvertPercent = computed(() => `${(1 / (props.scale || 1)) * 100}%`)\n</script>\n\n<template>\n  <div class=\"h-full w-full\">\n    <div relative :style=\"{ width: scaleInvertPercent, height: scaleInvertPercent }\">\n      <iframe\n        id=\"frame\" class=\"w-full h-full\"\n        :src=\"url\"\n        :style=\"scale ? { transform: `scale(${scale})`, transformOrigin: 'top left' } : {}\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/image-left.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { handleBackground } from '../layoutHelper'\n\nconst props = defineProps({\n  image: {\n    type: String,\n  },\n  class: {\n    type: String,\n  },\n  backgroundSize: {\n    type: String,\n    default: 'cover',\n  },\n})\n\nconst style = computed(() => handleBackground(props.image, false, props.backgroundSize))\n</script>\n\n<template>\n  <div class=\"grid grid-cols-2 w-full h-full auto-rows-fr\">\n    <div class=\"w-full h-full\" :style=\"style\" />\n    <div class=\"slidev-layout default\" :class=\"props.class\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/image-right.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { handleBackground } from '../layoutHelper'\n\nconst props = defineProps({\n  image: {\n    type: String,\n  },\n  class: {\n    type: String,\n  },\n  backgroundSize: {\n    type: String,\n    default: 'cover',\n  },\n})\n\nconst style = computed(() => handleBackground(props.image, false, props.backgroundSize))\n</script>\n\n<template>\n  <div class=\"grid grid-cols-2 w-full h-full auto-rows-fr\">\n    <div class=\"slidev-layout default\" :class=\"props.class\">\n      <slot />\n    </div>\n    <div class=\"w-full h-full\" :style=\"style\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/image.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { handleBackground } from '../layoutHelper'\n\nconst props = defineProps({\n  image: {\n    type: String,\n  },\n  backgroundSize: {\n    type: String,\n    default: 'cover',\n  },\n})\n\nconst style = computed(() => handleBackground(props.image, false, props.backgroundSize))\n</script>\n\n<template>\n  <div class=\"slidev-layout w-full h-full\" :style=\"style\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/intro.vue",
    "content": "<template>\n  <div class=\"slidev-layout intro\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/none.vue",
    "content": "<template>\n  <div>\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/quote.vue",
    "content": "<template>\n  <div class=\"slidev-layout quote\">\n    <div class=\"my-auto\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/section.vue",
    "content": "<template>\n  <div class=\"slidev-layout section\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/statement.vue",
    "content": "<template>\n  <div class=\"slidev-layout statement\">\n    <div class=\"my-auto\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/layouts/two-cols-header.vue",
    "content": "<!--\n  Usage:\n```md\n---\nlayout: two-cols-header\n---\nThis spans both\n::left::\n# Left\nThis shows on the left\n::right::\n# Right\nThis shows on the right\n::bottom::\nThis shows at the bottom, aligned to the end (bottom) of the grid\n\n<style>\n.two-cols-header {\n  column-gap: 20px; /* Adjust the gap size as needed */\n}\n</style>\n```\n-->\n\n<script setup lang=\"ts\">\nconst props = defineProps({\n  class: {\n    type: String,\n  },\n  layoutClass: {\n    type: String,\n  },\n})\n</script>\n\n<template>\n  <div class=\"slidev-layout two-cols-header w-full h-full\" :class=\"layoutClass\">\n    <div class=\"col-header\">\n      <slot />\n    </div>\n    <div class=\"col-left\" :class=\"props.class\">\n      <slot name=\"left\" />\n    </div>\n    <div class=\"col-right\" :class=\"props.class\">\n      <slot name=\"right\" />\n    </div>\n    <div class=\"col-bottom\" :class=\"props.class\">\n      <slot name=\"bottom\" />\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.two-cols-header {\n  display: grid;\n  grid-template-columns: repeat(2, 1fr);\n  grid-template-rows: auto 1fr auto;\n}\n\n.col-header {\n  grid-area: 1 / 1 / 2 / 3;\n}\n.col-left {\n  grid-area: 2 / 1 / 3 / 2;\n}\n.col-right {\n  grid-area: 2 / 2 / 3 / 3;\n}\n.col-bottom {\n  align-self: end;\n  grid-area: 3 / 1 / 3 / 3;\n}\n</style>\n"
  },
  {
    "path": "packages/client/layouts/two-cols.vue",
    "content": "<!--\n  Usage:\n\n```md\n---\nlayout: two-cols\n---\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right\n```\n\n-->\n\n<script setup lang=\"ts\">\nconst props = defineProps({\n  class: {\n    type: String,\n  },\n  layoutClass: {\n    type: String,\n  },\n})\n</script>\n\n<template>\n  <div class=\"slidev-layout two-columns w-full h-full grid grid-cols-2\" :class=\"props.layoutClass\">\n    <div class=\"col-left\" :class=\"props.class\">\n      <slot />\n      <slot name=\"left\" />\n    </div>\n    <div class=\"col-right\" :class=\"props.class\">\n      <slot name=\"right\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/logic/color.ts",
    "content": "import { isDark } from './dark'\n\n/**\n * Predefined color map for matching the branding\n *\n * Accpet a 6-digit hex color string or a hue number\n * Hue numbers are preferred because they will adapt better contrast in light/dark mode\n *\n * Hue numbers reference:\n * - 0: red\n * - 30: orange\n * - 60: yellow\n * - 120: green\n * - 180: cyan\n * - 240: blue\n * - 270: purple\n */\nconst predefinedColorMap = {\n  error: 0,\n  client: 60,\n  Light: 60,\n  Dark: 240,\n} as Record<string, number>\n\nexport function getHashColorFromString(\n  name: string,\n  opacity: number | string = 1,\n) {\n  if (predefinedColorMap[name])\n    return getHsla(predefinedColorMap[name], opacity)\n\n  let hash = 0\n  for (let i = 0; i < name.length; i++)\n    hash = name.charCodeAt(i) + ((hash << 5) - hash)\n  const hue = hash % 360\n  return getHsla(hue, opacity)\n}\n\nexport function getHsla(\n  hue: number,\n  opacity: number | string = 1,\n) {\n  const saturation = hue === -1\n    ? 0\n    : isDark.value ? 50 : 100\n  const lightness = isDark.value ? 60 : 20\n  return `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`\n}\n\nexport function getPluginColor(name: string, opacity = 1): string {\n  if (predefinedColorMap[name]) {\n    const color = predefinedColorMap[name]\n    if (typeof color === 'number') {\n      return getHsla(color, opacity)\n    }\n    else {\n      if (opacity === 1)\n        return color\n      const opacityHex = Math.floor(opacity * 255).toString(16).padStart(2, '0')\n      return color + opacityHex\n    }\n  }\n  return getHashColorFromString(name, opacity)\n}\n"
  },
  {
    "path": "packages/client/logic/contextMenu.ts",
    "content": "import type { ContextMenuItem } from '@slidev/types'\nimport type { ComputedRef } from 'vue'\nimport { shallowRef } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { configs, mode } from '../env'\nimport setupContextMenu from '../setup/context-menu'\n\nexport const currentContextMenu = shallowRef<null | {\n  x: number\n  y: number\n  items: ComputedRef<ContextMenuItem[]>\n}>(null)\n\nexport function openContextMenu(x: number, y: number) {\n  currentContextMenu.value = {\n    x,\n    y,\n    items: setupContextMenu(),\n  }\n}\n\nexport function closeContextMenu() {\n  currentContextMenu.value = null\n}\n\nexport function onContextMenu(ev: MouseEvent) {\n  if (configs.contextMenu !== true && configs.contextMenu != null && configs.contextMenu !== mode)\n    return\n  if (ev.shiftKey || ev.defaultPrevented)\n    return\n\n  const { isEmbedded } = useNav()\n  if (isEmbedded.value)\n    return\n\n  openContextMenu(ev.pageX, ev.pageY)\n  ev.preventDefault()\n  ev.stopPropagation()\n}\n"
  },
  {
    "path": "packages/client/logic/dark.ts",
    "content": "import { isClient, useLocalStorage, usePreferredDark, useToggle } from '@vueuse/core'\nimport { computed, watch } from 'vue'\nimport { configs } from '../env'\n\nconst preferredDark = usePreferredDark()\nconst store = useLocalStorage('slidev-color-schema', 'auto')\n\nexport const isColorSchemaConfigured = computed(() => configs.colorSchema !== 'auto')\nexport const isDark = computed<boolean>({\n  get() {\n    if (isColorSchemaConfigured.value)\n      return configs.colorSchema === 'dark'\n    return store.value === 'auto'\n      ? preferredDark.value\n      : store.value === 'dark'\n  },\n  set(v) {\n    if (isColorSchemaConfigured.value)\n      return\n    store.value = v === preferredDark.value\n      ? 'auto'\n      : v ? 'dark' : 'light'\n  },\n})\n\nexport const toggleDark = useToggle(isDark)\n\nif (isClient) {\n  const CSS_DISABLE_TRANS = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'\n\n  watch(\n    isDark,\n    (v) => {\n      const style = window!.document.createElement('style')\n      style.appendChild(document.createTextNode(CSS_DISABLE_TRANS))\n      window!.document.head.appendChild(style)\n\n      const html = document.querySelector('html')!\n      html.classList.toggle('dark', v)\n      html.classList.toggle('light', !v)\n\n      // Calling getComputedStyle forces the browser to redraw\n      // @ts-expect-error unused variable\n      const _ = window!.getComputedStyle(style!).opacity\n      document.head.removeChild(style!)\n    },\n    { immediate: true },\n  )\n}\n"
  },
  {
    "path": "packages/client/logic/overview.ts",
    "content": "import { computed, ref } from 'vue'\nimport { slides } from './slides'\n\n// To have same format(.value) as max, wrap it with ref.\nconst min = ref(1)\nconst max = computed(() => slides.value.length)\n\nexport const currentOverviewPage = ref(0)\nexport const overviewRowCount = ref(0)\n\nexport function prevOverviewPage() {\n  if (currentOverviewPage.value > min.value)\n    currentOverviewPage.value -= 1\n}\n\nexport function nextOverviewPage() {\n  if (currentOverviewPage.value < max.value)\n    currentOverviewPage.value += 1\n}\n\nexport function upOverviewPage() {\n  if (currentOverviewPage.value > min.value) {\n    let current = currentOverviewPage.value - overviewRowCount.value\n    if (current < min.value)\n      current = min.value\n\n    currentOverviewPage.value = current\n  }\n}\n\nexport function downOverviewPage() {\n  if (currentOverviewPage.value < max.value) {\n    let current = currentOverviewPage.value + overviewRowCount.value\n    if (current > max.value)\n      current = max.value\n\n    currentOverviewPage.value = current\n  }\n}\n"
  },
  {
    "path": "packages/client/logic/recording.ts",
    "content": "import type RecorderType from 'recordrtc'\nimport type { Options as RecorderOptions } from 'recordrtc'\nimport type { Ref } from 'vue'\nimport { isTruthy } from '@antfu/utils'\nimport { useDevicesList, useEventListener, useLocalStorage } from '@vueuse/core'\nimport { nextTick, ref, shallowRef, watch } from 'vue'\nimport { currentCamera, currentMic } from '../state'\n\ntype Defined<T> = T extends undefined ? never : T\ntype MimeType = Defined<RecorderOptions['mimeType']>\n\nexport const recordingName = ref('')\nexport const recordCamera = ref(true)\nexport const mimeType = useLocalStorage<MimeType>('slidev-record-mimetype', 'video/webm')\nexport const frameRate = useLocalStorage<number>('slidev-record-framerate', 30)\nexport const bitRate = useLocalStorage<number>('slidev-record-bitrate', 8192)\nexport const resolution = useLocalStorage<string>('slidev-record-resolution', '1920x1080')\n\nexport const mimeExtMap: Record<string, string> = {\n  'video/webm': 'webm',\n  'video/webm;codecs=h264': 'mp4',\n  'video/x-matroska;codecs=avc1': 'mkv',\n}\n\nexport function getFilename(media?: string, mimeType?: string) {\n  const d = new Date()\n\n  const pad = (v: number) => `${v}`.padStart(2, '0')\n\n  const date = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`\n\n  const ext = mimeType ? mimeExtMap[mimeType] : 'webm'\n\n  return `${[media, recordingName.value, date].filter(isTruthy).join('-')}.${ext}`\n}\n\nfunction getSupportedMimeTypes() {\n  if (MediaRecorder && typeof MediaRecorder.isTypeSupported === 'function')\n    return Object.keys(mimeExtMap).filter(mime => MediaRecorder.isTypeSupported(mime))\n  return []\n}\n\nexport const supportedMimeTypes = getSupportedMimeTypes()\n\nexport const {\n  devices,\n  videoInputs: cameras,\n  audioInputs: microphones,\n  ensurePermissions: ensureDevicesListPermissions,\n} = useDevicesList({\n  onUpdated() {\n    if (currentCamera.value !== 'none') {\n      if (!cameras.value.find(i => i.deviceId === currentCamera.value))\n        currentCamera.value = cameras.value[0]?.deviceId || 'default'\n    }\n    if (currentMic.value !== 'none') {\n      if (!microphones.value.find(i => i.deviceId === currentMic.value))\n        currentMic.value = microphones.value[0]?.deviceId || 'default'\n    }\n  },\n})\n\nexport function download(name: string, url: string) {\n  const a = document.createElement('a')\n  a.setAttribute('href', url)\n  a.setAttribute('download', name)\n  document.body.appendChild(a)\n  a.click()\n  document.body.removeChild(a)\n}\n\nexport function useRecording() {\n  const recording = ref(false)\n  const showAvatar = ref(false)\n\n  const recorderCamera: Ref<RecorderType | undefined> = shallowRef()\n  const recorderSlides: Ref<RecorderType | undefined> = shallowRef()\n  const streamCamera: Ref<MediaStream | undefined> = shallowRef()\n  const streamCapture: Ref<MediaStream | undefined> = shallowRef()\n  const streamSlides: Ref<MediaStream | undefined> = shallowRef()\n\n  const config: RecorderOptions = {\n    type: 'video',\n    // Extending recording limit as default is only 1h (see https://github.com/muaz-khan/RecordRTC/issues/144)\n    timeSlice: 24 * 60 * 60 * 1000,\n  }\n\n  async function toggleAvatar() {\n    if (currentCamera.value === 'none')\n      return\n\n    if (showAvatar.value) {\n      showAvatar.value = false\n      if (!recording.value)\n        closeStream(streamCamera)\n    }\n    else {\n      await startCameraStream()\n      if (streamCamera.value)\n        showAvatar.value = !!streamCamera.value\n    }\n  }\n\n  async function startCameraStream() {\n    await ensureDevicesListPermissions()\n    await nextTick()\n    if (!streamCamera.value) {\n      if (currentCamera.value === 'none' && currentMic.value === 'none')\n        return\n\n      streamCamera.value = await navigator.mediaDevices.getUserMedia({\n        video: (currentCamera.value === 'none' || recordCamera.value !== true)\n          ? false\n          : {\n              deviceId: currentCamera.value,\n            },\n        audio: currentMic.value === 'none'\n          ? false\n          : {\n              deviceId: currentMic.value,\n            },\n      })\n    }\n  }\n\n  watch(currentCamera, async (v) => {\n    if (v === 'none') {\n      closeStream(streamCamera)\n    }\n    else {\n      if (recording.value)\n        return\n      // restart camera stream\n      if (streamCamera.value) {\n        closeStream(streamCamera)\n        await startCameraStream()\n      }\n    }\n  })\n\n  async function startRecording(customConfig?: RecorderOptions) {\n    await ensureDevicesListPermissions()\n    const { default: Recorder } = await import('recordrtc')\n    await startCameraStream()\n\n    const [width, height] = resolution.value.split('x').map(Number)\n    streamCapture.value = await navigator.mediaDevices.getDisplayMedia({\n      video: {\n        // aspectRatio: 1.6,\n        frameRate: frameRate.value,\n        width,\n        height,\n        // @ts-expect-error missing types\n        cursor: 'motion',\n        resizeMode: 'crop-and-scale',\n      },\n      selfBrowserSurface: 'include',\n    })\n    streamCapture.value.addEventListener('inactive', stopRecording)\n\n    // We need to create a new Stream to merge video and audio to have the inactive event working on streamCapture\n    streamSlides.value = new MediaStream()\n    streamCapture.value!.getVideoTracks().forEach(videoTrack => streamSlides.value!.addTrack(videoTrack))\n\n    // merge config\n    Object.assign(config, customConfig)\n\n    if (streamCamera.value) {\n      const audioTrack = streamCamera.value!.getAudioTracks()?.[0]\n      if (audioTrack)\n        streamSlides.value!.addTrack(audioTrack)\n\n      recorderCamera.value = new Recorder(\n        streamCamera.value!,\n        config,\n      )\n      recorderCamera.value.startRecording()\n    }\n\n    recorderSlides.value = new Recorder(\n      streamSlides.value!,\n      config,\n    )\n\n    recorderSlides.value.startRecording()\n    recording.value = true\n  }\n\n  async function stopRecording() {\n    recording.value = false\n    recorderCamera.value?.stopRecording(() => {\n      if (recordCamera.value) {\n        const blob = recorderCamera.value!.getBlob()\n        const url = URL.createObjectURL(blob)\n        download(getFilename('camera', config.mimeType), url)\n        window.URL.revokeObjectURL(url)\n      }\n      recorderCamera.value = undefined\n      if (!showAvatar.value)\n        closeStream(streamCamera)\n    })\n    recorderSlides.value?.stopRecording(() => {\n      const blob = recorderSlides.value!.getBlob()\n      const url = URL.createObjectURL(blob)\n      download(getFilename('screen', config.mimeType), url)\n      window.URL.revokeObjectURL(url)\n      closeStream(streamCapture)\n      closeStream(streamSlides)\n      recorderSlides.value = undefined\n    })\n  }\n\n  function closeStream(stream: Ref<MediaStream | undefined>) {\n    const s = stream.value\n    if (!s)\n      return\n    s.getTracks().forEach((i) => {\n      i.stop()\n      s.removeTrack(i)\n    })\n    stream.value = undefined\n  }\n\n  function toggleRecording() {\n    if (recording.value)\n      stopRecording()\n    else\n      startRecording()\n  }\n\n  useEventListener('beforeunload', (event) => {\n    if (!recording.value)\n      return\n    // eslint-disable-next-line no-alert\n    if (confirm('Recording is not saved yet, do you want to leave?'))\n      return\n    event.preventDefault()\n    event.returnValue = ''\n  })\n\n  return {\n    recording,\n    showAvatar,\n    toggleRecording,\n    startRecording,\n    stopRecording,\n    toggleAvatar,\n    recorderCamera,\n    recorderSlides,\n    streamCamera,\n    streamCapture,\n    streamSlides,\n  }\n}\n\nexport const recorder = useRecording()\n"
  },
  {
    "path": "packages/client/logic/route.ts",
    "content": "import type { WritableComputedRef } from 'vue'\nimport { computed, nextTick, unref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nexport function useRouteQuery<T extends string | string[]>(\n  name: string,\n  defaultValue?: T,\n  {\n    mode = 'replace',\n  } = {},\n): WritableComputedRef<T> {\n  const router = useRouter()\n\n  return computed<any>({\n    get() {\n      const data = router.currentRoute.value.query[name]\n      if (data == null)\n        return defaultValue ?? null\n      if (Array.isArray(data))\n        return data.filter(Boolean)\n      return data\n    },\n    set(v) {\n      nextTick(() => {\n        const oldValue = router.currentRoute.value.query[name]\n        if ((oldValue ?? defaultValue?.toString()) === v.toString())\n          return\n        router[unref(mode) as 'replace' | 'push']({\n          query: {\n            ...router.currentRoute.value.query,\n            [name]: `${v}` === defaultValue ? undefined : v,\n          },\n        })\n      })\n    },\n  })\n}\n"
  },
  {
    "path": "packages/client/logic/screenshot.ts",
    "content": "import { computed, ref } from 'vue'\n\nexport async function startScreenshotSession(width: number, height: number) {\n  const canvas = document.createElement('canvas')\n  canvas.width = width\n  canvas.height = height\n  const context = canvas.getContext('2d')!\n  const video = document.createElement('video')\n  video.width = width\n  video.height = height\n\n  const captureStream = ref<MediaStream | null>(await navigator.mediaDevices.getDisplayMedia({\n    video: {\n      // Use a rather small frame rate\n      frameRate: 10,\n      // @ts-expect-error missing types\n      cursor: 'never',\n    },\n    selfBrowserSurface: 'include',\n    preferCurrentTab: true,\n  }))\n  captureStream.value!.addEventListener('inactive', dispose)\n\n  video.srcObject = captureStream.value!\n  video.play()\n\n  function screenshot(element: HTMLElement) {\n    if (!captureStream.value)\n      throw new Error('captureStream inactive')\n    context.clearRect(0, 0, width, height)\n    const { left, top, width: elWidth } = element.getBoundingClientRect()\n    context.drawImage(\n      video,\n      left * window.devicePixelRatio,\n      top * window.devicePixelRatio,\n      elWidth * window.devicePixelRatio,\n      elWidth / width * height * window.devicePixelRatio,\n      0,\n      0,\n      width,\n      height,\n    )\n    return canvas.toDataURL('image/png')\n  }\n\n  function dispose() {\n    captureStream.value?.getTracks().forEach(track => track.stop())\n    captureStream.value = null\n  }\n\n  return {\n    isActive: computed(() => !!captureStream.value),\n    screenshot,\n    dispose,\n  }\n}\n\nexport type ScreenshotSession = Awaited<ReturnType<typeof startScreenshotSession>>\n\nconst chromeVersion = window.navigator.userAgent.match(/Chrome\\/(\\d+)/)?.[1]\nexport const isScreenshotSupported = chromeVersion ? Number(chromeVersion) >= 94 : false\n"
  },
  {
    "path": "packages/client/logic/shortcuts.ts",
    "content": "import type { ShortcutOptions } from '@slidev/types'\nimport type { Fn, KeyFilter } from '@vueuse/core'\nimport type { Ref } from 'vue'\nimport { onKeyStroke } from '@vueuse/core'\nimport { and, not } from '@vueuse/math'\nimport { watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport setupShortcuts from '../setup/shortcuts'\nimport { fullscreen, isInputting, isOnFocus, magicKeys, shortcutsEnabled, shortcutsLocked } from '../state'\n\nexport function registerShortcuts() {\n  const { isPrintMode } = useNav()\n  const enabled = and(not(isInputting), not(isOnFocus), not(isPrintMode), shortcutsEnabled, not(shortcutsLocked))\n\n  const allShortcuts = setupShortcuts()\n  const shortcuts = new Map<string | Ref<boolean>, ShortcutOptions>(\n    allShortcuts.map((options: ShortcutOptions) => [options.key, options]),\n  )\n\n  shortcuts.forEach((options) => {\n    if (options.fn)\n      shortcut(options.key, options.fn, options.autoRepeat)\n  })\n\n  strokeShortcut('f', () => fullscreen.toggle())\n\n  function shortcut(key: string | Ref<boolean>, fn: Fn, autoRepeat = false) {\n    if (typeof key === 'string')\n      key = magicKeys[key]\n\n    const source = and(key, enabled)\n    let count = 0\n    let timer: any\n    const trigger = () => {\n      clearTimeout(timer)\n      if (!source.value) {\n        count = 0\n        return\n      }\n      if (autoRepeat) {\n        timer = setTimeout(trigger, Math.max(1000 - count * 250, 150))\n        count++\n      }\n      fn()\n    }\n\n    return watch(source, trigger, { flush: 'sync' })\n  }\n\n  function strokeShortcut(key: KeyFilter, fn: Fn) {\n    return onKeyStroke(key, (ev) => {\n      if (!enabled.value)\n        return\n      if (!ev.repeat)\n        fn()\n    })\n  }\n}\n"
  },
  {
    "path": "packages/client/logic/slides.ts",
    "content": "import type { SlideRoute } from '@slidev/types'\nimport { slides } from '#slidev/slides'\nimport { computed, watch, watchEffect } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useSlideContext } from '../context'\n\nexport { slides }\n\nexport function getSlide(no: number | string) {\n  return slides.value.find(\n    s => (s.no === +no || s.meta.slide?.frontmatter.routeAlias === no),\n  )\n}\n\nexport function getSlidePath(\n  route: SlideRoute | number | string,\n  presenter: boolean,\n  exporting: boolean = false,\n) {\n  if (typeof route === 'number' || typeof route === 'string')\n    route = getSlide(route)!\n  const no = route.meta.slide?.frontmatter.routeAlias ?? route.no\n  return exporting ? `/export/${no}` : presenter ? `/presenter/${no}` : `/${no}`\n}\n\nexport function useIsSlideActive() {\n  const { $page } = useSlideContext()\n  const { currentSlideNo } = useNav()\n  return computed(() => $page.value === currentSlideNo.value)\n}\n\nexport function onSlideEnter(cb: () => void) {\n  const isActive = useIsSlideActive()\n  watchEffect(() => isActive.value && cb())\n}\n\nexport function onSlideLeave(cb: () => void) {\n  const isActive = useIsSlideActive()\n  watch(isActive, () => !isActive.value && cb())\n}\n"
  },
  {
    "path": "packages/client/logic/snapshot.ts",
    "content": "import type { SlidevContextNavFull } from '../composables/useNav'\nimport type { ScreenshotSession } from './screenshot'\nimport { sleep } from '@antfu/utils'\nimport { slideHeight, slideWidth } from '../env'\nimport { captureDelay, disableTransition } from '../state'\nimport { snapshotState } from '../state/snapshot'\nimport { isDark } from './dark'\nimport { startScreenshotSession } from './screenshot'\nimport { getSlide } from './slides'\n\nconst chromeVersion = window.navigator.userAgent.match(/Chrome\\/(\\d+)/)?.[1]\nexport const isScreenshotSupported = chromeVersion ? Number(chromeVersion) >= 94 : false\n\nconst initialWait = 100\n\nexport class SlideSnapshotManager {\n  private _screenshotSession: ScreenshotSession | null = null\n\n  getSnapshot(slideNo: number, isDark: boolean) {\n    const id = slideNo + (isDark ? '-dark' : '-light')\n    const data = snapshotState.state[id]\n    if (!data) {\n      return\n    }\n    const slide = getSlide(slideNo)\n    if (!slide) {\n      return\n    }\n    if (data?.revision === slide?.meta.slide.revision) {\n      return data.image\n    }\n  }\n\n  private async saveSnapshot(slideNo: number, dataUrl: string, isDark: boolean) {\n    if (!__DEV__)\n      return false\n    const slide = getSlide(slideNo)\n    if (!slide)\n      return false\n\n    const id = slideNo + (isDark ? '-dark' : '-light')\n    const revision = slide.meta.slide.revision\n    snapshotState.patch(id, {\n      revision,\n      image: dataUrl,\n    })\n  }\n\n  async startCapturing(nav: SlidevContextNavFull) {\n    if (!__DEV__)\n      return false\n\n    // TODO: show a dialog to confirm\n\n    if (this._screenshotSession) {\n      this._screenshotSession.dispose()\n      this._screenshotSession = null\n    }\n\n    try {\n      this._screenshotSession = await startScreenshotSession(\n        slideWidth.value,\n        slideHeight.value,\n      )\n\n      disableTransition.value = true\n      nav.go(1, 0, true)\n\n      await sleep(initialWait + captureDelay.value)\n      while (true) {\n        if (!this._screenshotSession) {\n          break\n        }\n        this.saveSnapshot(\n          nav.currentSlideNo.value,\n          this._screenshotSession.screenshot(document.getElementById('slide-content')!),\n          isDark.value,\n        )\n        if (nav.hasNext.value) {\n          await sleep(captureDelay.value)\n          nav.nextSlide(true)\n          await sleep(captureDelay.value)\n        }\n        else {\n          break\n        }\n      }\n\n      // TODO: show a message when done\n\n      return true\n    }\n    catch (e) {\n      console.error(e)\n      return false\n    }\n    finally {\n      disableTransition.value = false\n      if (this._screenshotSession) {\n        this._screenshotSession.dispose()\n        this._screenshotSession = null\n      }\n    }\n  }\n}\n\nexport const snapshotManager = new SlideSnapshotManager()\n"
  },
  {
    "path": "packages/client/logic/transition.ts",
    "content": "import type { SlideRoute } from '@slidev/types'\nimport type { TransitionGroupProps } from 'vue'\nimport { configs } from '../env'\n\nconst transitionResolveMap: Record<string, string | undefined> = {\n  'slide-left': 'slide-left | slide-right',\n  'slide-right': 'slide-right | slide-left',\n  'slide-up': 'slide-up | slide-down',\n  'slide-down': 'slide-down | slide-up',\n}\n\nexport function resolveTransition(transition?: string | TransitionGroupProps, isBackward = false): TransitionGroupProps | undefined {\n  if (!transition)\n    return undefined\n  if (typeof transition === 'string') {\n    transition = {\n      name: transition,\n    }\n  }\n\n  if (!transition.name)\n    return undefined\n\n  let name = transition.name.includes('|')\n    ? transition.name\n    : (transitionResolveMap[transition.name] || transition.name)\n\n  if (name.includes('|')) {\n    const [forward, backward] = name.split('|').map(i => i.trim())\n    name = isBackward ? backward : forward\n  }\n\n  if (!name)\n    return undefined\n\n  return {\n    ...transition,\n    name,\n  }\n}\n\nexport function getCurrentTransition(direction: number, currentRoute?: SlideRoute, prevRoute?: SlideRoute) {\n  let transition = direction > 0\n    ? prevRoute?.meta?.transition\n    : currentRoute?.meta?.transition\n  if (!transition)\n    transition = configs.transition || undefined\n\n  return resolveTransition(transition, direction < 0)\n}\n"
  },
  {
    "path": "packages/client/logic/utils.ts",
    "content": "import { parseRangeString } from '@slidev/parser/core'\n\nexport function makeId(length = 5) {\n  const result = []\n  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\n  const charactersLength = characters.length\n  for (let i = 0; i < length; i++)\n    result.push(characters.charAt(Math.floor(Math.random() * charactersLength)))\n  return result.join('')\n}\n\nexport function updateCodeHighlightRange(\n  rangeStr: string,\n  linesCount: number,\n  startLine: number,\n  getTokenOfLine: (line: number) => Element[],\n) {\n  const highlights: number[] = parseRangeString(linesCount + startLine - 1, rangeStr)\n  for (let line = 0; line < linesCount; line++) {\n    const tokens = getTokenOfLine(line)\n    const isHighlighted = highlights.includes(line + startLine)\n    for (const token of tokens) {\n      // token.classList.toggle(CLASS_VCLICK_TARGET, true)\n      token.classList.toggle('slidev-code-highlighted', isHighlighted)\n      token.classList.toggle('slidev-code-dishonored', !isHighlighted)\n\n      // for backward compatibility\n      token.classList.toggle('highlighted', isHighlighted)\n      token.classList.toggle('dishonored', !isHighlighted)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/client/main.ts",
    "content": "/// <reference types=\"@slidev/types/client\" />\n\nimport { createApp } from 'vue'\nimport App from './App.vue'\nimport setupMain from './setup/main'\n\nasync function main() {\n  const app = createApp(App)\n  await setupMain(app)\n  app.mount('#app')\n}\n\nmain()\n"
  },
  {
    "path": "packages/client/modules/context.ts",
    "content": "import type { ComputedRef } from '@vue/reactivity'\nimport type { SlidevContextNav } from '../composables/useNav'\nimport type { configs } from '../env'\n\nexport interface SlidevContext {\n  nav: SlidevContextNav\n  configs: typeof configs\n  themeConfigs: ComputedRef<typeof configs['themeConfig']>\n}\n"
  },
  {
    "path": "packages/client/modules/mermaid.ts",
    "content": "import mermaidRenderers from '#slidev/setups/mermaid-renderer'\nimport { clearUndefined } from '@antfu/utils'\nimport lz from 'lz-string'\nimport mermaid from 'mermaid/dist/mermaid.esm.mjs'\nimport { makeId } from '../logic/utils'\nimport setupMermaid from '../setup/mermaid'\n\nmermaid.startOnLoad = false\nmermaid.initialize({ startOnLoad: false })\n\nconst cache = new Map<string, string>()\nlet containerElement: Element | undefined\n\nexport async function renderMermaid(lzEncoded: string, options: any) {\n  containerElement ??= document.getElementById('mermaid-rendering-container')!\n  const key = lzEncoded + JSON.stringify(options)\n  const _cache = cache.get(key)\n  if (_cache)\n    return _cache\n\n  const code = lz.decompressFromBase64(lzEncoded)\n\n  // custom renderer\n  for (const setup of mermaidRenderers) {\n    const renderer = await setup()\n    if (renderer) {\n      const svg = await renderer(code, options)\n      cache.set(key, svg)\n      return svg\n    }\n  }\n\n  // fallback: existing mermaid\n  mermaid.initialize({\n    startOnLoad: false,\n    ...clearUndefined(await setupMermaid() || {}),\n    ...clearUndefined(options),\n  })\n  const id = makeId()\n  const { svg } = await mermaid.render(id, code, containerElement)\n  cache.set(key, svg)\n  return svg\n}\n"
  },
  {
    "path": "packages/client/modules/v-click.ts",
    "content": "import type { ClicksElement, RawAtValue } from '@slidev/types'\nimport type { App, DirectiveBinding } from 'vue'\nimport { computed, watchEffect } from 'vue'\nimport {\n  CLASS_VCLICK_CURRENT,\n  CLASS_VCLICK_FADE,\n  CLASS_VCLICK_HIDDEN,\n  CLASS_VCLICK_HIDDEN_EXP,\n  CLASS_VCLICK_PRIOR,\n  CLASS_VCLICK_TARGET,\n  injectionClicksContext,\n} from '../constants'\nimport { directiveInject } from '../utils'\n\nexport function createVClickDirectives() {\n  return {\n    install(app: App) {\n      app.directive<HTMLElement, RawAtValue>('click', {\n        // @ts-expect-error extra prop\n        name: 'v-click',\n\n        mounted(el, dir) {\n          const resolved = resolveClick(el, dir, dir.value)\n          if (resolved == null)\n            return\n\n          el.classList.toggle(CLASS_VCLICK_TARGET, true)\n\n          // Expose the resolved clicks info to the element to make it easier to understand and debug\n          el.dataset.slidevClicksStart = String(resolved.start)\n          if (Number.isFinite(resolved.end))\n            el.dataset.slidevClicksEnd = String(resolved.end)\n\n          // @ts-expect-error extra prop\n          el.watchStopHandle = watchEffect(() => {\n            const active = resolved.isActive.value\n            const current = resolved.isCurrent.value\n            const prior = active && !current\n\n            if (resolved.flagHide) {\n              el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)\n              el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)\n            }\n            else {\n              el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)\n            }\n\n            el.classList.toggle(CLASS_VCLICK_CURRENT, current)\n            el.classList.toggle(CLASS_VCLICK_PRIOR, prior)\n          })\n        },\n        unmounted,\n      })\n\n      app.directive<HTMLElement, RawAtValue>('after', {\n        // @ts-expect-error extra prop\n        name: 'v-after',\n\n        mounted(el, dir) {\n          const resolved = resolveClick(el, dir, '+0')\n          if (resolved == null)\n            return\n\n          el.classList.toggle(CLASS_VCLICK_TARGET, true)\n\n          // @ts-expect-error extra prop\n          el.watchStopHandle = watchEffect(() => {\n            const active = resolved.isActive.value\n            const current = resolved.isCurrent.value\n            const prior = active && !current\n\n            if (resolved.flagHide) {\n              el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)\n              el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)\n            }\n            else {\n              el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)\n            }\n\n            el.classList.toggle(CLASS_VCLICK_CURRENT, current)\n            el.classList.toggle(CLASS_VCLICK_PRIOR, prior)\n          })\n        },\n        unmounted,\n      })\n\n      app.directive<HTMLElement, RawAtValue>('click-hide', {\n        // @ts-expect-error extra prop\n        name: 'v-click-hide',\n\n        mounted(el, dir) {\n          const resolved = resolveClick(el, dir, dir.value, true)\n          if (resolved == null)\n            return\n\n          el.classList.toggle(CLASS_VCLICK_TARGET, true)\n\n          // @ts-expect-error extra prop\n          el.watchStopHandle = watchEffect(() => {\n            const active = resolved.isActive.value\n            const current = resolved.isCurrent.value\n            const prior = active && !current\n\n            el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)\n            el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)\n\n            el.classList.toggle(CLASS_VCLICK_CURRENT, current)\n            el.classList.toggle(CLASS_VCLICK_PRIOR, prior)\n          })\n        },\n        unmounted,\n      })\n    },\n  }\n}\n\nexport const resolvedClickMap = new Map<ClicksElement, ReturnType<typeof resolveClick>>()\n\nexport function resolveClick(el: Element | string, dir: DirectiveBinding<any>, value: RawAtValue, explicitHide = false) {\n  const ctx = directiveInject(dir, injectionClicksContext)?.value\n\n  if (!el || !ctx)\n    return null\n\n  const flagHide = explicitHide || (dir.modifiers.hide !== false && dir.modifiers.hide != null)\n  const flagFade = dir.modifiers.fade !== false && dir.modifiers.fade != null\n\n  const info = ctx.calculate(value)\n  if (!info)\n    return null\n\n  ctx.register(el, info)\n\n  const isShown = computed(() => flagHide ? !info.isActive.value : info.isActive.value)\n  const visibilityState = computed(() => {\n    if (isShown.value)\n      return 'shown'\n    if (Number.isFinite(info.end))\n      return ctx.current < info.start ? 'before' : 'after'\n    else\n      return flagHide ? 'after' : 'before'\n  })\n\n  const resolved = {\n    ...info,\n    isShown,\n    visibilityState,\n    flagFade,\n    flagHide,\n  }\n  resolvedClickMap.set(el, resolved)\n  return resolved\n}\n\nfunction unmounted(el: HTMLElement, dir: DirectiveBinding<any>) {\n  el.classList.toggle(CLASS_VCLICK_TARGET, false)\n  const ctx = directiveInject(dir, injectionClicksContext)?.value\n  ctx?.unregister(el)\n  // @ts-expect-error extra prop\n  el.watchStopHandle?.()\n}\n"
  },
  {
    "path": "packages/client/modules/v-drag.ts",
    "content": "import type { App } from 'vue'\nimport type { DragElementState } from '../composables/useDragElements'\nimport { watch } from 'vue'\nimport { useDragElement } from '../composables/useDragElements'\n\nexport function createVDragDirective() {\n  return {\n    install(app: App) {\n      app.directive<HTMLElement & { draggingState: DragElementState }>('drag', {\n        // @ts-expect-error extra prop\n        name: 'v-drag',\n\n        created(el, binding, vnode) {\n          const state = useDragElement(binding, binding.value, vnode.props?.markdownSource)\n          if (vnode.props) {\n            vnode.props = { ...vnode.props }\n            delete vnode.props.markdownSource\n          }\n          state.container.value = el\n          el.draggingState = state\n          el.dataset.dragId = state.dragId\n          state.watchStopHandles.push(\n            watch(state.containerStyle, (style) => {\n              for (const [k, v] of Object.entries(style)) {\n                if (v)\n                  el.style[k as any] = v as any\n              }\n            }, { immediate: true }),\n          )\n          el.addEventListener('dblclick', state.startDragging)\n        },\n        mounted(el) {\n          el.draggingState.mounted()\n        },\n        unmounted(el) {\n          const state = el.draggingState\n          state.unmounted()\n          el.removeEventListener('dblclick', state.startDragging)\n          state.watchStopHandles.forEach(fn => fn())\n        },\n      })\n    },\n  }\n}\n"
  },
  {
    "path": "packages/client/modules/v-mark.ts",
    "content": "import type { RoughAnnotationConfig } from '@slidev/rough-notation'\nimport type { RawAtValue } from '@slidev/types'\nimport type { App } from 'vue'\nimport { annotate } from '@slidev/rough-notation'\nimport { computed, watchEffect } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { resolveClick } from './v-click'\n\nexport interface RoughDirectiveOptions extends Partial<RoughAnnotationConfig> {\n  at: RawAtValue\n}\n\nexport type RoughDirectiveValue = RawAtValue | RoughDirectiveOptions\n\nfunction addClass(options: RoughDirectiveOptions, cls: string) {\n  options.class = [options.class, cls].filter(Boolean).join(' ')\n  return options\n}\n\n// Definitions of supported modifiers\nconst vMarkModifiers: Record<string, (options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions> = {\n  // Types\n  'box': options => Object.assign(options, { type: 'box' }),\n  'circle': options => Object.assign(options, { type: 'circle' }),\n  'underline': options => Object.assign(options, { type: 'underline' }),\n  'highlight': options => Object.assign(options, { type: 'highlight' }),\n  'strike-through': options => Object.assign(options, { type: 'strike-through' }),\n  'crossed-off': options => Object.assign(options, { type: 'crossed-off' }),\n  'bracket': options => Object.assign(options, { type: 'bracket' }),\n\n  // Type Aliases\n  'strike': options => Object.assign(options, { type: 'strike-through' }),\n  'cross': options => Object.assign(options, { type: 'crossed-off' }),\n  'crossed': options => Object.assign(options, { type: 'crossed-off' }),\n  'linethrough': options => Object.assign(options, { type: 'strike-through' }),\n  'line-through': options => Object.assign(options, { type: 'strike-through' }),\n\n  // Colors\n  // @unocss-include\n  'black': options => addClass(options, 'text-black'),\n  'blue': options => addClass(options, 'text-blue'),\n  'cyan': options => addClass(options, 'text-cyan'),\n  'gray': options => addClass(options, 'text-gray'),\n  'green': options => addClass(options, 'text-green'),\n  'indigo': options => addClass(options, 'text-indigo'),\n  'lime': options => addClass(options, 'text-lime'),\n  'orange': options => addClass(options, 'text-orange'),\n  'pink': options => addClass(options, 'text-pink'),\n  'purple': options => addClass(options, 'text-purple'),\n  'red': options => addClass(options, 'text-red'),\n  'teal': options => addClass(options, 'text-teal'),\n  'white': options => addClass(options, 'text-white'),\n  'yellow': options => addClass(options, 'text-yellow'),\n}\n\nconst vMarkModifiersDynamic: [RegExp, (match: RegExpMatchArray, options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions][] = [\n  // Support setting delay like `v-mark.delay300=\"1\"`\n  [/^delay-?(\\d+)?$/, (match, options, value) => {\n    const ms = (match[1] ? Number.parseInt(match[1]) : value) || 300\n    options.delay = ms\n    return options\n  }],\n  // Support setting opacity like `v-mark.op50=\"1\"`\n  [/^(?:op|opacity)-?(\\d+)?$/, (match, options, value) => {\n    const opacity = (match[1] ? Number.parseInt(match[1]) : value) || 100\n    options.opacity = opacity / 100\n    return options\n  }],\n]\n\n/**\n * This supports v-mark directive to add notations to elements, powered by `rough-notation`.\n */\nexport function createVMarkDirective() {\n  return {\n    install(app: App) {\n      app.directive<HTMLElement, RoughDirectiveValue>('mark', {\n        // @ts-expect-error extra prop\n        name: 'v-mark',\n\n        mounted: (el, binding) => {\n          const { isPrintMode } = useNav()\n\n          const options = computed(() => {\n            const bindingOptions = (typeof binding.value === 'object' && !Array.isArray(binding.value))\n              ? { ...binding.value }\n              : { at: binding.value }\n\n            let modifierOptions: RoughDirectiveOptions = { at: bindingOptions.at }\n            const unknownModifiers = Object.entries(binding.modifiers)\n              .filter(([k, v]) => {\n                if (vMarkModifiers[k]) {\n                  modifierOptions = vMarkModifiers[k](modifierOptions, v)\n                  return false\n                }\n                for (const [re, fn] of vMarkModifiersDynamic) {\n                  const match = k.match(re)\n                  if (match) {\n                    modifierOptions = fn(match, modifierOptions, v)\n                    return false\n                  }\n                }\n                return true\n              })\n\n            if (unknownModifiers.length)\n              console.warn('[Slidev] Invalid modifiers for v-mark:', unknownModifiers)\n\n            const options = {\n              ...modifierOptions,\n              ...bindingOptions,\n            }\n            options.type ||= 'underline'\n\n            if (isPrintMode.value)\n              options.animationDuration = 1 /* millisecond */\n\n            return options\n          })\n\n          const annotation = annotate(el, options.value as RoughAnnotationConfig)\n\n          const resolvedClick = resolveClick(el, binding, options.value.at)\n          if (!resolvedClick) {\n            // No click animation, just show the mark\n            annotation.show()\n            return\n          }\n\n          // @ts-expect-error extra prop\n          el.watchStopHandle = watchEffect(() => {\n            let shouldShow: boolean | undefined\n\n            if (options.value.class)\n              annotation.class = options.value.class\n            if (options.value.color)\n              annotation.color = options.value.color\n\n            const at = options.value.at\n\n            if (at === true)\n              shouldShow = true\n            else if (at === false)\n              shouldShow = false\n            else\n              shouldShow = resolvedClick.isActive.value\n\n            if (shouldShow == null)\n              return\n\n            if (shouldShow)\n              annotation.show()\n            else\n              annotation.hide()\n          })\n        },\n\n        unmounted: (el) => {\n          // @ts-expect-error extra prop\n          el.watchStopHandle?.()\n        },\n      })\n    },\n  }\n}\n"
  },
  {
    "path": "packages/client/modules/v-motion.ts",
    "content": "import type { ClicksInfo } from '@slidev/types'\nimport type { App, ObjectDirective } from 'vue'\nimport { MotionDirective } from '@vueuse/motion'\nimport { watch } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { injectionClicksContext, injectionCurrentPage, injectionRenderContext } from '../constants'\nimport { makeId } from '../logic/utils'\nimport { directiveInject } from '../utils'\nimport { resolvedClickMap } from './v-click'\n\nexport function createVMotionDirectives() {\n  return {\n    install(app: App) {\n      const original = MotionDirective() as ObjectDirective\n      app.directive<HTMLElement | SVGElement, string>('motion', {\n        // @ts-expect-error extra prop\n        name: 'v-motion',\n        mounted(el, binding, node, prevNode) {\n          const clicksContext = directiveInject(binding, injectionClicksContext)\n          const thisPage = directiveInject(binding, injectionCurrentPage)\n          const renderContext = directiveInject(binding, injectionRenderContext)\n          const { currentPage, clicks: currentClicks, isPrintMode } = useNav()\n\n          const props = node.props = { ...node.props }\n\n          const variantInitial = { ...props.initial, ...props.variants?.['slidev-initial'] }\n          const variantEnter = { ...props.enter, ...props.variants?.['slidev-enter'] }\n          const variantLeave = { ...props.leave, ...props.variants?.['slidev-leave'] }\n          delete props.initial\n          delete props.enter\n          delete props.leave\n\n          const idPrefix = `${makeId()}-`\n          const clicks: {\n            id: string\n            at: number | [number, number]\n            variant: Record<string, unknown>\n            info: ClicksInfo | null | undefined\n          }[] = []\n\n          for (const k of Object.keys(props)) {\n            if (k.startsWith('click-')) {\n              const s = k.slice(6)\n              const at = s.includes('-') ? s.split('-').map(Number) as [number, number] : +s\n              const id = idPrefix + s\n              clicks.push({\n                id,\n                at,\n                variant: { ...props[k] },\n                info: clicksContext?.value.calculate(at),\n              })\n              delete props[k]\n            }\n          }\n\n          clicks.sort((a, b) => (Array.isArray(a.at) ? a.at[0] : a.at) - (Array.isArray(b.at) ? b.at[0] : b.at))\n\n          original.created!(el, binding, node, prevNode)\n          original.mounted!(el, binding, node, prevNode)\n\n          // @ts-expect-error extra prop\n          const motion = el.motionInstance\n          motion.clickIds = clicks.map(i => i.id)\n          motion.set(variantInitial)\n          motion.watchStopHandle = watch(\n            [thisPage, currentPage, currentClicks].filter(Boolean),\n            () => {\n              const visibility = resolvedClickMap.get(el)?.visibilityState.value ?? 'shown'\n              if (!clicksContext?.value || !['slide', 'presenter'].includes(renderContext?.value ?? '')) {\n                const mixedVariant: Record<string, unknown> = { ...variantInitial, ...variantEnter }\n                for (const { variant } of clicks)\n                  Object.assign(mixedVariant, variant)\n\n                motion.set(mixedVariant)\n              }\n              else if (isPrintMode.value || thisPage?.value === currentPage.value) {\n                if (visibility === 'shown') {\n                  const mixedVariant: Record<string, unknown> = { ...variantInitial, ...variantEnter }\n                  for (const { variant, info } of clicks) {\n                    if (!info || info.isActive.value)\n                      Object.assign(mixedVariant, variant)\n                  }\n                  if (isPrintMode.value)\n                    motion.set(mixedVariant) // print with clicks\n                  else\n                    motion.apply(mixedVariant)\n                }\n                else {\n                  motion.apply(visibility === 'before' ? variantInitial : variantLeave)\n                }\n              }\n              else {\n                motion.apply((thisPage?.value ?? -1) > currentPage.value ? variantInitial : variantLeave)\n              }\n            },\n            {\n              immediate: true,\n            },\n          )\n        },\n        unmounted(el) {\n          // @ts-expect-error extra prop\n          const motion = el.motionInstance\n          motion.watchStopHandle()\n        },\n      })\n    },\n  }\n}\n"
  },
  {
    "path": "packages/client/package.json",
    "content": "{\n  \"name\": \"@slidev/client\",\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Presentation slides for developers\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"exports\": {\n    \".\": \"./index.ts\",\n    \"./package.json\": \"./package.json\",\n    \"./constants\": \"./constants.ts\",\n    \"./context\": \"./context.ts\",\n    \"./env\": \"./env.ts\",\n    \"./layoutHelper\": \"./layoutHelper.ts\",\n    \"./routes\": \"./routes.ts\",\n    \"./utils\": \"./utils.ts\",\n    \"./*\": \"./*\"\n  },\n  \"main\": \"./public.ts\",\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"dependencies\": {\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@iconify-json/carbon\": \"catalog:icons\",\n    \"@iconify-json/ph\": \"catalog:icons\",\n    \"@iconify-json/svg-spinners\": \"catalog:icons\",\n    \"@shikijs/engine-javascript\": \"catalog:frontend\",\n    \"@shikijs/monaco\": \"catalog:monaco\",\n    \"@shikijs/vitepress-twoslash\": \"catalog:prod\",\n    \"@slidev/parser\": \"workspace:*\",\n    \"@slidev/rough-notation\": \"catalog:frontend\",\n    \"@slidev/types\": \"workspace:*\",\n    \"@typescript/ata\": \"catalog:monaco\",\n    \"@unhead/vue\": \"catalog:frontend\",\n    \"@unocss/extractor-mdc\": \"catalog:prod\",\n    \"@unocss/preset-mini\": \"catalog:prod\",\n    \"@unocss/reset\": \"catalog:frontend\",\n    \"@vueuse/core\": \"catalog:frontend\",\n    \"@vueuse/math\": \"catalog:frontend\",\n    \"@vueuse/motion\": \"catalog:frontend\",\n    \"ansis\": \"catalog:prod\",\n    \"drauu\": \"catalog:frontend\",\n    \"file-saver\": \"catalog:frontend\",\n    \"floating-vue\": \"catalog:frontend\",\n    \"fuse.js\": \"catalog:frontend\",\n    \"katex\": \"catalog:frontend\",\n    \"lz-string\": \"catalog:frontend\",\n    \"mermaid\": \"catalog:frontend\",\n    \"monaco-editor\": \"catalog:monaco\",\n    \"nanotar\": \"catalog:frontend\",\n    \"pptxgenjs\": \"catalog:prod\",\n    \"recordrtc\": \"catalog:frontend\",\n    \"shiki\": \"catalog:frontend\",\n    \"shiki-magic-move\": \"catalog:frontend\",\n    \"typescript\": \"catalog:dev\",\n    \"unocss\": \"catalog:prod\",\n    \"vue\": \"catalog:frontend\",\n    \"vue-router\": \"catalog:frontend\",\n    \"yaml\": \"catalog:prod\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"catalog:prod\"\n  }\n}\n"
  },
  {
    "path": "packages/client/pages/404.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useNav } from '../composables/useNav'\n\nconst { currentRoute } = useRouter()\nconst { total } = useNav()\n\nconst guessedSlide = computed(() => {\n  const path = currentRoute.value.path\n  const match = path.match(/\\d+/)\n  if (match) {\n    const slideNo = +match[0]\n    if (slideNo > 0 && slideNo <= total.value)\n      return slideNo\n  }\n  return null\n})\n</script>\n\n<template>\n  <div class=\"grid justify-center text-center pt-15% gap-5\">\n    <div>\n      <h1 class=\"text-9xl font-light\">\n        404\n      </h1>\n      <p class=\"text-2xl\">\n        Page <code class=\"op-60\">{{ currentRoute.path }}</code> not found\n      </p>\n    </div>\n    <div class=\"mt-3 flex flex-col gap-2 max-w-xs mx-auto w-full\">\n      <RouterLink v-if=\"guessedSlide !== 1\" to=\"/\" class=\"page-link\">\n        Go Home\n      </RouterLink>\n      <RouterLink v-if=\"guessedSlide\" :to=\"`/${guessedSlide}`\" class=\"page-link\">\n        Go to Slide {{ guessedSlide }}\n      </RouterLink>\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.page-link {\n  @apply py-2 px-4 bg-gray/10 hover:bg-gray/20 rounded;\n}\n</style>\n"
  },
  {
    "path": "packages/client/pages/entry.vue",
    "content": "<template>\n  <div class=\"h-full w-full flex items-center justify-center gap-5 lt-md:flex-col\">\n    <RouterLink to=\"/\" class=\"page-link\">\n      <div class=\"i-carbon:presentation-file\" /> Slides\n    </RouterLink>\n    <RouterLink to=\"/presenter\" class=\"page-link\">\n      <div class=\"i-carbon:user-speaker\" /> Presenter\n    </RouterLink>\n    <RouterLink to=\"/notes\" class=\"page-link\">\n      <div class=\"i-carbon:catalog\" /> Notes\n    </RouterLink>\n    <RouterLink to=\"/overview\" class=\"page-link\">\n      <div class=\"i-carbon:list-boxes\" /> Overview\n    </RouterLink>\n  </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.page-link {\n  @apply flex flex-col gap-2 items-center justify-center h-40 min-w-40 rounded bg-gray:10 p4 hover:bg-gray/20;\n}\n\n.page-link > svg {\n  @apply text-3em op50;\n}\n</style>\n"
  },
  {
    "path": "packages/client/pages/export.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ScreenshotSession } from '../logic/screenshot'\nimport { sleep } from '@antfu/utils'\nimport { parseRangeString } from '@slidev/parser/utils'\nimport { useHead } from '@unhead/vue'\nimport { provideLocal, useElementSize, useStyleTag, watchDebounced } from '@vueuse/core'\nimport { computed, ref, useTemplateRef, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useDarkMode } from '../composables/useDarkMode'\nimport { useNav } from '../composables/useNav'\nimport { patchMonacoColors } from '../composables/usePrintStyles'\nimport { injectionSlideScale } from '../constants'\nimport { configs, slideHeight, slidesTitle, slideWidth } from '../env'\nimport ExportPdfTip from '../internals/ExportPdfTip.vue'\nimport FormCheckbox from '../internals/FormCheckbox.vue'\nimport FormItem from '../internals/FormItem.vue'\nimport PrintSlide from '../internals/PrintSlide.vue'\nimport SegmentControl from '../internals/SegmentControl.vue'\nimport { isScreenshotSupported, startScreenshotSession } from '../logic/screenshot'\nimport { captureDelay, skipExportPdfTip } from '../state'\nimport Play from './play.vue'\n\nconst { slides, isPrintWithClicks, hasNext, go, next, currentSlideNo, clicks, printRange } = useNav()\nconst router = useRouter()\nconst { isColorSchemaConfigured, isDark } = useDarkMode()\nconst { width: containerWidth } = useElementSize(useTemplateRef('export-container'))\nconst { height: contentHeight } = useElementSize(useTemplateRef('export-content'))\nconst scale = computed(() => containerWidth.value / slideWidth.value)\nconst contentMarginBottom = computed(() => `${contentHeight.value * (scale.value - 1)}px`)\nconst rangesRaw = ref('')\nconst initialWait = ref(1000)\ntype ScreenshotResult = { slideIndex: number, clickIndex: number, dataUrl: string }[]\nconst screenshotSession = ref<ScreenshotSession | null>(null)\nconst capturedImages = ref<ScreenshotResult | null>(null)\nconst title = ref(configs.exportFilename || slidesTitle)\n\nuseHead({\n  title,\n})\n\nprovideLocal(injectionSlideScale, scale)\n\nconst showExportPdfTip = ref(false)\nfunction pdf() {\n  if (skipExportPdfTip.value) {\n    doPrint()\n  }\n  else {\n    showExportPdfTip.value = true\n  }\n}\n\nfunction doPrint() {\n  patchMonacoColors()\n  setTimeout(window.print, 100)\n}\n\nasync function capturePngs() {\n  if (screenshotSession.value) {\n    screenshotSession.value.dispose()\n    screenshotSession.value = null\n  }\n  if (capturedImages.value)\n    return capturedImages.value\n  try {\n    const scale = 2\n    screenshotSession.value = await startScreenshotSession(slideWidth.value * scale, slideHeight.value * scale)\n    const result: ScreenshotResult = []\n\n    go(1, 0, true)\n\n    await sleep(initialWait.value + captureDelay.value)\n    while (true) {\n      if (!screenshotSession.value) {\n        break\n      }\n      result.push({\n        slideIndex: currentSlideNo.value - 1,\n        clickIndex: clicks.value,\n        dataUrl: screenshotSession.value.screenshot(document.getElementById('slide-content')!),\n      })\n      if (hasNext.value) {\n        await sleep(captureDelay.value)\n        next()\n        await sleep(captureDelay.value)\n      }\n      else {\n        break\n      }\n    }\n\n    if (screenshotSession.value) {\n      screenshotSession.value.dispose()\n      capturedImages.value = result\n      screenshotSession.value = null\n    }\n  }\n  catch (e) {\n    console.error(e)\n    capturedImages.value = null\n  }\n  finally {\n    router.push('/export')\n  }\n  return capturedImages.value\n}\n\nasync function pptx() {\n  const pngs = await capturePngs()\n  if (!pngs)\n    return\n  const pptx = await import('pptxgenjs')\n    .then(r => r.default)\n    .then(PptxGen => new PptxGen())\n\n  const layoutName = `${slideWidth.value}x${slideHeight.value}`\n  pptx.defineLayout({\n    name: layoutName,\n    width: slideWidth.value / 96,\n    height: slideHeight.value / 96,\n  })\n  pptx.layout = layoutName\n  if (configs.author)\n    pptx.author = configs.author\n  pptx.company = 'Created using Slidev'\n  pptx.title = title.value\n  if (typeof configs.info === 'string')\n    pptx.subject = configs.info\n\n  pngs.forEach(({ slideIndex, dataUrl }) => {\n    const slide = pptx.addSlide()\n    slide.background = {\n      data: dataUrl,\n    }\n\n    const note = slides.value[slideIndex].meta.slide.note\n    if (note)\n      slide.addNotes(note)\n  })\n\n  const blob = await pptx.write({\n    outputType: 'blob',\n    compression: true,\n  }) as Blob\n  const url = URL.createObjectURL(blob)\n  const a = document.createElement('a')\n  a.href = url\n  a.download = `${title.value}.pptx`\n  a.click()\n}\n\nasync function pngsGz() {\n  const pngs = await capturePngs()\n  if (!pngs)\n    return\n  const { createTarGzip } = await import('nanotar')\n  const data = await createTarGzip(\n    pngs.map(({ slideIndex, dataUrl }) => ({\n      name: `${slideIndex}.png`,\n      data: new Uint8Array(atob(dataUrl.split(',')[1]).split('').map(char => char.charCodeAt(0))),\n    })),\n  )\n  const a = document.createElement('a')\n  const blob = new Blob([data], { type: 'application/gzip' })\n  a.href = URL.createObjectURL(blob)\n  a.download = `${title.value}.tar.gz`\n  a.click()\n}\n\nuseStyleTag(computed(() => screenshotSession.value?.isActive\n  ? `\nhtml {\n  cursor: none;\n  margin-bottom: 20px;\n}\nbody {\n  pointer-events: none;\n}`\n  : `\n:root {\n  --slidev-slide-scale: ${scale.value};\n}\n`))\n\n// clear captured images when settings changed\nwatch(\n  [\n    isDark,\n    printRange,\n    isPrintWithClicks,\n  ],\n  () => capturedImages.value = null,\n)\n\nwatchDebounced(\n  [slides, rangesRaw],\n  () => printRange.value = parseRangeString(slides.value.length, rangesRaw.value),\n  { debounce: 300 },\n)\n\n// clear captured images when HMR\nif (import.meta.hot) {\n  import.meta.hot.on('vite:beforeUpdate', () => {\n    capturedImages.value = null\n  })\n}\n</script>\n\n<template>\n  <Play v-if=\"screenshotSession?.isActive\" />\n  <div\n    v-else\n    class=\"fixed inset-0 flex flex-col md:flex-row md:gap-8 print:position-unset print:inset-0 print:block print:min-h-max justify-center of-hidden bg-main\"\n  >\n    <div class=\"print:hidden min-w-fit flex flex-wrap md:flex-nowrap md:of-y-auto md:flex-col gap-2 p-6 max-w-100\">\n      <h1 class=\"text-3xl md:my-4 flex items-center gap-2 w-full\">\n        <RouterLink to=\"/\" class=\"i-carbon:previous-outline op-70 hover:op-100\" />\n        Browser Exporter\n        <sup op50 italic text-sm>Experimental</sup>\n      </h1>\n      <div flex=\"~ col gap-2\">\n        <h2>Options</h2>\n        <FormItem title=\"Title\">\n          <input v-model=\"title\" type=\"text\">\n        </FormItem>\n        <FormItem title=\"Range\">\n          <input v-model=\"rangesRaw\" type=\"text\" :placeholder=\"`1-${slides.length}`\">\n        </FormItem>\n        <FormItem title=\"Color Mode\">\n          <SegmentControl\n            v-model=\"isDark\"\n            :options=\"[\n              { value: false, label: 'Light' },\n              { value: true, label: 'Dark' },\n            ]\"\n            :disabled=\"isColorSchemaConfigured\"\n          />\n        </FormItem>\n        <FormItem title=\"With clicks\">\n          <FormCheckbox v-model=\"isPrintWithClicks\" />\n        </FormItem>\n      </div>\n      <div class=\"flex-grow\" />\n      <div class=\"min-w-fit\" flex=\"~ col gap-3\">\n        <div border=\"~ main rounded-lg\" p3 flex=\"~ col gap-2\">\n          <h2>Export as Vector File</h2>\n          <div class=\"flex flex-col gap-2 min-w-max\">\n            <button class=\"slidev-form-button\" @click=\"pdf\">\n              PDF\n            </button>\n          </div>\n        </div>\n\n        <div border=\"~ main rounded-lg\" p3 flex=\"~ col gap-2\" :class=\"isScreenshotSupported ? '' : 'border-orange'\">\n          <h2>Export as Images</h2>\n          <div v-if=\"!isScreenshotSupported\" class=\"min-w-full w-0 text-orange/100 p-1 mb-2 bg-orange/10 rounded\">\n            <span class=\"i-carbon:warning-alt inline-block mb--.5\" />\n            Your browser may not support image capturing.\n            If you encounter issues, please use a modern Chromium-based browser,\n            or export via the CLI.\n          </div>\n          <div class=\"flex flex-col gap-2 min-w-max\">\n            <button class=\"slidev-form-button\" @click=\"pptx\">\n              PPTX\n            </button>\n            <button class=\"slidev-form-button\" @click=\"pngsGz\">\n              PNGs.gz\n            </button>\n          </div>\n          <div w-full h-1px border=\"t main\" my2 />\n          <div class=\"relative flex flex-col gap-2 flex-nowrap\">\n            <div class=\"flex flex-col gap-2 min-w-max\">\n              <button v-if=\"capturedImages\" class=\"slidev-form-button flex justify-center items-center gap-2\" @click=\"capturedImages = null\">\n                <span class=\"i-carbon:trash-can inline-block text-xl\" />\n                Clear Captured Images\n              </button>\n              <button v-else class=\"slidev-form-button flex justify-center items-center gap-2\" @click=\"capturePngs\">\n                <div class=\"i-carbon:drop-photo inline-block text-xl\" />\n                Pre-capture slides as Images\n              </button>\n              <FormItem title=\"Delay\" description=\"Delay between capturing each slide in milliseconds.<br>Increase this value if slides are captured incompletely. <br>(Not related to PDF export)\">\n                <input v-model=\"captureDelay\" type=\"number\" step=\"50\" min=\"50\">\n              </FormItem>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div id=\"export-container\" ref=\"export-container\" relative>\n      <div print:hidden fixed right-5 bottom-5 px2 py0 z-label slidev-glass-effect>\n        <span op75>Rendering as {{ capturedImages ? 'Captured Images' : 'DOM' }} </span>\n      </div>\n      <div v-show=\"!capturedImages\" id=\"export-content\" ref=\"export-content\">\n        <PrintSlide v-for=\"route, index in slides\" :key=\"index\" :hidden=\"!printRange.includes(index + 1)\" :route />\n      </div>\n      <div v-if=\"capturedImages\" id=\"export-content-images\" class=\"print:hidden grid\">\n        <div v-for=\"png, i of capturedImages\" :key=\"i\" class=\"print-slide-container\">\n          <img :src=\"png.dataUrl\">\n        </div>\n      </div>\n    </div>\n    <div id=\"twoslash-container\" />\n    <ExportPdfTip v-model=\"showExportPdfTip\" @print=\"doPrint\" />\n  </div>\n</template>\n\n<style scoped>\n@media not print {\n  #export-container {\n    scrollbar-width: thin;\n    scroll-behavior: smooth;\n    --uno: w-full overflow-x-hidden overflow-y-auto max-h-full max-w-300 p-6;\n  }\n\n  #export-content {\n    transform: v-bind('`scale(${scale})`');\n    margin-bottom: v-bind('contentMarginBottom');\n    --uno: origin-tl;\n  }\n\n  #export-content,\n  #export-content-images {\n    --uno: flex flex-col gap-2;\n  }\n}\n\n@media print {\n  #export-content {\n    transform: scale(1);\n    display: block !important;\n  }\n}\n\nlabel {\n  --uno: text-xl flex gap-2 items-center select-none;\n\n  span {\n    --uno: flex-grow;\n  }\n\n  input[type='text'],\n  input[type='number'] {\n    --uno: border border-main rounded px-2 py-1;\n  }\n}\n\nh2 {\n  --uno: font-500 op-70;\n}\n\n#export-content {\n  --uno: pointer-events-none;\n}\n</style>\n\n<style>\n@media print {\n  html,\n  body,\n  #app {\n    overflow: unset !important;\n  }\n}\n\n@media not print {\n  #export-content-images .print-slide-container,\n  #export-content .print-slide-container {\n    --uno: border border-main rounded-md shadow of-hidden;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/client/pages/notes-edit.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SlideRoute } from '@slidev/types'\nimport { useHead } from '@unhead/vue'\nimport { debouncedWatch } from '@vueuse/core'\nimport { ref } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport { useDynamicSlideInfo } from '../composables/useSlideInfo'\nimport { slidesTitle } from '../env'\nimport IconButton from '../internals/IconButton.vue'\nimport Modal from '../internals/Modal.vue'\n\nuseHead({ title: `Notes Edit - ${slidesTitle}` })\n\nconst { slides } = useNav()\n\nconst showHelp = ref(false)\nconst note = ref(serializeNotes(slides.value))\n\nfunction serializeNotes(slides: SlideRoute[]) {\n  const lines: string[] = []\n\n  for (const slide of slides) {\n    if (!slide.meta.slide.note?.trim())\n      continue\n    lines.push(`--- #${slide.no}`)\n    lines.push('')\n    lines.push(slide.meta.slide.note)\n    lines.push('')\n  }\n\n  return lines.join('\\n')\n}\n\nfunction deserializeNotes(notes: string, slides: SlideRoute[]) {\n  const lines = notes.split(/^(---\\s*#\\d+\\s*)$/gm)\n\n  lines.forEach((line, index) => {\n    const match = line.match(/^---\\s*#(\\d+)\\s*$/)\n    if (match) {\n      const no = Number.parseInt(match[1])\n      const note = lines[index + 1].trim()\n      const slide = slides.find(s => s.no === no)\n      if (slide) {\n        slide.meta.slide.note = note\n        useDynamicSlideInfo(no).update({ note })\n      }\n    }\n  })\n}\n\ndebouncedWatch(note, (value) => {\n  deserializeNotes(value, slides.value)\n}, { debounce: 300 })\n</script>\n\n<template>\n  <Modal v-model=\"showHelp\" class=\"px-6 py-4 flex flex-col gap-2\">\n    <div class=\"flex gap-2 text-xl\">\n      <div class=\"i-carbon:information my-auto\" /> Help\n    </div>\n    <div class=\"prose dark:prose-invert\">\n      <p>This is the batch notes editor. You can edit the notes for all the slides at once here.</p>\n\n      <p>The note for each slide are separated by <code>--- #[no]</code> lines, you might want to keep them while editing.</p>\n    </div>\n    <div class=\"flex my-1\">\n      <button class=\"slidev-form-button\" @click=\"showHelp = false\">\n        Close\n      </button>\n    </div>\n  </Modal>\n  <div class=\"h-full\">\n    <div class=\"slidev-glass-effect fixed bottom-5 right-5 rounded-full border border-main\">\n      <IconButton title=\"Help\" class=\"rounded-full\" @click=\"showHelp = true\">\n        <div class=\"i-carbon:help text-2xl\" />\n      </IconButton>\n    </div>\n    <textarea\n      v-model=\"note\"\n      class=\"prose dark:prose-invert resize-none p5 outline-none bg-transparent block h-full w-full! max-w-full! max-h-full! min-h-full! min-w-full!\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/pages/notes.vue",
    "content": "<script setup lang=\"ts\">\nimport { useHead } from '@unhead/vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport { createClicksContextBase } from '../composables/useClicks'\nimport { useNav } from '../composables/useNav'\nimport { slidesTitle } from '../env'\nimport ClicksSlider from '../internals/ClicksSlider.vue'\nimport CurrentProgressBar from '../internals/CurrentProgressBar.vue'\nimport IconButton from '../internals/IconButton.vue'\nimport Modal from '../internals/Modal.vue'\nimport NoteDisplay from '../internals/NoteDisplay.vue'\nimport TimerBar from '../internals/TimerBar.vue'\nimport { fullscreen } from '../state'\nimport { sharedState } from '../state/shared'\n\nuseHead({ title: `Notes - ${slidesTitle}` })\n\nconst { slides, total } = useNav()\nconst { isFullscreen, toggle: toggleFullscreen } = fullscreen\n\nconst scroller = ref<HTMLDivElement>()\nconst fontSize = useLocalStorage('slidev-notes-font-size', 18)\nconst pageNo = computed(() => sharedState.page)\nconst showHelp = ref(false)\nconst currentRoute = computed(() => slides.value.find(i => i.no === pageNo.value))\n\nwatch(pageNo, () => {\n  scroller.value?.scrollTo({ left: 0, top: 0, behavior: 'smooth' })\n  window.scrollTo({ left: 0, top: 0, behavior: 'smooth' })\n})\n\nfunction increaseFontSize() {\n  fontSize.value = fontSize.value + 1\n}\n\nfunction decreaseFontSize() {\n  fontSize.value = fontSize.value - 1\n}\n\nconst clicksContext = computed(() => {\n  const clicks = sharedState.clicks\n  const total = sharedState.clicksTotal\n  return createClicksContextBase(ref(clicks), undefined, total)\n})\n</script>\n\n<template>\n  <Modal v-model=\"showHelp\" class=\"px-6 py-4 flex flex-col gap-2\">\n    <div class=\"flex gap-2 text-xl\">\n      <div class=\"i-carbon:information my-auto\" /> Help\n    </div>\n    <div class=\"prose dark:prose-invert\">\n      <p>This is the hands-free live notes viewer.</p>\n      <p>It's designed to be used in a separate view or device. The progress is controlled by and auto synced with the main presenter or slide.</p>\n    </div>\n    <div class=\"flex my-1\">\n      <button class=\"slidev-form-button\" @click=\"showHelp = false\">\n        Close\n      </button>\n    </div>\n  </Modal>\n  <div class=\"h-full flex flex-col\">\n    <CurrentProgressBar :clicks-context=\"clicksContext\" :current=\"pageNo\" />\n    <TimerBar />\n    <div\n      ref=\"scroller\"\n      class=\"px-5 py-3 flex-auto h-full overflow-auto\"\n      :style=\"{ fontSize: `${fontSize}px` }\"\n    >\n      <NoteDisplay\n        :note=\"currentRoute?.meta.slide.note\"\n        :note-html=\"currentRoute?.meta.slide.noteHTML\"\n        :placeholder=\"`No notes for Slide ${pageNo}.`\"\n        :clicks-context=\"clicksContext\"\n        :auto-scroll=\"true\"\n      />\n    </div>\n    <div class=\"flex-none border-t border-main\" px3 py2>\n      <ClicksSlider :clicks-context=\"clicksContext\" readonly />\n    </div>\n    <div class=\"flex-none border-t border-main\">\n      <div class=\"flex gap-1 items-center px-6 py-3\">\n        <IconButton :title=\"isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'\" @click=\"toggleFullscreen\">\n          <div v-if=\"isFullscreen\" class=\"i-carbon:minimize\" />\n          <div v-else class=\"i-carbon:maximize\" />\n        </IconButton>\n        <IconButton title=\"Increase font size\" @click=\"increaseFontSize\">\n          <div class=\"i-carbon:zoom-in\" />\n        </IconButton>\n        <IconButton title=\"Decrease font size\" @click=\"decreaseFontSize\">\n          <div class=\"i-carbon:zoom-out\" />\n        </IconButton>\n        <IconButton title=\"Edit notes\" to=\"/notes-edit\" target=\"_blank\">\n          <div class=\"i-carbon:edit\" />\n        </IconButton>\n        <IconButton title=\"Help\" class=\"rounded-full\" @click=\"showHelp = true\">\n          <div class=\"i-carbon:help\" />\n        </IconButton>\n        <div class=\"flex-auto\" />\n        <div class=\"px2 my-auto\">\n          <span class=\"text-lg\">{{ pageNo }}</span>\n          <span class=\"opacity-50 text-sm\"> / {{ total }}</span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/pages/overview.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClicksContext, SlideRoute } from '@slidev/types'\nimport { useHead } from '@unhead/vue'\nimport { computed, nextTick, onMounted, reactive, ref, shallowRef } from 'vue'\nimport { createFixedClicks } from '../composables/useClicks'\nimport { useNav } from '../composables/useNav'\nimport { CLICKS_MAX } from '../constants'\nimport { pathPrefix, slidesTitle } from '../env'\nimport ClicksSlider from '../internals/ClicksSlider.vue'\nimport DrawingPreview from '../internals/DrawingPreview.vue'\nimport IconButton from '../internals/IconButton.vue'\nimport NoteEditable from '../internals/NoteEditable.vue'\nimport SlideContainer from '../internals/SlideContainer.vue'\nimport SlideWrapper from '../internals/SlideWrapper.vue'\nimport { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'\nimport { getSlidePath } from '../logic/slides'\n\nconst cardWidth = 450\n\nuseHead({ title: `Overview - ${slidesTitle}` })\n\nconst { openInEditor, slides } = useNav()\n\nconst blocks: Map<number, HTMLElement> = reactive(new Map())\nconst activeBlocks = ref<number[]>([])\nconst edittingNote = ref<number | null>(null)\nconst wordCounts = computed(() => slides.value.map(route => wordCount(route.meta?.slide?.note || '')))\nconst totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))\nconst totalClicks = computed(() => slides.value.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))\n\nconst activeSlide = shallowRef<SlideRoute>()\nconst clicksContextMap = new WeakMap<SlideRoute, ClicksContext>()\nfunction getClicksContext(route: SlideRoute) {\n  // We create a local clicks context to calculate the total clicks of the slide\n  if (!clicksContextMap.has(route))\n    clicksContextMap.set(route, createFixedClicks(route, CLICKS_MAX))\n  return clicksContextMap.get(route)!\n}\n\nfunction getSlideClicks(route: SlideRoute) {\n  return route.meta?.clicks || getClicksContext(route)?.total\n}\n\nfunction toggleRoute(route: SlideRoute) {\n  if (activeSlide.value === route)\n    activeSlide.value = undefined\n  else\n    activeSlide.value = route\n}\n\nfunction wordCount(str: string) {\n  const pattern = /[\\w`'\\-\\u0392-\\u03C9\\u00C0-\\u00FF\\u0600-\\u06FF\\u0400-\\u04FF]+|[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\uF900-\\uFAFF\\u3040-\\u309F\\uAC00-\\uD7AF]+/g\n  const m = str.match(pattern)\n  let count = 0\n  if (!m)\n    return 0\n  for (let i = 0; i < m.length; i++) {\n    if (m[i].charCodeAt(0) >= 0x4E00) {\n      count += m[i].length\n    }\n    else {\n      count += 1\n    }\n  }\n  return count\n}\n\nfunction isElementInViewport(el: HTMLElement) {\n  const rect = el.getBoundingClientRect()\n  const delta = 20\n  return (\n    rect.top >= 0 - delta\n    && rect.left >= 0 - delta\n    && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + delta\n    && rect.right <= (window.innerWidth || document.documentElement.clientWidth) + delta\n  )\n}\n\nfunction checkActiveBlocks() {\n  const active: number[] = []\n  Array.from(blocks.entries())\n    .forEach(([idx, el]) => {\n      if (isElementInViewport(el))\n        active.push(idx)\n    })\n  activeBlocks.value = active\n}\n\nfunction openSlideInNewTab(path: string) {\n  const a = document.createElement('a')\n  a.target = '_blank'\n  a.href = pathPrefix + path.slice(1)\n  a.click()\n}\n\nfunction scrollToSlide(idx: number) {\n  const el = blocks.get(idx)\n  if (el)\n    el.scrollIntoView({ behavior: 'smooth', block: 'start' })\n}\n\nfunction onMarkerClick(e: MouseEvent, clicks: number, route: SlideRoute) {\n  const ctx = getClicksContext(route)\n  if (ctx.current === clicks)\n    ctx.current = CLICKS_MAX\n  else\n    ctx.current = clicks\n  e.preventDefault()\n}\n\nonMounted(() => {\n  nextTick(() => {\n    checkActiveBlocks()\n  })\n})\n</script>\n\n<template>\n  <div class=\"h-screen w-screen of-hidden flex\">\n    <nav class=\"grid grid-rows-[auto_max-content] border-r border-main select-none max-h-full h-full\">\n      <div class=\"relative\">\n        <div class=\"absolute left-0 top-0 bottom-0 w-200 flex flex-col flex-auto items-end group p2 gap-1 max-h-full of-x-visible of-y-auto\" style=\"direction:rtl\">\n          <div\n            v-for=\"(route, idx) of slides\"\n            :key=\"route.no\"\n            class=\"relative\"\n            style=\"direction:ltr\"\n          >\n            <button\n              class=\"relative transition duration-300 w-8 h-8 rounded hover:bg-active hover:op100\"\n              :class=\"activeBlocks.includes(idx) ? 'op100 text-primary bg-gray:5' : 'op20'\"\n              @click=\"scrollToSlide(idx)\"\n            >\n              <div>{{ idx + 1 }}</div>\n            </button>\n            <div\n              v-if=\"route.meta?.slide?.title\"\n              class=\"pointer-events-none select-none absolute left-110% top-50% translate-y--50% ws-nowrap z-label px2 slidev-glass-effect transition duration-400 op0 group-hover:op100\"\n              :class=\"activeBlocks.includes(idx) ? 'text-primary' : 'text-main important-text-op-50'\"\n            >\n              {{ route.meta?.slide?.title }}\n            </div>\n          </div>\n        </div>\n      </div>\n      <div p2 border=\"t main\">\n        <IconButton\n          v-if=\"!isColorSchemaConfigured\"\n          :title=\"isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'\"\n          @click=\"toggleDark()\"\n        >\n          <carbon-moon v-if=\"isDark\" />\n          <carbon-sun v-else />\n        </IconButton>\n        <IconButton\n          v-else\n          :title=\"isDark ? 'Dark mode' : 'Light mode'\"\n          pointer-events-none op50\n        >\n          <carbon-moon v-if=\"isDark\" />\n          <carbon-sun v-else />\n        </IconButton>\n      </div>\n    </nav>\n    <main\n      class=\"flex-1 h-full of-auto\"\n      :style=\"`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`\"\n      @scroll=\"checkActiveBlocks\"\n    >\n      <div\n        v-for=\"(route, idx) of slides\"\n        :key=\"route.no\"\n        :ref=\"el => blocks.set(idx, el as any)\"\n        class=\"relative border-t border-main of-hidden flex gap-4 min-h-50 group\"\n        :class=\"idx === 0 ? 'pt5' : ''\"\n      >\n        <div class=\"select-none w-13 text-right my4 flex flex-col gap-1 items-end\">\n          <div class=\"text-3xl op20 mb2\">\n            {{ idx + 1 }}\n          </div>\n          <IconButton\n            class=\"mr--3 op0 group-hover:op80\"\n            title=\"Play in new tab\"\n            @click=\"openSlideInNewTab(getSlidePath(route, false))\"\n          >\n            <div class=\"i-carbon:presentation-file\" />\n          </IconButton>\n          <IconButton\n            v-if=\"__DEV__ && route.meta?.slide\"\n            class=\"mr--3 op0 group-hover:op80\"\n            title=\"Open in editor\"\n            @click=\"openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)\"\n          >\n            <div class=\"i-carbon:cics-program\" />\n          </IconButton>\n        </div>\n        <div class=\"flex flex-col gap-2 my5\" :style=\"{ width: `${cardWidth}px` }\">\n          <div\n            class=\"border rounded border-main overflow-hidden bg-main select-none h-max\"\n            @dblclick=\"openSlideInNewTab(getSlidePath(route, false))\"\n          >\n            <SlideContainer\n              :key=\"route.no\"\n              :width=\"cardWidth\"\n              class=\"pointer-events-none important:[&_*]:select-none\"\n            >\n              <SlideWrapper\n                :clicks-context=\"getClicksContext(route)\"\n                :route=\"route\"\n                render-context=\"overview\"\n              />\n              <DrawingPreview :page=\"route.no\" />\n            </SlideContainer>\n          </div>\n          <ClicksSlider\n            v-if=\"getSlideClicks(route)\"\n            :active=\"activeSlide === route\"\n            :clicks-context=\"getClicksContext(route)\"\n            class=\"w-full mt-2\"\n            @dblclick=\"toggleRoute(route)\"\n            @click=\"activeSlide = route\"\n          />\n        </div>\n        <div class=\"py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100\">\n          <IconButton\n            title=\"Edit Note\"\n            class=\"rounded-full w-9 h-9 text-sm\"\n            :class=\"edittingNote === route.no ? 'important:op0' : ''\"\n            @click=\"edittingNote = route.no\"\n          >\n            <div class=\"i-carbon:pen\" />\n          </IconButton>\n        </div>\n        <NoteEditable\n          :no=\"route.no\"\n          class=\"max-w-250 w-250 text-lg rounded p3\"\n          :auto-height=\"true\"\n          :highlight=\"activeSlide === route\"\n          :editing=\"edittingNote === route.no\"\n          :clicks-context=\"getClicksContext(route)\"\n          @dblclick=\"edittingNote !== route.no ? edittingNote = route.no : null\"\n          @update:editing=\"edittingNote = null\"\n          @marker-click=\"(e, clicks) => onMarkerClick(e, clicks, route)\"\n        />\n        <div\n          v-if=\"wordCounts[idx] > 0\"\n          class=\"select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs\"\n        >\n          {{ wordCounts[idx] }} words\n        </div>\n      </div>\n    </main>\n    <div class=\"absolute top-0 right-0 px3 py1.5 border-b border-l rounded-lb bg-main border-main select-none\">\n      <div class=\"text-xs op50\">\n        {{ slides.length }} slides ·\n        {{ totalClicks + slides.length - 1 }} clicks ·\n        {{ totalWords }} words\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/pages/play.vue",
    "content": "<script setup lang=\"ts\">\nimport { useStyleTag } from '@vueuse/core'\nimport { computed, ref, shallowRef } from 'vue'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useHideCursorIdle } from '../composables/useHideCursorIdle'\nimport { useNav } from '../composables/useNav'\nimport { useSwipeControls } from '../composables/useSwipeControls'\nimport { useWakeLock } from '../composables/useWakeLock'\nimport Controls from '../internals/Controls.vue'\nimport NavControls from '../internals/NavControls.vue'\nimport PresenterMouse from '../internals/PresenterMouse.vue'\nimport SlideContainer from '../internals/SlideContainer.vue'\nimport SlidesShow from '../internals/SlidesShow.vue'\nimport { onContextMenu } from '../logic/contextMenu'\nimport { registerShortcuts } from '../logic/shortcuts'\nimport { editorHeight, editorWidth, isEditorVertical, isScreenVertical, showEditor, viewerCssFilter, viewerCssFilterDefaults } from '../state'\n\nconst { next, prev, isPrintMode, isPlaying, isEmbedded } = useNav()\nconst { isDrawing } = useDrawings()\n\nconst root = ref<HTMLDivElement>()\nfunction onClick(e: MouseEvent) {\n  if (showEditor.value)\n    return\n\n  if (e.button === 0 && (e.target as HTMLElement)?.id === 'slide-container') {\n    // click right to next, left to previous\n    if ((e.pageX / window.innerWidth) > 0.5)\n      next()\n    else\n      prev()\n  }\n}\n\nuseSwipeControls(root)\nregisterShortcuts()\nif (__SLIDEV_FEATURE_WAKE_LOCK__)\n  useWakeLock()\nuseHideCursorIdle(computed(() => isPlaying.value && !isEmbedded.value && !showEditor.value))\n\nif (import.meta.hot) {\n  useStyleTag(computed(() => showEditor.value\n    ? `\n    vite-error-overlay {\n      --width: calc(100vw - ${isEditorVertical.value ? 0 : editorWidth.value}px);\n      --height: calc(100vh - ${isEditorVertical.value ? editorHeight.value : 0}px);\n      position: fixed;\n      left: 0;\n      top: 0;\n      width: calc(var(--width) / var(--slidev-slide-scale));\n      height: calc(var(--height) / var(--slidev-slide-scale));\n      transform-origin: top left;\n      transform: scale(var(--slidev-slide-scale));\n    }`\n    : '',\n  ))\n}\n\nconst persistNav = computed(() => isScreenVertical.value || showEditor.value)\n\nconst SideEditor = shallowRef<any>()\nif (__DEV__ && __SLIDEV_FEATURE_EDITOR__)\n  import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)\n\nconst contentStyle = computed(() => {\n  let filter = ''\n\n  if (viewerCssFilter.value.brightness !== viewerCssFilterDefaults.brightness)\n    filter += `brightness(${viewerCssFilter.value.brightness}) `\n  if (viewerCssFilter.value.contrast !== viewerCssFilterDefaults.contrast)\n    filter += `contrast(${viewerCssFilter.value.contrast}) `\n  if (viewerCssFilter.value.sepia !== viewerCssFilterDefaults.sepia)\n    filter += `sepia(${viewerCssFilter.value.sepia}) `\n  if (viewerCssFilter.value.hueRotate !== viewerCssFilterDefaults.hueRotate)\n    filter += `hue-rotate(${viewerCssFilter.value.hueRotate}deg) `\n  if (viewerCssFilter.value.invert)\n    filter += 'invert(1) '\n\n  return {\n    filter,\n  }\n})\n</script>\n\n<template>\n  <div\n    id=\"page-root\" ref=\"root\" class=\"grid\"\n    :class=\"isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'\"\n  >\n    <SlideContainer\n      :style=\"{ background: 'var(--slidev-slide-container-background, black)' }\"\n      is-main\n      :content-style=\"contentStyle\"\n      @pointerdown=\"onClick\"\n      @contextmenu=\"onContextMenu\"\n    >\n      <template #default>\n        <SlidesShow render-context=\"slide\" />\n        <PresenterMouse />\n      </template>\n      <template #controls>\n        <div\n          v-if=\"!isPrintMode\"\n          class=\"absolute bottom-0 left-0 transition duration-300 opacity-0 hover:opacity-100 focus-within:opacity-100 focus-visible:opacity-100\"\n          :class=\"[\n            persistNav ? '!opacity-100 right-0' : 'opacity-0 p-2',\n            isDrawing ? 'pointer-events-none' : '',\n          ]\"\n        >\n          <NavControls :persist=\"persistNav\" />\n        </div>\n      </template>\n    </SlideContainer>\n    <SideEditor v-if=\"SideEditor && showEditor\" :resize=\"true\" />\n  </div>\n  <Controls v-if=\"!isPrintMode\" />\n  <div id=\"twoslash-container\" />\n</template>\n"
  },
  {
    "path": "packages/client/pages/presenter/print.vue",
    "content": "<script setup lang=\"ts\">\nimport { useHead } from '@unhead/vue'\nimport { useStyleTag } from '@vueuse/core'\nimport { computed } from 'vue'\nimport { useNav } from '../../composables/useNav'\nimport { configs } from '../../env'\nimport NoteDisplay from '../../internals/NoteDisplay.vue'\n\nconst { slides, total } = useNav()\n\nuseStyleTag(`\n@page {\n  size: A4;\n  margin-top: 1.5cm;\n  margin-bottom: 1cm;\n}\n* {\n  -webkit-print-color-adjust: exact;\n}\nhtml,\nhtml body,\nhtml #app,\nhtml #page-root {\n  height: auto;\n  overflow: auto !important;\n}\n`)\n\nuseHead({\n  title: `Notes - ${configs.title}`,\n})\n\nconst slidesWithNote = computed(() => slides.value\n  .map(route => route.meta?.slide)\n  .filter(slide => slide !== undefined && slide.noteHTML !== ''))\n</script>\n\n<template>\n  <div id=\"page-root\">\n    <div class=\"m-4\">\n      <div class=\"mb-10\">\n        <h1 class=\"text-4xl font-bold mt-2\">\n          {{ configs.title }}\n        </h1>\n        <div class=\"opacity-50\">\n          {{ new Date().toLocaleString() }}\n        </div>\n      </div>\n\n      <div v-for=\"(slide, index) of slidesWithNote\" :key=\"index\" class=\"flex flex-col gap-4 break-inside-avoid-page\">\n        <div>\n          <h2 class=\"text-lg\">\n            <div class=\"font-bold flex gap-2\">\n              <div class=\"opacity-50\">\n                {{ slide?.no }}/{{ total }}\n              </div>\n              {{ slide?.title }}\n              <div class=\"flex-auto\" />\n            </div>\n          </h2>\n          <NoteDisplay\n            :note-html=\"slide!.noteHTML\"\n            class=\"max-w-full\"\n          />\n        </div>\n        <hr v-if=\"index < slidesWithNote.length - 1\" class=\"border-main mb-8\">\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/client/pages/presenter.vue",
    "content": "<script setup lang=\"ts\">\nimport { useHead } from '@unhead/vue'\nimport { useEventListener, useLocalStorage, useMediaQuery, useMouse, useWindowFocus } from '@vueuse/core'\nimport { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'\nimport { createClicksContextBase } from '../composables/useClicks'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useNav } from '../composables/useNav'\nimport { useSwipeControls } from '../composables/useSwipeControls'\nimport { useWakeLock } from '../composables/useWakeLock'\nimport { slidesTitle } from '../env'\nimport ClicksSlider from '../internals/ClicksSlider.vue'\nimport ContextMenu from '../internals/ContextMenu.vue'\nimport CurrentProgressBar from '../internals/CurrentProgressBar.vue'\nimport DrawingControls from '../internals/DrawingControls.vue'\nimport Goto from '../internals/Goto.vue'\nimport IconButton from '../internals/IconButton.vue'\nimport NavControls from '../internals/NavControls.vue'\nimport NoteEditable from '../internals/NoteEditable.vue'\nimport NoteStatic from '../internals/NoteStatic.vue'\nimport QuickOverview from '../internals/QuickOverview.vue'\nimport ScreenCaptureMirror from '../internals/ScreenCaptureMirror.vue'\nimport SegmentControl from '../internals/SegmentControl.vue'\nimport SlideContainer from '../internals/SlideContainer.vue'\nimport SlidesShow from '../internals/SlidesShow.vue'\nimport SlideWrapper from '../internals/SlideWrapper.vue'\nimport TimerBar from '../internals/TimerBar.vue'\nimport TimerInlined from '../internals/TimerInlined.vue'\nimport { onContextMenu } from '../logic/contextMenu'\nimport { registerShortcuts } from '../logic/shortcuts'\nimport { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showPresenterCursor } from '../state'\nimport { sharedState } from '../state/shared'\n\nconst inFocus = useWindowFocus()\nconst main = ref<HTMLDivElement>()\nconst gridContainer = ref<HTMLDivElement>()\nconst noteSection = ref<HTMLDivElement>()\nconst bottomSection = ref<HTMLDivElement>()\n\nregisterShortcuts()\nuseSwipeControls(main)\nif (__SLIDEV_FEATURE_WAKE_LOCK__)\n  useWakeLock()\n\nconst {\n  clicksContext,\n  currentSlideNo,\n  currentSlideRoute,\n  hasNext,\n  nextRoute,\n  slides,\n  getPrimaryClicks,\n} = useNav()\nconst { isDrawing } = useDrawings()\n\nuseHead({ title: `Presenter - ${slidesTitle}` })\n\nconst notesEditing = ref(false)\n\nconst clicksCtxMap = computed(() => slides.value.map((route) => {\n  const clicks = ref(0)\n  return {\n    context: createClicksContextBase(clicks, route?.meta.slide?.frontmatter.clicksStart ?? 0, route?.meta.clicks),\n    clicks,\n  }\n}))\nconst nextFrame = computed(() => {\n  if (clicksContext.value.current < clicksContext.value.total)\n    return [currentSlideRoute.value!, clicksContext.value.current + 1] as const\n  else if (hasNext.value)\n    return [nextRoute.value, 0] as const\n  else\n    return null\n})\n\nconst nextFrameClicksCtx = computed(() => {\n  return nextFrame.value && clicksCtxMap.value[nextFrame.value[0].no - 1]\n})\n\nwatch(\n  nextFrame,\n  () => {\n    if (nextFrameClicksCtx.value && nextFrame.value)\n      nextFrameClicksCtx.value.clicks.value = nextFrame.value[1]\n  },\n  { immediate: true },\n)\n\nconst mainSlideMode = useLocalStorage<'slides' | 'mirror'>('slidev-presenter-main-slide-mode', 'slides')\n\n// Resize state (persisted)\nconst notesWidth = useLocalStorage('slidev-presenter-notes-width', 360)\nconst notesRowSize = useLocalStorage('slidev-presenter-notes-row-size', 280)\nconst bottomSectionHeight = ref(0)\nconst isResizingNotes = ref(false)\nconst isResizingNotesRow = ref(false)\nconst resizeStartX = ref(0)\nconst resizeStartWidth = ref(360)\nconst resizeStartY = ref(0)\nconst resizeStartRowSize = ref(280)\n\nconst RESIZER_LIMITS = {\n  minNotesWidth: 240,\n  maxNotesWidth: 720,\n  minNotesRowSize: 160,\n  maxNotesWidthRatio: 0.7,\n  maxNotesRowHeightRatio: 0.75,\n}\n\nconst isLayout1Wide = useMediaQuery('(min-aspect-ratio: 1/1)')\nconst isLayout1Stacked = useMediaQuery('(max-aspect-ratio: 3/5)')\nconst isNotesOnRight = computed(() => presenterLayout.value === 1 && isLayout1Wide.value)\nconst isNotesResizable = computed(() => !(presenterLayout.value === 1 && isLayout1Stacked.value))\nconst isNotesRowResizable = computed(() =>\n  (presenterLayout.value === 1 && !isLayout1Stacked.value) || presenterLayout.value === 2 || presenterLayout.value === 3,\n)\nconst isNotesOnBottom = computed(() => presenterLayout.value === 1 && !isLayout1Stacked.value)\n\nfunction clampNotesWidth(width: number) {\n  if (!Number.isFinite(width))\n    return RESIZER_LIMITS.minNotesWidth\n  return Math.max(\n    RESIZER_LIMITS.minNotesWidth,\n    Math.min(RESIZER_LIMITS.maxNotesWidth, Math.round(width)),\n  )\n}\n\nfunction updateNotesWidthFromPointer(clientX: number) {\n  const container = gridContainer.value\n  if (!container)\n    return\n\n  const rect = container.getBoundingClientRect()\n  const deltaX = clientX - resizeStartX.value\n  const proposedWidth = isNotesOnRight.value\n    ? resizeStartWidth.value - deltaX\n    : resizeStartWidth.value + deltaX\n  const nextWidth = clampNotesWidth(proposedWidth)\n  const maxByViewport = Math.round(rect.width * RESIZER_LIMITS.maxNotesWidthRatio)\n  notesWidth.value = Math.min(nextWidth, Math.max(RESIZER_LIMITS.minNotesWidth, maxByViewport))\n}\n\nfunction onNotesResizeStart(e: PointerEvent) {\n  if (!isNotesResizable.value)\n    return\n  if (e.button !== 0)\n    return\n  e.preventDefault()\n  resizeStartX.value = e.clientX\n  resizeStartWidth.value = notesWidth.value\n  isResizingNotes.value = true\n}\n\nfunction clampNotesRowSize(size: number) {\n  if (!Number.isFinite(size))\n    return RESIZER_LIMITS.minNotesRowSize\n  return Math.max(RESIZER_LIMITS.minNotesRowSize, Math.round(size))\n}\n\nfunction updateNotesRowSizeFromPointer(clientY: number) {\n  const container = gridContainer.value\n  if (!container)\n    return\n\n  const rect = container.getBoundingClientRect()\n  const deltaY = clientY - resizeStartY.value\n  const proposed = isNotesOnBottom.value\n    ? resizeStartRowSize.value - deltaY\n    : resizeStartRowSize.value + deltaY\n  const maxByViewport = Math.round(rect.height * RESIZER_LIMITS.maxNotesRowHeightRatio)\n  notesRowSize.value = Math.min(clampNotesRowSize(proposed), Math.max(RESIZER_LIMITS.minNotesRowSize, maxByViewport))\n}\n\nfunction onNotesRowResizeStart(e: PointerEvent) {\n  if (!isNotesRowResizable.value)\n    return\n  if (e.button !== 0)\n    return\n  e.preventDefault()\n\n  // In layout 2, notesRowSize controls the top section (main slide) height\n  // In other layouts, it represents the notes area height\n  const currentHeight = presenterLayout.value === 2\n    ? main.value?.getBoundingClientRect().height\n    : noteSection.value?.getBoundingClientRect().height\n  resizeStartY.value = e.clientY\n  resizeStartRowSize.value = clampNotesRowSize(currentHeight ?? notesRowSize.value)\n  isResizingNotesRow.value = true\n}\n\nfunction updateBottomSectionHeight() {\n  const element = bottomSection.value\n  if (!element)\n    return\n  bottomSectionHeight.value = Math.round(element.getBoundingClientRect().height)\n}\n\nfunction stopResizing() {\n  isResizingNotes.value = false\n  isResizingNotesRow.value = false\n}\n\nfunction syncResizerLayoutState() {\n  updateBottomSectionHeight()\n  normalizeResizerState()\n}\n\nuseEventListener(window, 'pointermove', (e) => {\n  if (isResizingNotes.value)\n    updateNotesWidthFromPointer(e.clientX)\n  if (isResizingNotesRow.value)\n    updateNotesRowSizeFromPointer(e.clientY)\n})\n\nuseEventListener(window, 'pointerup', stopResizing)\nuseEventListener(window, 'pointercancel', stopResizing)\n\nonMounted(() => {\n  syncResizerLayoutState()\n})\n\nuseEventListener(window, 'resize', () => {\n  syncResizerLayoutState()\n})\n\nfunction normalizeResizerState() {\n  notesWidth.value = clampNotesWidth(notesWidth.value)\n  notesRowSize.value = clampNotesRowSize(notesRowSize.value)\n\n  const container = gridContainer.value\n  if (!container)\n    return\n\n  const rect = container.getBoundingClientRect()\n  const maxWidth = Math.round(rect.width * RESIZER_LIMITS.maxNotesWidthRatio)\n  const maxRowSize = Math.round(rect.height * RESIZER_LIMITS.maxNotesRowHeightRatio)\n\n  notesWidth.value = Math.min(notesWidth.value, Math.max(RESIZER_LIMITS.minNotesWidth, maxWidth))\n  notesRowSize.value = Math.min(notesRowSize.value, Math.max(RESIZER_LIMITS.minNotesRowSize, maxRowSize))\n}\n\nconst SideEditor = shallowRef<any>()\nif (__DEV__ && __SLIDEV_FEATURE_EDITOR__)\n  import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)\n\n// sync presenter cursor\nonMounted(() => {\n  const slidesContainer = main.value!.querySelector('#slide-content')!\n  const mouse = reactive(useMouse())\n  const focus = useWindowFocus()\n\n  watch(\n    () => {\n      if (!focus.value || isDrawing.value || !showPresenterCursor.value || !slidesContainer)\n        return undefined\n\n      const rect = slidesContainer.getBoundingClientRect()\n      const x = (mouse.x - rect.left) / rect.width * 100\n      const y = (mouse.y - rect.top) / rect.height * 100\n\n      if (x < 0 || x > 100 || y < 0 || y > 100)\n        return undefined\n\n      return { x, y }\n    },\n    (pos) => {\n      sharedState.cursor = pos\n    },\n  )\n})\n</script>\n\n<template>\n  <div class=\"bg-main h-full slidev-presenter grid grid-rows-[max-content_1fr] of-hidden\">\n    <div>\n      <CurrentProgressBar />\n      <TimerBar />\n    </div>\n    <div\n      ref=\"gridContainer\"\n      class=\"grid-container\"\n      :class=\"`layout${presenterLayout}`\"\n      :style=\"{\n        '--slidev-presenter-notes-width': `${notesWidth}px`,\n        '--slidev-presenter-notes-row-size': `${notesRowSize}px`,\n        '--slidev-presenter-bottom-height': `${bottomSectionHeight}px`,\n      }\"\n    >\n      <!-- Unified vertical resizer for wide layout -->\n      <div\n        v-if=\"isNotesResizable && isNotesOnRight\"\n        class=\"notes-vertical-resizer\"\n        role=\"separator\"\n        aria-orientation=\"vertical\"\n        title=\"Resize notes panel\"\n        @pointerdown=\"onNotesResizeStart\"\n      />\n      <!-- Unified vertical resizer for layout 3 -->\n      <div\n        v-if=\"isNotesResizable && presenterLayout === 3\"\n        class=\"notes-vertical-resizer-left\"\n        role=\"separator\"\n        aria-orientation=\"vertical\"\n        title=\"Resize notes panel\"\n        @pointerdown=\"onNotesResizeStart\"\n      />\n      <div ref=\"main\" class=\"relative grid-section main flex flex-col\">\n        <div flex=\"~ gap-4 items-center\" border=\"b main\" p1>\n          <span op50 px2>Current</span>\n          <div flex-auto />\n          <SegmentControl\n            v-model=\"mainSlideMode\"\n            :options=\"[\n              { label: 'Slides', value: 'slides' },\n              { label: 'Screen Mirror', value: 'mirror' },\n            ]\"\n          />\n        </div>\n        <template v-if=\"mainSlideMode === 'mirror'\">\n          <ScreenCaptureMirror />\n        </template>\n\n        <!-- We use v-show here to still infer the clicks context -->\n        <SlideContainer\n          v-show=\"mainSlideMode === 'slides'\"\n          key=\"main\"\n          class=\"p-2 lg:p-4 flex-auto\"\n          is-main\n          @contextmenu=\"onContextMenu\"\n        >\n          <SlidesShow render-context=\"presenter\" />\n        </SlideContainer>\n\n        <ClicksSlider\n          :key=\"currentSlideRoute?.no\"\n          :clicks-context=\"getPrimaryClicks(currentSlideRoute)\"\n          class=\"w-full pb2 px4 flex-none\"\n        />\n      </div>\n      <div class=\"relative grid-section next flex flex-col p-2 lg:p-4\">\n        <div\n          v-if=\"isNotesRowResizable && presenterLayout === 2\"\n          class=\"notes-row-resizer top-[-6px]\"\n          role=\"separator\"\n          aria-orientation=\"horizontal\"\n          title=\"Resize notes panel height\"\n          @pointerdown=\"onNotesRowResizeStart\"\n        />\n        <SlideContainer v-if=\"nextFrame && nextFrameClicksCtx\" key=\"next\">\n          <SlideWrapper\n            :key=\"nextFrame[0].no\"\n            :clicks-context=\"nextFrameClicksCtx.context\"\n            :route=\"nextFrame[0]\"\n            render-context=\"previewNext\"\n          />\n        </SlideContainer>\n        <div v-else class=\"h-full flex justify-center items-center\">\n          <div class=\"text-gray-500\">\n            End of the presentation\n          </div>\n        </div>\n        <div class=\"absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm\">\n          Next\n        </div>\n      </div>\n      <div ref=\"noteSection\" class=\"relative grid-section note overflow-hidden\">\n        <div\n          v-if=\"isNotesResizable && !isNotesOnRight && presenterLayout !== 3\"\n          class=\"notes-resizer right-[-6px]\"\n          role=\"separator\"\n          aria-orientation=\"vertical\"\n          title=\"Resize notes panel\"\n          @pointerdown=\"onNotesResizeStart\"\n        />\n        <div\n          v-if=\"isNotesRowResizable && presenterLayout !== 2\"\n          class=\"notes-row-resizer\"\n          :class=\"isNotesOnBottom ? 'top-[-6px]' : 'bottom-[-6px]'\"\n          role=\"separator\"\n          aria-orientation=\"horizontal\"\n          title=\"Resize notes panel height\"\n          @pointerdown=\"onNotesRowResizeStart\"\n        />\n\n        <SideEditor v-if=\"SideEditor && showEditor\" class=\"h-full\" />\n\n        <div v-else class=\"h-full grid grid-rows-[1fr_min-content]\">\n          <NoteEditable\n            v-if=\"__DEV__\"\n            :key=\"`edit-${currentSlideNo}`\"\n            v-model:editing=\"notesEditing\"\n            :no=\"currentSlideNo\"\n            class=\"w-full max-w-full h-full overflow-auto p-2 lg:p-4\"\n            :clicks-context=\"clicksContext\"\n            :style=\"{ fontSize: `${presenterNotesFontSize}em` }\"\n          />\n          <NoteStatic\n            v-else\n            :key=\"`static-${currentSlideNo}`\"\n            :no=\"currentSlideNo\"\n            class=\"w-full max-w-full h-full overflow-auto p-2 lg:p-4\"\n            :style=\"{ fontSize: `${presenterNotesFontSize}em` }\"\n            :clicks-context=\"clicksContext\"\n          />\n          <div border-t border-main />\n          <div class=\"py-1 px-2 text-sm transition\" :class=\"inFocus ? '' : 'op25'\">\n            <IconButton title=\"Increase font size\" @click=\"increasePresenterFontSize\">\n              <div class=\"i-carbon:zoom-in\" />\n            </IconButton>\n            <IconButton title=\"Decrease font size\" @click=\"decreasePresenterFontSize\">\n              <div class=\"i-carbon:zoom-out\" />\n            </IconButton>\n            <IconButton\n              v-if=\"__DEV__\"\n              title=\"Edit Notes\"\n              @click=\"notesEditing = !notesEditing\"\n            >\n              <div class=\"i-carbon:edit\" />\n            </IconButton>\n          </div>\n        </div>\n      </div>\n      <div ref=\"bottomSection\" class=\"grid-section bottom flex\">\n        <NavControls :persist=\"true\" class=\"transition\" :class=\"inFocus ? '' : 'op25'\" />\n        <div flex-auto />\n        <TimerInlined />\n      </div>\n      <DrawingControls v-if=\"__SLIDEV_FEATURE_DRAWINGS__\" />\n    </div>\n  </div>\n  <Goto />\n  <QuickOverview />\n  <ContextMenu />\n</template>\n\n<style scoped>\n.slidev-presenter {\n  --slidev-controls-foreground: current;\n}\n\n.grid-container {\n  --slidev-presenter-notes-width: 360px;\n  --slidev-presenter-notes-row-size: 280px;\n  --uno: bg-gray/20 flex-1 of-hidden;\n  display: grid;\n  gap: 1px 1px;\n}\n\n.grid-container.layout1 {\n  grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);\n  grid-template-rows: minmax(0, 2fr) minmax(0, var(--slidev-presenter-notes-row-size)) min-content;\n  grid-template-areas:\n    'main main'\n    'note next'\n    'bottom bottom';\n}\n\n.grid-container.layout2 {\n  grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);\n  grid-template-rows: minmax(0, var(--slidev-presenter-notes-row-size)) minmax(0, 1fr) min-content;\n  grid-template-areas:\n    'note main'\n    'note next'\n    'bottom bottom';\n}\n\n@media (max-aspect-ratio: 3/5) {\n  .grid-container.layout1 {\n    grid-template-columns: 1fr;\n    grid-template-rows: 2fr 1fr 1fr min-content;\n    grid-template-areas:\n      'main'\n      'note'\n      'next'\n      'bottom';\n  }\n}\n\n@media (min-aspect-ratio: 1/1) {\n  .grid-container.layout1 {\n    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) var(--slidev-presenter-notes-width);\n    grid-template-rows: minmax(0, 1fr) minmax(0, var(--slidev-presenter-notes-row-size)) min-content;\n    grid-template-areas:\n      'main main next'\n      'main main note'\n      'bottom bottom bottom';\n  }\n}\n\n.grid-container.layout3 {\n  grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);\n  grid-template-rows: minmax(0, var(--slidev-presenter-notes-row-size)) minmax(0, 1fr) min-content;\n  grid-template-areas:\n    'note next'\n    'main next'\n    'bottom bottom';\n}\n\n.grid-section {\n  --uno: bg-main;\n}\n.grid-section.top {\n  grid-area: top;\n}\n.grid-section.main {\n  grid-area: main;\n}\n.grid-section.next {\n  grid-area: next;\n}\n.grid-section.note {\n  grid-area: note;\n}\n.grid-section.bottom {\n  grid-area: bottom;\n}\n\n.notes-resizer {\n  position: absolute;\n  top: 0;\n  width: 12px;\n  height: 100%;\n  cursor: col-resize;\n  z-index: 10;\n  touch-action: none;\n}\n\n.notes-resizer::before {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: 0;\n  width: 1px;\n  height: 100%;\n  background-color: currentColor;\n  opacity: 0.2;\n  transform: translateX(-50%);\n}\n\n.notes-row-resizer {\n  position: absolute;\n  left: 0;\n  width: 100%;\n  height: 12px;\n  cursor: row-resize;\n  z-index: 10;\n  touch-action: none;\n}\n\n.notes-row-resizer::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 50%;\n  width: 100%;\n  height: 1px;\n  background-color: currentColor;\n  opacity: 0.2;\n  transform: translateY(-50%);\n}\n\n.notes-vertical-resizer {\n  position: absolute;\n  right: var(--slidev-presenter-notes-width);\n  top: 0;\n  bottom: var(--slidev-presenter-bottom-height, 0px);\n  width: 12px;\n  cursor: col-resize;\n  z-index: 10;\n  touch-action: none;\n  transform: translateX(50%);\n}\n\n.notes-vertical-resizer::before {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: 0;\n  width: 1px;\n  height: 100%;\n  background-color: currentColor;\n  opacity: 0.2;\n  transform: translateX(-50%);\n}\n\n.notes-vertical-resizer-left {\n  position: absolute;\n  left: var(--slidev-presenter-notes-width);\n  top: 0;\n  bottom: var(--slidev-presenter-bottom-height, 0px);\n  width: 12px;\n  cursor: col-resize;\n  z-index: 10;\n  touch-action: none;\n  transform: translateX(-50%);\n}\n\n.notes-vertical-resizer-left::before {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: 0;\n  width: 1px;\n  height: 100%;\n  background-color: currentColor;\n  opacity: 0.2;\n  transform: translateX(-50%);\n}\n</style>\n"
  },
  {
    "path": "packages/client/pages/print.vue",
    "content": "<script setup lang=\"ts\">\nimport { recomputeAllPoppers } from 'floating-vue'\nimport { onMounted, watchEffect } from 'vue'\nimport { useNav } from '../composables/useNav'\nimport PrintContainer from '../internals/PrintContainer.vue'\nimport { windowSize } from '../state'\n\nconst { isPrintMode } = useNav()\n\nwatchEffect(() => {\n  if (isPrintMode)\n    (document.body.parentNode as HTMLElement).classList.add('print')\n  else\n    (document.body.parentNode as HTMLElement).classList.remove('print')\n})\n\nonMounted(() => {\n  recomputeAllPoppers()\n})\n</script>\n\n<template>\n  <div id=\"page-root\" class=\"grid grid-cols-[1fr_max-content]\">\n    <PrintContainer\n      class=\"w-full h-full\"\n      :style=\"{ background: 'var(--slidev-slide-container-background, black)' }\"\n      :width=\"windowSize.width.value\"\n    />\n    <div id=\"twoslash-container\" />\n  </div>\n</template>\n\n<style lang=\"postcss\">\nhtml.print,\nhtml.print body,\nhtml.print #app {\n  height: auto;\n  overflow: auto;\n}\nhtml.print #page-root {\n  height: auto;\n  overflow: hidden;\n}\nhtml.print * {\n  -webkit-print-color-adjust: exact;\n}\nhtml.print {\n  width: 100%;\n  height: 100%;\n  overflow: visible;\n}\nhtml.print body {\n  margin: 0 auto;\n  border: 0;\n  padding: 0;\n  float: none;\n  overflow: visible;\n}\n</style>\n"
  },
  {
    "path": "packages/client/scripts/unocss-scan.ts",
    "content": "import fs from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { createGenerator } from '@unocss/core'\nimport fg from 'fast-glob'\nimport config from '../uno.config'\n\nconst uno = await createGenerator(config)\n\nconst files = await fg(\n  '**/*.vue',\n  {\n    cwd: fileURLToPath(new URL('..', import.meta.url)),\n    ignore: ['**/*.generated/**', '**/node_modules/**'],\n    absolute: true,\n  },\n)\n\nconst tokens = new Set<string>()\nfor (const file of files) {\n  const content = await fs.readFile(file, 'utf-8')\n  await uno.applyExtractors(\n    content,\n    file,\n    tokens,\n  )\n}\n\nconst result = await uno.generate(tokens)\n\nawait fs.writeFile(\n  fileURLToPath(new URL('../.generated/unocss-tokens.ts', import.meta.url)),\n  [\n    '/* eslint-disable eslint-comments/no-unlimited-disable */',\n    '/* eslint-disable */',\n    `export default ${JSON.stringify(Array.from(result.matched).sort(), null, 2)}`,\n  ].join('\\n'),\n  'utf-8',\n)\n"
  },
  {
    "path": "packages/client/setup/code-runners.ts",
    "content": "import type { CodeRunner, CodeRunnerOutput, CodeRunnerOutputs, CodeRunnerOutputText } from '@slidev/types'\nimport type { CodeToHastOptions } from 'shiki'\nimport type ts from 'typescript'\nimport deps from '#slidev/monaco-run-deps'\nimport setups from '#slidev/setups/code-runners'\nimport { createSingletonPromise } from '@antfu/utils'\nimport { ref } from 'vue'\nimport { configs } from '../env'\n\nexport default createSingletonPromise(async () => {\n  const runners: Record<string, CodeRunner> = {\n    javascript: runTypeScript,\n    js: runTypeScript,\n    typescript: runTypeScript,\n    ts: runTypeScript,\n  }\n\n  const { defaultHighlightOptions, getEagerHighlighter } = await (await import('./shiki')).default()\n\n  const highlighter = await getEagerHighlighter()\n  const highlight = (code: string, lang: string, options?: Partial<CodeToHastOptions>) => {\n    return highlighter.codeToHtml(code, {\n      ...defaultHighlightOptions,\n      lang,\n      ...options,\n    })\n  }\n\n  const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {\n    try {\n      const runner = runners[lang]\n      if (!runner)\n        throw new Error(`Runner for language \"${lang}\" not found`)\n      return await runner(\n        code,\n        {\n          options,\n          highlight,\n          run: async (code, lang) => {\n            return await run(code, lang, options)\n          },\n        },\n      )\n    }\n    catch (e) {\n      console.error(e)\n      return {\n        error: `${e}`,\n      }\n    }\n  }\n\n  for (const setup of setups) {\n    const result = await setup(runners)\n    Object.assign(runners, result)\n  }\n\n  return {\n    highlight,\n    run,\n  }\n})\n\n// Ported from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/playground/src/sidebar/runtime.ts\nfunction runJavaScript(code: string): CodeRunnerOutputs {\n  const result = ref<CodeRunnerOutput[]>([])\n\n  const onError = (error: any) => result.value.push({ error: String(error) })\n  const logger = (...objs: any[]) => result.value.push(objs.map(printObject))\n  const vmConsole = Object.assign({}, console)\n  vmConsole.info = vmConsole.log = vmConsole.debug = vmConsole.warn = vmConsole.error = logger\n  vmConsole.clear = () => result.value.length = 0\n  try {\n    const wrappedCode = `return async (console, __slidev_import, __slidev_on_error) => {\n    ${configs.monacoRunUseStrict ? `\"use strict\";` : ''}\n      try {\n        ${fixupCode(code)}\n      } catch (e) {\n        __slidev_on_error(e)\n      }\n    }`\n    // eslint-disable-next-line no-new-func\n    ;(new Function(wrappedCode)())(vmConsole, (specifier: string) => {\n      const mod = deps[specifier]\n      if (!mod)\n        throw new Error(`Module not found: ${specifier}.\\nAvailable modules: ${Object.keys(deps).join(', ')}. Please refer to https://sli.dev/custom/config-code-runners#additional-runner-dependencies`)\n      return mod\n    }, onError)\n  }\n  catch (error) {\n    onError(error)\n  }\n\n  function printObject(arg: any): CodeRunnerOutputText {\n    if (typeof arg === 'string') {\n      return {\n        text: arg,\n      }\n    }\n    return {\n      text: objectToText(arg),\n      highlightLang: 'javascript',\n    }\n  }\n\n  function objectToText(arg: any): string {\n    let textRep = ''\n    if (arg instanceof Error) {\n      textRep = `Error: ${JSON.stringify(arg.message)}`\n    }\n    else if (arg === null || arg === undefined || typeof arg === 'symbol') {\n      textRep = String(arg)\n    }\n    else if (Array.isArray(arg)) {\n      textRep = `[${arg.map(objectToText).join(', ')}]`\n    }\n    else if (arg instanceof Set) {\n      const setIter = [...arg]\n      textRep = `Set (${arg.size}) {${setIter.map(objectToText).join(', ')}}`\n    }\n    else if (arg instanceof Map) {\n      const mapIter = [...arg.entries()]\n      textRep\n        = `Map (${arg.size}) {${mapIter\n          .map(([k, v]) => `${objectToText(k)} => ${objectToText(v)}`)\n          .join(', ')\n        }}`\n    }\n    else if (arg instanceof RegExp) {\n      textRep = arg.toString()\n    }\n    else if (typeof arg === 'string') {\n      textRep = JSON.stringify(arg)\n    }\n    else if (typeof arg === 'object') {\n      const name = arg.constructor?.name ?? ''\n      // No one needs to know an obj is an obj\n      const nameWithoutObject = name && name === 'Object' ? '' : name\n      const prefix = nameWithoutObject ? `${nameWithoutObject}: ` : ''\n\n      // JSON.stringify omits any keys with a value of undefined. To get around this, we replace undefined with the text __undefined__ and then do a global replace using regex back to keyword undefined\n      textRep\n        = prefix\n          + JSON.stringify(arg, (_, value) => (value === undefined ? '__undefined__' : value), 2).replace(\n            /\"__undefined__\"/g,\n            'undefined',\n          )\n\n      textRep = String(textRep)\n    }\n    else {\n      textRep = String(arg)\n    }\n    return textRep\n  }\n\n  function fixupCode(code: string) {\n    // The reflect-metadata runtime is available, so allow that to go through\n    code = code.replace(`import \"reflect-metadata\"`, '').replace(`require(\"reflect-metadata\")`, '')\n    // Transpiled typescript sometimes contains an empty export, remove it.\n    code = code.replace('export {};', '')\n\n    return code\n  }\n\n  return result\n}\n\nlet tsModule: typeof import('typescript') | undefined\n\nexport async function runTypeScript(code: string) {\n  tsModule ??= await import('typescript')\n\n  code = tsModule.transpileModule(code, {\n    compilerOptions: {\n      module: tsModule.ModuleKind.ESNext,\n      target: tsModule.ScriptTarget.ES2022,\n    },\n    transformers: {\n      after: [transformImports],\n    },\n  }).outputText\n\n  const importRegex = /\\bimport\\s*\\((.+)\\)/g\n  code = code.replace(importRegex, (_full, specifier) => `__slidev_import(${specifier})`)\n\n  return runJavaScript(code)\n}\n\n/**\n * Transform import statements to dynamic imports\n */\nfunction transformImports(context: ts.TransformationContext): ts.Transformer<ts.SourceFile> {\n  const { factory } = context\n  const { isImportDeclaration, isNamedImports, NodeFlags } = tsModule!\n  return (sourceFile: ts.SourceFile) => {\n    const statements = [...sourceFile.statements]\n    for (let i = 0; i < statements.length; i++) {\n      const statement = statements[i]\n      if (!isImportDeclaration(statement))\n        continue\n      let bindingPattern: ts.ObjectBindingPattern | ts.Identifier\n      const namedBindings = statement.importClause?.namedBindings\n      const bindings: ts.BindingElement[] = []\n      if (statement.importClause?.name)\n        bindings.push(factory.createBindingElement(undefined, factory.createIdentifier('default'), statement.importClause.name))\n      if (namedBindings) {\n        if (isNamedImports(namedBindings)) {\n          for (const specifier of namedBindings.elements)\n            bindings.push(factory.createBindingElement(undefined, specifier.propertyName, specifier.name))\n          bindingPattern = factory.createObjectBindingPattern(bindings)\n        }\n        else {\n          bindingPattern = factory.createIdentifier(namedBindings.name.text)\n        }\n      }\n      else {\n        bindingPattern = factory.createObjectBindingPattern(bindings)\n      }\n\n      const newStatement = factory.createVariableStatement(\n        undefined,\n        factory.createVariableDeclarationList(\n          [\n            factory.createVariableDeclaration(\n              bindingPattern,\n              undefined,\n              undefined,\n              factory.createAwaitExpression(\n                factory.createCallExpression(\n                  factory.createIdentifier('import'),\n                  undefined,\n                  [statement.moduleSpecifier],\n                ),\n              ),\n            ),\n          ],\n          NodeFlags.Const,\n        ),\n      )\n      statements[i] = newStatement\n    }\n    return factory.updateSourceFile(sourceFile, statements)\n  }\n}\n"
  },
  {
    "path": "packages/client/setup/context-menu.ts",
    "content": "/// <reference types=\"unplugin-icons/types/vue3\" />\n\nimport type { ContextMenuItem } from '@slidev/types'\nimport type { ComputedRef } from 'vue'\nimport setups from '#slidev/setups/context-menu'\nimport { computed } from 'vue'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useNav } from '../composables/useNav'\nimport { fullscreen, showEditor, toggleOverview } from '../state'\n\n// @unocss-include\n\nlet items: ComputedRef<ContextMenuItem[]> | undefined\n\nexport default () => {\n  if (items)\n    return items\n\n  const {\n    next,\n    nextSlide,\n    prev,\n    prevSlide,\n    hasNext,\n    hasPrev,\n    currentPage,\n    total,\n    isPresenter,\n    enterPresenter,\n    exitPresenter,\n    isEmbedded,\n    isPresenterAvailable,\n  } = useNav()\n  const { drawingEnabled } = useDrawings()\n  const {\n    isFullscreen,\n    toggle: toggleFullscreen,\n  } = fullscreen\n\n  return items = setups.reduce(\n    (items, fn) => fn(items),\n    computed(() => [\n      {\n        small: true,\n        icon: 'i-carbon:arrow-left',\n        label: 'Previous Click',\n        action: prev,\n        disabled: !hasPrev.value,\n      },\n      {\n        small: true,\n        icon: 'i-carbon:arrow-right',\n        label: 'Next Click',\n        action: next,\n        disabled: !hasNext.value,\n      },\n      {\n        small: true,\n        icon: 'i-carbon:arrow-up',\n        label: 'Previous Slide',\n        action: prevSlide,\n        disabled: currentPage.value <= 1,\n      },\n      {\n        small: true,\n        icon: 'i-carbon:arrow-down',\n        label: 'Next Slide',\n        action: nextSlide,\n        disabled: currentPage.value >= total.value,\n      },\n      'separator',\n      {\n        icon: 'i-carbon:text-annotation-toggle', // IconTextNotationToggle,\n        label: showEditor.value ? 'Hide editor' : 'Show editor',\n        action: () => (showEditor.value = !showEditor.value),\n        show: __DEV__,\n      },\n      {\n        icon: 'i-carbon:pen',\n        label: drawingEnabled.value ? 'Hide drawing toolbar' : 'Show drawing toolbar',\n        action: () => (drawingEnabled.value = !drawingEnabled.value),\n      },\n      {\n        icon: 'i-carbon:apps',\n        label: 'Show slide overview',\n        action: toggleOverview,\n      },\n      isPresenter.value && {\n        icon: 'i-carbon:presentation-file',\n        label: 'Exit Presenter Mode',\n        action: exitPresenter,\n      },\n      __SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable.value && {\n        icon: 'i-carbon:user-speaker',\n        label: 'Enter Presenter Mode',\n        action: enterPresenter,\n      },\n      !isEmbedded.value && {\n        icon: isFullscreen.value ? 'i-carbon:minimize' : 'i-carbon:maximize',\n        label: isFullscreen.value ? 'Close fullscreen' : 'Enter fullscreen',\n        action: toggleFullscreen,\n      },\n    ].filter(Boolean) as ContextMenuItem[]),\n  )\n}\n"
  },
  {
    "path": "packages/client/setup/main.ts",
    "content": "import type { AppContext } from '@slidev/types'\nimport type { App } from 'vue'\nimport setups from '#slidev/setups/main'\nimport TwoSlashFloatingVue from '@shikijs/vitepress-twoslash/client'\nimport { createHead } from '@unhead/vue/client'\nimport { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'\nimport { createVClickDirectives } from '../modules/v-click'\nimport { createVDragDirective } from '../modules/v-drag'\nimport { createVMarkDirective } from '../modules/v-mark'\nimport { createVMotionDirectives } from '../modules/v-motion'\nimport setupRoutes from '../setup/routes'\n\nimport '#slidev/styles'\n\nexport default async function setupMain(app: App) {\n  function setMaxHeight() {\n    // disable the mobile navbar scroll\n    // see https://css-tricks.com/the-trick-to-viewport-units-on-mobile/\n    document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`)\n  }\n  setMaxHeight()\n  window.addEventListener('resize', setMaxHeight)\n\n  const router = createRouter({\n    history: __SLIDEV_HASH_ROUTE__\n      ? createWebHashHistory(import.meta.env.BASE_URL)\n      : createWebHistory(import.meta.env.BASE_URL),\n    routes: setupRoutes(),\n  })\n\n  app.use(router)\n  app.use(createHead())\n  app.use(createVClickDirectives())\n  app.use(createVMarkDirective())\n  app.use(createVDragDirective())\n  app.use(createVMotionDirectives())\n  app.use(TwoSlashFloatingVue as any, { container: '#twoslash-container' })\n\n  const context: AppContext = {\n    app,\n    router,\n  }\n\n  for (const setup of setups)\n    await setup(context)\n}\n"
  },
  {
    "path": "packages/client/setup/mermaid.ts",
    "content": "import type { MermaidConfig } from 'mermaid'\nimport setups from '#slidev/setups/mermaid'\nimport { createSingletonPromise } from '@antfu/utils'\n\nexport default createSingletonPromise(async () => {\n  const setupReturn: MermaidConfig = {\n    theme: 'default',\n  }\n\n  for (const setup of setups)\n    Object.assign(setupReturn, await setup())\n\n  return setupReturn\n})\n"
  },
  {
    "path": "packages/client/setup/monaco.ts",
    "content": "import type { MonacoSetupReturn } from '@slidev/types'\nimport configs from '#slidev/configs'\nimport setups from '#slidev/setups/monaco'\nimport { createSingletonPromise } from '@antfu/utils'\nimport { shikiToMonaco } from '@shikijs/monaco'\nimport { setupTypeAcquisition } from '@typescript/ata'\nimport * as monaco from 'monaco-editor'\n\nimport EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'\n// @ts-expect-error missing types\nimport { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'\nimport CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'\nimport HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'\nimport JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'\nimport TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'\n// @ts-expect-error missing types\nimport { ContextViewService } from 'monaco-editor/esm/vs/platform/contextview/browser/contextViewService'\n// @ts-expect-error missing types\nimport { SyncDescriptor } from 'monaco-editor/esm/vs/platform/instantiation/common/descriptors'\n\nimport ts from 'typescript'\nimport { watchEffect } from 'vue'\nimport { isDark } from '../logic/dark'\nimport { lockShortcuts } from '../state'\n\nwindow.MonacoEnvironment = {\n  getWorker(_, label) {\n    if (label === 'json')\n      return new JsonWorker()\n    if (label === 'css' || label === 'scss' || label === 'less')\n      return new CssWorker()\n    if (label === 'html' || label === 'handlebars' || label === 'razor')\n      return new HtmlWorker()\n    if (label === 'typescript' || label === 'javascript')\n      return new TsWorker()\n    return new EditorWorker()\n  },\n}\n\nclass ContextViewService2 extends ContextViewService {\n  showContextView(...args: any) {\n    super.showContextView(...args)\n    // @ts-expect-error missing types\n    const contextView = this.contextView.view as HTMLElement\n    contextView.style.left = `calc(${contextView.style.left} / var(--slidev-slide-scale))`\n    contextView.style.top = `calc(${contextView.style.top} / var(--slidev-slide-scale))`\n    // Reset the scale to 1. Otherwise, the sub-menu will be in the wrong position.\n    contextView.style.transform = `scale(calc(1 / var(--slidev-slide-scale)))`\n    contextView.style.transformOrigin = '0 0'\n  }\n}\n\n// Initialize services first, otherwise we can't override them.\nStandaloneServices.initialize({\n  contextViewService: new SyncDescriptor(ContextViewService2, [], true),\n})\n\nconst setup = createSingletonPromise(async () => {\n  const defaults = monaco.languages.typescript.typescriptDefaults\n  defaults.setCompilerOptions({\n    ...defaults.getCompilerOptions(),\n    strict: true,\n    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,\n    module: monaco.languages.typescript.ModuleKind.ESNext,\n  })\n\n  const ata = configs.monacoTypesSource === 'cdn'\n    ? setupTypeAcquisition({\n        projectName: 'TypeScript Playground',\n        typescript: ts as any, // Version mismatch. No problem found so far.\n        logger: console,\n        delegate: {\n          receivedFile: (code: string, path: string) => {\n            defaults.addExtraLib(code, `file://${path}`)\n            const uri = monaco.Uri.file(path)\n            if (monaco.editor.getModel(uri) === null)\n              monaco.editor.createModel(code, 'javascript', uri)\n          },\n          progress: (downloaded: number, total: number) => {\n          // eslint-disable-next-line no-console\n            console.debug(`[Typescript ATA] ${downloaded} / ${total}`)\n          },\n        },\n      })\n    : () => { }\n\n  const { getEagerHighlighter, languageNames, themeOption } = await (await import('./shiki')).default()\n\n  monaco.languages.register({ id: 'vue' })\n  monaco.languages.register({ id: 'html' })\n  monaco.languages.register({ id: 'css' })\n  monaco.languages.register({ id: 'typescript' })\n  monaco.languages.register({ id: 'javascript' })\n  for (const lang of languageNames) {\n    monaco.languages.register({ id: lang })\n  }\n\n  const editorOptions: MonacoSetupReturn['editorOptions'] & object = {}\n  for (const setup of setups) {\n    const result = await setup(monaco)\n    Object.assign(editorOptions, result?.editorOptions)\n  }\n\n  // Disable shortcuts when focusing Monaco editor.\n  monaco.editor.onDidCreateEditor((editor) => {\n    let release: (() => void) | null = null\n    editor.onDidFocusEditorWidget(() => {\n      release = lockShortcuts()\n    })\n    editor.onDidBlurEditorWidget(() => {\n      release?.()\n      release = null\n    })\n  })\n\n  // Use Shiki to highlight Monaco\n  const highlighter = await getEagerHighlighter()\n  shikiToMonaco(highlighter, monaco)\n  if (typeof themeOption === 'string') {\n    monaco.editor.setTheme(themeOption)\n  }\n  else {\n    watchEffect(() => {\n      monaco.editor.setTheme(isDark.value\n        ? themeOption.dark || 'vitesse-dark'\n        : themeOption.light || 'vitesse-light')\n    })\n  }\n\n  return {\n    monaco,\n    ata,\n    editorOptions,\n  }\n})\n\nasync function _addFile(raw: () => Promise<{ default: string }>, path: string) {\n  const uri = monaco.Uri.file(path)\n  const code = (await raw()).default\n  monaco.languages.typescript.typescriptDefaults.addExtraLib(code, `file:///${path}`)\n  monaco.editor.createModel(code, 'javascript', uri)\n}\n\nconst addFileCache = new Map<string, Promise<void>>()\n\nexport async function addFile(raw: () => Promise<{ default: string }>, path: string) {\n  if (addFileCache.has(path))\n    return addFileCache.get(path)\n  const promise = _addFile(raw, path)\n  addFileCache.set(path, promise)\n  return promise\n}\n\nexport default setup\n"
  },
  {
    "path": "packages/client/setup/root.ts",
    "content": "import setups from '#slidev/setups/root'\nimport { useHead } from '@unhead/vue'\nimport { computed, getCurrentInstance, reactive, ref, shallowRef, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { createFixedClicks } from '../composables/useClicks'\nimport { useEmbeddedControl } from '../composables/useEmbeddedCtrl'\nimport { useNav } from '../composables/useNav'\nimport { usePrintStyles } from '../composables/usePrintStyles'\nimport { injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext, TRUST_ORIGINS } from '../constants'\nimport { configs, slidesTitle } from '../env'\nimport { getSlidePath } from '../logic/slides'\nimport { makeId } from '../logic/utils'\nimport { hmrSkipTransition, syncDirections } from '../state'\nimport { initDrawingState } from '../state/drawings'\nimport { initSharedState, onPatch, patch } from '../state/shared'\n\nexport default function setupRoot() {\n  const app = getCurrentInstance()!.appContext.app\n\n  const context = reactive({\n    nav: useNav(),\n    configs,\n    themeConfigs: computed(() => configs.themeConfig),\n  })\n  app.provide(injectionRenderContext, ref('none' as const))\n  app.provide(injectionSlidevContext, context)\n  app.provide(injectionCurrentPage, computed(() => context.nav.currentSlideNo))\n  app.provide(injectionClicksContext, shallowRef(createFixedClicks()))\n\n  // allows controls from postMessages\n  if (__DEV__) {\n    // @ts-expect-error expose global\n    window.__slidev__ = context\n    useEmbeddedControl()\n  }\n\n  // User Setups\n  for (const setup of setups)\n    setup()\n\n  const {\n    clicksContext,\n    currentSlideNo,\n    hasPrimarySlide,\n    isNotesViewer,\n    isPresenter,\n    isPrintMode,\n  } = useNav()\n\n  useHead({\n    title: slidesTitle,\n    htmlAttrs: configs.htmlAttrs,\n  })\n\n  usePrintStyles()\n\n  initSharedState(`${slidesTitle} - shared`)\n  initDrawingState(`${slidesTitle} - drawings`)\n\n  const id = `${location.origin}_${makeId()}`\n  const syncType = computed(() => isPresenter.value ? 'presenter' : 'viewer')\n\n  // update shared state\n  function updateSharedState() {\n    const shouldSend = isPresenter.value\n      ? syncDirections.value.presenterSend\n      : syncDirections.value.viewerSend\n\n    if (!shouldSend)\n      return\n    if (isNotesViewer.value || isPrintMode.value)\n      return\n    // we allow Presenter mode, or Viewer mode from trusted origins to update the shared state\n    if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))\n      return\n\n    patch('page', +currentSlideNo.value)\n    patch('clicks', clicksContext.value.current)\n    patch('clicksTotal', clicksContext.value.total)\n    patch('lastUpdate', {\n      id,\n      type: syncType.value,\n      time: new Date().getTime(),\n    })\n  }\n  const router = useRouter()\n  router.afterEach(updateSharedState)\n  watch(clicksContext, updateSharedState)\n\n  onPatch((state) => {\n    const shouldReceive = isPresenter.value\n      ? syncDirections.value.presenterReceive\n      : syncDirections.value.viewerReceive\n    if (!shouldReceive)\n      return\n    if (!hasPrimarySlide.value || isPrintMode.value)\n      return\n    if (state.lastUpdate?.type === syncType.value)\n      return\n    if ((+state.page === +currentSlideNo.value && +clicksContext.value.current === +state.clicks))\n      return\n    // if (state.lastUpdate?.type === 'presenter') {\n    hmrSkipTransition.value = false\n    router.replace({\n      path: getSlidePath(state.page, isPresenter.value),\n      query: {\n        ...router.currentRoute.value.query,\n        clicks: state.clicks || 0,\n      },\n    })\n    // }\n  })\n}\n"
  },
  {
    "path": "packages/client/setup/routes.ts",
    "content": "import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'\nimport configs from '#slidev/configs'\nimport setups from '#slidev/setups/routes'\n\nexport default function setupRoutes() {\n  const routes: RouteRecordRaw[] = []\n\n  function passwordGuard(to: RouteLocationNormalized) {\n    if (!configs.remote || configs.remote === to.query.password)\n      return true\n    if (configs.remote && to.query.password === undefined) {\n      // eslint-disable-next-line no-alert\n      const password = prompt('Enter password')\n      if (configs.remote === password)\n        return true\n    }\n    if (to.params.no)\n      return { path: `/${to.params.no}` }\n    return { path: '' }\n  }\n\n  if (__SLIDEV_FEATURE_PRESENTER__) {\n    routes.push(\n      {\n        name: 'entry',\n        path: '/entry',\n        component: () => import('../pages/entry.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        name: 'overview',\n        path: '/overview',\n        component: () => import('../pages/overview.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        name: 'notes',\n        path: '/notes',\n        component: () => import('../pages/notes.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        name: 'notes-edit',\n        path: '/notes-edit',\n        component: () => import('../pages/notes-edit.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        name: 'presenter',\n        path: '/presenter/:no',\n        component: () => import('../pages/presenter.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        path: '/presenter',\n        redirect: { path: '/presenter/1' },\n      },\n    )\n  }\n\n  if (__SLIDEV_FEATURE_PRINT__) {\n    routes.push(\n      {\n        name: 'print',\n        path: '/print',\n        component: () => import('../pages/print.vue'),\n        beforeEnter: passwordGuard,\n      },\n      {\n        path: '/presenter/print',\n        component: () => import('../pages/presenter/print.vue'),\n        beforeEnter: passwordGuard,\n      },\n    )\n  }\n\n  if (__SLIDEV_FEATURE_BROWSER_EXPORTER__) {\n    routes.push(\n      {\n        name: 'export',\n        path: '/export/:no?',\n        component: () => import('../pages/export.vue'),\n        beforeEnter: passwordGuard,\n      },\n    )\n  }\n\n  routes.push(\n    {\n      name: 'play',\n      path: '/:no',\n      component: () => import('../pages/play.vue'),\n    },\n    {\n      path: '',\n      redirect: { path: '/1' },\n    },\n    {\n      path: '/:pathMatch(.*)*',\n      name: 'NotFound',\n      component: () => import('../pages/404.vue'),\n    },\n  )\n\n  return setups.reduce((routes, setup) => setup(routes), routes)\n}\n"
  },
  {
    "path": "packages/client/setup/shiki-options.ts",
    "content": "// This module also runs in the Node.js environment\n\nimport type { ResolvedSlidevUtils, ShikiContext, ShikiSetupReturn } from '@slidev/types'\nimport type { LanguageInput, ThemeInput, ThemeRegistrationAny } from 'shiki'\nimport { objectMap } from '@antfu/utils'\nimport { red, yellow } from 'ansis'\nimport { bundledLanguages, bundledThemes } from 'shiki'\n\nexport const shikiContext: ShikiContext = {\n  /** @deprecated */\n  loadTheme() {\n    throw new Error('`loadTheme` is no longer supported.')\n  },\n}\n\nexport function resolveShikiOptions(options: (ShikiSetupReturn | void)[]) {\n  const mergedOptions: Record<string, any> = Object.assign({}, ...options)\n\n  if ('theme' in mergedOptions && 'themes' in mergedOptions)\n    delete mergedOptions.theme\n\n  // Rename theme to themes when provided in multiple themes format, but exclude when it's a theme object.\n  if (mergedOptions.theme && typeof mergedOptions.theme !== 'string' && !mergedOptions.theme.name && !mergedOptions.theme.tokenColors) {\n    mergedOptions.themes = mergedOptions.theme\n    delete mergedOptions.theme\n  }\n\n  // No theme at all, apply the default\n  if (!mergedOptions.theme && !mergedOptions.themes) {\n    mergedOptions.themes = {\n      dark: 'vitesse-dark',\n      light: 'vitesse-light',\n    }\n  }\n\n  if (mergedOptions.themes)\n    mergedOptions.defaultColor = false\n\n  const themeOption = extractThemeName(mergedOptions.theme) || extractThemeNames(mergedOptions.themes || {})\n  const themeNames = typeof themeOption === 'string' ? [themeOption] : Object.values(themeOption)\n\n  const themeInput: Record<string, ThemeInput> = Object.assign({}, bundledThemes)\n  if (typeof mergedOptions.theme === 'object' && mergedOptions.theme?.name) {\n    themeInput[mergedOptions.theme.name] = mergedOptions.theme\n  }\n  if (mergedOptions.themes) {\n    for (const theme of Object.values<ThemeRegistrationAny | string>(mergedOptions.themes)) {\n      if (typeof theme === 'object' && theme?.name) {\n        themeInput[theme.name] = theme\n      }\n    }\n  }\n\n  const languageNames = new Set<string>(['markdown', 'vue', 'javascript', 'typescript', 'html', 'css'])\n  const languageInput: Record<string, LanguageInput> = Object.assign({}, bundledLanguages)\n  for (const option of options) {\n    const langs = option?.langs\n    if (langs == null)\n      continue\n    if (Array.isArray(langs)) {\n      for (const lang of langs.flat()) {\n        if (typeof lang === 'function') {\n          console.error(red('[slidev] `langs` option returned by setup/shiki.ts cannot be an array containing functions. Please use the record format (`{ [name]: () => {...} }`) instead.'))\n        }\n        else if (typeof lang === 'string') {\n          // a name of a Shiki built-in language\n          // which can be loaded on demand without overhead, so all built-in languages are available.\n          // Only need to include them explicitly in browser environment.\n          languageNames.add(lang)\n        }\n        else if (lang.name) {\n          // a custom grammar object\n          languageNames.add(lang.name)\n          languageInput[lang.name] = lang\n          for (const alias of lang.aliases || []) {\n            languageNames.add(alias)\n            languageInput[alias] = lang\n          }\n        }\n        else {\n          console.error(red('[slidev] Invalid lang option in shiki setup:'), lang)\n        }\n      }\n    }\n    else if (typeof langs === 'object') {\n      // a map from name to loader or grammar object\n      for (const name of Object.keys(langs))\n        languageNames.add(name)\n      Object.assign(languageInput, langs)\n    }\n    else {\n      console.error(red('[slidev] Invalid langs option in shiki setup:'), langs)\n    }\n  }\n\n  return {\n    options: mergedOptions as ResolvedSlidevUtils['shikiOptions'],\n    themeOption,\n    themeNames,\n    themeInput,\n    languageNames,\n    languageInput,\n  }\n}\n\nfunction extractThemeName(theme?: ThemeRegistrationAny | string): string | undefined {\n  if (!theme)\n    return undefined\n  if (typeof theme === 'string')\n    return theme\n  if (!theme.name)\n    console.warn(yellow('[slidev] Theme'), theme, yellow('does not have a name, which may cause issues.'))\n  return theme.name\n}\n\nfunction extractThemeNames(themes?: Record<string, ThemeRegistrationAny | string>): Record<string, string> {\n  if (!themes)\n    return {}\n  return objectMap(themes, (key, theme) => {\n    const name = extractThemeName(theme)\n    if (!name)\n      return undefined\n    return [key, name]\n  })\n}\n"
  },
  {
    "path": "packages/client/setup/shiki.ts",
    "content": "import setups from '#slidev/setups/shiki'\nimport { createSingletonPromise } from '@antfu/utils'\nimport { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'\nimport { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'\nimport { resolveShikiOptions, shikiContext } from './shiki-options'\n\nexport default createSingletonPromise(async () => {\n  const { options, languageNames, languageInput, themeOption, themeNames, themeInput } = resolveShikiOptions(await Promise.all(setups.map(setup => setup(shikiContext))))\n\n  const createHighlighter = createBundledHighlighter<string, string>({\n    engine: createJavaScriptRegexEngine,\n    langs: languageInput,\n    themes: themeInput,\n  })\n  const shorthands = createSingletonShorthands(createHighlighter)\n  const getEagerHighlighter = createSingletonPromise(() => shorthands.getSingletonHighlighter({\n    ...options,\n    langs: [...languageNames],\n    themes: themeNames,\n  }))\n\n  return {\n    defaultHighlightOptions: options,\n    getEagerHighlighter,\n    shorthands,\n    languageNames,\n    themeNames,\n    themeOption,\n  }\n})\n"
  },
  {
    "path": "packages/client/setup/shortcuts.ts",
    "content": "import type { NavOperations, ShortcutOptions } from '@slidev/types'\nimport setups from '#slidev/setups/shortcuts'\nimport { and, not, or } from '@vueuse/math'\nimport { useDrawings } from '../composables/useDrawings'\nimport { useNav } from '../composables/useNav'\nimport { toggleDark } from '../logic/dark'\nimport { activeDragElement, magicKeys, showGotoDialog, showOverview, toggleOverview } from '../state'\nimport { downloadPDF } from '../utils'\nimport { currentOverviewPage, downOverviewPage, nextOverviewPage, prevOverviewPage, upOverviewPage } from './../logic/overview'\n\nexport default function setupShortcuts() {\n  const { go, goFirst, goLast, next, nextSlide, prev, prevSlide } = useNav()\n  const { drawingEnabled } = useDrawings()\n  const { escape, space, shift, left, right, up, down, enter, d, g, o, '`': backtick } = magicKeys\n\n  const context: NavOperations = {\n    next,\n    prev,\n    nextSlide,\n    prevSlide,\n    go,\n    goFirst,\n    goLast,\n    downloadPDF,\n    toggleDark,\n    toggleOverview,\n    toggleDrawing: () => drawingEnabled.value = !drawingEnabled.value,\n    escapeOverview: () => showOverview.value = false,\n    showGotoDialog: () => showGotoDialog.value = !showGotoDialog.value,\n  }\n\n  const navViaArrowKeys = and(not(showOverview), not(activeDragElement))\n\n  let shortcuts: ShortcutOptions[] = [\n    { name: 'next_space', key: and(space, not(shift)), fn: next, autoRepeat: true },\n    { name: 'prev_space', key: and(space, shift), fn: prev, autoRepeat: true },\n    { name: 'next_right', key: and(right, not(shift), navViaArrowKeys), fn: next, autoRepeat: true },\n    { name: 'prev_left', key: and(left, not(shift), navViaArrowKeys), fn: prev, autoRepeat: true },\n    { name: 'next_page_key', key: 'pageDown', fn: next, autoRepeat: true },\n    { name: 'prev_page_key', key: 'pageUp', fn: prev, autoRepeat: true },\n    { name: 'next_down', key: and(down, navViaArrowKeys), fn: nextSlide, autoRepeat: true },\n    { name: 'prev_up', key: and(up, navViaArrowKeys), fn: prevSlide, autoRepeat: true },\n    { name: 'next_shift', key: and(right, shift), fn: nextSlide, autoRepeat: true },\n    { name: 'prev_shift', key: and(left, shift), fn: prevSlide, autoRepeat: true },\n    { name: 'toggle_dark', key: and(d, not(drawingEnabled)), fn: toggleDark },\n    { name: 'toggle_overview', key: and(or(o, backtick), not(drawingEnabled)), fn: toggleOverview },\n    { name: 'hide_overview', key: and(escape, not(drawingEnabled)), fn: () => showOverview.value = false },\n    { name: 'goto', key: and(g, not(drawingEnabled)), fn: () => showGotoDialog.value = !showGotoDialog.value },\n    { name: 'next_overview', key: and(right, showOverview), fn: nextOverviewPage },\n    { name: 'prev_overview', key: and(left, showOverview), fn: prevOverviewPage },\n    { name: 'up_overview', key: and(up, showOverview), fn: upOverviewPage },\n    { name: 'down_overview', key: and(down, showOverview), fn: downOverviewPage },\n    {\n      name: 'goto_from_overview',\n      key: and(enter, showOverview),\n      fn: () => {\n        go(currentOverviewPage.value)\n        showOverview.value = false\n      },\n    },\n  ]\n\n  const baseShortcutNames = new Set(shortcuts.map(s => s.name))\n\n  for (const setup of setups) {\n    shortcuts = setup(context, shortcuts)\n  }\n\n  const remainingBaseShortcutNames = shortcuts.filter(s => s.name && baseShortcutNames.has(s.name))\n  if (remainingBaseShortcutNames.length === 0) {\n    const message = [\n      '========== WARNING ==========',\n      'defineShortcutsSetup did not return any of the base shortcuts.',\n      'See https://sli.dev/custom/config-shortcuts.html for migration.',\n      'If it is intentional, return at least one shortcut with one of the base names (e.g. name:\"goto\").',\n    ].join('\\n\\n')\n    // eslint-disable-next-line no-alert\n    alert(message)\n\n    console.warn(message)\n  }\n\n  return shortcuts\n}\n"
  },
  {
    "path": "packages/client/shim-vue.d.ts",
    "content": "declare module 'vue' {\n  type SlideContext = import('./context').SlideContext\n  interface ComponentCustomProperties extends SlideContext {\n  }\n}\n\ndeclare module 'vue-router' {\n  import type { TransitionGroupProps } from 'vue'\n\n  interface RouteMeta {\n    // inherited from frontmatter\n    layout?: string\n    name?: string\n    class?: string\n    clicks?: number\n    transition?: string | TransitionGroupProps | undefined\n    preload?: boolean\n\n    // slide info\n    slide?: Omit<import('@slidev/types').SlideInfo, 'source'> & {\n      noteHTML: string\n      filepath: string\n      start: number\n      id: number\n      no: number\n    }\n\n    // private fields\n    __clicksContext: import('@slidev/types').ClicksContext | null\n    __preloaded?: boolean\n  }\n}\n\nexport {}\n"
  },
  {
    "path": "packages/client/shim.d.ts",
    "content": "declare module '*.md' {\n  // with unplugin-vue-markdown, markdowns can be treat as Vue components\n  import type { ComponentOptions } from 'vue'\n\n  const component: ComponentOptions\n  export default component\n}\n\ndeclare module 'mermaid/dist/mermaid.esm.mjs' {\n  import Mermaid from 'mermaid/dist/mermaid.d.ts'\n\n  export default Mermaid\n}\n"
  },
  {
    "path": "packages/client/state/drawings.ts",
    "content": "// @ts-expect-error - virtual module\nimport serverDrawingState from 'server-reactive:drawings?diff'\nimport { createSyncState } from './syncState'\n\nexport type DrawingsState = Record<number, string | undefined>\n\nexport const {\n  init: initDrawingState,\n  onPatch: onPatchDrawingState,\n  onUpdate: onDrawingUpdate,\n  patch: patchDrawingState,\n  state: drawingState,\n} = createSyncState<DrawingsState>(\n  serverDrawingState,\n  serverDrawingState,\n  __SLIDEV_FEATURE_DRAWINGS_PERSIST__,\n)\n"
  },
  {
    "path": "packages/client/state/index.ts",
    "content": "export * from './storage'\n"
  },
  {
    "path": "packages/client/state/shared.ts",
    "content": "// @ts-expect-error - virtual module\nimport serverState from 'server-reactive:nav'\nimport { createSyncState } from './syncState'\n\nexport interface SharedState {\n  page: number\n  clicks: number\n  clicksTotal: number\n\n  timer: {\n    status: 'stopped' | 'running' | 'paused'\n    slides: Record<number, {\n      start?: number\n      end?: number\n    }>\n    startedAt: number\n    pausedAt: number\n  }\n\n  cursor?: {\n    x: number\n    y: number\n  }\n\n  lastUpdate?: {\n    id: string\n    type: 'presenter' | 'viewer'\n    time: number\n  }\n}\n\nconst { init, onPatch, onUpdate, patch, state } = createSyncState<SharedState>(serverState, {\n  page: 1,\n  clicks: 0,\n  clicksTotal: 0,\n  timer: {\n    status: 'stopped',\n    slides: {},\n    startedAt: 0,\n    pausedAt: 0,\n  },\n})\n\nexport {\n  init as initSharedState,\n  onPatch,\n  onUpdate as onSharedUpdate,\n  patch,\n  state as sharedState,\n}\n"
  },
  {
    "path": "packages/client/state/snapshot.ts",
    "content": "// @ts-expect-error - virtual module\nimport serverSnapshotState from 'server-reactive:snapshots?diff'\nimport { createSyncState } from './syncState'\n\nexport type SnapshotState = Record<string, {\n  revision: string\n  image: string\n}>\n\nexport const snapshotState = createSyncState<SnapshotState>(\n  serverSnapshotState,\n  serverSnapshotState,\n  true,\n)\n"
  },
  {
    "path": "packages/client/state/storage.ts",
    "content": "import type { DragElementState } from '../composables/useDragElements'\nimport { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'\nimport { computed, reactive, ref, shallowRef } from 'vue'\nimport { slideAspect } from '../env'\n\nexport const showRecordingDialog = ref(false)\nexport const showInfoDialog = ref(false)\nexport const showGotoDialog = ref(false)\nexport const showOverview = ref(false)\n\n/**\n * Skip slides transition when triggered by HMR.\n * Will reset automatically after user navigations\n */\nexport const hmrSkipTransition = ref(false)\nexport const disableTransition = ref(false)\n\nexport const shortcutsEnabled = ref(true)\n\n// Use a locking mechanism to support multiple simultaneous locks\n// and avoid race conditions. Race conditions may occur, for example,\n// when locking shortcuts on editor focus and moving from one editor\n// to another, as blur events can be triggered after focus.\nconst shortcutsLocks = reactive(new Set<symbol>())\n\nexport const shortcutsLocked = computed(() => shortcutsLocks.size > 0)\n\nexport function lockShortcuts() {\n  const lock = Symbol('shortcuts lock')\n  shortcutsLocks.add(lock)\n  return () => {\n    shortcutsLocks.delete(lock)\n  }\n}\n\nexport const breakpoints = useBreakpoints({\n  xs: 460,\n  ...breakpointsTailwind,\n})\nexport const windowSize = useWindowSize()\nexport const magicKeys = useMagicKeys()\nexport const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120)\nexport const fullscreen = useFullscreen(isClient ? document.body : null)\n\nexport const activeElement = useActiveElement()\nexport const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || ''))\nexport const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))\n\nexport const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })\nexport const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })\nexport const slideScale = useLocalStorage<number>('slidev-scale', 0)\nexport const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true)\nexport const hideCursorIdle = useLocalStorage('slidev-hide-cursor-idle', true)\nexport const skipExportPdfTip = useLocalStorage('slidev-skip-export-pdf-tip', false)\nexport const captureDelay = useLocalStorage('slidev-export-capture-delay', 400, { listenToStorageChanges: false })\n\nexport const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })\nexport const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })\nexport const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })\nexport const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })\nexport const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })\n\nexport const activeDragElement = shallowRef<DragElementState | null>(null)\n\nexport const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })\nexport const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })\n\nexport const viewerCssFilterDefaults = {\n  invert: false,\n  contrast: 1,\n  brightness: 1,\n  hueRotate: 0,\n  saturate: 1,\n  sepia: 0,\n}\nexport const viewerCssFilter = useLocalStorage(\n  'slidev-viewer-css-filter',\n  viewerCssFilterDefaults,\n  { listenToStorageChanges: false, mergeDefaults: true, deep: true },\n)\nexport const hasViewerCssFilter = computed(() => {\n  return (Object.keys(viewerCssFilterDefaults) as (keyof typeof viewerCssFilterDefaults)[])\n    .some(k => viewerCssFilter.value[k] !== viewerCssFilterDefaults[k])\n})\n\nexport function togglePresenterLayout() {\n  presenterLayout.value = presenterLayout.value + 1\n  if (presenterLayout.value > 3)\n    presenterLayout.value = 1\n}\n\nexport function increasePresenterFontSize() {\n  presenterNotesFontSize.value = Math.min(2, presenterNotesFontSize.value + 0.1)\n}\n\nexport function decreasePresenterFontSize() {\n  presenterNotesFontSize.value = Math.max(0.5, presenterNotesFontSize.value - 0.1)\n}\n\nexport const toggleOverview = useToggle(showOverview)\n\nexport const syncDirections = useLocalStorage(\n  'slidev-sync-directions',\n  {\n    viewerSend: true,\n    viewerReceive: true,\n    presenterSend: true,\n    presenterReceive: true,\n  },\n  {\n    listenToStorageChanges: false,\n    mergeDefaults: true,\n  },\n)\n"
  },
  {
    "path": "packages/client/state/syncState.ts",
    "content": "import { reactive, ref, toRaw, watch } from 'vue'\n\nexport type SyncWrite<State extends object> = (state: State, updating?: boolean) => void\n\nexport interface Sync {\n  enabled?: boolean\n  init: <State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist?: boolean) => SyncWrite<State> | undefined\n}\n\ninterface BuiltinSync extends Sync {\n  channels: BroadcastChannel[]\n  disable: () => void\n  listener?: (event: StorageEvent) => void\n}\n\nconst builtinSync: BuiltinSync = {\n  channels: [],\n  enabled: true,\n  init<State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist = false) {\n    let stateChannel: BroadcastChannel\n    if (!__SLIDEV_HAS_SERVER__ && !persist) {\n      stateChannel = new BroadcastChannel(channelKey)\n      stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => onUpdate(event.data))\n      this.channels.push(stateChannel)\n    }\n    else if (!__SLIDEV_HAS_SERVER__ && persist) {\n      this.listener = function (event: StorageEvent) {\n        if (event && event.key === channelKey && event.newValue)\n          onUpdate(JSON.parse(event.newValue) as Partial<State>)\n      }\n      window.addEventListener('storage', this.listener)\n      const serializedState = window.localStorage.getItem(channelKey)\n      if (serializedState)\n        onUpdate(JSON.parse(serializedState) as Partial<State>)\n    }\n    return (state: State, updating = false) => {\n      if (this.enabled) {\n        if (!persist && stateChannel && !updating)\n          stateChannel.postMessage(toRaw(state))\n        if (persist && !updating)\n          window.localStorage.setItem(channelKey, JSON.stringify(state))\n      }\n    }\n  },\n  disable() {\n    this.enabled = false\n    this.channels.forEach(channel => channel.close())\n    if (this.listener) {\n      window.removeEventListener('storage', this.listener)\n    }\n  },\n}\nconst syncInterfaces: Sync[] = reactive([builtinSync])\nconst channels: Map<string, { onUpdate: (data: Partial<object>) => void, persist?: boolean, state: object }> = new Map()\nconst syncWrites = ref<Record<string, SyncWrite<object>[]>>({})\n\nexport function disableBuiltinSync() {\n  builtinSync.disable()\n}\n\nexport function addSyncMethod(sync: Sync) {\n  syncInterfaces.push(sync)\n  for (const [channelKey, { onUpdate, persist, state }] of channels.entries()) {\n    const write = sync.init(channelKey, onUpdate, state, persist)\n    if (write) {\n      syncWrites.value[channelKey].push(write)\n    }\n  }\n}\n\nexport function createSyncState<State extends object>(serverState: State, defaultState: State, persist = false) {\n  const onPatchCallbacks: ((state: State) => void)[] = []\n  let patching = false\n  let updating = false\n  let patchingTimeout: NodeJS.Timeout\n  let updatingTimeout: NodeJS.Timeout\n\n  const state = __SLIDEV_HAS_SERVER__\n    ? reactive<State>(serverState) as State\n    : reactive<State>(defaultState) as State\n\n  function onPatch(fn: (state: State) => void) {\n    onPatchCallbacks.push(fn)\n  }\n\n  function patch<K extends keyof State>(key: K, value: State[K]) {\n    if (state[key] === value)\n      return\n    clearTimeout(patchingTimeout)\n    patching = true\n    state[key] = value\n    patchingTimeout = setTimeout(() => patching = false, 0)\n  }\n\n  function onUpdate(patch: Partial<State>) {\n    if (!patching) {\n      clearTimeout(updatingTimeout)\n      updating = true\n      Object.entries(patch).forEach(([key, value]) => {\n        state[key as keyof State] = value as State[keyof State]\n      })\n      updatingTimeout = setTimeout(() => updating = false, 0)\n    }\n  }\n\n  function init(channelKey: string) {\n    channels.set(channelKey, { onUpdate, persist, state })\n    syncWrites.value[channelKey] = syncInterfaces\n      .map(sync => sync.init<State>(channelKey, onUpdate, state, persist))\n      .filter((x): x is SyncWrite<object> => Boolean(x))\n\n    function onStateChanged() {\n      syncWrites.value[channelKey].forEach(write => write?.(toRaw(state), updating))\n      if (!patching)\n        onPatchCallbacks.forEach((fn: (state: State) => void) => fn(state))\n    }\n\n    watch(state, onStateChanged, { deep: true })\n  }\n\n  return { init, onPatch, onUpdate, patch, state }\n}\n"
  },
  {
    "path": "packages/client/styles/code.css",
    "content": "html.dark:root {\n  color-scheme: dark;\n}\n\n/* Shiki */\nhtml.dark .shiki {\n  color: var(--shiki-dark, inherit);\n  --twoslash-popup-bg: var(--shiki-dark-bg, inherit);\n}\n\nhtml.dark .shiki span {\n  color: var(--shiki-dark);\n}\n\nhtml:not(.dark) .shiki {\n  color: var(--shiki-light, inherit);\n  --twoslash-popup-bg: var(--shiki-light-bg, inherit);\n}\n\nhtml:not(.dark) .shiki span {\n  color: var(--shiki-light);\n}\n\n.twoslash-meta-line.twoslash-popover-line {\n  margin-top: -10px;\n}\n\n/* Slidev */\n.slidev-code-wrapper {\n  margin: var(--slidev-code-margin) !important;\n  scroll-padding: var(--slidev-code-padding);\n  &:-webkit-scrollbar {\n    width: 0px;\n  }\n}\n\n.slidev-code {\n  font-family: var(--slidev-code-font-family) !important;\n  padding: var(--slidev-code-padding) !important;\n  font-size: var(--slidev-code-font-size) !important;\n  line-height: var(--slidev-code-line-height) !important;\n  border-radius: var(--slidev-code-radius) !important;\n  background: var(--slidev-code-background);\n  overflow: auto;\n}\n\n.slidev-code-block-title,\n.slidev-code-group-tabs {\n  background: var(--slidev-code-background);\n  color: var(--slidev-code-tab-text-color);\n  padding-left: var(--slidev-code-padding);\n  padding-right: var(--slidev-code-padding);\n  font-size: var(--slidev-code-tab-font-size);\n  border-radius: var(--slidev-code-radius) var(--slidev-code-radius) 0 0;\n  box-shadow: inset 0 -1px var(--slidev-code-tab-divider);\n  display: flex;\n  gap: 8px;\n  align-items: center;\n}\n\n.slidev-code-block-title {\n  padding: var(--slidev-code-padding);\n}\n\n.slidev-code-tab {\n  font-size: var(--slidev-code-tab-font-size);\n  white-space: nowrap;\n  cursor: pointer;\n  transition: color 0.25s;\n  padding: var(--slidev-code-padding);\n  border-bottom: 2px solid transparent;\n  color: var(--slidev-code-tab-text-color);\n  position: relative;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.slidev-code-tab:hover {\n  color: var(--slidev-code-tab-active-text-color) !important;\n}\n\n.slidev-code-group-blocks .slidev-code-wrapper {\n  margin: 0 !important;\n}\n\n.slidev-code-group-blocks .slidev-code {\n  border-radius: 0 0 var(--slidev-code-radius) var(--slidev-code-radius) !important;\n}\n\n.slidev-code-group-blocks .slidev-code-wrapper.active {\n  display: block;\n}\n\n.slidev-code-group-blocks .slidev-code-wrapper {\n  display: none;\n}\n\n.slidev-code-block-title + .slidev-code,\n.slidev-code-group-tabs + .slidev-code {\n  border-top-left-radius: 0 !important;\n  border-top-right-radius: 0 !important;\n}\n\n.slidev-code .slidev-code-highlighted {\n}\n.slidev-code .slidev-code-dishonored {\n  opacity: 0.3;\n  pointer-events: none;\n}\n\n.slidev-code-line-numbers .slidev-code code {\n  counter-reset: step;\n  counter-increment: step calc(var(--start, 1) - 1);\n}\n\n.slidev-code-line-numbers .slidev-code code .line::before {\n  content: counter(step);\n  counter-increment: step;\n  display: inline-block;\n  text-align: right;\n  --uno: w-4 mr-6 text-gray-400 dark-text-gray-600;\n}\n\n/* Inline Code */\n.slidev-note :not(pre) > code,\n.slidev-layout :not(pre) > code {\n  font-size: 0.9em;\n  background: var(--slidev-code-background);\n  border-radius: var(--slidev-code-radius);\n  --uno: font-light py-0.5 px-1.5;\n}\n\n.slidev-note :not(pre) > code:after,\n.slidev-note :not(pre) > code:before {\n  content: '';\n}\n\n.slidev-layout :not(pre) > code:before {\n  margin-right: -0.08em;\n}\n\n/* Revert CSS reset for KaTex */\n.katex,\n.katex :after,\n.katex :before {\n  border-color: currentColor;\n}\n"
  },
  {
    "path": "packages/client/styles/index.css",
    "content": "html,\nbody,\n#app,\n#page-root {\n  padding: 0;\n  margin: 0;\n  width: 100%;\n  height: 100vh;\n  height: calc(var(--vh, 1vh) * 100);\n  overflow: hidden;\n  print-color-adjust: exact;\n  -webkit-print-color-adjust: exact;\n  --uno: font-sans bg-main;\n}\n\nhtml {\n  background: transparent;\n}\n\n.slidev-icon-btn {\n  aspect-ratio: 1;\n  user-select: none;\n  outline: none;\n  cursor: pointer;\n  @apply inline-flex items-center justify-center opacity-75 transition duration-200 ease-in-out align-middle rounded p-1 relative;\n  @apply hover:(opacity-100 bg-gray-400 bg-opacity-10);\n  @apply focus-visible:(opacity-100 outline outline-2 outline-offset-2 outline-black dark:outline-white);\n  @apply md:p-2;\n}\n\n.slidev-icon-btn.shallow {\n  opacity: 0.3;\n}\n\n.slidev-icon-btn.active {\n  opacity: 1;\n}\n\n.slidev-icon-btn.disabled {\n  opacity: 0.25;\n  pointer-events: none;\n}\n\n.slidev-layout a.slidev-icon-btn {\n  @apply border-none hover:border-none hover:text-white;\n}\n\n.slidev-vclick-target {\n  @apply transition-opacity duration-100;\n}\n\n.slidev-vclick-hidden {\n  opacity: 0 !important;\n  pointer-events: none !important;\n  user-select: none !important;\n}\n\n.slidev-vclick-display-none {\n  display: none !important;\n}\n\n.slidev-vclick-fade {\n  opacity: 0.5;\n}\n\n.slidev-icon {\n  display: inline-block;\n  vertical-align: sub;\n  line-height: 1em;\n}\n\n.slidev-page {\n  position: relative;\n  top: 0;\n  left: 0;\n  right: 0;\n  width: 100%;\n}\n\n/* Note Clicks */\n\n.slidev-note-with-clicks .slidev-note-fade {\n  color: #888888cc;\n}\n\n.dark .slidev-note-with-clicks .slidev-note-fade {\n  color: #a1a1a1cc;\n}\n\n.slidev-note-click-mark {\n  user-select: none;\n  font-size: 0.7em;\n  display: inline-flex;\n  --uno: text-violet bg-violet/10 px1 font-mono rounded items-center border border-transparent;\n}\n.slidev-note-click-mark.slidev-note-click-mark-active {\n  --uno: border border-violet;\n}\n.slidev-note-click-mark.slidev-note-click-mark-past {\n  filter: saturate(0);\n  opacity: 0.5;\n}\n.slidev-note-click-mark.slidev-note-click-mark-future {\n  opacity: 0.5;\n}\n\n.slidev-note-click-mark::before {\n  content: '';\n  display: inline-block;\n  --un-icon: url(\"data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M23 28a1 1 0 0 1-.71-.29l-6.13-6.14l-3.33 5a1 1 0 0 1-1 .44a1 1 0 0 1-.81-.7l-6-20A1 1 0 0 1 6.29 5l20 6a1 1 0 0 1 .7.81a1 1 0 0 1-.44 1l-5 3.33l6.14 6.13a1 1 0 0 1 0 1.42l-4 4A1 1 0 0 1 23 28m0-2.41L25.59 23l-7.16-7.15l5.25-3.5L7.49 7.49l4.86 16.19l3.5-5.25Z'/%3E%3C/svg%3E\");\n  -webkit-mask: var(--un-icon) no-repeat;\n  mask: var(--un-icon) no-repeat;\n  -webkit-mask-size: 100% 100%;\n  mask-size: 100% 100%;\n  background-color: currentColor;\n  color: inherit;\n  width: 1.2em;\n  height: 1.2em;\n  opacity: 0.8;\n}\n\n.slidev-note-click-mark::after {\n  content: attr(data-clicks);\n  display: inline-block;\n  transform: translateY(0.1em);\n}\n\n.slidev-form-button {\n  --uno: text-white px-4 py-1 rounded border-b-2;\n  --uno: 'bg-gray-500 border-gray-700';\n  --uno: 'hover:(bg-gray-400 border-gray6)';\n}\n.slidev-form-button.primary {\n  --uno: bg-teal-600 border-teal-800;\n  --uno: 'hover:(bg-teal-500 border-teal-700)';\n}\n\n/* Transform the position back for Rough Notation (v-mark) */\n.rough-annotation {\n  transform: scale(calc(1 / var(--slidev-slide-scale)));\n}\n\n#twoslash-container {\n  position: fixed;\n}\n\n#twoslash-container .v-popper__wrapper:not(.no-slide-scale > *) {\n  transform: scale(calc(1 * var(--slidev-slide-scale)));\n  transform-origin: 30px top;\n}\n\n.slidev-note ul {\n  margin: 0;\n}\n"
  },
  {
    "path": "packages/client/styles/katex.css",
    "content": ".slidev-katex-wrapper .mord.highlighted {\n}\n.slidev-katex-wrapper .mord.dishonored {\n  opacity: 0.3;\n}\n"
  },
  {
    "path": "packages/client/styles/layouts-base.css",
    "content": ".slidev-layout {\n  @apply px-14 py-10 text-[1.1rem] h-full;\n\n  pre,\n  code {\n    @apply select-text;\n  }\n\n  code {\n    @apply font-mono;\n  }\n\n  h1 {\n    @apply text-4xl mb-4;\n  }\n\n  h2 {\n    @apply text-3xl;\n  }\n\n  h3 {\n    @apply text-2xl;\n  }\n\n  h4 {\n    @apply text-xl;\n  }\n\n  h5 {\n    @apply text-base;\n  }\n\n  h6 {\n    @apply text-sm pt-1 uppercase tracking-widest font-500;\n  }\n\n  h6:not(.opacity-100) {\n    @apply opacity-40;\n  }\n\n  p {\n    @apply my-4 leading-6;\n  }\n\n  ul {\n    list-style: square;\n  }\n\n  ol {\n    list-style: decimal;\n  }\n\n  li {\n    @apply leading-1.8em;\n  }\n\n  blockquote {\n    background: var(--slidev-code-background);\n    color: var(--slidev-code-foreground);\n    @apply text-sm px-2 py-1 border-primary border-l rounded;\n  }\n\n  blockquote > * {\n    @apply my-0;\n  }\n\n  table {\n    @apply w-full;\n  }\n\n  tr {\n    @apply border-b border-main;\n  }\n\n  th {\n    @apply text-left font-400;\n  }\n\n  a {\n    @apply border-current border-b border-dashed hover:text-primary hover:border-solid;\n  }\n\n  td,\n  th {\n    @apply p-2 py-3;\n  }\n\n  b,\n  strong {\n    @apply font-600;\n  }\n\n  kbd {\n    @apply border border-main border-b-2 rounded;\n    @apply bg-gray-400 bg-opacity-5 py-0.5 px-1 text-xs font-mono;\n  }\n}\n\n.slidev-layout,\n[dir='ltr'],\n.slidev-layout [dir='ltr'] {\n  h1 {\n    @apply -ml-[0.05em] mr-0;\n  }\n\n  h6 {\n    @apply -ml-[0.05em] mr-0;\n  }\n\n  li {\n    @apply ml-1.1em pl-0.2em mr-0 pr-0;\n  }\n}\n\n[dir='rtl'],\n.slidev-layout [dir='rtl'] {\n  h1 {\n    @apply -mr-[0.05em] ml-0;\n  }\n\n  h6 {\n    @apply -mr-[0.05em] ml-0;\n  }\n\n  li {\n    @apply mr-1.1em pr-0.2em ml-0 pl-0;\n  }\n}\n"
  },
  {
    "path": "packages/client/styles/shiki-twoslash.css",
    "content": ":root {\n  --twoslash-popup-bg: var(--slidev-code-background);\n  --twoslash-popup-color: var(--slidev-code-foreground);\n  --twoslash-docs-color: inherit;\n  --twoslash-docs-font: inherit;\n  --twoslash-code-font: theme('fontFamily.mono');\n  --twoslash-underline-color: #8888;\n  --twoslash-border-color: #8888;\n  --twoslash-cursor-color: var(--slidev-theme-primary);\n  --twoslash-matched-color: var(--slidev-theme-primary);\n}\n\n.twoslash-popup-container {\n  font-size: 13px;\n}\n\n.twoslash-popup-container .twoslash-popup-code {\n  font-size: 0.85em;\n}\n\n.twoslash-floating .twoslash-popup-docs-tags .twoslash-popup-docs-tag-name {\n  color: inherit;\n  opacity: 0.5;\n}\n"
  },
  {
    "path": "packages/client/styles/transitions.css",
    "content": "/* Sliding */\n.slide-left-enter-active,\n.slide-left-leave-active,\n.slide-right-enter-active,\n.slide-right-leave-active,\n.slide-up-enter-active,\n.slide-up-leave-active,\n.slide-down-enter-active,\n.slide-down-leave-active {\n  transition: all var(--slidev-transition-duration) ease;\n}\n\n.slide-left-enter-from,\n.slide-right-leave-to {\n  translate: 100% 0;\n}\n\n.slide-left-leave-to,\n.slide-right-enter-from {\n  translate: -100% 0;\n}\n\n.slide-up-enter-from,\n.slide-down-leave-to {\n  translate: 0 100%;\n}\n\n.slide-up-leave-to,\n.slide-down-enter-from {\n  translate: 0 -100%;\n}\n\n/* Fading */\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity var(--slidev-transition-duration) ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n\n.fade-out-leave-active {\n  transition: opacity calc(var(--slidev-transition-duration) * 0.6) ease-out;\n}\n\n.fade-out-enter-active {\n  transition: opacity calc(var(--slidev-transition-duration) * 0.8) ease-in;\n  transition-delay: calc(var(--slidev-transition-duration) * 0.6);\n}\n\n.fade-out-enter-from,\n.fade-out-leave-to {\n  opacity: 0;\n}\n"
  },
  {
    "path": "packages/client/styles/vars.css",
    "content": ":root {\n  --slidev-code-background: #f5f5f5;\n  --slidev-code-foreground: #1b1b1b;\n  --slidev-code-font-family: theme('fontFamily.mono');\n  --slidev-code-padding: 8px;\n  --slidev-code-font-size: 12px;\n  --slidev-code-line-height: 18px;\n  --slidev-code-radius: 4px;\n  --slidev-code-margin: 4px 0;\n  --slidev-theme-primary: #3ab9d5;\n\n  --slidev-transition-duration: 0.5s;\n  --slidev-slide-container-background: black;\n  --slidev-controls-foreground: white;\n\n  --slidev-code-tab-divider: #e5e5e5;\n  --slidev-code-tab-text-color: #67676c;\n  --slidev-code-tab-font-size: 12px;\n  --slidev-code-tab-active-text-color: #3c3c43;\n}\n\nhtml.dark {\n  --slidev-code-background: #1b1b1b;\n  --slidev-code-foreground: #eee;\n  --slidev-code-tab-divider: #222222;\n  --slidev-code-tab-text-color: #98989f;\n  --slidev-code-tab-active-text-color: #dfdfd6;\n}\n"
  },
  {
    "path": "packages/client/uno.config.ts",
    "content": "import extractorMdc from '@unocss/extractor-mdc'\nimport { variantMatcher } from '@unocss/preset-mini/utils'\nimport {\n  defineConfig,\n  presetAttributify,\n  presetTypography,\n  presetWind3,\n  transformerDirectives,\n  transformerVariantGroup,\n} from 'unocss'\n\nexport default defineConfig({\n  safelist: [\n    '!opacity-0',\n    'prose',\n    // See https://github.com/slidevjs/slidev/issues/1705\n    'grid-rows-[1fr_max-content]',\n    'grid-cols-[1fr_max-content]',\n  ],\n  shortcuts: {\n    'bg-main': 'bg-white dark:bg-[#121212]',\n    'bg-active': 'bg-gray-400/10',\n    'border-main': 'border-gray/20',\n    'text-main': 'text-[#181818] dark:text-[#ddd]',\n    'text-primary': 'color-$slidev-theme-primary',\n    'bg-primary': 'bg-$slidev-theme-primary',\n    'border-primary': 'border-$slidev-theme-primary',\n    'abs-tl': 'absolute top-0 left-0',\n    'abs-tr': 'absolute top-0 right-0',\n    'abs-b': 'absolute bottom-0 left-0 right-0',\n    'abs-bl': 'absolute bottom-0 left-0',\n    'abs-br': 'absolute bottom-0 right-0',\n\n    'z-drawing': 'z-10',\n    'z-camera': 'z-15',\n    'z-dragging': 'z-18',\n    'z-menu': 'z-20',\n    'z-label': 'z-40',\n    'z-nav': 'z-50',\n    'z-context-menu': 'z-60',\n    'z-modal': 'z-70',\n    'z-focus-indicator': 'z-200',\n\n    'slidev-glass-effect': 'shadow-xl backdrop-blur-8 border border-main bg-main bg-opacity-75!',\n  },\n  // Slidev Specific Variants, probably extrat to a preset later\n  variants: [\n    // `forward:` and `backward:` variant to selectively apply styles based on the direction of the slide\n    // For example, `forward:text-red` will only apply to the slides that are navigated forward\n    variantMatcher('forward', input => ({ prefix: `.slidev-nav-go-forward ${input.prefix}` })),\n    variantMatcher('backward', input => ({ prefix: `.slidev-nav-go-backward ${input.prefix}` })),\n  ],\n  presets: [\n    presetWind3(),\n    presetAttributify(),\n    presetTypography(),\n    /* Preset Icons is added in ../node/setups/unocss.ts */\n  ],\n  transformers: [\n    transformerDirectives({ enforce: 'pre' }),\n    transformerVariantGroup(),\n  ],\n  extractors: [\n    extractorMdc(),\n  ],\n})\n"
  },
  {
    "path": "packages/client/utils.ts",
    "content": "import type { SlideRoute } from '@slidev/types'\nimport type { DirectiveBinding, InjectionKey } from 'vue'\nimport { configs } from './env'\n\nexport function getSlideClass(route?: SlideRoute, extra = '') {\n  const classes = ['slidev-page', extra]\n\n  const no = route?.meta?.slide?.no\n  if (no != null)\n    classes.push(`slidev-page-${no}`)\n\n  return classes.filter(Boolean).join(' ')\n}\n\nexport async function downloadPDF() {\n  const { saveAs } = await import('file-saver')\n  saveAs(\n    typeof configs.download === 'string'\n      ? configs.download\n      : configs.exportFilename\n        ? `${configs.exportFilename}.pdf`\n        : `${import.meta.env.BASE_URL}slidev-exported.pdf`,\n    `${configs.title}.pdf`,\n  )\n}\n\nexport function directiveInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {\n  return (dir.instance?.$ as any).provides[key as any] ?? defaultValue\n}\n\nexport function directiveProvide<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, value?: T) {\n  const instance = dir.instance?.$ as any\n  if (instance) {\n    let provides = instance.provides\n    const parentProvides = instance.parent?.provides\n    if (provides === parentProvides)\n      provides = instance.provides = Object.create(parentProvides)\n    provides[key as any] = value\n  }\n}\n"
  },
  {
    "path": "packages/create-app/README.md",
    "content": "# create-slidev\n\n[![NPM version](https://img.shields.io/npm/v/create-slidev?color=3AB9D4&label=)](https://www.npmjs.com/package/create-slidev)\n\nStarter template generator for [Slidev](https://sli.dev).\n\n## Usage\n\n```bash\nnpm init slidev\n```\n\nor\n\n```bash\nyarn create slidev\n```\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/create-app/build.mjs",
    "content": "import { existsSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url))\nconst __templateDir = resolve(__dirname, 'template')\nconst __pagesDir = resolve(__templateDir, 'pages')\n\nconst shouldCreatePagesDict = () => !existsSync(__pagesDir)\n\n// key: copy to (relative ./)\n// value: origin (relative ./template)\nconst needCopyFiles = {\n  'slides.md': '../../../demo/starter/slides.md',\n  'pages/imported-slides.md': '../../../demo/starter/pages/imported-slides.md',\n  'snippets/external.ts': '../../../demo/starter/snippets/external.ts',\n}\n\nasync function main() {\n  if (shouldCreatePagesDict())\n    await fs.mkdir(__pagesDir, { recursive: true })\n\n  await Promise.all(\n    Object.keys(needCopyFiles).map(async (relativeTargetPath) => {\n      const sourcePath = resolve(__templateDir, needCopyFiles[relativeTargetPath])\n      const targetPath = resolve(__templateDir, relativeTargetPath)\n      if (existsSync(targetPath))\n        await fs.rm(targetPath, { recursive: true, force: true })\n\n      await fs.mkdir(dirname(targetPath), { recursive: true })\n      await fs.copyFile(sourcePath, targetPath)\n    }),\n  )\n  // eslint-disable-next-line no-console\n  console.log('done...')\n}\n\nmain()\n"
  },
  {
    "path": "packages/create-app/index.mjs",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\n\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\n// @ts-check\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { blue, bold, cyan, dim, green, yellow } from 'ansis'\nimport minimist from 'minimist'\nimport prompts from 'prompts'\nimport { x } from 'tinyexec'\n\nconst argv = minimist(process.argv.slice(2))\nconst cwd = process.cwd()\nconst require = createRequire(import.meta.url)\nconst __dirname = fileURLToPath(new URL('.', import.meta.url))\nconst { version } = require('./package.json')\n\nconst renameFiles = {\n  _gitignore: '.gitignore',\n  _npmrc: '.npmrc',\n}\n\nasync function init() {\n  console.log()\n  console.log(`  ${cyan('●') + blue('■') + yellow('▲')}`)\n  console.log(`${bold('  Slidev') + dim(' Creator')}  ${blue(`v${version}`)}`)\n  console.log()\n\n  let targetDir = argv._[0]\n  if (!targetDir) {\n    /**\n     * @type {{ projectName: string }}\n     */\n    const { projectName } = await prompts({\n      type: 'text',\n      name: 'projectName',\n      message: 'Project name:',\n      initial: 'slidev',\n    })\n    targetDir = projectName.trim()\n  }\n  const packageName = await getValidPackageName(targetDir)\n  const root = path.join(cwd, targetDir)\n\n  if (!fs.existsSync(root)) {\n    fs.mkdirSync(root, { recursive: true })\n  }\n  else {\n    const existing = fs.readdirSync(root)\n    if (existing.length) {\n      console.log(yellow(`  Target directory \"${targetDir}\" is not empty.`))\n      /**\n       * @type {{ yes: boolean }}\n       */\n      const { yes } = await prompts({\n        type: 'confirm',\n        name: 'yes',\n        initial: 'Y',\n        message: 'Remove existing files and continue?',\n      })\n      if (yes)\n        emptyDir(root)\n\n      else\n        return\n    }\n  }\n\n  console.log(dim('  Scaffolding project in ') + targetDir + dim(' ...'))\n\n  const templateDir = path.join(__dirname, 'template')\n\n  const write = (file, content) => {\n    const targetPath = renameFiles[file]\n      ? path.join(root, renameFiles[file])\n      : path.join(root, file)\n    if (content)\n      fs.writeFileSync(targetPath, content)\n\n    else\n      copy(path.join(templateDir, file), targetPath)\n  }\n\n  const files = fs.readdirSync(templateDir)\n  for (const file of files.filter(f => f !== 'package.json'))\n    write(file)\n\n  const pkg = require(path.join(templateDir, 'package.json'))\n\n  pkg.name = packageName\n\n  write('package.json', JSON.stringify(pkg, null, 2))\n\n  const pkgManager = (/pnpm/.test(process.env.npm_execpath || '') || /pnpm/.test(process.env.npm_config_user_agent || ''))\n    ? 'pnpm'\n    : /yarn/.test(process.env.npm_execpath || '') ? 'yarn' : 'npm'\n\n  const related = path.relative(cwd, root)\n\n  console.log(green('  Done.\\n'))\n\n  /**\n   * @type {{ yes: boolean }}\n   */\n  const { yes } = await prompts({\n    type: 'confirm',\n    name: 'yes',\n    initial: 'Y',\n    message: 'Install and start it now?',\n  })\n\n  if (yes) {\n    const { agent } = await prompts({\n      name: 'agent',\n      type: 'select',\n      message: 'Choose the package manager',\n      choices: ['npm', 'yarn', 'pnpm', 'bun', 'deno'].map(i => ({ value: i, title: i })),\n    })\n\n    if (!agent)\n      return\n\n    await x(agent, ['install'], { nodeOptions: { stdio: 'inherit', cwd: root } })\n    await x(agent, ['run', 'dev'], { nodeOptions: { stdio: 'inherit', cwd: root } })\n  }\n  else {\n    console.log(dim('\\n  start it later by:\\n'))\n    if (root !== cwd)\n      console.log(blue(`  cd ${bold(related)}`))\n\n    console.log(blue(`  ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`))\n    console.log(blue(`  ${pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`}`))\n    console.log()\n    console.log(`  ${cyan('●')} ${blue('■')} ${yellow('▲')}`)\n    console.log()\n  }\n}\n\nfunction copy(src, dest) {\n  const stat = fs.statSync(src)\n  if (stat.isDirectory())\n    copyDir(src, dest)\n\n  else\n    fs.copyFileSync(src, dest)\n}\n\nasync function getValidPackageName(projectName) {\n  projectName = path.basename(projectName)\n  const packageNameRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/\n  if (packageNameRegExp.test(projectName)) {\n    return projectName\n  }\n  else {\n    const suggestedPackageName = projectName\n      .trim()\n      .toLowerCase()\n      .replace(/\\s+/g, '-')\n      .replace(/^[._]/, '')\n      .replace(/[^a-z0-9-~]+/g, '-')\n\n    /**\n     * @type {{ inputPackageName: string }}\n     */\n    const { inputPackageName } = await prompts({\n      type: 'text',\n      name: 'inputPackageName',\n      message: 'Package name:',\n      initial: suggestedPackageName,\n      validate: input => packageNameRegExp.test(input) ? true : 'Invalid package.json name',\n    })\n    return inputPackageName\n  }\n}\n\nfunction copyDir(srcDir, destDir) {\n  fs.mkdirSync(destDir, { recursive: true })\n  for (const file of fs.readdirSync(srcDir)) {\n    const srcFile = path.resolve(srcDir, file)\n    const destFile = path.resolve(destDir, file)\n    copy(srcFile, destFile)\n  }\n}\n\nfunction emptyDir(dir) {\n  if (!fs.existsSync(dir))\n    return\n\n  for (const file of fs.readdirSync(dir)) {\n    const abs = path.resolve(dir, file)\n    // baseline is Node 12 so can't use rmSync :(\n    if (fs.lstatSync(abs).isDirectory()) {\n      emptyDir(abs)\n      fs.rmdirSync(abs)\n    }\n    else {\n      fs.unlinkSync(abs)\n    }\n  }\n}\n\ninit().catch((e) => {\n  console.error(e)\n})\n"
  },
  {
    "path": "packages/create-app/package.json",
    "content": "{\n  \"name\": \"create-slidev\",\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Create starter template for Slidev\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"main\": \"index.mjs\",\n  \"bin\": {\n    \"create-slidev\": \"index.mjs\"\n  },\n  \"files\": [\n    \"index.mjs\",\n    \"template\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"node build.mjs\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"ansis\": \"catalog:prod\",\n    \"minimist\": \"catalog:prod\",\n    \"prompts\": \"catalog:prod\",\n    \"tinyexec\": \"catalog:prod\"\n  }\n}\n"
  },
  {
    "path": "packages/create-app/template/README.md",
    "content": "# Welcome to [Slidev](https://github.com/slidevjs/slidev)!\n\nTo start the slide show:\n\n- `pnpm install`\n- `pnpm dev`\n- visit <http://localhost:3030>\n\nEdit the [slides.md](./slides.md) to see the changes.\n\nLearn more about Slidev at the [documentation](https://sli.dev/).\n"
  },
  {
    "path": "packages/create-app/template/_gitignore",
    "content": "node_modules\n.DS_Store\ndist\n*.local\n.vite-inspect\n.remote-assets\ncomponents.d.ts\n"
  },
  {
    "path": "packages/create-app/template/_npmrc",
    "content": "# for pnpm\nshamefully-hoist=true\nauto-install-peers=true\n"
  },
  {
    "path": "packages/create-app/template/components/Counter.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nconst props = defineProps({\n  count: {\n    default: 0,\n  },\n})\n\nconst counter = ref(props.count)\n</script>\n\n<template>\n  <div flex=\"~\" w=\"min\" border=\"~ main rounded-md\">\n    <button\n      border=\"r main\"\n      p=\"2\"\n      font=\"mono\"\n      outline=\"!none\"\n      hover:bg=\"gray-400 opacity-20\"\n      @click=\"counter -= 1\"\n    >\n      -\n    </button>\n    <span m=\"auto\" p=\"2\">{{ counter }}</span>\n    <button\n      border=\"l main\"\n      p=\"2\"\n      font=\"mono\"\n      outline=\"!none\"\n      hover:bg=\"gray-400 opacity-20\"\n      @click=\"counter += 1\"\n    >\n      +\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/create-app/template/netlify.toml",
    "content": "[build]\npublish = \"dist\"\ncommand = \"npm run build\"\n\n[build.environment]\nNODE_VERSION = \"20\"\n\n[[redirects]]\nfrom = \"/.well-known/*\"\nto = \"/.well-known/:splat\"\nstatus = 200\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.html\"\nstatus = 200\n"
  },
  {
    "path": "packages/create-app/template/package.json",
    "content": "{\n  \"name\": \"untitled\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"slidev build\",\n    \"dev\": \"slidev --open\",\n    \"export\": \"slidev export\"\n  },\n  \"dependencies\": {\n    \"@slidev/cli\": \"^52.14.1\",\n    \"@slidev/theme-default\": \"latest\",\n    \"@slidev/theme-seriph\": \"latest\",\n    \"vue\": \"^3.5.29\"\n  }\n}\n"
  },
  {
    "path": "packages/create-app/template/vercel.json",
    "content": "{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/index.html\" }\n  ],\n  \"buildCommand\": \"npm run build\",\n  \"outputDirectory\": \"dist\"\n}\n"
  },
  {
    "path": "packages/create-theme/README.md",
    "content": "# create-slidev-theme\n\n[![NPM version](https://img.shields.io/npm/v/create-slidev-theme?color=3AB9D4&label=)](https://www.npmjs.com/package/create-slidev-theme)\n\n[Slidev](https://sli.dev) theme template generater.\n\n## Usage\n\n```bash\nnpm init slidev-theme\n```\n\nor\n\n```bash\nyarn create slidev-theme\n```\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/create-theme/index.mjs",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\n\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\n// @ts-check\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { blue, bold, cyan, dim, green, yellow } from 'ansis'\nimport minimist from 'minimist'\nimport prompts from 'prompts'\n\nconst argv = minimist(process.argv.slice(2))\nconst cwd = process.cwd()\nconst require = createRequire(import.meta.url)\nconst __dirname = fileURLToPath(new URL('.', import.meta.url))\nconst { version } = require('./package.json')\n\nconst renameFiles = {\n  _gitignore: '.gitignore',\n  _npmrc: '.npmrc',\n}\n\nasync function init() {\n  console.log()\n  console.log(`  ${cyan('●') + blue('■') + yellow('▲')}`)\n  console.log(`${bold('  Slidev') + dim(' Theme Creator')}  ${blue(`v${version}`)}`)\n  console.log()\n\n  let targetDir = argv._[0]\n  if (!targetDir) {\n    /**\n     * @type {{ name: string }}\n     */\n    const { name } = await prompts({\n      type: 'text',\n      name: 'name',\n      message: 'Theme name:',\n      initial: 'slidev-theme-starter',\n    })\n    targetDir = name\n  }\n  const packageName = await getValidPackageName(targetDir)\n  const root = path.join(cwd, targetDir)\n\n  if (!fs.existsSync(root)) {\n    fs.mkdirSync(root, { recursive: true })\n  }\n  else {\n    const existing = fs.readdirSync(root)\n    if (existing.length) {\n      console.log(yellow(`  Target directory \"${targetDir}\" is not empty.`))\n      /**\n       * @type {{ yes: boolean }}\n       */\n      const { yes } = await prompts({\n        type: 'confirm',\n        name: 'yes',\n        initial: 'Y',\n        message: 'Remove existing files and continue?',\n      })\n      if (yes)\n        emptyDir(root)\n\n      else\n        return\n    }\n  }\n\n  console.log(dim('  Scaffolding Slidev theme in ') + targetDir + dim(' ...'))\n\n  prepareTemplate(root, path.join(__dirname, 'template'), packageName)\n\n  const pkgManager = (/pnpm/.test(process.env.npm_execpath) || /pnpm/.test(process.env.npm_config_user_agent))\n    ? 'pnpm'\n    : /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'\n\n  const related = path.relative(cwd, root)\n\n  console.log(green('  Done.\\n'))\n\n  console.log(dim('\\n  start it by:\\n'))\n  if (root !== cwd)\n    console.log(blue(`  cd ${bold(related)}`))\n\n  console.log(blue(`  ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`))\n  console.log(blue(`  ${pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`}`))\n  console.log()\n  console.log(`  ${cyan('●')} ${blue('■')} ${yellow('▲')}`)\n  console.log()\n}\n\nfunction prepareTemplate(root, templateDir, packageName) {\n  const write = (file, content) => {\n    const targetPath = renameFiles[file]\n      ? path.join(root, renameFiles[file])\n      : path.join(root, file)\n    if (content)\n      fs.writeFileSync(targetPath, content)\n\n    else\n      copy(path.join(templateDir, file), targetPath)\n  }\n\n  const files = fs.readdirSync(templateDir)\n  for (const file of files.filter(f => !['package.json', 'README.md'].includes(f)))\n    write(file)\n\n  write(\n    'package.json',\n    JSON.stringify({\n      name: packageName,\n      version: '0.0.0',\n      ...require(path.join(templateDir, 'package.json')),\n    }, null, 2),\n  )\n\n  write(\n    'README.md',\n    fs\n      .readFileSync(path.join(templateDir, 'README.md'), 'utf-8')\n      .replace(/\\{\\{package-name\\}\\}/g, packageName)\n      .replace(/\\{\\{name\\}\\}/g, getThemeName(packageName)),\n  )\n}\n\nfunction copy(src, dest) {\n  const stat = fs.statSync(src)\n  if (stat.isDirectory())\n    copyDir(src, dest)\n\n  else\n    fs.copyFileSync(src, dest)\n}\n\nfunction getValidPackageName(projectName) {\n  projectName = path.basename(projectName)\n  if (!projectName.startsWith('slidev-theme-'))\n    return `slidev-theme-${projectName}`\n  return projectName\n}\n\nfunction getThemeName(pkgName) {\n  return pkgName.replace(/^slidev-theme-/, '')\n}\n\nfunction copyDir(srcDir, destDir) {\n  fs.mkdirSync(destDir, { recursive: true })\n  for (const file of fs.readdirSync(srcDir)) {\n    const srcFile = path.resolve(srcDir, file)\n    const destFile = path.resolve(destDir, file)\n    copy(srcFile, destFile)\n  }\n}\n\nfunction emptyDir(dir) {\n  if (!fs.existsSync(dir))\n    return\n\n  for (const file of fs.readdirSync(dir)) {\n    const abs = path.resolve(dir, file)\n    // baseline is Node 12 so can't use rmSync :(\n    if (fs.lstatSync(abs).isDirectory()) {\n      emptyDir(abs)\n      fs.rmdirSync(abs)\n    }\n    else {\n      fs.unlinkSync(abs)\n    }\n  }\n}\n\ninit().catch((e) => {\n  console.error(e)\n})\n"
  },
  {
    "path": "packages/create-theme/package.json",
    "content": "{\n  \"name\": \"create-slidev-theme\",\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Create starter theme template for Slidev\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"main\": \"index.mjs\",\n  \"bin\": {\n    \"create-slidev-theme\": \"index.mjs\"\n  },\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"dependencies\": {\n    \"ansis\": \"catalog:prod\",\n    \"minimist\": \"catalog:prod\",\n    \"prompts\": \"catalog:prod\"\n  }\n}\n"
  },
  {
    "path": "packages/create-theme/template/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"antfu.slidev\"]\n}\n"
  },
  {
    "path": "packages/create-theme/template/README.md",
    "content": "# {{package-name}}\n\n[![NPM version](https://img.shields.io/npm/v/{{package-name}}?color=3AB9D4&label=)](https://www.npmjs.com/package/{{package-name}})\n\nA (...) theme for [Slidev](https://github.com/slidevjs/slidev).\n\n<!--\n  Learn more about how to write a theme:\n  https://sli.dev/guide/write-theme.html\n--->\n\n<!--\n  run `npm run dev` to check out the slides for more details of how to start writing a theme\n-->\n\n<!--\n  Put some screenshots here to demonstrate your theme\n\n  Live demo: [...]\n-->\n\n## Install\n\nAdd the following frontmatter to your `slides.md`. Start Slidev then it will prompt you to install the theme automatically.\n\n<pre><code>---\ntheme: <b>{{name}}</b>\n---</code></pre>\n\nLearn more about [how to use a theme](https://sli.dev/guide/theme-addon#use-theme).\n\n## Layouts\n\nThis theme provides the following layouts:\n\n> TODO:\n\n## Components\n\nThis theme provides the following components:\n\n> TODO:\n\n## Contributing\n\n- `npm install`\n- `npm run dev` to start theme preview of `example.md`\n- Edit the `example.md` and style to see the changes\n- `npm run export` to generate the preview PDF\n- `npm run screenshot` to generate the preview PNG\n"
  },
  {
    "path": "packages/create-theme/template/_gitignore",
    "content": "node_modules\n.DS_Store\ndist\n*.local\n.vite-inspect\n.remote-assets\n.idea/\ncomponents.d.ts\n"
  },
  {
    "path": "packages/create-theme/template/_npmrc",
    "content": "# for pnpm\nshamefully-hoist=true\n"
  },
  {
    "path": "packages/create-theme/template/components/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/create-theme/template/example.md",
    "content": "---\ntheme: ./\n---\n\n# Slidev Theme Starter\n\nPresentation slides for developers\n\n<div class=\"pt-12\">\n  <span @click=\"$slidev.nav.next\" class=\"px-2 py-1 rounded cursor-pointer\" flex=\"~ justify-center items-center gap-2\" hover=\"bg-white bg-opacity-10\">\n    Press Space for next page <div class=\"i-carbon:arrow-right inline-block\"/>\n  </span>\n</div>\n\n---\n\n# What is Slidev?\n\nSlidev is a slide maker and presentation tool designed for developers. It includes the following features:\n\n- 📝 **Text-based** - focus on your content with Markdown, then style it later\n- 🎨 **Themable** - themes can be shared and reused as npm packages\n- 🧑‍💻 **Developer Friendly** - code highlighting, live coding with autocompletion\n- 🤹 **Interactive** - embed Vue components to enhance your expressions\n- 🎥 **Recording** - built-in recording and camera view\n- 📤 **Portable** - export to PDF, PPTX, PNGs, or even a hostable SPA\n- 🛠 **Hackable** - virtually anything that's possible on a webpage is possible in Slidev\n\n<br>\n<br>\n\nRead more about [Why Slidev?](https://sli.dev/guide/why)\n\n---\n\n# Navigation\n\nHover on the bottom-left corner to see the navigation's controls panel\n\n## Keyboard Shortcuts\n\n|     |     |\n| --- | --- |\n| <kbd>space</kbd> / <kbd>tab</kbd> / <kbd>right</kbd> | next animation or slide |\n| <kbd>left</kbd>  / <kbd>shift</kbd><kbd>space</kbd> | previous animation or slide |\n| <kbd>up</kbd> | previous slide |\n| <kbd>down</kbd> | next slide |\n\n---\nlayout: image-right\nimage: https://cover.sli.dev\n---\n\n# Code\n\nUse code snippets and get the highlighting directly!\n\n```ts\ninterface User {\n  id: number\n  firstName: string\n  lastName: string\n  role: string\n}\n\nfunction updateUser(id: number, update: Partial<User>) {\n  const user = getUser(id)\n  const newUser = { ...user, ...update }\n  saveUser(id, newUser)\n}\n```\n\n---\nlayout: center\nclass: \"text-center\"\n---\n\n# Learn More\n\n[Documentation](https://sli.dev) / [GitHub Repo](https://github.com/slidevjs/slidev)\n"
  },
  {
    "path": "packages/create-theme/template/layouts/cover.vue",
    "content": "<template>\n  <div class=\"slidev-layout cover\">\n    <div class=\"my-auto w-full\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/create-theme/template/layouts/intro.vue",
    "content": "<template>\n  <div class=\"slidev-layout intro\">\n    <div class=\"my-auto\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/create-theme/template/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"keywords\": [\n    \"slidev-theme\",\n    \"slidev\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"slidev build example.md\",\n    \"dev\": \"slidev example.md --open\",\n    \"export\": \"slidev export example.md\",\n    \"screenshot\": \"slidev export example.md --format png\"\n  },\n  \"dependencies\": {\n    \"@slidev/types\": \"^52.14.1\"\n  },\n  \"devDependencies\": {\n    \"@slidev/cli\": \"^52.14.1\"\n  },\n  \"//\": \"Learn more: https://sli.dev/guide/write-theme.html\",\n  \"slidev\": {\n    \"colorSchema\": \"both\",\n    \"defaults\": {\n      \"fonts\": {\n        \"sans\": \"Nunito Sans\",\n        \"mono\": \"Fira Code\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/create-theme/template/setup/shiki.ts",
    "content": "import type { ShikiSetupReturn } from '@slidev/types'\nimport { defineShikiSetup } from '@slidev/types'\n\nexport default defineShikiSetup((): ShikiSetupReturn => {\n  return {\n    themes: {\n      dark: 'vitesse-dark',\n      light: 'vitesse-light',\n    },\n  }\n})\n"
  },
  {
    "path": "packages/create-theme/template/styles/index.ts",
    "content": "// inherit from base layouts, remove it to get full customizations\nimport '@slidev/client/styles/layouts-base.css'\nimport './layout.css'\n"
  },
  {
    "path": "packages/create-theme/template/styles/layout.css",
    "content": ":root {\n  /* default theme color */\n  /* can be overrided by uses `themeConfig` option */\n  --slidev-theme-primary: #5d8392;\n}\n\n.slidev-layout.cover,\n.slidev-layout.intro {\n  @apply h-full grid;\n\n  h1 {\n    @apply text-6xl leading-20;\n  }\n\n  h1 + p {\n    @apply -mt-2 opacity-50 mb-4;\n  }\n\n  p + h2,\n  ul + h2,\n  table + h2 {\n    @apply mt-10;\n  }\n}\n"
  },
  {
    "path": "packages/parser/README.md",
    "content": "# @slidev/parser\n\n[![NPM version](https://img.shields.io/npm/v/@slidev/parser?color=3AB9D4&label=)](https://www.npmjs.com/package/@slidev/parser)\n\nSlides markdown parser for [Slidev](https://sli.dev).\n\nLearn more about the [Syntax](https://sli.dev/guide/syntax.html)\n\nRefer to the TypeScript definitions for API.\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/parser/package.json",
    "content": "{\n  \"name\": \"@slidev/parser\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Markdown parser for Slidev\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"exports\": {\n    \".\": \"./dist/index.mjs\",\n    \"./core\": \"./dist/core.mjs\",\n    \"./fs\": \"./dist/fs.mjs\",\n    \"./utils\": \"./dist/utils.mjs\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.mts\",\n  \"files\": [\n    \"*.d.ts\",\n    \"dist\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"dev\": \"nr build --watch\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@slidev/types\": \"workspace:*\",\n    \"yaml\": \"catalog:prod\"\n  }\n}\n"
  },
  {
    "path": "packages/parser/src/config.ts",
    "content": "import type { DrawingsOptions, FontOptions, ResolvedDrawingsOptions, ResolvedExportOptions, ResolvedFontOptions, SlidevConfig, SlidevThemeMeta } from '@slidev/types'\nimport { toArray, uniq } from '@antfu/utils'\nimport { parseAspectRatio } from './utils'\n\nexport function getDefaultConfig(): SlidevConfig {\n  return {\n    theme: 'default',\n    title: 'Slidev',\n    titleTemplate: '%s - Slidev',\n    addons: [],\n    remoteAssets: false,\n    monaco: true,\n    monacoTypesSource: 'local',\n    monacoTypesAdditionalPackages: [],\n    monacoTypesIgnorePackages: [],\n    monacoRunAdditionalDeps: [],\n    monacoRunUseStrict: true,\n    download: false,\n    export: {} as ResolvedExportOptions,\n    info: false,\n    highlighter: 'shiki',\n    twoslash: true,\n    lineNumbers: false,\n    colorSchema: 'auto',\n    routerMode: 'history',\n    aspectRatio: 16 / 9,\n    canvasWidth: 980,\n    exportFilename: '',\n    selectable: false,\n    themeConfig: {},\n    fonts: {} as ResolvedFontOptions,\n    favicon: 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png',\n    drawings: {} as ResolvedDrawingsOptions,\n    plantUmlServer: 'https://www.plantuml.com/plantuml',\n    codeCopy: true,\n    magicMoveCopy: true,\n    author: '',\n    record: 'dev',\n    css: 'unocss',\n    presenter: true,\n    browserExporter: 'dev',\n    htmlAttrs: {},\n    transition: null,\n    editor: true,\n    contextMenu: null,\n    wakeLock: true,\n    remote: false,\n    mdc: false,\n    comark: false,\n    seoMeta: {},\n    notesAutoRuby: {},\n    duration: '30min',\n    timer: 'stopwatch',\n    magicMoveDuration: 800,\n    preloadImages: true,\n  }\n}\n\nexport function resolveConfig(headmatter: any, themeMeta: SlidevThemeMeta = {}, filepath?: string, verify = false) {\n  const themeHightlighter = ['prism', 'shiki', 'shikiji'].includes(themeMeta.highlighter || '')\n    ? themeMeta.highlighter as 'shiki'\n    : undefined\n  const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')\n    ? themeMeta.colorSchema as 'light' | 'dark'\n    : undefined\n\n  const defaultConfig = getDefaultConfig()\n\n  const config: SlidevConfig = {\n    ...defaultConfig,\n    highlighter: themeHightlighter || defaultConfig.highlighter,\n    colorSchema: themeColorSchema || defaultConfig.colorSchema,\n    ...themeMeta.defaults,\n    ...headmatter.config,\n    ...headmatter,\n    fonts: resolveFonts({\n      ...themeMeta.defaults?.fonts,\n      ...headmatter.config?.fonts,\n      ...headmatter?.fonts,\n    }),\n    drawings: resolveDrawings(headmatter.drawings, filepath),\n    htmlAttrs: {\n      ...themeMeta.defaults?.htmlAttrs,\n      ...headmatter.config?.htmlAttrs,\n      ...headmatter?.htmlAttrs,\n    },\n  }\n\n  // @ts-expect-error compat\n  if (config.highlighter === 'shikiji') {\n    console.warn(`[slidev] \"shikiji\" is merged back to \"shiki\", you can safely change it \"highlighter: shiki\"`)\n    config.highlighter = 'shiki'\n  }\n\n  // @ts-expect-error compat\n  if (config.highlighter === 'prism')\n    throw new Error(`[slidev] \"prism\" support has been dropped. Please use \"highlighter: shiki\" instead`)\n\n  if (config.colorSchema !== 'dark' && config.colorSchema !== 'light')\n    config.colorSchema = 'auto'\n  if (themeColorSchema && config.colorSchema === 'auto')\n    config.colorSchema = themeColorSchema\n\n  config.aspectRatio = parseAspectRatio(config.aspectRatio)\n\n  if (verify)\n    verifyConfig(config, themeMeta)\n\n  return config\n}\n\nexport function verifyConfig(\n  config: SlidevConfig,\n  themeMeta: SlidevThemeMeta = {},\n  warn = (v: string) => console.warn(`[slidev] ${v}`),\n) {\n  const themeHightlighter = themeMeta.highlighter === 'shiki'\n    ? themeMeta.highlighter as 'shiki'\n    : undefined\n  const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')\n    ? themeMeta.colorSchema as 'light' | 'dark'\n    : undefined\n\n  if (themeColorSchema && config.colorSchema !== themeColorSchema)\n    warn(`Color schema \"${config.colorSchema}\" does not supported by the theme`)\n\n  if (themeHightlighter && config.highlighter !== themeHightlighter)\n    warn(`Syntax highlighter \"${config.highlighter}\" does not supported by the theme`)\n\n  if (config.css !== 'unocss') {\n    warn(`Unsupported Atomic CSS engine \"${config.css}\", fallback to UnoCSS`)\n    config.css = 'unocss'\n  }\n}\n\nexport function resolveFonts(fonts: FontOptions = {}): ResolvedFontOptions {\n  const {\n    fallbacks = true,\n    italic = false,\n    provider = 'google',\n  } = fonts\n  let sans = toArray(fonts.sans).flatMap(i => i.split(/,\\s*/g)).map(i => i.trim())\n  let serif = toArray(fonts.serif).flatMap(i => i.split(/,\\s*/g)).map(i => i.trim())\n  let mono = toArray(fonts.mono).flatMap(i => i.split(/,\\s*/g)).map(i => i.trim())\n  const weights = toArray(fonts.weights || '200,400,600').flatMap(i => i.toString().split(/,\\s*/g)).map(i => i.trim())\n  const custom = toArray(fonts.custom).flatMap(i => i.split(/,\\s*/g)).map(i => i.trim())\n\n  const local = toArray(fonts.local).flatMap(i => i.split(/,\\s*/g)).map(i => i.trim())\n  const webfonts = fonts.webfonts\n    ? fonts.webfonts\n    : fallbacks\n      ? uniq([...sans, ...serif, ...mono, ...custom]).filter(i => !local.includes(i))\n      : []\n\n  function toQuoted(font: string) {\n    if (/^(['\"]).*\\1$/.test(font))\n      return font\n    return `\"${font}\"`\n  }\n\n  if (fallbacks) {\n    sans = uniq([\n      ...sans.map(toQuoted),\n      'ui-sans-serif',\n      'system-ui',\n      '-apple-system',\n      'BlinkMacSystemFont',\n      '\"Segoe UI\"',\n      'Roboto',\n      '\"Helvetica Neue\"',\n      'Arial',\n      '\"Noto Sans\"',\n      'sans-serif',\n      '\"Apple Color Emoji\"',\n      '\"Segoe UI Emoji\"',\n      '\"Segoe UI Symbol\"',\n      '\"Noto Color Emoji\"',\n    ])\n    serif = uniq([\n      ...serif.map(toQuoted),\n      'ui-serif',\n      'Georgia',\n      'Cambria',\n      '\"Times New Roman\"',\n      'Times',\n      'serif',\n    ])\n    mono = uniq([\n      ...mono.map(toQuoted),\n      'ui-monospace',\n      'SFMono-Regular',\n      'Menlo',\n      'Monaco',\n      'Consolas',\n      '\"Liberation Mono\"',\n      '\"Courier New\"',\n      'monospace',\n    ])\n  }\n\n  return {\n    sans,\n    serif,\n    mono,\n    webfonts,\n    provider,\n    local,\n    italic,\n    weights,\n  }\n}\n\nfunction resolveDrawings(options: DrawingsOptions = {}, filepath?: string): ResolvedDrawingsOptions {\n  const {\n    enabled = true,\n    persist = false,\n    presenterOnly = false,\n    syncAll = true,\n  } = options\n\n  const persistPath = typeof persist === 'string'\n    ? persist\n    : persist\n      ? `.slidev/drawings${filepath ? `/${filepath.match(/([^\\\\/]+?)(\\.\\w+)?$/)?.[1]}` : ''}`\n      : false\n\n  return {\n    enabled,\n    persist: persistPath,\n    presenterOnly,\n    syncAll,\n  }\n}\n"
  },
  {
    "path": "packages/parser/src/core.ts",
    "content": "import type { FrontmatterStyle, SlidevDetectedFeatures, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'\nimport { ensurePrefix } from '@antfu/utils'\nimport YAML from 'yaml'\n\nexport interface SlidevParserOptions {\n  noParseYAML?: boolean\n  preserveCR?: boolean\n}\n\nexport function stringify(data: SlidevMarkdown) {\n  return `${data.slides.map(stringifySlide).join('\\n').trim()}\\n`\n}\n\nexport function stringifySlide(data: SourceSlideInfo, idx = 0) {\n  return (data.raw.startsWith('---') || idx === 0)\n    ? data.raw\n    : `---\\n${ensurePrefix('\\n', data.raw)}`\n}\n\nexport function prettifySlide(data: SourceSlideInfo) {\n  const trimed = data.content.trim()\n  data.content = trimed ? `\\n${data.content.trim()}\\n` : ''\n  data.raw = data.frontmatterDoc?.contents\n    ? data.frontmatterStyle === 'yaml'\n      ? `\\`\\`\\`yaml\\n${data.frontmatterDoc.toString().trim()}\\n\\`\\`\\`\\n${data.content}`\n      : `---\\n${data.frontmatterDoc.toString().trim()}\\n---\\n${data.content}`\n    : data.content\n  if (data.note)\n    data.raw += `\\n<!--\\n${data.note.trim()}\\n-->\\n`\n  return data\n}\n\nexport function prettify(data: SlidevMarkdown) {\n  data.slides.forEach(prettifySlide)\n  return data\n}\n\nfunction matter(code: string, options: SlidevParserOptions) {\n  let type: FrontmatterStyle | undefined\n  let raw: string | undefined\n\n  let content = code\n    .replace(/^---.*\\r?\\n([\\s\\S]*?)---/, (_, f) => {\n      type = 'frontmatter'\n      raw = f\n      return ''\n    })\n\n  if (type !== 'frontmatter') {\n    content = content\n      .replace(/^\\s*```ya?ml([\\s\\S]*?)```/, (_, f) => {\n        type = 'yaml'\n        raw = f\n        return ''\n      })\n  }\n\n  const doc = raw && !options.noParseYAML ? YAML.parseDocument(raw) : undefined\n\n  return {\n    type,\n    raw,\n    doc,\n    data: doc?.toJSON(),\n    content,\n  }\n}\n\nconst IMAGE_EXTENSIONS = /\\.(?:png|jpe?g|gif|svg|webp|avif|ico|bmp|tiff?)$/i\n\n/**\n * Extract image URLs from slide content and frontmatter.\n * Strips code blocks first to avoid false positives.\n */\nexport function extractImagesUsage(content: string, frontmatter: Record<string, any>): string[] {\n  const images = new Set<string>()\n\n  // Collect from frontmatter keys\n  for (const key of ['image', 'backgroundImage', 'background']) {\n    const val = frontmatter[key]\n    if (typeof val === 'string' && val && !val.startsWith('data:')) {\n      // For `background`, only include if it looks like an image URL\n      if (key === 'background') {\n        if (IMAGE_EXTENSIONS.test(val) || val.startsWith('/') || val.startsWith('http'))\n          images.add(val)\n      }\n      else {\n        images.add(val)\n      }\n    }\n  }\n\n  // Strip code blocks to avoid false positives\n  const stripped = content.replace(/^```[\\s\\S]+?^```/gm, '')\n\n  // Markdown images: ![alt](url)\n  for (const [, url] of stripped.matchAll(/!\\[[^\\]]*\\]\\(([^)]+)\\)/g)) {\n    if (url && !url.startsWith('data:'))\n      images.add(url.trim())\n  }\n\n  // Vue component props: src=\"url\", image=\"url\"\n  for (const [, url] of stripped.matchAll(/\\b(?:src|image)=[\"']([^\"']+)[\"']/g)) {\n    if (url && !url.startsWith('data:') && !url.includes('{{') && IMAGE_EXTENSIONS.test(url))\n      images.add(url.trim())\n  }\n\n  // Vue bound props: :src=\"'/path/to/img.png'\"\n  for (const [, url] of stripped.matchAll(/:(?:src|image)=[\"']'([^']+)'[\"']/g)) {\n    if (url && !url.startsWith('data:') && IMAGE_EXTENSIONS.test(url))\n      images.add(url.trim())\n  }\n\n  // CSS url() with image extension filter\n  for (const [, url] of stripped.matchAll(/url\\([\"']?([^\"')]+)[\"']?\\)/g)) {\n    if (url && !url.startsWith('data:') && IMAGE_EXTENSIONS.test(url))\n      images.add(url.trim())\n  }\n\n  return Array.from(images)\n}\n\nexport function detectFeatures(code: string): SlidevDetectedFeatures {\n  return {\n    katex: !!code.match(/\\$.*?\\$/) || !!code.match(/\\$\\$/),\n    monaco: code.match(/\\{monaco.*\\}/) ? scanMonacoReferencedMods(code) : false,\n    tweet: !!code.match(/<Tweet\\b/),\n    mermaid: !!code.match(/^```mermaid/m),\n  }\n}\n\nexport function parseSlide(raw: string, options: SlidevParserOptions = {}): Omit<SourceSlideInfo, 'filepath' | 'index' | 'start' | 'contentStart' | 'end'> {\n  const matterResult = matter(raw, options)\n  let note: string | undefined\n  const frontmatter = matterResult.data || {}\n  let content = matterResult.content.trim()\n  const revision = hash(raw.trim())\n\n  const comments = Array.from(content.matchAll(/<!--([\\s\\S]*?)-->/g))\n  if (comments.length) {\n    const last = comments[comments.length - 1]\n    if (last.index !== undefined && last.index + last[0].length >= content.length) {\n      note = last[1].trim()\n      content = content.slice(0, last.index).trim()\n    }\n  }\n\n  let title\n  let level\n  if (frontmatter.title || frontmatter.name) {\n    title = frontmatter.title || frontmatter.name\n  }\n  else {\n    const match = content.match(/^(#+) (.*)$/m)\n    title = match?.[2]?.trim()\n    level = match?.[1]?.length\n  }\n  if (frontmatter.level)\n    level = frontmatter.level || 1\n\n  const images = extractImagesUsage(content, frontmatter)\n\n  return {\n    raw,\n    title,\n    level,\n    revision,\n    content,\n    contentRaw: content,\n    frontmatter,\n    frontmatterStyle: matterResult.type,\n    frontmatterDoc: matterResult.doc,\n    frontmatterRaw: matterResult.raw,\n    note,\n    images,\n  }\n}\n\nexport async function parse(\n  markdown: string,\n  filepath: string,\n  extensions?: SlidevPreparserExtension[],\n  options: SlidevParserOptions = {},\n): Promise<SlidevMarkdown> {\n  const lines = markdown.split(options.preserveCR ? '\\n' : /\\r?\\n/g)\n  const slides: SourceSlideInfo[] = []\n\n  let start = 0\n  let contentStart = 0\n\n  async function slice(end: number) {\n    if (start === end)\n      return\n    const raw = lines.slice(start, end).join('\\n')\n    const slide: SourceSlideInfo = {\n      ...parseSlide(raw, options),\n      filepath,\n      index: slides.length,\n      start,\n      contentStart,\n      end,\n    }\n    if (extensions) {\n      for (const e of extensions) {\n        if (e.transformSlide) {\n          const newContent = await e.transformSlide(slide.content, slide.frontmatter)\n          if (newContent !== undefined)\n            slide.content = newContent\n          if (typeof slide.frontmatter.title === 'string') {\n            slide.title = slide.frontmatter.title\n          }\n          if (typeof slide.frontmatter.level === 'number') {\n            slide.level = slide.frontmatter.level\n          }\n        }\n\n        if (e.transformNote) {\n          const newNote = await e.transformNote(slide.note, slide.frontmatter)\n          if (newNote !== undefined)\n            slide.note = newNote\n        }\n      }\n    }\n    slides.push(slide)\n    start = end + 1\n    contentStart = end + 1\n  }\n\n  if (extensions) {\n    for (const e of extensions) {\n      if (e.transformRawLines)\n        await e.transformRawLines(lines)\n    }\n  }\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trimEnd()\n    if (line.startsWith('---')) {\n      await slice(i)\n\n      const next = lines[i + 1]\n      // found frontmatter, skip next dash\n      if (line[3] !== '-' && next?.trim()) {\n        start = i\n        for (i += 1; i < lines.length; i++) {\n          if (lines[i].trimEnd() === '---')\n            break\n        }\n        contentStart = i + 1\n      }\n    }\n    // skip code block\n    else if (line.trimStart().startsWith('```')) {\n      const codeBlockLevel = line.match(/^\\s*`+/)![0]\n      let j = i + 1\n      for (; j < lines.length; j++) {\n        if (lines[j].startsWith(codeBlockLevel))\n          break\n      }\n      // Update i only when code block ends\n      if (j !== lines.length)\n        i = j\n    }\n  }\n\n  if (start <= lines.length - 1)\n    await slice(lines.length)\n\n  return {\n    filepath,\n    raw: markdown,\n    slides,\n  }\n}\n\nexport function parseSync(\n  markdown: string,\n  filepath: string,\n  options: SlidevParserOptions = {},\n): SlidevMarkdown {\n  const lines = markdown.split(options.preserveCR ? '\\n' : /\\r?\\n/g)\n  const slides: SourceSlideInfo[] = []\n\n  let start = 0\n  let contentStart = 0\n\n  function slice(end: number) {\n    if (start === end)\n      return\n    const raw = lines.slice(start, end).join('\\n')\n    const slide: SourceSlideInfo = {\n      ...parseSlide(raw, options),\n      filepath,\n      index: slides.length,\n      start,\n      contentStart,\n      end,\n    }\n    slides.push(slide)\n    start = end + 1\n    contentStart = end + 1\n  }\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trimEnd()\n    if (line.startsWith('---')) {\n      slice(i)\n\n      const next = lines[i + 1]\n      // found frontmatter, skip next dash\n      if (line[3] !== '-' && next?.trim()) {\n        start = i\n        for (i += 1; i < lines.length; i++) {\n          if (lines[i].trimEnd() === '---')\n            break\n        }\n        contentStart = i + 1\n      }\n    }\n    // skip code block\n    else if (line.trimStart().startsWith('```')) {\n      const codeBlockLevel = line.match(/^\\s*`+/)![0]\n      let j = i + 1\n      for (; j < lines.length; j++) {\n        if (lines[j].startsWith(codeBlockLevel))\n          break\n      }\n      // Update i only when code block ends\n      if (j !== lines.length)\n        i = j\n    }\n  }\n\n  if (start <= lines.length - 1)\n    slice(lines.length)\n\n  return {\n    filepath,\n    raw: markdown,\n    slides,\n  }\n}\n\nfunction scanMonacoReferencedMods(md: string) {\n  const types = new Set<string>()\n  const deps = new Set<string>()\n  md.replace(\n    /^```(\\w+)\\s*\\{monaco([^}]*)\\}\\s*(\\S[\\s\\S]*?)^```/gm,\n    (full, lang = 'ts', kind: string, code: string) => {\n      lang = lang.trim()\n      const isDep = kind === '-run'\n      if (['js', 'javascript', 'ts', 'typescript'].includes(lang)) {\n        for (const [, , specifier] of code.matchAll(/\\s+from\\s+([\"'])([/.\\w@-]+)\\1/g)) {\n          if (specifier) {\n            if (!'./'.includes(specifier))\n              types.add(specifier) // All local TS files are loaded by globbing\n            if (isDep)\n              deps.add(specifier)\n          }\n        }\n      }\n      return ''\n    },\n  )\n  return {\n    types: Array.from(types),\n    deps: Array.from(deps),\n  }\n}\n\nfunction hash(str: string) {\n  let hash = 0\n  for (let i = 0; i < str.length; i++) {\n    hash = ((hash << 5) - hash) + str.charCodeAt(i)\n    hash |= 0\n  }\n  return hash.toString(36).slice(0, 12)\n}\n\nexport * from './config'\nexport * from './utils'\n"
  },
  {
    "path": "packages/parser/src/fs.ts",
    "content": "import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport YAML from 'yaml'\nimport { detectFeatures, parse, parseRangeString, stringify } from './core'\n\nexport * from './core'\n\nlet preparserExtensionLoader: PreparserExtensionLoader | null = null\n\nexport function injectPreparserExtensionLoader(fn: PreparserExtensionLoader) {\n  preparserExtensionLoader = fn\n}\n\n/**\n * Slidev data without config and themeMeta,\n * because config and themeMeta depends on the theme to be loaded.\n */\nexport type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>\n\nexport async function load(\n  userRoot: string,\n  filepath: string,\n  sources: Record<string, string> | ((path: string) => Promise<string>) = {},\n  mode?: string,\n): Promise<LoadedSlidevData> {\n  const loadSource = typeof sources === 'function' ? sources : async (path: string) => sources[path] ?? readFile(path, 'utf-8')\n\n  const markdown = await loadSource(filepath)\n\n  let extensions: SlidevPreparserExtension[] | undefined\n  if (preparserExtensionLoader) {\n    // #703\n    // identify the headmatter, to be able to load preparser extensions\n    // (strict parsing based on the parsing code)\n    const lines = markdown.split(/\\r?\\n/g)\n    let hm = ''\n    if (lines[0].match(/^---([^-].*)?$/) && !lines[1]?.match(/^\\s*$/)) {\n      let hEnd = 1\n      while (hEnd < lines.length && !lines[hEnd].trimEnd().match(/^---$/))\n        hEnd++\n      hm = lines.slice(1, hEnd).join('\\n')\n    }\n    const o = YAML.parse(hm) as Record<string, unknown> ?? {}\n    extensions = await preparserExtensionLoader(o, filepath, mode)\n  }\n\n  const markdownFiles: Record<string, SlidevMarkdown> = {}\n  const watchFiles: Record<string, Set<number>> = {}\n  const slides: SlideInfo[] = []\n\n  async function loadMarkdown(path: string, range?: string, frontmatterOverride?: Record<string, unknown>, importers?: SourceSlideInfo[]) {\n    let md = markdownFiles[path]\n    if (!md) {\n      const raw = await loadSource(path)\n      md = await parse(raw, path, extensions)\n      markdownFiles[path] = md\n      watchFiles[path] = new Set()\n    }\n\n    const directImporter = importers?.at(-1)\n    for (const index of parseRangeString(md.slides.length, range)) {\n      const subSlide = md.slides[index - 1]\n      try {\n        await loadSlide(md, subSlide, frontmatterOverride, importers)\n      }\n      catch (e) {\n        md.errors ??= []\n        md.errors.push({\n          row: subSlide.start,\n          message: `Error when loading slide: ${e}`,\n        })\n        continue\n      }\n      if (directImporter)\n        (directImporter.imports ??= []).push(subSlide)\n    }\n\n    return md\n  }\n\n  async function loadSlide(md: SlidevMarkdown, slide: SourceSlideInfo, frontmatterOverride?: Record<string, unknown>, importChain?: SourceSlideInfo[]) {\n    if (slide.frontmatter.disabled || slide.frontmatter.hide)\n      return\n    if (slide.frontmatter.src) {\n      const [rawPath, rangeRaw] = slide.frontmatter.src.split('#')\n      const path = slash(\n        rawPath.startsWith('/')\n          ? resolve(userRoot, rawPath.substring(1))\n          : resolve(dirname(slide.filepath), rawPath),\n      )\n\n      frontmatterOverride = {\n        ...slide.frontmatter,\n        ...frontmatterOverride,\n      }\n      delete frontmatterOverride.src\n\n      if (!existsSync(path)) {\n        md.errors ??= []\n        md.errors.push({\n          row: slide.start,\n          message: `Imported markdown file not found: ${path}`,\n        })\n      }\n      else {\n        await loadMarkdown(path, rangeRaw, frontmatterOverride, importChain ? [...importChain, slide] : [slide])\n      }\n    }\n    else {\n      slides.push({\n        frontmatter: { ...slide.frontmatter, ...frontmatterOverride },\n        content: slide.content,\n        revision: slide.revision,\n        frontmatterRaw: slide.frontmatterRaw,\n        note: slide.note,\n        title: slide.title,\n        level: slide.level,\n        index: slides.length,\n        importChain,\n        source: slide,\n      })\n    }\n  }\n\n  const entry = await loadMarkdown(slash(filepath))\n\n  const headmatter = { ...entry.slides[0]?.frontmatter }\n  if (slides[0]?.title)\n    headmatter.title ??= slides[0].title\n\n  return {\n    slides,\n    entry,\n    headmatter,\n    features: detectFeatures(slides.map(s => s.source.raw).join('')),\n    markdownFiles,\n    watchFiles,\n  }\n}\n\nexport async function save(markdown: SlidevMarkdown) {\n  const fileContent = stringify(markdown)\n  await writeFile(markdown.filepath, fileContent, 'utf-8')\n  return fileContent\n}\n"
  },
  {
    "path": "packages/parser/src/index.ts",
    "content": "export * from './core'\n"
  },
  {
    "path": "packages/parser/src/timesplit/index.ts",
    "content": "export * from './timesplit'\nexport * from './timestring'\n"
  },
  {
    "path": "packages/parser/src/timesplit/timesplit.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { parseTimesplits } from './timesplit'\n\ndescribe('parseTimesplits', () => {\n  it('should parse timestamp into seconds', () => {\n    expect(parseTimesplits([\n      { no: 1, timesplit: '+10s' },\n      { no: 5, timesplit: '10:10' },\n      { no: 3, timesplit: '15m' },\n      { no: 7, timesplit: '1h10m30s' },\n    ])).toMatchInlineSnapshot(`\n      [\n        {\n          \"noEnd\": 1,\n          \"noStart\": 0,\n          \"timestampEnd\": 10,\n          \"timestampStart\": 0,\n          \"title\": \"[start]\",\n        },\n        {\n          \"noEnd\": 5,\n          \"noStart\": 1,\n          \"timestampEnd\": 610,\n          \"timestampStart\": 10,\n        },\n        {\n          \"noEnd\": 3,\n          \"noStart\": 5,\n          \"timestampEnd\": 900,\n          \"timestampStart\": 610,\n        },\n        {\n          \"noEnd\": 7,\n          \"noStart\": 3,\n          \"timestampEnd\": 4230,\n          \"timestampStart\": 900,\n        },\n        {\n          \"noEnd\": 7,\n          \"noStart\": 7,\n          \"timestampEnd\": 4230,\n          \"timestampStart\": 4230,\n        },\n      ]\n    `)\n  })\n})\n"
  },
  {
    "path": "packages/parser/src/timesplit/timesplit.ts",
    "content": "import { parseTimeString } from './timestring'\n\nexport interface TimesplitInput {\n  no: number\n  timesplit: string\n  title?: string\n}\n\nexport interface TimesplitOutput {\n  timestampStart: number\n  timestampEnd: number\n  noStart: number\n  noEnd: number\n  title?: string\n}\n\nexport function parseTimesplits(inputs: TimesplitInput[]): TimesplitOutput[] {\n  let ts = 0\n  const outputs: TimesplitOutput[] = []\n  let current: TimesplitOutput = {\n    timestampStart: ts,\n    timestampEnd: ts,\n    noStart: 0,\n    noEnd: 0,\n    title: '[start]',\n  }\n  outputs.push(current)\n  for (const input of inputs) {\n    const time = parseTimeString(input.timesplit)\n    const end = time.relative\n      ? ts + time.seconds\n      : time.seconds\n    if (end < ts) {\n      throw new Error(`Timesplit end ${end} is before start ${ts}`)\n    }\n    current.timestampEnd = end\n    current.noEnd = input.no\n    if (input.title) {\n      current.title = input.title\n    }\n    ts = end\n    current = {\n      timestampStart: end,\n      timestampEnd: end,\n      noStart: input.no,\n      noEnd: input.no,\n    }\n    outputs.push(current)\n  }\n  return outputs\n}\n"
  },
  {
    "path": "packages/parser/src/timesplit/timestring.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { parseTimeString } from './timestring'\n\ndescribe('parseTimeString', () => {\n  it('should parse timestamp into seconds', () => {\n    expect(parseTimeString('10:50.1')).toEqual({ seconds: 650.1, relative: false })\n    expect(parseTimeString('10s')).toEqual({ seconds: 10, relative: false })\n    expect(parseTimeString('5m')).toEqual({ seconds: 300, relative: false })\n    expect(parseTimeString('3min')).toEqual({ seconds: 180, relative: false })\n    expect(parseTimeString('3mins 5secs')).toEqual({ seconds: 185, relative: false })\n    expect(parseTimeString('10.5m3s')).toEqual({ seconds: 633, relative: false })\n    expect(parseTimeString('+10s')).toEqual({ seconds: 10, relative: true })\n    expect(parseTimeString('1h10m30s')).toEqual({ seconds: 4230, relative: false })\n    expect(parseTimeString('1h4s')).toEqual({ seconds: 3604, relative: false })\n    expect(parseTimeString('1:1:1')).toEqual({ seconds: 3661, relative: false })\n    expect(parseTimeString('0.5years')).toEqual({ seconds: 15778476, relative: false })\n  })\n\n  it('should throw an error for invalid timestamp', () => {\n    expect(() => parseTimeString('10x')).toThrow('Invalid timestamp unit: x')\n    expect(() => parseTimeString('10h:10m:10s')).toThrow('Invalid timestamp format')\n    expect(() => parseTimeString('hello 1s world')).toThrow('Unknown timestamp remaining: hello  world')\n  })\n})\n"
  },
  {
    "path": "packages/parser/src/timesplit/timestring.ts",
    "content": "/**\n * Parse timestamp into seconds\n *\n * Accepts:\n * - 10:50.1\n * - 10s\n * - 5m\n * - 3min\n * - 3mins 5secs\n * - 10.5m3s\n * - +10s\n * - 1h10m30s\n * - 1h4s\n * - 1:1:1\n */\nexport function parseTimeString(timestamp: string | number): {\n  seconds: number\n  relative: boolean\n} {\n  if (typeof timestamp === 'number') {\n    return {\n      seconds: timestamp,\n      relative: false,\n    }\n  }\n\n  const relative = timestamp.startsWith('+')\n  if (relative) {\n    timestamp = timestamp.slice(1)\n  }\n  let seconds = 0\n  if (timestamp.includes(':')) {\n    const parts = timestamp.split(':').map(Number)\n    let h = 0\n    let m = 0\n    let s = 0\n    if (parts.length === 3) {\n      h = parts[0]\n      m = parts[1]\n      s = parts[2]\n    }\n    else if (parts.length === 2) {\n      m = parts[0]\n      s = parts[1]\n    }\n    else if (parts.length === 1) {\n      s = parts[0]\n    }\n    else {\n      throw new TypeError('Invalid timestamp format')\n    }\n    if (Number.isNaN(h) || Number.isNaN(m) || Number.isNaN(s)) {\n      throw new TypeError('Invalid timestamp format')\n    }\n    seconds = (h || 0) * 3600 + (m || 0) * 60 + (s || 0)\n  }\n  else if (!timestamp.match(/[a-z]/i)) {\n    seconds = Number(timestamp)\n  }\n  else {\n    const unitMap: Record<string, number> = {\n      s: 1,\n      sec: 1,\n      secs: 1,\n      m: 60,\n      min: 60,\n      mins: 60,\n      h: 3600,\n      hr: 3600,\n      hrs: 3600,\n      hour: 3600,\n      hours: 3600,\n      day: 86400,\n      days: 86400,\n      week: 604800,\n      weeks: 604800,\n      month: 2629746,\n      months: 2629746,\n      year: 31556952,\n      years: 31556952,\n    }\n    const regex = /([\\d.]+)([a-z]+)/gi\n    const matches = timestamp.matchAll(regex)\n    if (matches) {\n      for (const match of matches) {\n        const value = Number(match[1])\n        if (Number.isNaN(value)) {\n          throw new TypeError(`Invalid timestamp value: ${match[1]}`)\n        }\n        const unit = match[2].toLowerCase()\n        if (!(unit in unitMap)) {\n          throw new TypeError(`Invalid timestamp unit: ${unit}`)\n        }\n        seconds += value * unitMap[unit]\n      }\n    }\n    const remaining = timestamp.replace(regex, '').trim()\n    if (remaining) {\n      throw new TypeError(`Unknown timestamp remaining: ${remaining}`)\n    }\n  }\n\n  return {\n    seconds,\n    relative,\n  }\n}\n"
  },
  {
    "path": "packages/parser/src/utils.ts",
    "content": "import { isNumber, range, uniq } from '@antfu/utils'\n\nexport * from './timesplit'\n\n/**\n * 1,3-5,8 => [1, 3, 4, 5, 8]\n */\nexport function parseRangeString(total: number, rangeStr?: string) {\n  if (!rangeStr || rangeStr === 'all' || rangeStr === '*')\n    return range(1, total + 1)\n\n  if (rangeStr === 'none')\n    return []\n\n  const indexes: number[] = []\n  for (const part of rangeStr.split(/[,;]/g)) {\n    if (!part.includes('-')) {\n      indexes.push(+part)\n    }\n    else {\n      const [start, end] = part.split('-', 2)\n      indexes.push(\n        ...range(+start, !end ? (total + 1) : (+end + 1)),\n      )\n    }\n  }\n\n  return uniq(indexes).filter(i => i <= total).sort((a, b) => a - b)\n}\n\n/**\n * Accepts `16/9` `1:1` `3x4`\n */\nexport function parseAspectRatio(str: string | number) {\n  if (isNumber(str))\n    return str\n  if (!Number.isNaN(+str))\n    return +str\n  const [wStr = '', hStr = ''] = str.split(/[:/x|]/)\n  const w = Number.parseFloat(wStr.trim())\n  const h = Number.parseFloat(hStr.trim())\n\n  if (Number.isNaN(w) || Number.isNaN(h) || h === 0)\n    throw new Error(`Invalid aspect ratio \"${str}\"`)\n\n  return w / h\n}\n"
  },
  {
    "path": "packages/parser/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown'\nimport baseConfig from '../../tsdown.config.ts'\n\nexport default defineConfig({\n  ...baseConfig,\n  entry: {\n    index: 'src/index.ts',\n    core: 'src/core.ts',\n    fs: 'src/fs.ts',\n    utils: 'src/utils.ts',\n  },\n})\n"
  },
  {
    "path": "packages/slidev/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-2021 Anthony Fu\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": "packages/slidev/bin/slidev.mjs",
    "content": "#!/usr/bin/env node\n'use strict'\nimport('../dist/cli.mjs')\n"
  },
  {
    "path": "packages/slidev/node/cli.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevConfig, SlidevData } from '@slidev/types'\nimport type { LogLevel, ViteDevServer } from 'vite'\nimport type { Argv } from 'yargs'\nimport { exec } from 'node:child_process'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport * as readline from 'node:readline'\nimport { verifyConfig } from '@slidev/parser'\nimport { blue, bold, cyan, cyanBright, dim, gray, green, underline, yellow } from 'ansis'\nimport equal from 'fast-deep-equal'\nimport { getPort } from 'get-port-please'\nimport openBrowser from 'open'\nimport yargs from 'yargs'\nimport { version } from '../package.json'\nimport { createServer } from './commands/serve'\nimport { getThemeMeta, resolveTheme } from './integrations/themes'\nimport { resolveOptions } from './options'\nimport { parser } from './parser'\nimport { getRoots, isInstalledGlobally, resolveEntry } from './resolver'\nimport setupPreparser from './setups/preparser'\nimport { updateFrontmatterPatch } from './utils'\n\nconst CONFIG_RESTART_FIELDS: (keyof SlidevConfig)[] = [\n  'monaco',\n  'routerMode',\n  'fonts',\n  'css',\n  'mdc',\n  'editor',\n  'theme',\n  'seoMeta',\n]\n\nconst FILES_CHANGE_RESTART = [\n  'setup/shiki.ts',\n  'setup/katex.ts',\n  'setup/preparser.ts',\n  'setup/transformers.ts',\n  'setup/unocss.ts',\n  'setup/vite-plugins.ts',\n  'uno.config.ts',\n  'unocss.config.ts',\n  'vite.config.{js,ts,mjs,mts}',\n]\n\nsetupPreparser()\n\nconst cli = yargs(process.argv.slice(2))\n  .scriptName('slidev')\n  .usage('$0 [args]')\n  .version(version)\n  .strict()\n  .showHelpOnFail(false)\n  .alias('h', 'help')\n  .alias('v', 'version')\n\ncli.command(\n  '* [entry]',\n  'Start a local server for Slidev',\n  args => commonOptions(args)\n    .option('port', {\n      alias: 'p',\n      type: 'number',\n      describe: 'port',\n    })\n    .option('open', {\n      alias: 'o',\n      default: false,\n      type: 'boolean',\n      describe: 'open in browser',\n    })\n    .option('remote', {\n      type: 'string',\n      describe: 'listen public host and enable remote control',\n    })\n    .option('tunnel', {\n      default: false,\n      type: 'boolean',\n      describe: 'open a Cloudflare Quick Tunnel to make Slidev available on the internet',\n    })\n    .option('log', {\n      default: 'warn',\n      type: 'string',\n      choices: ['error', 'warn', 'info', 'silent'],\n      describe: 'log level',\n    })\n    .option('inspect', {\n      default: false,\n      type: 'boolean',\n      describe: 'enable the inspect plugin for debugging',\n    })\n    .option('force', {\n      alias: 'f',\n      default: false,\n      type: 'boolean',\n      describe: 'force the optimizer to ignore the cache and re-bundle',\n    })\n    .option('bind', {\n      type: 'string',\n      default: '0.0.0.0',\n      describe: 'specify which IP addresses the server should listen on in remote mode',\n    })\n    .option('base', {\n      type: 'string',\n      describe: 'base URL. Example: /demo/',\n      default: '/',\n    })\n    .strict()\n    .help(),\n  async ({ entry, theme, port: userPort, open, log, remote, tunnel, force, inspect, bind, base }) => {\n    let server: ViteDevServer | undefined\n    let port = 3030\n\n    let lastRemoteUrl: string | undefined\n\n    let restartTimer: ReturnType<typeof setTimeout> | undefined\n    async function restartServer() {\n      await server?.close()\n      server = undefined\n      clearTimeout(restartTimer)\n      restartTimer = setTimeout(() => {\n        console.log(yellow('\\n  restarting...\\n'))\n        initServer()\n      }, 500)\n    }\n\n    async function initServer() {\n      const options = await resolveOptions({ entry, remote, theme, inspect, base }, 'dev')\n      const host = remote !== undefined ? bind : 'localhost'\n      port = userPort || await getPort({\n        port: 3030,\n        random: false,\n        portRange: [3030, 4000],\n        host,\n      })\n      server = (await createServer(\n        options,\n        {\n          server: {\n            port,\n            strictPort: true,\n            open,\n            host,\n            // @ts-expect-error Vite <= 4\n            force,\n          },\n          optimizeDeps: {\n            // Vite 5\n            force,\n          },\n          logLevel: log as LogLevel,\n          base,\n        },\n        {\n          async loadData(loadedSource) {\n            const { data: oldData, entry } = options\n            const loaded = await parser.load(options.userRoot, entry, loadedSource, 'dev')\n\n            const themeRaw = theme || loaded.headmatter.theme as string || 'default'\n            if (options.themeRaw !== themeRaw) {\n              console.log(yellow('\\n  restarting on theme change\\n'))\n              restartServer()\n              return false\n            }\n            // Because themeRaw is not changed, we don't resolve it again\n            const themeMeta = options.themeRoots[0] ? await getThemeMeta(themeRaw, options.themeRoots[0]) : undefined\n            const newData: SlidevData = {\n              ...loaded,\n              themeMeta,\n              config: parser.resolveConfig(loaded.headmatter, themeMeta, entry),\n            }\n\n            if (CONFIG_RESTART_FIELDS.some(i => !equal(newData.config[i], oldData.config[i]))) {\n              console.log(yellow('\\n  restarting on config change\\n'))\n              restartServer()\n              return false\n            }\n\n            if ((newData.features.katex && !oldData.features.katex) || (newData.features.monaco && !oldData.features.monaco)) {\n              console.log(yellow('\\n  restarting on feature change\\n'))\n              restartServer()\n              return false\n            }\n\n            return newData\n          },\n        },\n      ))\n\n      await server.listen()\n\n      let tunnelUrl = ''\n      if (tunnel) {\n        if (remote != null)\n          tunnelUrl = await openTunnel(port)\n        else\n          console.log(yellow('\\n  --remote is required for tunneling, Cloudflare Quick Tunnel is not enabled.\\n'))\n      }\n\n      let publicIp: string | undefined\n      if (remote)\n        publicIp = await import('public-ip').then(r => r.publicIpv4())\n\n      lastRemoteUrl = printInfo(options, port, base, remote, tunnelUrl, publicIp)\n\n      return options\n    }\n\n    async function openTunnel(port: number) {\n      const { startTunnel } = await import('untun')\n      const tunnel = await startTunnel({\n        port,\n        acceptCloudflareNotice: true,\n      })\n      return await tunnel?.getURL() ?? ''\n    }\n\n    const SHORTCUTS = [\n      {\n        name: 'r',\n        fullname: 'restart',\n        action() {\n          restartServer()\n        },\n      },\n      {\n        name: 'o',\n        fullname: 'open',\n        action() {\n          openBrowser(`http://localhost:${port}${base}`)\n        },\n      },\n      {\n        name: 'e',\n        fullname: 'edit',\n        action() {\n          exec(`code \"${entry}\"`)\n        },\n      },\n      {\n        name: 'q',\n        fullname: 'quit',\n        action() {\n          try {\n            server?.close()\n          }\n          finally {\n            process.exit()\n          }\n        },\n      },\n      {\n        name: 'c',\n        fullname: 'qrcode',\n        async action() {\n          if (!lastRemoteUrl)\n            return\n          await import('uqr')\n            .then(async (r) => {\n              const code = r.renderUnicodeCompact(lastRemoteUrl!)\n              console.log(`\\n${dim('  QR Code for remote control: ')}\\n  ${blue(lastRemoteUrl!)}\\n`)\n              console.log(code.split('\\n').map(i => `  ${i}`).join('\\n'))\n              const publicIp = await import('public-ip').then(r => r.publicIpv4())\n              if (publicIp)\n                console.log(`\\n${dim(' Public IP: ')}  ${blue(publicIp)}\\n`)\n            })\n        },\n      },\n    ]\n\n    function bindShortcut() {\n      process.stdin.resume()\n      process.stdin.setEncoding('utf8')\n      readline.emitKeypressEvents(process.stdin)\n      if (process.stdin.isTTY)\n        process.stdin.setRawMode(true)\n\n      const onKeyPress = (str: string, key: { ctrl: boolean, name: string }) => {\n        if (key.ctrl && key.name === 'c') {\n          process.exit()\n        }\n        else {\n          const [sh] = SHORTCUTS.filter(item => item.name === str)\n          if (sh) {\n            try {\n              sh.action()\n            }\n            catch (err) {\n              console.error(`Failed to execute shortcut ${sh.fullname}`, err)\n            }\n          }\n        }\n      }\n\n      process.stdin.on('keypress', onKeyPress)\n    }\n\n    const { roots } = await initServer()\n    bindShortcut()\n\n    // Start watcher to restart server on file changes\n    const { watch } = await import('chokidar')\n    const watchGlobs = roots\n      .filter(i => !i.includes('node_modules'))\n      .flatMap(root => FILES_CHANGE_RESTART.map(i => path.join(root, i)))\n    const watcher = watch(watchGlobs, {\n      ignored: ['node_modules', '.git'],\n      ignoreInitial: true,\n      ignorePermissionErrors: true,\n    })\n    watcher.on('unlink', (file) => {\n      console.log(yellow(`\\n  file ${file} removed, restarting...\\n`))\n      restartServer()\n    })\n    watcher.on('add', (file) => {\n      console.log(yellow(`\\n  file ${file} added, restarting...\\n`))\n      restartServer()\n    })\n    watcher.on('change', (file) => {\n      console.log(yellow(`\\n  file ${file} changed, restarting...\\n`))\n      restartServer()\n    })\n  },\n)\n\ncli.command(\n  'build [entry..]',\n  'Build hostable SPA',\n  args => exportOptions(commonOptions(args))\n    .option('out', {\n      alias: 'o',\n      type: 'string',\n      default: 'dist',\n      describe: 'output dir',\n    })\n    .option('base', {\n      type: 'string',\n      describe: 'output base. Example: /demo/',\n    })\n    .option('download', {\n      alias: 'd',\n      type: 'boolean',\n      describe: 'allow download as PDF',\n    })\n    .option('without-notes', {\n      type: 'boolean',\n      describe: 'exclude speaker notes from the built output',\n    })\n    .option('inspect', {\n      default: false,\n      type: 'boolean',\n      describe: 'enable the inspect plugin for debugging',\n    })\n    .strict()\n    .help(),\n  async (args) => {\n    const { entry, theme, base, download, out, inspect, 'without-notes': withoutNotes } = args\n    const { build } = await import('./commands/build')\n\n    for (const entryFile of entry as unknown as string[]) {\n      const options = await resolveOptions({ entry: entryFile, theme, inspect, download, base, withoutNotes }, 'build')\n\n      printInfo(options)\n      await build(\n        options,\n        {\n          base,\n          build: {\n            outDir: entry.length === 1 ? out : path.join(out, path.basename(entryFile, '.md')),\n          },\n        },\n        { ...args, entry: entryFile },\n      )\n    }\n  },\n)\n\ncli.command(\n  'format [entry..]',\n  'Format the markdown file',\n  args => commonOptions(args)\n    .strict()\n    .help(),\n  async ({ entry }) => {\n    for (const entryFile of entry as unknown as string[]) {\n      const md = await parser.parse(await fs.readFile(entryFile, 'utf-8'), entryFile)\n      parser.prettify(md)\n      await parser.save(md)\n    }\n  },\n)\n\ncli.command(\n  'theme [subcommand]',\n  'Theme related operations',\n  (command) => {\n    return command\n      .command(\n        'eject',\n        'Eject current theme into local file system',\n        args => commonOptions(args)\n          .option('dir', {\n            type: 'string',\n            default: 'theme',\n          }),\n        async ({ entry: entryRaw, dir, theme: themeInput }) => {\n          const entry = await resolveEntry(entryRaw)\n          const roots = await getRoots(entry)\n          const data = await parser.load(roots.userRoot, entry)\n          let themeRaw = themeInput || data.headmatter.theme as string | null | undefined\n          themeRaw = themeRaw === null ? 'none' : (themeRaw || 'default')\n          if (themeRaw === 'none') {\n            console.error('Cannot eject theme \"none\"')\n            process.exit(1)\n          }\n          if ('/.'.includes(themeRaw[0]) || (themeRaw[0] !== '@' && themeRaw.includes('/'))) {\n            console.error('Theme is already ejected')\n            process.exit(1)\n          }\n          const [name, root] = (await resolveTheme(themeRaw, entry)) as [string, string]\n\n          await fs.mkdir(path.resolve(dir), { recursive: true })\n          await fs.cp(\n            root,\n            path.resolve(dir),\n            {\n              recursive: true,\n              filter: i => !/node_modules|\\.git/.test(path.relative(root, i)),\n            },\n          )\n\n          const dirPath = `./${dir}`\n          const firstSlide = data.entry.slides[0]\n          updateFrontmatterPatch(firstSlide, { theme: dirPath })\n          parser.prettifySlide(firstSlide)\n          await parser.save(data.entry)\n\n          console.log(`Theme \"${name}\" ejected successfully to \"${dirPath}\"`)\n        },\n      )\n  },\n  () => {\n    cli.showHelp()\n    process.exit(1)\n  },\n)\n\ncli.command(\n  'export [entry..]',\n  'Export slides to PDF',\n  args => exportOptions(commonOptions(args))\n    .strict()\n    .help(),\n  async (args) => {\n    const { entry, theme } = args\n    const { exportSlides, getExportOptions } = await import('./commands/export')\n    const candidatePort = await getPort(12445)\n\n    let warned = false\n    for (const entryFile of entry as unknown as string) {\n      const options = await resolveOptions({ entry: entryFile, theme }, 'export')\n\n      if (options.data.config.browserExporter !== false && !warned) {\n        warned = true\n        console.log(cyanBright('[Slidev] Try the new browser exporter!'))\n        console.log(\n          cyanBright('You can use the browser exporter instead by starting the dev server as normal and visit'),\n          `${blue('localhost:')}${dim('<port>')}${blue('/export')}\\n`,\n        )\n      }\n\n      let server: ViteDevServer | undefined\n      try {\n        server = await createServer(\n          options,\n          {\n            server: { port: candidatePort },\n            clearScreen: false,\n          },\n        )\n        await server.listen(candidatePort)\n        const port = getViteServerPort(server)\n        printInfo(options)\n        const result = await exportSlides({\n          port,\n          ...getExportOptions({ ...args, entry: entryFile }, options),\n        })\n        console.log(`${green('  ✓ ')}${dim('exported to ')}${result}\\n`)\n      }\n      finally {\n        await server?.close()\n      }\n    }\n\n    process.exit(0)\n  },\n)\n\ncli.command(\n  'export-notes [entry..]',\n  'Export slide notes to PDF',\n  args => args\n    .positional('entry', {\n      default: 'slides.md',\n      type: 'string',\n      describe: 'path to the slides markdown entry',\n    })\n    .option('output', {\n      type: 'string',\n      describe: 'path to the output',\n    })\n    .option('timeout', {\n      default: 30000,\n      type: 'number',\n      describe: 'timeout for rendering the print page',\n    })\n    .option('wait', {\n      default: 0,\n      type: 'number',\n      describe: 'wait for the specified ms before exporting',\n    })\n    .strict()\n    .help(),\n  async ({\n    entry,\n    output,\n    timeout,\n    wait,\n  }) => {\n    const { exportNotes } = await import('./commands/export')\n    const candidatePort = await getPort(12445)\n\n    for (const entryFile of entry as unknown as string[]) {\n      const options = await resolveOptions({ entry: entryFile }, 'export')\n      let server: ViteDevServer | undefined\n      try {\n        server = await createServer(\n          options,\n          {\n            server: { port: candidatePort },\n            clearScreen: false,\n          },\n        )\n        await server.listen(candidatePort)\n        const port = getViteServerPort(server)\n\n        printInfo(options)\n\n        const result = await exportNotes({\n          port,\n          output: output || (options.data.config.exportFilename ? `${options.data.config.exportFilename}-notes` : `${path.basename(entryFile, '.md')}-export-notes`),\n          timeout,\n          wait,\n        })\n        console.log(`${green('  ✓ ')}${dim('exported to ')}${result}\\n`)\n      }\n      finally {\n        await server?.close()\n      }\n    }\n\n    process.exit(0)\n  },\n)\n\ncli\n  .help()\n  .parse()\n\nfunction getViteServerPort(server: ViteDevServer): number {\n  const address = server.httpServer?.address()\n  if (address && typeof address === 'object')\n    return address.port\n  throw new Error('Failed to get Vite server port')\n}\n\nfunction commonOptions(args: Argv<object>) {\n  return args\n    .positional('entry', {\n      default: 'slides.md',\n      type: 'string',\n      describe: 'path to the slides markdown entry',\n    })\n    .option('theme', {\n      alias: 't',\n      type: 'string',\n      describe: 'override theme',\n    })\n}\n\nfunction exportOptions<T>(args: Argv<T>) {\n  return args\n    .option('output', {\n      type: 'string',\n      describe: 'path to the output',\n    })\n    .option('format', {\n      type: 'string',\n      choices: ['pdf', 'png', 'pptx', 'md'],\n      describe: 'output format',\n    })\n    .option('timeout', {\n      type: 'number',\n      describe: 'timeout for rendering the print page',\n    })\n    .option('wait', {\n      type: 'number',\n      describe: 'wait for the specified ms before exporting',\n    })\n    .option('wait-until', {\n      type: 'string',\n      choices: ['networkidle', 'load', 'domcontentloaded', 'none'],\n      describe: 'wait until the specified event before exporting each slide',\n    })\n    .option('range', {\n      type: 'string',\n      describe: 'page ranges to export, for example \"1,4-5,6\"',\n    })\n    .option('dark', {\n      type: 'boolean',\n      describe: 'export as dark theme',\n    })\n    .option('with-clicks', {\n      alias: 'c',\n      type: 'boolean',\n      describe: 'export pages for every clicks',\n    })\n    .option('executable-path', {\n      type: 'string',\n      describe: 'executable to override playwright bundled browser',\n    })\n    .option('with-toc', {\n      type: 'boolean',\n      describe: 'export pages with outline',\n    })\n    .option('per-slide', {\n      type: 'boolean',\n      describe: 'slide slides slide by slide. Works better with global components, but will break cross slide links and TOC in PDF',\n    })\n    .option('scale', {\n      type: 'number',\n      describe: 'scale factor for image export',\n    })\n    .option('omit-background', {\n      type: 'boolean',\n      describe: 'export png pages without the default browser background',\n    })\n}\n\nfunction printInfo(\n  options: ResolvedSlidevOptions,\n  port?: number,\n  base?: string,\n  remote?: string,\n  tunnelUrl?: string,\n  publicIp?: string,\n) {\n  if (base && (!base.startsWith('/') || !base.endsWith('/'))) {\n    console.error('Base URL must start and end with a slash \"/\"')\n    process.exit(1)\n  }\n\n  console.log()\n  console.log()\n  console.log(`  ${cyan('●') + blue('■') + yellow('▲')}`)\n  console.log(`${bold('  Slidev')}  ${blue(`v${version}`)} ${isInstalledGlobally.value ? yellow('(global)') : ''}`)\n  console.log()\n\n  verifyConfig(options.data.config, options.data.themeMeta, v => console.warn(yellow(`  ! ${v}`)))\n\n  console.log(dim('  theme       ') + (options.theme ? green(options.theme) : gray('none')))\n  console.log(dim('  css engine  ') + blue('unocss'))\n  console.log(dim('  entry       ') + dim(path.normalize(path.dirname(options.entry)) + path.sep) + path.basename(options.entry))\n\n  if (port) {\n    const baseText = base?.slice(0, -1) || ''\n    const portAndBase = port + baseText\n    const baseUrl = `http://localhost:${bold(portAndBase)}`\n    const query = remote ? `?password=${remote}` : ''\n    const presenterPath = `${options.data.config.routerMode === 'hash' ? '/#/' : '/'}presenter/${query}`\n    const entryPath = `${options.data.config.routerMode === 'hash' ? '/#/' : '/'}entry${query}/`\n    const overviewPath = `${options.data.config.routerMode === 'hash' ? '/#/' : '/'}overview${query}/`\n    console.log()\n    console.log(`${dim('  public slide show ')}  > ${cyan(`${baseUrl}/`)}`)\n    if (query)\n      console.log(`${dim('  private slide show ')} > ${cyan(`${baseUrl}/${query}`)}`)\n    if (options.utils.define.__SLIDEV_FEATURE_PRESENTER__)\n      console.log(`${dim('  presenter mode ')}     > ${blue(`${baseUrl}${presenterPath}`)}`)\n    console.log(`${dim('  slides overview ')}    > ${blue(`${baseUrl}${overviewPath}`)}`)\n    if (options.utils.define.__SLIDEV_FEATURE_BROWSER_EXPORTER__)\n      console.log(`${dim('  export slides')}       > ${blue(`${baseUrl}/export/`)}`)\n    if (options.inspect)\n      console.log(`${dim('  vite inspector')}      > ${yellow(`${baseUrl}/__inspect/`)}`)\n\n    let lastRemoteUrl = ''\n\n    if (remote !== undefined) {\n      Object.values(os.networkInterfaces())\n        .forEach(v => (v || [])\n          .filter(details => String(details.family).slice(-1) === '4' && !details.address.includes('127.0.0.1'))\n          .forEach(({ address }) => {\n            lastRemoteUrl = `http://${address}:${portAndBase}${entryPath}`\n            console.log(`${dim('  remote control ')}     > ${blue(lastRemoteUrl)}`)\n          }))\n\n      if (publicIp) {\n        lastRemoteUrl = `http://${publicIp}:${portAndBase}${entryPath}`\n        console.log(`${dim('  remote control ')}     > ${blue(lastRemoteUrl)}`)\n      }\n\n      if (tunnelUrl) {\n        lastRemoteUrl = `${tunnelUrl}${baseText}${entryPath}`\n        console.log(`${dim('  remote via tunnel')}   > ${yellow(lastRemoteUrl)}`)\n      }\n    }\n    else {\n      console.log(`${dim('  remote control ')}     > ${dim('pass --remote to enable')}`)\n    }\n\n    console.log()\n    console.log(`${dim('  shortcuts ')}          > ${underline('r')}${dim('estart | ')}${underline('o')}${dim('pen | ')}${underline('e')}${dim('dit | ')}${underline('q')}${dim('uit')}${lastRemoteUrl ? ` | ${dim('qr')}${underline('c')}${dim('ode')}` : ''}`)\n\n    return lastRemoteUrl\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/commands/build.ts",
    "content": "import type { BuildArgs, ResolvedSlidevOptions } from '@slidev/types'\nimport type { InlineConfig, ResolvedConfig } from 'vite'\nimport { existsSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport http from 'node:http'\nimport { join, resolve } from 'node:path'\nimport connect from 'connect'\nimport sirv from 'sirv'\nimport { build as viteBuild } from 'vite'\nimport { resolveViteConfigs } from './shared'\n\nexport async function build(\n  options: ResolvedSlidevOptions,\n  viteConfig: InlineConfig = {},\n  args: BuildArgs,\n) {\n  const indexPath = resolve(options.userRoot, 'index.html')\n\n  let originalIndexHTML: string | undefined\n  if (existsSync(indexPath))\n    originalIndexHTML = await fs.readFile(indexPath, 'utf-8')\n\n  await fs.writeFile(indexPath, options.utils.indexHtml, 'utf-8')\n  let config: ResolvedConfig = undefined!\n\n  try {\n    const inlineConfig = await resolveViteConfigs(\n      options,\n      {\n        plugins: [\n          {\n            name: 'resolve-config',\n            configResolved(_config) {\n              config = _config\n            },\n          },\n        ],\n        build: {\n          chunkSizeWarningLimit: 2000,\n        },\n      } satisfies InlineConfig,\n      viteConfig,\n      'build',\n    )\n\n    await viteBuild(inlineConfig)\n  }\n  finally {\n    if (originalIndexHTML != null)\n      await fs.writeFile(indexPath, originalIndexHTML, 'utf-8')\n    else\n      await fs.unlink(indexPath)\n  }\n\n  const outDir = resolve(options.userRoot, config.build.outDir)\n\n  // copy or generate ogImage if it's a relative path, skip if not\n  if (options.data.config.seoMeta?.ogImage === 'auto' || options.data.config.seoMeta?.ogImage?.startsWith('.')) {\n    const filename = options.data.config.seoMeta?.ogImage === 'auto' ? 'og-image.png' : options.data.config.seoMeta.ogImage\n    const projectOgImagePath = resolve(options.userRoot, filename)\n    const outputOgImagePath = resolve(outDir, filename)\n\n    const projectOgImageExists = await fs.access(projectOgImagePath).then(() => true).catch(() => false)\n    if (projectOgImageExists) {\n      await fs.copyFile(projectOgImagePath, outputOgImagePath)\n    }\n    else if (options.data.config.seoMeta?.ogImage === 'auto') {\n      const port = 12445\n      const app = connect()\n      const server = http.createServer(app)\n      app.use(\n        config.base,\n        sirv(outDir, {\n          etag: true,\n          single: true,\n          dev: true,\n        }),\n      )\n      server.listen(port)\n\n      const { exportSlides } = await import('./export')\n      const tempDir = resolve(outDir, 'temp')\n      await fs.mkdir(tempDir, { recursive: true })\n\n      await exportSlides({\n        port,\n        base: config.base,\n        slides: options.data.slides,\n        total: options.data.slides.length,\n        format: 'png',\n        output: tempDir,\n        range: '1',\n        width: options.data.config.canvasWidth,\n        height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),\n        routerMode: options.data.config.routerMode,\n        waitUntil: 'networkidle',\n        timeout: args.timeout || 30000,\n        perSlide: true,\n        omitBackground: false,\n        dark: args.dark,\n      })\n\n      const tempFiles = await fs.readdir(tempDir)\n      const pngFile = tempFiles.find(file => file.endsWith('.png'))\n      if (pngFile) {\n        const generatedPath = resolve(tempDir, pngFile)\n        await fs.copyFile(generatedPath, projectOgImagePath)\n        await fs.copyFile(generatedPath, outputOgImagePath)\n      }\n\n      await fs.rm(tempDir, { recursive: true, force: true })\n      server.close()\n    }\n    else {\n      throw new Error(`[Slidev] ogImage: ${filename} not found`)\n    }\n  }\n\n  // copy index.html to 404.html for GitHub Pages\n  await fs.copyFile(resolve(outDir, 'index.html'), resolve(outDir, '404.html'))\n  // _redirects for SPA\n  const redirectsPath = resolve(outDir, '_redirects')\n  if (!existsSync(redirectsPath))\n    await fs.writeFile(redirectsPath, `${config.base}*    ${config.base}index.html   200\\n`, 'utf-8')\n\n  if ([true, 'true', 'auto'].includes(options.data.config.download)) {\n    const { exportSlides, getExportOptions } = await import('./export')\n\n    const port = 12445\n    const app = connect()\n    const server = http.createServer(app)\n    app.use(\n      config.base,\n      sirv(outDir, {\n        etag: true,\n        single: true,\n        dev: true,\n      }),\n    )\n    server.listen(port)\n    const filename = options.data.config.exportFilename || 'slidev-exported'\n    await exportSlides({\n      port,\n      base: config.base,\n      ...getExportOptions(args, options, join(outDir, `${filename}.pdf`)),\n    })\n    server.close()\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/commands/export.ts",
    "content": "import type { ExportArgs, ResolvedSlidevOptions, SlideInfo, TocItem } from '@slidev/types'\nimport { Buffer } from 'node:buffer'\nimport fs from 'node:fs/promises'\nimport path, { dirname, relative } from 'node:path'\nimport process from 'node:process'\nimport { clearUndefined, ensureSuffix, slash } from '@antfu/utils'\nimport { outlinePdfFactory } from '@lillallol/outline-pdf'\nimport { parseRangeString } from '@slidev/parser/core'\nimport { blue, cyan, dim, green, yellow } from 'ansis'\nimport { Presets, SingleBar } from 'cli-progress'\nimport { resolve } from 'mlly'\nimport * as pdfLib from 'pdf-lib'\nimport { PDFDocument } from 'pdf-lib'\nimport { getRoots } from '../resolver'\n\nexport interface ExportOptions {\n  total: number\n  range?: string\n  slides: SlideInfo[]\n  port?: number\n  base?: string\n  format?: 'pdf' | 'png' | 'pptx' | 'md'\n  output?: string\n  timeout?: number\n  wait?: number\n  waitUntil: 'networkidle' | 'load' | 'domcontentloaded' | undefined\n  dark?: boolean\n  routerMode?: 'hash' | 'history'\n  width?: number\n  height?: number\n  withClicks?: boolean\n  executablePath?: string\n  withToc?: boolean\n  /**\n   * Render slides slide by slide. Works better with global components, but will break cross slide links and TOC in PDF.\n   * @default false\n   */\n  perSlide?: boolean\n  scale?: number\n  omitBackground?: boolean\n}\n\ninterface ExportPngResult {\n  slideIndex: number\n  buffer: Buffer\n  filename: string\n}\n\nfunction addToTree(tree: TocItem[], info: SlideInfo, slideIndexes: Record<number, number>, level = 1) {\n  const titleLevel = info.level\n  if (titleLevel && titleLevel > level && tree.length > 0 && tree[tree.length - 1].titleLevel < titleLevel) {\n    addToTree(tree[tree.length - 1].children, info, slideIndexes, level + 1)\n  }\n  else {\n    tree.push({\n      no: info.index,\n      children: [],\n      level,\n      titleLevel: titleLevel ?? level,\n      path: String(slideIndexes[info.index + 1]),\n      hideInToc: Boolean(info.frontmatter?.hideInToc),\n      title: info.title,\n    })\n  }\n}\n\nfunction makeOutline(tree: TocItem[]): string {\n  return tree.map(({ title, path, level, children }) => {\n    const rootOutline = title ? `${path}|${'-'.repeat(level - 1)}|${title}` : null\n\n    const childrenOutline = makeOutline(children)\n\n    return childrenOutline.length > 0 ? `${rootOutline}\\n${childrenOutline}` : rootOutline\n  }).filter(outline => !!outline).join('\\n')\n}\n\nexport interface ExportNotesOptions {\n  port?: number\n  base?: string\n  output?: string\n  timeout?: number\n  wait?: number\n}\n\nfunction createSlidevProgress(indeterminate = false) {\n  function getSpinner(n = 0) {\n    return [cyan('●'), green('◆'), blue('■'), yellow('▲')][n % 4]\n  }\n  let current = 0\n  let spinner = 0\n  let timer: any\n\n  const progress = new SingleBar({\n    clearOnComplete: true,\n    hideCursor: true,\n    format: `  {spin} ${yellow('rendering')}${indeterminate ? dim(yellow('...')) : ' {bar} {value}/{total}'}`,\n    linewrap: false,\n    barsize: 30,\n  }, Presets.shades_grey)\n\n  return {\n    bar: progress,\n    start(total: number) {\n      progress.start(total, 0, { spin: getSpinner(spinner) })\n      timer = setInterval(() => {\n        spinner += 1\n        progress.update(current, { spin: getSpinner(spinner) })\n      }, 200)\n    },\n    update(v: number) {\n      current = v\n      progress.update(v, { spin: getSpinner(spinner) })\n    },\n    stop() {\n      clearInterval(timer)\n      progress.stop()\n    },\n  }\n}\n\nexport async function exportNotes({\n  port = 18724,\n  base = '/',\n  output = 'notes',\n  timeout = 30000,\n  wait = 0,\n}: ExportNotesOptions): Promise<string> {\n  const { chromium } = await importPlaywright()\n  const browser = await chromium.launch()\n  const context = await browser.newContext()\n  const page = await context.newPage()\n\n  const progress = createSlidevProgress(true)\n\n  progress.start(1)\n\n  if (!output.endsWith('.pdf'))\n    output = `${output}.pdf`\n\n  await page.goto(`http://localhost:${port}${base}presenter/print`, { waitUntil: 'networkidle', timeout })\n  await page.waitForLoadState('networkidle')\n  await page.emulateMedia({ media: 'screen' })\n\n  if (wait)\n    await page.waitForTimeout(wait)\n\n  await page.pdf({\n    path: output,\n    margin: {\n      left: 0,\n      top: 0,\n      right: 0,\n      bottom: 0,\n    },\n    printBackground: true,\n    preferCSSPageSize: true,\n  })\n\n  progress.stop()\n  browser.close()\n\n  return output\n}\n\nexport async function exportSlides({\n  port = 18724,\n  total = 0,\n  range,\n  format = 'pdf',\n  output = 'slides',\n  slides,\n  base = '/',\n  timeout = 30000,\n  wait = 0,\n  dark = false,\n  routerMode = 'history',\n  width = 1920,\n  height = 1080,\n  withClicks = false,\n  executablePath = undefined,\n  withToc = false,\n  perSlide = false,\n  scale = 1,\n  waitUntil,\n  omitBackground = false,\n}: ExportOptions) {\n  const pages: number[] = parseRangeString(total, range)\n\n  const { chromium } = await importPlaywright()\n  const browser = await chromium.launch({\n    executablePath,\n  })\n  const context = await browser.newContext({\n    viewport: {\n      width,\n      // Calculate height for every slides to be in the viewport to trigger the rendering of iframes (twitter, youtube...)\n      height: perSlide ? height : height * pages.length,\n    },\n    deviceScaleFactor: scale,\n  })\n  const page = await context.newPage()\n  const progress = createSlidevProgress(!perSlide)\n  progress.start(pages.length)\n\n  if (format === 'pdf') {\n    await genPagePdf()\n  }\n  else if (format === 'png') {\n    await genPagePng(output)\n  }\n  else if (format === 'md') {\n    await genPageMd()\n  }\n  else if (format === 'pptx') {\n    const buffers = await genPagePng(false)\n    await genPagePptx(buffers)\n  }\n  else {\n    throw new Error(`[slidev] Unsupported exporting format \"${format}\"`)\n  }\n\n  progress.stop()\n  browser.close()\n\n  const relativeOutput = slash(relative('.', output))\n  return relativeOutput.startsWith('.') ? relativeOutput : `./${relativeOutput}`\n\n  async function go(no: number | string, clicks?: string) {\n    const query = new URLSearchParams()\n    if (withClicks)\n      query.set('print', 'clicks')\n    else\n      query.set('print', 'true')\n    if (range)\n      query.set('range', range)\n    if (clicks)\n      query.set('clicks', clicks)\n\n    const url = routerMode === 'hash'\n      ? `http://localhost:${port}${base}?${query}#${no}`\n      : `http://localhost:${port}${base}${no}?${query}`\n    await page.goto(url, {\n      waitUntil,\n      timeout,\n    })\n    if (waitUntil)\n      await page.waitForLoadState(waitUntil)\n    await page.emulateMedia({ colorScheme: dark ? 'dark' : 'light', media: 'screen' })\n    const slide = no === 'print'\n      ? page.locator('body')\n      : page.locator(`[data-slidev-no=\"${no}\"]`)\n    await slide.waitFor()\n\n    // Wait for slides to be loaded\n    {\n      const elements = slide.locator('.slidev-slide-loading')\n      const count = await elements.count()\n      for (let index = 0; index < count; index++)\n        await elements.nth(index).waitFor({ state: 'detached' })\n    }\n    // Check for \"data-waitfor\" attribute and wait for given element to be loaded\n    {\n      const elements = slide.locator('[data-waitfor]')\n      const count = await elements.count()\n      for (let index = 0; index < count; index++) {\n        const element = elements.nth(index)\n        const attribute = await element.getAttribute('data-waitfor')\n        if (attribute) {\n          await element.locator(attribute).waitFor({ state: 'visible' }).catch((e) => {\n            console.error(e)\n            process.exitCode = 1\n          })\n        }\n      }\n    }\n    // Wait for frames to load\n    {\n      const frames = page.frames()\n      await Promise.all(frames.map(frame => frame.waitForLoadState()))\n    }\n    // Wait for Mermaid graphs to be rendered\n    {\n      const container = slide.locator('#mermaid-rendering-container')\n      const count = await container.count()\n      if (count > 0) {\n        while (true) {\n          const element = container.locator('div').first()\n          if (await element.count() === 0)\n            break\n          await element.waitFor({ state: 'detached' })\n        }\n        await container.evaluate(node => node.style.display = 'none')\n      }\n    }\n    // Hide Monaco aria container\n    {\n      const elements = slide.locator('.monaco-aria-container')\n      const count = await elements.count()\n      for (let index = 0; index < count; index++) {\n        const element = elements.nth(index)\n        await element.evaluate(node => node.style.display = 'none')\n      }\n    }\n    // Wait for the given time\n    if (wait)\n      await page.waitForTimeout(wait)\n  }\n\n  async function getSlidesIndex() {\n    const clicksBySlide: Record<string, number> = {}\n    const slides = page.locator('.print-slide-container')\n    const count = await slides.count()\n    for (let i = 0; i < count; i++) {\n      const id = (await slides.nth(i).getAttribute('id')) || ''\n      const path = Number(id.split('-')[0])\n      clicksBySlide[path] = (clicksBySlide[path] || 0) + 1\n    }\n\n    const slideIndexes = Object.fromEntries(Object.entries(clicksBySlide)\n      .reduce<[string, number][]>((acc, [path, clicks], i) => {\n        acc.push([path, clicks + (acc[i - 1]?.[1] ?? 0)])\n        return acc\n      }, []))\n    return slideIndexes\n  }\n\n  function getClicksFromUrl(url: string) {\n    return url.match(/clicks=([1-9]\\d*)/)?.[1]\n  }\n\n  async function genPageWithClicks(\n    fn: (no: number, clicks?: string) => Promise<any>,\n    no: number,\n    clicks?: string,\n  ) {\n    await fn(no, clicks)\n    if (withClicks) {\n      await page.keyboard.press('ArrowRight', { delay: 100 })\n      const _clicks = getClicksFromUrl(page.url())\n      if (_clicks && clicks !== _clicks)\n        await genPageWithClicks(fn, no, _clicks)\n    }\n  }\n\n  async function genPagePdfPerSlide() {\n    const buffers: Buffer[] = []\n    const genPdfBuffer = async (i: number, clicks?: string) => {\n      await go(i, clicks)\n      const pdf = await page.pdf({\n        width,\n        height,\n        margin: {\n          left: 0,\n          top: 0,\n          right: 0,\n          bottom: 0,\n        },\n        pageRanges: '1',\n        printBackground: true,\n        preferCSSPageSize: true,\n      })\n      buffers.push(pdf)\n    }\n    let idx = 0\n    for (const i of pages) {\n      await genPageWithClicks(genPdfBuffer, i)\n      progress.update(++idx)\n    }\n\n    let mergedPdf = await PDFDocument.create({})\n    for (const pdfBytes of buffers) {\n      const pdf = await PDFDocument.load(pdfBytes)\n      const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices())\n      copiedPages.forEach((page) => {\n        mergedPdf.addPage(page)\n      })\n    }\n\n    // Edit generated PDF: add metadata and (optionally) TOC\n    addPdfMetadata(mergedPdf)\n\n    if (withToc)\n      mergedPdf = await addTocToPdf(mergedPdf)\n\n    const buffer = await mergedPdf.save()\n    await fs.writeFile(output, buffer)\n  }\n\n  async function genPagePdfOnePiece() {\n    await go('print')\n    await page.pdf({\n      path: output,\n      width,\n      height,\n      margin: {\n        left: 0,\n        top: 0,\n        right: 0,\n        bottom: 0,\n      },\n      printBackground: true,\n      preferCSSPageSize: true,\n    })\n\n    // Edit generated PDF: add metadata and (optionally) TOC\n    let pdfData = await fs.readFile(output)\n    let pdf = await PDFDocument.load(pdfData)\n\n    addPdfMetadata(pdf)\n\n    if (withToc)\n      pdf = await addTocToPdf(pdf)\n\n    pdfData = Buffer.from(await pdf.save())\n    await fs.writeFile(output, pdfData)\n  }\n\n  async function genPagePngOnePiece(writeToDisk: string | false) {\n    const result: ExportPngResult[] = []\n    await go('print')\n    const slideContainers = page.locator('.print-slide-container')\n    const count = await slideContainers.count()\n\n    for (let i = 0; i < count; i++) {\n      const id = (await slideContainers.nth(i).getAttribute('id')) || ''\n      const slideNo = +id.split('-')[0]\n\n      // Only process slides that are in the specified range\n      if (!pages.includes(slideNo))\n        continue\n\n      progress.update(result.length + 1)\n\n      const buffer = await slideContainers.nth(i).screenshot({\n        omitBackground,\n      })\n      const filename = `${withClicks ? id : slideNo}.png`\n      result.push({ slideIndex: slideNo - 1, buffer, filename })\n      if (writeToDisk)\n        await fs.writeFile(path.join(writeToDisk, filename), buffer)\n    }\n    return result\n  }\n\n  async function genPagePngPerSlide(writeToDisk: string | false) {\n    const result: ExportPngResult[] = []\n    const genScreenshot = async (no: number, clicks?: string) => {\n      await go(no, clicks)\n      const buffer = await page.screenshot({\n        omitBackground,\n      })\n      const filename = `${no.toString().padStart(2, '0')}${clicks ? `-${clicks}` : ''}.png`\n      result.push({ slideIndex: no - 1, buffer, filename })\n      if (writeToDisk) {\n        await fs.writeFile(\n          path.join(writeToDisk, filename),\n          buffer,\n        )\n      }\n    }\n    for (const no of pages)\n      await genPageWithClicks(genScreenshot, no)\n    return result\n  }\n\n  function genPagePdf() {\n    if (!output.endsWith('.pdf'))\n      output = `${output}.pdf`\n    return perSlide\n      ? genPagePdfPerSlide()\n      : genPagePdfOnePiece()\n  }\n\n  async function genPagePng(writeToDisk: string | false) {\n    if (writeToDisk) {\n      await fs.rm(writeToDisk, { force: true, recursive: true })\n      await fs.mkdir(writeToDisk, { recursive: true })\n    }\n    return perSlide\n      ? genPagePngPerSlide(writeToDisk)\n      : genPagePngOnePiece(writeToDisk)\n  }\n\n  async function genPageMd() {\n    const pngs = await genPagePng(dirname(output))\n    const content = slides\n      .filter(({ index }) => pages.includes(index + 1))\n      .map(({ title, index, note }) =>\n        pngs.filter(({ slideIndex }) => slideIndex === index)\n          .map(({ filename }) => `![${title || (index + 1)}](./${filename})\\n\\n`)\n          .join('')\n          + (note ? `${note.trim()}\\n\\n` : ''),\n      )\n      .join('---\\n\\n')\n    await fs.writeFile(ensureSuffix('.md', output), content)\n  }\n\n  // Ported from https://github.com/marp-team/marp-cli/blob/main/src/converter.ts\n  async function genPagePptx(pngs: ExportPngResult[]) {\n    const { default: PptxGenJS } = await import('pptxgenjs')\n    const pptx = new PptxGenJS()\n\n    const layoutName = `${width}x${height}`\n    pptx.defineLayout({\n      name: layoutName,\n      width: width / 96,\n      height: height / 96,\n    })\n    pptx.layout = layoutName\n\n    const titleSlide = slides[0]\n    pptx.author = titleSlide?.frontmatter?.author\n    pptx.company = 'Created using Slidev'\n    if (titleSlide?.title)\n      pptx.title = titleSlide?.title\n    if (titleSlide?.frontmatter?.info)\n      pptx.subject = titleSlide?.frontmatter?.info\n\n    pngs.forEach(({ slideIndex, buffer }) => {\n      const slide = pptx.addSlide()\n      slide.background = {\n        data: `data:image/png;base64,${buffer.toString('base64')}`,\n      }\n\n      const note = slides[slideIndex].note\n      if (note)\n        slide.addNotes(note)\n    })\n\n    const buffer = await pptx.write({\n      outputType: 'nodebuffer',\n    }) as Buffer\n    if (!output.endsWith('.pptx'))\n      output = `${output}.pptx`\n    await fs.writeFile(output, buffer)\n  }\n\n  // Adds metadata (title, author, keywords) to PDF document, mutating it\n  function addPdfMetadata(pdf: PDFDocument): void {\n    const titleSlide = slides[0]\n    if (titleSlide?.title)\n      pdf.setTitle(titleSlide.title)\n    if (titleSlide?.frontmatter?.info)\n      pdf.setSubject(titleSlide.frontmatter.info)\n    if (titleSlide?.frontmatter?.author)\n      pdf.setAuthor(titleSlide.frontmatter.author)\n    if (titleSlide?.frontmatter?.keywords) {\n      if (Array.isArray(titleSlide?.frontmatter?.keywords))\n        pdf.setKeywords(titleSlide?.frontmatter?.keywords)\n      else\n        pdf.setKeywords(titleSlide?.frontmatter?.keywords.split(','))\n    }\n  }\n\n  async function addTocToPdf(pdf: PDFDocument): Promise<PDFDocument> {\n    const outlinePdf = outlinePdfFactory(pdfLib)\n    const slideIndexes = await getSlidesIndex()\n\n    const tocTree = slides.filter(slide => slide.title)\n      .reduce((acc: TocItem[], slide) => {\n        addToTree(acc, slide, slideIndexes)\n        return acc\n      }, [])\n\n    const outline = makeOutline(tocTree)\n\n    return await outlinePdf({ outline, pdf })\n  }\n}\n\nexport function getExportOptions(args: ExportArgs, options: ResolvedSlidevOptions, outFilename?: string): Omit<ExportOptions, 'port' | 'base'> {\n  const config = {\n    ...options.data.config.export,\n    ...args,\n    ...clearUndefined({\n      waitUntil: args['wait-until'],\n      withClicks: args['with-clicks'],\n      executablePath: args['executable-path'],\n      withToc: args['with-toc'],\n      perSlide: args['per-slide'],\n      omitBackground: args['omit-background'],\n    }),\n  }\n  const {\n    entry,\n    output,\n    format,\n    timeout,\n    wait,\n    waitUntil,\n    range,\n    dark,\n    withClicks,\n    executablePath,\n    withToc,\n    perSlide,\n    scale,\n    omitBackground,\n  } = config\n  outFilename = output || outFilename || options.data.config.exportFilename || `${path.basename(entry, '.md')}-export`\n  return {\n    output: outFilename,\n    slides: options.data.slides,\n    total: options.data.slides.length,\n    range,\n    format: (format || 'pdf') as 'pdf' | 'png' | 'pptx' | 'md',\n    timeout: timeout ?? 30000,\n    wait: wait ?? 0,\n    waitUntil: waitUntil === 'none' ? undefined : (waitUntil ?? 'networkidle') as 'networkidle' | 'load' | 'domcontentloaded',\n    dark: dark || options.data.config.colorSchema === 'dark',\n    routerMode: options.data.config.routerMode,\n    width: options.data.config.canvasWidth,\n    height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),\n    withClicks: withClicks ?? format === 'pptx',\n    executablePath,\n    withToc: withToc || false,\n    perSlide: perSlide || false,\n    scale: scale || 2,\n    omitBackground: omitBackground ?? false,\n  }\n}\n\nasync function importPlaywright(): Promise<typeof import('playwright-chromium')> {\n  const { userRoot, userWorkspaceRoot } = await getRoots()\n\n  // 1. resolve from user root\n  try {\n    return await import(await resolve('playwright-chromium', { url: userRoot }))\n  }\n  catch { }\n\n  // 2. resolve from user workspace root\n  if (userWorkspaceRoot !== userRoot) {\n    try {\n      return await import(await resolve('playwright-chromium', { url: userWorkspaceRoot }))\n    }\n    catch { }\n  }\n\n  // 3. resolve from global registry\n  const { resolveGlobal } = await import('resolve-global')\n  try {\n    const imported = await import(resolveGlobal('playwright-chromium'))\n    return imported.default ?? imported\n  }\n  catch { }\n\n  // 4. resolve from current @slidev/cli installation\n  try {\n    return await import('playwright-chromium')\n  }\n  catch { }\n\n  throw new Error('The exporting for Slidev is powered by Playwright, please install it via `npm i -D playwright-chromium`')\n}\n"
  },
  {
    "path": "packages/slidev/node/commands/serve.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevServerOptions } from '@slidev/types'\nimport type { InlineConfig } from 'vite'\nimport { join } from 'node:path'\nimport process from 'node:process'\nimport { createServer as createViteServer } from 'vite'\nimport { resolveViteConfigs } from './shared'\n\nexport async function createServer(\n  options: ResolvedSlidevOptions,\n  viteConfig: InlineConfig = {},\n  serverOptions?: SlidevServerOptions,\n) {\n  // default open editor to code, #312\n  process.env.EDITOR = process.env.EDITOR || 'code'\n\n  const inlineConfig = await resolveViteConfigs(\n    options,\n    {\n      optimizeDeps: {\n        entries: [\n          join(options.clientRoot, 'main.ts'),\n        ],\n      },\n    } satisfies InlineConfig,\n    viteConfig,\n    'serve',\n    serverOptions,\n  )\n\n  return await createViteServer(inlineConfig)\n}\n"
  },
  {
    "path": "packages/slidev/node/commands/shared.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevData, SlidevServerOptions } from '@slidev/types'\nimport type { ConfigEnv, InlineConfig } from 'vite'\nimport MarkdownExit from 'markdown-exit'\nimport { loadConfigFromFile, mergeConfig } from 'vite'\nimport { resolveSourceFiles } from '../resolver'\nimport markdownItLink from '../syntax/markdown-it/markdown-it-link'\nimport { stringifyMarkdownTokens } from '../utils'\nimport { ViteSlidevPlugin } from '../vite'\n\nexport const sharedMd = MarkdownExit({ html: true })\nsharedMd.use(markdownItLink)\n\nexport function getSlideTitle(data: SlidevData) {\n  const tokens = sharedMd.parseInline(data.config.title, {})\n  const title = stringifyMarkdownTokens(tokens)\n  const slideTitle = data.config.titleTemplate.replace('%s', title)\n  return slideTitle === 'Slidev - Slidev' ? 'Slidev' : slideTitle\n}\n\nexport async function resolveViteConfigs(\n  options: ResolvedSlidevOptions,\n  baseConfig: InlineConfig,\n  overrideConfigs: InlineConfig,\n  command: 'serve' | 'build',\n  serverOptions?: SlidevServerOptions,\n) {\n  // Merge theme & addon & user configs\n  const configEnv: ConfigEnv = {\n    mode: command === 'build' ? 'production' : 'development',\n    command,\n  }\n  const files = resolveSourceFiles(options.roots, 'vite.config')\n  const configs = await Promise.all(files.map(file => loadConfigFromFile(configEnv, file)))\n  for (const config of configs) {\n    if (!config?.config)\n      continue\n    baseConfig = mergeConfig(baseConfig, config.config)\n  }\n\n  // Merge command-specific overrides\n  baseConfig = mergeConfig(baseConfig, overrideConfigs)\n\n  // Merge common overrides\n  baseConfig = mergeConfig(baseConfig, {\n    configFile: false,\n    root: options.userRoot,\n    plugins: await ViteSlidevPlugin(options, baseConfig.slidev, serverOptions),\n    define: {\n      // Fixes Vue production mode breaking PDF Export #1245\n      __VUE_PROD_DEVTOOLS__: false,\n    },\n  } satisfies InlineConfig)\n\n  return baseConfig\n}\n"
  },
  {
    "path": "packages/slidev/node/index.ts",
    "content": "export { createServer } from './commands/serve'\nexport * from './options'\nexport { parser } from './parser'\nexport { ViteSlidevPlugin } from './vite'\n"
  },
  {
    "path": "packages/slidev/node/integrations/addons.ts",
    "content": "import fs from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { satisfies } from 'semver'\nimport { version } from '../../package.json'\nimport { createResolver, getRoots } from '../resolver'\n\nexport async function resolveAddons(addonsInConfig: string[]) {\n  const { userRoot, userPkgJson } = await getRoots()\n  const resolved: string[] = []\n\n  const resolveAddonNameAndRoot = createResolver('addon', {})\n\n  async function resolveAddon(name: string, parent: string) {\n    const [, pkgRoot] = await resolveAddonNameAndRoot(name, parent)\n    if (!pkgRoot)\n      return\n    resolved.push(pkgRoot)\n    const { slidev = {}, engines = {} } = JSON.parse(await fs.readFile(resolve(pkgRoot, 'package.json'), 'utf-8'))\n\n    if (engines.slidev && !satisfies(version, engines.slidev, { includePrerelease: true }))\n      throw new Error(`[slidev] addon \"${name}\" requires Slidev version range \"${engines.slidev}\" but found \"${version}\"`)\n\n    if (Array.isArray(slidev.addons))\n      await Promise.all(slidev.addons.map((addon: string) => resolveAddon(addon, pkgRoot)))\n  }\n\n  if (Array.isArray(addonsInConfig))\n    await Promise.all(addonsInConfig.map((addon: string) => resolveAddon(addon, userRoot)))\n  if (Array.isArray(userPkgJson.slidev?.addons))\n    await Promise.all(userPkgJson.slidev.addons.map((addon: string) => resolveAddon(addon, userRoot)))\n\n  return resolved\n}\n"
  },
  {
    "path": "packages/slidev/node/integrations/drawings.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport fg from 'fast-glob'\n\nfunction resolveDrawingsDir(options: ResolvedSlidevOptions): string | undefined {\n  return options.data.config.drawings.persist\n    ? resolve(\n        dirname(options.entry),\n        options.data.config.drawings.persist,\n      )\n    : undefined\n}\n\nexport async function loadDrawings(options: ResolvedSlidevOptions) {\n  const dir = resolveDrawingsDir(options)\n  if (!dir || !existsSync(dir))\n    return {}\n\n  const files = await fg('*.svg', {\n    onlyFiles: true,\n    cwd: dir,\n    absolute: true,\n    suppressErrors: true,\n  })\n\n  const obj: Record<string, string> = {}\n  await Promise.all(files.map(async (path) => {\n    const num = +basename(path, '.svg')\n    if (Number.isNaN(num))\n      return\n    const content = await fs.readFile(path, 'utf8')\n    const lines = content.split(/\\n/g)\n    obj[num.toString()] = lines.slice(1, -1).join('\\n')\n  }))\n\n  return obj\n}\n\nexport async function writeDrawings(options: ResolvedSlidevOptions, drawing: Record<string, string>) {\n  const dir = resolveDrawingsDir(options)\n  if (!dir)\n    return\n\n  const width = options.data.config.canvasWidth\n  const height = Math.round(width / options.data.config.aspectRatio)\n  const SVG_HEAD = `<svg width=\"${width}\" height=\"${height}\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">`\n\n  await fs.mkdir(dir, { recursive: true })\n\n  return Promise.all(\n    Object.entries(drawing).map(async ([key, value]) => {\n      if (!value)\n        return\n\n      const svg = `${SVG_HEAD}\\n${value}\\n</svg>`\n      await fs.writeFile(join(dir, `${key}.svg`), svg, 'utf-8')\n    }),\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/integrations/snapshots.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\n\nfunction resolveSnapshotsDir(options: ResolvedSlidevOptions): string {\n  return resolve(dirname(options.entry), '.slidev/snapshots')\n}\n\nexport async function loadSnapshots(options: ResolvedSlidevOptions) {\n  const dir = resolveSnapshotsDir(options)\n  const file = join(dir, 'snapshots.json')\n  if (!dir || !existsSync(file))\n    return {}\n\n  return JSON.parse(await fs.readFile(file, 'utf8'))\n}\n\nexport async function writeSnapshots(options: ResolvedSlidevOptions, data: Record<string, any>) {\n  const dir = resolveSnapshotsDir(options)\n  if (!dir)\n    return\n\n  await fs.mkdir(dir, { recursive: true })\n  // TODO: write as each image file\n  await fs.writeFile(join(dir, 'snapshots.json'), JSON.stringify(data, null, 2), 'utf-8')\n}\n"
  },
  {
    "path": "packages/slidev/node/integrations/themes.ts",
    "content": "import type { SlidevThemeMeta } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { satisfies } from 'semver'\nimport { version } from '../../package.json'\nimport { createResolver } from '../resolver'\n\nconst officialThemes: Record<string, string> = {\n  'none': '',\n  'default': '@slidev/theme-default',\n  'seriph': '@slidev/theme-seriph',\n  'apple-basic': '@slidev/theme-apple-basic',\n  'shibainu': '@slidev/theme-shibainu',\n  'bricks': '@slidev/theme-bricks',\n}\n\nexport const resolveTheme = createResolver('theme', officialThemes)\n\nexport async function getThemeMeta(name: string, root: string) {\n  const path = join(root, 'package.json')\n  if (!existsSync(path))\n    return {}\n\n  const { slidev = {}, engines = {} } = JSON.parse(await fs.readFile(path, 'utf-8'))\n\n  if (engines.slidev && !satisfies(version, engines.slidev, { includePrerelease: true }))\n    throw new Error(`[slidev] theme \"${name}\" requires Slidev version range \"${engines.slidev}\" but found \"${version}\"`)\n\n  return slidev as SlidevThemeMeta\n}\n"
  },
  {
    "path": "packages/slidev/node/options.ts",
    "content": "import type { ResolvedSlidevOptions, ResolvedSlidevUtils, SlidevData, SlidevEntryOptions } from '@slidev/types'\nimport path from 'node:path'\nimport { objectMap, uniq } from '@antfu/utils'\nimport fg from 'fast-glob'\nimport { createDebug } from 'obug'\nimport pm from 'picomatch'\nimport { resolveAddons } from './integrations/addons'\nimport { getThemeMeta, resolveTheme } from './integrations/themes'\nimport { parser } from './parser'\nimport { getRoots, resolveEntry, toAtFS } from './resolver'\nimport setupIndexHtml from './setups/indexHtml'\nimport setupKatex from './setups/katex'\nimport setupShiki from './setups/shiki'\n\nconst debug = createDebug('slidev:options')\n\nexport async function resolveOptions(\n  entryOptions: SlidevEntryOptions,\n  mode: ResolvedSlidevOptions['mode'],\n): Promise<ResolvedSlidevOptions> {\n  const entry = await resolveEntry(entryOptions.entry)\n  const rootsInfo = await getRoots(entry)\n  const loaded = await parser.load(rootsInfo.userRoot, entry, undefined, mode)\n\n  // Load theme data first, because it may affect the config\n  let themeRaw = entryOptions.theme || loaded.headmatter.theme as string | null | undefined\n  themeRaw = themeRaw === null ? 'none' : (themeRaw || 'default')\n  const [theme, themeRoot] = await resolveTheme(themeRaw, entry)\n  const themeRoots = themeRoot ? [themeRoot] : []\n  const themeMeta = themeRoot ? await getThemeMeta(theme, themeRoot) : undefined\n\n  const config = parser.resolveConfig(loaded.headmatter, themeMeta, entryOptions.entry)\n  const addonRoots = await resolveAddons(config.addons)\n  const roots = uniq([...themeRoots, ...addonRoots, rootsInfo.userRoot])\n\n  if (entryOptions.download)\n    config.download ||= entryOptions.download\n\n  debug({\n    ...rootsInfo,\n    ...entryOptions,\n    config,\n    mode,\n    entry,\n    themeRaw,\n    theme,\n    themeRoots,\n    addonRoots,\n    roots,\n  })\n\n  const data: SlidevData = {\n    ...loaded,\n    config,\n    themeMeta,\n  }\n\n  const resolved: Omit<ResolvedSlidevOptions, 'utils'> = {\n    ...rootsInfo,\n    ...entryOptions,\n    data,\n    mode,\n    entry,\n    themeRaw,\n    theme,\n    themeRoots,\n    addonRoots,\n    roots,\n  }\n\n  return {\n    ...resolved,\n    utils: await createDataUtils(resolved),\n  }\n}\n\nexport async function createDataUtils(resolved: Omit<ResolvedSlidevOptions, 'utils'>): Promise<ResolvedSlidevUtils> {\n  const monacoTypesIgnorePackagesMatches = (resolved.data.config.monacoTypesIgnorePackages || [])\n    .map(i => pm.makeRe(i))\n\n  let _layouts_cache_time = 0\n  let _layouts_cache: Promise<Record<string, string>> | null = null\n\n  return {\n    ...await setupShiki(resolved.roots),\n    katexOptions: await setupKatex(resolved.roots),\n    indexHtml: await setupIndexHtml(resolved),\n    define: getDefine(resolved),\n    iconsResolvePath: [resolved.clientRoot, ...resolved.roots].reverse(),\n    isMonacoTypesIgnored: pkg => monacoTypesIgnorePackagesMatches.some(i => i.test(pkg)),\n    getLayouts: () => {\n      const now = Date.now()\n      if (_layouts_cache && now - _layouts_cache_time < 2000)\n        return _layouts_cache\n      _layouts_cache_time = now\n      return _layouts_cache = worker()\n\n      async function worker() {\n        const layouts: Record<string, string> = {}\n        const layoutPaths = await Promise.all(\n          [resolved.clientRoot, ...resolved.roots]\n            .map(root => fg('layouts/**/*.{vue,js,mjs,ts,mts}', {\n              cwd: root,\n              absolute: true,\n              suppressErrors: true,\n            })),\n        )\n        for (const layoutPath of layoutPaths.flat(1)) {\n          const layoutName = path.basename(layoutPath).replace(/\\.\\w+$/, '')\n          layouts[layoutName] = layoutPath\n        }\n        return layouts\n      }\n    },\n  }\n}\n\nfunction getDefine(options: Omit<ResolvedSlidevOptions, 'utils'>): Record<string, string> {\n  const matchMode = (mode: string | boolean) => mode === true || mode === options.mode\n  return objectMap(\n    {\n      __DEV__: options.mode === 'dev',\n      __SLIDEV_CLIENT_ROOT__: toAtFS(options.clientRoot),\n      __SLIDEV_HASH_ROUTE__: options.data.config.routerMode === 'hash',\n      __SLIDEV_FEATURE_DRAWINGS__: matchMode(options.data.config.drawings.enabled),\n      __SLIDEV_FEATURE_EDITOR__: options.mode === 'dev' && options.data.config.editor !== false,\n      __SLIDEV_FEATURE_DRAWINGS_PERSIST__: !!options.data.config.drawings.persist,\n      __SLIDEV_FEATURE_RECORD__: matchMode(options.data.config.record),\n      __SLIDEV_FEATURE_PRESENTER__: matchMode(options.data.config.presenter),\n      __SLIDEV_FEATURE_PRINT__: options.mode === 'export' || (options.mode === 'build' && [true, 'true', 'auto'].includes(options.data.config.download)),\n      __SLIDEV_FEATURE_BROWSER_EXPORTER__: matchMode(options.data.config.browserExporter),\n      __SLIDEV_FEATURE_WAKE_LOCK__: matchMode(options.data.config.wakeLock),\n      __SLIDEV_HAS_SERVER__: options.mode !== 'build',\n    },\n    (v, k) => [v, JSON.stringify(k)],\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/parser.ts",
    "content": "export * as parser from '@slidev/parser/fs'\n"
  },
  {
    "path": "packages/slidev/node/resolver.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest'\nimport { createResolver, getRoots } from './resolver'\n\nconst vitefuMocks = vi.hoisted(() => {\n  const FAKE_ROOT = '/user/project'\n  return {\n    findDepPkgJsonPath: vi.fn((name: string) => {\n      if (name === '@slidev/client')\n        return `${FAKE_ROOT}/node_modules/@slidev/client/package.json`\n      if (name === '@slidev/theme-official')\n        return `${FAKE_ROOT}/node_modules/@slidev/theme-official/package.json`\n      if (name === 'slidev-theme-xyz')\n        return `${FAKE_ROOT}/node_modules/slidev-theme-xyz/package.json`\n      if (name === 'slidev-theme-full')\n        return `${FAKE_ROOT}/node_modules/slidev-theme-full/package.json`\n      return null\n    }),\n    findClosestPkgJsonPath: vi.fn().mockResolvedValue(`${FAKE_ROOT}/package.json`),\n  }\n})\n\nvi.mock('vitefu', () => vitefuMocks)\n\ndescribe('createResolver', () => {\n  beforeEach(async () => {\n    await getRoots('/user/project')\n  })\n\n  it('resolves @slidev/ official prefixed theme', async () => {\n    const resolver = createResolver('theme', {})\n\n    const res = await resolver('official', '/')\n\n    expect(res).toEqual([\n      '@slidev/theme-official',\n      '/user/project/node_modules/@slidev/theme-official',\n    ],\n    )\n  })\n\n  it('resolves slidev-theme- third party prefixed theme', async () => {\n    const resolver = createResolver('theme', {})\n\n    const res = await resolver('xyz', '/')\n\n    expect(res).toEqual([\n      'slidev-theme-xyz',\n      '/user/project/node_modules/slidev-theme-xyz',\n    ],\n    )\n  })\n\n  it('resolves fully-specified package', async () => {\n    const resolver = createResolver('theme', {})\n\n    const res = await resolver('slidev-theme-full', '/')\n\n    expect(res).toEqual([\n      'slidev-theme-full',\n      '/user/project/node_modules/slidev-theme-full',\n    ],\n    )\n  })\n})\n"
  },
  {
    "path": "packages/slidev/node/resolver.ts",
    "content": "import type { RootsInfo } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport { copyFile, readFile } from 'node:fs/promises'\nimport { dirname, join, relative, resolve } from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { parseNi, run } from '@antfu/ni'\nimport { ensurePrefix, slash } from '@antfu/utils'\nimport { underline, yellow } from 'ansis'\nimport globalDirs from 'global-directory'\nimport { resolvePath } from 'mlly'\nimport prompts from 'prompts'\nimport { resolveGlobal } from 'resolve-global'\nimport { findClosestPkgJsonPath, findDepPkgJsonPath } from 'vitefu'\n\nconst cliRoot = fileURLToPath(new URL('..', import.meta.url))\n\nexport const isInstalledGlobally: { value?: boolean } = {}\n\n/**\n * Resolve path for import url on Vite client side\n */\nexport async function resolveImportUrl(id: string) {\n  return toAtFS(await resolveImportPath(id, true))\n}\n\nexport function toAtFS(path: string) {\n  return `/@fs${ensurePrefix('/', slash(path))}`\n}\n\n/**\n * Find the actual path of the import. If Slidev is installed globally, it will also search globally.\n */\nexport async function resolveImportPath(importName: string, ensure: true): Promise<string>\nexport async function resolveImportPath(importName: string, ensure?: boolean): Promise<string | undefined>\nexport async function resolveImportPath(importName: string, ensure = false) {\n  try {\n    return await resolvePath(importName, {\n      url: import.meta.url,\n    })\n  }\n  catch { }\n\n  if (isInstalledGlobally.value) {\n    try {\n      return resolveGlobal(importName)\n    }\n    catch { }\n  }\n\n  if (ensure)\n    throw new Error(`Failed to resolve package \"${importName}\"`)\n}\n\n/**\n * Find the root of the package. If Slidev is installed globally, it will also search globally.\n */\nexport async function findPkgRoot(dep: string, parent: string, ensure: true): Promise<string>\nexport async function findPkgRoot(dep: string, parent: string, ensure?: boolean): Promise<string | undefined>\nexport async function findPkgRoot(dep: string, parent: string, ensure = false) {\n  const pkgJsonPath = await findDepPkgJsonPath(dep, parent)\n  const path = pkgJsonPath ? dirname(pkgJsonPath) : isInstalledGlobally.value ? await findGlobalPkgRoot(dep, false) : undefined\n  if (ensure && !path)\n    throw new Error(`Failed to resolve package \"${dep}\"`)\n  return path\n}\n\nexport async function findGlobalPkgRoot(name: string, ensure: true): Promise<string>\nexport async function findGlobalPkgRoot(name: string, ensure?: boolean): Promise<string | undefined>\nexport async function findGlobalPkgRoot(name: string, ensure = false) {\n  const localPath = await findDepPkgJsonPath(name, cliRoot)\n  if (localPath)\n    return dirname(localPath)\n  const yarnPath = join(globalDirs.yarn.packages, name)\n  if (existsSync(`${yarnPath}/package.json`))\n    return yarnPath\n  const npmPath = join(globalDirs.npm.packages, name)\n  if (existsSync(`${npmPath}/package.json`))\n    return npmPath\n  if (ensure)\n    throw new Error(`Failed to resolve global package \"${name}\"`)\n}\n\nexport async function resolveEntry(entryRaw: string) {\n  if (!existsSync(entryRaw) && !entryRaw.endsWith('.md') && !/[/\\\\]/.test(entryRaw))\n    entryRaw += '.md'\n  const entry = resolve(entryRaw)\n  if (!existsSync(entry)) {\n    // Check if stdin is available for prompts (i.e., is a TTY)\n    if (!process.stdin.isTTY) {\n      console.error(`Entry file \"${entry}\" does not exist and cannot prompt for confirmation`)\n      process.exit(1)\n    }\n    const { create } = await prompts({\n      name: 'create',\n      type: 'confirm',\n      initial: 'Y',\n      message: `Entry file ${yellow(`\"${entry}\"`)} does not exist, do you want to create it?`,\n    })\n    if (create)\n      await copyFile(resolve(cliRoot, 'template.md'), entry)\n    else\n      process.exit(0)\n  }\n  return slash(entry)\n}\n\n/**\n * Create a resolver for theme or addon\n */\nexport function createResolver(type: 'theme' | 'addon', officials: Record<string, string>) {\n  async function promptForInstallation(pkgName: string) {\n    // Check if stdin is available for prompts (i.e., is a TTY)\n    if (!process.stdin.isTTY) {\n      console.error(`The ${type} \"${pkgName}\" was not found and cannot prompt for installation`)\n      process.exit(1)\n    }\n\n    const { confirm } = await prompts({\n      name: 'confirm',\n      initial: 'Y',\n      type: 'confirm',\n      message: `The ${type} \"${pkgName}\" was not found ${underline(isInstalledGlobally.value ? 'globally' : 'in your project')}, do you want to install it now?`,\n    })\n\n    if (!confirm)\n      process.exit(1)\n\n    if (isInstalledGlobally.value)\n      await run(parseNi, ['-g', pkgName])\n    else\n      await run(parseNi, [pkgName])\n  }\n\n  return async function (name: string, importer: string): Promise<[name: string, root: string | null]> {\n    const { userRoot } = await getRoots()\n\n    if (name === 'none')\n      return ['', null]\n\n    // local path\n    if (name[0] === '/')\n      return [name, name]\n    if (name.startsWith('@/'))\n      return [name, resolve(userRoot, name.slice(2))]\n    if (name[0] === '.' || (name[0] !== '@' && name.includes('/')))\n      return [name, resolve(dirname(importer), name)]\n\n    // search for local packages first\n    {\n      const possiblePkgNames = [name]\n\n      if (!name.includes('/') && !name.startsWith('@')) {\n        possiblePkgNames.unshift(\n          `@slidev/${type}-${name}`,\n          `slidev-${type}-${name}`,\n        )\n      }\n\n      for (const pkgName of possiblePkgNames) {\n        const pkgRoot = await findPkgRoot(pkgName, importer)\n        if (pkgRoot)\n          return [pkgName, pkgRoot]\n      }\n    }\n\n    // fallback to prompt install\n    const pkgName = officials[name] ?? (name[0] === '@' ? name : `slidev-${type}-${name}`)\n    await promptForInstallation(pkgName)\n    return [pkgName, await findPkgRoot(pkgName, importer, true)]\n  }\n}\n\nasync function getUserPkgJson(userRoot: string) {\n  const path = resolve(userRoot, 'package.json')\n  if (existsSync(path))\n    return JSON.parse(await readFile(path, 'utf-8')) as Record<string, any>\n  return {}\n}\n\n// npm: https://docs.npmjs.com/cli/v7/using-npm/workspaces#installing-workspaces\n// yarn: https://classic.yarnpkg.com/en/docs/workspaces/#toc-how-to-use-it\nasync function hasWorkspacePackageJSON(root: string): Promise<boolean> {\n  const path = join(root, 'package.json')\n  if (!existsSync(path))\n    return false\n  const content = JSON.parse(await readFile(path, 'utf-8')) || {}\n  return !!content.workspaces\n}\n\nfunction hasRootFile(root: string): boolean {\n  // https://github.com/vitejs/vite/issues/2820#issuecomment-812495079\n  const ROOT_FILES = [\n    // '.git',\n\n    // https://pnpm.js.org/workspaces/\n    'pnpm-workspace.yaml',\n\n    // https://rushjs.io/pages/advanced/config_files/\n    // 'rush.json',\n\n    // https://nx.dev/latest/react/getting-started/nx-setup\n    // 'workspace.json',\n    // 'nx.json'\n  ]\n\n  return ROOT_FILES.some(file => existsSync(join(root, file)))\n}\n\n/**\n * Search up for the nearest workspace root\n */\nasync function searchForWorkspaceRoot(\n  current: string,\n  root = current,\n): Promise<string> {\n  if (hasRootFile(current))\n    return current\n  if (await hasWorkspacePackageJSON(current))\n    return current\n\n  const dir = dirname(current)\n  // reach the fs root\n  if (!dir || dir === current)\n    return root\n\n  return searchForWorkspaceRoot(dir, root)\n}\n\nlet rootsInfo: RootsInfo | null = null\n\nexport async function getRoots(entry?: string): Promise<RootsInfo> {\n  if (rootsInfo)\n    return rootsInfo\n  if (!entry)\n    throw new Error('[slidev] Cannot find roots without entry')\n  const userRoot = dirname(entry)\n  isInstalledGlobally.value\n    = slash(relative(userRoot, process.argv[1])).includes('/.pnpm/')\n      || (await import('is-installed-globally')).default\n  const clientRoot = await findPkgRoot('@slidev/client', cliRoot, true)\n  const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot)\n  const userPkgJson = await getUserPkgJson(closestPkgRoot)\n  const userWorkspaceRoot = await searchForWorkspaceRoot(closestPkgRoot)\n  rootsInfo = {\n    cliRoot,\n    clientRoot,\n    userRoot,\n    userPkgJson,\n    userWorkspaceRoot,\n  }\n  return rootsInfo\n}\n\nexport function resolveSourceFiles(\n  roots: string[],\n  subpath: string,\n  extensions = ['.mjs', '.js', '.mts', '.ts'], // The same order as https://vite.dev/config/shared-options#resolve-extensions\n) {\n  const results: string[] = []\n  for (const root of roots) {\n    for (const ext of extensions) {\n      const fullPath = join(root, subpath + ext)\n      if (existsSync(fullPath)) {\n        results.push(fullPath)\n        break\n      }\n    }\n  }\n  return results\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/indexHtml.ts",
    "content": "import type { ResolvedSlidevOptions, SeoMeta } from '@slidev/types'\nimport type { ResolvableLink } from 'unhead/types'\nimport { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport { white, yellow } from 'ansis'\nimport { createHead, extractUnheadInputFromHtml, transformHtmlTemplate } from 'unhead/server'\nimport { version } from '../../package.json'\nimport { getSlideTitle } from '../commands/shared'\nimport { toAtFS } from '../resolver'\nimport { generateCoollabsFontsUrl, generateGoogleFontsUrl } from '../utils'\n\nfunction escapeHtml(str: string): string {\n  return str\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#039;')\n}\n\nfunction toAttrValue(unsafe: unknown) {\n  return JSON.stringify(escapeHtml(String(unsafe)))\n}\n\nfunction collectPreloadImages(data: Omit<ResolvedSlidevOptions, 'utils'>['data'], base?: string): ResolvableLink[] {\n  const config = data.config\n  if (config.preloadImages === false)\n    return []\n\n  const seen = new Set<string>()\n  const links: ResolvableLink[] = []\n  const basePrefix = base ? base.replace(/\\/$/, '') : ''\n\n  for (const slide of data.slides) {\n    const images = slide.images || slide.source?.images\n    if (!images?.length)\n      continue\n    for (const url of images) {\n      if (seen.has(url))\n        continue\n      seen.add(url)\n      const href = url.startsWith('http') || url.startsWith('//')\n        ? url\n        : `${basePrefix}${url.startsWith('/') ? url : `/${url}`}`\n      links.push({ rel: 'preload', as: 'image', href })\n    }\n  }\n\n  return links\n}\n\nexport default async function setupIndexHtml({ mode, entry, clientRoot, userRoot, roots, data, base }: Omit<ResolvedSlidevOptions, 'utils'>): Promise<string> {\n  let main = await readFile(join(clientRoot, 'index.html'), 'utf-8')\n  let body = ''\n\n  const inputs: any[] = []\n\n  for (const root of roots) {\n    const path = join(root, 'index.html')\n    if (!existsSync(path))\n      continue\n\n    const html = await readFile(path, 'utf-8')\n\n    if (root === userRoot && html.includes('<!DOCTYPE')) {\n      console.error(yellow(`[Slidev] Ignored provided index.html with doctype declaration. (${white(path)})`))\n      console.error(yellow('This file may be generated by Slidev, please remove it from your project.'))\n      continue\n    }\n\n    inputs.push(extractUnheadInputFromHtml(html).input)\n    body += `\\n${(html.match(/<body>([\\s\\S]*?)<\\/body>/i)?.[1] || '').trim()}`\n  }\n\n  if (data.features.tweet) {\n    body += '\\n<script async src=\"https://platform.twitter.com/widgets.js\"></script>'\n  }\n\n  const webFontsLink: ResolvableLink[] = []\n  if (data.config.fonts.webfonts.length) {\n    const { provider } = data.config.fonts\n    if (provider === 'google') {\n      webFontsLink.push({ rel: 'stylesheet', href: generateGoogleFontsUrl(data.config.fonts), type: 'text/css' })\n    }\n    else if (provider === 'coollabs') {\n      webFontsLink.push({ rel: 'stylesheet', href: generateCoollabsFontsUrl(data.config.fonts), type: 'text/css' })\n    }\n  }\n\n  const { info, author, keywords } = data.headmatter\n  const seoMeta = (data.headmatter.seoMeta ?? {}) as SeoMeta\n\n  const ogImage = seoMeta.ogImage === 'auto'\n    ? './og-image.png'\n    : seoMeta.ogImage\n      ? seoMeta.ogImage\n      : existsSync(join(userRoot, 'og-image.png'))\n        ? './og-image.png'\n        : undefined\n\n  const title = getSlideTitle(data)\n  const description = info ? toAttrValue(info) : null\n  const unhead = createHead({\n    init: [\n      {\n        htmlAttrs: data.headmatter.lang ? { lang: data.headmatter.lang as string } : undefined,\n        title,\n        link: [\n          data.config.favicon ? { rel: 'icon', href: data.config.favicon } : null,\n          ...webFontsLink,\n          ...collectPreloadImages(data, base),\n        ].filter(x => x),\n        meta: [\n          { 'http-equiv': 'Content-Type', 'content': 'text/html; charset=UTF-8' },\n          { property: 'slidev:version', content: version },\n          { property: 'slidev:entry', content: mode === 'dev' && slash(entry) },\n          { name: 'description', content: description },\n          { name: 'author', content: author ? toAttrValue(author) : null },\n          { name: 'keywords', content: keywords ? toAttrValue(Array.isArray(keywords) ? keywords.join(', ') : keywords) : null },\n          { property: 'og:title', content: seoMeta.ogTitle || title },\n          { property: 'og:description', content: seoMeta.ogDescription || description },\n          { property: 'og:image', content: ogImage },\n          { property: 'og:url', content: seoMeta.ogUrl },\n          { property: 'twitter:card', content: seoMeta.twitterCard },\n          { property: 'twitter:site', content: seoMeta.twitterSite },\n          { property: 'twitter:title', content: seoMeta.twitterTitle },\n          { property: 'twitter:description', content: seoMeta.twitterDescription },\n          { property: 'twitter:image', content: seoMeta.twitterImage },\n          { property: 'twitter:url', content: seoMeta.twitterUrl },\n        ].filter(x => x.content),\n      },\n      ...inputs,\n    ],\n  })\n\n  const mainUrl = toAtFS(join(clientRoot, 'main.ts'))\n  if (mode === 'build') {\n    main = main.replace('__ENTRY__', mainUrl)\n  }\n  else {\n    const basePrefix = base ? base.slice(0, -1) : ''\n    main = main.replace('__ENTRY__', encodeURI(basePrefix + mainUrl))\n  }\n\n  main = main.replace('<!-- body -->', body)\n\n  const html = await transformHtmlTemplate(unhead, main)\n  return html\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/katex.ts",
    "content": "import type { KatexSetup } from '@slidev/types'\nimport type { KatexOptions } from 'katex'\nimport { loadSetups } from './load'\n\nexport default async function setupKatex(roots: string[]): Promise<KatexOptions> {\n  const options = await loadSetups<KatexSetup>(roots, 'katex.ts', [])\n  return Object.assign(\n    { strict: false },\n    ...options,\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/load.ts",
    "content": "import type { Awaitable } from '@antfu/utils'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { loadModule } from '../utils'\n\nexport async function loadSetups<F extends (...args: any) => any>(\n  roots: string[],\n  filename: string,\n  args: Parameters<F>,\n  extraLoader?: (root: string) => ReturnType<F>[],\n) {\n  return await Promise.all(roots.flatMap((root) => {\n    const tasks: Awaitable<ReturnType<F>>[] = []\n    const path = resolve(root, 'setup', filename)\n    if (existsSync(path)) {\n      tasks.push(loadModule<{ default: F }>(path).then(mod => mod.default(...args)))\n    }\n    if (extraLoader) {\n      tasks.push(...extraLoader(root))\n    }\n    return tasks\n  }))\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/preparser.ts",
    "content": "import type { PreparserSetup } from '@slidev/types'\nimport { uniq } from '@antfu/utils'\nimport { injectPreparserExtensionLoader } from '@slidev/parser/fs'\nimport { resolveAddons } from '../integrations/addons'\nimport { getRoots } from '../resolver'\nimport { loadSetups } from './load'\n\nexport default function setupPreparser() {\n  injectPreparserExtensionLoader(async (headmatter: Record<string, unknown>, filepath: string, mode?: string) => {\n    // Ensure addons is an array or an empty array if undefined\n    const addons = Array.isArray(headmatter?.addons) ? headmatter.addons as string[] : []\n\n    const { userRoot } = await getRoots()\n    const roots = uniq([\n      ...await resolveAddons(addons),\n      userRoot,\n    ])\n\n    const returns = await loadSetups<PreparserSetup>(roots, 'preparser.ts', [{ filepath, headmatter, mode }])\n    return returns.flat()\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/shiki.ts",
    "content": "import type { ResolvedSlidevUtils, ShikiSetup } from '@slidev/types'\nimport fs from 'node:fs/promises'\nimport { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'\nimport { createJavaScriptRegexEngine } from 'shiki/engine/javascript'\nimport { resolveShikiOptions } from '../../../client/setup/shiki-options'\nimport { loadSetups } from './load'\n\nlet cachedRoots: string[] | undefined\nlet cachedShiki: Pick<ResolvedSlidevUtils, 'shiki' | 'shikiOptions'> | undefined\n\nexport default async function setupShiki(roots: string[]) {\n  // Here we use shallow equality because when server is restarted, the roots will be different object.\n  if (cachedRoots === roots)\n    return cachedShiki!\n\n  const optionsRaw = await loadSetups<ShikiSetup>(\n    roots,\n    'shiki.ts',\n    [{\n      /** @deprecated */\n      async loadTheme(path: string) {\n        console.warn('[slidev] `loadTheme` in `setup/shiki.ts` is deprecated. Pass directly the theme name it\\'s supported by Shiki. For custom themes, load it manually via `JSON.parse(fs.readFileSync(path, \\'utf-8\\'))` and pass the raw JSON object instead.')\n        return JSON.parse(await fs.readFile(path, 'utf-8'))\n      },\n    }],\n  )\n  const { options, languageInput, themeInput } = resolveShikiOptions(optionsRaw)\n\n  const createHighlighter = createBundledHighlighter<string, string>({\n    engine: createJavaScriptRegexEngine,\n    langs: languageInput,\n    themes: themeInput,\n  })\n\n  cachedRoots = roots\n  return cachedShiki = {\n    shiki: createSingletonShorthands(createHighlighter),\n    shikiOptions: options,\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/transformers.ts",
    "content": "import type { TransformersSetup, TransformersSetupReturn } from '@slidev/types'\nimport { loadSetups } from './load'\n\nexport default async function setupTransformers(roots: string[]) {\n  const returns = await loadSetups<TransformersSetup>(roots, 'transformers.ts', [])\n  const result: TransformersSetupReturn = {\n    pre: [],\n    preCodeblock: [],\n    postCodeblock: [],\n    post: [],\n  }\n  for (const r of [...returns].reverse()) {\n    if (r.pre)\n      result.pre.push(...r.pre)\n    if (r.preCodeblock)\n      result.preCodeblock.push(...r.preCodeblock)\n  }\n  for (const r of returns) {\n    if (r.postCodeblock)\n      result.postCodeblock.push(...r.postCodeblock)\n    if (r.post)\n      result.post.push(...r.post)\n  }\n  return result\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/unocss.ts",
    "content": "import type { ResolvedSlidevOptions, UnoSetup } from '@slidev/types'\nimport type { UserConfig } from '@unocss/core'\nimport type { Theme } from '@unocss/preset-uno'\nimport { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { mergeConfigs, presetIcons } from 'unocss'\nimport { loadSetups } from '../setups/load'\nimport { loadModule } from '../utils'\n\nexport default async function setupUnocss(\n  { clientRoot, roots, data, utils }: ResolvedSlidevOptions,\n) {\n  function loadFileConfigs(root: string) {\n    return [\n      resolve(root, 'uno.config.ts'),\n      resolve(root, 'unocss.config.ts'),\n    ].map(async (i) => {\n      if (!existsSync(i))\n        return undefined\n      const loaded = await loadModule(i) as UserConfig | { default: UserConfig }\n      return 'default' in loaded ? loaded.default : loaded\n    })\n  }\n\n  const configs = [\n    {\n      presets: [\n        presetIcons({\n          collectionsNodeResolvePath: utils.iconsResolvePath,\n          collections: {\n            slidev: {\n              logo: () => readFile(resolve(clientRoot, 'assets/logo.svg'), 'utf-8'),\n            },\n          },\n        }),\n      ],\n      safelist: await loadModule(resolve(clientRoot, '.generated/unocss-tokens.ts')),\n    },\n    (await loadModule<{ default: UserConfig }>(resolve(clientRoot, 'uno.config.ts'))).default,\n    ...await loadSetups<UnoSetup>(roots, 'unocss.ts', [], loadFileConfigs),\n  ].filter(Boolean) as UserConfig<Theme>[]\n\n  const config = mergeConfigs(configs)\n\n  config.theme ||= {}\n  config.theme.fontFamily ||= {}\n  config.theme.fontFamily.sans ||= data.config.fonts.sans.join(',')\n  config.theme.fontFamily.mono ||= data.config.fonts.mono.join(',')\n  config.theme.fontFamily.serif ||= data.config.fonts.serif.join(',')\n\n  return config\n}\n"
  },
  {
    "path": "packages/slidev/node/setups/vite-plugins.ts",
    "content": "import type { ResolvedSlidevOptions, VitePluginsSetup } from '@slidev/types'\nimport type { PluginOption } from 'vite'\nimport { loadSetups } from './load'\n\nexport default async function setupVitePlugins(options: ResolvedSlidevOptions): Promise<PluginOption> {\n  const plugins = await loadSetups<VitePluginsSetup>(options.roots, 'vite-plugins.ts', [options])\n  return plugins\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/index.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type MagicString from 'magic-string'\nimport type MarkdownExit from 'markdown-exit'\nimport MarkdownItComark from '@comark/markdown-it'\nimport { taskLists as MarkdownItTaskList } from '@hedgedoc/markdown-it-plugins'\n// @ts-expect-error missing types\nimport MarkdownItFootnote from 'markdown-it-footnote'\nimport MarkdownItEscapeInlineCode from './markdown-it-escape-code'\nimport MarkdownItKatex from './markdown-it-katex'\nimport MarkdownItLink from './markdown-it-link'\nimport MarkdownItShiki from './markdown-it-shiki'\nimport MarkdownItVDrag from './markdown-it-v-drag'\n\nexport async function useMarkdownItPlugins(md: MarkdownExit, options: ResolvedSlidevOptions, markdownTransformMap: Map<string, MagicString>) {\n  const { data: { features, config }, utils: { katexOptions } } = options\n\n  if (config.highlighter === 'shiki') {\n    // @ts-expect-error @shikijs/markdown-it types expect MarkdownItAsync, but MarkdownExit is API-compatible\n    md.use(await MarkdownItShiki(options))\n  }\n\n  md.use(MarkdownItLink)\n  md.use(MarkdownItEscapeInlineCode)\n  md.use(MarkdownItFootnote)\n  md.use(MarkdownItTaskList, { enabled: true, lineNumber: true, label: true })\n  if (features.katex)\n    md.use(MarkdownItKatex, katexOptions)\n  md.use(MarkdownItVDrag, markdownTransformMap)\n  if (config.comark || config.mdc)\n    md.use(MarkdownItComark)\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/markdown-it-escape-code.ts",
    "content": "import type MarkdownExit from 'markdown-exit'\n\nexport default function MarkdownItEscapeInlineCode(md: MarkdownExit) {\n  const codeInline = md.renderer.rules.code_inline!\n  md.renderer.rules.code_inline = async (tokens, idx, options, env, self) => {\n    const result = await codeInline(tokens, idx, options, env, self)\n    return result.replace(/^<code/, '<code v-pre')\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/markdown-it-katex.ts",
    "content": "// Ported from https://github.com/waylonflinn/markdown-it-katex\n\n/* Process inline math */\n/*\nLike markdown-it-simplemath, this is a stripped down, simplified version of:\nhttps://github.com/runarberg/markdown-it-math\n\nIt differs in that it takes (a subset of) LaTeX as input and relies on KaTeX\nfor rendering output.\n*/\n\nimport type { KatexOptions } from 'katex'\nimport katex from 'katex'\nimport { escapeVueInCode } from '../transform/utils'\n\n// Test if potential opening or closing delimiter\n// Assumes that there is a \"$\" at state.src[pos]\nfunction isValidDelim(state: any, pos: number) {\n  const max = state.posMax\n  let can_open = true\n  let can_close = true\n\n  const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1\n  const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1\n\n  // Check non-whitespace conditions for opening and closing, and\n  // check that closing delimeter isn't followed by a number\n  if (prevChar === 0x20/* \" \" */ || prevChar === 0x09 ||/* \\t */ (nextChar >= 0x30/* \"0\" */ && nextChar <= 0x39/* \"9\" */))\n    can_close = false\n\n  if (nextChar === 0x20/* \" \" */ || nextChar === 0x09/* \\t */)\n    can_open = false\n\n  return {\n    can_open,\n    can_close,\n  }\n}\n\nfunction math_inline(state: any, silent: boolean) {\n  let match, token, res, pos\n\n  if (state.src[state.pos] !== '$')\n    return false\n\n  res = isValidDelim(state, state.pos)\n  if (!res.can_open) {\n    if (!silent)\n      state.pending += '$'\n    state.pos += 1\n    return true\n  }\n\n  // First check for and bypass all properly escaped delimiters\n  // This loop will assume that the first leading backtick can not\n  // be the first character in state.src, which is known since\n  // we have found an opening delimiter already.\n  const start = state.pos + 1\n  match = start\n  // eslint-disable-next-line no-cond-assign\n  while ((match = state.src.indexOf('$', match)) !== -1) {\n    // Found potential $, look for escapes, pos will point to\n    // first non escape when complete\n    pos = match - 1\n    while (state.src[pos] === '\\\\') pos -= 1\n\n    // Even number of escapes, potential closing delimiter found\n    if (((match - pos) % 2) === 1)\n      break\n    match += 1\n  }\n\n  // No closing delimter found.  Consume $ and continue.\n  if (match === -1) {\n    if (!silent)\n      state.pending += '$'\n    state.pos = start\n    return true\n  }\n\n  // Check if we have empty content, ie: $$.  Do not parse.\n  if (match - start === 0) {\n    if (!silent)\n      state.pending += '$$'\n    state.pos = start + 1\n    return true\n  }\n\n  // Check for valid closing delimiter\n  res = isValidDelim(state, match)\n  if (!res.can_close) {\n    if (!silent)\n      state.pending += '$'\n    state.pos = start\n    return true\n  }\n\n  if (!silent) {\n    token = state.push('math_inline', 'math', 0)\n    token.markup = '$'\n    token.content = state.src.slice(start, match)\n  }\n\n  state.pos = match + 1\n  return true\n}\n\nfunction math_block(state: any, start: number, end: number, silent: boolean) {\n  let firstLine\n  let lastLine\n  let next\n  let lastPos\n  let found = false\n  let pos = state.bMarks[start] + state.tShift[start]\n  let max = state.eMarks[start]\n\n  if (pos + 2 > max)\n    return false\n  if (state.src.slice(pos, pos + 2) !== '$$')\n    return false\n\n  pos += 2\n  firstLine = state.src.slice(pos, max)\n\n  if (silent)\n    return true\n  if (firstLine.trim().slice(-2) === '$$') {\n    // Single line expression\n    firstLine = firstLine.trim().slice(0, -2)\n    found = true\n  }\n\n  for (next = start; !found;) {\n    next++\n\n    if (next >= end)\n      break\n\n    pos = state.bMarks[next] + state.tShift[next]\n    max = state.eMarks[next]\n\n    if (pos < max && state.tShift[next] < state.blkIndent) {\n      // non-empty line with negative indent should stop the list:\n      break\n    }\n\n    if (state.src.slice(pos, max).trim().slice(-2) === '$$') {\n      lastPos = state.src.slice(0, max).lastIndexOf('$$')\n      lastLine = state.src.slice(pos, lastPos)\n      found = true\n    }\n  }\n\n  state.line = next + 1\n\n  const token = state.push('math_block', 'math', 0)\n  token.block = true\n  token.content = (firstLine && firstLine.trim() ? `${firstLine}\\n` : '')\n    + state.getLines(start + 1, next, state.tShift[start], true)\n    + (lastLine && lastLine.trim() ? lastLine : '')\n  token.map = [start, state.line]\n  token.markup = '$$'\n  return true\n}\n\nexport default function MarkdownItKatex(md: any, options: KatexOptions) {\n  // set KaTeX as the renderer for markdown-it-simplemath\n  const katexInline = function (latex: string) {\n    options.displayMode = false\n    try {\n      return escapeVueInCode(katex.renderToString(latex, options))\n    }\n    catch (error) {\n      if (options.throwOnError)\n\n        console.warn(error)\n      return latex\n    }\n  }\n\n  const inlineRenderer = function (tokens: any, idx: number) {\n    return katexInline(tokens[idx].content)\n  }\n\n  const katexBlock = function (latex: string) {\n    options.displayMode = true\n    try {\n      return `<p>${escapeVueInCode(katex.renderToString(latex, options))}</p>`\n    }\n    catch (error) {\n      if (options.throwOnError)\n\n        console.warn(error)\n      return latex\n    }\n  }\n\n  const blockRenderer = function (tokens: any, idx: number) {\n    return `${katexBlock(tokens[idx].content)}\\n`\n  }\n\n  md.inline.ruler.after('escape', 'math_inline', math_inline)\n  md.block.ruler.after('blockquote', 'math_block', math_block, {\n    alt: ['paragraph', 'reference', 'blockquote', 'list'],\n  })\n  md.renderer.rules.math_inline = inlineRenderer\n  md.renderer.rules.math_block = blockRenderer\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/markdown-it-link.ts",
    "content": "import type MarkdownExit from 'markdown-exit'\n\nexport default function MarkdownItLink(md: MarkdownExit) {\n  const defaultRender = md.renderer.rules.link_open\n    ?? ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options))\n\n  md.renderer.rules.link_open = function (tokens, idx, options, env, self) {\n    const token = tokens[idx]\n    const hrefIndex = token.attrIndex('href')\n    const attr = token.attrs?.[hrefIndex]\n    const href = attr?.[1] ?? ''\n    if ('./#'.includes(href[0]) || /^\\d+$/.test(href)) {\n      token.tag = 'Link'\n      attr![0] = 'to'\n\n      for (let i = idx + 1; i < tokens.length; i++) {\n        if (tokens[i].type === 'link_close') {\n          tokens[i].tag = 'Link'\n          break\n        }\n      }\n    }\n    else if (token.attrGet('target') == null) {\n      token.attrPush(['target', '_blank'])\n    }\n    return defaultRender(tokens, idx, options, env, self)\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/markdown-it-shiki.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { ShikiTransformer } from 'shiki'\nimport { isTruthy } from '@antfu/utils'\nimport { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async'\nimport { escapeVueInCode } from '../transform/utils'\n\nexport default async function MarkdownItShiki({ data: { config }, mode, utils: { shiki, shikiOptions } }: ResolvedSlidevOptions) {\n  async function getTwoslashTransformer() {\n    const [,,{ transformerTwoslash }] = await Promise.all([\n      // trigger shiki to load the langs\n      shiki.codeToHast('', { lang: 'js', ...shikiOptions }),\n      shiki.codeToHast('', { lang: 'ts', ...shikiOptions }),\n\n      import('@shikijs/vitepress-twoslash'),\n    ])\n    return transformerTwoslash({\n      explicitTrigger: true,\n      twoslashOptions: {\n        handbookOptions: {\n          noErrorValidation: true,\n        },\n      },\n    })\n  }\n\n  const transformers = [\n    ...shikiOptions.transformers || [],\n    (config.twoslash === true || config.twoslash === mode) && await getTwoslashTransformer(),\n    {\n      pre(pre) {\n        this.addClassToHast(pre, 'slidev-code')\n        delete pre.properties.tabindex\n      },\n      postprocess(code) {\n        return escapeVueInCode(code)\n      },\n    } satisfies ShikiTransformer,\n  ].filter(isTruthy) as ShikiTransformer[]\n\n  return fromAsyncCodeToHtml(shiki.codeToHtml, {\n    ...shikiOptions,\n    transformers,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/markdown-it/markdown-it-v-drag.ts",
    "content": "import type MagicString from 'magic-string-stack'\nimport type MarkdownExit from 'markdown-exit'\nimport { SourceMapConsumer } from 'source-map-js'\n\ntype Token = ReturnType<MarkdownExit['parseInline']>[number]\n\nconst dragComponentRegex = /<(v-?drag-?\\w*)([\\s>])/i\nconst dragDirectiveRegex = /(?<![</\\w])v-drag(=\".*?\")?/i\n\nexport default function MarkdownItVDrag(md: MarkdownExit, markdownTransformMap: Map<string, MagicString>) {\n  const visited = new WeakSet()\n  const sourceMapConsumers = new WeakMap<MagicString, SourceMapConsumer>()\n\n  function getSourceMapConsumer(id: string) {\n    const s = markdownTransformMap.get(id)\n    if (!s)\n      return undefined\n    let smc = sourceMapConsumers.get(s)\n    if (smc)\n      return smc\n    const sourceMap = s.generateMap()\n    smc = new SourceMapConsumer({\n      ...sourceMap,\n      version: sourceMap.version.toString(),\n    })\n    sourceMapConsumers.set(s, smc)\n    return smc\n  }\n\n  const _parse = md.parse\n  md.parse = function (src, env) {\n    const smc = getSourceMapConsumer(env.id)\n    const toOriginalPos = smc\n      ? (line: number) => smc.originalPositionFor({ line: line + 1, column: 0 }).line - 1\n      : (line: number) => line\n    function toMarkdownSource(map: [number, number], idx: number) {\n      const start = toOriginalPos(map[0])\n      const end = toOriginalPos(map[1])\n      return `[${start},${Math.max(start + 1, end)},${idx}]`\n    }\n\n    function replaceChildren(token: Token, regex: RegExp, replacement: string) {\n      for (const child of token.children ?? []) {\n        if (child.type === 'html_block' || child.type === 'html_inline') {\n          child.content = child.content.replace(regex, replacement)\n        }\n        replaceChildren(child, regex, replacement)\n      }\n    }\n\n    return _parse.call(this, src, env)\n      .map((token) => {\n        if (!['html_block', 'html_inline', 'inline'].includes(token.type) || !token.content.includes('drag') || visited.has(token))\n          return token\n\n        // Iterates all html tokens and replaces <v-drag> with <v-drag :markdownSource=\"...\"> to pass the markdown source to the component\n        token.content = token.content\n          .replace(dragComponentRegex, (_, tag, space, idx) => {\n            const replacement = `<${tag} :markdownSource=\"${toMarkdownSource(token.map!, idx)}\"${space}`\n            replaceChildren(token, dragComponentRegex, replacement)\n            return replacement\n          })\n          .replace(dragDirectiveRegex, (_, value, idx) => {\n            const replacement = `v-drag${value ?? ''} :markdownSource=\"${toMarkdownSource(token.map!, idx)}\"`\n            replaceChildren(token, dragDirectiveRegex, replacement)\n            return replacement\n          })\n\n        visited.add(token)\n        return token\n      })\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/code-wrapper.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport { normalizeRangeStr } from './utils'\n\n// eslint-disable-next-line regexp/no-super-linear-backtracking\nexport const reCodeBlock = /^```([\\w'-]+)?(?:[ \\t]*|[ \\t][ \\w\\t'-]*)(?:\\[([^\\]]*)\\])?[ \\t]*(?:\\{([\\w*,|-]+)\\}[ \\t]*(\\{[^}]*\\})?([^\\r\\n]*))?\\r?\\n((?:(?!^```)[\\s\\S])*?)^```$/gm\n\n/**\n * Transform code block with wrapper\n */\nexport function transformCodeWrapper(ctx: MarkdownTransformContext) {\n  ctx.s.replace(\n    reCodeBlock,\n    (full, lang = '', title = '', rangeStr = '', options = '', attrs = '', code: string) => {\n      const ranges = normalizeRangeStr(rangeStr)\n      code = code.trimEnd()\n      options = options.trim() || '{}'\n      return `\\n<CodeBlockWrapper v-bind=\"${options}\" :title='${JSON.stringify(title)}' :ranges='${JSON.stringify(ranges)}'>\\n\\n\\`\\`\\`${lang}${title ? ` [${title}]` : ''}${attrs ? ` ${attrs.trim()}` : ''}\\n${code}\\n\\`\\`\\`\\n\\n</CodeBlockWrapper>`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/in-page-css.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport { getCodeBlocks, getCommentBlocks } from './utils'\n\n/**\n * Transform <style> in markdown to scoped style with page selector\n */\nexport function transformPageCSS(ctx: MarkdownTransformContext) {\n  const codeBlocks = getCodeBlocks(ctx.s.original)\n  const commentBlocks = getCommentBlocks(ctx.s.original)\n\n  ctx.s.replace(\n    /(\\n<style[^>]*>)([\\s\\S]+?)(<\\/style>)/g,\n    (full, start, css, end, index) => {\n      if (codeBlocks.isInsideCodeblocks(index))\n        return full\n      if (commentBlocks.isInsideCommentBlocks(index))\n        return ``\n      if (!start.includes('scoped'))\n        start = start.replace('<style', '<style scoped')\n      return `${start}\\n${css}${end}`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/index.ts",
    "content": "import type { MarkdownTransformer, ResolvedSlidevOptions } from '@slidev/types'\nimport setupTransformers from '../../setups/transformers'\nimport { transformCodeWrapper } from './code-wrapper'\nimport { transformPageCSS } from './in-page-css'\nimport { transformKaTexWrapper } from './katex-wrapper'\nimport { transformMagicMove } from './magic-move'\nimport { transformMermaid } from './mermaid'\nimport { transformMonaco } from './monaco'\nimport { transformPlantUml } from './plant-uml'\nimport { transformSlotSugar } from './slot-sugar'\nimport { transformSnippet } from './snippet'\n\nexport async function getMarkdownTransformers(options: ResolvedSlidevOptions): Promise<(false | MarkdownTransformer)[]> {\n  const extras = await setupTransformers(options.roots)\n  return [\n    ...extras.pre,\n\n    transformSnippet,\n    options.data.config.highlighter === 'shiki' && transformMagicMove,\n\n    ...extras.preCodeblock,\n\n    transformMermaid,\n    transformPlantUml,\n    options.data.features.monaco && transformMonaco,\n\n    ...extras.postCodeblock,\n\n    transformCodeWrapper,\n    options.data.features.katex && transformKaTexWrapper,\n    transformPageCSS,\n    transformSlotSugar,\n\n    ...extras.post,\n  ]\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/katex-wrapper.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\n\n/**\n * Wrapper KaTex syntax `$$...$$` for highlighting\n */\nexport function transformKaTexWrapper(ctx: MarkdownTransformContext) {\n  ctx.s.replace(\n    /^\\$\\$(?:\\s*\\{([\\w*,|-]+)\\}\\s*?(?:(\\{[^}]*\\})\\s*?)?)?\\n(\\S[\\s\\S]*?)^\\$\\$/gm,\n    (full, rangeStr: string = '', options = '', code: string) => {\n      const ranges = !rangeStr.trim() ? [] : rangeStr.trim().split(/\\|/g).map(i => i.trim())\n      code = code.trimEnd()\n      options = options.trim() || '{}'\n      return `<KaTexBlockWrapper v-bind=\"${options}\" :ranges='${JSON.stringify(ranges)}'>\\n\\n\\$\\$\\n${code}\\n\\$\\$\\n</KaTexBlockWrapper>\\n`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/magic-move.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport lz from 'lz-string'\nimport { toKeyedTokens } from 'shiki-magic-move/core'\nimport { reCodeBlock } from './code-wrapper'\nimport { normalizeRangeStr } from './utils'\n\n// eslint-disable-next-line regexp/no-super-linear-backtracking\nconst reMagicMoveBlock = /^````(?:md|markdown) magic-move(?: *\\[([^\\]]*)\\])?(?: *(\\{[^}]*\\}))? *([^\\n]*)\\n([\\s\\S]+?)^````\\s*?$/gm\n\nfunction parseLineNumbersOption(options: string) {\n  return /lines: *true/.test(options) ? true : /lines: *false/.test(options) ? false : undefined\n}\n\n/**\n * Transform magic-move code blocks\n */\nexport async function transformMagicMove(ctx: MarkdownTransformContext) {\n  const { codeToTokens } = ctx.options.utils.shiki\n  const replacements: [number, number, Promise<string>][] = []\n\n  ctx.s.replace(\n    reMagicMoveBlock,\n    (full, title = '', options = '{}', _attrs = '', body: string, start: number) => {\n      const end = start + full.length\n      replacements.push([start, end, worker()])\n      return ''\n      async function worker() {\n        const matches = Array.from(body.matchAll(reCodeBlock))\n\n        if (!matches.length)\n          throw new Error('Magic Move block must contain at least one code block')\n\n        const defaultLineNumbers = parseLineNumbersOption(options) ?? ctx.options.data.config.lineNumbers\n\n        const ranges = matches.map(i => normalizeRangeStr(i[3]))\n        const steps = await Promise.all(matches.map(async (i) => {\n          const lang = i[1]\n          const lineNumbers = parseLineNumbersOption(i[4]) ?? defaultLineNumbers\n          const code = i[6].trimEnd()\n          const options = {\n            ...ctx.options.utils.shikiOptions,\n            lang,\n          }\n          const { tokens, bg, fg, rootStyle, themeName } = await codeToTokens(code, options)\n          return {\n            ...toKeyedTokens(code, tokens, JSON.stringify([lang, 'themes' in options ? options.themes : options.theme]), lineNumbers),\n            bg,\n            fg,\n            rootStyle,\n            themeName,\n            lang,\n          }\n        }))\n        const compressed = lz.compressToBase64(JSON.stringify(steps))\n        return `<ShikiMagicMove v-bind=\"${options}\" steps-lz=\"${compressed}\" :title='${JSON.stringify(title)}' :step-ranges='${JSON.stringify(ranges)}' />`\n      }\n    },\n  )\n\n  for (const [start, end, content] of replacements) {\n    // magic-string internally uses `overwrite` instead of `update` in the `replace` method\n    ctx.s.overwrite(start, end, await content)\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/mermaid.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport lz from 'lz-string'\n\n/**\n * Transform Mermaid code blocks (render done on client side)\n */\nexport function transformMermaid(ctx: MarkdownTransformContext) {\n  ctx.s.replace(\n    /^```mermaid *(\\{[^\\n]*\\})?\\n([\\s\\S]+?)\\n```/gm,\n    (full, options = '', code = '') => {\n      code = code.trim()\n      options = options.trim() || '{}'\n      const encoded = lz.compressToBase64(code)\n      return `<Mermaid code-lz=\"${encoded}\" v-bind=\"${options}\" />`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/monaco.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport lz from 'lz-string'\n\nexport function transformMonaco(ctx: MarkdownTransformContext) {\n  const enabled = (ctx.options.data.config.monaco === true || ctx.options.data.config.monaco === ctx.options.mode)\n\n  if (!enabled) {\n    ctx.s.replace(/\\{monaco([\\w:,-]*)\\}/g, '')\n    return\n  }\n\n  // transform monaco\n  ctx.s.replace(\n    /^```(\\w+) *\\{monaco-diff\\} *(?:(\\{[^\\n]*\\}) *)?\\n([\\s\\S]+?)^~~~ *\\n([\\s\\S]+?)^```/gm,\n    (full, lang = 'ts', options = '{}', code: string, diff: string) => {\n      lang = lang.trim()\n      options = options.trim() || '{}'\n      const encoded = lz.compressToBase64(code)\n      const encodedDiff = lz.compressToBase64(diff)\n      return `<Monaco code-lz=\"${encoded}\" diff-lz=\"${encodedDiff}\" lang=\"${lang}\" v-bind=\"${options}\" />`\n    },\n  )\n  ctx.s.replace(\n    /^```(\\w+) *\\{monaco\\} *(?:(\\{[^\\n]*\\}) *)?\\n([\\s\\S]+?)^```/gm,\n    (full, lang = 'ts', options = '{}', code: string) => {\n      lang = lang.trim()\n      options = options.trim() || '{}'\n      const encoded = lz.compressToBase64(code)\n      return `<Monaco code-lz=\"${encoded}\" lang=\"${lang}\" v-bind=\"${options}\" />`\n    },\n  )\n  ctx.s.replace(\n    /^```(\\w+) *\\{monaco-run\\} *(?:(\\{[^\\n]*\\}) *)?\\n([\\s\\S]+?)^```/gm,\n    (full, lang = 'ts', options = '{}', code: string) => {\n      lang = lang.trim()\n      options = options.trim() || '{}'\n      const encoded = lz.compressToBase64(code)\n      return `<Monaco runnable code-lz=\"${encoded}\" lang=\"${lang}\" v-bind=\"${options}\" />`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/plant-uml.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport { encode as encodePlantUml } from 'plantuml-encoder'\n\nexport function transformPlantUml(ctx: MarkdownTransformContext) {\n  const server = ctx.options.data.config.plantUmlServer\n  ctx.s.replace(\n    /^```plantuml[^\\n{}]*(\\{[^}\\n]*\\})?\\n([\\s\\S]+?)\\n```/gm,\n    (full, options = '', content = '') => {\n      const code = encodePlantUml(content.trim())\n      options = options.trim() || '{}'\n      return `<PlantUml :code=\"'${code}'\" :server=\"'${server}'\" v-bind=\"${options}\" />`\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/slot-sugar.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport { getCodeBlocks, getCommentBlocks } from './utils'\n\nexport function transformSlotSugar(\n  ctx: MarkdownTransformContext,\n) {\n  const linesWithNewline = ctx.s.original.split(/(\\r?\\n)/g)\n  const codeBlocks = getCodeBlocks(ctx.s.original)\n  const commentBlocks = getCommentBlocks(ctx.s.original)\n\n  const lines: string[] = []\n  for (let i = 0; i < linesWithNewline.length; i += 2) {\n    const line = linesWithNewline[i]\n    const newline = linesWithNewline[i + 1] || ''\n    lines.push(line + newline)\n  }\n\n  let prevSlot = false\n\n  let offset = 0\n  lines.forEach((line) => {\n    const start = offset\n    offset += line.length\n    if (codeBlocks.isInsideCodeblocks(offset) || commentBlocks.isInsideCommentBlocks(offset))\n      return\n    const match = line.match(/^::\\s*([\\w.\\-:]+)\\s*::(\\s*)$/)\n    if (match) {\n      ctx.s.overwrite(start, offset - match[2].length, `${prevSlot ? '\\n\\n</template>\\n' : '\\n'}<template v-slot:${match[1]}=\"slotProps\">\\n`)\n      prevSlot = true\n    }\n  })\n\n  if (prevSlot)\n    ctx.s.append('\\n\\n</template>')\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/snippet.ts",
    "content": "// Ported from https://github.com/vuejs/vitepress/blob/main/src/node/markdown/plugins/snippet.ts\n\nimport type { MarkdownTransformContext } from '@slidev/types'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { slash } from '@antfu/utils'\nimport lz from 'lz-string'\nimport { monacoWriterWhitelist } from '../../vite/monacoWrite'\n\nfunction dedent(text: string): string {\n  const lines = text.split('\\n')\n\n  const minIndentLength = lines.reduce((acc, line) => {\n    for (let i = 0; i < line.length; i++) {\n      if (line[i] !== ' ' && line[i] !== '\\t')\n        return Math.min(i, acc)\n    }\n    return acc\n  }, Number.POSITIVE_INFINITY)\n\n  if (minIndentLength < Number.POSITIVE_INFINITY)\n    return lines.map(x => x.slice(minIndentLength)).join('\\n')\n\n  return text\n}\n\n/* eslint-disable regexp/no-super-linear-backtracking */\nconst markers = [\n  {\n    start: /^\\s*\\/\\/\\s*#?region\\b\\s*(.*?)\\s*$/,\n    end: /^\\s*\\/\\/\\s*#?endregion\\b\\s*(.*?)\\s*$/,\n  },\n  {\n    start: /^\\s*<!--\\s*#?region\\b\\s*(.*?)\\s*-->/,\n    end: /^\\s*<!--\\s*#?endregion\\b\\s*(.*?)\\s*-->/,\n  },\n  {\n    start: /^\\s*\\/\\*\\s*#region\\b\\s*(.*?)\\s*\\*\\//,\n    end: /^\\s*\\/\\*\\s*#endregion\\b\\s*(.*?)\\s*\\*\\//,\n  },\n  {\n    start: /^\\s*#[rR]egion\\b\\s*(.*?)\\s*$/,\n    end: /^\\s*#[eE]nd ?[rR]egion\\b\\s*(.*?)\\s*$/,\n  },\n  {\n    start: /^\\s*#\\s*#?region\\b\\s*(.*?)\\s*$/,\n    end: /^\\s*#\\s*#?endregion\\b\\s*(.*?)\\s*$/,\n  },\n  {\n    start: /^\\s*(?:--|::|@?REM)\\s*#region\\b\\s*(.*?)\\s*$/,\n    end: /^\\s*(?:--|::|@?REM)\\s*#endregion\\b\\s*(.*?)\\s*$/,\n  },\n  {\n    start: /^\\s*#pragma\\s+region\\b\\s*(.*?)\\s*$/,\n    end: /^\\s*#pragma\\s+endregion\\b\\s*(.*?)\\s*$/,\n  },\n  {\n    start: /^\\s*\\(\\*\\s*#region\\b\\s*(.*?)\\s*\\*\\)/,\n    end: /^\\s*\\(\\*\\s*#endregion\\b\\s*(.*?)\\s*\\*\\)/,\n  },\n]\n/* eslint-enable regexp/no-super-linear-backtracking */\n\nfunction findRegion(lines: Array<string>, regionName: string) {\n  let chosen: { re: (typeof markers)[number], start: number } | null = null\n  // find the regex pair for a start marker that matches the given region name\n  for (let i = 0; i < lines.length; i++) {\n    for (const re of markers) {\n      if (re.start.exec(lines[i])?.[1] === regionName) {\n        chosen = { re, start: i + 1 }\n        break\n      }\n    }\n    if (chosen)\n      break\n  }\n  if (!chosen)\n    return null\n\n  let counter = 1\n  // scan the rest of the lines to find the matching end marker, handling nested markers\n  for (let i = chosen.start; i < lines.length; i++) {\n    // check for an inner start marker for the same region\n    if (chosen.re.start.exec(lines[i])?.[1] === regionName) {\n      counter++\n      continue\n    }\n    // check for an end marker for the same region\n    const endRegion = chosen.re.end.exec(lines[i])?.[1]\n    // allow empty region name on the end marker as a fallback\n    if (endRegion === regionName || endRegion === '') {\n      if (--counter === 0) {\n        return {\n          ...chosen,\n          end: i,\n        }\n      }\n    }\n  }\n\n  return null\n}\n\nconst reMonacoWrite = /^\\{monaco-write\\}/\n\n/**\n * format: \">>> /path/to/file.extension#region language meta...\"\n *    where #region, language and meta are optional\n *    meta should starts with {\n *    lang can contain special characters like C++, C#, F#, etc.\n *    path can be relative to the current file or absolute\n *    file extension is optional\n *    path can contain spaces and dots\n *\n * captures: ['/path/to/file.extension', '#region', 'language', '{meta}']\n */\nexport function transformSnippet({ s, slide, options }: MarkdownTransformContext) {\n  const watchFiles = options.data.watchFiles\n  const dir = path.dirname(slide.source?.filepath ?? options.entry ?? options.userRoot)\n\n  s.replace(\n    // eslint-disable-next-line regexp/no-super-linear-backtracking\n    /^<<<[ \\t]*(\\S.*?)(#[\\w-]+)?[ \\t]*(?:[ \\t](\\S+?))?[ \\t]*(\\{.*)?$/gm,\n    (full, filepath = '', regionName = '', lang = '', meta = '') => {\n      const src = slash(\n        /^@\\//.test(filepath)\n          ? path.resolve(options.userRoot, filepath.slice(2))\n          : path.resolve(dir, filepath),\n      )\n\n      meta = meta.trim()\n      lang = lang.trim()\n      lang = lang || path.extname(filepath).slice(1)\n\n      const isAFile = fs.statSync(src).isFile()\n      if (!fs.existsSync(src) || !isAFile) {\n        throw new Error(isAFile\n          ? `Code snippet path not found: ${src}`\n          : `Invalid code snippet option`)\n      }\n\n      let content = fs.readFileSync(src, 'utf8')\n\n      if (regionName) {\n        const lines = content.split(/\\r?\\n/)\n        const region = findRegion(lines, regionName.slice(1))\n\n        if (region) {\n          content = dedent(\n            lines\n              .slice(region.start, region.end)\n              .filter(l => !(region.re.start.test(l) || region.re.end.test(l)))\n              .join('\\n'),\n          )\n        }\n      }\n\n      if (meta.match(reMonacoWrite)) {\n        monacoWriterWhitelist.add(filepath)\n        lang = lang.trim()\n        meta = meta.replace(reMonacoWrite, '').trim() || '{}'\n        const encoded = lz.compressToBase64(content)\n        return `<Monaco writable=${JSON.stringify(filepath)} code-lz=\"${encoded}\" lang=\"${lang}\" v-bind=\"${meta}\" />`\n      }\n      else {\n        watchFiles[src] ??= new Set()\n        watchFiles[src].add(slide.index)\n      }\n\n      return `\\`\\`\\`${lang} ${meta}\\n${content}\\n\\`\\`\\``\n    },\n  )\n}\n"
  },
  {
    "path": "packages/slidev/node/syntax/transform/utils.ts",
    "content": "export function normalizeRangeStr(rangeStr = '') {\n  return !rangeStr.trim() ? [] : rangeStr.trim().split(/\\|/g).map(i => i.trim())\n}\n\nexport function getCodeBlocks(md: string) {\n  const codeblocks = Array\n    // eslint-disable-next-line regexp/no-contradiction-with-assertion\n    .from(md.matchAll(/^```[\\s\\S]*?^```/gm))\n    .map((m) => {\n      const start = m.index!\n      const end = m.index! + m[0].length\n      const startLine = md.slice(0, start).match(/\\n/g)?.length || 0\n      const endLine = md.slice(0, end).match(/\\n/g)?.length || 0\n      return [start, end, startLine, endLine]\n    })\n\n  return {\n    codeblocks,\n    isInsideCodeblocks(idx: number) {\n      return codeblocks.some(([s, e]) => s <= idx && idx <= e)\n    },\n    isLineInsideCodeblocks(line: number) {\n      return codeblocks.some(([, , s, e]) => s <= line && line <= e)\n    },\n  }\n}\n\nexport function getCommentBlocks(md: string) {\n  const commentBlocks = Array\n    .from(md.matchAll(/<!--[\\s\\S]*?-->/g))\n    .map((m) => {\n      const start = m.index!\n      const end = m.index! + m[0].length\n      const startLine = md.slice(0, start).match(/\\n/g)?.length || 0\n      const endLine = md.slice(0, end).match(/\\n/g)?.length || 0\n      return [start, end, startLine, endLine]\n    })\n\n  return {\n    commentBlocks,\n    isInsideCommentBlocks(idx: number) {\n      return commentBlocks.some(([s, e]) => s <= idx && idx <= e)\n    },\n    isLineInsideCommentBlocks(line: number) {\n      return commentBlocks.some(([, , s, e]) => s <= line && line <= e)\n    },\n  }\n}\n\n/**\n * Escape `{{` in code block to prevent Vue interpret it, #99, #1316\n */\nexport function escapeVueInCode(md: string) {\n  return md.replace(/\\{\\{/g, '&lbrace;&lbrace;')\n}\n"
  },
  {
    "path": "packages/slidev/node/utils.ts",
    "content": "import type { ResolvedFontOptions, SourceSlideInfo } from '@slidev/types'\nimport type MarkdownExit from 'markdown-exit'\nimport type { Connect, GeneralImportGlobOptions } from 'vite'\nimport { relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { slash } from '@antfu/utils'\nimport { createJiti } from 'jiti'\nimport YAML from 'yaml'\n\ntype Token = ReturnType<MarkdownExit['parseInline']>[number]\n\ntype Jiti = ReturnType<typeof createJiti>\nlet jiti: Jiti | undefined\nexport function loadModule<T = unknown>(absolutePath: string): Promise<T> {\n  jiti ??= createJiti(fileURLToPath(import.meta.url), {\n    // Allows changes to take effect\n    moduleCache: false,\n  })\n  return jiti.import(absolutePath) as Promise<T>\n}\n\nexport function stringifyMarkdownTokens(tokens: Token[]) {\n  return tokens.map(token => token.children\n    ?.filter(t => ['text', 'code_inline'].includes(t.type) && !t.content.match(/^\\s*$/))\n    .map(t => t.content.trim())\n    .join(' '))\n    .filter(Boolean)\n    .join(' ')\n}\n\nexport function generateFontParams(options: ResolvedFontOptions) {\n  const weights = options.weights\n    .flatMap(i => options.italic ? [`0,${i}`, `1,${i}`] : [`${i}`])\n    .sort()\n    .join(';')\n  const fontParams = options.webfonts\n    .map(i => `family=${i.replace(/^(['\"])(.*)\\1$/, '$1').replace(/\\s+/g, '+')}:${options.italic ? 'ital,' : ''}wght@${weights}`)\n    .join('&')\n  return fontParams\n}\n\nexport function generateGoogleFontsUrl(options: ResolvedFontOptions) {\n  return `https://fonts.googleapis.com/css2?${generateFontParams(options)}&display=swap`\n}\n\nexport function generateCoollabsFontsUrl(options: ResolvedFontOptions) {\n  return `https://api.fonts.coollabs.io/fonts?${generateFontParams(options)}&display=swap`\n}\n\n/**\n * Update frontmatter patch and preserve the comments\n */\nexport function updateFrontmatterPatch(source: SourceSlideInfo, frontmatter: Record<string, any>) {\n  let doc = source.frontmatterDoc\n  if (!doc) {\n    source.frontmatterStyle = 'frontmatter'\n    source.frontmatterDoc = doc = new YAML.Document({})\n  }\n  for (const [key, value] of Object.entries(frontmatter)) {\n    source.frontmatter[key] = value\n    if (value == null) {\n      doc.delete(key)\n    }\n    else {\n      const valueNode = doc.createNode(value)\n      let found = false\n      YAML.visit(doc.contents, {\n        Pair(_key, node, path) {\n          if (path.length === 1 && YAML.isScalar(node.key) && node.key.value === key) {\n            node.value = valueNode\n            found = true\n            return YAML.visit.BREAK\n          }\n        },\n      })\n      if (!found) {\n        if (!YAML.isMap(doc.contents))\n          doc.contents = doc.createNode({})\n        doc.contents.add(\n          doc.createPair(key, valueNode),\n        )\n      }\n    }\n  }\n}\n\nexport function getBodyJson(req: Connect.IncomingMessage) {\n  return new Promise<any>((resolve, reject) => {\n    let body = ''\n    req.on('data', chunk => body += chunk)\n    req.on('error', reject)\n    req.on('end', () => {\n      try {\n        resolve(JSON.parse(body) || {})\n      }\n      catch (e) {\n        reject(e)\n      }\n    })\n  })\n}\n\nexport function makeAbsoluteImportGlob(\n  userRoot: string,\n  globs: string[],\n  options: Partial<GeneralImportGlobOptions> = {},\n) {\n  // Vite's import.meta.glob only supports relative paths\n  const relativeGlobs = globs.map(glob => `./${slash(relative(userRoot, glob))}`)\n  const opts: GeneralImportGlobOptions = {\n    eager: true,\n    exhaustive: true,\n    base: '/',\n    ...options,\n  }\n  return `import.meta.glob(${JSON.stringify(relativeGlobs)}, ${JSON.stringify(opts)})`\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/configs.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { isString } from '@antfu/utils'\nimport { getSlideTitle, sharedMd } from '../commands/shared'\n\nexport const templateConfigs: VirtualModuleTemplate = {\n  id: '/@slidev/configs',\n  getContent({ data, remote }) {\n    const config = {\n      ...data.config,\n      remote,\n      slidesTitle: getSlideTitle(data),\n    }\n\n    if (isString(config.info))\n      config.info = sharedMd.render(config.info)\n\n    return `export default ${JSON.stringify(config)}`\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/deprecated.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\n\n/**\n * Kept for backward compatibility, use #slidev/slides instead\n *\n * @deprecated\n */\nexport const templateLegacyRoutes: VirtualModuleTemplate = {\n  id: '/@slidev/routes',\n  getContent() {\n    return [\n      `export { slides } from '#slidev/slides'`,\n      `console.warn('[slidev] #slidev/routes is deprecated, use #slidev/slides instead')`,\n    ].join('\\n')\n  },\n}\n\n/**\n * Kept for backward compatibility, use #slidev/title-renderer instead\n *\n * @deprecated\n */\nexport const templateLegacyTitles: VirtualModuleTemplate = {\n  id: '/@slidev/titles.md',\n  getContent() {\n    return `\n<script setup lang=\"ts\">\nimport TitleRenderer from '#slidev/title-renderer'\ndefineProps<{ no: number | string }>()\nconsole.warn('/@slidev/titles.md is deprecated, import from #slidev/title-renderer instead')\n</script>\n\n<TitleRenderer :no=\"no\" />\n`\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/global-layers.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { join } from 'node:path'\nimport { makeAbsoluteImportGlob } from '../utils'\n\nexport const templateGlobalLayers: VirtualModuleTemplate = {\n  id: `/@slidev/global-layers`,\n  getContent({ userRoot, roots }) {\n    function* getComponent(name: string, names: string[]) {\n      yield `const ${name}Components = [\\n`\n      for (const root of roots) {\n        const globs = names.map(name => join(root, `${name}.{ts,js,vue}`))\n        yield '  Object.values('\n        yield makeAbsoluteImportGlob(userRoot, globs, { import: 'default' })\n        yield ')[0],\\n'\n      }\n      yield `].filter(Boolean)\\n`\n      yield `export const ${name} = { render: () => ${name}Components.map(comp => h(comp)) }\\n\\n`\n    }\n\n    return [\n      `import { h } from 'vue'\\n\\n`,\n      ...getComponent('GlobalTop', ['global', 'global-top', 'GlobalTop']),\n      ...getComponent('GlobalBottom', ['global-bottom', 'GlobalBottom']),\n      ...getComponent('SlideTop', ['slide-top', 'SlideTop']),\n      ...getComponent('SlideBottom', ['slide-bottom', 'SlideBottom']),\n    ].join('')\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/index.ts",
    "content": "import { templateConfigs } from './configs'\nimport { templateLegacyRoutes, templateLegacyTitles } from './deprecated'\nimport { templateGlobalLayers } from './global-layers'\nimport { templateLayouts } from './layouts'\nimport { templateMonacoRunDeps } from './monaco-deps'\nimport { templateMonacoTypes } from './monaco-types'\nimport { templateNavControls } from './nav-controls'\nimport { templateSetups } from './setups'\nimport { templateSlides } from './slides'\nimport { templateStyle } from './styles'\nimport { templateTitleRenderer, templateTitleRendererMd } from './titles'\n\nexport const templates = [\n  templateMonacoTypes,\n  templateMonacoRunDeps,\n  templateConfigs,\n  templateStyle,\n  templateGlobalLayers,\n  templateNavControls,\n  templateSlides,\n  templateLayouts,\n  templateTitleRenderer,\n  templateTitleRendererMd,\n  ...templateSetups,\n\n  // Deprecated\n  templateLegacyRoutes,\n  templateLegacyTitles,\n]\n"
  },
  {
    "path": "packages/slidev/node/virtual/layouts.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { objectMap } from '@antfu/utils'\nimport { toAtFS } from '../resolver'\n\nexport const templateLayouts: VirtualModuleTemplate = {\n  id: '/@slidev/layouts',\n  async getContent({ utils }) {\n    const imports: string[] = []\n    const layouts = objectMap(\n      await utils.getLayouts(),\n      (k, v) => {\n        imports.push(`import __layout_${k} from \"${toAtFS(v)}\"`)\n        return [k, `__layout_${k}`]\n      },\n    )\n\n    return [\n      imports.join('\\n'),\n      `export default {\\n${Object.entries(layouts).map(([k, v]) => `\"${k}\": ${v}`).join(',\\n')}\\n}`,\n    ].join('\\n\\n')\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/monaco-deps.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { resolve } from 'node:path'\nimport { uniq } from '@antfu/utils'\n\nexport const templateMonacoRunDeps: VirtualModuleTemplate = {\n  id: '/@slidev/monaco-run-deps',\n  async getContent({ userRoot, data }) {\n    if (!data.features.monaco)\n      return ''\n    const deps = uniq([\n      ...data.features.monaco.deps,\n      ...(data.config.monacoTypesAdditionalPackages || []),\n      ...(data.config.monacoRunAdditionalDeps || []),\n    ])\n    const importerPath = resolve(userRoot, './snippets/__importer__.ts')\n    let result = ''\n    for (let i = 0; i < deps.length; i++) {\n      const specifier = deps[i]\n      const resolved = await this.resolve(specifier, importerPath)\n      if (!resolved)\n        continue\n      result += `import * as vendored${i} from ${JSON.stringify(resolved.id)}\\n`\n    }\n    result += 'export default {\\n'\n    for (let i = 0; i < deps.length; i++)\n      result += `${JSON.stringify(deps[i])}: vendored${i},\\n`\n\n    result += '}\\n'\n    return result\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/monaco-types.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { builtinModules } from 'node:module'\n\nimport { join, resolve } from 'node:path'\nimport { uniq } from '@antfu/utils'\nimport fg from 'fast-glob'\nimport { toAtFS } from '../resolver'\n\nexport const templateMonacoTypes: VirtualModuleTemplate = {\n  id: '/@slidev/monaco-types',\n  getContent: async ({ userRoot, data, utils }) => {\n    if (!data.features.monaco)\n      return ''\n    const typesRoot = join(userRoot, 'snippets')\n    const files = await fg(['**/*.ts', '**/*.mts', '**/*.cts'], { cwd: typesRoot })\n    let result = 'import { addFile } from \"@slidev/client/setup/monaco.ts\"\\n'\n\n    // User snippets\n    for (const file of files) {\n      const url = `${toAtFS(resolve(typesRoot, file))}?monaco-types&raw`\n      result += `addFile(() => import(${JSON.stringify(url)}), ${JSON.stringify(file)})\\n`\n    }\n\n    // Copied from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/ata/src/edgeCases.ts\n    // Converts some of the known global imports to node so that we grab the right info\n    function mapModuleNameToModule(moduleSpecifier: string) {\n      if (moduleSpecifier.startsWith('node:'))\n        return 'node'\n      if (builtinModules.includes(moduleSpecifier))\n        return 'node'\n      const mainPackageName = moduleSpecifier.split('/')[0]\n      if (builtinModules.includes(mainPackageName) && !mainPackageName.startsWith('@'))\n        return 'node'\n\n      // strip module filepath e.g. lodash/identity => lodash\n      const [a = '', b = ''] = moduleSpecifier.split('/')\n      const moduleName = a.startsWith('@') ? `${a}/${b}` : a\n\n      return moduleName\n    }\n\n    // Dependencies\n    let deps = [...data.config.monacoTypesAdditionalPackages]\n    if (data.config.monacoTypesSource === 'local')\n      deps.push(...data.features.monaco.types)\n\n    deps = uniq(deps.map((specifier) => {\n      if (specifier[0] === '.')\n        return ''\n      return mapModuleNameToModule(specifier)\n    }).filter(Boolean))\n    deps = deps.filter(pkg => !utils.isMonacoTypesIgnored(pkg))\n\n    for (const pkg of deps) {\n      result += `import(${JSON.stringify(`/@slidev-monaco-types/resolve?${new URLSearchParams({ pkg })}`)})\\n`\n    }\n\n    return result\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/nav-controls.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { toAtFS } from '../resolver'\n\nexport const templateNavControls: VirtualModuleTemplate = {\n  id: '/@slidev/custom-nav-controls',\n  getContent({ roots }) {\n    const components = roots\n      .flatMap((root) => {\n        return [\n          join(root, 'custom-nav-controls.vue'),\n          join(root, 'CustomNavControls.vue'),\n        ]\n      })\n      .filter(i => existsSync(i))\n\n    const imports = components.map((i, idx) => `import __n${idx} from '${toAtFS(i)}'`).join('\\n')\n    const render = components.map((i, idx) => `h(__n${idx})`).join(',')\n\n    return `${imports}\nimport { h } from 'vue'\nexport default {\n  render: () => [${render}],\n}`\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/setups.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { join } from 'node:path'\nimport { makeAbsoluteImportGlob } from '../utils'\n\nfunction createSetupTemplate(name: string): VirtualModuleTemplate {\n  return {\n    id: `/@slidev/setups/${name}`,\n    getContent({ userRoot, roots }) {\n      const globs = roots.map((root) => {\n        const glob = join(root, `setup/${name}.{ts,js,mts,mjs}`)\n        return `Object.values(${makeAbsoluteImportGlob(userRoot, [glob], { import: 'default' })})[0]`\n      })\n      return `export default [${globs.join(', ')}].filter(Boolean)`\n    },\n  }\n}\n\n// setups\nconst setupModules = ['shiki', 'code-runners', 'monaco', 'mermaid', 'mermaid-renderer', 'main', 'root', 'routes', 'shortcuts', 'context-menu']\n\nexport const templateSetups = setupModules.map(createSetupTemplate)\n"
  },
  {
    "path": "packages/slidev/node/virtual/slides.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\n\nexport const VIRTUAL_SLIDE_PREFIX = '/@slidev/slides/'\n\nexport const templateSlides: VirtualModuleTemplate = {\n  id: '/@slidev/slides',\n  async getContent({ data, utils }) {\n    const layouts = await utils.getLayouts()\n    const statements = [\n      `import { defineAsyncComponent, shallowRef } from 'vue'`,\n      `import SlideError from '${layouts.error}'`,\n      `import SlideLoading from '@slidev/client/internals/SlideLoading.vue'`,\n      `const componentsCache = new Array(${data.slides.length})`,\n      `const getAsyncComponent = (idx, loader) => defineAsyncComponent({`,\n      `  loader,`,\n      `  delay: 300,`,\n      `  loadingComponent: SlideLoading,`,\n      `  errorComponent: SlideError,`,\n      `  onError: e => console.error('Failed to load slide ' + (idx + 1), e) `,\n      `})`,\n    ]\n    const slides = data.slides\n      .map((_, idx) => {\n        const no = idx + 1\n        statements.push(\n          `import { meta as f${no} } from '${VIRTUAL_SLIDE_PREFIX}${no}/frontmatter'`,\n          // For some unknown reason, import error won't be caught by the error component. Catch it here.\n          `const load${no} = async () => {`,\n          `  try { return componentsCache[${idx}] ??= await import('${VIRTUAL_SLIDE_PREFIX}${no}/md') }`,\n          `  catch (e) { console.error('slide failed to load', e); return SlideError }`,\n          `}`,\n        )\n        return `{ no: ${no}, meta: f${no}, load: load${no}, component: getAsyncComponent(${idx}, load${no}) }`\n      })\n    return [\n      ...statements,\n      `const data = [\\n${slides.join(',\\n')}\\n]`,\n      `if (import.meta.hot) {`,\n      `  import.meta.hot.data.slides ??= shallowRef()`,\n      `  import.meta.hot.data.slides.value = data`,\n      `  import.meta.hot.dispose(() => componentsCache.length = 0)`,\n      `  import.meta.hot.accept()`,\n      `}`,\n      `export const slides = import.meta.hot ? import.meta.hot.data.slides : shallowRef(data)`,\n    ].join('\\n')\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/styles.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\nimport { join } from 'node:path'\nimport { resolveImportUrl, toAtFS } from '../resolver'\nimport { makeAbsoluteImportGlob } from '../utils'\n\nexport const templateStyle: VirtualModuleTemplate = {\n  id: '/@slidev/styles',\n  async getContent({ data, clientRoot, userRoot, roots }) {\n    function resolveUrlOfClient(name: string) {\n      return toAtFS(join(clientRoot, name))\n    }\n\n    const imports: string[] = [\n      'styles/vars.css',\n      'styles/index.css',\n      'styles/code.css',\n      'styles/katex.css',\n      'styles/transitions.css',\n    ].map(path => makeAbsoluteImportGlob(userRoot, [join(clientRoot, path)]))\n\n    for (const root of roots) {\n      imports.push(makeAbsoluteImportGlob(userRoot, [\n        join(root, 'styles/index.{ts,js,css}'),\n        join(root, 'styles.{ts,js,css}'),\n        join(root, 'style.{ts,js,css}'),\n      ]))\n    }\n\n    if (data.features.katex)\n      imports.push(`import \"${await resolveImportUrl('katex/dist/katex.min.css')}\"`)\n\n    if (data.config.highlighter === 'shiki') {\n      imports.push(\n        `import \"${await resolveImportUrl('@shikijs/vitepress-twoslash/style.css')}\"`,\n        `import \"${resolveUrlOfClient('styles/shiki-twoslash.css')}\"`,\n        `import \"${await resolveImportUrl('shiki-magic-move/style.css')}\"`,\n      )\n    }\n\n    imports.unshift(\n      `import \"${await resolveImportUrl('@unocss/reset/tailwind.css')}\"`,\n      'import \"uno:preflights.css\"',\n      'import \"uno:typography.css\"',\n      'import \"uno:shortcuts.css\"',\n    )\n    imports.push('import \"uno.css\"')\n\n    return imports.join('\\n')\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/titles.ts",
    "content": "import type { VirtualModuleTemplate } from './types'\n\nexport const templateTitleRendererMd: VirtualModuleTemplate = {\n  id: '/@slidev/title-renderer.md',\n  getContent({ data }) {\n    const lines = data.slides\n      .map(({ title }, i) => `<template ${i === 0 ? 'v-if' : 'v-else-if'}=\"no === ${i + 1}\">\\n\\n${title}\\n\\n</template>`)\n\n    lines.push(\n      `<script setup lang=\"ts\">`,\n      `import { useSlideContext } from '@slidev/client/context.ts'`,\n      `import { computed } from 'vue'`,\n      `const props = defineProps<{ no?: number | string }>()`,\n      `const { $page } = useSlideContext()`,\n      `const no = computed(() => +(props.no ?? $page.value))`,\n      `</script>`,\n    )\n\n    return lines.join('\\n')\n  },\n}\n\nexport const templateTitleRenderer: VirtualModuleTemplate = {\n  id: '/@slidev/title-renderer',\n  async getContent() {\n    return 'export { default } from \"/@slidev/title-renderer.md\"'\n  },\n}\n"
  },
  {
    "path": "packages/slidev/node/virtual/types.ts",
    "content": "import type { Awaitable } from '@antfu/utils'\nimport type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { PluginContext } from 'rollup'\n\nexport interface VirtualModuleTemplate {\n  id: string\n  getContent: (this: PluginContext, options: ResolvedSlidevOptions) => Awaitable<string>\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/common.ts",
    "content": "export const regexSlideReqPath = /^\\/__slidev\\/slides\\/(\\d+)\\.json$/\nexport const regexSlideFacadeId = /^\\/@slidev\\/slides\\/(\\d+)\\/(md|frontmatter)($|\\?)/\nexport const regexSlideSourceId = /__slidev_(\\d+)\\.(md|frontmatter)$/\n\nexport const templateInjectionMarker = '/* @slidev-injection */'\nexport const templateImportContextUtils = `import { useSlideContext as _useSlideContext, frontmatterToProps as _frontmatterToProps } from \"@slidev/client/context.ts\"`\nexport const templateInitContext = `const { $slidev, $nav, $clicksContext, $clicks, $page, $renderContext, $frontmatter } = _useSlideContext()`\n"
  },
  {
    "path": "packages/slidev/node/vite/compilerFlagsVue.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport { objectEntries } from '@antfu/utils'\n\n/**\n * Replace compiler flags like `__DEV__` in Vue SFC\n */\nexport function createVueCompilerFlagsPlugin(\n  options: ResolvedSlidevOptions,\n): Plugin {\n  const define = objectEntries(options.utils.define)\n  return {\n    name: 'slidev:flags',\n    enforce: 'pre',\n    transform: {\n      // TODO: static filter\n      handler(code, id) {\n        if (!id.match(/\\.vue($|\\?)/) && !id.includes('?vue&'))\n          return\n        const original = code\n        define.forEach(([from, to]) => {\n          code = code.replaceAll(from, to)\n        })\n        if (original !== code)\n          return code\n      },\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/components.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport { join } from 'node:path'\nimport IconsResolver from 'unplugin-icons/resolver'\nimport Components from 'unplugin-vue-components/vite'\n\nexport function createComponentsPlugin(\n  { clientRoot, roots }: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  return Components({\n    extensions: ['vue', 'md', 'js', 'ts', 'jsx', 'tsx'],\n\n    dirs: [\n      join(clientRoot, 'builtin'),\n      ...roots.map(i => join(i, 'components')),\n    ],\n    globsExclude: [],\n\n    include: [/\\.vue$/, /\\.vue\\?vue/, /\\.vue\\?v=/, /\\.md$/, /\\.md\\?vue/],\n    exclude: [],\n\n    resolvers: [\n      IconsResolver({\n        prefix: '',\n        customCollections: Object.keys(pluginOptions.icons?.customCollections || []),\n      }),\n    ],\n\n    dts: false,\n\n    ...pluginOptions.components,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/contextInjection.ts",
    "content": "import type { Plugin } from 'vite'\nimport { templateImportContextUtils, templateInitContext, templateInjectionMarker } from './common'\n\n/**\n * Inject `$slidev` into the script block of a Vue component\n */\nexport function createContextInjectionPlugin(): Plugin {\n  return {\n    name: 'slidev:context-injection',\n    transform: {\n      // TODO: static filter\n      async handler(code, id) {\n        if (!id.endsWith('.vue') || id.includes('/@slidev/client/') || id.includes('/packages/client/'))\n          return\n        if (code.includes(templateInjectionMarker) || code.includes('useSlideContext()'))\n          return code // Assume that the context is already imported and used\n        const imports = [\n          templateImportContextUtils,\n          templateInitContext,\n          templateInjectionMarker,\n        ]\n\n        // Find all <script> blocks\n        const matchScripts = [...code.matchAll(/<script([^>]*)>/g)]\n        // Find the <script ... setup> block\n        const setupScriptMatch = [...code.matchAll(/<script([^>]*)setup([^>]*)>/g)].at(0)\n        if (setupScriptMatch) {\n        // Only inject into the <script setup> block\n          const setupTag = setupScriptMatch[0]\n          const setupTagIndex = setupScriptMatch.index || 0\n          const setupTagEnd = setupTagIndex + setupTag.length\n          // Insert imports right after the <script setup ...> tag\n          return `${code.slice(0, setupTagEnd)}\\n${imports.join('\\n')}\\n${code.slice(setupTagEnd)}`\n        }\n        else if (!setupScriptMatch && matchScripts.length === 1) {\n        // not a setup script\n          const matchExport = code.match(/export\\s+default\\s+\\{/)\n          if (matchExport) {\n          // script exports a component\n            const exportIndex = (matchExport.index || 0) + matchExport[0].length\n            let component = code.slice(exportIndex)\n            component = component.slice(0, component.indexOf('</script>'))\n\n            const scriptIndex = (matchScripts[0].index || 0) + matchScripts[0][0].length\n            const provideImport = '\\nimport { injectionSlidevContext } from \"@slidev/client/constants.ts\"\\n'\n            code = `${code.slice(0, scriptIndex)}${provideImport}${code.slice(scriptIndex)}`\n\n            let injectIndex = exportIndex + provideImport.length\n            let injectObject = '$slidev: { from: injectionSlidevContext },'\n            const matchInject = component.match(/.*inject\\s*:\\s*([[{])/)\n            if (matchInject) {\n            // component has a inject option\n              injectIndex += (matchInject.index || 0) + matchInject[0].length\n              if (matchInject[1] === '[') {\n              // inject option in array\n                let injects = component.slice((matchInject.index || 0) + matchInject[0].length)\n                const injectEndIndex = injects.indexOf(']')\n                injects = injects.slice(0, injectEndIndex)\n                injectObject += injects.split(',').map(inject => `${inject}: {from: ${inject}}`).join(',')\n                return `${code.slice(0, injectIndex - 1)}{\\n${injectObject}\\n}${code.slice(injectIndex + injectEndIndex + 1)}`\n              }\n              else {\n              // inject option in object\n                return `${code.slice(0, injectIndex)}\\n${injectObject}\\n${code.slice(injectIndex)}`\n              }\n            }\n            // add inject option\n            return `${code.slice(0, injectIndex)}\\ninject: { ${injectObject} },\\n${code.slice(injectIndex)}`\n          }\n        }\n        // no setup script and not a vue component\n        return `<script setup>\\n${imports.join('\\n')}\\n</script>\\n${code}`\n      },\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/extendConfig.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { Plugin, UserConfig } from 'vite'\nimport { join } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport { slash, uniq } from '@antfu/utils'\nimport { createResolve } from 'mlly'\nimport { mergeConfig } from 'vite'\nimport { isInstalledGlobally, resolveImportPath, toAtFS } from '../resolver'\n\nconst INCLUDE_GLOBAL = [\n  '@typescript/ata',\n  'file-saver',\n  'lz-string',\n  'recordrtc',\n  'typescript',\n  'yaml',\n  'pptxgenjs',\n  'ansis',\n]\n\nconst INCLUDE_LOCAL = INCLUDE_GLOBAL.map(i => `@slidev/cli > @slidev/client > ${i}`)\n\n// @keep-sorted\nconst EXCLUDE_GLOBAL = [\n  '@antfu/utils',\n  '@shikijs/monaco',\n  '@shikijs/vitepress-twoslash/client',\n  '@slidev/client',\n  '@slidev/client/constants',\n  '@slidev/client/context',\n  '@slidev/client/logic/dark',\n  '@slidev/parser',\n  '@slidev/parser/core',\n  '@slidev/rough-notation',\n  '@slidev/types',\n  '@unhead/vue',\n  '@unocss/reset',\n  '@vueuse/core',\n  '@vueuse/math',\n  '@vueuse/motion',\n  '@vueuse/shared',\n  'drauu',\n  'floating-vue',\n  'fuse.js',\n  'mermaid',\n  'monaco-editor',\n  'shiki-magic-move/vue',\n  'shiki',\n  'shiki/core',\n  'vue-demi',\n  'vue-router',\n  'vue',\n]\n\nconst EXCLUDE_LOCAL = EXCLUDE_GLOBAL\n\nconst ASYNC_MODULES = [\n  'file-saver',\n  'vue',\n  '@vue',\n]\n\nexport function createConfigPlugin(options: ResolvedSlidevOptions): Plugin {\n  const resolveClientDep = createResolve({\n    // Same as Vite's default resolve conditions\n    conditions: ['import', 'module', 'browser', 'default', options.mode === 'build' ? 'production' : 'development'],\n    url: pathToFileURL(options.clientRoot),\n  })\n  return {\n    name: 'slidev:config',\n    async config(config) {\n      const injection: UserConfig = {\n        define: options.utils.define,\n        resolve: {\n          alias: [\n            {\n              find: /^@slidev\\/client$/,\n              replacement: `${toAtFS(options.clientRoot)}/index.ts`,\n            },\n            {\n              find: /^@slidev\\/client\\/(.*)/,\n              replacement: `${toAtFS(options.clientRoot)}/$1`,\n            },\n            {\n              find: /^#slidev\\/(.*)/,\n              replacement: '/@slidev/$1',\n            },\n            {\n              find: 'vue',\n              replacement: await resolveImportPath('vue/dist/vue.esm-bundler.js', true),\n            },\n            ...(isInstalledGlobally.value\n              ? await Promise.all(INCLUDE_GLOBAL.map(async dep => ({\n                  find: dep,\n                  replacement: fileURLToPath(await resolveClientDep(dep)),\n                })))\n              : []\n            ),\n          ],\n          dedupe: ['vue'],\n        },\n        optimizeDeps: isInstalledGlobally.value\n          ? {\n              exclude: EXCLUDE_GLOBAL,\n              include: INCLUDE_GLOBAL,\n            }\n          : {\n              // We need to specify the full deps path for non-hoisted modules\n              exclude: EXCLUDE_LOCAL,\n              include: INCLUDE_LOCAL,\n            },\n        css: {\n          postcss: {\n            plugins: [\n              await import('postcss-nested').then(r => (r.default || r)()) as any,\n            ],\n          },\n        },\n        server: {\n          fs: {\n            strict: true,\n            allow: uniq([\n              options.userWorkspaceRoot,\n              options.clientRoot,\n              // Special case for PNPM global installation\n              isInstalledGlobally.value\n                ? slash(options.cliRoot).replace(/\\/\\.pnpm\\/.*$/gi, '')\n                : options.cliRoot,\n              ...options.roots,\n            ]),\n          },\n        },\n        publicDir: join(options.userRoot, 'public'),\n        build: {\n          rollupOptions: {\n            output: {\n              chunkFileNames(chunkInfo) {\n                const DEFAULT = 'assets/[name]-[hash].js'\n\n                // Already handled in manualChunks\n                if (chunkInfo.name.includes('/'))\n                  return DEFAULT\n\n                // Over 60% of the chunk is slidev client code, we put it into slidev chunk\n                if (chunkInfo.moduleIds.filter(i => isSlidevClient(i)).length > chunkInfo.moduleIds.length * 0.6)\n                  return 'assets/slidev/[name]-[hash].js'\n\n                // Monaco Editor\n                if (chunkInfo.moduleIds.filter(i => i.match(/\\/monaco-editor(-core)?\\//)).length > chunkInfo.moduleIds.length * 0.6)\n                  return 'assets/monaco/[name]-[hash].js'\n\n                return DEFAULT\n              },\n              manualChunks(id) {\n                if (id.startsWith('/@slidev-monaco-types/') || id.includes('/@slidev/monaco-types') || id.endsWith('?monaco-types&raw'))\n                  return 'monaco/bundled-types'\n                if (id.includes('/shiki/') || id.includes('/@shikijs/'))\n                  return `modules/shiki`\n                if (id.startsWith('~icons/'))\n                  return 'modules/unplugin-icons'\n                // It seems that moving slides out will breaks the production build\n                // Would need to find a better way to handle this\n                // const slideMatch = id.match(/\\/@slidev\\/slides\\/(\\d+)/)\n                // if (slideMatch && !id.includes('.frontmatter'))\n                //   return `slides/${slideMatch[1]}`\n\n                const matchedAsyncModule = ASYNC_MODULES.find(i => id.includes(`/node_modules/${i}`))\n                if (matchedAsyncModule)\n                  return `modules/${matchedAsyncModule.replace('@', '').replace('/', '-')}`\n              },\n            },\n          },\n        },\n        cacheDir: isInstalledGlobally.value ? join(options.cliRoot, 'node_modules/.vite') : undefined,\n      }\n\n      function isSlidevClient(id: string) {\n        return id.includes('/@slidev/') || id.includes('/slidev/packages/client/') || id.includes('/@vueuse/')\n      }\n\n      // function getNodeModuleName(path: string) {\n      //   const nodeModuelsMatch = [...path.matchAll(/node_modules\\/(@[^/]+\\/[^/]+|[^/]+)\\//g)]\n      //   if (nodeModuelsMatch.length)\n      //     return nodeModuelsMatch[nodeModuelsMatch.length - 1][1]\n      // }\n\n      return mergeConfig(injection, config)\n    },\n    configureServer(server) {\n      // serve our index.html after vite history fallback\n      return () => {\n        server.middlewares.use(async (req, res, next) => {\n          if (req.url === '/index.html') {\n            const headers = server.config.server.headers ?? {}\n\n            for (const header in headers) {\n              res.setHeader(header, headers[header]!)\n            }\n\n            res.setHeader('Content-Type', 'text/html')\n            res.statusCode = 200\n            res.end(options.utils.indexHtml)\n            return\n          }\n          next()\n        })\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/hmrPatch.ts",
    "content": "import type { Plugin } from 'vite'\nimport { regexSlideSourceId } from './common'\n\n/**\n * force reload slide component to ensure v-click resolves correctly\n */\nexport function createHmrPatchPlugin(): Plugin {\n  return {\n    name: 'slidev:hmr-patch',\n    transform: {\n      filter: {\n        id: {\n          include: regexSlideSourceId,\n        },\n      },\n      handler(code, id) {\n        if (!id.match(regexSlideSourceId))\n          return\n        return code.replace('if (_rerender_only)', 'if (false)')\n      },\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/icons.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport Icons from 'unplugin-icons/vite'\n\nexport function createIconsPlugin(\n  options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  return Icons({\n    defaultClass: 'slidev-icon',\n    collectionsNodeResolvePath: options.utils.iconsResolvePath,\n    ...pluginOptions.icons,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/index.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions, SlidevServerOptions } from '@slidev/types'\nimport type { PluginOption } from 'vite'\nimport setupVitePlugins from '../setups/vite-plugins'\nimport { createVueCompilerFlagsPlugin } from './compilerFlagsVue'\nimport { createComponentsPlugin } from './components'\nimport { createContextInjectionPlugin } from './contextInjection'\nimport { createConfigPlugin } from './extendConfig'\nimport { createHmrPatchPlugin } from './hmrPatch'\nimport { createIconsPlugin } from './icons'\nimport { createInspectPlugin } from './inspect'\nimport { createLayoutWrapperPlugin } from './layoutWrapper'\nimport { createSlidesLoader } from './loaders'\nimport { createMarkdownPlugin } from './markdown'\nimport { createMonacoTypesLoader } from './monacoTypes'\nimport { createMonacoWriterPlugin } from './monacoWrite'\nimport { createPatchMonacoSourceMapPlugin } from './patchMonacoSourceMap'\nimport { createRemoteAssetsPlugin } from './remoteAssets'\nimport { createServerRefPlugin } from './serverRef'\nimport { createStaticCopyPlugin } from './staticCopy'\nimport { createUnocssPlugin } from './unocss'\nimport { createVuePlugin } from './vue'\n\nexport function ViteSlidevPlugin(\n  options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions = {},\n  serverOptions: SlidevServerOptions = {},\n): Promise<PluginOption[]> {\n  return Promise.all([\n    createSlidesLoader(options, serverOptions),\n    createMarkdownPlugin(options, pluginOptions),\n    createLayoutWrapperPlugin(options),\n    createContextInjectionPlugin(),\n    createVuePlugin(options, pluginOptions),\n    createHmrPatchPlugin(),\n    createComponentsPlugin(options, pluginOptions),\n    createIconsPlugin(options, pluginOptions),\n    createRemoteAssetsPlugin(options, pluginOptions),\n    createServerRefPlugin(options, pluginOptions),\n    createConfigPlugin(options),\n    createMonacoTypesLoader(options),\n    createMonacoWriterPlugin(options),\n    createVueCompilerFlagsPlugin(options),\n    createUnocssPlugin(options, pluginOptions),\n    createStaticCopyPlugin(options, pluginOptions),\n    createInspectPlugin(options, pluginOptions),\n    createPatchMonacoSourceMapPlugin(),\n\n    setupVitePlugins(options),\n  ])\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/inspect.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\n\nexport async function createInspectPlugin(\n  options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  if (!options.inspect)\n    return\n  const { default: PluginInspect } = await import('vite-plugin-inspect')\n  return PluginInspect({\n    dev: true,\n    build: true,\n    ...pluginOptions.inspect,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/layoutWrapper.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport { bold, gray, red, yellow } from 'ansis'\nimport { toAtFS } from '../resolver'\nimport { regexSlideSourceId, templateImportContextUtils, templateInitContext, templateInjectionMarker } from './common'\n\nexport function createLayoutWrapperPlugin(\n  { data, utils }: ResolvedSlidevOptions,\n): Plugin {\n  return {\n    name: 'slidev:layout-wrapper',\n    transform: {\n      filter: {\n        id: {\n          include: regexSlideSourceId,\n        },\n      },\n      async handler(code, id) {\n        const match = id.match(regexSlideSourceId)\n        if (!match)\n          return\n        const [, no, type] = match\n        if (type !== 'md')\n          return\n        const index = +no - 1\n        const layouts = await utils.getLayouts()\n        const rawLayoutName = data.slides[index]?.frontmatter?.layout ?? data.slides[0]?.frontmatter?.defaults?.layout\n        let layoutName = rawLayoutName || (index === 0 ? 'cover' : 'default')\n        if (!layouts[layoutName]) {\n          console.error(red(`\\nUnknown layout \"${bold(layoutName)}\".${yellow(' Available layouts are:')}`)\n            + Object.keys(layouts).map((i, idx) => (idx % 3 === 0 ? '\\n    ' : '') + gray(i.padEnd(15, ' '))).join('  '))\n          console.error()\n          layoutName = 'default'\n        }\n\n        const setupTag = code.match(/^<script setup.*>/m)\n        if (!setupTag)\n          throw new Error(`[Slidev] Internal error: <script setup> block not found in slide ${index + 1}.`)\n\n        const templatePart = code.slice(0, setupTag.index!)\n        const scriptPart = code.slice(setupTag.index!)\n\n        const bodyStart = templatePart.indexOf('<template>') + 10\n        const bodyEnd = templatePart.lastIndexOf('</template>')\n        let body = code.slice(bodyStart, bodyEnd).trim()\n        if (body.startsWith('<div>') && body.endsWith('</div>'))\n          body = body.slice(5, -6)\n\n        return [\n          templatePart.slice(0, bodyStart),\n          `<InjectedLayout v-bind=\"_frontmatterToProps($frontmatter,${index})\">\\n${body}\\n</InjectedLayout>`,\n          templatePart.slice(bodyEnd),\n          scriptPart.slice(0, setupTag[0].length),\n          `import InjectedLayout from \"${toAtFS(layouts[layoutName])}\"`,\n          templateImportContextUtils,\n          templateInitContext,\n          '$clicksContext.setup()',\n          templateInjectionMarker,\n          scriptPart.slice(setupTag[0].length),\n        ].join('\\n')\n      },\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/loaders.ts",
    "content": "import type { ResolvedSlidevOptions, SlideInfo, SlidePatch, SlidevData, SlidevServerOptions } from '@slidev/types'\nimport type { LoadResult } from 'rollup'\nimport type { ModuleNode, Plugin, ViteDevServer } from 'vite'\nimport { notNullish, range } from '@antfu/utils'\nimport * as parser from '@slidev/parser/fs'\nimport equal from 'fast-deep-equal'\nimport MarkdownExit from 'markdown-exit'\nimport YAML from 'yaml'\nimport { createDataUtils } from '../options'\nimport MarkdownItKatex from '../syntax/markdown-it/markdown-it-katex'\nimport markdownItLink from '../syntax/markdown-it/markdown-it-link'\nimport { getBodyJson, updateFrontmatterPatch } from '../utils'\nimport { templates } from '../virtual'\nimport { templateConfigs } from '../virtual/configs'\nimport { templateMonacoRunDeps } from '../virtual/monaco-deps'\nimport { templateMonacoTypes } from '../virtual/monaco-types'\nimport { templateSlides, VIRTUAL_SLIDE_PREFIX } from '../virtual/slides'\nimport { templateTitleRendererMd } from '../virtual/titles'\nimport { regexSlideFacadeId, regexSlideReqPath, regexSlideSourceId } from './common'\n\nexport function createSlidesLoader(\n  options: ResolvedSlidevOptions,\n  serverOptions: SlidevServerOptions,\n): Plugin {\n  const { data, mode, utils, withoutNotes } = options\n\n  const notesMd = MarkdownExit({ html: true })\n  notesMd.use(markdownItLink)\n  if (data.features.katex)\n    notesMd.use(MarkdownItKatex, utils.katexOptions)\n\n  const hmrSlidesIndexes = new Set<number>()\n  let server: ViteDevServer | undefined\n  let skipHmr: { filePath: string, fileContent: string } | null = null\n\n  interface ResolvedSourceIds {\n    md: string[]\n    frontmatter: string[]\n  }\n  let sourceIds = resolveSourceIds(data)\n\n  function resolveSourceIds(data: SlidevData) {\n    const ids: ResolvedSourceIds = {\n      md: [],\n      frontmatter: [],\n    }\n    for (const type of ['md', 'frontmatter'] as const) {\n      for (let i = 0; i < data.slides.length; i++) {\n        ids[type].push(`${data.slides[i].source.filepath}__slidev_${i + 1}.${type}`)\n      }\n    }\n    return ids\n  }\n\n  function updateServerWatcher() {\n    if (!server)\n      return\n    server.watcher.add(Object.keys(data.watchFiles))\n  }\n\n  function getFrontmatter(pageNo: number) {\n    return {\n      ...(data.headmatter?.defaults as object || {}),\n      ...(data.slides[pageNo]?.frontmatter || {}),\n    }\n  }\n\n  return {\n    name: 'slidev:loader',\n    enforce: 'pre',\n\n    configureServer(_server) {\n      server = _server\n      updateServerWatcher()\n\n      server.middlewares.use(async (req, res, next) => {\n        const match = req.url?.match(regexSlideReqPath)\n        if (!match)\n          return next()\n\n        const [, no] = match\n        const idx = Number.parseInt(no) - 1\n        if (req.method === 'GET') {\n          res.write(JSON.stringify(withRenderedNote(data.slides[idx])))\n          return res.end()\n        }\n        else if (req.method === 'POST') {\n          const body: SlidePatch = await getBodyJson(req)\n          const slide = data.slides[idx]\n\n          if (body.content && body.content !== slide.source.content)\n            hmrSlidesIndexes.add(idx)\n\n          if (body.content)\n            slide.content = slide.source.content = body.content\n          if (body.frontmatterRaw != null) {\n            if (body.frontmatterRaw.trim() === '') {\n              slide.source.frontmatterDoc = slide.source.frontmatterStyle = undefined\n            }\n            else {\n              const parsed = YAML.parseDocument(body.frontmatterRaw)\n              if (parsed.errors.length)\n                console.error('ERROR when saving frontmatter', parsed.errors)\n              else\n                slide.source.frontmatterDoc = parsed\n            }\n          }\n          if (body.note)\n            slide.note = slide.source.note = body.note\n          if (body.frontmatter) {\n            updateFrontmatterPatch(slide.source, body.frontmatter)\n            Object.assign(slide.frontmatter, body.frontmatter)\n          }\n\n          parser.prettifySlide(slide.source)\n          const fileContent = await parser.save(data.markdownFiles[slide.source.filepath])\n          if (body.skipHmr) {\n            skipHmr = {\n              filePath: slide.source.filepath,\n              fileContent,\n            }\n            server?.moduleGraph.invalidateModule(\n              server.moduleGraph.getModuleById(sourceIds.md[idx])!,\n            )\n            if (body.frontmatter) {\n              server?.moduleGraph.invalidateModule(\n                server.moduleGraph.getModuleById(sourceIds.frontmatter[idx])!,\n              )\n            }\n          }\n\n          res.statusCode = 200\n          res.write(JSON.stringify(withRenderedNote(slide)))\n          return res.end()\n        }\n\n        next()\n      })\n    },\n\n    async handleHotUpdate(ctx) {\n      const forceChangedSlides = data.watchFiles[ctx.file]\n      if (!forceChangedSlides)\n        return\n\n      for (const index of forceChangedSlides) {\n        hmrSlidesIndexes.add(index)\n      }\n\n      const newData = await serverOptions.loadData?.({\n        [ctx.file]: await ctx.read(),\n      })\n\n      if (!newData)\n        return []\n\n      if (skipHmr && newData.markdownFiles[skipHmr.filePath]?.raw === skipHmr.fileContent) {\n        skipHmr = null\n        return []\n      }\n\n      const moduleIds = new Set<string>()\n\n      const newSourceIds = resolveSourceIds(newData)\n      for (const type of ['md', 'frontmatter'] as const) {\n        const old = sourceIds[type]\n        const newIds = newSourceIds[type]\n        for (let i = 0; i < newIds.length; i++) {\n          if (old[i] !== newIds[i]) {\n            moduleIds.add(`${VIRTUAL_SLIDE_PREFIX}${i + 1}/${type}`)\n          }\n        }\n      }\n      sourceIds = newSourceIds\n\n      if (data.slides.length !== newData.slides.length) {\n        moduleIds.add(templateSlides.id)\n      }\n\n      if (!equal(data.headmatter.defaults, newData.headmatter.defaults)) {\n        moduleIds.add(templateSlides.id)\n        range(data.slides.length).map(i => hmrSlidesIndexes.add(i))\n      }\n\n      if (!equal(data.config, newData.config))\n        moduleIds.add(templateConfigs.id)\n\n      if (!equal(data.features, newData.features)) {\n        setTimeout(() => {\n          ctx.server.hot.send({ type: 'full-reload' })\n        }, 1)\n      }\n\n      const length = Math.min(data.slides.length, newData.slides.length)\n\n      for (let i = 0; i < length; i++) {\n        const a = data.slides[i]\n        const b = newData.slides[i]\n\n        if (\n          !hmrSlidesIndexes.has(i)\n          && a.content.trim() === b.content.trim()\n          && a.title?.trim() === b.title?.trim()\n          && equal(a.frontmatter, b.frontmatter)\n        ) {\n          if (a.note !== b.note) {\n            ctx.server.hot.send(\n              'slidev:update-note',\n              {\n                no: i + 1,\n                note: b!.note || '',\n                noteHTML: renderNote(b!.note || ''),\n              },\n            )\n          }\n          continue\n        }\n\n        ctx.server.hot.send(\n          'slidev:update-slide',\n          {\n            no: i + 1,\n            data: withRenderedNote(newData.slides[i]),\n          },\n        )\n        hmrSlidesIndexes.add(i)\n      }\n\n      Object.assign(data, newData)\n      Object.assign(utils, createDataUtils(options))\n\n      if (hmrSlidesIndexes.size > 0)\n        moduleIds.add(templateTitleRendererMd.id)\n\n      for (const idx of hmrSlidesIndexes) {\n        moduleIds.add(sourceIds.frontmatter[idx])\n      }\n\n      const reloadBeforeOthers: ModuleNode[] = []\n      const vueModules: ModuleNode[] = []\n      for (const idx of hmrSlidesIndexes) {\n        const main = ctx.server.moduleGraph.getModuleById(sourceIds.md[idx])\n        if (main) {\n          const styles = [...main.clientImportedModules].filter(m => m.id?.includes(`&type=style`))\n          if (styles.length) {\n            // `pluginVue.transform(mainModule)` must be called before `pluginVue.load(styleModule)`\n            // to refresh the internal descriptor cache of `@vitejs/plugin-vue`\n            reloadBeforeOthers.push(main)\n            vueModules.push(...styles)\n          }\n          else {\n            vueModules.push(main)\n          }\n        }\n      }\n\n      hmrSlidesIndexes.clear()\n\n      await Promise.all(reloadBeforeOthers.map(m => ctx.server.reloadModule(m)))\n\n      const moduleEntries = [\n        ...ctx.modules.filter(i => i.id === templateMonacoRunDeps.id || i.id === templateMonacoTypes.id),\n        ...vueModules,\n        ...Array.from(moduleIds).map(id => ctx.server.moduleGraph.getModuleById(id)),\n      ]\n        .filter(notNullish)\n        .filter(i => !i.id?.startsWith('/@id/@vite-icons'))\n\n      updateServerWatcher()\n\n      return moduleEntries\n    },\n\n    resolveId: {\n      order: 'pre',\n      handler(id) {\n        if (id.startsWith('/@slidev/') || id.includes('__slidev_'))\n          return id\n        return null\n      },\n    },\n\n    async load(id): Promise<LoadResult> {\n      const template = templates.find(i => i.id === id)\n      if (template) {\n        return {\n          code: await template.getContent.call(this, options),\n          map: { mappings: '' },\n        }\n      }\n\n      const matchFacade = id.match(regexSlideFacadeId)\n      if (matchFacade) {\n        const [, no, type] = matchFacade\n        const idx = +no - 1\n        const sourceId = JSON.stringify(sourceIds[type as 'md' | 'frontmatter'][idx])\n        return [\n          `export * from ${sourceId}`,\n          `export { default } from ${sourceId}`,\n        ].join('\\n')\n      }\n\n      const matchSource = id.match(regexSlideSourceId)\n      if (matchSource) {\n        const [, no, type] = matchSource\n        const idx = +no - 1\n        const slide = data.slides[idx]\n        if (!slide)\n          return\n\n        if (type === 'md') {\n          return {\n            code: slide.content,\n            map: { mappings: '' },\n          }\n        }\n        else if (type === 'frontmatter') {\n          const slideBase = {\n            ...withRenderedNote(slide),\n            frontmatter: undefined,\n            source: undefined,\n            importChain: undefined,\n            // remove raw content in build, optimize the bundle size\n            ...(mode === 'build' ? { raw: '', content: '', note: '' } : {}),\n          }\n          const fontmatter = getFrontmatter(idx)\n\n          return {\n            code: [\n              '// @unocss-include',\n              'import { computed, reactive, shallowReactive } from \"vue\"',\n              `export const frontmatterData = ${JSON.stringify(fontmatter)}`,\n              // handle HMR, update frontmatter with update\n              'if (import.meta.hot) {',\n              '  import.meta.hot.data.frontmatter ??= reactive(frontmatterData)',\n              '  import.meta.hot.accept(({ frontmatterData: update }) => {',\n              '    const frontmatter = import.meta.hot.data.frontmatter',\n              '    Object.keys(frontmatter).forEach(key => {',\n              '      if (!(key in update)) delete frontmatter[key]',\n              '    })',\n              '    Object.assign(frontmatter, update)',\n              '  })',\n              '}',\n              'export const frontmatter = import.meta.hot ? import.meta.hot.data.frontmatter : reactive(frontmatterData)',\n              'export default frontmatter',\n              'export const meta = shallowReactive({',\n              '  get layout(){ return frontmatter.layout },',\n              '  get transition(){ return frontmatter.transition },',\n              '  get class(){ return frontmatter.class },',\n              '  get clicks(){ return frontmatter.clicks },',\n              '  get name(){ return frontmatter.name },',\n              '  get preload(){ return frontmatter.preload },',\n              // No need to be reactive, as it's only used once after reload\n              '  slide: {',\n              `    ...(${JSON.stringify(slideBase)}),`,\n              `    frontmatter,`,\n              `    filepath: ${JSON.stringify(mode === 'dev' ? slide.source.filepath : '')},`,\n              `    start: ${JSON.stringify(slide.source.start)},`,\n              `    id: ${idx},`,\n              `    no: ${no},`,\n              '  },',\n              '  __clicksContext: null,',\n              '  __preloaded: false,',\n              '})',\n            ].join('\\n'),\n            map: { mappings: '' },\n          }\n        }\n      }\n\n      // Entry files, shouldn't be processed by MarkdownIt\n      if (data.markdownFiles[id])\n        return ''\n    },\n  }\n\n  function renderNote(text: string = '') {\n    if (withoutNotes)\n      return ''\n\n    let clickCount = 0\n    const notesAutoRuby: Record<string, string | undefined> = (data.headmatter as any).notesAutoRuby || {}\n\n    // Apply [click] marker\n    let md = text\n      // replace [click] marker with span\n      .replace(/\\[click(?::(\\d+))?\\]/gi, (_, count = 1) => {\n        clickCount += Number(count)\n        return `<span class=\"slidev-note-click-mark\" data-clicks=\"${clickCount}\"></span>`\n      })\n\n    // Apply notesAutoRuby\n    const keys = Object.keys(notesAutoRuby)\n      .sort((b, a) => b.length - a.length)\n      // Add word boundaries to the keys when they are simple alphabets or numbers\n      .map(i => /^[\\w-]+$/.test(i) ? `\\\\b${i}\\\\b` : i)\n\n    if (keys.length) {\n      const regex = new RegExp(`(${keys.join('|')})`, 'g')\n      md = md.replace(\n        regex,\n        (match) => {\n          if (notesAutoRuby[match])\n            return `<ruby>${match}<rt>${notesAutoRuby[match]}</rt></ruby>`\n          return match\n        },\n      )\n    }\n\n    const html = notesMd.render(md)\n    return html\n  }\n\n  function withRenderedNote(data: SlideInfo): SlideInfo {\n    return {\n      ...data,\n      ...withoutNotes && { note: '' },\n      noteHTML: renderNote(data?.note),\n    }\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/markdown.ts",
    "content": "import type { MarkdownTransformContext, ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport MagicString from 'magic-string-stack'\nimport Markdown from 'unplugin-vue-markdown/vite'\nimport { useMarkdownItPlugins } from '../syntax/markdown-it'\nimport { getMarkdownTransformers } from '../syntax/transform'\nimport { regexSlideSourceId } from './common'\n\nexport async function createMarkdownPlugin(\n  options: ResolvedSlidevOptions,\n  { markdown: mdOptions }: SlidevPluginOptions,\n): Promise<Plugin> {\n  const markdownTransformMap = new Map<string, MagicString>()\n  const transformers = await getMarkdownTransformers(options)\n\n  return Markdown({\n    include: [/\\.md$/],\n    wrapperClasses: '',\n    headEnabled: false,\n    frontmatter: false,\n    escapeCodeTagInterpolation: false,\n    markdownOptions: {\n      quotes: '\"\"\\'\\'',\n      html: true,\n      xhtmlOut: true,\n      linkify: true,\n      ...mdOptions?.markdownOptions,\n    },\n    ...mdOptions,\n    async markdownSetup(md) {\n      await useMarkdownItPlugins(md, options, markdownTransformMap)\n      await mdOptions?.markdownSetup?.(md)\n    },\n    transforms: {\n      ...mdOptions?.transforms,\n      async before(code, id) {\n        // Skip entry Markdown files\n        if (options.data.markdownFiles[id])\n          return ''\n\n        code = await mdOptions?.transforms?.before?.(code, id) ?? code\n\n        const match = id.match(regexSlideSourceId)\n        if (!match)\n          return code\n\n        const s = new MagicString(code)\n        markdownTransformMap.set(id, s)\n        const ctx: MarkdownTransformContext = {\n          s,\n          slide: options.data.slides[+match[1] - 1],\n          options,\n        }\n\n        for (const transformer of transformers) {\n          if (!transformer)\n            continue\n          await transformer(ctx)\n          if (!ctx.s.isEmpty())\n            ctx.s.commit()\n        }\n\n        return s.toString()\n      },\n    },\n  }) as Plugin\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/monacoTypes.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport fs from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport fg from 'fast-glob'\nimport { findDepPkgJsonPath } from 'vitefu'\nimport { toAtFS } from '../resolver'\n\nexport function createMonacoTypesLoader({ userRoot, utils }: ResolvedSlidevOptions): Plugin {\n  return {\n    name: 'slidev:monaco-types-loader',\n\n    resolveId(id) {\n      if (id.startsWith('/@slidev-monaco-types/'))\n        return id\n      return null\n    },\n\n    async load(id) {\n      if (!id.startsWith('/@slidev-monaco-types/'))\n        return null\n\n      const url = new URL(id, 'http://localhost')\n      if (url.pathname === '/@slidev-monaco-types/resolve') {\n        const query = new URLSearchParams(url.search)\n        const pkg = query.get('pkg')!\n        const importer = query.get('importer') ?? userRoot\n\n        const pkgJsonPath = await findDepPkgJsonPath(pkg, importer)\n        if (!pkgJsonPath)\n          throw new Error(`Package \"${pkg}\" not found in \"${importer}\"`)\n        const root = slash(dirname(pkgJsonPath))\n\n        const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf-8'))\n        let deps = Object.keys(pkgJson.dependencies ?? {})\n        deps = deps.filter(pkg => !utils.isMonacoTypesIgnored(pkg))\n\n        return [\n          `import \"/@slidev-monaco-types/load?${new URLSearchParams({ root, name: pkgJson.name })}\"`,\n          ...deps.map(dep => `import \"/@slidev-monaco-types/resolve?${new URLSearchParams({ pkg: dep, importer: root })}\"`),\n        ].join('\\n')\n      }\n\n      if (url.pathname === '/@slidev-monaco-types/load') {\n        const query = new URLSearchParams(url.search)\n        const root = query.get('root')!\n        const name = query.get('name')!\n        const files = await fg(\n          [\n            '**/*.ts',\n            '**/*.mts',\n            '**/*.cts',\n            'package.json',\n          ],\n          {\n            cwd: root,\n            followSymbolicLinks: true,\n            ignore: ['**/node_modules/**'],\n          },\n        )\n\n        if (!files.length)\n          return '/** No files found **/'\n\n        return [\n          'import { addFile } from \"@slidev/client/setup/monaco.ts\"',\n          ...files.map(file => `addFile(() => import(${\n            JSON.stringify(`${toAtFS(resolve(root, file))}?monaco-types&raw`)\n          }), ${JSON.stringify(`node_modules/${name}/${file}`)})`),\n        ].join('\\n')\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/monacoWrite.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nexport const monacoWriterWhitelist = new Set<string>()\n\nexport function createMonacoWriterPlugin({ userRoot }: ResolvedSlidevOptions): Plugin {\n  return {\n    name: 'slidev:monaco-write',\n    apply: 'serve',\n\n    configureServer(server) {\n      server.ws.on('connection', (socket) => {\n        socket.on('message', async (data) => {\n          let json\n          try {\n            json = JSON.parse(data.toString())\n          }\n          catch {\n            return\n          }\n          if (json.type === 'custom' && json.event === 'slidev:monaco-write') {\n            const { file, content } = json.data\n            if (!monacoWriterWhitelist.has(file)) {\n              console.error(`[Slidev] Unauthorized file write: ${file}`)\n              return\n            }\n            const filepath = path.join(userRoot, file)\n            // eslint-disable-next-line no-console\n            console.log('[Slidev] Writing file:', filepath)\n            await fs.writeFile(filepath, content, 'utf-8')\n          }\n        })\n      })\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/patchMonacoSourceMap.ts",
    "content": "import type { Plugin } from 'vite'\nimport { readFile } from 'node:fs/promises'\n\nconst postfixRE = /[?#].*$/\nfunction cleanUrl(url: string) {\n  return url.replace(postfixRE, '')\n}\n\n/**\n * Temporary workaround for https://github.com/microsoft/monaco-editor/issues/4712\n */\nexport function createPatchMonacoSourceMapPlugin(\n): Plugin {\n  return {\n    name: 'slidev:patch-monaco-sourcemap',\n    enforce: 'pre',\n    load(id) {\n      if (!id.includes('node_modules/monaco-editor/esm/vs/base'))\n        return\n      return readFile(cleanUrl(id), 'utf-8')\n    },\n  }\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/remoteAssets.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\n\nexport async function createRemoteAssetsPlugin(\n  { data: { config }, mode }: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  if (!(config.remoteAssets === true || config.remoteAssets === mode))\n    return\n\n  const { VitePluginRemoteAssets, DefaultRules } = await import('vite-plugin-remote-assets')\n  return VitePluginRemoteAssets({\n    resolveMode: id => id.endsWith('index.html') ? 'relative' : '@fs',\n    awaitDownload: mode === 'build',\n    ...pluginOptions.remoteAssets,\n    rules: [\n      ...DefaultRules,\n      {\n        match: /\\b(https?:\\/\\/image.unsplash\\.com.*?)(?=[`'\")\\]])/gi,\n        ext: '.png',\n      },\n      ...pluginOptions.remoteAssets?.rules ?? [],\n    ],\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/serverRef.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport ServerRef from 'vite-plugin-vue-server-ref'\nimport { loadDrawings, writeDrawings } from '../integrations/drawings'\nimport { loadSnapshots, writeSnapshots } from '../integrations/snapshots'\n\nexport async function createServerRefPlugin(\n  options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  return ServerRef({\n    debug: false, // process.env.NODE_ENV === 'development',\n    state: {\n      sync: false,\n      nav: {\n        page: 0,\n        clicks: 0,\n        timer: {\n          status: 'stopped',\n          slides: {},\n          startedAt: 0,\n          pausedAt: 0,\n        },\n      },\n      drawings: await loadDrawings(options),\n      snapshots: await loadSnapshots(options),\n      ...pluginOptions.serverRef?.state,\n    },\n    onChanged(key, data, patch, timestamp) {\n      pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp)\n\n      if (options.data.config.drawings.persist && key === 'drawings')\n        writeDrawings(options, patch ?? data)\n\n      if (key === 'snapshots')\n        writeSnapshots(options, data)\n    },\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/staticCopy.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport async function createStaticCopyPlugin(\n  { themeRoots, addonRoots }: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  const publicDirs = [...themeRoots, ...addonRoots].map(i => join(i, 'public')).filter(existsSync)\n  if (!publicDirs.length)\n    return\n  const { viteStaticCopy } = await import('vite-plugin-static-copy')\n  return viteStaticCopy({\n    silent: true,\n    targets: publicDirs.map(dir => ({\n      src: `${dir}/*`,\n      dest: 'theme',\n    })),\n    ...pluginOptions.staticCopy,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/unocss.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport UnoCSS from 'unocss/vite'\nimport setupUnocss from '../setups/unocss'\n\nexport async function createUnocssPlugin(\n  options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n) {\n  return UnoCSS({\n    configFile: false,\n    ...await setupUnocss(options),\n    ...pluginOptions.unocss,\n  })\n}\n"
  },
  {
    "path": "packages/slidev/node/vite/vue.ts",
    "content": "import type { ResolvedSlidevOptions, SlidevPluginOptions } from '@slidev/types'\nimport type { Plugin } from 'vite'\nimport Vue from '@vitejs/plugin-vue'\nimport VueJsx from '@vitejs/plugin-vue-jsx'\n\nconst customElements = new Set([\n  // katex\n  'annotation',\n  'math',\n  'menclose',\n  'mfrac',\n  'mglyph',\n  'mi',\n  'mlabeledtr',\n  'mn',\n  'mo',\n  'mover',\n  'mpadded',\n  'mphantom',\n  'mroot',\n  'mrow',\n  'mspace',\n  'msqrt',\n  'mstyle',\n  'msub',\n  'msubsup',\n  'msup',\n  'mtable',\n  'mtd',\n  'mtext',\n  'mtr',\n  'munder',\n  'munderover',\n  'semantics',\n])\n\nexport async function createVuePlugin(\n  _options: ResolvedSlidevOptions,\n  pluginOptions: SlidevPluginOptions,\n): Promise<Plugin[]> {\n  const {\n    vue: vueOptions = {},\n    vuejsx: vuejsxOptions = {},\n  } = pluginOptions\n\n  const VuePlugin = Vue({\n    include: [/\\.vue$/, /\\.vue\\?vue/, /\\.vue\\?v=/, /\\.md$/, /\\.md\\?vue/],\n    exclude: [],\n    ...vueOptions,\n    template: {\n      ...vueOptions?.template,\n      compilerOptions: {\n        ...vueOptions?.template?.compilerOptions,\n        isCustomElement(tag) {\n          return customElements.has(tag) || vueOptions?.template?.compilerOptions?.isCustomElement?.(tag)\n        },\n      },\n    },\n  })\n  const VueJsxPlugin = VueJsx(vuejsxOptions)\n\n  return [\n    VueJsxPlugin,\n    VuePlugin,\n  ]\n}\n"
  },
  {
    "path": "packages/slidev/package.json",
    "content": "{\n  \"name\": \"@slidev/cli\",\n  \"type\": \"module\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Presentation slides for developers\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.mts\",\n      \"import\": \"./dist/index.mjs\"\n    },\n    \"./*\": \"./*\"\n  },\n  \"main\": \"dist/index.mjs\",\n  \"module\": \"dist/index.mjs\",\n  \"types\": \"dist/index.d.mts\",\n  \"bin\": {\n    \"slidev\": \"./bin/slidev.mjs\"\n  },\n  \"files\": [\n    \"bin\",\n    \"dist\",\n    \"skills\",\n    \"template.md\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"tsdown node/index.ts node/cli.ts\",\n    \"dev\": \"nr build --watch\",\n    \"prepublishOnly\": \"npm run build\",\n    \"start\": \"tsx node/cli.ts\"\n  },\n  \"peerDependencies\": {\n    \"playwright-chromium\": \"^1.10.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"playwright-chromium\": {\n      \"optional\": true\n    }\n  },\n  \"dependencies\": {\n    \"@antfu/ni\": \"catalog:prod\",\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@comark/markdown-it\": \"catalog:prod\",\n    \"@iconify-json/carbon\": \"catalog:icons\",\n    \"@iconify-json/ph\": \"catalog:icons\",\n    \"@iconify-json/svg-spinners\": \"catalog:icons\",\n    \"@lillallol/outline-pdf\": \"catalog:prod\",\n    \"@shikijs/markdown-it\": \"catalog:frontend\",\n    \"@shikijs/twoslash\": \"catalog:prod\",\n    \"@shikijs/vitepress-twoslash\": \"catalog:prod\",\n    \"@slidev/client\": \"workspace:*\",\n    \"@slidev/parser\": \"workspace:*\",\n    \"@slidev/types\": \"workspace:*\",\n    \"@unocss/extractor-mdc\": \"catalog:prod\",\n    \"@unocss/reset\": \"catalog:frontend\",\n    \"@vitejs/plugin-vue\": \"catalog:prod\",\n    \"@vitejs/plugin-vue-jsx\": \"catalog:prod\",\n    \"ansis\": \"catalog:prod\",\n    \"chokidar\": \"catalog:prod\",\n    \"cli-progress\": \"catalog:prod\",\n    \"connect\": \"catalog:prod\",\n    \"fast-deep-equal\": \"catalog:prod\",\n    \"fast-glob\": \"catalog:prod\",\n    \"get-port-please\": \"catalog:prod\",\n    \"global-directory\": \"catalog:prod\",\n    \"htmlparser2\": \"catalog:prod\",\n    \"is-installed-globally\": \"catalog:prod\",\n    \"jiti\": \"catalog:prod\",\n    \"katex\": \"catalog:frontend\",\n    \"local-pkg\": \"catalog:prod\",\n    \"lz-string\": \"catalog:frontend\",\n    \"magic-string\": \"catalog:prod\",\n    \"magic-string-stack\": \"catalog:prod\",\n    \"markdown-exit\": \"catalog:prod\",\n    \"markdown-it-footnote\": \"catalog:prod\",\n    \"mlly\": \"catalog:prod\",\n    \"monaco-editor\": \"catalog:monaco\",\n    \"obug\": \"catalog:prod\",\n    \"open\": \"catalog:prod\",\n    \"pdf-lib\": \"catalog:prod\",\n    \"picomatch\": \"catalog:prod\",\n    \"plantuml-encoder\": \"catalog:frontend\",\n    \"postcss-nested\": \"catalog:dev\",\n    \"pptxgenjs\": \"catalog:prod\",\n    \"prompts\": \"catalog:prod\",\n    \"public-ip\": \"catalog:prod\",\n    \"resolve-from\": \"catalog:prod\",\n    \"resolve-global\": \"catalog:prod\",\n    \"semver\": \"catalog:prod\",\n    \"shiki\": \"catalog:frontend\",\n    \"shiki-magic-move\": \"catalog:frontend\",\n    \"sirv\": \"catalog:prod\",\n    \"source-map-js\": \"catalog:prod\",\n    \"typescript\": \"catalog:dev\",\n    \"unhead\": \"catalog:frontend\",\n    \"unocss\": \"catalog:prod\",\n    \"unplugin-icons\": \"catalog:prod\",\n    \"unplugin-vue-components\": \"catalog:prod\",\n    \"unplugin-vue-markdown\": \"catalog:prod\",\n    \"untun\": \"catalog:prod\",\n    \"uqr\": \"catalog:prod\",\n    \"vite\": \"catalog:prod\",\n    \"vite-plugin-inspect\": \"catalog:prod\",\n    \"vite-plugin-remote-assets\": \"catalog:prod\",\n    \"vite-plugin-static-copy\": \"catalog:prod\",\n    \"vite-plugin-vue-server-ref\": \"catalog:prod\",\n    \"vitefu\": \"catalog:dev\",\n    \"vue\": \"catalog:frontend\",\n    \"yaml\": \"catalog:prod\",\n    \"yargs\": \"catalog:prod\"\n  },\n  \"devDependencies\": {\n    \"@hedgedoc/markdown-it-plugins\": \"catalog:prod\",\n    \"@types/picomatch\": \"catalog:types\",\n    \"@types/plantuml-encoder\": \"catalog:types\"\n  }\n}\n"
  },
  {
    "path": "packages/slidev/template.md",
    "content": "---\n# try also 'default' to start simple\ntheme: seriph\n# random image from a curated Unsplash collection by Anthony\n# like them? see https://unsplash.com/collections/94734566/slidev\nbackground: https://cover.sli.dev\n# apply any unocss classes to the current slide\nclass: 'text-center'\n# some information about the slides, markdown enabled\ninfo: |\n  ## Slidev Starter Template\n  Presentation slides for developers.\n\n  Learn more at [Sli.dev](https://sli.dev)\ntransition: slide-left\ntitle: Welcome to Slidev\ncomark: true\n---\n\n# Welcome to Slidev\n\nPresentation slides for developers\n\n<div class=\"pt-12\">\n  <span @click=\"$slidev.nav.next\" class=\"px-2 py-1 rounded cursor-pointer\" flex=\"~ justify-center items-center gap-2\" hover=\"bg-white bg-opacity-10\">\n    Press Space for next page <div class=\"i-carbon:arrow-right inline-block\"/>\n  </span>\n</div>\n\n<div class=\"abs-br m-6 flex gap-2\">\n  <button @click=\"$slidev.nav.openInEditor()\" title=\"Open in Editor\" class=\"text-xl slidev-icon-btn opacity-50 !border-none !hover:text-white\">\n    <div class=\"i-carbon:edit\" />\n  </button>\n  <a href=\"https://github.com/slidevjs/slidev\" target=\"_blank\" alt=\"GitHub\" title=\"Open in GitHub\"\n    class=\"text-xl slidev-icon-btn opacity-50 !border-none !hover:text-white\">\n    <carbon-logo-github />\n  </a>\n</div>\n\n<!--\nThe last comment block of each slide will be treated as slide notes. It will be visible and editable in Presenter Mode along with the slide. [Read more in the docs](https://sli.dev/guide/syntax.html#notes)\n-->\n\n---\ntransition: fade-out\n---\n\n# What is Slidev?\n\nSlidev is a slide maker and accompanying presentation tool designed for developers. It consists of the following features:\n\n- 📝 **Text-based** - focus on the content with Markdown, and apply styles later\n- 🎨 **Themable** - themes can be shared and used as npm packages\n- 🧑‍💻 **Developer Friendly** - code highlighting, live coding with autocompletion\n- 🤹 **Interactive** - embedding Vue components to enhance your slides\n- 🎥 **Recording** - built-in recording and camera view\n- 📤 **Portable** - export to PDF, PPTX, PNGs, or even a hostable SPA\n- 🛠 **Hackable** - virtually anything that's possible on a webpage is possible in Slidev\n\n<br>\n<br>\n\nRead more about Slidev in [Why Slidev?](https://sli.dev/guide/why)\n\n<!--\nYou can have `style` tags in markdown to override the style for the current page.\nLearn more: https://sli.dev/guide/syntax#embedded-styles\n-->\n\n<style>\nh1 {\n  background-color: #2B90B6;\n  background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%);\n  background-size: 100%;\n  -webkit-background-clip: text;\n  -moz-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  -moz-text-fill-color: transparent;\n}\n</style>\n\n<!--\nHere is another comment.\n-->\n\n---\nlayout: default\n---\n\n# Table of contents\n\n```html\n<Toc minDepth=\"1\" maxDepth=\"1\"></Toc>\n```\n\n<Toc maxDepth=\"1\"></Toc>\n\n---\ntransition: slide-up\nlevel: 2\n---\n\n# Navigation\n\nHover on the bottom-left corner to see the navigation's control panel, [learn more](https://sli.dev/guide/navigation.html)\n\n## Keyboard Shortcuts\n\n|     |     |\n| --- | --- |\n| <kbd>right</kbd> / <kbd>space</kbd>| next animation or slide |\n| <kbd>left</kbd>  / <kbd>shift</kbd><kbd>space</kbd> | previous animation or slide |\n| <kbd>up</kbd> | previous slide |\n| <kbd>down</kbd> | next slide |\n\n<!-- https://sli.dev/guide/animations.html#click-animation -->\n<img\n  v-click\n  class=\"absolute -bottom-9 -left-7 w-80 opacity-50\"\n  src=\"https://sli.dev/assets/arrow-bottom-left.svg\"\n  alt=\"\"\n/>\n<p v-after class=\"absolute bottom-23 left-45 opacity-30 transform -rotate-10\">Here!</p>\n\n---\nlayout: image-right\nimage: https://cover.sli.dev\n---\n\n# Code\n\nUse code snippets and get automatic highlighting, and even types hover![^1]\n\n```ts {all|5|7|7-8|10|all} twoslash\n// TwoSlash enables TypeScript hover information\n// and errors in markdown code blocks\n// More at https://shiki.style/packages/twoslash\n\nimport { computed, ref } from 'vue'\n\nconst count = ref(0)\nconst doubled = computed(() => count.value * 2)\n\ndoubled.value = 2\n```\n\n<arrow v-click=\"[4, 5]\" x1=\"350\" y1=\"310\" x2=\"195\" y2=\"334\" color=\"#953\" width=\"2\" arrowSize=\"1\" />\n\n<!-- This allow you to embed external code blocks -->\n<!-- <<< @/snippets/external.ts#snippet -->\n\n<!-- Footer -->\n[^1]: [Learn More](https://sli.dev/guide/syntax.html#line-highlighting)\n\n<!-- Inline style -->\n<style>\n.footnotes-sep {\n  @apply mt-5 opacity-10;\n}\n.footnotes {\n  @apply text-sm opacity-75;\n}\n.footnote-backref {\n  display: none;\n}\n</style>\n\n---\n\n# Components\n\n<div grid=\"~ cols-2 gap-4\">\n<div>\n\nYou can use Vue components directly inside your slides.\n\nWe have provided a few built-in components like `<Tweet/>` and `<Youtube/>` that you can use directly. Adding your own custom components is also super easy.\n\n```html\n<Counter :count=\"10\" />\n```\n\n<!-- ./components/Counter.vue -->\n<Counter :count=\"10\" m=\"t-4\" />\n\nCheck out [the guides](https://sli.dev/builtin/components.html) for more.\n\n</div>\n<div>\n\n```html\n<Tweet id=\"1390115482657726468\" />\n```\n\n<Tweet id=\"1390115482657726468\" scale=\"0.65\" />\n\n</div>\n</div>\n\n<!--\nPresenter notes with **bold**, *italic*, and ~~strike~~ text.\n\nAlso, HTML elements are valid:\n<div class=\"flex w-full\">\n  <span style=\"flex-grow: 1;\">Left content</span>\n  <span>Right content</span>\n</div>\n-->\n\n---\nclass: px-20\n---\n\n# Themes\n\nSlidev comes with powerful theming support. Themes can provide styles, layouts, components, or even configurations for tools. Switch between themes on a per-slide basis with just **one change** in your frontmatter:\n\n<div grid=\"~ cols-2 gap-2\" m=\"t-2\">\n\n```yaml\n---\ntheme: default\n---\n```\n\n```yaml\n---\ntheme: seriph\n---\n```\n\n<img border=\"rounded\" src=\"https://github.com/slidevjs/themes/blob/main/screenshots/theme-default/01.png?raw=true\" alt=\"\">\n\n<img border=\"rounded\" src=\"https://github.com/slidevjs/themes/blob/main/screenshots/theme-seriph/01.png?raw=true\" alt=\"\">\n\n</div>\n\nRead more about [How to use a theme](https://sli.dev/guide/theme-addon#use-theme) and\ncheck out the [Awesome Themes Gallery](https://sli.dev/resources/theme-gallery).\n\n---\npreload: false\n---\n\n# Animations\n\nAnimations are powered by [@vueuse/motion](https://motion.vueuse.org/).\n\n```html\n<div\n  v-motion\n  :initial=\"{ x: -80 }\"\n  :enter=\"{ x: 0 }\">\n  Slidev\n</div>\n```\n\n<div class=\"w-60 relative mt-6\">\n  <div class=\"relative w-40 h-40\">\n    <img\n      v-motion\n      :initial=\"{ x: 800, y: -100, scale: 1.5, rotate: -50 }\"\n      :enter=\"final\"\n      class=\"absolute top-0 left-0 right-0 bottom-0\"\n      src=\"https://sli.dev/logo-square.png\"\n      alt=\"\"\n    />\n    <img\n      v-motion\n      :initial=\"{ y: 500, x: -100, scale: 2 }\"\n      :enter=\"final\"\n      class=\"absolute top-0 left-0 right-0 bottom-0\"\n      src=\"https://sli.dev/logo-circle.png\"\n      alt=\"\"\n    />\n    <img\n      v-motion\n      :initial=\"{ x: 600, y: 400, scale: 2, rotate: 100 }\"\n      :enter=\"final\"\n      class=\"absolute top-0 left-0 right-0 bottom-0\"\n      src=\"https://sli.dev/logo-triangle.png\"\n      alt=\"\"\n    />\n  </div>\n\n  <div\n    class=\"text-5xl absolute top-14 left-40 text-[#2B90B6] -z-1\"\n    v-motion\n    :initial=\"{ x: -80, opacity: 0}\"\n    :enter=\"{ x: 0, opacity: 1, transition: { delay: 2000, duration: 1000 } }\">\n    Slidev\n  </div>\n</div>\n\n<!-- vue script setup scripts can be directly used in markdown, and will only affect the current page -->\n<script setup lang=\"ts\">\nconst final = {\n  x: 0,\n  y: 0,\n  rotate: 0,\n  scale: 1,\n  transition: {\n    type: 'spring',\n    damping: 10,\n    stiffness: 20,\n    mass: 2\n  }\n}\n</script>\n\n<div\n  v-motion\n  :initial=\"{ x:35, y: 40, opacity: 0}\"\n  :enter=\"{ y: 0, opacity: 1, transition: { delay: 3500 } }\">\n\n[Learn More](https://sli.dev/guide/animations.html#motion)\n\n</div>\n\n---\n\n# LaTeX\n\nLaTeX is supported out-of-box powered by [KaTeX](https://katex.org/).\n\n<br>\n\nInline $\\sqrt{3x-1}+(1+x)^2$\n\nBlock\n$$ {1|3|all}\n\\begin{array}{c}\n\n\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} &\n= \\frac{4\\pi}{c}\\vec{\\mathbf{j}}    \\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n\n\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n\n\\nabla \\cdot \\vec{\\mathbf{B}} & = 0\n\n\\end{array}\n$$\n\n<br>\n\n[Learn more](https://sli.dev/guide/syntax#latex)\n\n---\n\n# Diagrams\n\nYou can create diagrams / graphs from textual descriptions, directly in your Markdown.\n\n<div class=\"grid grid-cols-4 gap-5 pt-4 -mb-6\">\n\n```mermaid {scale: 0.5, alt: 'A simple sequence diagram'}\nsequenceDiagram\n    Alice->John: Hello John, how are you?\n    Note over Alice,John: A typical interaction\n```\n\n```mermaid {theme: 'neutral', scale: 0.8}\ngraph TD\nB[Text] --> C{Decision}\nC -->|One| D[Result 1]\nC -->|Two| E[Result 2]\n```\n\n```mermaid\nmindmap\n  root((mindmap))\n    Origins\n      Long history\n      ::icon(fa fa-book)\n      Popularisation\n        British popular psychology author Tony Buzan\n    Research\n      On effectivness<br/>and features\n      On Automatic creation\n        Uses\n            Creative techniques\n            Strategic planning\n            Argument mapping\n    Tools\n      Pen and paper\n      Mermaid\n```\n\n```plantuml {scale: 0.7}\n@startuml\n\npackage \"Some Group\" {\n  HTTP - [First Component]\n  [Another Component]\n}\n\nnode \"Other Groups\" {\n  FTP - [Second Component]\n  [First Component] --> FTP\n}\n\ncloud {\n  [Example 1]\n}\n\ndatabase \"MySql\" {\n  folder \"This is my folder\" {\n    [Folder 3]\n  }\n  frame \"Foo\" {\n    [Frame 4]\n  }\n}\n\n[Another Component] --> [Example 1]\n[Example 1] --> [Folder 3]\n[Folder 3] --> [Frame 4]\n\n@enduml\n```\n\n</div>\n\n[Learn More](https://sli.dev/guide/syntax.html#diagrams)\n\n---\nlayout: center\nclass: text-center\n---\n\n# Learn More\n\n[Documentation](https://sli.dev) · [GitHub](https://github.com/slidevjs/slidev) · [Showcases](https://sli.dev/showcases.html)\n"
  },
  {
    "path": "packages/slidev/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/slidev/tsdown.config.ts",
    "content": "export { default } from '../../tsdown.config.ts'\n"
  },
  {
    "path": "packages/types/README.md",
    "content": "# @slidev/types\n\n[![NPM version](https://img.shields.io/npm/v/@slidev/types?color=3AB9D4&label=)](https://www.npmjs.com/package/@slidev/types)\n\nShared types for [Slidev](https://sli.dev).\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/types/client.d.ts",
    "content": "// Types for virtual modules\n// `#slidev/*` is an alias for `/@slidev/*`, because TS will consider `/@slidev/*` as an absolute path that we can't override\n\ndeclare module '#slidev/configs' {\n  import type { SlidevConfig } from '@slidev/types'\n\n  const configs: SlidevConfig & { slidesTitle: string }\n  export default configs\n}\n\ndeclare module '#slidev/global-layers' {\n  import type { ComponentOptions } from 'vue'\n\n  export const GlobalTop: ComponentOptions\n  export const GlobalBottom: ComponentOptions\n\n  export const SlideTop: ComponentOptions\n  export const SlideBottom: ComponentOptions\n}\n\ndeclare module '#slidev/slides' {\n  import type { SlideRoute } from '@slidev/types'\n  import type { ShallowRef } from 'vue'\n\n  const slides: ShallowRef<SlideRoute[]>\n  export { slides }\n}\n\ndeclare module '#slidev/title-renderer' {\n  import type { ComponentOptions } from 'vue'\n\n  const component: ComponentOptions\n  export default component\n}\n\ndeclare module '#slidev/custom-nav-controls' {\n  import type { ComponentOptions } from 'vue'\n\n  const component: ComponentOptions\n  export default component\n}\n\ndeclare module '#slidev/setups/shiki' {\n  import type { ShikiSetup } from '@slidev/types'\n\n  const setups: ShikiSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/monaco' {\n  import type { MonacoSetup } from '@slidev/types'\n\n  const setups: MonacoSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/code-runners' {\n  import type { CodeRunnersSetup } from '@slidev/types'\n\n  const setups: CodeRunnersSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/mermaid' {\n  import type { MermaidSetup } from '@slidev/types'\n\n  const setups: MermaidSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/mermaid-renderer' {\n  import type { MermaidRendererSetup } from '@slidev/types'\n\n  const setups: MermaidRendererSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/main' {\n  import type { AppSetup } from '@slidev/types'\n\n  const setups: AppSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/root' {\n  import type { RootSetup } from '@slidev/types'\n\n  const setups: RootSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/shortcuts' {\n  import type { ShortcutsSetup } from '@slidev/types'\n\n  const setups: ShortcutsSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/routes' {\n  import type { RoutesSetup } from '@slidev/types'\n\n  const setups: RoutesSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/setups/context-menu' {\n  import type { ContextMenuSetup } from '@slidev/types'\n\n  const setups: ContextMenuSetup[]\n  export default setups\n}\n\ndeclare module '#slidev/styles' {\n  // side-effects only\n}\n\ndeclare module '#slidev/monaco-types' {\n  // side-effects only\n}\n\ndeclare module '#slidev/monaco-run-deps' {\n  const modules: Recored<string, unknown>\n  export default modules\n}\n"
  },
  {
    "path": "packages/types/index.d.ts",
    "content": "import './client'\n\nexport * from './dist/index.d.mts'\n"
  },
  {
    "path": "packages/types/package.json",
    "content": "{\n  \"name\": \"@slidev/types\",\n  \"version\": \"52.14.1\",\n  \"description\": \"Shared types declarations for Slidev\",\n  \"author\": \"Anthony Fu <anthonyfu117@hotmail.com>\",\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/antfu\",\n  \"homepage\": \"https://sli.dev\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"bugs\": \"https://github.com/slidevjs/slidev/issues\",\n  \"main\": \"dist/index.mjs\",\n  \"module\": \"dist/index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"files\": [\n    \"client.d.ts\",\n    \"dist\",\n    \"index.d.ts\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"tsdown src/index.ts\",\n    \"dev\": \"nr build --watch\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@shikijs/markdown-it\": \"catalog:frontend\",\n    \"@vitejs/plugin-vue\": \"catalog:prod\",\n    \"@vitejs/plugin-vue-jsx\": \"catalog:prod\",\n    \"katex\": \"catalog:frontend\",\n    \"mermaid\": \"catalog:frontend\",\n    \"monaco-editor\": \"catalog:monaco\",\n    \"shiki\": \"catalog:frontend\",\n    \"unocss\": \"catalog:prod\",\n    \"unplugin-icons\": \"catalog:prod\",\n    \"unplugin-vue-markdown\": \"catalog:prod\",\n    \"vite-plugin-inspect\": \"catalog:prod\",\n    \"vite-plugin-remote-assets\": \"catalog:prod\",\n    \"vite-plugin-static-copy\": \"catalog:prod\",\n    \"vite-plugin-vue-server-ref\": \"catalog:prod\",\n    \"vue\": \"catalog:frontend\",\n    \"vue-router\": \"catalog:frontend\"\n  }\n}\n"
  },
  {
    "path": "packages/types/src/builtin-layouts.ts",
    "content": "export type BuiltinLayouts\n  = | '404'\n    | 'center'\n    | 'cover'\n    | 'default'\n    | 'end'\n    | 'error'\n    | 'fact'\n    | 'full'\n    | 'iframe-left'\n    | 'iframe-right'\n    | 'iframe'\n    | 'image-left'\n    | 'image-right'\n    | 'image'\n    | 'intro'\n    | 'none'\n    | 'quote'\n    | 'section'\n    | 'statement'\n    | 'two-cols-header'\n    | 'two-cols'\n"
  },
  {
    "path": "packages/types/src/cli.ts",
    "content": "export interface CommonArgs {\n  entry: string\n  theme?: string\n}\n\nexport interface ExportArgs extends CommonArgs {\n  'output'?: string\n  'format'?: string\n  'timeout'?: number\n  'wait'?: number\n  'wait-until'?: string\n  'range'?: string\n  'dark'?: boolean\n  'with-clicks'?: boolean\n  'executable-path'?: string\n  'with-toc'?: boolean\n  'per-slide'?: boolean\n  'scale'?: number\n  'omit-background'?: boolean\n}\n\nexport interface BuildArgs extends ExportArgs {\n  'out': string\n  'base'?: string\n  'download'?: boolean\n  'inspect': boolean\n  'without-notes'?: boolean\n}\n"
  },
  {
    "path": "packages/types/src/clicks.ts",
    "content": "import type { ComputedRef } from 'vue'\n\nexport type RawSingleAtValue = null | undefined | boolean | string | number\nexport type RawRangeAtValue = null | undefined | false | [string | number, string | number]\nexport type RawAtValue = RawSingleAtValue | RawRangeAtValue\n\nexport type NormalizedSingleClickValue\n  = | number // since absolute click\n    | string // since relative click\n    | null // disabled\nexport type NormalizedRangeClickValue\n  = | [number, number] // [absolute start, absolute end]\n    | [number, string] // [absolute start, absolute end based on start]\n    | [string, number] // [relative start, absolute end]\n    | [string, string] // [relative start, relative end]\n    | [string | number, string | number] // make TypeScript happy\n    | null // disabled\nexport type NormalizedAtValue\n  = | NormalizedSingleClickValue // since\n    | NormalizedRangeClickValue // range\n\nexport type ClicksElement = Element | string\n\nexport interface ClicksInfo {\n  /**\n   * The absolute start click num\n   */\n  start: number\n  /**\n   * The absolute end click num\n   */\n  end: number\n  /**\n   * The required total click num\n   */\n  max: number\n  /**\n   * The delta for relative clicks\n   */\n  delta: number\n  /**\n   * currentClicks - start\n   */\n  currentOffset: ComputedRef<number>\n  /**\n   * currentOffset === 0\n   */\n  isCurrent: ComputedRef<boolean>\n  /**\n   * Computed ref of whether the click is active\n   */\n  isActive: ComputedRef<boolean>\n}\n\nexport interface ClicksContext {\n  current: number\n  readonly clicksStart: number\n  readonly relativeSizeMap: Map<ClicksElement, number>\n  readonly maxMap: Map<ClicksElement, number>\n  calculateSince: (at: RawSingleAtValue, size?: number) => ClicksInfo | null\n  calculateRange: (at: RawRangeAtValue) => ClicksInfo | null\n  calculate: (at: RawAtValue) => ClicksInfo | null\n  register: (el: ClicksElement, info: Pick<ClicksInfo, 'delta' | 'max'> | null) => void\n  unregister: (el: ClicksElement) => void\n  readonly isMounted: boolean\n  setup: () => void\n  readonly currentOffset: number\n  readonly total: number\n}\n"
  },
  {
    "path": "packages/types/src/code-runner.ts",
    "content": "import type { Arrayable, Awaitable } from '@antfu/utils'\nimport type { CodeToHastOptions } from 'shiki'\nimport type { MaybeRefOrGetter } from 'vue'\n\nexport interface CodeRunnerContext {\n  /**\n   * Options passed to runner via the `runnerOptions` prop.\n   */\n  options: Record<string, unknown>\n  /**\n   * Highlight code with shiki.\n   */\n  highlight: (code: string, lang: string, options?: Partial<CodeToHastOptions>) => string\n  /**\n   * Use (other) code runner to run code.\n   */\n  run: (code: string, lang: string) => Promise<CodeRunnerOutputs>\n}\n\nexport interface CodeRunnerOutputHtml {\n  /**\n   * The HTML to be rendered.\n   *\n   * Slidev does NOT sanitize the HTML for you - make sure it's from trusted sources or sanitize it before passing it in\n   */\n  html: string\n}\n\nexport interface CodeRunnerOutputDom {\n  /**\n   * The DOM element to be rendered.\n   */\n  element: HTMLElement\n}\n\nexport interface CodeRunnerOutputError {\n  /**\n   * The error message to be displayed.\n   */\n  error: string\n}\n\nexport interface CodeRunnerOutputText {\n  /**\n   * The text to be displayed.\n   */\n  text: string\n  /**\n   * The class to be applied to the text.\n   */\n  class?: string\n  /**\n   * The language to be highlighted.\n   */\n  highlightLang?: string\n}\n\nexport type CodeRunnerOutputTextArray = CodeRunnerOutputText[]\n\nexport type CodeRunnerOutput = CodeRunnerOutputHtml | CodeRunnerOutputError | CodeRunnerOutputText | CodeRunnerOutputTextArray | CodeRunnerOutputDom\n\nexport type CodeRunnerOutputs = MaybeRefOrGetter<Arrayable<CodeRunnerOutput>>\n\nexport type CodeRunner = (code: string, ctx: CodeRunnerContext) => Awaitable<CodeRunnerOutputs>\n\nexport type CodeRunnerProviders = Record<string, CodeRunner>\n"
  },
  {
    "path": "packages/types/src/config.ts",
    "content": "import type { ExportArgs } from './cli'\nimport type { HeadmatterConfig } from './frontmatter'\n\nexport interface ResolvedSlidevConfigSub {\n  export: ResolvedExportOptions\n  drawings: ResolvedDrawingsOptions\n  fonts: ResolvedFontOptions\n  aspectRatio: number\n}\n\nexport interface SlidevConfig extends\n  Omit<Required<HeadmatterConfig>, keyof ResolvedSlidevConfigSub>,\n  ResolvedSlidevConfigSub {\n}\n\nexport interface ResolvedFontOptions {\n  sans: string[]\n  mono: string[]\n  serif: string[]\n  weights: string[]\n  italic: boolean\n  provider: 'none' | 'google' | 'coollabs'\n  webfonts: string[]\n  local: string[]\n}\n\nexport interface ResolvedDrawingsOptions {\n  persist: string | false\n  enabled: boolean | 'dev' | 'build'\n  presenterOnly: boolean\n  syncAll: boolean\n}\n\nexport interface ResolvedExportOptions extends Omit<ExportArgs, 'entry' | 'theme'> {\n  withClicks?: boolean\n  executablePath?: string\n  withToc?: boolean\n}\n"
  },
  {
    "path": "packages/types/src/context-menu.ts",
    "content": "import type { Component } from 'vue'\n\ntype ContextMenuOption = {\n  action: () => void\n  disabled?: boolean\n  show?: boolean\n} & (\n  | {\n    small?: false\n    icon?: Component | string\n    label: string | Component\n  }\n  | {\n    small: true\n    icon: Component | string\n    label: string\n  }\n  )\n\nexport type ContextMenuItem = ContextMenuOption | 'separator'\n"
  },
  {
    "path": "packages/types/src/env.ts",
    "content": ""
  },
  {
    "path": "packages/types/src/frontmatter.ts",
    "content": "import type { BuiltinLayouts } from './builtin-layouts'\nimport type { SlidevThemeConfig } from './types'\n\nexport interface Headmatter extends HeadmatterConfig, Omit<Frontmatter, 'title'> {\n  /**\n   * Default frontmatter options applied to all slides\n   */\n  defaults?: Frontmatter\n}\n\nexport interface HeadmatterConfig extends TransitionOptions {\n  /**\n   * Title of the slides\n   */\n  title?: string\n  /**\n   * String template to compose title\n   *\n   * @example \"%s - Slidev\" - to suffix \" - Slidev\" to all pages\n   * @default '%s - Slidev'\n   */\n  titleTemplate?: string\n  /**\n   * Theme to use for the slides\n   *\n   * See https://sli.dev/guide/theme-addon#use-theme\n   * @default 'default'\n   */\n  theme?: string\n  /**\n   * List of Slidev addons\n   *\n   * @default []\n   */\n  addons?: string[]\n  /**\n   * Download remote assets in local using vite-plugin-remote-assets\n   *\n   * @default false\n   */\n  remoteAssets?: boolean | 'dev' | 'build'\n  /**\n   * Show a download button in the SPA build,\n   * could also be a link to custom pdf\n   *\n   * @default false\n   */\n  download?: boolean | string\n  /**\n   * Show a copy button in code blocks\n   *\n   * @default true\n   */\n  codeCopy?: boolean\n  /**\n   * Show copy button in magic move code blocks\n   *\n   * `'final'` for only show copy button on the final step\n   * `'always'` or `true` for show copy button on all steps\n   *\n   * @default true\n   */\n  magicMoveCopy?: boolean | 'final' | 'always'\n  /**\n   * The author of the slides\n   */\n  author?: string\n  /**\n   * Information shows on the built SPA\n   * Can be a markdown string\n   *\n   * @default false\n   */\n  info?: string | boolean\n  /**\n   * Prefer highlighter\n   *\n   * See https://sli.dev/custom/config-highlighter.html\n   * @default shiki\n   */\n  highlighter?: 'shiki'\n  /**\n   * Enable Twoslash\n   *\n   * @default true\n   */\n  twoslash?: boolean | 'dev' | 'build'\n  /**\n   * Show line numbers in code blocks\n   *\n   * @default false\n   */\n  lineNumbers?: boolean\n  /**\n   * Force slides color schema\n   *\n   * @default 'auto'\n   */\n  colorSchema?: 'dark' | 'light' | 'all' | 'auto'\n  /**\n   * Router mode for vue-router\n   *\n   * @default 'history'\n   */\n  routerMode?: 'hash' | 'history'\n  /**\n   * Aspect ratio for slides\n   * should be like `16/9` or `1:1`\n   *\n   * @default '16/9'\n   */\n  aspectRatio?: number | string\n  /**\n   * The actual width for slides canvas.\n   * unit in px.\n   *\n   * @default '980'\n   */\n  canvasWidth?: number\n  /**\n   * Controls whether texts in slides are selectable\n   *\n   * @default true\n   */\n  selectable?: boolean\n  /**\n   * Configure for themes, will inject intro root styles as\n   * `--slidev-theme-x` for attribute `x`\n   *\n   * This allows themes to have customization options in frontmatter\n   * Refer to themes' document for options avaliable\n   *\n   * @default {}\n   */\n  themeConfig?: SlidevThemeConfig\n  /**\n   * Configure fonts for the slides and app\n   *\n   * @default {}\n   */\n  fonts?: FontOptions\n  /**\n   * Configure the icon for app\n   *\n   * @default 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png'\n   */\n  favicon?: string\n  /**\n   * Options for drawings\n   *\n   * @default {}\n   */\n  drawings?: DrawingsOptions\n  /**\n   * URL of PlantUML server used to render diagrams\n   *\n   * @default https://www.plantuml.com/plantuml\n   */\n  plantUmlServer?: string\n  /**\n   * Enable slides recording\n   *\n   * @default 'dev'\n   */\n  record?: boolean | 'dev' | 'build'\n  /**\n   * Expose the server to inbound requests (listen to `0.0.0.0`)\n   *\n   * Pass a string to set the password for accessing presenter mode.\n   *\n   * @default false\n   */\n  remote?: string | boolean\n  /**\n   * Engine for Atomic CSS\n   *\n   * See https://unocss.dev/\n   * @deprecated\n   * @default 'unocss'\n   */\n  css?: 'unocss'\n  /**\n   * Enable presenter mode\n   *\n   * @default true\n   */\n  presenter?: boolean | 'dev' | 'build'\n  /**\n   * Enable browser exporter\n   *\n   * @default 'dev'\n   */\n  browserExporter?: boolean | 'dev' | 'build'\n  /**\n   * Attributes to apply to the HTML element\n   *\n   * @default {}\n   */\n  htmlAttrs?: Record<string, string>\n  /**\n   * Suppport Comark syntax\n   *\n   * https://comark.dev/syntax/markdown\n   *\n   * @default false\n   */\n  comark?: boolean\n  /**\n   * @deprecated MDC is now Comark. Use the `comark` option instead\n   * @default false\n   */\n  mdc?: boolean\n  /**\n   * Enable built-in editor\n   *\n   * @default true\n   */\n  editor?: boolean\n  /**\n   * Enable context menu\n   *\n   * @default true\n   */\n  contextMenu?: boolean | 'dev' | 'build' | null\n  /**\n   * Enable wake lock\n   */\n  wakeLock?: boolean | 'dev' | 'build'\n  /**\n   * Force the filename used when exporting the presentation.\n   * The extension, e.g. .pdf, gets automatically added.\n   *\n   * @default ''\n   */\n  exportFilename?: string | null\n  /**\n   * Enable Monaco\n   *\n   * See https://sli.dev/custom/config-monaco.html\n   * @default true\n   */\n  monaco?: boolean | 'dev' | 'build'\n  /**\n   * Where to load monaco types from\n   *\n   * - `cdn` - load from CDN with `@typescript/ata`\n   * - `local` - load from local node_modules\n   *\n   * @default 'local'\n   */\n  monacoTypesSource?: 'cdn' | 'local' | 'none'\n  /**\n   * Additional node packages to load as monaco types\n   *\n   * @default []\n   */\n  monacoTypesAdditionalPackages?: string[]\n  /**\n   * Packages to ignore when loading monaco types\n   *\n   * @default []\n   */\n  monacoTypesIgnorePackages?: string[]\n  /**\n   * Additional local modules to load as dependencies of monaco runnable\n   *\n   * @default []\n   */\n  monacoRunAdditionalDeps?: string[]\n  /**\n   * Whether to run monaco runnable code in strict mode\n   *\n   * @default true\n   */\n  monacoRunUseStrict?: boolean\n  /**\n   * Seo meta tags settings\n   *\n   * @default {}\n   */\n  seoMeta?: SeoMeta\n  /**\n   * Auto replace words with `<ruby>` tags in notes\n   *\n   * @default {}\n   *\n   * @example\n   * ```yaml\n   * notesAutoRuby:\n   *   大丈夫: だいじょうぶ\n   * ```\n   */\n  notesAutoRuby?: Record<string, string>\n  /**\n   * The expected duration of the slide\n   *\n   * @example\n   * ```yaml\n   * duration: 35min\n   * ```\n   *\n   * @default '30min'\n   */\n  duration?: string | number\n  /**\n   * Timer mode\n   *\n   * @default 'stopwatch'\n   */\n  timer?: 'stopwatch' | 'countdown'\n  /**\n   * Duration for shiki magic move transitions in milliseconds\n   *\n   * @default 800\n   */\n  magicMoveDuration?: number\n  /**\n   * Preload images extracted from slides for faster navigation.\n   *\n   * - `true` - enable with default look-ahead of 3 slides\n   * - `false` - disable image preloading\n   * - `{ ahead: number }` - enable with custom look-ahead window\n   *\n   * @default true\n   */\n  preloadImages?: boolean | { ahead?: number }\n}\n\nexport interface Frontmatter extends TransitionOptions {\n  /**\n   * Slide layout to use\n   *\n   * Default to 'cover' for the first slide, 'default' for the rest\n   */\n  layout?: BuiltinLayouts | string\n  /**\n   * Custom class added to the slide root element\n   */\n  class?: string | string[] | Record<string, unknown>\n  /**\n   * Manually specified the total clicks needed to this slide\n   *\n   * When not specified, the clicks will be calculated by the usage of v-clicks\n   *\n   * See https://sli.dev/guide/animations\n   */\n  clicks?: number\n  /**\n   * Manually specified the total clicks needed to this slide to start\n   *\n   * @default 0\n   */\n  clicksStart?: number\n  /**\n   * Preload the slide when the previous slide is active\n   * @default true\n   */\n  preload?: boolean\n  /**\n   * Completely hide and disable the slide\n   */\n  hide?: boolean\n  /**\n   * Same as `hide`, completely hide and disable the slide\n   */\n  disabled?: boolean\n  /**\n   * Hide the slide for the `<Toc>` components\n   *\n   * See https://sli.dev/builtin/components#toc\n   */\n  hideInToc?: boolean\n  /**\n   * Override the title for the `<TitleRenderer>` and `<Toc>` components\n   * Only if `title` has also been declared\n   */\n  title?: string\n  /**\n   * Override the title level for the `<TitleRenderer>` and `<Toc>` components\n   * Only if `title` has also been declared\n   */\n  level?: number\n  /**\n   * Create a route alias that can be used in the URL or with the `<Link>` component\n   */\n  routeAlias?: string\n  /**\n   * Custom zoom level for the slide\n   * @default 1\n   */\n  zoom?: number\n  /**\n   * Store the positions of draggable elements\n   * Normally you don't need to set this manually\n   *\n   * See https://sli.dev/features/draggable\n   */\n  dragPos?: Record<string, string>\n  /**\n   * Includes a markdown file\n   *\n   * See https://sli.dev/guide/syntax.html#importing-slides\n   */\n  src?: string\n  // /**\n  //  * Set time split for the end of the slide\n  //  *\n  //  * Accepts:\n  //  * - 10:05\n  //  * - 10m5s\n  //  * - +10s (relative to the previous point)\n  //  */\n  // timesplit?: string\n  // /**\n  //  * Set title for the time split\n  //  *\n  //  * Default to slide title\n  //  */\n  // timesplitTitle?: string\n}\n\nexport interface DrawingsOptions {\n  /**\n   * Persist the drawings to disk\n   * Passing string to specify the directory (default to `.slidev/drawings`)\n   *\n   * @default false\n   */\n  persist?: boolean | string\n\n  /**\n   * @default true\n   */\n  enabled?: boolean | 'dev' | 'build'\n\n  /**\n   * Only allow drawing from presenter mode\n   *\n   * @default false\n   */\n  presenterOnly?: boolean\n\n  /**\n   * Sync drawing for all instances\n   *\n   * @default true\n   */\n  syncAll?: boolean\n}\n\nexport interface FontOptions {\n  /**\n   * Sans serif fonts (default fonts for most text)\n   */\n  sans?: string | string[]\n  /**\n   * Serif fonts\n   */\n  serif?: string | string[]\n  /**\n   * Monospace fonts, for code blocks and etc.\n   */\n  mono?: string | string[]\n  /**\n   * Load webfonts for custom CSS (does not apply anywhere by default)\n   */\n  custom?: string | string[]\n  /**\n   * Weights for fonts\n   *\n   * @default [200, 400, 600]\n   */\n  weights?: string | (string | number)[]\n  /**\n   * Import italic fonts\n   *\n   * @default false\n   */\n  italic?: boolean\n  /**\n   * @default 'google'\n   */\n  provider?: 'none' | 'google' | 'coollabs'\n  /**\n   * Specify web fonts names, will detect from `sans`, `mono`, `serif` if not provided\n   */\n  webfonts?: string[]\n  /**\n   * Specify local fonts names, be excluded from webfonts\n   */\n  local?: string[]\n  /**\n   * Use fonts fallback\n   *\n   * @default true\n   */\n  fallbacks?: boolean\n}\n\nexport type BuiltinSlideTransition = 'fade' | 'fade-out' | 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right' | 'view-transition'\n\nexport interface TransitionOptions {\n  /**\n   * Page transition, powered by Vue's `<TransitionGroup/>`\n   *\n   * Built-in transitions:\n   * - fade\n   * - fade-out\n   * - slide-left\n   * - slide-right\n   * - slide-up\n   * - slide-down\n   * - view-transition\n   *\n   * See https://sli.dev/guide/animations.html#pages-transitions\n   *\n   * See https://vuejs.org/guide/built-ins/transition.html\n   */\n  transition?: BuiltinSlideTransition | string | TransitionGroupProps | null\n}\n\nexport interface TransitionGroupProps {\n  appear?: boolean\n  persisted?: boolean\n  tag?: string\n  moveClass?: string\n  css?: boolean\n  duration?: number | {\n    enter: number\n    leave: number\n  }\n  enterFromClass?: string\n  enterActiveClass?: string\n  enterToClass?: string\n  appearFromClass?: string\n  appearActiveClass?: string\n  appearToClass?: string\n  leaveFromClass?: string\n  leaveActiveClass?: string\n  leaveToClass?: string\n}\n\n/**\n * The following type should map to unhead MataFlat type\n */\nexport interface SeoMeta {\n  ogTitle?: string\n  ogDescription?: string\n  ogImage?: string\n  ogUrl?: string\n  twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'\n  twitterSite?: string\n  twitterTitle?: string\n  twitterDescription?: string\n  twitterImage?: string\n  twitterUrl?: string\n}\n"
  },
  {
    "path": "packages/types/src/hmr.ts",
    "content": "import type { SlideInfo } from './types'\n\ndeclare module 'vite' {\n  interface CustomEventMap {\n    'slidev:update-slide': {\n      no: number\n      data: SlideInfo\n    }\n    'slidev:update-note': {\n      no: number\n      note: string\n      noteHTML: string\n    }\n  }\n}\n"
  },
  {
    "path": "packages/types/src/index.ts",
    "content": "export * from './cli'\nexport * from './clicks'\nexport * from './code-runner'\nexport * from './config'\nexport * from './context-menu'\nexport * from './frontmatter'\nexport * from './hmr'\nexport * from './options'\nexport * from './setups'\nexport * from './toc'\nexport * from './transform'\nexport * from './types'\nexport * from './vite'\n"
  },
  {
    "path": "packages/types/src/options.ts",
    "content": "import type { MarkdownItShikiOptions } from '@shikijs/markdown-it'\nimport type { KatexOptions } from 'katex'\nimport type { CodeOptionsThemes, ShorthandsBundle } from 'shiki/core'\nimport type { SlidevData } from './types'\n\nexport interface RootsInfo {\n  cliRoot: string\n  clientRoot: string\n  userRoot: string\n  userPkgJson: Record<string, any>\n  userWorkspaceRoot: string\n}\n\nexport interface SlidevEntryOptions {\n  /**\n   * Markdown entry\n   */\n  entry: string\n\n  /**\n   * Theme id\n   */\n  theme?: string\n\n  /**\n   * Remote password\n   */\n  remote?: string\n\n  /**\n   * Enable inspect plugin\n   */\n  inspect?: boolean\n\n  /**\n   * Build with --download option\n   */\n  download?: boolean\n\n  /**\n   * Base URL in dev or build mode\n   */\n  base?: string\n\n  /**\n   * Exclude speaker notes from the built output\n   */\n  withoutNotes?: boolean\n}\n\nexport interface ResolvedSlidevOptions extends RootsInfo, SlidevEntryOptions {\n  data: SlidevData\n  themeRaw: string\n  themeRoots: string[]\n  addonRoots: string[]\n  /**\n   * =`[...themeRoots, ...addonRoots, userRoot]` (`clientRoot` excluded)\n   */\n  roots: string[]\n  mode: 'dev' | 'build' | 'export'\n  utils: ResolvedSlidevUtils\n}\n\nexport interface ResolvedSlidevUtils {\n  shiki: ShorthandsBundle<string, string>\n  shikiOptions: MarkdownItShikiOptions & CodeOptionsThemes\n  katexOptions: KatexOptions | null\n  indexHtml: string\n  define: Record<string, string>\n  iconsResolvePath: string[]\n  isMonacoTypesIgnored: (pkg: string) => boolean\n  getLayouts: () => Promise<Record<string, string>>\n}\n\nexport interface SlidevServerOptions {\n  /**\n   * @returns `false` if server should be restarted\n   */\n  loadData?: (loadedSource: Record<string, string>) => Promise<SlidevData | false>\n}\n"
  },
  {
    "path": "packages/types/src/setups.ts",
    "content": "import type { Awaitable } from '@antfu/utils'\nimport type { KatexOptions } from 'katex'\nimport type { MermaidConfig } from 'mermaid'\nimport type * as monaco from 'monaco-editor'\nimport type { BuiltinLanguage, BuiltinTheme, CodeOptionsMeta, CodeOptionsThemes, CodeToHastOptionsCommon, LanguageInput, LanguageRegistration, MaybeArray } from 'shiki'\nimport type { VitePluginConfig as UnoCssConfig } from 'unocss/vite'\nimport type { PluginOption as VitePluginOption } from 'vite'\nimport type { App, ComputedRef, Ref } from 'vue'\nimport type { Router, RouteRecordRaw } from 'vue-router'\nimport type { CodeRunnerProviders } from './code-runner'\nimport type { ContextMenuItem } from './context-menu'\nimport type { ResolvedSlidevOptions } from './options'\nimport type { MarkdownTransformer } from './transform'\nimport type { SlidevPreparserExtension } from './types'\n\nexport interface AppContext {\n  app: App\n  router: Router\n}\n\nexport interface MonacoSetupReturn {\n  editorOptions?: monaco.editor.IEditorOptions\n}\n\nexport interface NavOperations {\n  next: () => void\n  prev: () => Promise<void>\n  nextSlide: () => void\n  prevSlide: () => Promise<void>\n  go: (index: number) => void\n  goFirst: () => void\n  goLast: () => void\n  downloadPDF: () => Promise<void>\n  toggleDark: () => void\n  toggleOverview: () => void\n  toggleDrawing: () => void\n  escapeOverview: () => void\n  showGotoDialog: () => void\n}\n\nexport interface ShortcutOptions {\n  key: string | Ref<boolean>\n  fn?: () => void\n  autoRepeat?: boolean\n  name?: string\n}\n\nexport interface ShikiContext {\n  /**\n   * @deprecated Pass directly the theme name it's supported by Shiki.\n   * For custom themes, load it manually via `JSON.parse(fs.readFileSync(path, 'utf-8'))` and pass the raw JSON object instead.\n   */\n  loadTheme: (path: string) => Promise<any>\n}\n\nexport type ShikiSetupReturn\n  = Partial<\n    & Omit<CodeToHastOptionsCommon<BuiltinLanguage>, 'lang'>\n    & CodeOptionsThemes<BuiltinTheme>\n    & CodeOptionsMeta\n    & {\n      langs: (MaybeArray<LanguageRegistration> | BuiltinLanguage)[] | Record<string, LanguageInput>\n    }\n  >\n\nexport interface TransformersSetupReturn {\n  pre: (MarkdownTransformer | false)[]\n  preCodeblock: (MarkdownTransformer | false)[]\n  postCodeblock: (MarkdownTransformer | false)[]\n  post: (MarkdownTransformer | false)[]\n}\n\n// node side\nexport type ShikiSetup = (shiki: ShikiContext) => Awaitable<ShikiSetupReturn | void>\nexport type KatexSetup = () => Awaitable<Partial<KatexOptions> | void>\nexport type UnoSetup = () => Awaitable<Partial<UnoCssConfig> | void>\nexport type TransformersSetup = () => Awaitable<Partial<TransformersSetupReturn>>\nexport type PreparserSetup = (context: {\n  filepath: string\n  headmatter: Record<string, unknown>\n  mode?: string\n}) => Awaitable<SlidevPreparserExtension[]>\nexport type VitePluginsSetup = (options: ResolvedSlidevOptions) => VitePluginOption\n\n// client side\nexport type MonacoSetup = (m: typeof monaco) => Awaitable<MonacoSetupReturn | void>\nexport type AppSetup = (context: AppContext) => Awaitable<void>\nexport type RootSetup = () => Awaitable<void>\nexport type RoutesSetup = (routes: RouteRecordRaw[]) => RouteRecordRaw[]\nexport type MermaidSetup = () => Awaitable<Partial<MermaidConfig> | void>\nexport type MermaidRenderFn = (code: string, options: Record<string, any>) => Awaitable<string>\nexport type MermaidRendererSetup = () => Awaitable<MermaidRenderFn | void>\nexport type ShortcutsSetup = (nav: NavOperations, defaultShortcuts: ShortcutOptions[]) => Array<ShortcutOptions>\nexport type CodeRunnersSetup = (runners: CodeRunnerProviders) => Awaitable<CodeRunnerProviders | void>\nexport type ContextMenuSetup = (items: ComputedRef<ContextMenuItem[]>) => ComputedRef<ContextMenuItem[]>\n\nfunction defineSetup<Fn>(fn: Fn) {\n  return fn\n}\n\nexport const defineShikiSetup = defineSetup<ShikiSetup>\nexport const defineUnoSetup = defineSetup<UnoSetup>\nexport const defineMonacoSetup = defineSetup<MonacoSetup>\nexport const defineAppSetup = defineSetup<AppSetup>\nexport const defineRootSetup = defineSetup<RootSetup>\nexport const defineRoutesSetup = defineSetup<RoutesSetup>\nexport const defineMermaidSetup = defineSetup<MermaidSetup>\nexport const defineMermaidRendererSetup = defineSetup<MermaidRendererSetup>\nexport const defineKatexSetup = defineSetup<KatexSetup>\nexport const defineShortcutsSetup = defineSetup<ShortcutsSetup>\nexport const defineTransformersSetup = defineSetup<TransformersSetup>\nexport const definePreparserSetup = defineSetup<PreparserSetup>\nexport const defineVitePluginsSetup = defineSetup<VitePluginsSetup>\nexport const defineCodeRunnersSetup = defineSetup<CodeRunnersSetup>\nexport const defineContextMenuSetup = defineSetup<ContextMenuSetup>\n"
  },
  {
    "path": "packages/types/src/toc.ts",
    "content": "export interface TocItem {\n  no: number\n  active?: boolean\n  activeParent?: boolean\n  children: TocItem[]\n  hasActiveParent?: boolean\n  level: number\n  titleLevel: number\n  path: string\n  hideInToc?: boolean\n  title?: string\n}\n"
  },
  {
    "path": "packages/types/src/transform.ts",
    "content": "import type { Awaitable } from '@antfu/utils'\nimport type MagicString from 'magic-string-stack'\nimport type { ResolvedSlidevOptions } from './options'\nimport type { SlideInfo } from './types'\n\nexport interface MarkdownTransformContext {\n  /**\n   * The magic string instance for the current markdown content\n   */\n  s: MagicString\n\n  /**\n   * The slide info of the current slide\n   */\n  slide: SlideInfo\n\n  /**\n   * Resolved Slidev options\n   */\n  options: ResolvedSlidevOptions\n}\n\nexport type MarkdownTransformer = (ctx: MarkdownTransformContext) => Awaitable<void>\n"
  },
  {
    "path": "packages/types/src/types.ts",
    "content": "import type { Component } from 'vue'\nimport type { RouteComponent, RouteMeta } from 'vue-router'\nimport type YAML from 'yaml'\nimport type { SlidevConfig } from './config'\n\nexport type FrontmatterStyle = 'frontmatter' | 'yaml'\n\nexport interface SlideInfoBase {\n  revision: string\n  frontmatter: Record<string, any>\n  content: string\n  frontmatterRaw?: string\n  note?: string\n  title?: string\n  level?: number\n  /**\n   * Image URLs extracted from the slide content (frontmatter, markdown, Vue components, CSS).\n   * Populated at parse time to survive build-mode content stripping.\n   */\n  images?: string[]\n}\n\nexport interface SourceSlideInfo extends SlideInfoBase {\n  /**\n   * The filepath of the markdown file\n   */\n  filepath: string\n  /**\n   * The index of the slide in the markdown file\n   */\n  index: number\n  /**\n   * The range of the slide in the markdown file\n   */\n  start: number\n  contentStart: number\n  end: number\n  raw: string\n  /**\n   * Raw content before being processed by preparsers (if any)\n   */\n  contentRaw: string\n  /**\n   * Slides imported by this slide.\n   */\n  imports?: SourceSlideInfo[]\n  frontmatterDoc?: YAML.Document<YAML.Node, true>\n  frontmatterStyle?: FrontmatterStyle\n}\n\nexport interface SlideInfo extends SlideInfoBase {\n  /**\n   * The index of the slide in the presentation\n   */\n  index: number\n  /**\n   * The importers of this slide. `[]` if this slide is the entry markdown file\n   */\n  importChain?: SourceSlideInfo[]\n  /**\n   * The source slide where the content is from\n   */\n  source: SourceSlideInfo\n  noteHTML?: string\n}\n\n/**\n * Editable fields for a slide\n */\nexport type SlidePatch = Partial<Pick<SlideInfoBase, 'content' | 'note' | 'frontmatterRaw'>> & {\n  skipHmr?: boolean\n  /**\n   * The frontmatter patch (only the changed fields)\n   * `null` to remove a field\n   */\n  frontmatter?: Record<string, any>\n}\n\n/**\n * Metadata for \"slidev\" field in themes' package.json\n */\nexport interface SlidevThemeMeta {\n  defaults?: Partial<SlidevConfig>\n  colorSchema?: 'dark' | 'light' | 'both'\n  highlighter?: 'shiki'\n}\n\nexport type SlidevThemeConfig = Record<string, string | number>\n\nexport interface SlidevDetectedFeatures {\n  katex: boolean\n  /**\n   * `false` or referenced module specifiers\n   */\n  monaco: false | {\n    types: string[]\n    deps: string[]\n  }\n  tweet: boolean\n  mermaid: boolean\n}\n\nexport interface SlidevMarkdown {\n  filepath: string\n  raw: string\n  /**\n   * All slides in this markdown file\n   */\n  slides: SourceSlideInfo[]\n  errors?: { row: number, message: string }[]\n}\n\nexport interface SlidevData {\n  /**\n   * Slides that should be rendered (disabled slides excluded)\n   */\n  slides: SlideInfo[]\n  entry: SlidevMarkdown\n  config: SlidevConfig\n  headmatter: Record<string, unknown>\n  features: SlidevDetectedFeatures\n  themeMeta?: SlidevThemeMeta\n  markdownFiles: Record<string, SlidevMarkdown>\n  /**\n   * From watched files to indexes of slides that must be reloaded regardless of the loaded content\n   */\n  watchFiles: Record<string, Set<number>>\n}\n\nexport interface SlidevPreparserExtension {\n  name?: string\n  transformRawLines?: (lines: string[]) => Promise<void> | void\n  transformSlide?: (content: string, frontmatter: any) => Promise<string | undefined>\n  transformNote?: (note: string | undefined, frontmatter: any) => Promise<string | undefined>\n}\n\nexport type PreparserExtensionLoader = (headmatter: Record<string, unknown>, filepath: string, mode?: string) => Promise<SlidevPreparserExtension[]>\n\nexport type RenderContext = 'none' | 'slide' | 'overview' | 'presenter' | 'previewNext'\n\nexport interface SlideRoute {\n  no: number\n  meta: RouteMeta & Required<Pick<RouteMeta, 'slide'>>\n  /**\n   * load the slide component itself\n   */\n  load: () => Promise<{ default: RouteComponent }>\n  /**\n   * Wrapped async component\n   */\n  component: Component\n}\n"
  },
  {
    "path": "packages/types/src/vite.ts",
    "content": "import type { ArgumentsType } from '@antfu/utils'\nimport type Vue from '@vitejs/plugin-vue'\nimport type VueJsx from '@vitejs/plugin-vue-jsx'\nimport type { VitePluginConfig as UnoCSSConfig } from 'unocss/vite'\nimport type Icons from 'unplugin-icons/vite'\nimport type Components from 'unplugin-vue-components/vite'\nimport type Markdown from 'unplugin-vue-markdown/vite'\nimport type { ViteInspectOptions } from 'vite-plugin-inspect'\nimport type RemoteAssets from 'vite-plugin-remote-assets'\nimport type { ViteStaticCopyOptions } from 'vite-plugin-static-copy'\nimport type ServerRef from 'vite-plugin-vue-server-ref'\n\nexport interface SlidevPluginOptions {\n  vue?: ArgumentsType<typeof Vue>[0]\n  vuejsx?: ArgumentsType<typeof VueJsx>[0]\n  markdown?: ArgumentsType<typeof Markdown>[0]\n  components?: ArgumentsType<typeof Components>[0]\n  icons?: ArgumentsType<typeof Icons>[0]\n  remoteAssets?: ArgumentsType<typeof RemoteAssets>[0]\n  serverRef?: ArgumentsType<typeof ServerRef>[0]\n  unocss?: UnoCSSConfig\n  staticCopy?: ViteStaticCopyOptions\n  inspect?: ViteInspectOptions\n}\n\n// extend vite.config.ts\ndeclare module 'vite' {\n  interface UserConfig {\n    /**\n     * Custom internal plugin options for Slidev (advanced)\n     *\n     * See https://github.com/slidevjs/slidev/blob/main/packages/slidev/node/options.ts#L50\n     */\n    slidev?: SlidevPluginOptions\n  }\n}\n"
  },
  {
    "path": "packages/types/tsdown.config.ts",
    "content": "export { default } from '../../tsdown.config.ts'\n"
  },
  {
    "path": "packages/vscode/.vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\nnode_modules/**\nlanguage-server/**\nscripts/**\nsrc/**\ntsdown.config.ts\n**/*.map\nsyntaxes/codeblock-patch.ts\nsyntaxes/slidev.example.md\nsyntaxes/tsconfig.json\nsyntaxes/.vscode\n"
  },
  {
    "path": "packages/vscode/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020-PRESENT Slidev Team\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": "packages/vscode/README.md",
    "content": "<br>\n<p align=\"center\">\n<a href=\"https://github.com/slidevjs/slidev\" target=\"_blank\">\n<img src=\"https://github.com/slidevjs/slidev/blob/main/assets/logo-for-vscode.png?raw=true\" alt=\"Slidev\" width=\"350\"/>\n</a>\n</p>\n\n<p align=\"center\">\nPresentation <b>slide</b>s for <b>dev</b>elopers<br>\n<sup><del>who use VS Code 🤣</del></sup>\n</p>\n\n<p align=\"center\">\n<a href=\"https://marketplace.visualstudio.com/items?itemName=antfu.slidev\" target=\"__blank\"><img src=\"https://img.shields.io/visual-studio-marketplace/v/antfu.slidev.svg?color=4EC5D4&amp;label=VS%20Code%20Marketplace&logo=visual-studio-code\" alt=\"Visual Studio Marketplace Version\" /></a>\n<a href=\"https://marketplace.visualstudio.com/items?itemName=antfu.slidev\" target=\"__blank\"><img src=\"https://img.shields.io/visual-studio-marketplace/d/antfu.slidev.svg?color=2B90B6\" alt=\"Visual Studio Marketplace Downloads\" /></a>\n<a href=\"https://kermanx.github.io/reactive-vscode/\" target=\"__blank\"><img src=\"https://img.shields.io/badge/made_with-reactive--vscode-%23007ACC?style=flat&labelColor=%23229863\"  alt=\"Made with reacrive-vscode\" /></a>\n</p>\n\n<br>\n\n### Features\n\n- Preview slides in the side panel\n- Slides tree view\n- Re-ordering slides\n- Folding for slide blocks\n- Multiple slides project support\n- Start dev server in one click\n\n<img width=\"974\" src=\"https://github.com/slidevjs/slidev/assets/63178754/2c9ba01a-d21f-4b33-b6b6-4e249873f865\">\n\n## Usage\n\nClick the `Slidev` icon in the activity bar to open the **Slidev panel**. In the Slidev panel, you can see the projects tree view, slides tree view, and the preview webview.\n\nIn the **projects tree view**, you can see all the Slidev projects in your workspace. You can click the item to open the corresponding file, and click the `eye` icon over it to switch the active project. The `add` icon allows you to load a slides project that wasn't scanned automatically.\n\nIn the **slides tree view**, you can see all the slides in the active project. You can click the item to move your cursor to the slide in the editor, and drag and drop to reorder the slides.\n\nIn the **preview webview**, you can click the `run-all` icon to start the dev server and click the `globe` icon to open the slides in the browser. Toggle `lock` icon to sync/unsync the preview navigation with the editor cursor.\n\nThere are also some **commands** you can use. Type `Slidev` in the command palette to see them.\n\n## Sponsors\n\n<p align=\"center\">\n  <a href=\"https://antfu.me/sponsor\">\n    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.png'/>\n  </a>\n</p>\n\n## License\n\nMIT License © 2021-2025 [Anthony Fu](https://github.com/antfu)\n"
  },
  {
    "path": "packages/vscode/language-server/bin.ts",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\nimport process from 'node:process'\nimport { version } from '../package.json'\n\nif (process.argv.includes('--version'))\n  console.log(version)\nelse\n  import('./index.js')\n"
  },
  {
    "path": "packages/vscode/language-server/index.ts",
    "content": "import { createConnection, createServer, createSimpleProject } from '@volar/language-server/node'\nimport { slidevLanguagePlugin } from './languagePlugin'\nimport { create as createPrettierService } from './prettierService'\nimport { create as createYamlService } from './volar-service-yaml'\n\nconst connection = createConnection()\nconst server = createServer(connection)\n\nconnection.onInitialize((params) => {\n  return server.initialize(\n    params,\n    createSimpleProject([slidevLanguagePlugin]),\n    [\n      createYamlService({\n        getLanguageSettings() {\n          return {\n            completion: true,\n            customTags: [],\n            format: false,\n            hover: true,\n            isKubernetes: false,\n            validate: true,\n            yamlVersion: '1.2',\n            parentSkeletonSelectedFirst: false,\n            disableDefaultProperties: true,\n            schemas: [\n              {\n                priority: 3,\n                fileMatch: ['volar-embedded-content://frontmatter_0/**/*.md'],\n                uri: (new URL('../schema/headmatter.json', import.meta.url)).toString(),\n              },\n              {\n                priority: 2,\n                fileMatch: ['volar-embedded-content://**/*.md'],\n                uri: (new URL('../schema/frontmatter.json', import.meta.url)).toString(),\n              },\n            ],\n          }\n        },\n      }),\n      createPrettierService(),\n    ],\n  )\n})\n\nconnection.onInitialized(server.initialized)\n\nconnection.onShutdown(server.shutdown)\n\nconnection.listen()\n"
  },
  {
    "path": "packages/vscode/language-server/languagePlugin.ts",
    "content": "import type { SlidevMarkdown } from '@slidev/types'\nimport type { LanguagePlugin, VirtualCode } from '@volar/language-core'\nimport type { URI } from 'vscode-uri'\nimport { parseSync } from '@slidev/parser'\n\nexport const slidevLanguagePlugin: LanguagePlugin<URI> = {\n  getLanguageId() {\n    return undefined\n  },\n  createVirtualCode(uri, languageId, snapshot) {\n    if (languageId === 'markdown') {\n      const source = snapshot.getText(0, snapshot.getLength())\n      const parsed = parseSync(source, uri.fsPath, {\n        noParseYAML: true,\n        preserveCR: true,\n      })\n\n      return {\n        id: 'root',\n        languageId: 'markdown',\n        mappings: [],\n        embeddedCodes: [...getEmbeddedCodes(parsed)],\n        snapshot,\n      }\n    }\n  },\n}\n\nfunction* getEmbeddedCodes(parsed: SlidevMarkdown): Generator<VirtualCode> {\n  const lines = parsed.raw.split('\\n')\n  function lineToPos(line: number) {\n    let pos = 0\n    for (let i = 0; i <= line && i < lines.length; i++) {\n      pos += lines[i].length + 1\n    }\n    return pos\n  }\n  for (const { frontmatterRaw, start, contentStart, content, index } of parsed.slides) {\n    if (frontmatterRaw != null) {\n      yield {\n        id: `frontmatter_${index}`,\n        languageId: 'yaml',\n        snapshot: {\n          getText: (start, end) => frontmatterRaw.substring(start, end),\n          getLength: () => frontmatterRaw.length,\n          getChangeRange: () => undefined,\n        },\n        mappings: [{\n          sourceOffsets: [lineToPos(start)],\n          generatedOffsets: [0],\n          lengths: [frontmatterRaw.length],\n          data: {\n            verification: true,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: true,\n          },\n        }],\n        embeddedCodes: [],\n      }\n    }\n    if (content) {\n      yield {\n        id: `content_${index}`,\n        languageId: 'markdown',\n        snapshot: {\n          getText: (start, end) => content.substring(start, end),\n          getLength: () => content.length,\n          getChangeRange: () => undefined,\n        },\n        mappings: [{\n          sourceOffsets: [lineToPos(contentStart)],\n          generatedOffsets: [0],\n          lengths: [content.length],\n          data: {\n            verification: true,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: false,\n          },\n        }],\n        embeddedCodes: [],\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vscode/language-server/prettierService.ts",
    "content": "import prettier from 'prettier'\nimport { create as createPrettierPlugin } from 'volar-service-prettier'\n\nexport function create() {\n  return createPrettierPlugin(\n    prettier,\n    {\n      documentSelector: ['yaml'],\n      async getFormattingOptions(_prettier, _document, formatOptions) {\n        return {\n          parser: 'yaml',\n          tabWidth: formatOptions.tabSize,\n          useTabs: !formatOptions.insertSpaces,\n        }\n      },\n    },\n  )\n}\n"
  },
  {
    "path": "packages/vscode/language-server/protocol.ts",
    "content": "export * from '@volar/language-server/protocol'\n"
  },
  {
    "path": "packages/vscode/language-server/volar-service-yaml.ts",
    "content": "// Vendored from https://github.com/volarjs/services/tree/master/packages/yaml\n// Await https://github.com/volarjs/services/pull/103\n\nimport type { Disposable, DocumentSelector, LanguageServiceContext, LanguageServicePlugin, LanguageServicePluginInstance, ProviderResult } from '@volar/language-service'\nimport type { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI, Utils } from 'vscode-uri'\nimport * as yaml from 'yaml-language-server'\n\nexport interface Provide {\n  'yaml/languageService': () => yaml.LanguageService\n}\n\nfunction noop(): undefined { }\n\n/**\n * Create a Volar language service for YAML documents.\n */\nexport function create({\n  documentSelector = ['yaml'],\n  getWorkspaceContextService = (context) => {\n    return {\n      resolveRelativePath(relativePath, resource) {\n        const base = resource.substring(0, resource.lastIndexOf('/') + 1)\n        let baseUri = URI.parse(base)\n        const decoded = context.decodeEmbeddedDocumentUri(baseUri)\n        if (decoded) {\n          baseUri = decoded[0]\n        }\n        return Utils.resolvePath(baseUri, relativePath).toString()\n      },\n    }\n  },\n  getLanguageSettings = () => {\n    return {\n      completion: true,\n      customTags: [],\n      format: true,\n      hover: true,\n      isKubernetes: false,\n      validate: true,\n      yamlVersion: '1.2',\n    }\n  },\n  onDidChangeLanguageSettings = () => {\n    return { dispose() { } }\n  },\n}: {\n  documentSelector?: DocumentSelector\n  getWorkspaceContextService?: (context: LanguageServiceContext) => yaml.WorkspaceContextService\n  getLanguageSettings?: (context: LanguageServiceContext) => ProviderResult<yaml.LanguageSettings>\n  onDidChangeLanguageSettings?: (listener: () => void, context: LanguageServiceContext) => Disposable\n} = {}): LanguageServicePlugin {\n  return {\n    name: 'yaml',\n    capabilities: {\n      codeActionProvider: {},\n      codeLensProvider: {\n        resolveProvider: false,\n      },\n      completionProvider: {\n        triggerCharacters: [' ', ':'],\n      },\n      definitionProvider: true,\n      diagnosticProvider: {\n        interFileDependencies: true,\n        workspaceDiagnostics: false,\n      },\n      documentOnTypeFormattingProvider: {\n        triggerCharacters: ['\\n'],\n      },\n      documentSymbolProvider: true,\n      hoverProvider: true,\n      documentLinkProvider: {},\n      foldingRangeProvider: true,\n      selectionRangeProvider: true,\n    },\n    create(context): LanguageServicePluginInstance<Provide> {\n      const ls = yaml.getLanguageService({\n        schemaRequestService: async uri => await context.env.fs?.readFile(URI.parse(uri)) ?? '',\n        telemetry: {\n          send: noop,\n          sendError: noop,\n          sendTrack: noop,\n        },\n        clientCapabilities: context.env?.clientCapabilities,\n        workspaceContext: getWorkspaceContextService(context),\n      })\n      let initializing: Promise<void> | undefined\n\n      const disposable = onDidChangeLanguageSettings(() => initializing = undefined, context)\n\n      return {\n        dispose() {\n          disposable.dispose()\n        },\n\n        provide: {\n          'yaml/languageService': () => ls,\n        },\n\n        provideCodeActions(document, range, context) {\n          return worker(document, () => {\n            return ls.getCodeAction(document, {\n              context,\n              range,\n              textDocument: document,\n            })\n          })\n        },\n\n        // provideCodeLenses(document) {\n        //   return worker(document, () => {\n        //     return ls.getCodeLens(document)\n        //   })\n        // },\n\n        provideCompletionItems(document, position) {\n          return worker(document, () => {\n            return ls.doComplete(document, position, false)\n          })\n        },\n\n        provideDefinition(document, position) {\n          return worker(document, () => {\n            return ls.doDefinition(document, { position, textDocument: document })\n          })\n        },\n\n        provideDiagnostics(document) {\n          return worker(document, () => {\n            return ls.doValidation(document, false)\n          })\n        },\n\n        provideDocumentSymbols(document) {\n          return worker(document, () => {\n            return ls.findDocumentSymbols2(document, {})\n          })\n        },\n\n        provideHover(document, position) {\n          return worker(document, () => {\n            return ls.doHover(document, position)\n          })\n        },\n\n        provideDocumentLinks(document) {\n          return worker(document, () => {\n            return ls.findLinks(document)\n          })\n        },\n\n        provideFoldingRanges(document) {\n          return worker(document, () => {\n            return ls.getFoldingRanges(document, context.env.clientCapabilities?.textDocument?.foldingRange ?? {})\n          })\n        },\n\n        provideOnTypeFormattingEdits(document, position, key, options) {\n          return worker(document, () => {\n            return ls.doDocumentOnTypeFormatting(document, { ch: key, options, position, textDocument: document })\n          })\n        },\n\n        provideSelectionRanges(document, positions) {\n          return worker(document, () => {\n            return ls.getSelectionRanges(document, positions)\n          })\n        },\n\n        // resolveCodeLens(codeLens) {\n        //   return ls.resolveCodeLens(codeLens)\n        // },\n      }\n\n      async function worker<T>(document: TextDocument, callback: () => T): Promise<Awaited<T> | undefined> {\n        if (!matchDocument(documentSelector, document)) {\n          return\n        }\n\n        await (initializing ??= initialize())\n\n        return await callback()\n      }\n\n      async function initialize() {\n        const settings = await getLanguageSettings(context)\n        ls.configure(settings)\n      }\n    },\n  }\n}\n\nfunction matchDocument(selector: DocumentSelector, document: TextDocument) {\n  for (const sel of selector) {\n    if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) {\n      return true\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "packages/vscode/package.json",
    "content": "{\n  \"publisher\": \"antfu\",\n  \"name\": \"slidev\",\n  \"displayName\": \"Slidev\",\n  \"type\": \"module\",\n  \"preview\": true,\n  \"version\": \"52.14.1\",\n  \"private\": true,\n  \"description\": \"Slidev support for VS Code\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/slidevjs/slidev\"\n  },\n  \"categories\": [\n    \"Other\"\n  ],\n  \"main\": \"./dist/index.cjs\",\n  \"icon\": \"dist/res/logo.png\",\n  \"engines\": {\n    \"vscode\": \"^1.99.0\"\n  },\n  \"activationEvents\": [\n    \"workspaceContains:**/*.md\"\n  ],\n  \"contributes\": {\n    \"languages\": [\n      {\n        \"id\": \"markdown\",\n        \"configuration\": \"./syntaxes/language-configuration.json\"\n      },\n      {\n        \"id\": \"slidev\"\n      }\n    ],\n    \"grammars\": [\n      {\n        \"language\": \"slidev\",\n        \"scopeName\": \"source.slidev\",\n        \"path\": \"./syntaxes/slidev.tmLanguage.json\",\n        \"embeddedLanguages\": {\n          \"source.ts\": \"typescript\",\n          \"source.yaml\": \"yaml\"\n        }\n      },\n      {\n        \"path\": \"./syntaxes/markdown.json\",\n        \"scopeName\": \"inject-to-markdown.main.slidev\",\n        \"injectTo\": [\n          \"text.html.markdown\"\n        ],\n        \"embeddedLanguages\": {\n          \"source.slidev\": \"slidev\"\n        }\n      },\n      {\n        \"path\": \"./syntaxes/codeblock.json\",\n        \"scopeName\": \"inject-to-markdown.codeblock.slidev\",\n        \"injectTo\": [\n          \"text.html.markdown\"\n        ],\n        \"embeddedLanguages\": {\n          \"source.slidev\": \"slidev\",\n          \"source.ts\": \"typescript\"\n        }\n      },\n      {\n        \"path\": \"./syntaxes/codeblock-patch.json\",\n        \"scopeName\": \"inject-to-markdown.codeblock.patch.slidev\",\n        \"injectTo\": [\n          \"text.html.markdown\"\n        ],\n        \"embeddedLanguages\": {\n          \"source.slidev\": \"slidev\",\n          \"meta.embedded.block.html\": \"html\",\n          \"source.js\": \"javascript\",\n          \"source.css\": \"css\",\n          \"meta.embedded.block.frontmatter\": \"yaml\",\n          \"meta.embedded.block.css\": \"css\",\n          \"meta.embedded.block.ini\": \"ini\",\n          \"meta.embedded.block.java\": \"java\",\n          \"meta.embedded.block.lua\": \"lua\",\n          \"meta.embedded.block.makefile\": \"makefile\",\n          \"meta.embedded.block.perl\": \"perl\",\n          \"meta.embedded.block.r\": \"r\",\n          \"meta.embedded.block.ruby\": \"ruby\",\n          \"meta.embedded.block.php\": \"php\",\n          \"meta.embedded.block.sql\": \"sql\",\n          \"meta.embedded.block.vs_net\": \"vs_net\",\n          \"meta.embedded.block.xml\": \"xml\",\n          \"meta.embedded.block.xsl\": \"xsl\",\n          \"meta.embedded.block.yaml\": \"yaml\",\n          \"meta.embedded.block.dosbatch\": \"dosbatch\",\n          \"meta.embedded.block.clojure\": \"clojure\",\n          \"meta.embedded.block.coffee\": \"coffee\",\n          \"meta.embedded.block.c\": \"c\",\n          \"meta.embedded.block.cpp\": \"cpp\",\n          \"meta.embedded.block.diff\": \"diff\",\n          \"meta.embedded.block.dockerfile\": \"dockerfile\",\n          \"meta.embedded.block.go\": \"go\",\n          \"meta.embedded.block.groovy\": \"groovy\",\n          \"meta.embedded.block.pug\": \"jade\",\n          \"meta.embedded.block.javascript\": \"javascript\",\n          \"meta.embedded.block.json\": \"json\",\n          \"meta.embedded.block.jsonc\": \"jsonc\",\n          \"meta.embedded.block.latex\": \"latex\",\n          \"meta.embedded.block.less\": \"less\",\n          \"meta.embedded.block.objc\": \"objc\",\n          \"meta.embedded.block.scss\": \"scss\",\n          \"meta.embedded.block.perl6\": \"perl6\",\n          \"meta.embedded.block.powershell\": \"powershell\",\n          \"meta.embedded.block.python\": \"python\",\n          \"meta.embedded.block.rust\": \"rust\",\n          \"meta.embedded.block.scala\": \"scala\",\n          \"meta.embedded.block.shellscript\": \"shellscript\",\n          \"meta.embedded.block.typescript\": \"typescript\",\n          \"meta.embedded.block.typescriptreact\": \"typescriptreact\",\n          \"meta.embedded.block.csharp\": \"csharp\",\n          \"meta.embedded.block.fsharp\": \"fsharp\"\n        }\n      }\n    ],\n    \"commands\": [\n      {\n        \"command\": \"slidev.enable-extension\",\n        \"category\": \"Slidev\",\n        \"title\": \"Force enable Slidev extension\"\n      },\n      {\n        \"command\": \"slidev.disable-extension\",\n        \"category\": \"Slidev\",\n        \"title\": \"Force disable Slidev extension\"\n      },\n      {\n        \"command\": \"slidev.rescan-projects\",\n        \"category\": \"Slidev\",\n        \"title\": \"Rescan Slidev projects\",\n        \"icon\": \"$(refresh)\"\n      },\n      {\n        \"command\": \"slidev.choose-entry\",\n        \"category\": \"Slidev\",\n        \"title\": \"Choose active slides entry\"\n      },\n      {\n        \"command\": \"slidev.add-entry\",\n        \"category\": \"Slidev\",\n        \"title\": \"Add Slidev markdown files as projects\",\n        \"icon\": \"$(add)\"\n      },\n      {\n        \"command\": \"slidev.remove-entry\",\n        \"category\": \"Slidev\",\n        \"title\": \"Remove the given entry file from the active slides entries\",\n        \"icon\": \"$(close)\"\n      },\n      {\n        \"command\": \"slidev.set-as-active\",\n        \"category\": \"Slidev\",\n        \"title\": \"Set the given entry file as the active one\",\n        \"icon\": \"$(eye)\"\n      },\n      {\n        \"command\": \"slidev.prev\",\n        \"category\": \"Slidev\",\n        \"title\": \"Go to previous slide in this Markdown\",\n        \"icon\": \"$(chevron-up)\"\n      },\n      {\n        \"command\": \"slidev.next\",\n        \"category\": \"Slidev\",\n        \"title\": \"Go to next slide in this Markdown\",\n        \"icon\": \"$(chevron-down)\"\n      },\n      {\n        \"command\": \"slidev.refresh-preview\",\n        \"category\": \"Slidev\",\n        \"title\": \"Refresh preview page\",\n        \"icon\": \"$(refresh)\"\n      },\n      {\n        \"command\": \"slidev.config-port\",\n        \"category\": \"Slidev\",\n        \"title\": \"Configure preview port\"\n      },\n      {\n        \"command\": \"slidev.start-dev\",\n        \"category\": \"Slidev\",\n        \"title\": \"Start slidev dev server\",\n        \"icon\": \"$(run-all)\",\n        \"enablement\": \"slidev:hasActiveProject\"\n      },\n      {\n        \"command\": \"slidev.open-in-browser\",\n        \"category\": \"Slidev\",\n        \"title\": \"Open slides in browser\",\n        \"icon\": \"$(globe)\"\n      },\n      {\n        \"command\": \"slidev.preview-prev-click\",\n        \"category\": \"Slidev\",\n        \"title\": \"Navigate to prev click in preview window\",\n        \"icon\": \"$(arrow-left)\",\n        \"enablement\": \"slidev:preview:has-prev-click\"\n      },\n      {\n        \"command\": \"slidev.preview-next-click\",\n        \"category\": \"Slidev\",\n        \"title\": \"Navigate to next click in preview window\",\n        \"icon\": \"$(arrow-right)\",\n        \"enablement\": \"slidev:preview:has-next-click\"\n      },\n      {\n        \"command\": \"slidev.preview-prev-slide\",\n        \"category\": \"Slidev\",\n        \"title\": \"Navigate to prev slide in preview window\",\n        \"icon\": \"$(arrow-up)\",\n        \"enablement\": \"slidev:preview:has-prev-slide\"\n      },\n      {\n        \"command\": \"slidev.preview-next-slide\",\n        \"category\": \"Slidev\",\n        \"title\": \"Navigate to next slide in preview window\",\n        \"icon\": \"$(arrow-down)\",\n        \"enablement\": \"slidev:preview:has-next-slide\"\n      },\n      {\n        \"command\": \"slidev.enable-preview-sync\",\n        \"category\": \"Slidev\",\n        \"title\": \"Sync preview window with editor cursor\",\n        \"icon\": \"$(unlock)\",\n        \"enablement\": \"!slidev:preview:sync\"\n      },\n      {\n        \"command\": \"slidev.disable-preview-sync\",\n        \"category\": \"Slidev\",\n        \"title\": \"Unsync preview window with editor cursor\",\n        \"icon\": \"$(lock)\",\n        \"enablement\": \"slidev:preview:sync\"\n      }\n    ],\n    \"menus\": {\n      \"commandPalette\": [\n        {\n          \"command\": \"slidev.remove-entry\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"slidev.set-as-active\",\n          \"when\": \"false\"\n        }\n      ],\n      \"editor/title\": [\n        {\n          \"when\": \"slidev:enabled && slidev:editing-markdown\",\n          \"command\": \"slidev.prev\",\n          \"group\": \"navigation\"\n        },\n        {\n          \"when\": \"slidev:enabled && slidev:editing-markdown\",\n          \"command\": \"slidev.next\",\n          \"group\": \"navigation\"\n        }\n      ],\n      \"view/title\": [\n        {\n          \"command\": \"slidev.add-entry\",\n          \"when\": \"view =~ /slidev-projects-tree/\",\n          \"group\": \"navigation@1\"\n        },\n        {\n          \"command\": \"slidev.rescan-projects\",\n          \"when\": \"view =~ /slidev-projects-tree/\",\n          \"group\": \"navigation@2\"\n        },\n        {\n          \"command\": \"slidev.preview-prev-slide\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:ready && !slidev:preview:compat\",\n          \"group\": \"navigation@1\"\n        },\n        {\n          \"command\": \"slidev.preview-next-slide\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:ready && !slidev:preview:compat\",\n          \"group\": \"navigation@2\"\n        },\n        {\n          \"command\": \"slidev.preview-prev-click\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:ready && !slidev:preview:compat\",\n          \"group\": \"navigation@3\"\n        },\n        {\n          \"command\": \"slidev.preview-next-click\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:ready && !slidev:preview:compat\",\n          \"group\": \"navigation@4\"\n        },\n        {\n          \"command\": \"slidev.start-dev\",\n          \"when\": \"view =~ /slidev-preview/ && !slidev:preview:ready\",\n          \"group\": \"navigation@5\"\n        },\n        {\n          \"command\": \"slidev.open-in-browser\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:ready\",\n          \"group\": \"navigation@5\"\n        },\n        {\n          \"command\": \"slidev.refresh-preview\",\n          \"when\": \"view =~ /slidev-preview/\",\n          \"group\": \"navigation@6\"\n        },\n        {\n          \"command\": \"slidev.enable-preview-sync\",\n          \"when\": \"view =~ /slidev-preview/ && !slidev:preview:sync\",\n          \"group\": \"navigation@7\"\n        },\n        {\n          \"command\": \"slidev.disable-preview-sync\",\n          \"when\": \"view =~ /slidev-preview/ && slidev:preview:sync\",\n          \"group\": \"navigation@7\"\n        }\n      ],\n      \"view/item/context\": [\n        {\n          \"command\": \"slidev.set-as-active\",\n          \"when\": \"view == slidev-projects-tree && viewItem =~ /<inactive>/\",\n          \"group\": \"inline@1\"\n        },\n        {\n          \"command\": \"slidev.remove-entry\",\n          \"when\": \"view == slidev-projects-tree && viewItem =~ /<project>/\",\n          \"group\": \"inline@2\"\n        }\n      ]\n    },\n    \"configuration\": {\n      \"title\": \"Slidev\",\n      \"properties\": {\n        \"slidev.force-enabled\": {\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"scope\": \"window\",\n          \"description\": \"Force enable Slidev extension\",\n          \"default\": null\n        },\n        \"slidev.port\": {\n          \"type\": \"number\",\n          \"scope\": \"window\",\n          \"description\": \"The default port of Slidev server\",\n          \"default\": 3030\n        },\n        \"slidev.annotations\": {\n          \"type\": \"boolean\",\n          \"scope\": \"window\",\n          \"description\": \"Display annotations for slides markdown files\",\n          \"default\": true\n        },\n        \"slidev.annotations-line-numbers\": {\n          \"type\": \"boolean\",\n          \"scope\": \"window\",\n          \"description\": \"Display line numbers in code blocks\",\n          \"default\": true\n        },\n        \"slidev.preview-sync\": {\n          \"type\": \"boolean\",\n          \"scope\": \"window\",\n          \"description\": \"Sync preview window with editor cursor\",\n          \"default\": true\n        },\n        \"slidev.include\": {\n          \"type\": \"array\",\n          \"scope\": \"window\",\n          \"description\": \"Glob patterns to include slides entries\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"**/slides.md\"\n          ]\n        },\n        \"slidev.exclude\": {\n          \"type\": \"string\",\n          \"scope\": \"window\",\n          \"description\": \"A glob pattern to exclude slides entries\",\n          \"default\": \"**/node_modules/**\"\n        },\n        \"slidev.dev-command\": {\n          \"type\": \"string\",\n          \"scope\": \"window\",\n          \"description\": \"The command to start Slidev dev server. See https://sli.dev/features/vscode-extension#dev-command\",\n          \"default\": \"npm exec -c 'slidev ${args}'\"\n        }\n      }\n    },\n    \"viewsContainers\": {\n      \"activitybar\": [\n        {\n          \"id\": \"slidev\",\n          \"title\": \"Slidev\",\n          \"icon\": \"dist/res/logo-mono.svg\"\n        }\n      ]\n    },\n    \"views\": {\n      \"slidev\": [\n        {\n          \"id\": \"slidev-projects-tree\",\n          \"name\": \"Projects\",\n          \"visibility\": \"collapsed\",\n          \"initialSize\": 1,\n          \"when\": \"slidev:enabled\"\n        },\n        {\n          \"id\": \"slidev-slides-tree\",\n          \"name\": \"Slides\",\n          \"visibility\": \"visible\",\n          \"initialSize\": 3,\n          \"when\": \"slidev:enabled\"\n        },\n        {\n          \"type\": \"webview\",\n          \"id\": \"slidev-preview\",\n          \"name\": \"Preview\",\n          \"visibility\": \"visible\",\n          \"initialSize\": 3,\n          \"when\": \"slidev:enabled\"\n        }\n      ]\n    },\n    \"viewsWelcome\": [\n      {\n        \"view\": \"slidev-slides-tree\",\n        \"contents\": \"No active slides entry.\\n[Choose one](command:slidev.choose-entry)\"\n      }\n    ],\n    \"languageModelTools\": [\n      {\n        \"name\": \"slidev_getActiveSlide\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"getActiveSlide\",\n        \"displayName\": \"Get Active Slide\",\n        \"modelDescription\": \"Get the information of the active slide the user is currently focused on in a Slidev presentation.\",\n        \"userDescription\": \"Get the information of the active slide in a Slidev presentation.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(debug-stackframe-active)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {}\n        }\n      },\n      {\n        \"name\": \"slidev_getSlideContent\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"getSlideContent\",\n        \"displayName\": \"Get Slide Content\",\n        \"modelDescription\": \"Get the content of a specific slide in a Slidev presentation by providing the slide number.\",\n        \"userDescription\": \"Get the content of a specific slide in a Slidev presentation by providing the slide number.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(file-code)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"entrySlidePath\": {\n              \"type\": \"string\",\n              \"description\": \"The path to the Slidev entry file (e.g., `./slides.md`). Empty string means the active slide entry.\",\n              \"default\": \"$ACTIVE_SLIDE_ENTRY\"\n            },\n            \"slideNo\": {\n              \"type\": \"number\",\n              \"description\": \"The slide number to retrieve content from. Starts from 1. Hidden slides are not counted.\"\n            }\n          }\n        }\n      },\n      {\n        \"name\": \"slidev_getAllSlideTitles\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"getAllSlideTitles\",\n        \"displayName\": \"Get All Slide Titles\",\n        \"modelDescription\": \"Get the list of all slide titles in the specified Slidev project.\",\n        \"userDescription\": \"Get the list of all slide titles in the specified Slidev project.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(list-unordered)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"entrySlidePath\": {\n              \"type\": \"string\",\n              \"description\": \"The path to the Slidev entry file (e.g., ./slides.md). Empty string means the active slide entry.\",\n              \"default\": \"$ACTIVE_SLIDE_ENTRY\"\n            }\n          }\n        }\n      },\n      {\n        \"name\": \"slidev_findSlideNoByTitle\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"findSlideNoByTitle\",\n        \"displayName\": \"Find Slide Number by Title\",\n        \"modelDescription\": \"Find the slide number in the specified Slidev project by its title.\",\n        \"userDescription\": \"Find the slide number in the specified Slidev project by its title.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(search)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"entrySlidePath\": {\n              \"type\": \"string\",\n              \"description\": \"The path to the Slidev entry file (e.g., ./slides.md). Empty string means the active slide entry.\",\n              \"default\": \"$ACTIVE_SLIDE_ENTRY\"\n            },\n            \"title\": {\n              \"type\": \"string\",\n              \"description\": \"The title of the slide to search for.\"\n            }\n          }\n        }\n      },\n      {\n        \"name\": \"slidev_listEntries\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"listEntries\",\n        \"displayName\": \"List All Loaded Slidev Entries\",\n        \"modelDescription\": \"Get all loaded Slidev project entry file paths.\",\n        \"userDescription\": \"Get all loaded Slidev project entry file paths.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(file-directory)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {}\n        }\n      },\n      {\n        \"name\": \"slidev_getPreviewPort\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"getPreviewPort\",\n        \"displayName\": \"Get Project Preview Port\",\n        \"modelDescription\": \"Get the preview port number of the specified Slidev project.\",\n        \"userDescription\": \"Get the preview port number of the specified Slidev project.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(plug)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"entrySlidePath\": {\n              \"type\": \"string\",\n              \"description\": \"The path to the Slidev entry file (e.g., ./slides.md). Empty string means the active slide entry.\",\n              \"default\": \"$ACTIVE_SLIDE_ENTRY\"\n            }\n          }\n        }\n      },\n      {\n        \"name\": \"slidev_chooseEntry\",\n        \"tags\": [\n          \"slidev\"\n        ],\n        \"toolReferenceName\": \"chooseEntry\",\n        \"displayName\": \"Choose Active Slidev Entry\",\n        \"modelDescription\": \"Switch the active Slidev project entry to the specified entry file path.\",\n        \"userDescription\": \"Switch the active Slidev project entry to the specified entry file path.\",\n        \"canBeReferencedInPrompt\": true,\n        \"icon\": \"$(arrow-swap)\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"entrySlidePath\": {\n              \"type\": \"string\",\n              \"description\": \"The path to the Slidev entry file to activate (e.g., ./slides.md).\",\n              \"default\": \"\"\n            }\n          },\n          \"required\": [\n            \"entrySlidePath\"\n          ]\n        }\n      }\n    ]\n  },\n  \"scripts\": {\n    \"publish\": \"tsx scripts/publish.ts\",\n    \"pack\": \"vsce package --no-dependencies\",\n    \"prepare\": \"tsx scripts/schema.ts\",\n    \"build\": \"tsdown --env.NODE_ENV production --treeshake --minify\",\n    \"dev\": \"nr prepare && tsdown --watch ./src --watch ./language-server --env.NODE_ENV development\",\n    \"vscode:prepublish\": \"nr build\"\n  },\n  \"devDependencies\": {\n    \"@antfu/utils\": \"catalog:frontend\",\n    \"@slidev/parser\": \"workspace:*\",\n    \"@slidev/types\": \"workspace:*\",\n    \"@types/node\": \"catalog:types\",\n    \"@types/vscode\": \"^1.99.0\",\n    \"@volar/language-server\": \"catalog:vscode\",\n    \"@volar/vscode\": \"catalog:vscode\",\n    \"get-port-please\": \"catalog:prod\",\n    \"mlly\": \"catalog:prod\",\n    \"ovsx\": \"catalog:dev\",\n    \"picomatch\": \"catalog:vscode\",\n    \"prettier\": \"catalog:vscode\",\n    \"reactive-vscode\": \"catalog:vscode\",\n    \"tm-grammars\": \"catalog:frontend\",\n    \"ts-json-schema-generator\": \"catalog:vscode\",\n    \"volar-service-prettier\": \"catalog:vscode\",\n    \"volar-service-yaml\": \"catalog:vscode\",\n    \"vscode-uri\": \"catalog:vscode\",\n    \"yaml-language-server\": \"catalog:vscode\"\n  }\n}\n"
  },
  {
    "path": "packages/vscode/schema/frontmatter.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"$ref\": \"#/definitions/Frontmatter\",\n  \"definitions\": {\n    \"Frontmatter\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"transition\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinSlideTransition\"\n            },\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"$ref\": \"#/definitions/TransitionGroupProps\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"description\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\",\n          \"markdownDescription\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\"\n        },\n        \"layout\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinLayouts\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ],\n          \"description\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\",\n          \"markdownDescription\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\"\n        },\n        \"class\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            {\n              \"type\": \"object\",\n              \"additionalProperties\": {}\n            }\n          ],\n          \"description\": \"Custom class added to the slide root element\",\n          \"markdownDescription\": \"Custom class added to the slide root element\"\n        },\n        \"clicks\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\"\n        },\n        \"clicksStart\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide to start\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide to start\",\n          \"default\": 0\n        },\n        \"preload\": {\n          \"type\": \"boolean\",\n          \"description\": \"Preload the slide when the previous slide is active\",\n          \"markdownDescription\": \"Preload the slide when the previous slide is active\",\n          \"default\": true\n        },\n        \"hide\": {\n          \"type\": \"boolean\",\n          \"description\": \"Completely hide and disable the slide\",\n          \"markdownDescription\": \"Completely hide and disable the slide\"\n        },\n        \"disabled\": {\n          \"type\": \"boolean\",\n          \"description\": \"Same as `hide`, completely hide and disable the slide\",\n          \"markdownDescription\": \"Same as `hide`, completely hide and disable the slide\"\n        },\n        \"hideInToc\": {\n          \"type\": \"boolean\",\n          \"description\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\",\n          \"markdownDescription\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\"\n        },\n        \"title\": {\n          \"type\": \"string\",\n          \"description\": \"Override the title for the `<TitleRenderer>` and `<Toc>` components Only if `title` has also been declared\",\n          \"markdownDescription\": \"Override the title for the `<TitleRenderer>` and `<Toc>` components\\nOnly if `title` has also been declared\"\n        },\n        \"level\": {\n          \"type\": \"number\",\n          \"description\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components Only if `title` has also been declared\",\n          \"markdownDescription\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components\\nOnly if `title` has also been declared\"\n        },\n        \"routeAlias\": {\n          \"type\": \"string\",\n          \"description\": \"Create a route alias that can be used in the URL or with the `<Link>` component\",\n          \"markdownDescription\": \"Create a route alias that can be used in the URL or with the `<Link>` component\"\n        },\n        \"zoom\": {\n          \"type\": \"number\",\n          \"description\": \"Custom zoom level for the slide\",\n          \"markdownDescription\": \"Custom zoom level for the slide\",\n          \"default\": 1\n        },\n        \"dragPos\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Store the positions of draggable elements Normally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\",\n          \"markdownDescription\": \"Store the positions of draggable elements\\nNormally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\"\n        },\n        \"src\": {\n          \"type\": \"string\",\n          \"description\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\",\n          \"markdownDescription\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\"\n        }\n      }\n    },\n    \"BuiltinSlideTransition\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"fade\",\n        \"fade-out\",\n        \"slide-up\",\n        \"slide-down\",\n        \"slide-left\",\n        \"slide-right\",\n        \"view-transition\"\n      ]\n    },\n    \"TransitionGroupProps\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"appear\": {\n          \"type\": \"boolean\"\n        },\n        \"persisted\": {\n          \"type\": \"boolean\"\n        },\n        \"tag\": {\n          \"type\": \"string\"\n        },\n        \"moveClass\": {\n          \"type\": \"string\"\n        },\n        \"css\": {\n          \"type\": \"boolean\"\n        },\n        \"duration\": {\n          \"anyOf\": [\n            {\n              \"type\": \"number\"\n            },\n            {\n              \"type\": \"object\",\n              \"properties\": {\n                \"enter\": {\n                  \"type\": \"number\"\n                },\n                \"leave\": {\n                  \"type\": \"number\"\n                }\n              },\n              \"required\": [\n                \"enter\",\n                \"leave\"\n              ]\n            }\n          ]\n        },\n        \"enterFromClass\": {\n          \"type\": \"string\"\n        },\n        \"enterActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"enterToClass\": {\n          \"type\": \"string\"\n        },\n        \"appearFromClass\": {\n          \"type\": \"string\"\n        },\n        \"appearActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"appearToClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveFromClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveToClass\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"BuiltinLayouts\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"404\",\n        \"center\",\n        \"cover\",\n        \"default\",\n        \"end\",\n        \"error\",\n        \"fact\",\n        \"full\",\n        \"iframe-left\",\n        \"iframe-right\",\n        \"iframe\",\n        \"image-left\",\n        \"image-right\",\n        \"image\",\n        \"intro\",\n        \"none\",\n        \"quote\",\n        \"section\",\n        \"statement\",\n        \"two-cols-header\",\n        \"two-cols\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vscode/schema/headmatter.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"$ref\": \"#/definitions/Headmatter\",\n  \"definitions\": {\n    \"Headmatter\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"layout\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinLayouts\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ],\n          \"description\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\",\n          \"markdownDescription\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\"\n        },\n        \"class\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            {\n              \"type\": \"object\",\n              \"additionalProperties\": {}\n            }\n          ],\n          \"description\": \"Custom class added to the slide root element\",\n          \"markdownDescription\": \"Custom class added to the slide root element\"\n        },\n        \"clicks\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\"\n        },\n        \"clicksStart\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide to start\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide to start\",\n          \"default\": 0\n        },\n        \"preload\": {\n          \"type\": \"boolean\",\n          \"description\": \"Preload the slide when the previous slide is active\",\n          \"markdownDescription\": \"Preload the slide when the previous slide is active\",\n          \"default\": true\n        },\n        \"hide\": {\n          \"type\": \"boolean\",\n          \"description\": \"Completely hide and disable the slide\",\n          \"markdownDescription\": \"Completely hide and disable the slide\"\n        },\n        \"disabled\": {\n          \"type\": \"boolean\",\n          \"description\": \"Same as `hide`, completely hide and disable the slide\",\n          \"markdownDescription\": \"Same as `hide`, completely hide and disable the slide\"\n        },\n        \"hideInToc\": {\n          \"type\": \"boolean\",\n          \"description\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\",\n          \"markdownDescription\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\"\n        },\n        \"level\": {\n          \"type\": \"number\",\n          \"description\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components Only if `title` has also been declared\",\n          \"markdownDescription\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components\\nOnly if `title` has also been declared\"\n        },\n        \"routeAlias\": {\n          \"type\": \"string\",\n          \"description\": \"Create a route alias that can be used in the URL or with the `<Link>` component\",\n          \"markdownDescription\": \"Create a route alias that can be used in the URL or with the `<Link>` component\"\n        },\n        \"zoom\": {\n          \"type\": \"number\",\n          \"description\": \"Custom zoom level for the slide\",\n          \"markdownDescription\": \"Custom zoom level for the slide\",\n          \"default\": 1\n        },\n        \"dragPos\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Store the positions of draggable elements Normally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\",\n          \"markdownDescription\": \"Store the positions of draggable elements\\nNormally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\"\n        },\n        \"src\": {\n          \"type\": \"string\",\n          \"description\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\",\n          \"markdownDescription\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\"\n        },\n        \"transition\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinSlideTransition\"\n            },\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"$ref\": \"#/definitions/TransitionGroupProps\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"description\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\",\n          \"markdownDescription\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\"\n        },\n        \"title\": {\n          \"type\": \"string\",\n          \"description\": \"Title of the slides\",\n          \"markdownDescription\": \"Title of the slides\"\n        },\n        \"titleTemplate\": {\n          \"type\": \"string\",\n          \"description\": \"String template to compose title\",\n          \"markdownDescription\": \"String template to compose title\",\n          \"default\": \"%s - Slidev\"\n        },\n        \"theme\": {\n          \"type\": \"string\",\n          \"description\": \"Theme to use for the slides\\n\\nSee https://sli.dev/guide/theme-addon#use-theme\",\n          \"markdownDescription\": \"Theme to use for the slides\\n\\nSee https://sli.dev/guide/theme-addon#use-theme\",\n          \"default\": \"default\"\n        },\n        \"addons\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"List of Slidev addons\",\n          \"markdownDescription\": \"List of Slidev addons\",\n          \"default\": []\n        },\n        \"remoteAssets\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Download remote assets in local using vite-plugin-remote-assets\",\n          \"markdownDescription\": \"Download remote assets in local using vite-plugin-remote-assets\",\n          \"default\": false\n        },\n        \"download\": {\n          \"type\": [\n            \"boolean\",\n            \"string\"\n          ],\n          \"description\": \"Show a download button in the SPA build, could also be a link to custom pdf\",\n          \"markdownDescription\": \"Show a download button in the SPA build,\\ncould also be a link to custom pdf\",\n          \"default\": false\n        },\n        \"codeCopy\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show a copy button in code blocks\",\n          \"markdownDescription\": \"Show a copy button in code blocks\",\n          \"default\": true\n        },\n        \"magicMoveCopy\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"final\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"always\"\n            }\n          ],\n          \"description\": \"Show copy button in magic move code blocks\\n\\n`'final'` for only show copy button on the final step `'always'` or `true` for show copy button on all steps\",\n          \"markdownDescription\": \"Show copy button in magic move code blocks\\n\\n`'final'` for only show copy button on the final step\\n`'always'` or `true` for show copy button on all steps\",\n          \"default\": true\n        },\n        \"author\": {\n          \"type\": \"string\",\n          \"description\": \"The author of the slides\",\n          \"markdownDescription\": \"The author of the slides\"\n        },\n        \"info\": {\n          \"type\": [\n            \"string\",\n            \"boolean\"\n          ],\n          \"description\": \"Information shows on the built SPA Can be a markdown string\",\n          \"markdownDescription\": \"Information shows on the built SPA\\nCan be a markdown string\",\n          \"default\": false\n        },\n        \"highlighter\": {\n          \"type\": \"string\",\n          \"const\": \"shiki\",\n          \"description\": \"Prefer highlighter\\n\\nSee https://sli.dev/custom/config-highlighter.html\",\n          \"markdownDescription\": \"Prefer highlighter\\n\\nSee https://sli.dev/custom/config-highlighter.html\",\n          \"default\": \"shiki\"\n        },\n        \"twoslash\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable Twoslash\",\n          \"markdownDescription\": \"Enable Twoslash\",\n          \"default\": true\n        },\n        \"lineNumbers\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show line numbers in code blocks\",\n          \"markdownDescription\": \"Show line numbers in code blocks\",\n          \"default\": false\n        },\n        \"colorSchema\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"dark\",\n            \"light\",\n            \"all\",\n            \"auto\"\n          ],\n          \"description\": \"Force slides color schema\",\n          \"markdownDescription\": \"Force slides color schema\",\n          \"default\": \"auto\"\n        },\n        \"routerMode\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"hash\",\n            \"history\"\n          ],\n          \"description\": \"Router mode for vue-router\",\n          \"markdownDescription\": \"Router mode for vue-router\",\n          \"default\": \"history\"\n        },\n        \"aspectRatio\": {\n          \"type\": [\n            \"number\",\n            \"string\"\n          ],\n          \"description\": \"Aspect ratio for slides should be like `16/9` or `1:1`\",\n          \"markdownDescription\": \"Aspect ratio for slides\\nshould be like `16/9` or `1:1`\",\n          \"default\": \"16/9\"\n        },\n        \"canvasWidth\": {\n          \"type\": \"number\",\n          \"description\": \"The actual width for slides canvas. unit in px.\",\n          \"markdownDescription\": \"The actual width for slides canvas.\\nunit in px.\",\n          \"default\": \"980\"\n        },\n        \"selectable\": {\n          \"type\": \"boolean\",\n          \"description\": \"Controls whether texts in slides are selectable\",\n          \"markdownDescription\": \"Controls whether texts in slides are selectable\",\n          \"default\": true\n        },\n        \"themeConfig\": {\n          \"$ref\": \"#/definitions/SlidevThemeConfig\",\n          \"description\": \"Configure for themes, will inject intro root styles as `--slidev-theme-x` for attribute `x`\\n\\nThis allows themes to have customization options in frontmatter Refer to themes' document for options avaliable\",\n          \"markdownDescription\": \"Configure for themes, will inject intro root styles as\\n`--slidev-theme-x` for attribute `x`\\n\\nThis allows themes to have customization options in frontmatter\\nRefer to themes' document for options avaliable\",\n          \"default\": {}\n        },\n        \"fonts\": {\n          \"$ref\": \"#/definitions/FontOptions\",\n          \"description\": \"Configure fonts for the slides and app\",\n          \"markdownDescription\": \"Configure fonts for the slides and app\",\n          \"default\": {}\n        },\n        \"favicon\": {\n          \"type\": \"string\",\n          \"description\": \"Configure the icon for app\",\n          \"markdownDescription\": \"Configure the icon for app\",\n          \"default\": \"https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png\"\n        },\n        \"drawings\": {\n          \"$ref\": \"#/definitions/DrawingsOptions\",\n          \"description\": \"Options for drawings\",\n          \"markdownDescription\": \"Options for drawings\",\n          \"default\": {}\n        },\n        \"plantUmlServer\": {\n          \"type\": \"string\",\n          \"description\": \"URL of PlantUML server used to render diagrams\",\n          \"markdownDescription\": \"URL of PlantUML server used to render diagrams\",\n          \"default\": \"https://www.plantuml.com/plantuml\"\n        },\n        \"record\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable slides recording\",\n          \"markdownDescription\": \"Enable slides recording\",\n          \"default\": \"dev\"\n        },\n        \"remote\": {\n          \"type\": [\n            \"string\",\n            \"boolean\"\n          ],\n          \"description\": \"Expose the server to inbound requests (listen to `0.0.0.0`)\\n\\nPass a string to set the password for accessing presenter mode.\",\n          \"markdownDescription\": \"Expose the server to inbound requests (listen to `0.0.0.0`)\\n\\nPass a string to set the password for accessing presenter mode.\",\n          \"default\": false\n        },\n        \"css\": {\n          \"type\": \"string\",\n          \"const\": \"unocss\",\n          \"description\": \"Engine for Atomic CSS\\n\\nSee https://unocss.dev/\",\n          \"markdownDescription\": \"Engine for Atomic CSS\\n\\nSee https://unocss.dev/\",\n          \"deprecated\": true,\n          \"default\": \"unocss\"\n        },\n        \"presenter\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable presenter mode\",\n          \"markdownDescription\": \"Enable presenter mode\",\n          \"default\": true\n        },\n        \"browserExporter\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable browser exporter\",\n          \"markdownDescription\": \"Enable browser exporter\",\n          \"default\": \"dev\"\n        },\n        \"htmlAttrs\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Attributes to apply to the HTML element\",\n          \"markdownDescription\": \"Attributes to apply to the HTML element\",\n          \"default\": {}\n        },\n        \"comark\": {\n          \"type\": \"boolean\",\n          \"description\": \"Suppport Comark syntax\\n\\nhttps://comark.dev/syntax/markdown\",\n          \"markdownDescription\": \"Suppport Comark syntax\\n\\nhttps://comark.dev/syntax/markdown\",\n          \"default\": false\n        },\n        \"mdc\": {\n          \"type\": \"boolean\",\n          \"deprecated\": \"MDC is now Comark. Use the `comark` option instead\",\n          \"default\": false\n        },\n        \"editor\": {\n          \"type\": \"boolean\",\n          \"description\": \"Enable built-in editor\",\n          \"markdownDescription\": \"Enable built-in editor\",\n          \"default\": true\n        },\n        \"contextMenu\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"description\": \"Enable context menu\",\n          \"markdownDescription\": \"Enable context menu\",\n          \"default\": true\n        },\n        \"wakeLock\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable wake lock\",\n          \"markdownDescription\": \"Enable wake lock\"\n        },\n        \"exportFilename\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"description\": \"Force the filename used when exporting the presentation. The extension, e.g. .pdf, gets automatically added.\",\n          \"markdownDescription\": \"Force the filename used when exporting the presentation.\\nThe extension, e.g. .pdf, gets automatically added.\",\n          \"default\": \"\"\n        },\n        \"monaco\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"description\": \"Enable Monaco\\n\\nSee https://sli.dev/custom/config-monaco.html\",\n          \"markdownDescription\": \"Enable Monaco\\n\\nSee https://sli.dev/custom/config-monaco.html\",\n          \"default\": true\n        },\n        \"monacoTypesSource\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"cdn\",\n            \"local\",\n            \"none\"\n          ],\n          \"description\": \"Where to load monaco types from\\n\\n- `cdn` - load from CDN with `@typescript/ata`\\n- `local` - load from local node_modules\",\n          \"markdownDescription\": \"Where to load monaco types from\\n\\n- `cdn` - load from CDN with `@typescript/ata`\\n- `local` - load from local node_modules\",\n          \"default\": \"local\"\n        },\n        \"monacoTypesAdditionalPackages\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Additional node packages to load as monaco types\",\n          \"markdownDescription\": \"Additional node packages to load as monaco types\",\n          \"default\": []\n        },\n        \"monacoTypesIgnorePackages\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Packages to ignore when loading monaco types\",\n          \"markdownDescription\": \"Packages to ignore when loading monaco types\",\n          \"default\": []\n        },\n        \"monacoRunAdditionalDeps\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Additional local modules to load as dependencies of monaco runnable\",\n          \"markdownDescription\": \"Additional local modules to load as dependencies of monaco runnable\",\n          \"default\": []\n        },\n        \"monacoRunUseStrict\": {\n          \"type\": \"boolean\",\n          \"description\": \"Whether to run monaco runnable code in strict mode\",\n          \"markdownDescription\": \"Whether to run monaco runnable code in strict mode\",\n          \"default\": true\n        },\n        \"seoMeta\": {\n          \"$ref\": \"#/definitions/SeoMeta\",\n          \"description\": \"Seo meta tags settings\",\n          \"markdownDescription\": \"Seo meta tags settings\",\n          \"default\": {}\n        },\n        \"notesAutoRuby\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Auto replace words with `<ruby>` tags in notes\",\n          \"markdownDescription\": \"Auto replace words with `<ruby>` tags in notes\",\n          \"default\": {}\n        },\n        \"duration\": {\n          \"type\": [\n            \"string\",\n            \"number\"\n          ],\n          \"description\": \"The expected duration of the slide\",\n          \"markdownDescription\": \"The expected duration of the slide\",\n          \"default\": \"30min\"\n        },\n        \"timer\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"stopwatch\",\n            \"countdown\"\n          ],\n          \"description\": \"Timer mode\",\n          \"markdownDescription\": \"Timer mode\",\n          \"default\": \"stopwatch\"\n        },\n        \"magicMoveDuration\": {\n          \"type\": \"number\",\n          \"description\": \"Duration for shiki magic move transitions in milliseconds\",\n          \"markdownDescription\": \"Duration for shiki magic move transitions in milliseconds\",\n          \"default\": 800\n        },\n        \"preloadImages\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"object\",\n              \"properties\": {\n                \"ahead\": {\n                  \"type\": \"number\"\n                }\n              }\n            }\n          ],\n          \"description\": \"Preload images extracted from slides for faster navigation.\\n\\n- `true` - enable with default look-ahead of 3 slides\\n- `false` - disable image preloading\\n- `{ ahead: number }` - enable with custom look-ahead window\",\n          \"markdownDescription\": \"Preload images extracted from slides for faster navigation.\\n\\n- `true` - enable with default look-ahead of 3 slides\\n- `false` - disable image preloading\\n- `{ ahead: number }` - enable with custom look-ahead window\",\n          \"default\": true\n        },\n        \"defaults\": {\n          \"$ref\": \"#/definitions/Frontmatter\",\n          \"description\": \"Default frontmatter options applied to all slides\",\n          \"markdownDescription\": \"Default frontmatter options applied to all slides\"\n        }\n      },\n      \"required\": [\n        \"transition\"\n      ]\n    },\n    \"BuiltinLayouts\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"404\",\n        \"center\",\n        \"cover\",\n        \"default\",\n        \"end\",\n        \"error\",\n        \"fact\",\n        \"full\",\n        \"iframe-left\",\n        \"iframe-right\",\n        \"iframe\",\n        \"image-left\",\n        \"image-right\",\n        \"image\",\n        \"intro\",\n        \"none\",\n        \"quote\",\n        \"section\",\n        \"statement\",\n        \"two-cols-header\",\n        \"two-cols\"\n      ]\n    },\n    \"BuiltinSlideTransition\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"fade\",\n        \"fade-out\",\n        \"slide-up\",\n        \"slide-down\",\n        \"slide-left\",\n        \"slide-right\",\n        \"view-transition\"\n      ]\n    },\n    \"TransitionGroupProps\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"appear\": {\n          \"type\": \"boolean\"\n        },\n        \"persisted\": {\n          \"type\": \"boolean\"\n        },\n        \"tag\": {\n          \"type\": \"string\"\n        },\n        \"moveClass\": {\n          \"type\": \"string\"\n        },\n        \"css\": {\n          \"type\": \"boolean\"\n        },\n        \"duration\": {\n          \"anyOf\": [\n            {\n              \"type\": \"number\"\n            },\n            {\n              \"type\": \"object\",\n              \"properties\": {\n                \"enter\": {\n                  \"type\": \"number\"\n                },\n                \"leave\": {\n                  \"type\": \"number\"\n                }\n              },\n              \"required\": [\n                \"enter\",\n                \"leave\"\n              ]\n            }\n          ]\n        },\n        \"enterFromClass\": {\n          \"type\": \"string\"\n        },\n        \"enterActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"enterToClass\": {\n          \"type\": \"string\"\n        },\n        \"appearFromClass\": {\n          \"type\": \"string\"\n        },\n        \"appearActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"appearToClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveFromClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveActiveClass\": {\n          \"type\": \"string\"\n        },\n        \"leaveToClass\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"SlidevThemeConfig\": {\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": [\n          \"string\",\n          \"number\"\n        ]\n      }\n    },\n    \"FontOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sans\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          ],\n          \"description\": \"Sans serif fonts (default fonts for most text)\",\n          \"markdownDescription\": \"Sans serif fonts (default fonts for most text)\"\n        },\n        \"serif\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          ],\n          \"description\": \"Serif fonts\",\n          \"markdownDescription\": \"Serif fonts\"\n        },\n        \"mono\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          ],\n          \"description\": \"Monospace fonts, for code blocks and etc.\",\n          \"markdownDescription\": \"Monospace fonts, for code blocks and etc.\"\n        },\n        \"custom\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          ],\n          \"description\": \"Load webfonts for custom CSS (does not apply anywhere by default)\",\n          \"markdownDescription\": \"Load webfonts for custom CSS (does not apply anywhere by default)\"\n        },\n        \"weights\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": [\n                  \"string\",\n                  \"number\"\n                ]\n              }\n            }\n          ],\n          \"description\": \"Weights for fonts\",\n          \"markdownDescription\": \"Weights for fonts\",\n          \"default\": [\n            200,\n            400,\n            600\n          ]\n        },\n        \"italic\": {\n          \"type\": \"boolean\",\n          \"description\": \"Import italic fonts\",\n          \"markdownDescription\": \"Import italic fonts\",\n          \"default\": false\n        },\n        \"provider\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\",\n            \"google\",\n            \"coollabs\"\n          ],\n          \"default\": \"google\"\n        },\n        \"webfonts\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Specify web fonts names, will detect from `sans`, `mono`, `serif` if not provided\",\n          \"markdownDescription\": \"Specify web fonts names, will detect from `sans`, `mono`, `serif` if not provided\"\n        },\n        \"local\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Specify local fonts names, be excluded from webfonts\",\n          \"markdownDescription\": \"Specify local fonts names, be excluded from webfonts\"\n        },\n        \"fallbacks\": {\n          \"type\": \"boolean\",\n          \"description\": \"Use fonts fallback\",\n          \"markdownDescription\": \"Use fonts fallback\",\n          \"default\": true\n        }\n      }\n    },\n    \"DrawingsOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"persist\": {\n          \"type\": [\n            \"boolean\",\n            \"string\"\n          ],\n          \"description\": \"Persist the drawings to disk Passing string to specify the directory (default to `.slidev/drawings`)\",\n          \"markdownDescription\": \"Persist the drawings to disk\\nPassing string to specify the directory (default to `.slidev/drawings`)\",\n          \"default\": false\n        },\n        \"enabled\": {\n          \"anyOf\": [\n            {\n              \"type\": \"boolean\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"dev\"\n            },\n            {\n              \"type\": \"string\",\n              \"const\": \"build\"\n            }\n          ],\n          \"default\": true\n        },\n        \"presenterOnly\": {\n          \"type\": \"boolean\",\n          \"description\": \"Only allow drawing from presenter mode\",\n          \"markdownDescription\": \"Only allow drawing from presenter mode\",\n          \"default\": false\n        },\n        \"syncAll\": {\n          \"type\": \"boolean\",\n          \"description\": \"Sync drawing for all instances\",\n          \"markdownDescription\": \"Sync drawing for all instances\",\n          \"default\": true\n        }\n      }\n    },\n    \"SeoMeta\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ogTitle\": {\n          \"type\": \"string\"\n        },\n        \"ogDescription\": {\n          \"type\": \"string\"\n        },\n        \"ogImage\": {\n          \"type\": \"string\"\n        },\n        \"ogUrl\": {\n          \"type\": \"string\"\n        },\n        \"twitterCard\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"summary\",\n            \"summary_large_image\",\n            \"app\",\n            \"player\"\n          ]\n        },\n        \"twitterSite\": {\n          \"type\": \"string\"\n        },\n        \"twitterTitle\": {\n          \"type\": \"string\"\n        },\n        \"twitterDescription\": {\n          \"type\": \"string\"\n        },\n        \"twitterImage\": {\n          \"type\": \"string\"\n        },\n        \"twitterUrl\": {\n          \"type\": \"string\"\n        }\n      },\n      \"description\": \"The following type should map to unhead MataFlat type\",\n      \"markdownDescription\": \"The following type should map to unhead MataFlat type\"\n    },\n    \"Frontmatter\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"transition\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinSlideTransition\"\n            },\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"$ref\": \"#/definitions/TransitionGroupProps\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"description\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\",\n          \"markdownDescription\": \"Page transition, powered by Vue's `<TransitionGroup/>`\\n\\nBuilt-in transitions:\\n- fade\\n- fade-out\\n- slide-left\\n- slide-right\\n- slide-up\\n- slide-down\\n- view-transition\\n\\nSee https://sli.dev/guide/animations.html#pages-transitions\\n\\nSee https://vuejs.org/guide/built-ins/transition.html\"\n        },\n        \"layout\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BuiltinLayouts\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ],\n          \"description\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\",\n          \"markdownDescription\": \"Slide layout to use\\n\\nDefault to 'cover' for the first slide, 'default' for the rest\"\n        },\n        \"class\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            {\n              \"type\": \"object\",\n              \"additionalProperties\": {}\n            }\n          ],\n          \"description\": \"Custom class added to the slide root element\",\n          \"markdownDescription\": \"Custom class added to the slide root element\"\n        },\n        \"clicks\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide\\n\\nWhen not specified, the clicks will be calculated by the usage of v-clicks\\n\\nSee https://sli.dev/guide/animations\"\n        },\n        \"clicksStart\": {\n          \"type\": \"number\",\n          \"description\": \"Manually specified the total clicks needed to this slide to start\",\n          \"markdownDescription\": \"Manually specified the total clicks needed to this slide to start\",\n          \"default\": 0\n        },\n        \"preload\": {\n          \"type\": \"boolean\",\n          \"description\": \"Preload the slide when the previous slide is active\",\n          \"markdownDescription\": \"Preload the slide when the previous slide is active\",\n          \"default\": true\n        },\n        \"hide\": {\n          \"type\": \"boolean\",\n          \"description\": \"Completely hide and disable the slide\",\n          \"markdownDescription\": \"Completely hide and disable the slide\"\n        },\n        \"disabled\": {\n          \"type\": \"boolean\",\n          \"description\": \"Same as `hide`, completely hide and disable the slide\",\n          \"markdownDescription\": \"Same as `hide`, completely hide and disable the slide\"\n        },\n        \"hideInToc\": {\n          \"type\": \"boolean\",\n          \"description\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\",\n          \"markdownDescription\": \"Hide the slide for the `<Toc>` components\\n\\nSee https://sli.dev/builtin/components#toc\"\n        },\n        \"title\": {\n          \"type\": \"string\",\n          \"description\": \"Override the title for the `<TitleRenderer>` and `<Toc>` components Only if `title` has also been declared\",\n          \"markdownDescription\": \"Override the title for the `<TitleRenderer>` and `<Toc>` components\\nOnly if `title` has also been declared\"\n        },\n        \"level\": {\n          \"type\": \"number\",\n          \"description\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components Only if `title` has also been declared\",\n          \"markdownDescription\": \"Override the title level for the `<TitleRenderer>` and `<Toc>` components\\nOnly if `title` has also been declared\"\n        },\n        \"routeAlias\": {\n          \"type\": \"string\",\n          \"description\": \"Create a route alias that can be used in the URL or with the `<Link>` component\",\n          \"markdownDescription\": \"Create a route alias that can be used in the URL or with the `<Link>` component\"\n        },\n        \"zoom\": {\n          \"type\": \"number\",\n          \"description\": \"Custom zoom level for the slide\",\n          \"markdownDescription\": \"Custom zoom level for the slide\",\n          \"default\": 1\n        },\n        \"dragPos\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Store the positions of draggable elements Normally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\",\n          \"markdownDescription\": \"Store the positions of draggable elements\\nNormally you don't need to set this manually\\n\\nSee https://sli.dev/features/draggable\"\n        },\n        \"src\": {\n          \"type\": \"string\",\n          \"description\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\",\n          \"markdownDescription\": \"Includes a markdown file\\n\\nSee https://sli.dev/guide/syntax.html#importing-slides\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vscode/scripts/publish.ts",
    "content": "import type { Options } from 'tinyexec'\nimport fs from 'node:fs/promises'\nimport process from 'node:process'\nimport { x } from 'tinyexec'\n\nasync function publish() {\n  const root = new URL('..', import.meta.url)\n  const rawJSON = await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8')\n  const pkg = JSON.parse(rawJSON)\n\n  if (pkg.version.includes('-')) {\n    console.warn('Skipping publish VS Code extension because the version contains a pre-release tag.')\n    return\n  }\n\n  if (!process.env.VSCE_TOKEN) {\n    console.error('Missing VSCE_TOKEN')\n    process.exit(1)\n  }\n  if (!process.env.OVSX_TOKEN) {\n    console.error('Missing OVSX_TOKEN')\n    process.exit(1)\n  }\n\n  console.log('Publishing VS Code extension...')\n\n  const options: Partial<Options> = {\n    nodeOptions: {\n      cwd: root,\n      stdio: 'inherit',\n    },\n    throwOnError: true,\n  }\n\n  await x('npm', ['run', 'build'], options)\n  console.log('\\nPublish to VSCE...\\n')\n  await x('npx', ['@vscode/vsce', 'publish', '--no-dependencies', '-p', process.env.VSCE_TOKEN!], options)\n  console.log('\\nPublish to OVSE...\\n')\n  await x('npx', ['ovsx', 'publish', '--no-dependencies', '-p', process.env.OVSX_TOKEN!], options)\n}\n\npublish()\n"
  },
  {
    "path": "packages/vscode/scripts/schema.ts",
    "content": "import fs from 'node:fs/promises'\nimport tsj from 'ts-json-schema-generator'\n\nconst program = tsj\n  .createGenerator({\n    path: '../../packages/types/src/frontmatter.ts',\n    tsconfig: '../../tsconfig.json',\n    markdownDescription: true,\n    additionalProperties: true,\n    jsDoc: 'extended',\n    skipTypeCheck: true,\n  })\n\nawait Promise.all([\n  fs.writeFile('./schema/headmatter.json', `${JSON.stringify(\n    program.createSchema('Headmatter'),\n    null,\n    2,\n  )}\\n`),\n\n  fs.writeFile('./schema/frontmatter.json', `${JSON.stringify(\n    program.createSchema('Frontmatter'),\n    null,\n    2,\n  )}\\n`),\n])\n"
  },
  {
    "path": "packages/vscode/src/commands.ts",
    "content": "import { relative } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport { useCommand } from 'reactive-vscode'\nimport { ConfigurationTarget, window, workspace } from 'vscode'\nimport { useDevServer } from './composables/useDevServer'\nimport { useFocusedSlide } from './composables/useFocusedSlide'\nimport { config } from './configs'\nimport { activeEntry, activeProject, addProject, projects, rescanProjects } from './projects'\nimport { findPossibleEntries } from './utils/findPossibleEntries'\nimport { getSlidesTitle } from './utils/getSlidesTitle'\nimport { usePreviewWebview } from './views/previewWebview'\n\nexport function useCommands() {\n  useCommand('slidev.enable-extension', () => config.update('force-enabled', true, ConfigurationTarget.Workspace))\n  useCommand('slidev.disable-extension', () => config.update('force-enabled', false, ConfigurationTarget.Workspace))\n\n  useCommand('slidev.rescan-projects', rescanProjects)\n\n  useCommand('slidev.choose-entry', async () => {\n    while (true) {\n      const addNewEntry = '$(add) Add new slides entry...'\n      const entry = await window.showQuickPick(\n        [...projects.keys(), addNewEntry],\n        {\n          title: 'Choose active slides entry.',\n        },\n      )\n      if (entry === addNewEntry) {\n        if (!await addEntry()) {\n          break\n        }\n      }\n      else if (entry) {\n        activeEntry.value = entry\n        break\n      }\n      else {\n        break\n      }\n    }\n  })\n\n  useCommand('slidev.add-entry', addEntry)\n\n  async function addEntry() {\n    const files = await findPossibleEntries()\n    const selected = await window.showQuickPick(files, {\n      title: 'Choose Markdown files to add as slides entries.',\n      canPickMany: true,\n    })\n    if (selected) {\n      for (const entry of selected)\n        await addProject(entry)\n      if (workspace.workspaceFolders) {\n        const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath\n        const relatives = selected.map(s => slash(relative(workspaceRoot, s)))\n        // write back to settings.json\n        await config.update('include', [...config.include, ...relatives])\n      }\n    }\n    return !!selected\n  }\n\n  useCommand('slidev.remove-entry', async (node: any) => {\n    const entry = slash(node.treeItem.resourceUri.fsPath)\n    if (activeEntry.value === entry)\n      activeEntry.value = null\n    projects.delete(entry)\n  })\n\n  useCommand('slidev.set-as-active', async (node: any) => {\n    const entry = slash(node.treeItem.resourceUri.fsPath)\n    activeEntry.value = entry\n  })\n\n  useCommand('slidev.goto', (filepath: string, index: number) => {\n    const { gotoSlide } = useFocusedSlide()\n    gotoSlide(filepath, index)\n  })\n  useCommand('slidev.next', () => {\n    const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()\n    if (!focusedMarkdown.value || focusedSourceSlide.value == null)\n      return\n    gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index + 1)\n  })\n  useCommand('slidev.prev', () => {\n    const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()\n    if (!focusedMarkdown.value || focusedSourceSlide.value == null)\n      return\n    gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index - 1)\n  })\n\n  useCommand('slidev.refresh-preview', async () => {\n    const { refresh } = usePreviewWebview()\n    await refresh()\n  })\n\n  useCommand('slidev.config-port', async () => {\n    if (!activeProject.value) {\n      window.showErrorMessage('No active project to configure port.')\n      return\n    }\n    const port = await window.showInputBox({\n      prompt: `Slidev Preview Port for ${getSlidesTitle(activeProject.value.data)}`,\n      value: config.port.toString(),\n      validateInput: (v) => {\n        if (!v.match(/^\\d+$/))\n          return 'Port should be a number'\n        if (+v < 1024 || +v > 65535)\n          return 'Port should be between 1024 and 65535'\n        return null\n      },\n    })\n    if (port && activeProject.value) {\n      activeProject.value.port.value = +port\n    }\n  })\n\n  useCommand('slidev.start-dev', async () => {\n    const project = activeProject.value\n    if (!project) {\n      window.showErrorMessage('Cannot start dev server: No active slides project.')\n      return\n    }\n\n    const { start, terminal } = useDevServer(project)\n    start()\n    terminal.value?.show()\n  })\n\n  useCommand('slidev.open-in-browser', () => usePreviewWebview().openExternal())\n\n  useCommand('slidev.preview-prev-click', () => usePreviewWebview().prevClick())\n  useCommand('slidev.preview-next-click', () => usePreviewWebview().nextClick())\n  useCommand('slidev.preview-prev-slide', () => usePreviewWebview().prevSlide())\n  useCommand('slidev.preview-next-slide', () => usePreviewWebview().nextSlide())\n\n  useCommand('slidev.enable-preview-sync', () => (config.update('preview-sync', true, ConfigurationTarget.Global)))\n  useCommand('slidev.disable-preview-sync', () => (config.update('preview-sync', false, ConfigurationTarget.Global)))\n}\n"
  },
  {
    "path": "packages/vscode/src/composables/useDebouncedComputed.ts",
    "content": "import { computed, shallowRef, watch } from 'reactive-vscode'\n\nexport function useDebouncedComputed<T>(source: () => T, delay: (newVal: T, oldVal: T) => number | null) {\n  const result = shallowRef(source())\n  let timeout: NodeJS.Timeout | undefined\n  watch(\n    source,\n    (newVal, oldVal) => {\n      clearTimeout(timeout)\n      const d = delay(newVal, oldVal)\n      if (d == null) {\n        result.value = newVal\n      }\n      else {\n        timeout = setTimeout(() => {\n          result.value = newVal\n        }, d)\n      }\n    },\n  )\n  return computed<T>(() => result.value)\n}\n"
  },
  {
    "path": "packages/vscode/src/composables/useDevServer.ts",
    "content": "import type { EffectScope, ShallowRef } from 'reactive-vscode'\nimport type { Terminal } from 'vscode'\nimport type { SlidevProject } from '../projects'\nimport { basename } from 'node:path'\nimport { effectScope, onScopeDispose, shallowRef, useAbsoluteUri, useDisposable } from 'reactive-vscode'\nimport { env, window } from 'vscode'\nimport { config } from '../configs'\nimport { getSlidesTitle } from '../utils/getSlidesTitle'\nimport { useServerDetector } from './useServerDetector'\n\nexport interface SlidevServer {\n  scope: EffectScope\n  terminal: ShallowRef<Terminal | null>\n  start: () => void\n}\n\nexport function useDevServer(project: SlidevProject) {\n  const { allocPort, redetect } = useServerDetector()\n\n  const { port, server } = project\n  if (server.value)\n    return server.value\n\n  const scope = effectScope(true)\n  return server.value = scope.run(() => {\n    const terminal = shallowRef<Terminal | null>(null)\n\n    async function start() {\n      if (terminal.value && terminal.value.exitStatus == null)\n        return\n\n      terminal.value = useDisposable(window.createTerminal({\n        name: getSlidesTitle(project.data),\n        cwd: project.userRoot,\n        iconPath: {\n          light: useAbsoluteUri('dist/res/logo-mono.svg').value,\n          dark: useAbsoluteUri('dist/res/logo-mono-dark.svg').value,\n        },\n        isTransient: true,\n      }))\n\n      const p = port.value ??= await allocPort()\n      const args = [\n        JSON.stringify(basename(project.entry)),\n        `--port ${p}`,\n        env.remoteName != null ? '--remote' : '',\n      ].filter(Boolean).join(' ')\n      // eslint-disable-next-line no-template-curly-in-string\n      terminal.value.sendText(config['dev-command'].replaceAll('${args}', args).replaceAll('${port}', `${p}`))\n\n      let intervalCount = 0\n      const maxIntervals = 100\n      const interval = setInterval(async () => {\n        intervalCount++\n        const ready = await redetect(p)\n        if (ready || intervalCount >= maxIntervals) {\n          clearInterval(interval)\n        }\n      }, 500)\n    }\n\n    onScopeDispose(() => {\n      close()\n      server.value = null\n    })\n\n    return {\n      scope,\n      terminal,\n      start,\n    }\n  })!\n}\n"
  },
  {
    "path": "packages/vscode/src/composables/useFocusedSlide.ts",
    "content": "import type { SlidevProject } from '../projects'\nimport { computed, defineService, useActiveTextEditor, useTextEditorSelection, useVscodeContext } from 'reactive-vscode'\nimport { Position, Range, Uri, window, workspace } from 'vscode'\nimport { activeData } from '../projects'\nimport { useDebouncedComputed } from './useDebouncedComputed'\nimport { useProjectFromDoc } from './useProjectFromDoc'\n\nexport const useFocusedSlide = defineService(() => {\n  const editor = useActiveTextEditor()\n  const selection = useTextEditorSelection(editor)\n  const debouncedEditor = useDebouncedComputed(() => editor.value, val => val ? null : 150)\n  const projectInfo = useProjectFromDoc(() => debouncedEditor.value?.document)\n  const focusedMarkdown = computed(() => projectInfo.value?.md)\n  useVscodeContext('slidev:editing-markdown', () => !!focusedMarkdown.value)\n\n  const focusedSourceSlide = useDebouncedComputed(\n    () => {\n      const md = focusedMarkdown.value\n      if (!md || !debouncedEditor.value || !selection.value) {\n        return null\n      }\n      const line = selection.value.active.line + 1\n      const slide = md.slides.find(s => line <= s.end)\n      return slide || md.slides.at(-1)!\n    },\n    (newVal, oldVal) => newVal?.filepath === oldVal?.filepath ? 30 : 150,\n  )\n\n  const displayedSourceSlide = computed(() => {\n    if (!focusedSourceSlide.value)\n      return null\n    let source = focusedSourceSlide.value\n    while (true) {\n      const firstChild = source.imports?.[0]\n      if (firstChild)\n        source = firstChild\n      else\n        return source\n    }\n  })\n  const focusedSlideNo = computed(() => {\n    const slides = projectInfo.value?.project.data.slides\n    if (!slides || !displayedSourceSlide.value)\n      return null\n    return slides.findIndex(s => s.source === displayedSourceSlide.value) + 1\n  })\n\n  async function gotoSlide(filepath: string, index: number) {\n    if (focusedMarkdown.value?.filepath === filepath && focusedSourceSlide.value?.index === index)\n      return\n    const slide = activeData.value?.markdownFiles[filepath]?.slides[index]\n    if (!slide)\n      return\n    const document = await workspace.openTextDocument(Uri.file(filepath))\n    const cursorPos = new Position(slide.contentStart, 0)\n    await window.showTextDocument(document, {\n      selection: new Range(cursorPos, cursorPos),\n    })\n  }\n\n  async function focusSlide(project: SlidevProject, no: number) {\n    const source = project.data.slides[no - 1]?.source\n    if (!source || displayedSourceSlide.value === source)\n      return\n    await gotoSlide(source.filepath, source.index)\n  }\n\n  return {\n    focusedMarkdown,\n    focusedSourceSlide,\n    focusedSlideNo,\n    gotoSlide,\n    focusSlide,\n  }\n})\n"
  },
  {
    "path": "packages/vscode/src/composables/useProjectFromDoc.ts",
    "content": "import type { MaybeRefOrGetter } from 'reactive-vscode'\nimport type { TextDocument } from 'vscode'\nimport { slash } from '@antfu/utils'\nimport { computed, toValue } from 'reactive-vscode'\nimport { activeProject, projects } from '../projects'\n\nexport function getProjectFromDoc(doc: TextDocument | undefined) {\n  if (!doc)\n    return null\n  const path = slash(doc.uri.fsPath)\n  const md = activeProject.value?.data.markdownFiles[path]\n  if (md)\n    return { project: activeProject.value!, md }\n  for (const project of projects.values()) {\n    const md = project.data.markdownFiles[path]\n    if (md)\n      return { project, md }\n  }\n  return null\n}\n\nexport function useProjectFromDoc(doc: MaybeRefOrGetter<TextDocument | undefined>) {\n  return computed(() => getProjectFromDoc(toValue(doc)))\n}\n"
  },
  {
    "path": "packages/vscode/src/composables/useServerDetector.ts",
    "content": "import type { SlidevProject } from '../projects'\nimport { createControlledPromise } from '@antfu/utils'\nimport { getPort as getPortPlease } from 'get-port-please'\nimport { computed, defineService, onScopeDispose, reactive, watch } from 'reactive-vscode'\nimport { config } from '../configs'\nimport { activeProject, askAddProject, projects, scannedProjects } from '../projects'\nimport { logger } from '../views/logger'\n\nconst versionRE = /<meta (?:name|property)=\"slidev:version\" content=\"([^\"]+)\">/\nconst entryRE = /<meta (?:property|charset)=\"slidev:entry\" content=\"([^\"]+)\">/\n\nexport interface DetectedServerState {\n  port: number\n  ready: boolean\n  message: string\n  compatMode: boolean\n  entry: string | null\n  pending: Promise<boolean> | null\n}\n\nexport const useServerDetector = defineService(() => {\n  const detectedPorts = reactive(new Map<number, DetectedServerState>())\n\n  async function redetect(port: number) {\n    const state = detectedPorts.get(port) || reactive<DetectedServerState>({\n      port,\n      ready: false,\n      message: '',\n      compatMode: false,\n      entry: null,\n      pending: null,\n    })\n    detectedPorts.set(port, state)\n\n    if (state.pending)\n      return state.pending\n    const { resolve } = state.pending = createControlledPromise()\n\n    async function pingUrl(url: string) {\n      try {\n        const text = await (await fetch(url)).text()\n        if (!text.match(/slidev/i))\n          return false\n        // Use semver to compare in the future\n        state.compatMode = !text.match(versionRE)\n        const detectedEntry = text.match(entryRE)?.[1]\n        if (detectedEntry) {\n          state.entry = detectedEntry\n          logger.info(`[Slidev] Detected Slidev server entry: ${detectedEntry} on port ${port}`)\n          if (scannedProjects.value) {\n            askAddProject(detectedEntry, `A Slidev server is detected running on localhost:${port}.`)\n          }\n        }\n        return true\n      }\n      catch (err) {\n        state.message = String(err)\n      }\n      return false\n    }\n\n    // We can't use `localhost:` here\n    state.ready = await pingUrl(`http://[::1]:${port}`) || await pingUrl(`http://127.0.0.1:${port}`)\n\n    state.pending = null\n    resolve(state.ready)\n    return state.ready\n  }\n\n  const portsToDetect = computed(() => {\n    const ports = new Set([config.port])\n    for (const project of projects.values()) {\n      if (project.port.value)\n        ports.add(project.port.value)\n    }\n    return ports\n  })\n\n  watch(portsToDetect, (ports, oldPorts) => {\n    for (const port of ports) {\n      if (!oldPorts?.has(port))\n        redetect(port)\n    }\n  }, { immediate: true })\n\n  const interval = setInterval(() => {\n    const activePort = activeProject.value?.port.value\n    if (activePort) {\n      redetect(activePort)\n    }\n  }, 4000)\n  onScopeDispose(() => clearInterval(interval))\n\n  function getDetected(project: SlidevProject) {\n    const port = project.port.value || config.port\n    const detected = detectedPorts.get(port)\n    if (detected?.entry === project.entry)\n      return detected\n    for (const state of detectedPorts.values()) {\n      if (state.entry === project.entry)\n        return state\n    }\n    return null\n  }\n\n  async function allocPort() {\n    const usedPorts = [...projects.values()].map(project => project.port.value ?? 0)\n    const minPort = Math.max(3029, ...usedPorts) + 1\n    return await getPortPlease({\n      portRange: [minPort, 4000],\n    })\n  }\n\n  return {\n    redetect,\n    getDetected,\n    allocPort,\n  }\n})\n"
  },
  {
    "path": "packages/vscode/src/configs.ts",
    "content": "import { defineConfig } from 'reactive-vscode'\n\nexport const config = defineConfig<{\n  'force-enabled': boolean\n  'port': number\n  'annotations': boolean\n  'annotations-line-numbers': boolean\n  'preview-sync': boolean\n  'include': string[]\n  'exclude': string\n  'dev-command': string\n}>('slidev')\n"
  },
  {
    "path": "packages/vscode/src/html/error.ts",
    "content": "import { activeProject, projects } from '../projects'\nimport { getSlidesTitle } from '../utils/getSlidesTitle'\n\nexport function generateErrorHtml(message: string) {\n  const project = activeProject.value\n  const info = project\n    ? `Slidev server for <i>${getSlidesTitle(project.data)}</i> ${project.port.value\n      ? `not found on <code>http://localhost:${project.port.value}</code>`\n      : `not started`\n    }.`\n    : projects.size\n      ? `No active project`\n      : `No projects found`\n  const errorMessage = message ? `(${message})` : ``\n  const instruction = project\n    ? project.port.value\n      ? `Please check the server status.`\n      : `Please start the server first.`\n    : projects.size\n      ? `Please choose one first.`\n      : `Please add one first.`\n  const action = project\n    ? project.port.value\n      ? ``\n      : `<button onclick=\"sendCommand('start-dev')\"> Start Dev Server </button>`\n    : projects.size\n      ? `<button onclick=\"sendCommand('choose-entry')\"> Choose active project </button>`\n      : `<button onclick=\"sendCommand('add-entry')\"> Add markdown file </button>`\n  return `\n  <head>\n    <script>\n      const vscode = acquireVsCodeApi()\n      window.sendCommand = (command) => void vscode.postMessage({ type: 'command', command })\n    </script>\n    <style>\n    button {\n      background: var(--vscode-button-secondaryBackground);\n      color: var(--vscode-button-secondaryForeground);\n      border: none;\n      padding: 8px 12px;\n      flex-grow: 1;\n    }\n    button:hover {\n      background: var(--vscode-button-secondaryHoverBackground);\n    }\n    code {\n      font-size: 0.9em;\n      font-family: var(--vscode-editor-font-family);\n      background: var(--vscode-textBlockQuote-border);\n      border-radius: 4px;\n      padding: 3px 5px;\n      text-wrap: nowrap;\n    }\n    .action-container {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 4px;\n      justify-content: center;\n      max-width: 180px;\n      margin: 0 auto;\n    }\n    </style>\n  </head>\n  <body>\n    <div style=\"text-align: center\">\n      <p> ${info} </p>\n      <p> ${errorMessage} </p>\n      <p> ${instruction} </p>\n      <div class=\"action-container\">\n        ${action}\n        <button onclick=\"sendCommand('config-port')\"> Configure Port </button>\n      </div>\n    </div>\n  </body>\n  `\n}\n"
  },
  {
    "path": "packages/vscode/src/html/ready.ts",
    "content": "export function generateReadyHtml(port: number) {\n  return `\n  <head>\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';\"\n    />\n    <style>\n    :root {\n      overflow: hidden;\n      --scale: 0.6;\n    }\n    body {\n      padding: 0;\n      width: 100vw;\n      height: 100vh;\n      overflow: visible;\n    }\n    iframe {\n      border: none;\n      width: calc(100% / var(--scale));\n      height: calc(100% / var(--scale));\n      transform: scale(var(--scale));\n      transform-origin: 0 0;\n    }\n    </style>\n  <head>\n  <body>\n    <iframe id=\"iframe\" sandbox=\"allow-same-origin allow-scripts\" src=\"http://localhost:${port}?embedded=true\"></iframe>\n    <script>\n      const vscode = acquireVsCodeApi()\n      const iframe = document.getElementById('iframe')\n      window.addEventListener('message', ({ data }) => {\n        if (data && data.target === 'slidev') {\n          if (data.sender === 'vscode')\n            iframe.contentWindow.postMessage(data, '*')\n          else\n            vscode.postMessage({\n              ...data,\n              type: 'update-state',\n            })\n        }\n      })\n    </script>\n  </body>`\n}\n"
  },
  {
    "path": "packages/vscode/src/index.ts",
    "content": "import { defineExtension } from 'reactive-vscode'\nimport { useCommands } from './commands'\nimport { useLanguageClient } from './languageClient'\nimport { useLmTools } from './lmTools'\nimport { activeEntry, useProjects } from './projects'\nimport { useAnnotations } from './views/annotations'\nimport { useFoldings } from './views/foldings'\nimport { logger } from './views/logger'\nimport { usePreviewWebview } from './views/previewWebview'\nimport { useProjectsTree } from './views/projectsTree'\nimport { useSlidesTree } from './views/slidesTree'\n\nconst { activate, deactivate } = defineExtension(() => {\n  // states\n  useProjects()\n\n  // commands\n  useCommands()\n\n  // views\n  useProjectsTree()\n  useSlidesTree()\n  usePreviewWebview()\n  useAnnotations()\n  useFoldings()\n\n  // language server\n  const labsInfo = useLanguageClient()\n\n  // language model tools. Wrap in try-catch for older vscode versions that do not support it.\n  try {\n    useLmTools()\n  }\n  catch (e) {\n    logger.warn(`Failed to initialize language model tools: ${e}`)\n  }\n\n  logger.info('Slidev activated.')\n  logger.info(`Entry: ${activeEntry.value}`)\n\n  return labsInfo\n})\n\nexport { activate, deactivate }\n"
  },
  {
    "path": "packages/vscode/src/languageClient.ts",
    "content": "import type { ServerOptions } from '@volar/vscode/node'\nimport { createLabsInfo } from '@volar/vscode'\nimport { LanguageClient, TransportKind } from '@volar/vscode/node'\nimport { computed, extensionContext, watch } from 'reactive-vscode'\nimport { Uri, window } from 'vscode'\nimport * as serverProtocol from '../language-server/protocol'\nimport { slidevFiles } from './projects'\nimport { logger } from './views/logger'\n\nexport function useLanguageClient() {\n  const serverModule = Uri.joinPath(extensionContext.value!.extensionUri, 'dist', 'language-server.cjs')\n  const runOptions = { execArgv: <string[]>[] }\n  const debugOptions = { execArgv: ['--nolazy', `--inspect=${6009}`] }\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverModule.fsPath,\n      transport: TransportKind.ipc,\n      options: runOptions,\n    },\n    debug: {\n      module: serverModule.fsPath,\n      transport: TransportKind.ipc,\n      options: debugOptions,\n    },\n  }\n\n  const documentSelector = computed(() => slidevFiles.value.map(path => ({ language: 'markdown', pattern: path })))\n\n  logger.info('Starting Slidev language server...')\n  const client = new LanguageClient(\n    'slidev-language-server',\n    'Slidev Language Server',\n    serverOptions,\n    { documentSelector: documentSelector.value },\n  )\n\n  async function doStart(shouldStop: boolean) {\n    if (shouldStop)\n      await client.stop()\n    client.clientOptions.documentSelector = documentSelector.value\n    await client.start()\n  }\n\n  let restartPromise: Promise<void> | undefined\n  async function start(shouldStop: boolean) {\n    await restartPromise\n    restartPromise = doStart(shouldStop)\n    await restartPromise\n  }\n\n  let shouldStop = false\n  watch(\n    () => slidevFiles.value.join('\\n'),\n    (files) => {\n      if (files.length === 0 && !shouldStop)\n        return\n      if (shouldStop)\n        window.setStatusBarMessage(`Restarting Slidev language server...`, start(true))\n      else\n        start(false)\n      shouldStop = true\n    },\n  )\n\n  const labsInfo = createLabsInfo(serverProtocol)\n  labsInfo.addLanguageClient(client)\n  return labsInfo.extensionExports\n}\n"
  },
  {
    "path": "packages/vscode/src/lmTools.ts",
    "content": "import { slash } from '@antfu/utils'\nimport { stringifySlide } from '@slidev/parser/core'\nimport { defineService, useDisposable } from 'reactive-vscode'\nimport { LanguageModelTextPart, LanguageModelToolResult, lm } from 'vscode'\nimport { useFocusedSlide } from './composables/useFocusedSlide'\nimport { activeEntry, activeProject, projects } from './projects'\n\nexport const useLmTools = defineService(() => {\n  const { focusedMarkdown, focusedSourceSlide, focusedSlideNo } = useFocusedSlide()\n\n  registerSimpleTool('slidev_getActiveSlide', () => {\n    const project = activeProject.value\n\n    if (project == null) {\n      throw new Error(`No active slide project found.`)\n    }\n\n    return formatObject({\n      'Entry file': project.entry,\n      'Root directory': project.userRoot,\n      'Preview server port': project.port.value || 'Not running',\n      'Number of slides': project.data.slides.length,\n      'Focused slide no. in presentation (from 1)': focusedSlideNo.value || 'None',\n      'Editing file': focusedMarkdown.value?.filepath || 'Not editing',\n      'Editing slide index in file (from 0)': focusedSourceSlide.value ? focusedSourceSlide.value.index : 'N/A',\n    })\n  })\n\n  registerSimpleTool('slidev_getSlideContent', (input: {\n    entrySlidePath: string\n    slideNo: number\n  }) => {\n    const project = resolveProjectFromEntry(input.entrySlidePath)\n    const slide = project.data.slides[input.slideNo - 1]\n\n    if (slide == null) {\n      throw new Error(`No content found for slide number ${input.slideNo} in entry: ${project.entry}. Available slides numbers: 1-${project.data.slides.length}`)\n    }\n\n    return `Content of slide number ${input.slideNo} in entry \"${project.entry}\" in file \"${slide.source.filepath}\":\\n\\n${stringifySlide(slide.source, 1)}`\n  })\n\n  // Get all slide titles\n  registerSimpleTool('slidev_getAllSlideTitles', (input: { entrySlidePath: string }) => {\n    const project = resolveProjectFromEntry(input.entrySlidePath)\n    const titles = project.data.slides.map((slide, idx) => `#${idx + 1}: ${slide.title || '(Untitled)'}`)\n    return formatList(titles)\n  })\n\n  // Find slide number by title\n  registerSimpleTool('slidev_findSlideNoByTitle', (input: { entrySlidePath: string, title: string }) => {\n    const project = resolveProjectFromEntry(input.entrySlidePath)\n    const idx = project.data.slides.findIndex(slide => slide.title === input.title)\n    if (idx === -1) {\n      throw new Error(`No slide found with title: \"${input.title}\".`)\n    }\n    return formatObject({\n      'Title': input.title,\n      'Slide number': idx + 1,\n    })\n  })\n\n  // List all loaded Slidev entries\n  registerSimpleTool('slidev_listEntries', () => {\n    const entries = [...projects.keys()]\n    if (entries.length === 0) {\n      return 'No loaded Slidev project entries.'\n    }\n    return formatList(entries)\n  })\n\n  // Get project preview port\n  registerSimpleTool('slidev_getPreviewPort', (input: { entrySlidePath: string }) => {\n    const project = resolveProjectFromEntry(input.entrySlidePath)\n    return formatObject({\n      'Project entry': project.entry,\n      'Preview port': project.port.value || 'Not running',\n    })\n  })\n\n  // Choose active Slidev entry\n  registerSimpleTool('slidev_chooseEntry', (input: { entrySlidePath: string }) => {\n    if (!input.entrySlidePath) {\n      throw new Error('entrySlidePath is required.')\n    }\n    const project = resolveProjectFromEntry(input.entrySlidePath)\n    activeEntry.value = project.entry\n    return formatObject({\n      'Active entry switched to': project.entry,\n    })\n  })\n})\n\nfunction registerSimpleTool<T>(name: string, invoke: (input: T) => string) {\n  useDisposable(lm.registerTool<T>(name, {\n    invoke({ input }) {\n      try {\n        const result = invoke(input)\n        return new LanguageModelToolResult([\n          new LanguageModelTextPart(result),\n        ])\n      }\n      catch (error: any) {\n        return new LanguageModelToolResult([\n          new LanguageModelTextPart(`Error: ${error.message || error.toString()}`),\n        ])\n      }\n    },\n  }))\n}\n\nfunction resolveProjectFromEntry(entry: string) {\n  if (entry === '' || entry === '$ACTIVE_SLIDE_ENTRY') {\n    if (!activeEntry.value) {\n      throw new Error('No active slide entry found. Please set an active slide entry before using this tool.')\n    }\n    entry = activeEntry.value\n  }\n\n  let project = projects.get(entry)\n  if (!project) {\n    entry = slash(entry)\n    const possibleProjects = [...projects.values()].filter(p => p.entry.includes(entry))\n    if (possibleProjects.length === 0) {\n      throw new Error(`No project found for entry: ${entry}. All entries: ${formatList(projects.keys())}`)\n    }\n    else if (possibleProjects.length > 1) {\n      throw new Error(`Multiple projects found for entry: ${entry}. Please specify the full path. All entries: ${formatList(projects.keys())}`)\n    }\n    else {\n      project = possibleProjects[0]\n    }\n  }\n\n  return project\n}\n\nfunction formatList(items: Iterable<string>): string {\n  const itemsArray = [...items]\n  if (itemsArray.length === 0) {\n    return 'No items found.'\n  }\n  return itemsArray.map(item => `- ${item}\\n`).join('')\n}\n\nfunction formatObject(obj: Record<string, string | number>): string {\n  return Object.entries(obj)\n    .map(([key, value]) => `- ${key}: ${value}\\n`)\n    .join('')\n}\n"
  },
  {
    "path": "packages/vscode/src/projects.ts",
    "content": "import type { LoadedSlidevData } from '@slidev/parser/fs'\nimport type { ComputedRef, EffectScope, Ref, ShallowRef } from 'reactive-vscode'\nimport type { SlidevServer } from './composables/useDevServer'\nimport type { DetectedServerState } from './composables/useServerDetector'\nimport { existsSync } from 'node:fs'\nimport { basename, dirname } from 'node:path'\nimport { debounce, slash } from '@antfu/utils'\nimport { load } from '@slidev/parser/fs'\nimport { isMatch } from 'picomatch'\nimport { computed, effectScope, extensionContext, markRaw, onScopeDispose, ref, shallowReactive, shallowRef, useDisposable, useFileSystemWatcher, useVscodeContext, watch, watchEffect } from 'reactive-vscode'\nimport { FileSystemError, Uri, window, workspace } from 'vscode'\nimport { useServerDetector } from './composables/useServerDetector'\nimport { config } from './configs'\nimport { findShallowestPath } from './utils/findShallowestPath'\nimport { logger } from './views/logger'\n\nexport interface SlidevProject {\n  readonly scope: EffectScope\n  readonly entry: string\n  readonly userRoot: string\n  readonly data: LoadedSlidevData\n  readonly port: Ref<number | null>\n  readonly server: ShallowRef<SlidevServer | null>\n  readonly detected: ComputedRef<DetectedServerState | null>\n}\n\nexport const projects = shallowReactive(new Map<string, SlidevProject>())\nexport const slidevFiles = computed(() => [...projects.values()].flatMap(p => Object.keys(p.data.markdownFiles)))\nexport const activeEntry = ref<string | null>(null)\nexport const activeProject = computed(() => activeEntry.value ? projects.get(activeEntry.value) : undefined)\nexport const activeData = computed(() => activeProject.value?.data)\n\nexport function useProjects() {\n  useFileSystemWatcher(() => config.include, {\n    async onDidCreate(uri) {\n      const path = slash(uri.fsPath)\n      if (!isMatch(path, config.exclude))\n        await addProject(path)\n    },\n    onDidChange: false,\n    async onDidDelete(uri) {\n      removeProject(slash(uri.fsPath))\n    },\n  })\n\n  rescanProjects()\n  watch(() => [config.include, config.exclude], debounce(200, rescanProjects))\n\n  // In case all the projects are removed manually, and the user may not want to disable the extension.\n  const everHadProjects = ref(false)\n  watchEffect(() => {\n    if (projects.size > 0)\n      everHadProjects.value = true\n  })\n\n  // Save active project to workspace state\n  watchEffect(() => {\n    if (activeEntry.value)\n      extensionContext.value!.workspaceState.update('slidev:activeProject', activeEntry.value)\n  })\n\n  // Auto set active project\n  watch(() => [...projects.keys(), activeEntry.value], () => {\n    if (!activeEntry.value) {\n      const previous = extensionContext.value!.workspaceState.get('slidev:activeProject', null)\n      if (previous && projects.has(previous)) {\n        activeEntry.value = previous\n        return\n      }\n      const firstKind = findShallowestPath(\n        [...projects.keys()].filter(path => basename(path) === 'slides.md'),\n      )\n      if (firstKind) {\n        activeEntry.value = firstKind\n        return\n      }\n      const secondKind = findShallowestPath(projects.keys())\n      if (secondKind) {\n        activeEntry.value = secondKind\n      }\n    }\n  }, { immediate: true })\n\n  useVscodeContext('slidev:enabled', () => {\n    const forceEnabled = config['force-enabled']\n    const enabled = forceEnabled == null ? everHadProjects.value : forceEnabled\n    logger.info(`Slidev ${enabled ? 'enabled' : 'disabled'}.`)\n    return enabled\n  })\n  useVscodeContext('slidev:hasActiveProject', () => !!activeEntry.value)\n\n  onScopeDispose(() => {\n    for (const project of projects.values()) {\n      project.scope.stop()\n    }\n    projects.clear()\n  })\n}\n\nlet scanningProjects = false\nexport const scannedProjects = ref(false)\nexport async function rescanProjects() {\n  if (scanningProjects)\n    return\n  scanningProjects = true\n  try {\n    const entries = new Set<string>()\n    for (const glob of config.include) {\n      (await workspace.findFiles(glob, config.exclude))\n        .forEach(file => entries.add(file.fsPath))\n    }\n    for (const entry of entries) {\n      await addProject(slash(entry))\n    }\n    for (const project of projects.values()) {\n      if (!existsSync(project.entry)) {\n        removeProject(project.entry)\n      }\n    }\n  }\n  finally {\n    scanningProjects = false\n    scannedProjects.value = true\n  }\n}\n\nexport async function addProject(entry: string) {\n  if (projects.has(entry))\n    return projects.get(entry)!\n\n  const { getDetected } = useServerDetector()\n\n  const scope = effectScope(true)\n  const data = shallowRef<LoadedSlidevData>(await loadProject(entry))\n  const project: SlidevProject = {\n    scope,\n    entry,\n    userRoot: dirname(entry),\n    get data() {\n      return data.value\n    },\n    server: shallowRef(null),\n    port: ref(null),\n    detected: computed(() => getDetected(project)),\n  }\n\n  scope.run(() => {\n    // Handle changes. VSCode already debounces rapid changes itself.\n    let pendingReload: Promise<LoadedSlidevData> | null = null\n    useDisposable(workspace.onDidChangeTextDocument(async ({ document }) => {\n      const path = slash(document.uri.fsPath)\n      if (data.value?.watchFiles[path]) {\n        const thisReload = pendingReload = loadProject(entry)\n        const newData = await thisReload\n        if (pendingReload === thisReload) { // still the latest\n          data.value = newData\n          pendingReload = null\n        }\n      }\n    }))\n\n    useDisposable(workspace.onDidCloseTextDocument(async (document) => {\n      const path = slash(document.uri.fsPath)\n      if (path !== entry) {\n        return\n      }\n      try {\n        await workspace.fs.stat(document.uri)\n      }\n      catch (err) {\n        if (err instanceof FileSystemError && err.code === 'FileNotFound') {\n          removeProject(entry)\n        }\n      }\n    }))\n\n    onScopeDispose(() => {\n      projects.get(entry)?.server.value?.scope.stop()\n    })\n  })\n\n  projects.set(entry, project)\n  return project\n}\n\nexport function removeProject(entry: string) {\n  const project = projects.get(entry)\n  if (!project)\n    return\n  if (activeEntry.value === entry)\n    activeEntry.value = null\n  project.scope.stop()\n  projects.delete(entry)\n}\n\nasync function loadProject(entry: string) {\n  const userRoot = dirname(entry)\n  return markRaw(await load(userRoot, entry, async (path: string) => {\n    const document = workspace.textDocuments.find(d => slash(d.uri.fsPath) === path)\n    if (document) {\n      return document.getText()\n    }\n    const buffer = await workspace.fs.readFile(Uri.file(path))\n    return (new TextDecoder('utf-8')).decode(buffer)\n  }))\n}\n\nconst ignoredEntries = new Set<string>()\nexport async function askAddProject(entry: string, message: string) {\n  if (projects.has(entry) || ignoredEntries.has(entry))\n    return\n  if (!workspace.getWorkspaceFolder(Uri.file(entry))) {\n    return\n  }\n  const result = await window.showInformationMessage(`${message}\\nDo you want to add ${entry} as a Slidev project?`, 'Yes', 'No')\n  if (result === 'Yes') {\n    await addProject(entry)\n  }\n  else {\n    ignoredEntries.add(entry)\n  }\n}\n"
  },
  {
    "path": "packages/vscode/src/utils/findPossibleEntries.ts",
    "content": "import { basename } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport { workspace } from 'vscode'\nimport { projects } from '../projects'\n\nexport async function findPossibleEntries() {\n  const files = await workspace.findFiles('**/*.md', '**/{node_modules,.github}/**')\n  return files\n    .map(uri => slash(uri.fsPath))\n    .filter(path => !projects.has(path))\n    .filter(path => ![\n      'code_of_conduct.md',\n      'contributing.md',\n      'license.md',\n      'licence.md',\n    ].includes(basename(path).toLocaleLowerCase()))\n}\n"
  },
  {
    "path": "packages/vscode/src/utils/findShallowestPath.ts",
    "content": "export function findShallowestPath(paths: Iterable<string>) {\n  let result: string | undefined\n  let minSlashes = Number.POSITIVE_INFINITY\n  for (const path of paths) {\n    const slashes = [...path.matchAll(/\\//g)].length\n    if (slashes < minSlashes) {\n      minSlashes = slashes\n      result = path\n    }\n  }\n  return result\n}\n"
  },
  {
    "path": "packages/vscode/src/utils/getFirstDisplayedChild.ts",
    "content": "import type { SourceSlideInfo } from '@slidev/types'\n\n/**\n * If `source` is displayed in the slides, return itself.\n *\n * Otherwise, return the recursively first child slide imported by `source`.\n */\nexport function getFirstDisplayedChild(source: SourceSlideInfo) {\n  while (true) {\n    const firstChild = source.imports?.[0]\n    if (firstChild)\n      source = firstChild\n    else\n      return source\n  }\n}\n"
  },
  {
    "path": "packages/vscode/src/utils/getSlidesTitle.ts",
    "content": "import type { LoadedSlidevData } from '@slidev/parser/fs'\nimport { basename } from 'node:path'\n\nexport function getSlidesTitle(data: LoadedSlidevData): string {\n  // If the title is set via headmatter, it has already been the first slide's title\n  return data.slides[0].title || basename(data.entry.filepath)\n}\n"
  },
  {
    "path": "packages/vscode/src/utils/toRelativePath.ts",
    "content": "import { relative } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport { workspace } from 'vscode'\n\nexport function toRelativePath(path: string) {\n  const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath ?? ''\n  return slash(relative(workspaceRoot, path))\n}\n"
  },
  {
    "path": "packages/vscode/src/views/annotations.ts",
    "content": "import type { SourceSlideInfo } from '@slidev/types'\nimport type { DecorationOptions } from 'vscode'\nimport { clamp, ensurePrefix } from '@antfu/utils'\nimport { computed, defineService, useActiveTextEditor, watch } from 'reactive-vscode'\nimport { Position, Range, ThemeColor, window } from 'vscode'\nimport { useProjectFromDoc } from '../composables/useProjectFromDoc'\nimport { config } from '../configs'\nimport { activeProject } from '../projects'\nimport { toRelativePath } from '../utils/toRelativePath'\n\nconst frontmatterBgOptions = {\n  isWholeLine: true,\n  backgroundColor: '#8881',\n}\n\nconst firstLineDecoration = window.createTextEditorDecorationType({})\nconst dividerDecoration = window.createTextEditorDecorationType({\n  ...frontmatterBgOptions,\n  color: new ThemeColor('panelTitle.inactiveForeground'),\n  borderStyle: 'solid',\n  borderWidth: '1px 0 0 0',\n  borderColor: new ThemeColor('panelTitle.activeBorder'),\n})\nconst frontmatterContentDecoration = window.createTextEditorDecorationType(frontmatterBgOptions)\nconst frontmatterEndDecoration = window.createTextEditorDecorationType({\n  ...frontmatterBgOptions,\n  color: new ThemeColor('panelTitle.inactiveForeground'),\n})\nconst errorDecoration = window.createTextEditorDecorationType({\n  isWholeLine: true,\n})\nconst codeBlockLineNumberDecoration = window.createTextEditorDecorationType({\n  isWholeLine: false,\n  rangeBehavior: 1,\n})\n\nfunction mergeSlideNumbers(slides: { index: number }[]): string {\n  const indexes = slides.map(s => s.index + 1)\n  const merged = [[indexes[0], indexes[0]]]\n  for (let i = 1; i < indexes.length; i++) {\n    if (merged[merged.length - 1][1] + 1 === indexes[i])\n      merged[merged.length - 1][1] = indexes[i]\n    else\n      merged.push([indexes[i], indexes[i]])\n  }\n  return merged.map(([start, end]) => start === end ? `#${start}` : `#${start}-${end}`).join(', ')\n}\n\ninterface CodeBlockInfo {\n  startLine: number\n  endLine: number\n  indent: string\n}\n\nfunction findCodeBlocks(docText: string): CodeBlockInfo[] {\n  const lines = docText.split(/\\r?\\n/)\n  const codeBlocks: CodeBlockInfo[] = []\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i]\n    const trimmedLine = line.trimStart()\n\n    if (trimmedLine.startsWith('```')) {\n      const indent = line.slice(0, line.length - trimmedLine.length)\n      const codeBlockLevel = line.match(/^\\s*`+/)![0]\n      const backtickCount = codeBlockLevel.trim().length\n      const startLine = i\n\n      if (backtickCount !== 3) {\n        continue\n      }\n\n      let endLine = i\n      for (let j = i + 1; j < lines.length; j++) {\n        if (lines[j].startsWith(codeBlockLevel)) {\n          endLine = j\n          break\n        }\n      }\n\n      if (endLine > startLine) {\n        codeBlocks.push({\n          startLine: startLine + 1,\n          endLine,\n          indent,\n        })\n      }\n\n      i = endLine\n    }\n  }\n\n  return codeBlocks\n}\n\nfunction updateCodeBlockLineNumbers(editor: ReturnType<typeof useActiveTextEditor>['value'], docText: string) {\n  if (!editor || !config['annotations-line-numbers'])\n    return\n\n  const codeBlockLineNumbers: DecorationOptions[] = []\n  const codeBlocks = findCodeBlocks(docText)\n\n  for (const block of codeBlocks) {\n    const lineCount = block.endLine - block.startLine\n    const maxLineNumber = lineCount\n    const numberWidth = String(maxLineNumber).length\n\n    for (let i = 0; i < lineCount; i++) {\n      const lineNumber = i + 1\n      const currentLine = block.startLine + i\n\n      if (currentLine >= editor.document.lineCount)\n        continue\n\n      // \\u2800 renders as a space but won't be trimmed\n      const paddedNumber = String(lineNumber).padStart(numberWidth, '\\u2800')\n\n      codeBlockLineNumbers.push({\n        range: new Range(\n          new Position(currentLine, 0),\n          new Position(currentLine, 0),\n        ),\n        renderOptions: {\n          before: {\n            contentText: `${paddedNumber}│`,\n            color: new ThemeColor('editorLineNumber.foreground'),\n            fontWeight: 'normal',\n            fontStyle: 'normal',\n            margin: '0 0.5em 0 0',\n          },\n        },\n      })\n    }\n  }\n\n  editor.setDecorations(codeBlockLineNumberDecoration, codeBlockLineNumbers)\n}\n\nexport const useAnnotations = defineService(() => {\n  const editor = useActiveTextEditor()\n  const doc = computed(() => editor.value?.document)\n  const projectInfo = useProjectFromDoc(doc)\n\n  watch(\n    [editor, doc, projectInfo, activeProject, () => config.annotations, () => config['annotations-line-numbers']],\n    ([editor, doc, projectInfo, activeProject, enabled, lineNumbersEnabled]) => {\n      if (!editor || !doc)\n        return\n\n      if (!projectInfo || !enabled) {\n        editor.setDecorations(firstLineDecoration, [])\n        editor.setDecorations(dividerDecoration, [])\n        editor.setDecorations(frontmatterContentDecoration, [])\n        editor.setDecorations(frontmatterEndDecoration, [])\n        editor.setDecorations(errorDecoration, [])\n        editor.setDecorations(codeBlockLineNumberDecoration, [])\n        return\n      }\n\n      const { project, md } = projectInfo\n      const isActive = project === activeProject\n      const docText = doc.getText()\n\n      function getTextContent(source: SourceSlideInfo) {\n        const slides = project.data.slides.filter(\n          s => s.source === source || s.importChain?.includes(source),\n        )\n        const posInfo = slides?.length\n          ? mergeSlideNumbers(slides)\n          : isActive ? '(hidden)' : ''\n        const entryInfo = source.index === 0 && project.data.entry !== md\n          ? ` (entry: ${toRelativePath(project.entry)})`\n          : ''\n        const activeInfo = source.index === 0 && !isActive\n          ? ' (inactive)'\n          : ''\n        return ` ${posInfo}${entryInfo}${activeInfo}`\n      }\n\n      const firstLineRanges: DecorationOptions[] = []\n      const dividerRanges: DecorationOptions[] = []\n      const frontmatterContentRanges: DecorationOptions[] = []\n      const frontmatterEndRanges: DecorationOptions[] = []\n      for (const slide of md.slides) {\n        const lineNo = slide.frontmatterStyle === 'frontmatter' ? slide.start : slide.start - 1\n        const line = doc.lineAt(clamp(lineNo, 0, doc.lineCount))\n\n        const start = new Position(line.lineNumber, 0)\n        const startDividerRange = new Range(start, new Position(line.lineNumber, line.text.length))\n\n        // If a markdown has no headmatter, we should set `isWholeLine` to `false` for the first line\n        const isSpecialCase = slide.index === 0 && !slide.frontmatterStyle\n        const ranges = isSpecialCase ? firstLineRanges : dividerRanges\n        ranges.push({\n          range: startDividerRange,\n          renderOptions: {\n            after: {\n              contentText: getTextContent(slide),\n              fontWeight: 'bold',\n              color: new ThemeColor('panelTitle.activeBorder'),\n            },\n          },\n        })\n\n        if (slide.frontmatterRaw != null) {\n          const range = docText.slice(doc.offsetAt(start))\n          const match = range.match(/^---[\\s\\S]*?\\n---/)\n          if (match && match.index != null) {\n            const endLine = doc.positionAt(doc.offsetAt(start) + match.index + match[0].length).line\n            frontmatterContentRanges.push({\n              range: new Range(new Position(line.lineNumber + 1, 0), new Position(endLine - 1, 0)),\n            })\n            if (endLine !== start.line) {\n              frontmatterEndRanges.push({\n                range: new Range(new Position(endLine, 0), new Position(endLine, 0)),\n              })\n            }\n          }\n        }\n      }\n\n      editor.setDecorations(firstLineDecoration, firstLineRanges)\n      editor.setDecorations(dividerDecoration, dividerRanges)\n      editor.setDecorations(frontmatterContentDecoration, frontmatterContentRanges)\n      editor.setDecorations(frontmatterEndDecoration, frontmatterEndRanges)\n\n      const errors: DecorationOptions[] = []\n      for (const error of md.errors ?? []) {\n        errors.push({\n          range: new Range(error.row, 0, error.row, 0),\n          renderOptions: {\n            after: {\n              contentText: ensurePrefix(error.message, ' '),\n              color: new ThemeColor('editorError.foreground'),\n              backgroundColor: new ThemeColor('editorError.background'),\n            },\n          },\n        })\n      }\n      editor.setDecorations(errorDecoration, errors)\n\n      if (lineNumbersEnabled) {\n        updateCodeBlockLineNumbers(editor, docText)\n      }\n      else {\n        editor.setDecorations(codeBlockLineNumberDecoration, [])\n      }\n    },\n    { immediate: true },\n  )\n})\n"
  },
  {
    "path": "packages/vscode/src/views/foldings.ts",
    "content": "import type { TextDocument } from 'vscode'\nimport { parse } from '@slidev/parser'\nimport { defineService, useDisposable, useEventEmitter, watch } from 'reactive-vscode'\nimport { FoldingRangeKind, languages } from 'vscode'\nimport { getProjectFromDoc } from '../composables/useProjectFromDoc'\nimport { projects } from '../projects'\n\nexport const useFoldings = defineService(() => {\n  const emitter = useEventEmitter<void>()\n  watch(projects, () => emitter.fire(), { deep: true })\n  useDisposable(languages.registerFoldingRangeProvider(\n    {\n      scheme: 'file',\n      language: 'markdown',\n    },\n    {\n      onDidChangeFoldingRanges: emitter.event,\n      async provideFoldingRanges(document: TextDocument) {\n        if (!getProjectFromDoc(document))\n          return // Not a slidev markdown file\n        // Not using global slides data because it updates too late\n        const md = await parse(document.getText(), document.uri.fsPath)\n        return md?.slides.map(source => ({\n          start: Math.max(0, source.frontmatterStyle === 'frontmatter' ? source.start : source.start - 1),\n          end: source.end - 1,\n          kind: FoldingRangeKind.Region,\n        }))\n      },\n    },\n  ))\n})\n"
  },
  {
    "path": "packages/vscode/src/views/logger.ts",
    "content": "import { defineLogger } from 'reactive-vscode'\n\nexport const logger = defineLogger('Slidev')\n"
  },
  {
    "path": "packages/vscode/src/views/previewWebview.ts",
    "content": "import { computed, defineService, extensionContext, reactive, ref, useIsDarkTheme, useVscodeContext, useWebviewView, watch, watchEffect } from 'reactive-vscode'\nimport { commands, env, Uri, window } from 'vscode'\nimport { useFocusedSlide } from '../composables/useFocusedSlide'\nimport { useServerDetector } from '../composables/useServerDetector'\nimport { config } from '../configs'\nimport { generateErrorHtml } from '../html/error'\nimport { generateReadyHtml } from '../html/ready'\nimport { activeData, activeProject } from '../projects'\nimport { logger } from './logger'\n\nexport const usePreviewWebview = defineService(() => {\n  const { focusedSlideNo, focusSlide } = useFocusedSlide()\n  const isDarkTheme = useIsDarkTheme()\n\n  const { redetect } = useServerDetector()\n  const port = computed(() => activeProject.value?.port.value || config.port)\n  const detected = computed(() => activeProject.value ? activeProject.value.detected.value : null)\n  const message = computed(() => detected.value?.message ?? '')\n  const compatMode = useVscodeContext('slidev:preview:compat', () => !!detected.value?.compatMode)\n  const ready = useVscodeContext('slidev:preview:ready', () => activeProject.value && !!detected.value?.ready)\n  const html = computed(() => ready.value\n    ? generateReadyHtml(port.value)\n    : generateErrorHtml(message.value),\n  )\n  useVscodeContext('slidev:preview:sync', () => config['preview-sync'])\n\n  const previewNavState = reactive({\n    no: 0,\n    clicks: 0,\n    hasNext: true,\n    hasPrev: false,\n  })\n\n  useVscodeContext('slidev:preview:has-prev-slide', () => previewNavState.no > 1)\n  useVscodeContext('slidev:preview:has-next-slide', () => activeData.value && previewNavState.no < activeData.value.slides.length)\n  useVscodeContext('slidev:preview:has-next-click', () => previewNavState.hasNext)\n  useVscodeContext('slidev:preview:has-prev-click', () => previewNavState.hasPrev)\n\n  const initializedClientId = ref('')\n\n  const { view, postMessage, forceReload } = useWebviewView(\n    'slidev-preview',\n    html,\n    {\n      retainContextWhenHidden: true,\n      webviewOptions: {\n        enableScripts: true,\n        localResourceRoots: [extensionContext.value!.extensionUri],\n      },\n      async onDidReceiveMessage(data) {\n        if (data.type === 'command') {\n          commands.executeCommand(`slidev.${data.command}`)\n        }\n        else if (data.type === 'update-state') {\n          if (initializedClientId.value === data.clientId) {\n            Object.assign(previewNavState, data.navState)\n          }\n          else {\n            initializedClientId.value = data.clientId\n            if (config['preview-sync'] && initializedClientId.value === data.clientId)\n              postSlidevMessage('navigate', { no: focusedSlideNo.value })\n          }\n        }\n      },\n    },\n  )\n\n  const pageId = ref(0)\n  async function refresh() {\n    if (!ready.value)\n      await redetect(port.value)\n    if (!view.value)\n      return\n    forceReload()\n    logger.info(`Webview refreshed. Current URL: http://localhost:${port.value}`)\n    setTimeout(() => pageId.value++, 300)\n  }\n  watch([ready, view, port], refresh)\n\n  function postSlidevMessage(type: string, data: Record<string, unknown>) {\n    return postMessage({\n      target: 'slidev',\n      sender: 'vscode',\n      type,\n      ...data,\n    })\n  }\n\n  watch([pageId], () => postSlidevMessage('css-vars', { '--slidev-slide-container-background': 'transparent' }))\n  watch([pageId, isDarkTheme], ([_, dark]) => postSlidevMessage('color-schema', { color: dark ? 'dark' : 'light' }))\n\n  watchEffect(() => {\n    if (view.value) {\n      const slideNo = ready.value && previewNavState.no > 0 ? ` (${previewNavState.no}/${activeData.value?.slides.length})` : ''\n      const compatInfo = compatMode.value ? ' (compat mode)' : ''\n      view.value.title = `Preview${slideNo}${compatInfo}`\n    }\n  })\n\n  function useNavOperation(operation: string, ...args: unknown[]) {\n    return () => {\n      if (compatMode.value)\n        window.showErrorMessage('Unsupported operation: Slidev server too old.')\n      postSlidevMessage('navigate', { operation, args })\n    }\n  }\n\n  watch(\n    () => previewNavState.no,\n    (no) => {\n      if (ready.value && config['preview-sync'] && activeProject.value) {\n        focusSlide(activeProject.value, no)\n      }\n    },\n  )\n  watch(\n    [() => config['preview-sync'], focusedSlideNo],\n    ([enabled, no]) => {\n      if (enabled && no != null && previewNavState.no !== no) {\n        postSlidevMessage('navigate', { no, clicks: 999999 })\n      }\n    },\n  )\n\n  return {\n    view,\n    refresh,\n    nextClick: useNavOperation('next'),\n    prevClick: useNavOperation('prev'),\n    nextSlide: useNavOperation('nextSlide', true),\n    prevSlide: useNavOperation('prevSlide', true),\n    openExternal: () => {\n      const hashRoute = activeData.value?.slides[0]?.frontmatter.routerMode === 'hash'\n      const query = previewNavState.clicks > 0 ? `?clicks=${previewNavState.clicks}` : ''\n      const url = `http://localhost:${port.value}/${hashRoute ? '#' : ''}${previewNavState.no}${query}`\n      return env.openExternal(Uri.parse(url))\n    },\n  }\n})\n"
  },
  {
    "path": "packages/vscode/src/views/projectsTree.ts",
    "content": "import type { TreeViewNode } from 'reactive-vscode'\nimport type { TreeItem } from 'vscode'\nimport type { SlidevProject } from '../projects'\nimport { isDeepEqual } from '@antfu/utils'\nimport { computed, defineService, shallowRef, useTreeView, watch } from 'reactive-vscode'\nimport { ThemeIcon, TreeItemCollapsibleState, Uri } from 'vscode'\nimport { activeEntry, projects } from '../projects'\nimport { getSlidesTitle } from '../utils/getSlidesTitle'\nimport { toRelativePath } from '../utils/toRelativePath'\n\n/**\n * No projects has dependency files. Then it would be weired to show a collapse button.\n */\nconst nothingToCollapse = computed(() => [...projects.values()]\n  .every(project => Object.keys(project.data.markdownFiles).length <= 1))\n\nfunction getProjectTreeItem(project: SlidevProject): TreeItem {\n  const active = activeEntry.value === project.entry\n  return {\n    label: toRelativePath(project.entry),\n    description: `${getSlidesTitle(project.data)}${project.detected.value?.ready ? ` (port: ${project.detected.value.port})` : ''}`,\n    resourceUri: Uri.file(project.entry),\n    iconPath: new ThemeIcon(active ? 'eye' : 'eye-closed'),\n    collapsibleState: nothingToCollapse.value ? TreeItemCollapsibleState.None : active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed,\n    contextValue: `<project><${active ? 'active' : 'inactive'}>`,\n    command: {\n      title: 'Open',\n      command: 'vscode.open',\n      arguments: [Uri.file(project.entry)],\n    },\n  }\n}\n\nfunction getFileTreeItem(file: string): TreeItem {\n  return {\n    resourceUri: Uri.file(file),\n    iconPath: new ThemeIcon('file'),\n    collapsibleState: TreeItemCollapsibleState.None,\n    command: {\n      title: 'Open',\n      command: 'vscode.open',\n      arguments: [Uri.file(file)],\n    },\n  }\n}\n\nexport const useProjectsTree = defineService(() => {\n  const treeData = computed(() => {\n    return [...projects.values()].map(project => ({\n      treeItem: getProjectTreeItem(project),\n      children: Object.keys(project.data.markdownFiles)\n        .filter(file => file.toLowerCase() !== project.entry.toLowerCase())\n        .map(file => ({ treeItem: getFileTreeItem(file) })),\n    }))\n  })\n\n  const treeItems = shallowRef<TreeViewNode[]>([])\n  watch(treeData, (treeData) => {\n    if (!isDeepEqual(treeItems.value, treeData)) {\n      treeItems.value = treeData\n    }\n  }, { immediate: true })\n\n  const treeView = useTreeView('slidev-projects-tree', treeItems, {\n    showCollapseAll: true,\n  })\n\n  return treeView\n})\n"
  },
  {
    "path": "packages/vscode/src/views/slidesTree.ts",
    "content": "import type { SlidevMarkdown, SourceSlideInfo } from '@slidev/types'\nimport type { TreeViewNode } from 'reactive-vscode'\nimport { isDeepEqual } from '@antfu/utils'\nimport { stringify } from '@slidev/parser/core'\nimport { computed, defineService, shallowRef, useTreeView, watch, watchEffect } from 'reactive-vscode'\nimport { DataTransferItem, ThemeIcon, TreeItemCollapsibleState, Uri, window, workspace } from 'vscode'\nimport { useFocusedSlide } from '../composables/useFocusedSlide'\nimport { activeData } from '../projects'\nimport { getSlidesTitle } from '../utils/getSlidesTitle'\nimport { toRelativePath } from '../utils/toRelativePath'\n\nexport const slideMineType = 'application/slidev.slide'\n\nconst layoutIconMap = {\n  'center': 'symbol-constant', // TODO: a better icon\n  'cover': 'home',\n  'default': 'window',\n  'end': 'primitive-square',\n  'fact': 'comment',\n  'full': 'screen-full',\n  'iframe-left': 'layout-sidebar-left',\n  'iframe-right': 'layout-sidebar-right',\n  'iframe': 'globe',\n  'image-left': 'layout-sidebar-left',\n  'image-right': 'layout-sidebar-right',\n  'image': 'file-media', // TODO: a better icon\n  'intro': 'debug-step-into',\n  'outro': 'debug-step-out',\n  'none': 'layout-statusbar',\n  'quote': 'quote',\n  'section': 'symbol-module', // TODO: a better icon\n  'statement': 'megaphone',\n  'two-cols-header': 'symbol-struct',\n  'two-cols': 'split-horizontal',\n} as Record<string, string>\n\nexport interface SlidesTreeNode extends TreeViewNode {\n  markdownPath: string\n  slideIndex: number\n  readonly children?: this[]\n}\n\nexport const useSlidesTree = defineService(() => {\n  const { focusedSourceSlide, gotoSlide } = useFocusedSlide()\n\n  const treeData = computed(() => {\n    const data = activeData.value\n    if (!data)\n      return null\n\n    const sourceToNode = new Map<string, SlidesTreeNode>()\n    const createNode = (slide: SourceSlideInfo) => {\n      const isFirstSlide = data.entry.slides.findIndex(s => s === slide) === 0\n      const layoutName = slide.frontmatter.layout || (isFirstSlide ? 'cover' : 'default')\n      const slideNo = slide.imports ? 0 : data.slides.findIndex(s => s.source === slide) + 1\n      const label = slide.imports ? '' : `${slideNo}. ${slide.title || '(Untitled)'}`\n      const description = slide.imports ? toRelativePath(slide.imports[0].filepath) : undefined\n      const icon = slide.imports ? 'link-external' : layoutIconMap[layoutName] ?? 'window'\n      const collapsibleState = slide.imports ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None\n\n      const node: SlidesTreeNode = {\n        markdownPath: slide.filepath,\n        slideIndex: slide.index,\n        children: slide.imports?.map(createNode),\n        treeItem: {\n          label,\n          description,\n          iconPath: new ThemeIcon(icon),\n          collapsibleState,\n          command: {\n            command: 'slidev.goto',\n            title: 'Goto',\n            arguments: [slide.filepath, slide.index],\n          },\n        },\n      }\n      sourceToNode.set(`${slide.filepath}:${slide.index}`, node)\n      return node\n    }\n    return {\n      items: data.entry.slides.map(createNode),\n      sourceToNode,\n    }\n  })\n\n  const treeItems = shallowRef<SlidesTreeNode[]>([])\n  const sourceToNode = shallowRef(new Map<string, SlidesTreeNode>())\n  watch(treeData, (treeData) => {\n    const newItems = treeData?.items || []\n    if (!isDeepEqual(treeItems.value, newItems)) {\n      treeItems.value = newItems\n      sourceToNode.value = treeData?.sourceToNode || new Map()\n    }\n  }, { immediate: true })\n\n  const treeView = useTreeView(\n    'slidev-slides-tree',\n    treeItems,\n    {\n      canSelectMany: true,\n      dragAndDropController: {\n        dragMimeTypes: [slideMineType],\n        dropMimeTypes: [slideMineType],\n        handleDrag(source, dataTransfer) {\n          const data = activeData.value\n          if (!data) {\n            window.showErrorMessage(`Cannot drag and drop slides: No active slides project.`)\n            return\n          }\n          const sources = source.map(node => data.markdownFiles[node.markdownPath]?.slides[node.slideIndex]).filter(Boolean)\n          dataTransfer.set(slideMineType, new DataTransferItem(sources))\n        },\n        async handleDrop(target, dataTransfer) {\n          const slides: SourceSlideInfo[] = dataTransfer.get(slideMineType)?.value\n          const data = activeData.value\n          if (!slides?.length || !target || !data)\n            return\n          if (slides.length === 0) {\n            window.showErrorMessage(`Cannot drag and drop slides: None of the selected slides are in the entry Markdown.`)\n            return\n          }\n          const targetIndex = target.slideIndex\n          const targetMarkdown = data.markdownFiles[target.markdownPath]\n          const oldSlides = targetMarkdown.slides.map(s => slides.includes(s) ? null : s)\n          const before = oldSlides.slice(0, targetIndex + 1).filter(Boolean) as SourceSlideInfo[]\n          const after = oldSlides.slice(targetIndex + 1).filter(Boolean) as SourceSlideInfo[]\n          const newTargetMarkdown = {\n            ...targetMarkdown,\n            slides: [\n              ...before,\n              ...slides,\n              ...after,\n            ],\n          }\n\n          const changedMarkdown = new Set<SlidevMarkdown>([newTargetMarkdown])\n          for (const markdown of Object.values(data.markdownFiles)) {\n            if (markdown === targetMarkdown)\n              continue // already handled\n            const newSlides = markdown.slides.filter(s => !slides.includes(s))\n            if (newSlides.length !== markdown.slides.length) {\n              changedMarkdown.add({\n                ...markdown,\n                slides: newSlides,\n              })\n            }\n          }\n\n          for (const markdown of changedMarkdown) {\n            const newContent = stringify(markdown)\n            await workspace.fs.writeFile(\n              Uri.file(markdown.filepath),\n              (new TextEncoder()).encode(newContent),\n            )\n          }\n\n          setTimeout(async () => {\n            await gotoSlide(target.markdownPath, before.length)\n          }, 100)\n        },\n      },\n      showCollapseAll: true,\n      title: () => activeData.value\n        ? `Slides: ${getSlidesTitle(activeData.value)}`\n        : 'Slides',\n    },\n  )\n\n  const focusedNode = computed(() => {\n    if (!focusedSourceSlide.value)\n      return null\n    const { filepath, index } = focusedSourceSlide.value\n    return sourceToNode.value.get(`${filepath}:${index}`)\n  })\n  watchEffect(() => {\n    if (treeView.visible.value && focusedNode.value) {\n      treeView.reveal(focusedNode.value, { select: true })\n    }\n  })\n\n  return {\n    treeView,\n  }\n})\n"
  },
  {
    "path": "packages/vscode/syntaxes/.vscode/settings.json",
    "content": "{\n  \"slidev.include\": [\n    \"**/slides.md\",\n    \"slidev.example.md\"\n  ]\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/codeblock-patch.ts",
    "content": "import Markdown from 'tm-grammars/grammars/markdown.json' with { type: 'json' }\n\nconst base = {\n  $schema: 'https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json',\n  fileTypes: [],\n  injectionSelector: 'L:text.html.markdown -markup.frontmatter.slidev -markup.fenced_code.block.markdown',\n  patterns: [\n  ],\n  repository: {\n    fenced_code_block_unknown: {\n      begin: '(^|\\\\G)(\\\\s*)(`{3,}|~{3,})\\\\s*(?=([^`]*)?$)',\n      beginCaptures: {\n        3: {\n          name: 'punctuation.definition.markdown',\n        },\n        4: {\n          name: 'fenced_code.block.language.attributes.markdown',\n          patterns: [],\n        },\n      },\n      end: '(^|\\\\G)(\\\\2|\\\\s{0,3})(\\\\3)\\\\s*$',\n      endCaptures: {\n        3: {\n          name: 'punctuation.definition.markdown',\n        },\n      },\n      name: 'markup.fenced_code.block.markdown',\n    },\n  },\n  scopeName: 'inject-to-markdown.codeblock.patch.slidev',\n} as any\n\nexport function generateCodeblockPatch() {\n  for (const [k, v] of Object.entries(Markdown.repository) as any) {\n    if (!k.startsWith('fenced_code_block_') || k === 'fenced_code_block_unknown')\n      continue\n    v.beginCaptures[5].patterns = []\n    base.patterns.push(v)\n  }\n\n  base.patterns.push(\n    {\n      include: '#fenced_code_block_unknown',\n    },\n  )\n  return base\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/codeblock.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json\",\n  \"fileTypes\": [],\n  \"injectionSelector\": \"L:fenced_code.block.language.attributes.markdown -meta.code_block_attrs.slidev\",\n  \"patterns\": [\n    {\n      \"match\": \"twoslash\",\n      \"name\": \"keyword.twoslash.slidev\"\n    },\n    {\n      \"match\": \"\\\\s*(\\\\{)([^}]*)(\\\\})\\\\s*(.*)$\",\n      \"name\": \"meta.code_block_attrs.slidev\",\n      \"captures\": {\n        \"1\": {\n          \"name\": \"punctuation.definition.range_or_monaco.slidev\"\n        },\n        \"2\": {\n          \"name\": \"meta.range_or_monaco.slidev\",\n          \"patterns\": [\n            {\n              \"include\": \"source.slidev#monaco-type\"\n            },\n            {\n              \"include\": \"source.slidev#range-with-steps\"\n            }\n          ]\n        },\n        \"3\": {\n          \"name\": \"punctuation.definition.range_or_monaco.slidev\"\n        },\n        \"4\": {\n          \"patterns\": [\n            {\n              \"match\": \"twoslash\",\n              \"name\": \"keyword.twoslash.slidev\"\n            },\n            {\n              \"begin\": \"(?=\\\\{)\",\n              \"end\": \"(?<=\\\\})(\\\\s*twoslash)?$\",\n              \"endCaptures\": {\n                \"1\": {\n                  \"name\": \"keyword.twoslash.slidev\"\n                }\n              },\n              \"contentName\": \"meta.embedded.block.code_block_options.ts\",\n              \"patterns\": [\n                {\n                  \"include\": \"source.ts#object-literal\"\n                }\n              ]\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"scopeName\": \"inject-to-markdown.codeblock.slidev\"\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/language-configuration.json",
    "content": "// Ported from https://github.com/vuejs/language-tools/blob/master/extensions/vscode/languages/markdown-language-configuration.json\n{\n  \"autoClosingPairs\": [\n    // html\n    {\n      \"open\": \"{\",\n      \"close\": \"}\"\n    },\n    {\n      \"open\": \"[\",\n      \"close\": \"]\"\n    },\n    {\n      \"open\": \"(\",\n      \"close\": \")\"\n    },\n    {\n      \"open\": \"'\",\n      \"close\": \"'\"\n    },\n    {\n      \"open\": \"\\\"\",\n      \"close\": \"\\\"\"\n    },\n    {\n      \"open\": \"<!--\",\n      \"close\": \"-->\",\n      \"notIn\": [\n        \"comment\",\n        \"string\"\n      ]\n    },\n    // javascript\n    // commented to fix https://github.com/vuejs/language-tools/issues/1428\n    // {\n    //   \"open\": \"`\",\n    //   \"close\": \"`\",\n    //   \"notIn\": [\n    //     \"string\",\n    //     \"comment\"\n    //   ]\n    // },\n    {\n      \"open\": \"/**\",\n      \"close\": \" */\",\n      \"notIn\": [\n        \"string\"\n      ]\n    }\n  ],\n  \"colorizedBracketPairs\": [\n    [\n      \"{{\",\n      \"}}\"\n    ]\n  ]\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/markdown.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json\",\n  \"fileTypes\": [],\n  \"injectionSelector\": \"L:text.html.markdown -markup.frontmatter.slidev\",\n  \"patterns\": [\n    {\n      \"include\": \"source.slidev#import-snippet\"\n    },\n    {\n      \"include\": \"source.slidev#slide-frontmatter\"\n    }\n  ],\n  \"scopeName\": \"inject-to-markdown.main.slidev\"\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/pages.md",
    "content": "# 1\n\n---\n\n# 2\n"
  },
  {
    "path": "packages/vscode/syntaxes/slidev.example.md",
    "content": "---\nfoo: a\nbar: 1\n---\n\n# Example Slides\n\n**Hello** World\n\n[](./a)\n\n```ts {a}\nconsole.log('Hello World')\n```\n\n---\n\n# Import Snippets\n\n---\nsrc: ./pages.md\n---\n\n---\n\n# Vue Component\n\n<div title=\"hi\" />\n<Comp :x=\"a\" />\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nlet a = ref(1)\n</script>\n\n---\nlayout: center\ntext: 1\n---\n\n# Code block\n\n```ts {1,2|3}\nconst a = 1\n```\n\n```ts twoslash\nconst a = 1\n```\n\n```vue {monaco-run}{showOutputAt: '+1'}\n<template>\n  <div />\n</template>\n```\n\n```ts {monaco-run}{showOutputAt: '+1'} twoslash\nconst a = 1\nconst b = 2\n```\n\n$$\n\\lambda = 1\n$$\n\n---\nlayout: center\ntext: 2\n---\n\n# Magic Move\n\n````md magic-move\n```ts\nconst a = 1\n```\n\n```ts\nconst a = 1\nconst b = 2\nconst c = 3\n```\n````\n"
  },
  {
    "path": "packages/vscode/syntaxes/slidev.tmLanguage.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json\",\n  \"name\": \"Slidev\",\n  \"scopeName\": \"source.slidev\",\n  \"patterns\": [],\n  \"repository\": {\n    \"import-snippet\": {\n      \"match\": \"^(<<<)\\\\s*(\\\\S+)(\\\\s+[\\\\w-]+)?(?:\\\\s*(\\\\{)([^}]*)(\\\\}))?(?:\\\\s*(\\\\{.*\\\\}))?\",\n      \"name\": \"meta.import_snippet.block.slidev\",\n      \"captures\": {\n        \"1\": {\n          \"name\": \"punctuation.definition.slidev\"\n        },\n        \"2\": {\n          \"name\": \"string.path.snippet.slidev\"\n        },\n        \"3\": {\n          \"name\": \"import_snippet.block.language.slidev\"\n        },\n        \"4\": {\n          \"name\": \"punctuation.separator.range.slidev\"\n        },\n        \"5\": {\n          \"name\": \"meta.range_or_monaco.slidev\",\n          \"patterns\": [\n            {\n              \"include\": \"#monaco-type\"\n            },\n            {\n              \"include\": \"#monaco-write\"\n            },\n            {\n              \"include\": \"#range-with-steps\"\n            }\n          ]\n        },\n        \"6\": {\n          \"name\": \"punctuation.separator.range.slidev\"\n        },\n        \"7\": {\n          \"patterns\": [\n            {\n              \"include\": \"source.ts#object-literal\"\n            }\n          ]\n        }\n      }\n    },\n    \"range-with-steps\": {\n      \"match\": \"([^|]*)(\\\\|)?\",\n      \"captures\": {\n        \"1\": {\n          \"patterns\": [\n            {\n              \"include\": \"#range\"\n            }\n          ]\n        },\n        \"2\": {\n          \"name\": \"punctuation.separator.steps.slidev\"\n        }\n      }\n    },\n    \"range\": {\n      \"match\": \"(\\\\d+|\\\\*|all)([,-])?\",\n      \"captures\": {\n        \"1\": {\n          \"name\": \"constant.numeric.range.slidev\"\n        },\n        \"2\": {\n          \"name\": \"punctuation.separator.range.slidev\"\n        }\n      }\n    },\n    \"monaco-type\": {\n      \"match\": \"monaco(-(run|diff))?\",\n      \"name\": \"keyword.monaco.slidev\"\n    },\n    \"monaco-write\": {\n      \"match\": \"monaco-write\",\n      \"name\": \"keyword.monaco.slidev\"\n    },\n    \"slide-frontmatter\": {\n      \"begin\": \"(^|\\\\G)(---).*$\",\n      \"beginCaptures\": {\n        \"2\": {\n          \"name\": \"punctuation.definition.frontmatter.slidev\"\n        }\n      },\n      \"end\": \"(?=^\\\\s*$)\",\n      \"name\": \"markup.frontmatter.slidev\",\n      \"patterns\": [\n        {\n          \"begin\": \"(?=^(?!\\\\s*$))\",\n          \"end\": \"(?=^\\\\s*$)\",\n          \"patterns\": [\n            {\n              \"begin\": \"\\\\G\",\n              \"while\": \"^(?!(^|\\\\G)(---).*$)\",\n              \"contentName\": \"meta.embedded.block.yaml\",\n              \"patterns\": [\n                {\n                  \"include\": \"source.yaml\"\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vscode/syntaxes/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"Preserve\"\n  },\n  \"include\": [\n    \"*.ts\",\n    \"*.vue\",\n    \"*.md\"\n  ],\n  \"vueCompilerOptions\": {\n    \"vitePressExtensions\": [\".md\"]\n  }\n}\n"
  },
  {
    "path": "packages/vscode/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"Preserve\"\n  }\n}\n"
  },
  {
    "path": "packages/vscode/tsdown.config.ts",
    "content": "import { existsSync } from 'node:fs'\nimport { copyFile, mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { resolvePath } from 'mlly'\nimport { defineConfig } from 'tsdown'\nimport { generateCodeblockPatch } from './syntaxes/codeblock-patch.ts'\n\nexport default defineConfig({\n  entry: {\n    'index': 'src/index.ts',\n    'language-server': 'language-server/bin.ts',\n  },\n  format: 'cjs',\n  target: 'node20',\n  clean: true,\n  minify: process.env.NODE_ENV === 'production',\n  sourcemap: true,\n  external: [\n    'vscode',\n  ],\n  alias: {\n    '@slidev/parser/fs': fileURLToPath(new URL('../parser/src/fs.ts', import.meta.url)),\n    '@slidev/parser/core': fileURLToPath(new URL('../parser/src/core.ts', import.meta.url)),\n    '@slidev/parser/types': fileURLToPath(new URL('../parser/src/types.ts', import.meta.url)),\n    '@slidev/parser': fileURLToPath(new URL('../parser/src/index.ts', import.meta.url)),\n  },\n  plugins: [\n    {\n      name: 'umd2esm',\n      resolveId: {\n        filter: {\n          id: /^(vscode-.*-languageservice|vscode-languageserver-types|jsonc-parser)/,\n        },\n        async handler(source, importer) {\n          const pathUmdMay = await resolvePath(source, { url: importer })\n          // Call twice the replace is to solve the problem of the path in Windows\n          const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\\\umd\\\\', '\\\\esm\\\\')\n          return { id: pathEsm }\n        },\n      },\n    },\n  ],\n  async onSuccess() {\n    const assetsDir = join(import.meta.dirname, '../../assets')\n    const resDir = join(import.meta.dirname, './dist/res')\n\n    if (!existsSync(resDir))\n      await mkdir(resDir, { recursive: true })\n\n    for (const file of ['logo-mono.svg', 'logo-mono-dark.svg', 'logo.png', 'logo.svg'])\n      await copyFile(join(assetsDir, file), join(resDir, file))\n\n    await writeFile(\n      join(import.meta.dirname, 'syntaxes/codeblock-patch.json'),\n      JSON.stringify(generateCodeblockPatch(), null, 2),\n    )\n  },\n})\n"
  },
  {
    "path": "patches/@hedgedoc__markdown-it-plugins@2.1.4.patch",
    "content": "diff --git a/dist/esm/task-lists/index.js b/dist/esm/task-lists/index.js\nindex 99087c6d616500138111a086da7f4a445fabd7dd..1c5bfa743fb5522bcf26e2d2f6480a7fd70f1531 100644\n--- a/dist/esm/task-lists/index.js\n+++ b/dist/esm/task-lists/index.js\n@@ -1,4 +1,4 @@\n-import Token from 'markdown-it/lib/token.js';\n+import Token from 'markdown-it/lib/token.mjs';\n const checkboxRegex = /^ *\\[([\\sx])] /i;\n export function taskLists(md, options = { enabled: false, label: false, lineNumber: false }) {\n     md.core.ruler.after('inline', 'task-lists', (state) => processToken(state, options));\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "catalogMode: prefer\nignoreWorkspaceRootCheck: true\nshamefullyHoist: true\nshellEmulator: true\ntrustPolicy: no-downgrade\ntrustPolicyExclude:\n  - langium\n\npackages:\n  - packages/*\n  - demo/*\n  - cypress/fixtures/*\n  - docs\n\nstrictPeerDependencies: false\nlinkWorkspacePackages: true\n\npatchedDependencies:\n  '@hedgedoc/markdown-it-plugins@2.1.4': patches/@hedgedoc__markdown-it-plugins@2.1.4.patch\ncatalogs:\n  demo:\n    '@vue/compiler-sfc': ^3.5.29\n  dev:\n    '@antfu/eslint-config': ^7.6.1\n    bumpp: ^10.4.1\n    cypress: ^14.5.4\n    eslint: ^10.0.2\n    eslint-plugin-format: ^2.0.1\n    lint-staged: ^16.3.1\n    nodemon: ^3.1.14\n    ovsx: ^0.10.9\n    playwright-chromium: ^1.58.2\n    postcss-nested: ^7.0.2\n    prettier: ^3.8.1\n    prettier-plugin-slidev: ^1.0.5\n    rimraf: ^6.1.3\n    semver: ^7.7.4\n    simple-git-hooks: ^2.13.1\n    taze: ^19.9.2\n    tsdown: ^0.18.4\n    tsx: ^4.21.0\n    typescript: ^5.9.3\n    undici: ^7.22.0\n    undici-types: ^7.22.0\n    vitefu: ^1.1.2\n    vitest: ^4.0.18\n    vue-tsc: ^3.2.5\n    zx: ^8.8.5\n  docs:\n    typeit: 8.1.0\n    vitepress: ^2.0.0-alpha.16\n    vitepress-plugin-group-icons: ^1.7.1\n    vitepress-plugin-llms: ^1.11.0\n  frontend:\n    '@antfu/utils': ^9.3.0\n    '@shikijs/engine-javascript': ^4.0.1\n    '@shikijs/markdown-it': ^4.0.1\n    '@slidev/rough-notation': ^0.1.0\n    '@unhead/vue': ^2.1.10\n    '@unocss/reset': ^66.6.3\n    '@vueuse/core': ^14.2.1\n    '@vueuse/math': ^14.2.1\n    '@vueuse/motion': ^3.0.3\n    drauu: ^1.0.0\n    file-saver: ^2.0.5\n    floating-vue: ^5.2.2\n    fuse.js: ^7.1.0\n    katex: ^0.16.33\n    lz-string: ^1.5.0\n    mermaid: ^11.12.3\n    nanotar: ^0.3.0\n    plantuml-encoder: ^1.4.0\n    recordrtc: ^5.6.2\n    shiki: ^4.0.1\n    shiki-magic-move: ^1.2.1\n    tm-grammars: ^1.31.2\n    unhead: ^2.1.10\n    vue: ^3.5.29\n    vue-router: ^5.0.3\n  icons:\n    '@iconify-json/carbon': ^1.2.19\n    '@iconify-json/mdi': ^1.2.3\n    '@iconify-json/ph': ^1.2.2\n    '@iconify-json/ri': ^1.2.10\n    '@iconify-json/svg-spinners': ^1.2.4\n    '@iconify/json': ^2.2.445\n  monaco:\n    '@shikijs/monaco': ^4.0.1\n    '@typescript/ata': ^0.9.8\n    monaco-editor: ^0.55.1\n  prod:\n    '@antfu/ni': ^28.2.0\n    '@comark/markdown-it': ^0.3.0\n    '@hedgedoc/markdown-it-plugins': ^2.1.4\n    '@lillallol/outline-pdf': ^4.0.0\n    '@shikijs/twoslash': ^4.0.1\n    '@shikijs/vitepress-twoslash': ^4.0.1\n    '@unocss/extractor-mdc': ^66.6.3\n    '@unocss/preset-mini': ^66.6.3\n    '@vitejs/plugin-vue': ^6.0.4\n    '@vitejs/plugin-vue-jsx': ^5.1.4\n    ansis: ^4.2.0\n    chokidar: ^5.0.0\n    cli-progress: ^3.12.0\n    connect: ^3.7.0\n    fast-deep-equal: ^3.1.3\n    fast-glob: ^3.3.3\n    get-port-please: ^3.2.0\n    global-directory: ^5.0.0\n    gray-matter: ^4.0.3\n    htmlparser2: ^10.1.0\n    is-installed-globally: ^1.0.0\n    jiti: ^2.6.1\n    local-pkg: ^1.1.2\n    magic-string: ^0.30.21\n    magic-string-stack: ^1.1.0\n    markdown-exit: ^1.0.0-beta.8\n    markdown-it-footnote: ^4.0.0\n    minimist: ^1.2.8\n    mlly: ^1.8.0\n    obug: ^2.1.1\n    open: ^11.0.0\n    pdf-lib: ^1.17.1\n    picomatch: ^4.0.3\n    pptxgenjs: ^4.0.1\n    prompts: ^2.4.2\n    public-ip: ^8.0.0\n    resolve-from: ^5.0.0\n    resolve-global: ^2.0.0\n    semver: ^7.7.4\n    sirv: ^3.0.2\n    source-map-js: ^1.2.1\n    tinyexec: ^1.0.2\n    unocss: ^66.6.2\n    unplugin-icons: ^23.0.1\n    unplugin-vue-components: ^31.0.0\n    unplugin-vue-markdown: ^30.0.0\n    untun: ^0.1.3\n    uqr: ^0.1.2\n    vite: ^7.3.1\n    vite-plugin-inspect: ^11.3.3\n    vite-plugin-remote-assets: ^2.1.0\n    vite-plugin-static-copy: ^3.2.0\n    vite-plugin-vue-server-ref: ^1.0.0\n    yaml: ^2.8.2\n    yargs: ^18.0.0\n  themes:\n    '@slidev/theme-default': ^0.25.0\n    '@slidev/theme-seriph': ^0.25.0\n  types:\n    '@types/cli-progress': ^3.11.6\n    '@types/connect': ^3.4.38\n    '@types/file-saver': ^2.0.7\n    '@types/js-yaml': ^4.0.9\n    '@types/katex': ^0.16.8\n    '@types/node': ^25.3.3\n    '@types/picomatch': ^4.0.2\n    '@types/plantuml-encoder': ^1.4.2\n    '@types/prompts': ^2.4.9\n    '@types/recordrtc': ^5.6.15\n    '@types/resolve': ^1.20.6\n    '@types/semver': ^7.7.1\n    '@types/yargs': ^17.0.35\n  vscode:\n    '@volar/language-server': ~2.4.28\n    '@volar/vscode': ^2.4.28\n    picomatch: ^4.0.3\n    prettier: ^2.8.8\n    reactive-vscode: ^1.0.0-beta.2\n    ts-json-schema-generator: ^2.5.0\n    volar-service-prettier: ^0.0.64\n    volar-service-yaml: ^0.0.64\n    vscode-uri: ^3.1.0\n    yaml-language-server: ^1.21.0\nonlyBuiltDependencies:\n  - '@vscode/vsce-sign'\n  - cypress\n  - esbuild\n  - keytar\n  - playwright-chromium\n  - simple-git-hooks\n  - typeit\n  - unrs-resolver\n"
  },
  {
    "path": "scripts/demo.mjs",
    "content": "import { fileURLToPath } from 'node:url'\nimport { $, cd, path } from 'zx'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\nconst demo = path.resolve(__dirname, '../docs/.vitepress/dist/demo')\n\nawait $`npm run build`\n\nconst demos = [\n  'composable-vue',\n  'starter',\n  'vue-runner',\n]\n\nfor (const name of demos) {\n  cd(path.resolve(__dirname, '../demo', name))\n  await $`npx slidev build --base /demo/${name}/ --out ${demo}/${name}`\n}\n"
  },
  {
    "path": "scripts/gen-layouts.ts",
    "content": "import fs from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport fg from 'fast-glob'\n\nconst files = await fg('*.vue', {\n  cwd: fileURLToPath(new URL('../packages/client/layouts', import.meta.url)),\n})\n\nawait fs.writeFile(new URL('../packages/types/src/builtin-layouts.ts', import.meta.url), `\nexport type BuiltinLayouts =\n${files.sort().map(i => `  | '${i.replace('.vue', '')}'`).join('\\n')}\n`, 'utf-8')\n"
  },
  {
    "path": "scripts/pack.mjs",
    "content": "import { resolve } from 'node:path'\nimport process from 'node:process'\nimport { $, argv, cd, fs } from 'zx'\n\nconst WORKSPACE_ROOT = process.cwd()\nconst PKG_ROOT = resolve(WORKSPACE_ROOT, argv._[0])\n\nconst packages = {\n  'types': './packages/types',\n  'parser': './packages/parser',\n  'cli': './packages/slidev',\n  'client': './packages/client',\n  'create-app': './packages/create-app',\n}\n\nasync function replaceDeps() {\n  cd(WORKSPACE_ROOT)\n  for (const path of Object.values(packages)) {\n    console.log('[pack] replaceDeps', path)\n    const pkgJson = JSON.parse(await fs.readFile(`${path}/package.json`, 'utf-8'))\n    for (const name in packages) {\n      const pkg = `@slidev/${name}`\n      if (pkgJson.dependencies?.[pkg])\n        pkgJson.dependencies[pkg] = `file:${PKG_ROOT}/${name}.tgz`\n      if (pkgJson.devDependencies?.[pkg])\n        pkgJson.devDependencies[pkg] = `file:${PKG_ROOT}/${name}.tgz`\n    }\n    await fs.writeFile(`${path}/package.json`, JSON.stringify(pkgJson, null, 2))\n  }\n}\n\nasync function pack() {\n  await replaceDeps()\n  await fs.mkdir(PKG_ROOT, { recursive: true })\n  for (const [name, path] of Object.entries(packages)) {\n    console.log('[pack] pack', path)\n    cd(resolve(WORKSPACE_ROOT, path))\n    const { stdout } = await $`pnpm pack`\n    await fs.move(stdout.split('\\n').filter(Boolean).at(-1).trim(), `${PKG_ROOT}/${name}.tgz`)\n  }\n}\n\nconsole.log('[pack] start packing...')\nawait pack()\nconsole.log('[pack] done')\n"
  },
  {
    "path": "scripts/publish.mjs",
    "content": "import { $, fs } from 'zx'\n\nawait fs.copyFile('README.md', 'packages/slidev/README.md')\nawait fs.copy('skills', 'packages/slidev/skills', { overwrite: true })\nawait $`pnpm -r publish --access public --no-git-checks`\n"
  },
  {
    "path": "scripts/remove-overridden-deps.mjs",
    "content": "import * as fs from 'node:fs/promises'\nimport { resolve } from 'node:path'\n\nconst path = resolve('./package.json')\n\nconst OVERRIDDEN_PKGS = [\n  '@slidev/cli',\n  '@slidev/types',\n  '@slidev/parser',\n  '@slidev/client',\n]\n\nasync function removeDeps() {\n  const pkgJson = JSON.parse(await fs.readFile(path, 'utf-8'))\n  let count = 0\n  for (const key in pkgJson.dependencies) {\n    if (OVERRIDDEN_PKGS.includes(key)) {\n      delete pkgJson.dependencies[key]\n      count++\n    }\n  }\n  for (const key in pkgJson.devDependencies) {\n    if (OVERRIDDEN_PKGS.includes(key)) {\n      delete pkgJson.devDependencies[key]\n      count++\n    }\n  }\n  await fs.writeFile(path, JSON.stringify(pkgJson, null, 2))\n  return count\n}\n\nconsole.log(`Removed ${await removeDeps()} dependencies from ${path}`)\n"
  },
  {
    "path": "scripts/update-versions.mjs",
    "content": "import { join } from 'node:path'\nimport { objectMap } from '@antfu/utils'\nimport { fs } from 'zx'\n\nconst templates = [\n  'packages/create-app/template',\n  'packages/create-theme/template',\n]\n\nconst { version } = await fs.readJSON('package.json')\n\nfor (const template of templates) {\n  const path = join(template, 'package.json')\n  const pkg = await fs.readJSON(path)\n  const deps = ['dependencies', 'devDependencies']\n  for (const name of deps) {\n    if (!pkg[name])\n      continue\n    pkg[name] = objectMap(pkg[name], (k, v) => {\n      if (k.startsWith('@slidev/') && !k.startsWith('@slidev/theme'))\n        return [k, `^${version}`]\n      return [k, v]\n    })\n  }\n  await fs.writeJSON(path, pkg, { spaces: 2 })\n}\n"
  },
  {
    "path": "shim.d.ts",
    "content": "export {}\ndeclare global {\n  const __DEV__: boolean\n  const __SLIDEV_HASH_ROUTE__: boolean\n  const __SLIDEV_CLIENT_ROOT__: string\n  const __SLIDEV_FEATURE_DRAWINGS__: boolean\n  const __SLIDEV_FEATURE_DRAWINGS_PERSIST__: boolean\n  const __SLIDEV_FEATURE_EDITOR__: boolean\n  const __SLIDEV_FEATURE_RECORD__: boolean\n  const __SLIDEV_FEATURE_PRESENTER__: boolean\n  const __SLIDEV_FEATURE_PRINT__: boolean\n  const __SLIDEV_FEATURE_BROWSER_EXPORTER__: boolean\n  const __SLIDEV_FEATURE_WAKE_LOCK__: boolean\n  const __SLIDEV_HAS_SERVER__: boolean\n}\n\ndeclare module '@vue/runtime-core' {\n  interface ComponentCustomProperties {\n    __DEV__: boolean\n    __SLIDEV_HASH_ROUTE__: boolean\n    __SLIDEV_CLIENT_ROOT__: string\n    __SLIDEV_FEATURE_DRAWINGS__: boolean\n    __SLIDEV_FEATURE_DRAWINGS_PERSIST__: boolean\n    __SLIDEV_FEATURE_EDITOR__: boolean\n    __SLIDEV_FEATURE_RECORD__: boolean\n    __SLIDEV_FEATURE_PRESENTER__: boolean\n    __SLIDEV_FEATURE_PRINT__: boolean\n    __SLIDEV_FEATURE_BROWSER_EXPORTER__: boolean\n    __SLIDEV_FEATURE_WAKE_LOCK__: boolean\n    __SLIDEV_HAS_SERVER__: boolean\n  }\n}\n"
  },
  {
    "path": "skills/GENERATION.md",
    "content": "# Skills Generation Information\n\nThis document contains information about how these skills were generated and how to keep them synchronized with the documentation.\n\n## Generation Details\n\n**Generated from documentation at:**\n- **Commit SHA**: `9d30081469cd7ed08586bb8973649a88567cfa25`\n- **Short SHA**: `9d300814`\n- **Date**: 2026-01-13 15:41:28 +0900\n- **Version**: v52.11.3\n- **Commit**: chore: release v52.11.3\n\n**Source documentation:**\n- Main docs: `/docs` folder\n\n**Generation date**: 2026-01-26\n\n## Structure\n\n```\nskills/\n├── GENERATION.md               # This file\n└── slidev/\n    ├── SKILL.md                # Table-based reference to all features\n    └── references/             # Agent-optimized reference files\n```\n\n## File Naming Convention\n\nFiles are prefixed by category:\n- `core-*` - Core documentation (syntax, config, CLI, etc.)\n- `code-*` - Code block features\n- `editor-*` - Editor integrations\n- `diagram-*` - Diagram and math support\n- `layout-*` - Layout and positioning\n- `style-*` - Styling features\n- `animation-*` - Animation features\n- `syntax-*` - Markdown syntax extensions\n- `presenter-*` - Presenter mode features\n- `build-*` - Build and export features\n- `tool-*` - CLI tools\n- `api-*` - API and hooks\n\n## How Skills Were Generated\n\nThe Slidev skills were created by:\n\n1. **Reading all documentation** from `/docs` folder:\n   - `/docs/guide/*.md` - Core guides (syntax, animations, layouts, etc.)\n   - `/docs/builtin/*.md` - Built-in features (CLI, components, layouts)\n   - `/docs/features/*.md` - Individual features (43 files)\n   - `/docs/custom/*.md` - Customization options\n\n2. **Creating table-based SKILL.md**: Main skill file with:\n   - Quick start and basic syntax\n   - Core references table (syntax, animations, config, CLI, etc.)\n   - Feature reference tables by category\n   - Links to detailed reference files\n\n3. **Creating core documentation references** (10 files):\n   - `core-syntax.md` - Markdown syntax from guide/syntax.md\n   - `core-animations.md` - Animation system from guide/animations.md\n   - `core-headmatter.md` - Deck config from custom/index.md\n   - `core-frontmatter.md` - Per-slide config from custom/index.md\n   - `core-cli.md` - CLI commands from builtin/cli.md\n   - `core-components.md` - Built-in components from builtin/components.md\n   - `core-layouts.md` - Built-in layouts from builtin/layouts.md\n   - `core-exporting.md` - Export options from guide/exporting.md\n   - `core-hosting.md` - Deployment from guide/hosting.md\n   - `core-global-context.md` - Navigation API from guide/global-context.md\n\n4. **Creating feature references** (41 files):\n   - Each feature from `/docs/features/` rewritten for agent consumption\n   - Concise, actionable format\n   - Clear code examples\n   - No unnecessary prose\n   - Prefixed by category\n\n## Updating Skills (For Future Agents)\n\nWhen Slidev documentation changes, follow these steps to update the skills:\n\n### 1. Check for Documentation Changes\n\n```bash\n# Get changes in docs since generation\ngit diff sha_of_last_generation..HEAD -- docs/\n\n# List changed files\ngit diff --name-only sha_of_last_generation..HEAD -- docs/\n\n# Get summary of changes\ngit log --oneline sha_of_last_generation..HEAD -- docs/\n```\n\n### 2. Identify What Changed\n\nFocus on these documentation areas:\n- `/docs/guide/` - Core concepts and workflows\n- `/docs/builtin/` - Built-in features (CLI, components, layouts)\n- `/docs/features/` - Individual features\n- `/docs/custom/` - Configuration and customization\n\n### 3. Update Skills\n\n**For minor changes** (typos, clarifications, small additions):\n- Update the relevant section in `SKILL.md`\n- Update corresponding file in `references/` if needed\n\n**For major changes**:\n- Read the changed documentation files\n- Update `SKILL.md` sections:\n  - Add new changes to appropriate sections\n  - Update examples if APIs changed\n  - Add to \"Common Patterns\" if applicable\n- Update or add files in `references/`\n\n**For new features**:\n- Add documentation file to `references/` with appropriate prefix\n  - Do not just copy the file from the docs, rewrite it for agent consumption\n- Add entry in `SKILL.md` with:\n  - Brief description in relevant section\n  - Code example\n  - Best practices if applicable\n  - Reference to detailed docs\n\n### 4. Incremental Sync Process\n\n```bash\n# 1. Check what docs changed\ngit diff sha_of_last_generation..HEAD -- docs/ > docs_changes.patch\n\n# 2. Review the changes\ncat docs_changes.patch\n\n# 4. Update references based on changes\n\n# 5. Update SKILL.md based on changes\n\n# 6. Update this file with new SHA\ngit rev-parse HEAD  # Get new SHA\n# Update GENERATION.md with new SHA\n```\n\n### 5. Sync Checklist\n\n- [ ] Read diff of docs since last generation\n- [ ] Identify new features added\n- [ ] Identify changed/deprecated features\n- [ ] Update `SKILL.md` with changes:\n  - [ ] Add new features to appropriate sections\n  - [ ] Update changed examples\n  - [ ] Remove deprecated features\n  - [ ] Update best practices if needed\n- [ ] Update `references/` folder:\n  - [ ] Add new feature docs with category prefix\n  - [ ] Update changed feature docs\n  - [ ] Remove deprecated feature docs\n- [ ] Update `index.md` if files added/removed\n- [ ] Update this `GENERATION.md` with new SHA\n\n## Maintenance Notes\n\n### Key Sections to Keep Updated\n\n1. **Core Concepts** - Only update for fundamental changes\n2. **Best Practices** - Update when new patterns emerge\n3. **Common Patterns** - Add new patterns as documentation shows them\n4. **Configuration** - Update when new options added\n5. **Troubleshooting** - Add new issues/solutions as they appear\n6. **References** - Always keep in sync with `/docs/features/`\n\n### When to Regenerate Completely\n\nConsider full regeneration when:\n- Major version update (v1.x → v2.x)\n- Complete documentation restructure\n- Multiple breaking changes\n- More than 30% of docs changed\n\n### Style Guidelines\n\nWhen updating, maintain style:\n- Practical, actionable guidance\n- ✅ Good vs ❌ Bad examples\n- Focus on common use cases\n- Concise explanations\n- Real-world patterns\n- Reference detailed docs for deep dives\n\n## Version History\n\n| Date       | SHA      | Version   | Changes |\n|------------|----------|-----------|---------|\n| 2026-01-26 | 9d300814 | v52.11.3  | Initial generation from docs |\n\n## Agent Instructions Summary\n\n**For future agents updating these skills:**\n\n1. Run `git diff sha_of_last_generation..HEAD -- docs/` to see all documentation changes\n2. Read changed files to understand what's new or modified\n3. Update `SKILL.md` by:\n   - Adding new features to appropriate sections\n   - Updating examples for changed APIs\n   - Removing deprecated features\n   - Adding new best practices or patterns\n4. Sync `references/` folder with `/docs/features/`\n5. Update this file with new SHA and version\n6. Test that skill content is accurate and follows style\n\n**Remember**: The goal is incremental updates, not complete rewrites. Only change what needs to change based on documentation diffs.\n\n## Questions?\n\nIf you're unsure about whether changes warrant updates:\n- **Small changes** (typos, clarifications): Optional, can skip\n- **New features**: Must add to skills\n- **Changed APIs**: Must update examples\n- **Deprecated features**: Must remove or mark as deprecated\n- **New best practices**: Should add to relevant sections\n\n---\n\nLast updated: 2026-01-26\nCurrent SHA: 9d300814\n"
  },
  {
    "path": "skills/slidev/README.md",
    "content": "# Slidev Skills for Claude Code\n\nAgent skills that help Claude Code understand and work with [Slidev](https://sli.dev) presentations.\n\n## Installation\n\n```bash\nnpx skills add slidevjs/slidev\n```\n\nThis will add the Slidev skill to your Claude Code configuration.\n\n## What's Included\n\nThe Slidev skill provides Claude Code with knowledge about:\n\n- **Core Syntax** - Markdown syntax, slide separators, frontmatter\n- **Animations** - Click animations, transitions, motion effects\n- **Code Features** - Line highlighting, Monaco editor, code groups, magic-move\n- **Diagrams** - Mermaid, PlantUML, LaTeX math\n- **Layouts** - Built-in layouts, slots, global layers\n- **Presenter Mode** - Recording, timer, remote access\n- **Exporting** - PDF, PPTX, PNG, SPA hosting\n\n## Usage\n\nOnce installed, Claude Code will automatically use Slidev knowledge when:\n\n- Creating new presentations\n- Adding slides with code examples\n- Setting up animations and transitions\n- Configuring themes and layouts\n- Exporting presentations\n\n### Example Prompts\n\n```\nCreate a Slidev presentation about TypeScript generics with code examples\n```\n\n```\nAdd a two-column slide with code on the left and explanation on the right\n```\n\n```\nSet up click animations to reveal bullet points one by one\n```\n\n```\nConfigure the presentation for PDF export with speaker notes\n```\n\n## Documentation\n\n- [Slidev Documentation](https://sli.dev)\n- [Theme Gallery](https://sli.dev/resources/theme-gallery)\n- [Showcases](https://sli.dev/resources/showcases)\n\n## License\n\nMIT\n"
  },
  {
    "path": "skills/slidev/SKILL.md",
    "content": "---\nname: slidev\ndescription: Create and present web-based slidedecks for developers using Slidev with Markdown, Vue components, code highlighting, animations, and interactive features. Use when building technical presentations, conference talks, code walkthroughs, teaching materials, or developer decks.\n---\n\n# Slidev - Presentation Slides for Developers\n\nWeb-based slides maker built on Vite, Vue, and Markdown.\n\n## When to Use\n\n- Technical presentations or slidedecks with live code examples\n- Syntax-highlighted code snippets with animations\n- Interactive demos (Monaco editor, runnable code)\n- Mathematical equations (LaTeX) or diagrams (Mermaid, PlantUML)\n- Record presentations with presenter notes\n- Export to PDF, PPTX, or host as SPA\n- Code walkthroughs for developer talks or workshops\n\n## Quick Start\n\n```bash\npnpm create slidev    # Create project\npnpm run dev          # Start dev server (opens http://localhost:3030)\npnpm run build        # Build static SPA\npnpm run export       # Export to PDF (requires playwright-chromium)\n```\n\n**Verify**: After `pnpm run dev`, confirm slides load at `http://localhost:3030`. After `pnpm run export`, check the output PDF exists in the project root.\n\n## Basic Syntax\n\n```md\n---\ntheme: default\ntitle: My Presentation\n---\n\n# First Slide\n\nContent here\n\n---\n\n# Second Slide\n\nMore content\n\n<!--\nPresenter notes go here\n-->\n```\n\n- `---` separates slides\n- First frontmatter = headmatter (deck config)\n- HTML comments = presenter notes\n\n## Core References\n\n| Topic | Description | Reference |\n|-------|-------------|-----------|\n| Markdown Syntax | Slide separators, frontmatter, notes, code blocks | [core-syntax](references/core-syntax.md) |\n| Animations | v-click, v-clicks, motion, transitions | [core-animations](references/core-animations.md) |\n| Headmatter | Deck-wide configuration options | [core-headmatter](references/core-headmatter.md) |\n| Frontmatter | Per-slide configuration options | [core-frontmatter](references/core-frontmatter.md) |\n| CLI Commands | Dev, build, export, theme commands | [core-cli](references/core-cli.md) |\n| Components | Built-in Vue components | [core-components](references/core-components.md) |\n| Layouts | Built-in slide layouts | [core-layouts](references/core-layouts.md) |\n| Exporting | PDF, PPTX, PNG export options | [core-exporting](references/core-exporting.md) |\n| Hosting | Build and deploy to various platforms | [core-hosting](references/core-hosting.md) |\n| Global Context | $nav, $slidev, composables API | [core-global-context](references/core-global-context.md) |\n\n## Feature Reference\n\n### Code & Editor\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Line highlighting | `` ```ts {2,3} `` | [code-line-highlighting](references/code-line-highlighting.md) |\n| Click-based highlighting | `` ```ts {1\\|2-3\\|all} `` | [code-line-highlighting](references/code-line-highlighting.md) |\n| Line numbers | `lineNumbers: true` or `{lines:true}` | [code-line-numbers](references/code-line-numbers.md) |\n| Scrollable code | `{maxHeight:'100px'}` | [code-max-height](references/code-max-height.md) |\n| Code tabs | `::code-group` (requires `comark: true`) | [code-groups](references/code-groups.md) |\n| Monaco editor | `` ```ts {monaco} `` | [editor-monaco](references/editor-monaco.md) |\n| Run code | `` ```ts {monaco-run} `` | [editor-monaco-run](references/editor-monaco-run.md) |\n| Edit files | `<<< ./file.ts {monaco-write}` | [editor-monaco-write](references/editor-monaco-write.md) |\n| Code animations | `` ````md magic-move `` | [code-magic-move](references/code-magic-move.md) |\n| TypeScript types | `` ```ts twoslash `` | [code-twoslash](references/code-twoslash.md) |\n| Import code | `<<< @/snippets/file.js` | [code-import-snippet](references/code-import-snippet.md) |\n\n### Diagrams & Math\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Mermaid diagrams | `` ```mermaid `` | [diagram-mermaid](references/diagram-mermaid.md) |\n| PlantUML diagrams | `` ```plantuml `` | [diagram-plantuml](references/diagram-plantuml.md) |\n| LaTeX math | `$inline$` or `$$block$$` | [diagram-latex](references/diagram-latex.md) |\n\n### Layout & Styling\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Canvas size | `canvasWidth`, `aspectRatio` | [layout-canvas-size](references/layout-canvas-size.md) |\n| Zoom slide | `zoom: 0.8` | [layout-zoom](references/layout-zoom.md) |\n| Scale elements | `<Transform :scale=\"0.5\">` | [layout-transform](references/layout-transform.md) |\n| Layout slots | `::right::`, `::default::` | [layout-slots](references/layout-slots.md) |\n| Scoped CSS | `<style>` in slide | [style-scoped](references/style-scoped.md) |\n| Global layers | `global-top.vue`, `global-bottom.vue` | [layout-global-layers](references/layout-global-layers.md) |\n| Draggable elements | `v-drag`, `<v-drag>` | [layout-draggable](references/layout-draggable.md) |\n| Icons | `<mdi-icon-name />` | [style-icons](references/style-icons.md) |\n\n### Animation & Interaction\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Click animations | `v-click`, `<v-clicks>` | [core-animations](references/core-animations.md) |\n| Rough markers | `v-mark.underline`, `v-mark.circle` | [animation-rough-marker](references/animation-rough-marker.md) |\n| Drawing mode | Press `C` or config `drawings:` | [animation-drawing](references/animation-drawing.md) |\n| Direction styles | `forward:delay-300` | [style-direction](references/style-direction.md) |\n| Note highlighting | `[click]` in notes | [animation-click-marker](references/animation-click-marker.md) |\n\n### Syntax Extensions\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Comark syntax | `comark: true` + `{style=\"color:red\"}` | [syntax-comark](references/syntax-comark.md) |\n| Block frontmatter | `` ```yaml `` instead of `---` | [syntax-block-frontmatter](references/syntax-block-frontmatter.md) |\n| Import slides | `src: ./other.md` | [syntax-importing-slides](references/syntax-importing-slides.md) |\n| Merge frontmatter | Main entry wins | [syntax-frontmatter-merging](references/syntax-frontmatter-merging.md) |\n\n### Presenter & Recording\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Recording | Press `G` for camera | [presenter-recording](references/presenter-recording.md) |\n| Timer | `duration: 30min`, `timer: countdown` | [presenter-timer](references/presenter-timer.md) |\n| Remote control | `slidev --remote` | [presenter-remote](references/presenter-remote.md) |\n| Ruby text | `notesAutoRuby:` | [presenter-notes-ruby](references/presenter-notes-ruby.md) |\n\n### Export & Build\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Export options | `slidev export` | [core-exporting](references/core-exporting.md) |\n| Build & deploy | `slidev build` | [core-hosting](references/core-hosting.md) |\n| Build with PDF | `download: true` | [build-pdf](references/build-pdf.md) |\n| Cache images | Automatic for remote URLs | [build-remote-assets](references/build-remote-assets.md) |\n| OG image | `seoMeta.ogImage` or `og-image.png` | [build-og-image](references/build-og-image.md) |\n| SEO tags | `seoMeta:` | [build-seo-meta](references/build-seo-meta.md) |\n\n**Export prerequisite**: `pnpm add -D playwright-chromium` is required for PDF/PPTX/PNG export. If export fails with a browser error, install this dependency first.\n\n### Editor & Tools\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Side editor | Click edit icon | [editor-side](references/editor-side.md) |\n| VS Code extension | Install `antfu.slidev` | [editor-vscode](references/editor-vscode.md) |\n| Prettier | `prettier-plugin-slidev` | [editor-prettier](references/editor-prettier.md) |\n| Eject theme | `slidev theme eject` | [tool-eject-theme](references/tool-eject-theme.md) |\n\n### Lifecycle & API\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Slide hooks | `onSlideEnter()`, `onSlideLeave()` | [api-slide-hooks](references/api-slide-hooks.md) |\n| Navigation API | `$nav`, `useNav()` | [core-global-context](references/core-global-context.md) |\n\n## Common Layouts\n\n| Layout | Purpose |\n|--------|---------|\n| `cover` | Title/cover slide |\n| `center` | Centered content |\n| `default` | Standard slide |\n| `two-cols` | Two columns (use `::right::`) |\n| `two-cols-header` | Header + two columns |\n| `image` / `image-left` / `image-right` | Image layouts |\n| `iframe` / `iframe-left` / `iframe-right` | Embed URLs |\n| `quote` | Quotation |\n| `section` | Section divider |\n| `fact` / `statement` | Data/statement display |\n| `intro` / `end` | Intro/end slides |\n\n## Resources\n\n- Documentation: https://sli.dev\n- Theme Gallery: https://sli.dev/resources/theme-gallery\n- Showcases: https://sli.dev/resources/showcases\n"
  },
  {
    "path": "skills/slidev/references/animation-click-marker.md",
    "content": "---\nname: click-marker\ndescription: Highlight and auto-scroll presenter notes based on click progress\n---\n\n# Click Markers\n\nHighlight and auto-scroll presenter notes based on click progress.\n\n## Syntax\n\nAdd `[click]` markers in presenter notes:\n\n```md\n<!--\nContent before the first click\n\n[click] This will be highlighted after the first click\n\nAlso highlighted after the first click\n\n- [click] This list element highlights after the second click\n\n[click:3] Last click (skip two clicks)\n-->\n```\n\n## Behavior\n\n- Notes between markers highlight in sync with slide progress\n- Auto-scrolls presenter view to active section\n- Use `[click:{n}]` to skip to specific click number\n\n## Requirements\n\n- Only works in presenter mode\n- Notes must be HTML comments at end of slide\n"
  },
  {
    "path": "skills/slidev/references/animation-drawing.md",
    "content": "---\nname: drawing\ndescription: Draw and annotate slides during presentation\n---\n\n# Drawing & Annotations\n\nDraw and annotate slides during presentation. Powered by drauu.\n\n## Enable Drawing\n\nClick the pen icon in the navigation bar or press `C`.\n\n## Stylus Support\n\nStylus pens (iPad + Apple Pencil) work automatically - draw with pen, navigate with fingers.\n\n## Persist Drawings\n\nSave drawings as SVGs and include in exports:\n\n```md\n---\ndrawings:\n  persist: true\n---\n```\n\nDrawings saved to `.slidev/drawings/`.\n\n## Disable Drawing\n\nEntirely:\n```md\n---\ndrawings:\n  enabled: false\n---\n```\n\nOnly in development:\n```md\n---\ndrawings:\n  enabled: dev\n---\n```\n\nOnly in presenter mode:\n```md\n---\ndrawings:\n  presenterOnly: true\n---\n```\n\n## Sync Settings\n\nDisable sync across instances:\n\n```md\n---\ndrawings:\n  syncAll: false\n---\n```\n\nOnly presenter's drawings sync to others.\n"
  },
  {
    "path": "skills/slidev/references/animation-rough-marker.md",
    "content": "---\nname: rough-marker\ndescription: Hand-drawn style highlighting using Rough Notation\n---\n\n# Rough Markers\n\nHand-drawn style highlighting using Rough Notation.\n\n## v-mark Directive\n\n```html\n<span v-mark>Important text</span>\n```\n\n## Marker Types\n\n```html\n<span v-mark.underline>Underlined</span>\n<span v-mark.circle>Circled</span>\n<span v-mark.highlight>Highlighted</span>\n<span v-mark.strike-through>Struck through</span>\n<span v-mark.box>Boxed</span>\n```\n\n## Colors\n\n```html\n<span v-mark.red>Red marker</span>\n<span v-mark.blue>Blue marker</span>\n```\n\nCustom color:\n```html\n<span v-mark=\"{ color: '#234' }\">Custom color</span>\n```\n\n## Click Timing\n\nWorks like v-click:\n\n```html\n<span v-mark=\"5\">Appears on click 5</span>\n<span v-mark=\"'+1'\">Next click</span>\n```\n\n## Full Options\n\n```html\n<span v-mark=\"{ at: 5, color: '#234', type: 'circle' }\">\n  Custom marker\n</span>\n```\n"
  },
  {
    "path": "skills/slidev/references/api-slide-hooks.md",
    "content": "---\nname: slide-hooks\ndescription: Lifecycle hooks for slide components\n---\n\n# Slide Hooks\n\nLifecycle hooks for slide components.\n\n## Available Hooks\n\n```ts\nimport { onSlideEnter, onSlideLeave, useIsSlideActive } from '@slidev/client'\n\nconst isActive = useIsSlideActive()\n\nonSlideEnter(() => {\n  // Called when slide becomes active\n})\n\nonSlideLeave(() => {\n  // Called when slide becomes inactive\n})\n```\n\n## Important\n\nDo NOT use `onMounted` / `onUnmounted` in slides - component instance persists even when slide is inactive.\n\nUse `onSlideEnter` and `onSlideLeave` instead.\n\n## Use Cases\n\n- Start/stop animations\n- Play/pause media\n- Initialize/cleanup resources\n- Track analytics\n"
  },
  {
    "path": "skills/slidev/references/build-og-image.md",
    "content": "---\nname: og-image\ndescription: Configure Open Graph preview image for social sharing\n---\n\n# Open Graph Image\n\nSet preview image for social media sharing.\n\n## Custom URL\n\n```md\n---\nseoMeta:\n  ogImage: https://url.to.your.image.png\n---\n```\n\n## Local Image\n\nPlace `./og-image.png` in project root - Slidev uses it automatically.\n\n## Auto-generate\n\nGenerate from first slide:\n\n```md\n---\nseoMeta:\n  ogImage: auto\n---\n```\n\nUses Playwright to capture first slide. Requires playwright to be installed.\n\nGenerated image saved as `./og-image.png` - can be committed to repo.\n"
  },
  {
    "path": "skills/slidev/references/build-pdf.md",
    "content": "---\nname: pdf\ndescription: Include downloadable PDF in SPA build\n---\n\n# Generate PDF when Building\n\nGenerate a downloadable PDF alongside your built slides.\n\n## Enable in Headmatter\n\n```md\n---\ndownload: true\n---\n```\n\nThis generates a PDF and adds a download button to the built slides.\n\n## Custom PDF URL\n\nSkip generation and use an existing PDF:\n\n```md\n---\ndownload: 'https://example.com/my-talk.pdf'\n---\n```\n\n## CLI Option\n\n```bash\nslidev build --download\n```\n\n## Export Options\n\nConfigure PDF export settings via:\n- CLI: `slidev build --download --with-clicks --timeout 60000`\n- Headmatter: Set `exportFilename`, `withClicks`, etc.\n"
  },
  {
    "path": "skills/slidev/references/build-remote-assets.md",
    "content": "---\nname: remote-assets\ndescription: Bundle remote images and assets for offline use\n---\n\n# Bundle Remote Assets\n\nRemote images are automatically cached on first run for faster loading.\n\n## Remote Images\n\n```md\n![Remote Image](https://sli.dev/favicon.png)\n```\n\nCached automatically by vite-plugin-remote-assets.\n\n## Local Images\n\nPlace in `public/` folder and reference with leading slash:\n\n```md\n![Local Image](/pic.png)\n```\n\nDo NOT use relative paths like `./pic.png`.\n\n## Custom Styling\n\nConvert to img tag for custom sizes/styles:\n\n```html\n<img src=\"/pic.png\" class=\"m-40 h-40 rounded shadow\" />\n```\n"
  },
  {
    "path": "skills/slidev/references/build-seo-meta.md",
    "content": "---\nname: seo-meta\ndescription: Configure SEO and social media meta tags\n---\n\n# SEO Meta Tags\n\nConfigure social media and search engine meta tags.\n\n## Configuration\n\n```yaml\n---\nseoMeta:\n  ogTitle: Slidev Starter Template\n  ogDescription: Presentation slides for developers\n  ogImage: https://cover.sli.dev\n  ogUrl: https://example.com\n  twitterCard: summary_large_image\n  twitterTitle: Slidev Starter Template\n  twitterDescription: Presentation slides for developers\n  twitterImage: https://cover.sli.dev\n  twitterSite: username\n  twitterUrl: https://example.com\n---\n```\n\n## Available Options\n\n**Open Graph (Facebook, LinkedIn):**\n- `ogTitle` - Title\n- `ogDescription` - Description\n- `ogImage` - Preview image URL\n- `ogUrl` - Canonical URL\n\n**Twitter Card:**\n- `twitterCard` - Card type (summary, summary_large_image)\n- `twitterTitle` - Title\n- `twitterDescription` - Description\n- `twitterImage` - Preview image URL\n- `twitterSite` - Twitter username\n\nPowered by unhead.\n"
  },
  {
    "path": "skills/slidev/references/code-groups.md",
    "content": "---\nname: code-groups\ndescription: Group multiple code blocks with tabs and automatic icons\n---\n\n# Code Groups\n\nGroup multiple code blocks with tabs and automatic icons.\n\n## Requirements\n\nEnable Comark syntax in headmatter:\n\n```md\n---\ncomark: true\n---\n```\n\n## Syntax\n\n````md\n::code-group\n\n```sh [npm]\nnpm i @slidev/cli\n```\n\n```sh [yarn]\nyarn add @slidev/cli\n```\n\n```sh [pnpm]\npnpm add @slidev/cli\n```\n\n::\n````\n\n## Title Icon Matching\n\nIcons auto-match by title name. Install `@iconify-json/vscode-icons` for built-in icons.\n\nSupported: npm, yarn, pnpm, bun, deno, vue, react, typescript, javascript, and many more.\n\n## Custom Icons\n\nUse `~icon~` syntax in title:\n\n````md\n```js [npm ~i-uil:github~]\nconsole.log('Hello!')\n```\n````\n\nRequires:\n1. Install icon collection: `pnpm add @iconify-json/uil`\n2. Add to safelist in `uno.config.ts`:\n\n```ts\nexport default defineConfig({\n  safelist: ['i-uil:github']\n})\n```\n"
  },
  {
    "path": "skills/slidev/references/code-import-snippet.md",
    "content": "---\nname: import-snippet\ndescription: Import code from external files into slides with optional region selection\n---\n\n# Import Code Snippets\n\nImport code from external files into slides.\n\n## Basic Syntax\n\n```md\n<<< @/snippets/snippet.js\n```\n\n`@` = package root directory. Recommended: place snippets in `@/snippets/`.\n\n## Import Region\n\nUse VS Code region syntax:\n\n```md\n<<< @/snippets/snippet.js#region-name\n```\n\n## Specify Language\n\n```md\n<<< @/snippets/snippet.js ts\n```\n\n## With Features\n\nCombine with line highlighting, Monaco editor:\n\n```md\n<<< @/snippets/snippet.js {2,3|5}{lines:true}\n<<< @/snippets/snippet.js ts {monaco}{height:200px}\n```\n\n## Placeholder\n\nUse `{*}` for line highlighting placeholder:\n\n```md\n<<< @/snippets/snippet.js {*}{lines:true}\n```\n\n## Monaco Write\n\nLink editor to file for live editing:\n\n```md\n<<< ./some-file.ts {monaco-write}\n```\n"
  },
  {
    "path": "skills/slidev/references/code-line-highlighting.md",
    "content": "---\nname: line-highlighting\ndescription: Highlight specific lines in code blocks with static or click-based dynamic highlighting\n---\n\n# Line Highlighting\n\nHighlight specific lines in code blocks.\n\n## Static Highlighting\n\n````md\n```ts {2,3}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\n## Dynamic (Click-based)\n\nUse `|` to separate stages:\n\n````md\n```ts {2-3|5|all}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\nClick progression: lines 2-3 → line 5 → all lines\n\n## Special Values\n\n- `hide` - Hide the code block\n- `none` - Show code without highlighting\n- `all` - Highlight all lines\n\n````md\n```ts {hide|none|all}\n// Hidden → No highlight → All highlighted\n```\n````\n"
  },
  {
    "path": "skills/slidev/references/code-line-numbers.md",
    "content": "---\nname: line-numbers\ndescription: Enable line numbering for code blocks globally or per-block\n---\n\n# Code Block Line Numbers\n\nEnable line numbering for code blocks.\n\n## Global Setting\n\nEnable for all code blocks in headmatter:\n\n```md\n---\nlineNumbers: true\n---\n```\n\n## Per-Block Setting\n\n````md\n```ts {6,7}{lines:true,startLine:5}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n```\n````\n\n## Options\n\n- `lines: true/false` - Enable/disable line numbers\n- `startLine: number` - Starting line number (default: 1)\n\n## With Line Highlighting\n\nUse `{*}` as placeholder when combining with other features:\n\n````md\n```ts {*}{lines:true,startLine:5}\n// code here\n```\n````\n"
  },
  {
    "path": "skills/slidev/references/code-magic-move.md",
    "content": "---\nname: magic-move\ndescription: Animate code changes with smooth transitions between code blocks\n---\n\n# Shiki Magic Move\n\nAnimate code changes with smooth transitions (like Keynote's Magic Move).\n\n## Basic Usage\n\n`````md\n````md magic-move\n```js\nconsole.log(`Step ${1}`)\n```\n```js\nconsole.log(`Step ${1 + 1}`)\n```\n```ts\nconsole.log(`Step ${3}` as string)\n```\n````\n`````\n\nNote: Use 4 backticks for the wrapper.\n\n## With Line Highlighting\n\n`````md\n````md magic-move {at:4, lines: true}\n```js {*|1|2-5}\nlet count = 1\nfunction add() {\n  count++\n}\n```\n\nNon-code blocks in between are ignored.\n\n```js {*}{lines: false}\nlet count = 1\nconst add = () => count += 1\n```\n````\n`````\n\n## How It Works\n\n- Wraps multiple code blocks as one\n- Each block is a \"step\"\n- Morphs between steps on click\n- Syntax highlighting preserved during animation\n\n## Resources\n\n- Playground: https://shiki-magic-move.netlify.app/\n"
  },
  {
    "path": "skills/slidev/references/code-max-height.md",
    "content": "---\nname: max-height\ndescription: Set a fixed height for code blocks with scrolling for long code\n---\n\n# Code Block Max Height\n\nSet a fixed height for code blocks with scrolling.\n\n## Usage\n\n````md\n```ts {2|3|7|12}{maxHeight:'100px'}\nfunction add(\n  a: Ref<number> | number,\n  b: Ref<number> | number\n) {\n  return computed(() => unref(a) + unref(b))\n}\n/// ...as many lines as you want\nconst c = add(1, 2)\n```\n````\n\n## With Line Highlighting Placeholder\n\nUse `{*}` when you only need maxHeight:\n\n````md\n```ts {*}{maxHeight:'100px'}\n// long code here\n```\n````\n\n## Use Case\n\nWhen code is too long to fit on one slide but you want to show it all with scrolling.\n"
  },
  {
    "path": "skills/slidev/references/code-twoslash.md",
    "content": "---\nname: twoslash\ndescription: Show TypeScript type information inline or on hover in code blocks\n---\n\n# TwoSlash Integration\n\nShow TypeScript type information inline or on hover.\n\n## Usage\n\n````md\n```ts twoslash\nimport { ref } from 'vue'\n\nconst count = ref(0)\n//            ^?\n```\n````\n\n## Features\n\n- Type information on hover\n- Inline type annotations with `^?`\n- Errors and warnings display\n- Full TypeScript compiler integration\n\n## Annotations\n\n```ts twoslash\nconst count = ref(0)\n//            ^?\n// Shows: const count: Ref<number>\n```\n\n## Use Case\n\nPerfect for TypeScript/JavaScript teaching materials where showing types helps understanding.\n\n## Resources\n\n- TwoSlash docs: https://twoslash.netlify.app/\n"
  },
  {
    "path": "skills/slidev/references/core-animations.md",
    "content": "---\nname: animations\ndescription: Click animations, motion effects, and slide transitions\n---\n\n# Animations\n\nClick animations, motion effects, and slide transitions.\n\n## Click Animations\n\n### v-click Directive\n\n```md\n<div v-click>Appears on click</div>\n<div v-click>Appears on next click</div>\n```\n\n### v-clicks Component\n\nAnimate list items:\n\n```md\n<v-clicks>\n\n- Item 1\n- Item 2\n- Item 3\n\n</v-clicks>\n```\n\nWith depth for nested lists:\n\n```md\n<v-clicks depth=\"2\">\n\n- Parent 1\n  - Child 1\n  - Child 2\n- Parent 2\n\n</v-clicks>\n```\n\n### Click Positioning\n\nRelative positioning:\n```md\n<div v-click>1st (default)</div>\n<div v-click=\"+1\">2nd</div>\n<div v-click=\"-1\">Same as previous</div>\n```\n\nAbsolute positioning:\n```md\n<div v-click=\"3\">Appears on click 3</div>\n<div v-click=\"[2,5]\">Visible clicks 2-5</div>\n```\n\n### v-after\n\nShow with previous element:\n\n```md\n<div v-click>Main element</div>\n<div v-after>Appears with main element</div>\n```\n\n### v-switch\n\nConditional rendering by click:\n\n```md\n<v-switch>\n  <template #1>First state</template>\n  <template #2>Second state</template>\n  <template #3>Third state</template>\n</v-switch>\n```\n\n## Custom Click Count\n\n```md\n---\nclicks: 10\n---\n```\n\nOr starting from specific count:\n\n```md\n---\nclicksStart: 5\n---\n```\n\n## Motion Animations\n\nUsing @vueuse/motion:\n\n```md\n<div\n  v-motion\n  :initial=\"{ x: -100, opacity: 0 }\"\n  :enter=\"{ x: 0, opacity: 1 }\"\n>\n  Animated content\n</div>\n```\n\nClick-based motion:\n\n```md\n<div\n  v-motion\n  :initial=\"{ scale: 1 }\"\n  :click-1=\"{ scale: 1.5 }\"\n  :click-2=\"{ scale: 1 }\"\n>\n  Scales on clicks\n</div>\n```\n\n## Slide Transitions\n\nIn headmatter (all slides):\n\n```md\n---\ntransition: slide-left\n---\n```\n\nPer-slide:\n\n```md\n---\ntransition: fade\n---\n```\n\n### Built-in Transitions\n\n- `fade` / `fade-out`\n- `slide-left` / `slide-right`\n- `slide-up` / `slide-down`\n- `view-transition` (View Transitions API)\n\n### Directional Transitions\n\nDifferent transitions for forward/backward:\n\n```md\n---\ntransition: slide-left | slide-right\n---\n```\n\n### Custom Transitions\n\nDefine CSS classes:\n\n```css\n.my-transition-enter-active,\n.my-transition-leave-active {\n  transition: all 0.5s ease;\n}\n.my-transition-enter-from,\n.my-transition-leave-to {\n  opacity: 0;\n  transform: translateX(100px);\n}\n```\n\nUse: `transition: my-transition`\n\n## CSS Classes\n\nAnimation targets get these classes:\n- `.slidev-vclick-target` - Animated element\n- `.slidev-vclick-hidden` - Hidden state\n- `.slidev-vclick-current` - Current click target\n- `.slidev-vclick-prior` - Previously shown\n\n## Default Animation CSS\n\n```css\n.slidev-vclick-target {\n  transition: opacity 100ms ease;\n}\n.slidev-vclick-hidden {\n  opacity: 0;\n  pointer-events: none;\n}\n```\n"
  },
  {
    "path": "skills/slidev/references/core-cli.md",
    "content": "---\nname: cli\ndescription: Slidev command-line interface reference\n---\n\n# CLI Commands\n\nSlidev command-line interface reference.\n\n## Dev Server\n\n```bash\nslidev [entry]\nslidev slides.md\n```\n\nOptions:\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--port` | 3030 | Server port |\n| `--open` | false | Open browser |\n| `--remote [password]` | - | Enable remote access |\n| `--bind` | 0.0.0.0 | Bind address |\n| `--base` | / | Base URL path |\n| `--log` | warn | Log level |\n| `--force` | false | Force optimizer re-bundle |\n| `--theme` | - | Override theme |\n\nExamples:\n```bash\nslidev --port 8080 --open\nslidev --remote mypassword\nslidev --base /talks/my-talk/\n```\n\n## Build\n\n```bash\nslidev build [entry]\n```\n\nOptions:\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--out` | dist | Output directory |\n| `--base` | / | Base URL for deployment |\n| `--download` | false | Include PDF download |\n| `--theme` | - | Override theme |\n| `--without-notes` | false | Exclude presenter notes |\n\nExamples:\n```bash\nslidev build --base /my-repo/\nslidev build --download --out public\nslidev build slides1.md slides2.md  # Multiple builds\n```\n\n## Export\n\n```bash\nslidev export [entry]\n```\n\nOptions:\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--output` | - | Output filename |\n| `--format` | pdf | pdf / png / pptx / md |\n| `--timeout` | 30000 | Timeout per slide (ms) |\n| `--range` | - | Slide range (e.g., 1,4-7) |\n| `--dark` | false | Export dark mode |\n| `--with-clicks` | false | Include click steps |\n| `--with-toc` | false | PDF table of contents |\n| `--wait` | 0 | Wait ms before export |\n| `--wait-until` | networkidle | Wait condition |\n| `--omit-background` | false | Transparent background |\n| `--executable-path` | - | Browser path |\n\nExamples:\n```bash\nslidev export\nslidev export --format pptx\nslidev export --format png --range 1-5\nslidev export --with-clicks --dark\nslidev export --timeout 60000 --wait 2000\n```\n\n## Format\n\n```bash\nslidev format [entry]\n```\n\nFormats the slides markdown file.\n\n## Theme Eject\n\n```bash\nslidev theme eject [entry]\n```\n\nOptions:\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--dir` | theme | Output directory |\n| `--theme` | - | Theme to eject |\n\nExtracts theme to local directory for customization.\n\n## npm Script Usage\n\nIn package.json:\n```json\n{\n  \"scripts\": {\n    \"dev\": \"slidev\",\n    \"build\": \"slidev build\",\n    \"export\": \"slidev export\"\n  }\n}\n```\n\nWith arguments (note `--`):\n```bash\nnpm run dev -- --port 8080 --open\nnpm run export -- --format pptx\n```\n\n## Boolean Options\n\n```bash\nslidev --open           # Same as --open true\nslidev --no-open        # Same as --open false\n```\n\n## Install CLI Globally\n\n```bash\nnpm i -g @slidev/cli\n```\n"
  },
  {
    "path": "skills/slidev/references/core-components.md",
    "content": "---\nname: components\ndescription: Ready-to-use components in Slidev\n---\n\n# Built-in Components\n\nReady-to-use components in Slidev.\n\n## Navigation\n\n### Link\n\nNavigate to slide:\n```md\n<Link to=\"5\">Go to slide 5</Link>\n<Link to=\"intro\">Go to intro</Link>  <!-- with routeAlias -->\n```\n\n### SlideCurrentNo / SlidesTotal\n\n```md\nSlide <SlideCurrentNo /> of <SlidesTotal />\n```\n\n### Toc (Table of Contents)\n\n```md\n<Toc />\n<Toc maxDepth=\"2\" />\n<Toc columns=\"2\" />\n```\n\nProps:\n- `columns` - Number of columns\n- `maxDepth` / `minDepth` - Heading depth filter\n- `mode` - 'all' | 'onlyCurrentTree' | 'onlySiblings'\n\n### TitleRenderer\n\nRender slide title:\n```md\n<TitleRenderer no=\"3\" />\n```\n\n## Animations\n\n### VClick / VClicks\n\n```md\n<VClick>Shows on click</VClick>\n\n<VClicks>\n\n- Item 1\n- Item 2\n\n</VClicks>\n```\n\n### VAfter\n\n```md\n<VClick>First</VClick>\n<VAfter>Shows with first</VAfter>\n```\n\n### VSwitch\n\n```md\n<VSwitch>\n  <template #1>State 1</template>\n  <template #2>State 2</template>\n</VSwitch>\n```\n\n## Drawing\n\n### Arrow\n\n```md\n<Arrow x1=\"10\" y1=\"10\" x2=\"100\" y2=\"100\" />\n<Arrow x1=\"10\" y1=\"10\" x2=\"100\" y2=\"100\" two-way />\n```\n\nProps: `x1`, `y1`, `x2`, `y2`, `width`, `color`, `two-way`\n\n### VDragArrow\n\nDraggable arrow:\n```md\n<VDragArrow />\n```\n\n## Layout\n\n### Transform\n\nScale elements:\n```md\n<Transform :scale=\"0.5\">\n  <LargeContent />\n</Transform>\n```\n\nProps: `scale`, `origin`\n\n### AutoFitText\n\nAuto-sizing text:\n```md\n<AutoFitText :max=\"200\" :min=\"50\" modelValue=\"Hello\" />\n```\n\n## Media\n\n### SlidevVideo\n\n```md\n<SlidevVideo v-click autoplay controls>\n  <source src=\"/video.mp4\" type=\"video/mp4\" />\n</SlidevVideo>\n```\n\nProps: `controls`, `autoplay`, `autoreset`, `poster`, `timestamp`\n\n### Youtube\n\n```md\n<Youtube id=\"dQw4w9WgXcQ\" />\n<Youtube id=\"dQw4w9WgXcQ\" width=\"600\" height=\"400\" />\n```\n\n### Tweet\n\n```md\n<Tweet id=\"1423789844234231808\" />\n<Tweet id=\"1423789844234231808\" :scale=\"0.8\" />\n```\n\n## Conditional\n\n### LightOrDark\n\n```md\n<LightOrDark>\n  <template #dark>Dark mode content</template>\n  <template #light>Light mode content</template>\n</LightOrDark>\n```\n\n### RenderWhen\n\n```md\n<RenderWhen context=\"presenter\">\n  Only in presenter mode\n</RenderWhen>\n```\n\nContext values:\n- `main` - Main presentation view\n- `visible` - Visible slides\n- `print` - Print/export mode\n- `slide` - Normal slide view\n- `overview` - Overview mode\n- `presenter` - Presenter mode\n- `previewNext` - Next slide preview\n\n## Branding\n\n### PoweredBySlidev\n\n```md\n<PoweredBySlidev />\n```\n\n## Draggable\n\n### VDrag\n\n```md\n<VDrag pos=\"myElement\">\n  Draggable content\n</VDrag>\n```\n\nSee [draggable](draggable.md) for details.\n\n## Component Auto-Import\n\nComponents from these sources are auto-imported:\n1. Built-in components\n2. Theme components\n3. Addon components\n4. `./components/` directory\n\nNo import statements needed.\n"
  },
  {
    "path": "skills/slidev/references/core-exporting.md",
    "content": "---\nname: exporting\ndescription: Export presentations to PDF, PPTX, PNG, or Markdown\n---\n\n# Exporting Slides\n\nExport presentations to PDF, PPTX, PNG, or Markdown.\n\n## Browser Exporter\n\nAccess at `http://localhost:3030/export`:\n- Select format and options\n- Preview and download\n\n## CLI Export\n\nRequires playwright:\n```bash\npnpm add -D playwright-chromium\n```\n\n### PDF Export\n\n```bash\nslidev export\nslidev export --output my-slides.pdf\n```\n\n### PowerPoint Export\n\n```bash\nslidev export --format pptx\n```\n\n### PNG Export\n\n```bash\nslidev export --format png\nslidev export --format png --range 1-5\n```\n\n### Markdown Export\n\n```bash\nslidev export --format md\n```\n\n## Export Options\n\n### With Click Steps\n\nExport each click as separate page:\n```bash\nslidev export --with-clicks\n```\n\n### Dark Mode\n\n```bash\nslidev export --dark\n```\n\n### Slide Range\n\n```bash\nslidev export --range 1,4-7,10\n```\n\n### Table of Contents\n\nPDF with clickable outline:\n```bash\nslidev export --with-toc\n```\n\n### Timeout\n\nFor slow-rendering slides:\n```bash\nslidev export --timeout 60000\n```\n\n### Wait\n\nWait before capture:\n```bash\nslidev export --wait 2000\n```\n\n### Wait Until\n\nWait condition:\n```bash\nslidev export --wait-until networkidle   # Default\nslidev export --wait-until domcontentloaded\nslidev export --wait-until load\nslidev export --wait-until none\n```\n\n### Transparent Background\n\n```bash\nslidev export --omit-background\n```\n\n### Custom Browser\n\n```bash\nslidev export --executable-path /path/to/chrome\n```\n\n## Headmatter Options\n\n```yaml\n---\nexportFilename: my-presentation\ndownload: true              # Add download button in build\nexport:\n  format: pdf\n  timeout: 30000\n  withClicks: false\n---\n```\n\n## Troubleshooting\n\n### Missing Content\n\nIncrease wait time:\n```bash\nslidev export --wait 3000 --timeout 60000\n```\n\n### Wrong Global Layer State\n\nUse `--per-slide` or use `slide-top.vue` instead of `global-top.vue`.\n\n### Broken Emojis\n\nUse system fonts or install emoji font on server.\n\n### CI/CD Export\n\nInstall playwright browsers:\n```bash\nnpx playwright install chromium\n```\n"
  },
  {
    "path": "skills/slidev/references/core-frontmatter.md",
    "content": "---\nname: frontmatter\ndescription: Configuration options for individual slides\n---\n\n# Per-Slide Frontmatter\n\nConfiguration options for individual slides.\n\n## Layout\n\n```yaml\n---\nlayout: center\n---\n```\n\nAvailable layouts: `default`, `cover`, `center`, `two-cols`, `two-cols-header`, `image`, `image-left`, `image-right`, `iframe`, `iframe-left`, `iframe-right`, `quote`, `section`, `statement`, `fact`, `full`, `intro`, `end`, `none`\n\n## Background\n\n```yaml\n---\nbackground: /image.jpg\nbackgroundSize: cover\nclass: text-white\n---\n```\n\n## Click Count\n\n```yaml\n---\nclicks: 5                   # Total clicks for this slide\nclicksStart: 0              # Starting click number\n---\n```\n\n## Transitions\n\n```yaml\n---\ntransition: fade            # Slide transition\n---\n```\n\nOr different for forward/backward:\n\n```yaml\n---\ntransition: slide-left | slide-right\n---\n```\n\n## Zoom\n\n```yaml\n---\nzoom: 0.8                   # Scale content (0.8 = 80%)\n---\n```\n\n## Hide Slide\n\n```yaml\n---\ndisabled: true              # Hide this slide\n# or\nhide: true\n---\n```\n\n## Table of Contents\n\n```yaml\n---\nhideInToc: true             # Hide from Toc component\nlevel: 2                    # Override heading level\ntitle: Custom Title         # Override slide title\n---\n```\n\n## Import External File\n\n```yaml\n---\nsrc: ./slides/intro.md      # Import markdown file\n---\n```\n\nWith specific slides:\n\n```yaml\n---\nsrc: ./other.md#2,5-7       # Import slides 2, 5, 6, 7\n---\n```\n\n## Route Alias\n\n```yaml\n---\nrouteAlias: intro           # URL: /intro instead of /1\n---\n```\n\n## Preload\n\n```yaml\n---\npreload: false              # Don't mount until entering\n---\n```\n\n## Draggable Positions\n\n```yaml\n---\ndragPos:\n  logo: 100,50,200,100,0    # Left,Top,Width,Height,Rotate\n  arrow: 300,200,50,50,45\n---\n```\n\n## Image Layouts\n\n```yaml\n---\nlayout: image-left\nimage: /photo.jpg\nbackgroundSize: contain\nclass: my-custom-class\n---\n```\n\n## Iframe Layouts\n\n```yaml\n---\nlayout: iframe\nurl: https://example.com\n---\n```\n\n## Two Columns\n\n```yaml\n---\nlayout: two-cols\n---\n\n# Left Side\n\nContent\n\n::right::\n\n# Right Side\n\nContent\n```\n\n## Two Columns with Header\n\n```yaml\n---\nlayout: two-cols-header\n---\n\n# Header\n\n::left::\n\nLeft content\n\n::right::\n\nRight content\n```\n\n## Full Example\n\n```yaml\n---\nlayout: center\nbackground: /bg.jpg\nclass: text-white text-center\ntransition: fade\nclicks: 3\nzoom: 0.9\nhideInToc: false\n---\n\n# Slide Content\n```\n"
  },
  {
    "path": "skills/slidev/references/core-global-context.md",
    "content": "---\nname: global-context\ndescription: Access navigation, slide info, and configuration programmatically\n---\n\n# Global Context & API\n\nAccess navigation, slide info, and configuration programmatically.\n\n## Template Variables\n\nAvailable in slides and components:\n\n```md\nPage {{ $page }} of {{ $nav.total }}\nTitle: {{ $slidev.configs.title }}\n```\n\n### $nav\n\nNavigation state and controls:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `$nav.currentPage` | number | Current page (1-indexed) |\n| `$nav.currentLayout` | string | Current layout name |\n| `$nav.total` | number | Total slides |\n| `$nav.isPresenter` | boolean | In presenter mode |\n| `$nav.next()` | function | Next click/slide |\n| `$nav.prev()` | function | Previous click/slide |\n| `$nav.nextSlide()` | function | Next slide |\n| `$nav.prevSlide()` | function | Previous slide |\n| `$nav.go(n)` | function | Go to slide n |\n\n### $slidev\n\nGlobal context:\n\n| Property | Description |\n|----------|-------------|\n| `$slidev.configs` | Project config (title, etc.) |\n| `$slidev.themeConfigs` | Theme config |\n\n### $frontmatter\n\nCurrent slide frontmatter:\n\n```md\nLayout: {{ $frontmatter.layout }}\n```\n\n### $clicks\n\nCurrent click count on slide.\n\n### $page\n\nCurrent page number (1-indexed).\n\n### $renderContext\n\nCurrent render context:\n- `'slide'` - Normal slide view\n- `'overview'` - Overview mode\n- `'presenter'` - Presenter mode\n- `'previewNext'` - Next slide preview\n\n## Composables\n\nImport from `@slidev/client`:\n\n```ts\nimport {\n  useNav,\n  useDarkMode,\n  useIsSlideActive,\n  useSlideContext,\n  onSlideEnter,\n  onSlideLeave,\n} from '@slidev/client'\n```\n\n### useNav\n\n```ts\nconst nav = useNav()\nnav.next()\nnav.go(5)\nconsole.log(nav.currentPage)\n```\n\n### useDarkMode\n\n```ts\nconst { isDark, toggle } = useDarkMode()\n```\n\n### useIsSlideActive\n\n```ts\nconst isActive = useIsSlideActive()\n// Returns ref<boolean>\n```\n\n### useSlideContext\n\n```ts\nconst { $page, $clicks, $frontmatter } = useSlideContext()\n```\n\n## Lifecycle Hooks\n\n```ts\nimport { onSlideEnter, onSlideLeave } from '@slidev/client'\n\nonSlideEnter(() => {\n  // Slide became active\n  startAnimation()\n})\n\nonSlideLeave(() => {\n  // Slide became inactive\n  cleanup()\n})\n```\n\n**Important:** Don't use `onMounted`/`onUnmounted` in slides - component instance persists. Use `onSlideEnter`/`onSlideLeave` instead.\n\n## Conditional Rendering Examples\n\n```html\n<!-- Show only in presenter mode -->\n<div v-if=\"$nav.isPresenter\">\n  Presenter notes\n</div>\n\n<!-- Hide on cover slide -->\n<footer v-if=\"$nav.currentLayout !== 'cover'\">\n  Page {{ $nav.currentPage }}\n</footer>\n\n<!-- Different content by context -->\n<template v-if=\"$renderContext === 'slide'\">\n  Normal view\n</template>\n<template v-else-if=\"$renderContext === 'presenter'\">\n  Presenter view\n</template>\n```\n\n## Type Imports\n\n```ts\nimport type { TocItem } from '@slidev/types'\n```\n"
  },
  {
    "path": "skills/slidev/references/core-headmatter.md",
    "content": "---\nname: headmatter\ndescription: Deck-wide configuration options in the first frontmatter block\n---\n\n# Headmatter Configuration\n\nDeck-wide configuration options in the first frontmatter block.\n\n## Theme & Appearance\n\n```yaml\n---\ntheme: default              # Theme package or path\ncolorSchema: auto           # 'auto' | 'light' | 'dark'\nfavicon: /favicon.ico       # Favicon URL\naspectRatio: 16/9           # Slide aspect ratio\ncanvasWidth: 980            # Canvas width in px\n---\n```\n\n## Fonts\n\n```yaml\n---\nfonts:\n  sans: Roboto\n  serif: Roboto Slab\n  mono: Fira Code\n  provider: google          # 'google' | 'none'\n---\n```\n\n## Code & Highlighting\n\n```yaml\n---\nhighlighter: shiki          # Code highlighter\nlineNumbers: false          # Show line numbers\nmonaco: true                # Enable Monaco editor ('true' | 'dev' | 'build')\ntwoslash: true              # Enable TwoSlash\nmonacoTypesSource: local    # 'local' | 'cdn' | 'none'\n---\n```\n\n## Features\n\n```yaml\n---\ndrawings:\n  enabled: true             # Enable drawing mode\n  persist: false            # Save drawings\n  presenterOnly: false      # Only presenter can draw\n  syncAll: true             # Sync across instances\nrecord: dev                 # Enable recording\nselectable: true            # Text selection\ncontextMenu: true           # Right-click menu\nwakeLock: true              # Prevent screen sleep\n---\n```\n\n## Export & Build\n\n```yaml\n---\ndownload: false             # PDF download button\nexportFilename: slides      # Export filename\nexport:\n  format: pdf\n  timeout: 30000\n  withClicks: false\n  withToc: false\n---\n```\n\n## Info & SEO\n\n```yaml\n---\ntitle: My Presentation\ntitleTemplate: '%s - Slidev'\nauthor: Your Name\nkeywords: slidev, presentation\ninfo: |\n  ## About\n  Presentation description\n---\n```\n\n## SEO Meta Tags\n\n```yaml\n---\nseoMeta:\n  ogTitle: Presentation Title\n  ogDescription: Description\n  ogImage: https://example.com/og.png\n  ogUrl: https://example.com\n  twitterCard: summary_large_image\n  twitterTitle: Title\n  twitterDescription: Description\n  twitterImage: https://example.com/twitter.png\n---\n```\n\n## Addons & Themes\n\n```yaml\n---\ntheme: seriph\naddons:\n  - excalidraw\n  - '@slidev/plugin-notes'\n---\n```\n\n## Theme Configuration\n\n```yaml\n---\nthemeConfig:\n  primary: '#5d8392'\n  # Theme-specific options\n---\n```\n\n## Defaults\n\nSet default frontmatter for all slides:\n\n```yaml\n---\ndefaults:\n  layout: default\n  transition: fade\n---\n```\n\n## HTML Attributes\n\n```yaml\n---\nhtmlAttrs:\n  dir: ltr\n  lang: en\n---\n```\n\n## Presenter & Browser\n\n```yaml\n---\npresenter: true             # 'true' | 'dev' | 'build'\nbrowserExporter: dev        # 'true' | 'dev' | 'build'\nrouterMode: history         # 'history' | 'hash'\n---\n```\n\n## Remote Assets\n\n```yaml\n---\nremoteAssets: false         # Download remote assets locally\nplantUmlServer: https://www.plantuml.com/plantuml\n---\n```\n\n## Full Template\n\n```yaml\n---\ntheme: default\ntitle: Presentation Title\nauthor: Your Name\nhighlighter: shiki\nlineNumbers: true\ntransition: slide-left\naspectRatio: 16/9\ncanvasWidth: 980\nfonts:\n  sans: Roboto\n  mono: Fira Code\ndrawings:\n  enabled: true\n  persist: true\ndownload: true\n---\n```\n"
  },
  {
    "path": "skills/slidev/references/core-hosting.md",
    "content": "---\nname: hosting\ndescription: Build and deploy Slidev presentations\n---\n\n# Hosting & Deployment\n\nBuild and deploy Slidev presentations.\n\n## Build for Production\n\n```bash\nslidev build\n```\n\nOutput: `dist/` folder (static SPA)\n\n### Options\n\n```bash\nslidev build --base /talks/my-talk/    # Custom base path\nslidev build --out public              # Custom output dir\nslidev build --download                # Include PDF\nslidev build --without-notes           # Exclude notes\n```\n\n### Multiple Presentations\n\n```bash\nslidev build slides1.md slides2.md\n```\n\n## GitHub Pages\n\n### GitHub Actions\n\nCreate `.github/workflows/deploy.yml`:\n\n```yaml\nname: Deploy\n\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n\n      - name: Install\n        run: npm install\n\n      - name: Build\n        run: npm run build -- --base /${{ github.event.repository.name }}/\n\n      - uses: actions/configure-pages@v4\n\n      - uses: actions/upload-pages-artifact@v3\n        with:\n          path: dist\n\n      - uses: actions/deploy-pages@v4\n```\n\n## Netlify\n\nCreate `netlify.toml`:\n\n```toml\n[build]\n  publish = 'dist'\n  command = 'npm run build'\n\n[[redirects]]\n  from = '/*'\n  to = '/index.html'\n  status = 200\n```\n\n## Vercel\n\nCreate `vercel.json`:\n\n```json\n{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/index.html\" }\n  ]\n}\n```\n\n## Docker\n\n### Using Official Image\n\n```bash\ndocker run --name slidev --rm -it \\\n  -v ${PWD}:/slidev \\\n  -p 3030:3030 \\\n  tangramor/slidev:latest\n```\n\n### Custom Dockerfile\n\n```dockerfile\nFROM tangramor/slidev:latest\n\nCOPY slides.md .\nCOPY public ./public\n\nRUN npm run build\n\nEXPOSE 80\nCMD [\"npx\", \"serve\", \"dist\"]\n```\n\n## Base Path\n\nFor subdirectory deployment:\n\n```bash\n# Build\nslidev build --base /my-slides/\n\n# Or in headmatter\n---\nbase: /my-slides/\n---\n```\n\n## Router Mode\n\nFor servers without rewrite support:\n\n```yaml\n---\nrouterMode: hash\n---\n```\n\nURLs become: `/#/1`, `/#/2`, etc.\n"
  },
  {
    "path": "skills/slidev/references/core-layouts.md",
    "content": "---\nname: layouts\ndescription: Available layouts for slides\n---\n\n# Built-in Layouts\n\nAvailable layouts for slides.\n\n## Basic Layouts\n\n### default\n\nStandard slide layout.\n```yaml\n---\nlayout: default\n---\n```\n\n### center\n\nContent centered horizontally and vertically.\n```yaml\n---\nlayout: center\n---\n```\n\n### cover\n\nTitle/cover slide with centered content.\n```yaml\n---\nlayout: cover\n---\n```\n\n### end\n\nEnd slide.\n```yaml\n---\nlayout: end\n---\n```\n\n### full\n\nFull-screen content, no padding.\n```yaml\n---\nlayout: full\n---\n```\n\n### none\n\nNo layout styling.\n```yaml\n---\nlayout: none\n---\n```\n\n## Text Layouts\n\n### intro\n\nIntroduction slide.\n```yaml\n---\nlayout: intro\n---\n```\n\n### quote\n\nLarge quotation display.\n```yaml\n---\nlayout: quote\n---\n```\n\n### section\n\nSection divider.\n```yaml\n---\nlayout: section\n---\n```\n\n### statement\n\nStatement/affirmation display.\n```yaml\n---\nlayout: statement\n---\n```\n\n### fact\n\nFact/data display.\n```yaml\n---\nlayout: fact\n---\n```\n\n## Multi-Column Layouts\n\n### two-cols\n\nTwo columns side by side:\n```md\n---\nlayout: two-cols\n---\n\n# Left Column\n\nLeft content\n\n::right::\n\n# Right Column\n\nRight content\n```\n\n### two-cols-header\n\nHeader with two columns below:\n```md\n---\nlayout: two-cols-header\n---\n\n# Header\n\n::left::\n\nLeft content\n\n::right::\n\nRight content\n```\n\n## Image Layouts\n\n### image\n\nFull-screen image:\n```yaml\n---\nlayout: image\nimage: /photo.jpg\nbackgroundSize: cover\n---\n```\n\n### image-left\n\nImage on left, content on right:\n```yaml\n---\nlayout: image-left\nimage: /photo.jpg\nclass: my-class\n---\n\n# Content on Right\n```\n\n### image-right\n\nImage on right, content on left:\n```yaml\n---\nlayout: image-right\nimage: /photo.jpg\n---\n\n# Content on Left\n```\n\nProps: `image`, `class`, `backgroundSize`\n\n## Iframe Layouts\n\n### iframe\n\nFull-screen iframe:\n```yaml\n---\nlayout: iframe\nurl: https://example.com\n---\n```\n\n### iframe-left\n\nIframe on left, content on right:\n```yaml\n---\nlayout: iframe-left\nurl: https://example.com\n---\n\n# Content\n```\n\n### iframe-right\n\nIframe on right, content on left:\n```yaml\n---\nlayout: iframe-right\nurl: https://example.com\n---\n\n# Content\n```\n\n## Layout Loading Order\n\n1. Slidev default layouts\n2. Theme layouts\n3. Addon layouts\n4. Custom layouts (`./layouts/`)\n\nLater sources override earlier ones.\n\n## Custom Layouts\n\nCreate `layouts/my-layout.vue`:\n\n```vue\n<template>\n  <div class=\"slidev-layout my-layout\">\n    <slot />\n  </div>\n</template>\n\n<style scoped>\n.my-layout {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n</style>\n```\n\nWith named slots:\n\n```vue\n<template>\n  <div class=\"slidev-layout two-areas\">\n    <div class=\"top\">\n      <slot name=\"top\" />\n    </div>\n    <div class=\"bottom\">\n      <slot />\n    </div>\n  </div>\n</template>\n```\n\nUsage:\n```md\n---\nlayout: two-areas\n---\n\n::top::\n\nTop content\n\n::default::\n\nBottom content\n```\n"
  },
  {
    "path": "skills/slidev/references/core-syntax.md",
    "content": "---\nname: syntax\ndescription: Core Markdown syntax for Slidev presentations\n---\n\n# Slidev Markdown Syntax\n\nCore Markdown syntax for Slidev presentations.\n\n## Slide Separator\n\nUse `---` with blank lines before and after:\n\n```md\n# Slide 1\n\nContent\n\n---\n\n# Slide 2\n\nMore content\n```\n\n## Headmatter (Deck Config)\n\nFirst frontmatter block configures the entire deck:\n\n```md\n---\ntheme: default\ntitle: My Presentation\nlineNumbers: true\n---\n\n# First Slide\n```\n\n## Per-Slide Frontmatter\n\nEach slide can have its own frontmatter:\n\n```md\n---\nlayout: center\nbackground: /image.jpg\nclass: text-white\n---\n\n# Centered Slide\n```\n\n## Presenter Notes\n\nHTML comments at end of slide become presenter notes:\n\n```md\n# My Slide\n\nContent here\n\n<!--\nThese are presenter notes.\n- Remember to mention X\n- Demo the feature\n-->\n```\n\n## Code Blocks\n\nStandard Markdown with Shiki highlighting:\n\n````md\n```ts\nconst hello = 'world'\n```\n````\n\nWith features:\n````md\n```ts {2,3}              // Line highlighting\n```ts {1|2-3|all}        // Click-based highlighting\n```ts {monaco}           // Monaco editor\n```ts {monaco-run}       // Runnable code\n```ts twoslash           // TypeScript types\n```\n````\n\n## LaTeX Math\n\nInline: `$E = mc^2$`\n\nBlock:\n```md\n$$\n\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}\n$$\n```\n\n## Diagrams\n\nMermaid:\n````md\n```mermaid\ngraph LR\n  A --> B --> C\n```\n````\n\nPlantUML:\n````md\n```plantuml\n@startuml\nAlice -> Bob : Hello\n@enduml\n```\n````\n\n## Comark Syntax\n\nEnable with `comark: true`:\n\n```md\n[styled text]{style=\"color:red\"}\n![](/image.png){width=500px}\n::component{prop=\"value\"}\n```\n\n## Scoped CSS\n\nStyles apply only to current slide:\n\n```md\n# Red Title\n\n<style>\nh1 { color: red; }\n</style>\n```\n\n## Import Slides\n\n```md\n---\nsrc: ./pages/intro.md\n---\n```\n\nImport specific slides:\n```md\n---\nsrc: ./other.md#2,5-7\n---\n```\n"
  },
  {
    "path": "skills/slidev/references/diagram-latex.md",
    "content": "---\nname: latex\ndescription: Render mathematical equations using KaTeX\n---\n\n# LaTeX\n\nRender mathematical equations. Powered by KaTeX.\n\n## Inline Math\n\n```md\n$\\sqrt{3x-1}+(1+x)^2$\n```\n\n## Block Math\n\n```md\n$$\n\\begin{aligned}\n\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\varepsilon_0} \\\\\n\\nabla \\cdot \\vec{B} &= 0\n\\end{aligned}\n$$\n```\n\n## Line Highlighting\n\n```md\n$$ {1|3|all}\n\\begin{aligned}\n\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\varepsilon_0} \\\\\n\\nabla \\cdot \\vec{B} &= 0 \\\\\n\\nabla \\times \\vec{E} &= -\\frac{\\partial\\vec{B}}{\\partial t}\n\\end{aligned}\n$$\n```\n\n## Chemical Equations\n\nEnable mhchem extension in `vite.config.ts`:\n\n```ts\nimport 'katex/contrib/mhchem'\n\nexport default {}\n```\n\nThen use:\n\n```md\n$$\n\\ce{B(OH)3 + H2O <--> B(OH)4^- + H+}\n$$\n```\n"
  },
  {
    "path": "skills/slidev/references/diagram-mermaid.md",
    "content": "---\nname: mermaid\ndescription: Create diagrams from text descriptions\n---\n\n# Mermaid Diagrams\n\nCreate diagrams from text descriptions.\n\n## Basic Usage\n\n````md\n```mermaid\nsequenceDiagram\n  Alice->John: Hello John, how are you?\n  Note over Alice,John: A typical interaction\n```\n````\n\n## With Options\n\n````md\n```mermaid {theme: 'neutral', scale: 0.8}\ngraph TD\nB[Text] --> C{Decision}\nC -->|One| D[Result 1]\nC -->|Two| E[Result 2]\n```\n````\n\n## Diagram Types\n\n- `graph` / `flowchart` - Flow diagrams\n- `sequenceDiagram` - Sequence diagrams\n- `classDiagram` - Class diagrams\n- `stateDiagram` - State diagrams\n- `erDiagram` - Entity relationship\n- `gantt` - Gantt charts\n- `pie` - Pie charts\n\n## Resources\n\n- Mermaid docs: https://mermaid.js.org/\n- Live editor: https://mermaid.live/\n"
  },
  {
    "path": "skills/slidev/references/diagram-plantuml.md",
    "content": "---\nname: plantuml\ndescription: Create UML diagrams from text descriptions\n---\n\n# PlantUML Diagrams\n\nCreate UML diagrams from text descriptions.\n\n## Basic Usage\n\n````md\n```plantuml\n@startuml\nAlice -> Bob : Hello!\n@enduml\n```\n````\n\n## Server Configuration\n\nDefault: Uses https://www.plantuml.com/plantuml\n\nCustom server in headmatter:\n\n```md\n---\nplantUmlServer: https://your-server.com/plantuml\n---\n```\n\n## Diagram Types\n\n- Sequence diagrams\n- Class diagrams\n- Activity diagrams\n- Component diagrams\n- State diagrams\n- Object diagrams\n- Use case diagrams\n\n## Resources\n\n- PlantUML docs: https://plantuml.com/\n- Live editor: https://plantuml.com/plantuml\n"
  },
  {
    "path": "skills/slidev/references/editor-monaco-run.md",
    "content": "---\nname: monaco-run\ndescription: Run code directly in the editor and see results\n---\n\n# Monaco Runner\n\nRun code directly in the editor and see results.\n\n## Basic Usage\n\n````md\n```ts {monaco-run}\nfunction distance(x: number, y: number) {\n  return Math.sqrt(x ** 2 + y ** 2)\n}\nconsole.log(distance(3, 4))\n```\n````\n\nShows a \"Run\" button and displays output below the code.\n\n## Disable Auto-run\n\n````md\n```ts {monaco-run} {autorun:false}\nconsole.log('Click the play button to run me')\n```\n````\n\n## Show Output on Click\n\n````md\n```ts {monaco-run} {showOutputAt:'+1'}\nconsole.log('Shown after 1 click')\n```\n````\n\n## Supported Languages\n\n- JavaScript\n- TypeScript\n\nFor other languages, configure custom code runners in `/custom/config-code-runners`.\n"
  },
  {
    "path": "skills/slidev/references/editor-monaco-write.md",
    "content": "---\nname: monaco-write\ndescription: Edit code and save changes back to the file\n---\n\n# Writable Monaco Editor\n\nEdit code and save changes back to the file.\n\n## Usage\n\n```md\n<<< ./some-file.ts {monaco-write}\n```\n\n## Behavior\n\n- Links Monaco editor to actual file on filesystem\n- Changes are saved directly to the file\n- Useful for live coding demonstrations\n\n## Warning\n\nBack up files before using - changes are saved directly.\n"
  },
  {
    "path": "skills/slidev/references/editor-monaco.md",
    "content": "---\nname: monaco\ndescription: Turn code blocks into fully-featured editors\n---\n\n# Monaco Editor\n\nTurn code blocks into fully-featured editors.\n\n## Basic Usage\n\n````md\n```ts {monaco}\nconsole.log('HelloWorld')\n```\n````\n\n## Diff Editor\n\nCompare two code versions:\n\n````md\n```ts {monaco-diff}\nconsole.log('Original text')\n~~~\nconsole.log('Modified text')\n```\n````\n\n## Editor Height\n\nAuto-grow as you type:\n\n````md\n```ts {monaco} {height:'auto'}\nconsole.log('Hello, World!')\n```\n````\n\nFixed height:\n\n````md\n```ts {monaco} {height:'300px'}\n// code here\n```\n````\n\n## Configuration\n\nSee `/custom/config-monaco` for Monaco editor customization options.\n"
  },
  {
    "path": "skills/slidev/references/editor-prettier.md",
    "content": "---\nname: prettier-plugin\ndescription: Format Slidev markdown files correctly\n---\n\n# Prettier Plugin\n\nFormat Slidev markdown files correctly.\n\n## Installation\n\n```bash\npnpm i -D prettier prettier-plugin-slidev\n```\n\n## Configuration\n\nCreate/modify `.prettierrc`:\n\n```json\n{\n  \"overrides\": [\n    {\n      \"files\": [\"slides.md\", \"pages/*.md\"],\n      \"options\": {\n        \"parser\": \"slidev\",\n        \"plugins\": [\"prettier-plugin-slidev\"]\n      }\n    }\n  ]\n}\n```\n\n## Why Needed\n\nSlidev's syntax (frontmatter, code blocks) may conflict with default Markdown formatting. This plugin understands Slidev-specific syntax.\n\n## Note\n\nMust specify files via `overrides` since Slidev and regular Markdown share `.md` extension.\n"
  },
  {
    "path": "skills/slidev/references/editor-side.md",
    "content": "---\nname: side-editor\ndescription: Edit slides source alongside the presentation\n---\n\n# Integrated Editor\n\nEdit slides source alongside the presentation.\n\n## Open Editor\n\nClick the edit icon in the navigation panel.\n\n## Features\n\n- Live reload on changes\n- Auto-save to file\n- Side-by-side editing\n- Syntax highlighting\n\n## Use Case\n\nMake quick edits during presentation preparation without switching applications.\n"
  },
  {
    "path": "skills/slidev/references/editor-vscode.md",
    "content": "---\nname: vscode-extension\ndescription: Manage slides visually in VS Code\n---\n\n# VS Code Extension\n\nManage slides visually in VS Code.\n\n## Installation\n\nInstall from VS Code Marketplace: `antfu.slidev`\n\n## Features\n\n- Preview slides in side panel\n- Slides tree view\n- Drag and drop to reorder slides\n- Folding for slide blocks\n- Multiple project support\n- One-click dev server start\n\n## Usage\n\n1. Click `Slidev` icon in activity bar\n2. Projects tree shows all Slidev projects in workspace\n3. Slides tree shows slides in active project\n4. Preview panel shows live preview\n\n## Commands\n\nType `Slidev` in command palette to see available commands.\n\n## Configuration\n\nInclude specific files as Slidev entries:\n\n```json\n{\n  \"slidev.include\": [\"**/presentation.md\"]\n}\n```\n\nCustom dev command:\n\n```json\n{\n  \"slidev.dev-command\": \"pnpm slidev ${args}\"\n}\n```\n\n## Placeholders\n\n- `${args}` - All CLI arguments\n- `${port}` - Port number\n"
  },
  {
    "path": "skills/slidev/references/layout-canvas-size.md",
    "content": "---\nname: canvas-size\ndescription: Configure slide canvas dimensions and aspect ratio\n---\n\n# Slide Canvas Size\n\nSet the canvas dimensions for all slides.\n\n## Configuration\n\n```md\n---\naspectRatio: 16/9\ncanvasWidth: 980\n---\n```\n\n- `aspectRatio`: Ratio of width to height (default: `16/9`)\n- `canvasWidth`: Canvas width in pixels (default: `980`)\n\n## Related Features\n\n- Scale individual slides: use `zoom` frontmatter option\n- Scale elements: use `<Transform>` component\n"
  },
  {
    "path": "skills/slidev/references/layout-draggable.md",
    "content": "---\nname: draggable-elements\ndescription: Move, resize, and rotate elements by dragging during presentation\n---\n\n# Draggable Elements\n\nMove, resize, and rotate elements by dragging during presentation.\n\n## Directive Usage\n\n### With Frontmatter Position\n\n```md\n---\ndragPos:\n  square: Left,Top,Width,Height,Rotate\n---\n\n<img v-drag=\"'square'\" src=\"https://sli.dev/logo.png\">\n```\n\n### Inline Position\n\n```md\n<img v-drag=\"[Left,Top,Width,Height,Rotate]\" src=\"https://sli.dev/logo.png\">\n```\n\n## Component Usage\n\n```md\n---\ndragPos:\n  foo: Left,Top,Width,Height,Rotate\n---\n\n<v-drag pos=\"foo\" text-3xl>\n  Draggable content\n</v-drag>\n```\n\n## Draggable Arrow\n\n```md\n<v-drag-arrow />\n```\n\n## Controls\n\n- Double-click: Start dragging\n- Arrow keys: Move element\n- Shift + drag: Preserve aspect ratio\n- Click outside: Stop dragging\n\n## Auto Height\n\nSet Height to `NaN` or `_` for auto height based on content.\n"
  },
  {
    "path": "skills/slidev/references/layout-global-layers.md",
    "content": "---\nname: global-layers\ndescription: Create components that persist across slides like footers and backgrounds\n---\n\n# Global Layers\n\nCreate components that persist across slides.\n\n## Layer Files\n\nCreate in project root:\n- `global-top.vue` - Above all slides (single instance)\n- `global-bottom.vue` - Below all slides (single instance)\n- `slide-top.vue` - Above each slide (per-slide instance)\n- `slide-bottom.vue` - Below each slide (per-slide instance)\n- `custom-nav-controls.vue` - Custom navigation controls\n\n## Z-Order (top to bottom)\n\n1. NavControls / custom-nav-controls.vue\n2. global-top.vue\n3. slide-top.vue\n4. Slide Content\n5. slide-bottom.vue\n6. global-bottom.vue\n\n## Example: Footer\n\n```html\n<!-- global-bottom.vue -->\n<template>\n  <footer class=\"absolute bottom-0 left-0 right-0 p-2\">Your Name</footer>\n</template>\n```\n\n## Conditional Rendering\n\n```html\n<!-- Hide on cover layout -->\n<template>\n  <footer v-if=\"$nav.currentLayout !== 'cover'\" class=\"absolute bottom-0 p-2\">\n    {{ $nav.currentPage }} / {{ $nav.total }}\n  </footer>\n</template>\n```\n\n## Export Note\n\nUse `--per-slide` export option when global layers depend on navigation state.\n"
  },
  {
    "path": "skills/slidev/references/layout-slots.md",
    "content": "---\nname: slot-sugar\ndescription: Shorthand syntax for layout named slots in multi-column layouts\n---\n\n# Slot Sugar for Layouts\n\nShorthand syntax for layout named slots.\n\n## Standard Vue Slot Syntax\n\n```md\n---\nlayout: two-cols\n---\n\n<template v-slot:default>\n\n# Left\n\nThis shows on the left\n\n</template>\n<template v-slot:right>\n\n# Right\n\nThis shows on the right\n\n</template>\n```\n\n## Shorthand Syntax\n\n```md\n---\nlayout: two-cols\n---\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right\n```\n\n## Explicit Default Slot\n\n```md\n---\nlayout: two-cols\n---\n\n::right::\n\n# Right\n\nThis shows on the right\n\n::default::\n\n# Left\n\nThis shows on the left\n```\n\n## Common Layouts with Slots\n\n- `two-cols`: `default` (left) and `right`\n- `two-cols-header`: `default`, `left`, `right`\n- `image-left/right`: `default` for content\n"
  },
  {
    "path": "skills/slidev/references/layout-transform.md",
    "content": "---\nname: transform-component\ndescription: Scale elements without affecting slide layout using the Transform component\n---\n\n# Transform Component\n\nScale elements without affecting slide layout.\n\n## Usage\n\n```md\n<Transform :scale=\"0.5\" origin=\"top center\">\n  <YourElements />\n</Transform>\n```\n\n## Props\n\n- `scale`: Scale factor (0.5 = 50%, 2 = 200%)\n- `origin`: Transform origin (CSS transform-origin value)\n\n## Use Cases\n\n- Shrink large diagrams\n- Scale code blocks\n- Fit oversized content\n- Create emphasis effects\n\n## Related Features\n\n- Scale all slides: Use `canvasWidth` / `aspectRatio` in headmatter\n- Scale individual slides: Use `zoom` frontmatter option\n"
  },
  {
    "path": "skills/slidev/references/layout-zoom.md",
    "content": "---\nname: zoom-slides\ndescription: Scale individual slide content using the zoom frontmatter option\n---\n\n# Zoom Slides\n\nScale individual slide content.\n\n## Usage\n\n```md\n---\nzoom: 0.8\n---\n\n# A Slide with lots of content\n\n---\n\n# Other slides aren't affected\n```\n\n## Values\n\n- `zoom: 0.8` - 80% size (fits more content)\n- `zoom: 1.2` - 120% size (larger, less content)\n- `zoom: 1` - Normal (default)\n\n## Use Cases\n\n- Fit dense content on one slide\n- Make text more readable\n- Adjust for different content densities\n\n## Related Features\n\n- Scale all slides: Use `canvasWidth` / `aspectRatio` in headmatter\n- Scale elements: Use `<Transform>` component\n"
  },
  {
    "path": "skills/slidev/references/presenter-notes-ruby.md",
    "content": "---\nname: notes-auto-ruby\ndescription: Automatically add ruby text (pronunciation guides) to presenter notes\n---\n\n# Notes Auto Ruby\n\nAutomatically add ruby text (pronunciation guides) to presenter notes.\n\n## Configuration\n\n```md\n---\nnotesAutoRuby:\n  日本語: ni hon go\n  勉強: べんきょう\n---\n```\n\n## Example\n\nNotes:\n```md\n<!--\n私は日本語を勉強しています。\n-->\n```\n\nRenders with ruby annotations above the matched words.\n\n## Use Case\n\n- Language learning presentations\n- Pronunciation guides for technical terms\n- Reading assistance for non-native text\n"
  },
  {
    "path": "skills/slidev/references/presenter-recording.md",
    "content": "---\nname: recording\ndescription: Record presentations with camera and screen capture\n---\n\n# Recording\n\nRecord presentations with built-in camera and screen capture.\n\n## Camera View\n\nClick the camera icon in navigation bar to show camera overlay.\n\n- Drag to move\n- Resize from bottom-right corner\n- Position persists across reloads\n\n## Start Recording\n\nClick the video icon in navigation bar.\n\nOptions:\n- Camera embedded in slides\n- Separate video files for camera and slides\n\nSaves as WebM video.\n\n## Technology\n\nPowered by RecordRTC and WebRTC API.\n"
  },
  {
    "path": "skills/slidev/references/presenter-remote.md",
    "content": "---\nname: remote-access\ndescription: Share presentation across network or internet\n---\n\n# Remote Access\n\nShare presentation across network or internet.\n\n## Enable Remote Access\n\n```bash\nslidev --remote\n```\n\n## Password Protection\n\n```bash\nslidev --remote=your_password\n```\n\nPassword required for presenter mode access.\n\n## Remote Tunnel\n\nExpose to internet via Cloudflare Quick Tunnels:\n\n```bash\nslidev --remote --tunnel\n```\n\nCreates public URL for sharing without server setup.\n\n## Use Cases\n\n- Control presentation from phone/tablet\n- Multiple presenters\n- Remote presentations\n- Live streaming\n- Audience viewing on their devices\n"
  },
  {
    "path": "skills/slidev/references/presenter-timer.md",
    "content": "---\nname: presenter-timer\ndescription: Timer and progress bar in presenter mode\n---\n\n# Presenter Timer\n\nTimer and progress bar in presenter mode.\n\n## Configuration\n\n```yaml\n---\nduration: 30min\ntimer: stopwatch\n---\n```\n\n## Options\n\n- `duration`: Presentation length (default: `30min`)\n- `timer`: Mode - `stopwatch` or `countdown` (default: `stopwatch`)\n\n## Features\n\n- Start, pause, reset controls\n- Progress bar showing time elapsed/remaining\n- Visible only in presenter mode\n\n## Duration Format\n\n- `30min` - 30 minutes\n- `1h` - 1 hour\n- `45min` - 45 minutes\n"
  },
  {
    "path": "skills/slidev/references/style-direction.md",
    "content": "---\nname: direction\ndescription: Navigation direction-based styling\n---\n\n# Navigation Direction Variants\n\nApply different styles based on navigation direction (forward/backward).\n\n## CSS Classes\n\n```css\n/* Delay only when navigating forward */\n.slidev-nav-go-forward .slidev-vclick-target {\n  transition-delay: 500ms;\n}\n.slidev-nav-go-backward .slidev-vclick-target {\n  transition-delay: 0;\n}\n```\n\n## UnoCSS Variants\n\nUse `forward:` or `backward:` prefix:\n\n```html\n<div v-click class=\"transition forward:delay-300\">Element</div>\n```\n\nAnimation is only delayed when navigating forward, not when going back.\n\n## Use Case\n\nCreate asymmetric animations where entering a slide feels different from leaving it.\n"
  },
  {
    "path": "skills/slidev/references/style-icons.md",
    "content": "---\nname: icons\ndescription: Using open-source icons in slides\n---\n\n# Icons\n\nUse any open-source icon directly in markdown. Powered by unplugin-icons and Iconify.\n\n## Installation\n\n```bash\npnpm add @iconify-json/[collection-name]\n```\n\n## Usage\n\nUse component syntax `<collection-icon-name />`:\n\n```md\n<mdi-account-circle />\n<carbon-badge />\n<uim-rocket />\n<logos-vue />\n```\n\n## Popular Collections\n\n- `@iconify-json/mdi` - Material Design Icons\n- `@iconify-json/carbon` - Carbon Design\n- `@iconify-json/logos` - SVG Logos\n- `@iconify-json/twemoji` - Twitter Emoji\n\n## Styling\n\nStyle like any HTML element:\n\n```html\n<uim-rocket class=\"text-3xl text-red-400 mx-2\" />\n<uim-rocket class=\"text-3xl text-orange-400 animate-ping\" />\n```\n\n## Browse Icons\n\n- https://icones.js.org/\n- https://icon-sets.iconify.design/\n"
  },
  {
    "path": "skills/slidev/references/style-scoped.md",
    "content": "---\nname: scoped\ndescription: Slide-scoped CSS styles\n---\n\n# Slide Scope Styles\n\nDefine CSS that applies only to the current slide.\n\n## Usage\n\n```md\n# This is Red\n\n<style>\nh1 {\n  color: red;\n}\n</style>\n\n---\n\n# Other slides are not affected\n```\n\n## Scoped by Default\n\nAll `<style>` tags in slides are automatically scoped.\n\nChild combinators (`.a > .b`) don't work as expected due to scoping.\n\n## Nested CSS with UnoCSS\n\n```md\n# Slidev\n\n> Hello **world**\n\n<style>\nblockquote {\n  strong {\n    --uno: 'text-teal-500 dark:text-teal-400';\n  }\n}\n</style>\n```\n\n## Global Styles\n\nFor global styles, use `styles/index.css` in your project.\n"
  },
  {
    "path": "skills/slidev/references/syntax-block-frontmatter.md",
    "content": "---\nname: block-frontmatter\ndescription: Using YAML code blocks as slide frontmatter for syntax highlighting\n---\n\n# Block Frontmatter\n\nUse a YAML code block as the frontmatter for slides when you need syntax highlighting and formatter support.\n\n## Usage\n\nInstead of traditional frontmatter `---`, use a yaml code block at the start of a slide:\n\n````md\n---\ntheme: default\n---\n\n# Slide 1\n\n---\n\n```yaml\nlayout: quote\n```\n\n# Slide 2\n\n---\n\n# Slide 3\n````\n\n## Key Points\n\n- Works for per-slide frontmatter only\n- Cannot use for headmatter (first frontmatter of the deck)\n- Provides syntax highlighting in editors\n- Compatible with prettier-plugin-slidev\n"
  },
  {
    "path": "skills/slidev/references/syntax-frontmatter-merging.md",
    "content": "---\nname: frontmatter-merging\ndescription: Priority rules when importing slides with conflicting frontmatter\n---\n\n# Frontmatter Merging\n\nWhen importing slides, frontmatter from main entry takes priority.\n\n## Example\n\nMain file (`slides.md`):\n```md\n---\nsrc: ./cover.md\nbackground: https://sli.dev/bar.png\nclass: text-center\n---\n```\n\nImported file (`cover.md`):\n```md\n---\nlayout: cover\nbackground: https://sli.dev/foo.png\n---\n\n# Cover\n\nCover Page\n```\n\n## Result\n\n```md\n---\nlayout: cover\nbackground: https://sli.dev/bar.png  # main entry wins\nclass: text-center\n---\n\n# Cover\n\nCover Page\n```\n\n## Priority Rule\n\nMain entry > Imported file for duplicate keys.\n"
  },
  {
    "path": "skills/slidev/references/syntax-importing-slides.md",
    "content": "---\nname: importing-slides\ndescription: Split presentations into multiple files for reusability\n---\n\n# Importing Slides\n\nSplit presentations into multiple files for reusability.\n\n## Basic Import\n\n```md\n# Title\n\nThis is a normal page\n\n---\nsrc: ./pages/toc.md\n---\n\n<!-- Content here is ignored -->\n\n---\n\n# Page 4\n\nAnother normal page\n```\n\n## Import Specific Slides\n\nUse hash to select slides:\n\n```md\n---\nsrc: ./another-presentation.md#2,5-7\n---\n```\n\nImports slides 2, 5, 6, and 7.\n\n## Reuse Slides\n\nImport the same file multiple times:\n\n```md\n---\nsrc: ./pages/toc.md\n---\n\n<!-- later... -->\n\n---\nsrc: ./pages/toc.md\n---\n```\n\n## Frontmatter Priority\n\nMain entry frontmatter overrides imported file frontmatter for duplicate keys.\n"
  },
  {
    "path": "skills/slidev/references/syntax-mdc.md",
    "content": "---\nname: comark\ndescription: Comark Syntax support\n---\n\n# Comark Syntax\n\nEnhanced Markdown with component and style syntax.\n\n## Enable\n\n```md\n---\ncomark: true\n---\n```\n\n## Inline Styles\n\n```md\nThis is a [red text]{style=\"color:red\"}\n```\n\n## Inline Components\n\n```md\n:inline-component{prop=\"value\"}\n```\n\n## Image Attributes\n\n```md\n![](/image.png){width=500px lazy}\n```\n\n## Block Components\n\n```md\n::block-component{prop=\"value\"}\nThe **default** slot content\n::\n```\n\n## Use Cases\n\n- Add inline styles without HTML\n- Use Vue components inline\n- Add attributes to images\n- Create complex component layouts\n\nBased on Comark Syntax.\n"
  },
  {
    "path": "skills/slidev/references/tool-eject-theme.md",
    "content": "---\nname: eject-theme\ndescription: Extract theme to local filesystem for customization\n---\n\n# Eject Theme\n\nExtract installed theme to local filesystem for customization.\n\n## Command\n\n```bash\nslidev theme eject\n```\n\n## Result\n\n- Theme files copied to `./theme/`\n- Frontmatter updated to `theme: ./theme`\n\n## Use Case\n\n- Full control over theme\n- Create new theme based on existing one\n- Customize without modifying node_modules\n\nIf creating a derivative theme, credit the original theme and author.\n"
  },
  {
    "path": "taze.config.ts",
    "content": "import { defineConfig } from 'taze'\n\nexport default defineConfig({\n  packageMode: {\n    // See #1537\n    'typeit': 'ignore',\n    // `engines.vscode` must be updated when bumping `@types/vscode` version\n    '@types/vscode': 'ignore',\n    // Prevent multiple versions. `volar-service-yaml` is using v2.\n    'prettier': 'patch',\n  },\n})\n"
  },
  {
    "path": "test/__snapshots__/parser.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`md parser > frontmatter.md > config 1`] = `\n{\n  \"drawings\": {\n    \"enabled\": true,\n    \"persist\": false,\n    \"presenterOnly\": false,\n    \"syncAll\": true,\n  },\n  \"fonts\": {\n    \"italic\": false,\n    \"local\": [],\n    \"mono\": [\n      \"\"Fira Code\"\",\n      \"ui-monospace\",\n      \"SFMono-Regular\",\n      \"Menlo\",\n      \"Monaco\",\n      \"Consolas\",\n      \"\"Liberation Mono\"\",\n      \"\"Courier New\"\",\n      \"monospace\",\n    ],\n    \"provider\": \"google\",\n    \"sans\": [\n      \"\"Roboto\"\",\n      \"\"Lato\"\",\n      \"ui-sans-serif\",\n      \"system-ui\",\n      \"-apple-system\",\n      \"BlinkMacSystemFont\",\n      \"\"Segoe UI\"\",\n      \"Roboto\",\n      \"\"Helvetica Neue\"\",\n      \"Arial\",\n      \"\"Noto Sans\"\",\n      \"sans-serif\",\n      \"\"Apple Color Emoji\"\",\n      \"\"Segoe UI Emoji\"\",\n      \"\"Segoe UI Symbol\"\",\n      \"\"Noto Color Emoji\"\",\n    ],\n    \"serif\": [\n      \"\"Mate SC\"\",\n      \"ui-serif\",\n      \"Georgia\",\n      \"Cambria\",\n      \"\"Times New Roman\"\",\n      \"Times\",\n      \"serif\",\n    ],\n    \"webfonts\": [\n      \"Roboto\",\n      \"Lato\",\n      \"Mate SC\",\n      \"Fira Code\",\n    ],\n    \"weights\": [\n      \"200\",\n      \"400\",\n      \"600\",\n    ],\n  },\n  \"layout\": \"cover\",\n  \"title\": \"Hi\",\n}\n`;\n\nexports[`md parser > frontmatter.md > features 1`] = `\n{\n  \"katex\": false,\n  \"mermaid\": false,\n  \"monaco\": false,\n  \"tweet\": false,\n}\n`;\n\nexports[`md parser > frontmatter.md > slides 1`] = `\n[\n  {\n    \"content\": \"# Hi\",\n    \"frontmatter\": {\n      \"fonts\": {\n        \"mono\": \"Fira Code\",\n        \"sans\": \"Roboto, Lato\",\n        \"serif\": \"Mate SC\",\n      },\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\nfonts:\n  sans: Roboto, Lato\n  serif: Mate SC\n  mono: Fira Code\n\",\n    \"importChain\": undefined,\n    \"index\": 0,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"243b9h\",\n    \"source\": {\n      \"content\": \"\n# Hi\n\",\n      \"contentRaw\": \"# Hi\",\n      \"contentStart\": 7,\n      \"end\": 10,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {\n        \"fonts\": {\n          \"mono\": \"Fira Code\",\n          \"sans\": \"Roboto, Lato\",\n          \"serif\": \"Mate SC\",\n        },\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"fonts\": {\n          \"mono\": \"Fira Code\",\n          \"sans\": \"Roboto, Lato\",\n          \"serif\": \"Mate SC\",\n        },\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\nfonts:\n  sans: Roboto, Lato\n  serif: Mate SC\n  mono: Fira Code\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\nfonts:\n  sans: Roboto, Lato\n  serif: Mate SC\n  mono: Fira Code\n---\n\n# Hi\n\",\n      \"revision\": \"243b9h\",\n      \"start\": 0,\n      \"title\": \"Hi\",\n    },\n    \"title\": \"Hi\",\n  },\n  {\n    \"content\": \"# Hello\",\n    \"frontmatter\": {\n      \"layout\": \"center\",\n      \"meta\": {\n        \"duration\": 12,\n        \"title\": \"FooBar\",\n      },\n    },\n    \"frontmatterRaw\": \"meta:\n  title: FooBar\n  duration: 12\nlayout: center\n\",\n    \"importChain\": undefined,\n    \"index\": 1,\n    \"level\": 1,\n    \"note\": \"This is note\",\n    \"revision\": \"bi4rhs\",\n    \"source\": {\n      \"content\": \"\n# Hello\n\",\n      \"contentRaw\": \"# Hello\",\n      \"contentStart\": 16,\n      \"end\": 23,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {\n        \"layout\": \"center\",\n        \"meta\": {\n          \"duration\": 12,\n          \"title\": \"FooBar\",\n        },\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"center\",\n        \"meta\": {\n          \"duration\": 12,\n          \"title\": \"FooBar\",\n        },\n      },\n      \"frontmatterRaw\": \"meta:\n  title: FooBar\n  duration: 12\nlayout: center\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 1,\n      \"level\": 1,\n      \"note\": \"This is note\",\n      \"raw\": \"---\nmeta:\n  title: FooBar\n  duration: 12\nlayout: center\n---\n\n# Hello\n\n<!--\nThis is note\n-->\n\",\n      \"revision\": \"bi4rhs\",\n      \"start\": 10,\n      \"title\": \"Hello\",\n    },\n    \"title\": \"Hello\",\n  },\n  {\n    \"content\": \"# Morning\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 2,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"-4tdhll\",\n    \"source\": {\n      \"content\": \"\n# Morning\n\",\n      \"contentRaw\": \"# Morning\",\n      \"contentStart\": 24,\n      \"end\": 27,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 2,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"\n# Morning\n\",\n      \"revision\": \"-4tdhll\",\n      \"start\": 24,\n      \"title\": \"Morning\",\n    },\n    \"title\": \"Morning\",\n  },\n  {\n    \"content\": \"<!-- This is not note -->\nHey\",\n    \"frontmatter\": {\n      \"layout\": \"text\",\n    },\n    \"frontmatterRaw\": \"layout: text\n\",\n    \"importChain\": undefined,\n    \"index\": 3,\n    \"level\": undefined,\n    \"note\": \"This is note\",\n    \"revision\": \"w0llj2\",\n    \"source\": {\n      \"content\": \"\n<!-- This is not note -->\nHey\n\",\n      \"contentRaw\": \"<!-- This is not note -->\nHey\",\n      \"contentStart\": 30,\n      \"end\": 38,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {\n        \"layout\": \"text\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"text\",\n      },\n      \"frontmatterRaw\": \"layout: text\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 3,\n      \"level\": undefined,\n      \"note\": \"This is note\",\n      \"raw\": \"---\nlayout: text\n---\n\n<!-- This is not note -->\nHey\n\n<!--\nThis is note\n-->\n\",\n      \"revision\": \"w0llj2\",\n      \"start\": 27,\n      \"title\": undefined,\n    },\n    \"title\": undefined,\n  },\n  {\n    \"content\": \"\\`\\`\\`md\n---\nthis should be treated as code block\n---\n\n---\n\nAlso part of the code block\n\\`\\`\\`\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 4,\n    \"level\": undefined,\n    \"note\": undefined,\n    \"revision\": \"-1rfsmv\",\n    \"source\": {\n      \"content\": \"\n\\`\\`\\`md\n---\nthis should be treated as code block\n---\n\n---\n\nAlso part of the code block\n\\`\\`\\`\n\",\n      \"contentRaw\": \"\\`\\`\\`md\n---\nthis should be treated as code block\n---\n\n---\n\nAlso part of the code block\n\\`\\`\\`\",\n      \"contentStart\": 39,\n      \"end\": 50,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 4,\n      \"level\": undefined,\n      \"note\": undefined,\n      \"raw\": \"\n\\`\\`\\`md\n---\nthis should be treated as code block\n---\n\n---\n\nAlso part of the code block\n\\`\\`\\`\n\",\n      \"revision\": \"-1rfsmv\",\n      \"start\": 39,\n      \"title\": undefined,\n    },\n    \"title\": undefined,\n  },\n  {\n    \"content\": \"Content 1\",\n    \"frontmatter\": {\n      \"layout\": \"from yaml\",\n    },\n    \"frontmatterRaw\": \"\n# The first yaml block should be treated as frontmatter\nlayout: from yaml\n\",\n    \"importChain\": undefined,\n    \"index\": 5,\n    \"level\": undefined,\n    \"note\": undefined,\n    \"revision\": \"rp0t01\",\n    \"source\": {\n      \"content\": \"\nContent 1\n\",\n      \"contentRaw\": \"Content 1\",\n      \"contentStart\": 51,\n      \"end\": 59,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {\n        \"layout\": \"from yaml\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"from yaml\",\n      },\n      \"frontmatterRaw\": \"\n# The first yaml block should be treated as frontmatter\nlayout: from yaml\n\",\n      \"frontmatterStyle\": \"yaml\",\n      \"images\": [],\n      \"index\": 5,\n      \"level\": undefined,\n      \"note\": undefined,\n      \"raw\": \"\\`\\`\\`yaml\n# The first yaml block should be treated as frontmatter\nlayout: from yaml\n\\`\\`\\`\n\nContent 1\n\",\n      \"revision\": \"rp0t01\",\n      \"start\": 51,\n      \"title\": undefined,\n    },\n    \"title\": undefined,\n  },\n  {\n    \"content\": \"\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 1\n\\`\\`\\`\n\nContent 2\",\n    \"frontmatter\": {\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\n\",\n    \"importChain\": undefined,\n    \"index\": 6,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"-vfy6yo\",\n    \"source\": {\n      \"content\": \"\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 1\n\\`\\`\\`\n\nContent 2\n\",\n      \"contentRaw\": \"\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 1\n\\`\\`\\`\n\nContent 2\",\n      \"contentStart\": 62,\n      \"end\": 70,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 6,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\n---\n\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 1\n\\`\\`\\`\n\nContent 2\n\",\n      \"revision\": \"-vfy6yo\",\n      \"start\": 59,\n      \"title\": \"When there is already a frontmatter, the first yaml block should be treated as content\",\n    },\n    \"title\": \"When there is already a frontmatter, the first yaml block should be treated as content\",\n  },\n  {\n    \"content\": \"# Title\n\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 2\n\\`\\`\\`\n\nContent 3\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 7,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"-hu94b6\",\n    \"source\": {\n      \"content\": \"\n# Title\n\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 2\n\\`\\`\\`\n\nContent 3\n\",\n      \"contentRaw\": \"# Title\n\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 2\n\\`\\`\\`\n\nContent 3\",\n      \"contentStart\": 71,\n      \"end\": 81,\n      \"filepath\": \"frontmatter.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 7,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"\n# Title\n\n\\`\\`\\`yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 2\n\\`\\`\\`\n\nContent 3\n\",\n      \"revision\": \"-hu94b6\",\n      \"start\": 71,\n      \"title\": \"Title\",\n    },\n    \"title\": \"Title\",\n  },\n]\n`;\n\nexports[`md parser > mdc.md > config 1`] = `\n{\n  \"comark\": true,\n  \"drawings\": {\n    \"enabled\": true,\n    \"persist\": false,\n    \"presenterOnly\": false,\n    \"syncAll\": true,\n  },\n  \"fonts\": {\n    \"italic\": false,\n    \"local\": [],\n    \"mono\": [\n      \"ui-monospace\",\n      \"SFMono-Regular\",\n      \"Menlo\",\n      \"Monaco\",\n      \"Consolas\",\n      \"\"Liberation Mono\"\",\n      \"\"Courier New\"\",\n      \"monospace\",\n    ],\n    \"provider\": \"google\",\n    \"sans\": [\n      \"ui-sans-serif\",\n      \"system-ui\",\n      \"-apple-system\",\n      \"BlinkMacSystemFont\",\n      \"\"Segoe UI\"\",\n      \"Roboto\",\n      \"\"Helvetica Neue\"\",\n      \"Arial\",\n      \"\"Noto Sans\"\",\n      \"sans-serif\",\n      \"\"Apple Color Emoji\"\",\n      \"\"Segoe UI Emoji\"\",\n      \"\"Segoe UI Symbol\"\",\n      \"\"Noto Color Emoji\"\",\n    ],\n    \"serif\": [\n      \"ui-serif\",\n      \"Georgia\",\n      \"Cambria\",\n      \"\"Times New Roman\"\",\n      \"Times\",\n      \"serif\",\n    ],\n    \"webfonts\": [],\n    \"weights\": [\n      \"200\",\n      \"400\",\n      \"600\",\n    ],\n  },\n  \"title\": \"Comark{style=\"color:red\"}\",\n}\n`;\n\nexports[`md parser > mdc.md > features 1`] = `\n{\n  \"katex\": false,\n  \"mermaid\": false,\n  \"monaco\": false,\n  \"tweet\": false,\n}\n`;\n\nexports[`md parser > mdc.md > slides 1`] = `\n[\n  {\n    \"content\": \"# Comark{style=\"color:red\"}\n\n:arrow{x1=1 y1=1 x2=2 y2=2}\",\n    \"frontmatter\": {\n      \"comark\": true,\n    },\n    \"frontmatterRaw\": \"comark: true\n\",\n    \"importChain\": undefined,\n    \"index\": 0,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"hmgmac\",\n    \"source\": {\n      \"content\": \"\n# Comark{style=\"color:red\"}\n\n:arrow{x1=1 y1=1 x2=2 y2=2}\n\",\n      \"contentRaw\": \"# Comark{style=\"color:red\"}\n\n:arrow{x1=1 y1=1 x2=2 y2=2}\",\n      \"contentStart\": 3,\n      \"end\": 8,\n      \"filepath\": \"mdc.md\",\n      \"frontmatter\": {\n        \"comark\": true,\n      },\n      \"frontmatterDoc\": {\n        \"comark\": true,\n      },\n      \"frontmatterRaw\": \"comark: true\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\ncomark: true\n---\n\n# Comark{style=\"color:red\"}\n\n:arrow{x1=1 y1=1 x2=2 y2=2}\n\",\n      \"revision\": \"hmgmac\",\n      \"start\": 0,\n      \"title\": \"Comark{style=\"color:red\"}\",\n    },\n    \"title\": \"Comark{style=\"color:red\"}\",\n  },\n]\n`;\n\nexports[`md parser > minimal.md > config 1`] = `\n{\n  \"drawings\": {\n    \"enabled\": true,\n    \"persist\": false,\n    \"presenterOnly\": false,\n    \"syncAll\": true,\n  },\n  \"fonts\": {\n    \"italic\": false,\n    \"local\": [],\n    \"mono\": [\n      \"ui-monospace\",\n      \"SFMono-Regular\",\n      \"Menlo\",\n      \"Monaco\",\n      \"Consolas\",\n      \"\"Liberation Mono\"\",\n      \"\"Courier New\"\",\n      \"monospace\",\n    ],\n    \"provider\": \"google\",\n    \"sans\": [\n      \"ui-sans-serif\",\n      \"system-ui\",\n      \"-apple-system\",\n      \"BlinkMacSystemFont\",\n      \"\"Segoe UI\"\",\n      \"Roboto\",\n      \"\"Helvetica Neue\"\",\n      \"Arial\",\n      \"\"Noto Sans\"\",\n      \"sans-serif\",\n      \"\"Apple Color Emoji\"\",\n      \"\"Segoe UI Emoji\"\",\n      \"\"Segoe UI Symbol\"\",\n      \"\"Noto Color Emoji\"\",\n    ],\n    \"serif\": [\n      \"ui-serif\",\n      \"Georgia\",\n      \"Cambria\",\n      \"\"Times New Roman\"\",\n      \"Times\",\n      \"serif\",\n    ],\n    \"webfonts\": [],\n    \"weights\": [\n      \"200\",\n      \"400\",\n      \"600\",\n    ],\n  },\n  \"title\": \"H1\",\n}\n`;\n\nexports[`md parser > minimal.md > features 1`] = `\n{\n  \"katex\": false,\n  \"mermaid\": false,\n  \"monaco\": false,\n  \"tweet\": false,\n}\n`;\n\nexports[`md parser > minimal.md > slides 1`] = `\n[\n  {\n    \"content\": \"# H1\n## H2\n### H3\n\nSample Text\n\n\\`\\`\\`ts\nconsole.log('Hello World')\n\\`\\`\\`\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 0,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"-l1yoe\",\n    \"source\": {\n      \"content\": \"\n# H1\n## H2\n### H3\n\nSample Text\n\n\\`\\`\\`ts\nconsole.log('Hello World')\n\\`\\`\\`\n\",\n      \"contentRaw\": \"# H1\n## H2\n### H3\n\nSample Text\n\n\\`\\`\\`ts\nconsole.log('Hello World')\n\\`\\`\\`\",\n      \"contentStart\": 0,\n      \"end\": 10,\n      \"filepath\": \"minimal.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"\n# H1\n## H2\n### H3\n\nSample Text\n\n\\`\\`\\`ts\nconsole.log('Hello World')\n\\`\\`\\`\n\",\n      \"revision\": \"-l1yoe\",\n      \"start\": 0,\n      \"title\": \"H1\",\n    },\n    \"title\": \"H1\",\n  },\n  {\n    \"content\": \"# Hello\n\n- Hello\n- Hi\n- Hey\n- Yo\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 1,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"1owo9s\",\n    \"source\": {\n      \"content\": \"\n# Hello\n\n- Hello\n- Hi\n- Hey\n- Yo\n\",\n      \"contentRaw\": \"# Hello\n\n- Hello\n- Hi\n- Hey\n- Yo\",\n      \"contentStart\": 11,\n      \"end\": 19,\n      \"filepath\": \"minimal.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 1,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"\n# Hello\n\n- Hello\n- Hi\n- Hey\n- Yo\n\",\n      \"revision\": \"1owo9s\",\n      \"start\": 11,\n      \"title\": \"Hello\",\n    },\n    \"title\": \"Hello\",\n  },\n  {\n    \"content\": \"Nice to meet you\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 2,\n    \"level\": undefined,\n    \"note\": undefined,\n    \"revision\": \"-kcr6u0\",\n    \"source\": {\n      \"content\": \"\nNice to meet you\n\",\n      \"contentRaw\": \"Nice to meet you\",\n      \"contentStart\": 20,\n      \"end\": 23,\n      \"filepath\": \"minimal.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 2,\n      \"level\": undefined,\n      \"note\": undefined,\n      \"raw\": \"\nNice to meet you\n\",\n      \"revision\": \"-kcr6u0\",\n      \"start\": 20,\n      \"title\": undefined,\n    },\n    \"title\": undefined,\n  },\n]\n`;\n\nexports[`md parser > multi-entries.md > config 1`] = `\n{\n  \"drawings\": {\n    \"enabled\": true,\n    \"persist\": false,\n    \"presenterOnly\": false,\n    \"syncAll\": true,\n  },\n  \"fonts\": {\n    \"italic\": false,\n    \"local\": [],\n    \"mono\": [\n      \"ui-monospace\",\n      \"SFMono-Regular\",\n      \"Menlo\",\n      \"Monaco\",\n      \"Consolas\",\n      \"\"Liberation Mono\"\",\n      \"\"Courier New\"\",\n      \"monospace\",\n    ],\n    \"provider\": \"google\",\n    \"sans\": [\n      \"ui-sans-serif\",\n      \"system-ui\",\n      \"-apple-system\",\n      \"BlinkMacSystemFont\",\n      \"\"Segoe UI\"\",\n      \"Roboto\",\n      \"\"Helvetica Neue\"\",\n      \"Arial\",\n      \"\"Noto Sans\"\",\n      \"sans-serif\",\n      \"\"Apple Color Emoji\"\",\n      \"\"Segoe UI Emoji\"\",\n      \"\"Segoe UI Symbol\"\",\n      \"\"Noto Color Emoji\"\",\n    ],\n    \"serif\": [\n      \"ui-serif\",\n      \"Georgia\",\n      \"Cambria\",\n      \"\"Times New Roman\"\",\n      \"Times\",\n      \"serif\",\n    ],\n    \"webfonts\": [],\n    \"weights\": [\n      \"200\",\n      \"400\",\n      \"600\",\n    ],\n  },\n  \"src\": \"sub/page1.md\",\n  \"title\": \"Page 1\",\n}\n`;\n\nexports[`md parser > multi-entries.md > features 1`] = `\n{\n  \"katex\": true,\n  \"mermaid\": false,\n  \"monaco\": false,\n  \"tweet\": true,\n}\n`;\n\nexports[`md parser > multi-entries.md > slides 1`] = `\n[\n  {\n    \"content\": \"# Page 1\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 3,\n        \"end\": 4,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"src\": \"sub/page1.md\",\n        },\n        \"frontmatterDoc\": {\n          \"src\": \"sub/page1.md\",\n        },\n        \"frontmatterRaw\": \"src: sub/page1.md\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [],\n        \"imports\": [\n          {\n            \"content\": \"# Page 1\",\n            \"contentRaw\": \"# Page 1\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/page1.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 1\n\",\n            \"revision\": \"q9078d\",\n            \"start\": 0,\n            \"title\": \"Page 1\",\n          },\n        ],\n        \"index\": 0,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: sub/page1.md\n---\n\",\n        \"revision\": \"ad0zia\",\n        \"start\": 0,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 0,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"q9078d\",\n    \"source\": {\n      \"content\": \"# Page 1\",\n      \"contentRaw\": \"# Page 1\",\n      \"contentStart\": 0,\n      \"end\": 2,\n      \"filepath\": \"sub/page1.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"# Page 1\n\",\n      \"revision\": \"q9078d\",\n      \"start\": 0,\n      \"title\": \"Page 1\",\n    },\n    \"title\": \"Page 1\",\n  },\n  {\n    \"content\": \"# Page 2\n\n<Tweet />\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#2\",\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\n\",\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 8,\n        \"end\": 9,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#2\",\n          \"src\": \"/sub/page2.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#2\",\n          \"src\": \"/sub/page2.md\",\n        },\n        \"frontmatterRaw\": \"src: /sub/page2.md\nbackground: https://sli.dev/demo-cover.png#2\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#2\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"# Page 2\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n            \"contentStart\": 3,\n            \"end\": 8,\n            \"filepath\": \"sub/page2.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n            \"revision\": \"rdjwxb\",\n            \"start\": 0,\n            \"title\": \"Page 2\",\n          },\n        ],\n        \"index\": 1,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: /sub/page2.md\nbackground: https://sli.dev/demo-cover.png#2\n---\n\",\n        \"revision\": \"-fzej2s\",\n        \"start\": 4,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 1,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"rdjwxb\",\n    \"source\": {\n      \"content\": \"# Page 2\n\n<Tweet />\",\n      \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n      \"contentStart\": 3,\n      \"end\": 8,\n      \"filepath\": \"sub/page2.md\",\n      \"frontmatter\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n      \"revision\": \"rdjwxb\",\n      \"start\": 0,\n      \"title\": \"Page 2\",\n    },\n    \"title\": \"Page 2\",\n  },\n  {\n    \"content\": \"# Page 3\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#34\",\n    },\n    \"frontmatterRaw\": undefined,\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 13,\n        \"end\": 14,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#34\",\n          \"src\": \"./sub/pages3-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#34\",\n          \"src\": \"./sub/pages3-4.md\",\n        },\n        \"frontmatterRaw\": \"src: ./sub/pages3-4.md\nbackground: https://sli.dev/demo-cover.png#34\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#34\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"# Page 3\",\n            \"contentRaw\": \"# Page 3\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 3\n\",\n            \"revision\": \"q9078f\",\n            \"start\": 0,\n            \"title\": \"Page 3\",\n          },\n          {\n            \"content\": \"# Page 4\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n            \"contentStart\": 5,\n            \"end\": 10,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 1,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n            \"revision\": \"vn9anh\",\n            \"start\": 2,\n            \"title\": \"Page 4\",\n          },\n        ],\n        \"index\": 2,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: ./sub/pages3-4.md\nbackground: https://sli.dev/demo-cover.png#34\n---\n\",\n        \"revision\": \"-y8rid0\",\n        \"start\": 9,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 2,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"q9078f\",\n    \"source\": {\n      \"content\": \"# Page 3\",\n      \"contentRaw\": \"# Page 3\",\n      \"contentStart\": 0,\n      \"end\": 2,\n      \"filepath\": \"sub/pages3-4.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"# Page 3\n\",\n      \"revision\": \"q9078f\",\n      \"start\": 0,\n      \"title\": \"Page 3\",\n    },\n    \"title\": \"Page 3\",\n  },\n  {\n    \"content\": \"# Page 4\n\n<Tweet />\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#34\",\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\n\",\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 13,\n        \"end\": 14,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#34\",\n          \"src\": \"./sub/pages3-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#34\",\n          \"src\": \"./sub/pages3-4.md\",\n        },\n        \"frontmatterRaw\": \"src: ./sub/pages3-4.md\nbackground: https://sli.dev/demo-cover.png#34\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#34\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"# Page 3\",\n            \"contentRaw\": \"# Page 3\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 3\n\",\n            \"revision\": \"q9078f\",\n            \"start\": 0,\n            \"title\": \"Page 3\",\n          },\n          {\n            \"content\": \"# Page 4\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n            \"contentStart\": 5,\n            \"end\": 10,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 1,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n            \"revision\": \"vn9anh\",\n            \"start\": 2,\n            \"title\": \"Page 4\",\n          },\n        ],\n        \"index\": 2,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: ./sub/pages3-4.md\nbackground: https://sli.dev/demo-cover.png#34\n---\n\",\n        \"revision\": \"-y8rid0\",\n        \"start\": 9,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 3,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"vn9anh\",\n    \"source\": {\n      \"content\": \"# Page 4\n\n<Tweet />\",\n      \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n      \"contentStart\": 5,\n      \"end\": 10,\n      \"filepath\": \"sub/pages3-4.md\",\n      \"frontmatter\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 1,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n      \"revision\": \"vn9anh\",\n      \"start\": 2,\n      \"title\": \"Page 4\",\n    },\n    \"title\": \"Page 4\",\n  },\n  {\n    \"content\": \"# Page 1\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#14\",\n    },\n    \"frontmatterRaw\": undefined,\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 18,\n        \"end\": 19,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterRaw\": \"src: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#14\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 3,\n            \"end\": 4,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterRaw\": \"src: /sub/page1.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 1\",\n                \"contentRaw\": \"# Page 1\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/page1.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 1\n\",\n                \"revision\": \"q9078d\",\n                \"start\": 0,\n                \"title\": \"Page 1\",\n              },\n            ],\n            \"index\": 0,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: /sub/page1.md\n---\n\",\n            \"revision\": \"-kzazk9\",\n            \"start\": 0,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 7,\n            \"end\": 8,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterRaw\": \"src: page2.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 2\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n                \"contentStart\": 3,\n                \"end\": 8,\n                \"filepath\": \"sub/page2.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n                \"revision\": \"rdjwxb\",\n                \"start\": 0,\n                \"title\": \"Page 2\",\n              },\n            ],\n            \"index\": 1,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: page2.md\n---\n\",\n            \"revision\": \"koer3m\",\n            \"start\": 4,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 11,\n            \"end\": 12,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 3\",\n                \"contentRaw\": \"# Page 3\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 3\n\",\n                \"revision\": \"q9078f\",\n                \"start\": 0,\n                \"title\": \"Page 3\",\n              },\n              {\n                \"content\": \"# Page 4\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n                \"contentStart\": 5,\n                \"end\": 10,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 1,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n                \"revision\": \"vn9anh\",\n                \"start\": 2,\n                \"title\": \"Page 4\",\n              },\n            ],\n            \"index\": 2,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n            \"revision\": \"-9fgden\",\n            \"start\": 8,\n            \"title\": undefined,\n          },\n        ],\n        \"index\": 3,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n---\n\",\n        \"revision\": \"-bcli90\",\n        \"start\": 14,\n        \"title\": undefined,\n      },\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 3,\n        \"end\": 4,\n        \"filepath\": \"sub/nested1-4.md\",\n        \"frontmatter\": {\n          \"src\": \"/sub/page1.md\",\n        },\n        \"frontmatterDoc\": {\n          \"src\": \"/sub/page1.md\",\n        },\n        \"frontmatterRaw\": \"src: /sub/page1.md\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [],\n        \"imports\": [\n          {\n            \"content\": \"# Page 1\",\n            \"contentRaw\": \"# Page 1\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/page1.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 1\n\",\n            \"revision\": \"q9078d\",\n            \"start\": 0,\n            \"title\": \"Page 1\",\n          },\n        ],\n        \"index\": 0,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: /sub/page1.md\n---\n\",\n        \"revision\": \"-kzazk9\",\n        \"start\": 0,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 4,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"q9078d\",\n    \"source\": {\n      \"content\": \"# Page 1\",\n      \"contentRaw\": \"# Page 1\",\n      \"contentStart\": 0,\n      \"end\": 2,\n      \"filepath\": \"sub/page1.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"# Page 1\n\",\n      \"revision\": \"q9078d\",\n      \"start\": 0,\n      \"title\": \"Page 1\",\n    },\n    \"title\": \"Page 1\",\n  },\n  {\n    \"content\": \"# Page 2\n\n<Tweet />\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#14\",\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\n\",\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 18,\n        \"end\": 19,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterRaw\": \"src: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#14\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 3,\n            \"end\": 4,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterRaw\": \"src: /sub/page1.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 1\",\n                \"contentRaw\": \"# Page 1\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/page1.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 1\n\",\n                \"revision\": \"q9078d\",\n                \"start\": 0,\n                \"title\": \"Page 1\",\n              },\n            ],\n            \"index\": 0,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: /sub/page1.md\n---\n\",\n            \"revision\": \"-kzazk9\",\n            \"start\": 0,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 7,\n            \"end\": 8,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterRaw\": \"src: page2.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 2\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n                \"contentStart\": 3,\n                \"end\": 8,\n                \"filepath\": \"sub/page2.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n                \"revision\": \"rdjwxb\",\n                \"start\": 0,\n                \"title\": \"Page 2\",\n              },\n            ],\n            \"index\": 1,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: page2.md\n---\n\",\n            \"revision\": \"koer3m\",\n            \"start\": 4,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 11,\n            \"end\": 12,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 3\",\n                \"contentRaw\": \"# Page 3\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 3\n\",\n                \"revision\": \"q9078f\",\n                \"start\": 0,\n                \"title\": \"Page 3\",\n              },\n              {\n                \"content\": \"# Page 4\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n                \"contentStart\": 5,\n                \"end\": 10,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 1,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n                \"revision\": \"vn9anh\",\n                \"start\": 2,\n                \"title\": \"Page 4\",\n              },\n            ],\n            \"index\": 2,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n            \"revision\": \"-9fgden\",\n            \"start\": 8,\n            \"title\": undefined,\n          },\n        ],\n        \"index\": 3,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n---\n\",\n        \"revision\": \"-bcli90\",\n        \"start\": 14,\n        \"title\": undefined,\n      },\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 7,\n        \"end\": 8,\n        \"filepath\": \"sub/nested1-4.md\",\n        \"frontmatter\": {\n          \"src\": \"page2.md\",\n        },\n        \"frontmatterDoc\": {\n          \"src\": \"page2.md\",\n        },\n        \"frontmatterRaw\": \"src: page2.md\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [],\n        \"imports\": [\n          {\n            \"content\": \"# Page 2\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n            \"contentStart\": 3,\n            \"end\": 8,\n            \"filepath\": \"sub/page2.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n            \"revision\": \"rdjwxb\",\n            \"start\": 0,\n            \"title\": \"Page 2\",\n          },\n        ],\n        \"index\": 1,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: page2.md\n---\n\",\n        \"revision\": \"koer3m\",\n        \"start\": 4,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 5,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"rdjwxb\",\n    \"source\": {\n      \"content\": \"# Page 2\n\n<Tweet />\",\n      \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n      \"contentStart\": 3,\n      \"end\": 8,\n      \"filepath\": \"sub/page2.md\",\n      \"frontmatter\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n      \"revision\": \"rdjwxb\",\n      \"start\": 0,\n      \"title\": \"Page 2\",\n    },\n    \"title\": \"Page 2\",\n  },\n  {\n    \"content\": \"# Page 3\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#14\",\n    },\n    \"frontmatterRaw\": undefined,\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 18,\n        \"end\": 19,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterRaw\": \"src: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#14\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 3,\n            \"end\": 4,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterRaw\": \"src: /sub/page1.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 1\",\n                \"contentRaw\": \"# Page 1\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/page1.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 1\n\",\n                \"revision\": \"q9078d\",\n                \"start\": 0,\n                \"title\": \"Page 1\",\n              },\n            ],\n            \"index\": 0,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: /sub/page1.md\n---\n\",\n            \"revision\": \"-kzazk9\",\n            \"start\": 0,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 7,\n            \"end\": 8,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterRaw\": \"src: page2.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 2\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n                \"contentStart\": 3,\n                \"end\": 8,\n                \"filepath\": \"sub/page2.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n                \"revision\": \"rdjwxb\",\n                \"start\": 0,\n                \"title\": \"Page 2\",\n              },\n            ],\n            \"index\": 1,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: page2.md\n---\n\",\n            \"revision\": \"koer3m\",\n            \"start\": 4,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 11,\n            \"end\": 12,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 3\",\n                \"contentRaw\": \"# Page 3\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 3\n\",\n                \"revision\": \"q9078f\",\n                \"start\": 0,\n                \"title\": \"Page 3\",\n              },\n              {\n                \"content\": \"# Page 4\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n                \"contentStart\": 5,\n                \"end\": 10,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 1,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n                \"revision\": \"vn9anh\",\n                \"start\": 2,\n                \"title\": \"Page 4\",\n              },\n            ],\n            \"index\": 2,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n            \"revision\": \"-9fgden\",\n            \"start\": 8,\n            \"title\": undefined,\n          },\n        ],\n        \"index\": 3,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n---\n\",\n        \"revision\": \"-bcli90\",\n        \"start\": 14,\n        \"title\": undefined,\n      },\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 11,\n        \"end\": 12,\n        \"filepath\": \"sub/nested1-4.md\",\n        \"frontmatter\": {\n          \"src\": \"../sub/pages3-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"src\": \"../sub/pages3-4.md\",\n        },\n        \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [],\n        \"imports\": [\n          {\n            \"content\": \"# Page 3\",\n            \"contentRaw\": \"# Page 3\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 3\n\",\n            \"revision\": \"q9078f\",\n            \"start\": 0,\n            \"title\": \"Page 3\",\n          },\n          {\n            \"content\": \"# Page 4\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n            \"contentStart\": 5,\n            \"end\": 10,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 1,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n            \"revision\": \"vn9anh\",\n            \"start\": 2,\n            \"title\": \"Page 4\",\n          },\n        ],\n        \"index\": 2,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n        \"revision\": \"-9fgden\",\n        \"start\": 8,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 6,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"q9078f\",\n    \"source\": {\n      \"content\": \"# Page 3\",\n      \"contentRaw\": \"# Page 3\",\n      \"contentStart\": 0,\n      \"end\": 2,\n      \"filepath\": \"sub/pages3-4.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 0,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"# Page 3\n\",\n      \"revision\": \"q9078f\",\n      \"start\": 0,\n      \"title\": \"Page 3\",\n    },\n    \"title\": \"Page 3\",\n  },\n  {\n    \"content\": \"# Page 4\n\n<Tweet />\",\n    \"frontmatter\": {\n      \"background\": \"https://sli.dev/demo-cover.png#14\",\n      \"layout\": \"cover\",\n    },\n    \"frontmatterRaw\": \"layout: cover\n\",\n    \"importChain\": [\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 18,\n        \"end\": 19,\n        \"filepath\": \"multi-entries.md\",\n        \"frontmatter\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"background\": \"https://sli.dev/demo-cover.png#14\",\n          \"src\": \"sub/nested1-4.md\",\n        },\n        \"frontmatterRaw\": \"src: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [\n          \"https://sli.dev/demo-cover.png#14\",\n        ],\n        \"imports\": [\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 3,\n            \"end\": 4,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"/sub/page1.md\",\n            },\n            \"frontmatterRaw\": \"src: /sub/page1.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 1\",\n                \"contentRaw\": \"# Page 1\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/page1.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 1\n\",\n                \"revision\": \"q9078d\",\n                \"start\": 0,\n                \"title\": \"Page 1\",\n              },\n            ],\n            \"index\": 0,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: /sub/page1.md\n---\n\",\n            \"revision\": \"-kzazk9\",\n            \"start\": 0,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 7,\n            \"end\": 8,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"page2.md\",\n            },\n            \"frontmatterRaw\": \"src: page2.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 2\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 2\n\n<Tweet />\",\n                \"contentStart\": 3,\n                \"end\": 8,\n                \"filepath\": \"sub/page2.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n\",\n                \"revision\": \"rdjwxb\",\n                \"start\": 0,\n                \"title\": \"Page 2\",\n              },\n            ],\n            \"index\": 1,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: page2.md\n---\n\",\n            \"revision\": \"koer3m\",\n            \"start\": 4,\n            \"title\": undefined,\n          },\n          {\n            \"content\": \"\",\n            \"contentRaw\": \"\",\n            \"contentStart\": 11,\n            \"end\": 12,\n            \"filepath\": \"sub/nested1-4.md\",\n            \"frontmatter\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterDoc\": {\n              \"src\": \"../sub/pages3-4.md\",\n            },\n            \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"imports\": [\n              {\n                \"content\": \"# Page 3\",\n                \"contentRaw\": \"# Page 3\",\n                \"contentStart\": 0,\n                \"end\": 2,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {},\n                \"frontmatterDoc\": undefined,\n                \"frontmatterRaw\": undefined,\n                \"frontmatterStyle\": undefined,\n                \"images\": [],\n                \"index\": 0,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"# Page 3\n\",\n                \"revision\": \"q9078f\",\n                \"start\": 0,\n                \"title\": \"Page 3\",\n              },\n              {\n                \"content\": \"# Page 4\n\n<Tweet />\",\n                \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n                \"contentStart\": 5,\n                \"end\": 10,\n                \"filepath\": \"sub/pages3-4.md\",\n                \"frontmatter\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterDoc\": {\n                  \"layout\": \"cover\",\n                },\n                \"frontmatterRaw\": \"layout: cover\n\",\n                \"frontmatterStyle\": \"frontmatter\",\n                \"images\": [],\n                \"index\": 1,\n                \"level\": 1,\n                \"note\": undefined,\n                \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n                \"revision\": \"vn9anh\",\n                \"start\": 2,\n                \"title\": \"Page 4\",\n              },\n            ],\n            \"index\": 2,\n            \"level\": undefined,\n            \"note\": undefined,\n            \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n            \"revision\": \"-9fgden\",\n            \"start\": 8,\n            \"title\": undefined,\n          },\n        ],\n        \"index\": 3,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n---\n\",\n        \"revision\": \"-bcli90\",\n        \"start\": 14,\n        \"title\": undefined,\n      },\n      {\n        \"content\": \"\",\n        \"contentRaw\": \"\",\n        \"contentStart\": 11,\n        \"end\": 12,\n        \"filepath\": \"sub/nested1-4.md\",\n        \"frontmatter\": {\n          \"src\": \"../sub/pages3-4.md\",\n        },\n        \"frontmatterDoc\": {\n          \"src\": \"../sub/pages3-4.md\",\n        },\n        \"frontmatterRaw\": \"src: ../sub/pages3-4.md\n\",\n        \"frontmatterStyle\": \"frontmatter\",\n        \"images\": [],\n        \"imports\": [\n          {\n            \"content\": \"# Page 3\",\n            \"contentRaw\": \"# Page 3\",\n            \"contentStart\": 0,\n            \"end\": 2,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {},\n            \"frontmatterDoc\": undefined,\n            \"frontmatterRaw\": undefined,\n            \"frontmatterStyle\": undefined,\n            \"images\": [],\n            \"index\": 0,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"# Page 3\n\",\n            \"revision\": \"q9078f\",\n            \"start\": 0,\n            \"title\": \"Page 3\",\n          },\n          {\n            \"content\": \"# Page 4\n\n<Tweet />\",\n            \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n            \"contentStart\": 5,\n            \"end\": 10,\n            \"filepath\": \"sub/pages3-4.md\",\n            \"frontmatter\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterDoc\": {\n              \"layout\": \"cover\",\n            },\n            \"frontmatterRaw\": \"layout: cover\n\",\n            \"frontmatterStyle\": \"frontmatter\",\n            \"images\": [],\n            \"index\": 1,\n            \"level\": 1,\n            \"note\": undefined,\n            \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n            \"revision\": \"vn9anh\",\n            \"start\": 2,\n            \"title\": \"Page 4\",\n          },\n        ],\n        \"index\": 2,\n        \"level\": undefined,\n        \"note\": undefined,\n        \"raw\": \"---\nsrc: ../sub/pages3-4.md\n---\n\",\n        \"revision\": \"-9fgden\",\n        \"start\": 8,\n        \"title\": undefined,\n      },\n    ],\n    \"index\": 7,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"vn9anh\",\n    \"source\": {\n      \"content\": \"# Page 4\n\n<Tweet />\",\n      \"contentRaw\": \"# Page 4\n\n<Tweet />\",\n      \"contentStart\": 5,\n      \"end\": 10,\n      \"filepath\": \"sub/pages3-4.md\",\n      \"frontmatter\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterDoc\": {\n        \"layout\": \"cover\",\n      },\n      \"frontmatterRaw\": \"layout: cover\n\",\n      \"frontmatterStyle\": \"frontmatter\",\n      \"images\": [],\n      \"index\": 1,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n\",\n      \"revision\": \"vn9anh\",\n      \"start\": 2,\n      \"title\": \"Page 4\",\n    },\n    \"title\": \"Page 4\",\n  },\n  {\n    \"content\": \"# Inline Page\n\n$x+2$\",\n    \"frontmatter\": {},\n    \"frontmatterRaw\": undefined,\n    \"importChain\": undefined,\n    \"index\": 8,\n    \"level\": 1,\n    \"note\": undefined,\n    \"revision\": \"6kem5s\",\n    \"source\": {\n      \"content\": \"\n# Inline Page\n\n$x+2$\n\",\n      \"contentRaw\": \"# Inline Page\n\n$x+2$\",\n      \"contentStart\": 20,\n      \"end\": 25,\n      \"filepath\": \"multi-entries.md\",\n      \"frontmatter\": {},\n      \"frontmatterDoc\": undefined,\n      \"frontmatterRaw\": undefined,\n      \"frontmatterStyle\": undefined,\n      \"images\": [],\n      \"index\": 4,\n      \"level\": 1,\n      \"note\": undefined,\n      \"raw\": \"\n# Inline Page\n\n$x+2$\n\",\n      \"revision\": \"6kem5s\",\n      \"start\": 20,\n      \"title\": \"Inline Page\",\n    },\n    \"title\": \"Inline Page\",\n  },\n]\n`;\n"
  },
  {
    "path": "test/__snapshots__/transform-all.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`transform-all 1`] = `\n\"\n# Page \n\nDefault Slot\n\n\n<CodeBlockWrapper v-bind=\"{lines:true}\" :title='\"\"' :ranges='[\"2\",\"3\",\"4\"]'>\n\n\\`\\`\\`ts\nfunction _foo() {\n  // ...\n}\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n<template v-slot:right=\"slotProps\">\n\n\nFoo \\`{{ code }}\\`\n\n\n\n</template>\n<template v-slot:left=\"slotProps\">\n\n<div>Left Slot</div>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"\"' :ranges='[]'>\n\n\\`\\`\\`md\n<style>\n.text-red { color: red; }\n</style>\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"with-title\"' :ranges='[]'>\n\n\\`\\`\\`js [with-title]\nconst a = 1\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n::code-group\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"npm\"' :ranges='[]'>\n\n\\`\\`\\`sh [npm]\nnpm i slidev\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"yarn\"' :ranges='[]'>\n\n\\`\\`\\`sh [yarn]\nyarn add slidev\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"pnpm\"' :ranges='[]'>\n\n\\`\\`\\`sh [pnpm]\npnpm add slidev\n\\`\\`\\`\n\n</CodeBlockWrapper>\n::\n\n<style scoped>\n\n.text-green { color: green; }\n</style>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"\"' :ranges='[]'>\n\n\\`\\`\\`ts\nfunction _foo() {\n  // ...\n}\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n</template>\"\n`;\n"
  },
  {
    "path": "test/__snapshots__/transform.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`code-wrapper with square brackets in code 1`] = `\n\"\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"\"' :ranges='[]'>\n\n\\`\\`\\`csharp\n[Flags]\nenum MyEnum {}\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\"\n`;\n\nexports[`code-wrapper with title and square brackets in code 1`] = `\n\"\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"MyEnum.cs\"' :ranges='[]'>\n\n\\`\\`\\`csharp [MyEnum.cs]\n[Flags]\nenum MyEnum {}\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\"\n`;\n\nexports[`code-wrapper with twoslash 1`] = `\n\"\n\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"filename.ts\"' :ranges='[]'>\n\n\\`\\`\\`ts [filename.ts]\nconst a = 1\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"filename.ts\"' :ranges='[]'>\n\n\\`\\`\\`ts [filename.ts]\nconst a = 1\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\"\n`;\n\nexports[`external snippet 1`] = `\n\"\n\\`\\`\\`ts {2|3|4}{lines:true}\nfunction _foo() {\n  // ...\n}\n\\`\\`\\`\n\"\n`;\n\nexports[`inline CSS 1`] = `\n\"\n# Page \n\n<style scoped>\n\nh1 {\n  color: red;\n}\n</style>\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"\"' :ranges='[]'>\n\n\\`\\`\\`css\n<style>\nh1 {\n  color: green;\n}\n</style>\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\"\n`;\n\nexports[`mermaid 1`] = `\n\"\n# Page \n\n<Mermaid code-lz=\"M4UwjgriB2DGIBECWBDA5gJxQWwFAAJ8BBAGyXgFoA+AKQHsALaALnwAkQSS796mAafAzoB3fCgwh8ATzoQA/AXwA5OgBcpdAG4gMxMvH58WxfGukAHcihL4k0DVlhqkdaEA\" v-bind=\"{}\" />\n\n<Mermaid code-lz=\"OYJwhgDgFgBAKgEQFACEDacCmAPALgXRgFoiA+GAYQG8FMBjASwGcGB7AOwF8kLiyAfAPLtM/GAjQAlTEwCuAG1wwAjPh59S/OAHdWYgKJSZCpQCZ8QA\" v-bind=\"{theme: 'neutral', scale: 0.8}\" />\n\"\n`;\n\nexports[`slot-sugar 1`] = `\n\"\n# Page \n\nDefault Slot\n\n<template v-slot:right=\"slotProps\">\n\nRight Slot\n\n\n</template>\n<template v-slot:left=\"slotProps\">\n\n<div>Left Slot</div>\n\n\n</template>\"\n`;\n\nexports[`slot-sugar with code 1`] = `\n\"\n# Page \n\nDefault Slot\n\n\n<template v-slot:code=\"slotProps\">\n\n\n\n<CodeBlockWrapper v-bind=\"{}\" :title='\"\"' :ranges='[]'>\n\n\\`\\`\\`md\nSlot Usage\n::right::\n::left::\n\\`\\`\\`\n\n</CodeBlockWrapper>\n\n\n\n</template>\"\n`;\n\nexports[`slot-sugar with default 1`] = `\n\"\n\n<template v-slot:right=\"slotProps\">\n\nRight Slot\n\n\n</template>\n<template v-slot:left=\"slotProps\">\n\n<div>Left Slot</div>\n\n\n</template>\n<template v-slot:default=\"slotProps\">\n\n# Page \nDefault Slot\n\n\n</template>\"\n`;\n\nexports[`slot-sugar with symbols in name 1`] = `\n\"\n# Page \n\nDefault Slot\n\n<template v-slot:slot::1=\"slotProps\">\n\nFirst Slot\n\n\n</template>\n<template v-slot:slot.2=\"slotProps\">\n\nSecond Slot\n\n\n</template>\"\n`;\n"
  },
  {
    "path": "test/__snapshots__/utils.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`utils > coollabs-fonts 1`] = `\"https://api.fonts.coollabs.io/fonts?family=Fira+Code:wght@200;400;600&family=PT+Serif:wght@200;400;600&display=swap\"`;\n\nexports[`utils > coollabs-fonts 2`] = `\"https://api.fonts.coollabs.io/fonts?family=Fira+Code:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=PT+Serif:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap\"`;\n\nexports[`utils > google-fonts 1`] = `\"https://fonts.googleapis.com/css2?family=Fira+Code:wght@200;400;600&family=PT+Serif:wght@200;400;600&display=swap\"`;\n\nexports[`utils > google-fonts 2`] = `\"https://fonts.googleapis.com/css2?family=Fira+Code:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&family=PT+Serif:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap\"`;\n"
  },
  {
    "path": "test/_tutils.ts",
    "content": "import type { MarkdownTransformContext } from '@slidev/types'\nimport path from 'node:path'\nimport MagicString from 'magic-string-stack'\nimport * as shiki from 'shiki'\n\nexport function createTransformContext(code: string): MarkdownTransformContext {\n  const s = new MagicString(code)\n  return {\n    s,\n    slide: { } as any,\n    options: {\n      userRoot: path.join(__dirname, './fixtures/'),\n      data: {\n        slides: [\n          {} as any,\n        ],\n        watchFiles: {},\n        config: {} as any,\n        features: {},\n      },\n      utils: {\n        shiki,\n        shikiOptions: {\n          theme: 'nord',\n        },\n      },\n    } as any,\n  }\n}\n"
  },
  {
    "path": "test/fixtures/markdown/frontmatter.md",
    "content": "---\nlayout: cover\nfonts:\n  sans: Roboto, Lato\n  serif: Mate SC\n  mono: Fira Code\n---\n\n# Hi\n\n---\nmeta:\n  title: FooBar\n  duration: 12\nlayout: center\n---\n\n# Hello\n\n<!--\nThis is note\n-->\n\n---\n\n# Morning\n\n---\nlayout: text\n---\n\n<!-- This is not note -->\nHey\n\n<!--\nThis is note\n-->\n\n---\n\n```md\n---\nthis should be treated as code block\n---\n\n---\n\nAlso part of the code block\n```\n\n---\n\n```yaml\n# The first yaml block should be treated as frontmatter\nlayout: from yaml\n```\n\nContent 1\n\n---\nlayout: cover\n---\n\n```yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 1\n```\n\nContent 2\n\n---\n\n# Title\n\n```yaml\n# When there is already a frontmatter, the first yaml block should be treated as content\nlayout: should not from yaml 2\n```\n\nContent 3\n"
  },
  {
    "path": "test/fixtures/markdown/mdc.md",
    "content": "---\ncomark: true\n---\n\n# Comark{style=\"color:red\"}\n\n:arrow{x1=1 y1=1 x2=2 y2=2}\n"
  },
  {
    "path": "test/fixtures/markdown/minimal.md",
    "content": "# H1\n## H2\n### H3\n\nSample Text\n\n```ts\nconsole.log('Hello World')\n```\n\n---\n\n# Hello\n\n- Hello\n- Hi\n- Hey\n- Yo\n\n---\n\nNice to meet you\n"
  },
  {
    "path": "test/fixtures/markdown/multi-entries.md",
    "content": "---\nsrc: sub/page1.md\n---\n\n---\nsrc: /sub/page2.md\nbackground: https://sli.dev/demo-cover.png#2\n---\n\n---\nsrc: ./sub/pages3-4.md\nbackground: https://sli.dev/demo-cover.png#34\n---\n\n---\nsrc: sub/nested1-4.md\nbackground: https://sli.dev/demo-cover.png#14\n---\n\n---\n\n# Inline Page\n\n$x+2$\n"
  },
  {
    "path": "test/fixtures/markdown/sub/nested1-4.md",
    "content": "---\nsrc: /sub/page1.md\n---\n\n---\nsrc: page2.md\n---\n\n---\nsrc: ../sub/pages3-4.md\n---\n"
  },
  {
    "path": "test/fixtures/markdown/sub/page1.md",
    "content": "# Page 1\n"
  },
  {
    "path": "test/fixtures/markdown/sub/page2.md",
    "content": "---\nlayout: cover\n---\n\n# Page 2\n\n<Tweet />\n"
  },
  {
    "path": "test/fixtures/markdown/sub/pages3-4.md",
    "content": "# Page 3\n\n---\nlayout: cover\n---\n\n# Page 4\n\n<Tweet />\n"
  },
  {
    "path": "test/fixtures/snippets/snippet.ts",
    "content": "// line 1\n\n// #region snippet\nfunction _foo() {\n  // ...\n}\n// #endregion snippet\n\n// line 9\n"
  },
  {
    "path": "test/mermaid-renderer.test.ts",
    "content": "import type { ResolvedSlidevOptions } from '@slidev/types'\nimport type { PluginContext } from 'rollup'\nimport { describe, expect, it } from 'vitest'\nimport { templateSetups } from '../packages/slidev/node/virtual/setups'\n\ndescribe('mermaid-renderer virtual module', () => {\n  it('registers /@slidev/setups/mermaid-renderer', () => {\n    const ids = templateSetups.map(t => t.id)\n    expect(ids).toContain('/@slidev/setups/mermaid-renderer')\n  })\n\n  it('generates content with mermaid-renderer glob', () => {\n    const template = templateSetups.find(t => t.id === '/@slidev/setups/mermaid-renderer')!\n    const content = template.getContent.call({} as PluginContext, {\n      userRoot: '/user/project',\n      roots: ['/user/project'],\n    } as ResolvedSlidevOptions)\n\n    expect(content).toContain('mermaid-renderer')\n    expect(content).toContain('export default')\n    expect(content).toContain('filter(Boolean)')\n  })\n\n  it('comes after mermaid in templateSetups', () => {\n    const ids = templateSetups.map(t => t.id)\n    const mermaidIdx = ids.indexOf('/@slidev/setups/mermaid')\n    const rendererIdx = ids.indexOf('/@slidev/setups/mermaid-renderer')\n    expect(mermaidIdx).toBeGreaterThanOrEqual(0)\n    expect(rendererIdx).toBeGreaterThan(mermaidIdx)\n  })\n})\n"
  },
  {
    "path": "test/parser.test.ts",
    "content": "import type { SlidevConfig, SlidevPreparserExtension } from '../packages/types/src'\nimport { basename, relative, resolve } from 'node:path'\nimport { objectMap, slash } from '@antfu/utils'\nimport fg from 'fast-glob'\nimport { describe, expect, it } from 'vitest'\nimport { extractImagesUsage } from '../packages/parser/src/core'\nimport { getDefaultConfig, load, parse, prettify, resolveConfig, stringify } from '../packages/parser/src/fs'\n\nfunction configDiff(v: SlidevConfig) {\n  const defaults = getDefaultConfig()\n  const res: Record<string, any> = {}\n  for (const key of Object.keys(v) as (keyof SlidevConfig)[]) {\n    if (JSON.stringify(v[key]) !== JSON.stringify(defaults[key]))\n      res[key] = v[key]\n  }\n  return res\n}\n\nfunction replaceCRLF(s: string) {\n  return s.replace(/\\r\\n/g, '\\n')\n}\n\ndescribe('md parser', () => {\n  const userRoot = resolve(__dirname, 'fixtures/markdown')\n  const files = fg.sync('*.md', {\n    cwd: userRoot,\n    absolute: true,\n  })\n\n  for (const file of files) {\n    it(basename(file), async () => {\n      const data = await load(userRoot, file)\n\n      expect(stringify(data.entry).trim()).toEqual(replaceCRLF(data.entry.raw.trim()))\n\n      prettify(data.entry)\n\n      // File path tests & convert to relative paths\n      data.markdownFiles = objectMap(data.markdownFiles, (path, md) => {\n        expect(md.filepath).toBe(path)\n        const relativePath = slash(relative(userRoot, path))\n        md.slides.forEach((slide) => {\n          expect(slide.filepath).toBe(path)\n          slide.filepath = relativePath\n        })\n        md.filepath = relativePath\n        return [relativePath, md]\n      })\n\n      expect(data.slides).toMatchSnapshot('slides')\n      expect(configDiff(resolveConfig(data.headmatter, {}))).toMatchSnapshot('config')\n      expect(data.features).toMatchSnapshot('features')\n    })\n  }\n\n  it('parse', async () => {\n    const data = await parse(`\na\n\n---\n\nb\n\n---\nlayout: z\n---\nc\n----\nd\n----\ne\n\n---\n\nf\n\n`, 'file.md')\n    expect(data.slides.map(i => i.content.trim()))\n      .toEqual(Array.from('abcdef'))\n    expect(data.slides[2].frontmatter)\n      .toEqual({ layout: 'z' })\n    expect(data.slides[3].frontmatter)\n      .toEqual({ })\n  })\n\n  it('parse section matter', async () => {\n    const data = await parse(`\na\n\n---\n\nb\n\n---section2\nlayout: z\n---\nc\n----   section 3\nd\n---- section-4\ne\n\n---\n\nf\n\n`, 'file.md')\n    expect(data.slides.map(i => i.content.trim()))\n      .toEqual(Array.from('abcdef'))\n    expect(data.slides[2].frontmatter)\n      .toEqual({ layout: 'z' })\n    expect(data.slides[3].frontmatter)\n      .toEqual({ })\n  })\n\n  async function parseWithExtension(\n    src: string,\n    transformRawLines: (lines: string[]) => void | Promise<void> = () => {},\n    more = {},\n    moreExts: SlidevPreparserExtension[] = [],\n  ) {\n    return await parse(\n      src,\n      'file.md',\n      [{ transformRawLines, ...more }, ...moreExts] as any,\n    )\n  }\n\n  it('parse with-extension replace', async () => {\n    const data = await parseWithExtension(`---\nga: bu\n---\na @@v@@\n\n---\n\nb\n@@v@@\n\n@@v@@ = @@v@@\n`, (lines) => {\n      for (const i in lines)\n        lines[i] = lines[i].replace(/@@v@@/g, 'thing')\n    })\n\n    expect(data.slides.map(i => i.content.trim().replace(/\\n/g, '%')).join('/'))\n      .toEqual('a thing/b%thing%%thing = thing')\n  })\n\n  it('parse with-extension custom-separator', async () => {\n    const data = await parseWithExtension(`---\nga: bu\n---\na @@v@@\n\nSEPARATOR\n\nb\n@@v@@\n\n@@v@@ = @@v@@\n`, (lines) => {\n      for (const i in lines)\n        lines[i] = lines[i].replace(/^SEPARATOR$/g, '---')\n    })\n\n    expect(data.slides.map(i => i.content.trim().replace(/\\n/g, '%')).join('/'))\n      .toEqual('a @@v@@/b%@@v@@%%@@v@@ = @@v@@')\n  })\n\n  it('parse with-extension eg-easy-cover', async () => {\n    function cov(i: string, more = '.jpg') {\n      return `---\nlayout: cover\nbackground: ${i}${more}\n---\n\n`\n    }\n    const data = await parseWithExtension(`@cov 1.jpg\n@cov 2.jpg\n# 2\n---\n\n# 3\n@cov 4.jpg\n`, (lines) => {\n      let i = 0\n      while (i < lines.length) {\n        if (lines[i].startsWith('@cov ')) {\n          const repl = [...cov(lines[i].substring(5), '').split('\\n')]\n          lines.splice(i, 1, ...repl)\n        }\n        i++\n      }\n    })\n\n    expect(data.slides.map(s => s.content.trim().replace(/\\n/g, '%')).join('/'))\n      .toEqual('/# 2/# 3/'.replace(/\\n/g, '%'))\n    expect(data.slides[0].frontmatter)\n      .toEqual({ layout: 'cover', background: '1.jpg' })\n    expect(data.slides[1].frontmatter)\n      .toEqual({ layout: 'cover', background: '2.jpg' })\n    expect(data.slides[3].frontmatter)\n      .toEqual({ layout: 'cover', background: '4.jpg' })\n  })\n\n  it('parse with-extension sequence', async () => {\n    const data = await parseWithExtension(`\na..A\na.a.A.A\n.a.A.\n`, undefined, {}, [{\n      name: 'test',\n      transformRawLines(lines: string[]) {\n        for (const i in lines)\n          lines[i] = lines[i].replace(/A/g, 'B').replace(/a/g, 'A')\n      },\n    }, {\n      name: 'test',\n      transformRawLines(lines: string[]) {\n        for (const i in lines)\n          lines[i] = lines[i].replace(/A/g, 'C')\n      },\n    }])\n    expect(data.slides.map(i => i.content.trim().replace(/\\n/g, '%')).join('/'))\n      .toEqual('C..B%C.C.B.B%.C.B.')\n  })\n\n  // Generate cartesian product of given iterables:\n  function* cartesian(...all: any[]): Generator<any[], void, void> {\n    const [head, ...tail] = all\n    const remainder = tail.length ? cartesian(...tail) : [[]]\n    for (const r of remainder) {\n      for (const h of head)\n        yield [h, ...r]\n    }\n  }\n  const B = [0, 1]\n  const Bs = [B, B, B, B, B, B]\n  const bNames = '_swScCF'\n\n  for (const desc of cartesian(...Bs)) {\n    const [withSlideBefore, withFrontmatter, withSlideAfter, prependContent, appendContent, addFrontmatter] = desc\n    it(`parse with-extension wrap ${desc.map((b, i) => bNames[b * (i + 1)]).join('')}`, async () => {\n      const code = [\n        withSlideBefore\n          ? [\n              '.',\n              '',\n              '---',\n            ]\n          : [],\n        (!withSlideBefore && withFrontmatter)\n          ? [\n              '---',\n            ]\n          : [],\n        withFrontmatter\n          ? [\n              'm: M',\n              'n: N',\n              '---',\n            ]\n          : [],\n        '',\n        'ccc',\n        '@a',\n        '@b',\n        'ddd',\n        '',\n        withSlideAfter\n          ? [\n              '',\n              '---',\n              '',\n              '..',\n            ]\n          : [],\n      ].flat().join('\\n')\n\n      const data = await parseWithExtension(\n        code,\n        undefined,\n        {\n          transformSlide(content: string, frontmatter: any) {\n            const lines = content.split('\\n')\n            let i = 0\n            let appendBeforeCount = 0\n            let appendAfterCount = 0\n            while (i < lines.length) {\n              const l = lines[i]\n              if (l.startsWith('@')) {\n                const t = l.substring(1)\n                lines.splice(i, 1)\n                if (prependContent)\n                  lines.splice(appendBeforeCount++, 0, `<${t}>`)\n                if (appendContent)\n                  lines.splice(lines.length - appendAfterCount++, 0, `</${t}>`)\n                if (addFrontmatter)\n                  frontmatter[`add${t}`] = 'add'\n                i--\n              }\n              i++\n            }\n            return lines.join('\\n')\n          },\n        },\n      )\n\n      function project(s: string) {\n        // like the trim in other tests, the goal is not to test newlines here\n        return s.replace(/%{2,}/g, '%')\n      }\n      expect(project(data.slides.map(i => i.content.replace(/\\n/g, '%')).join('/')))\n        .toEqual(project([\n          ...withSlideBefore ? ['./'] : [],\n          ...prependContent ? ['<a>%<b>%'] : [],\n          'ccc%ddd',\n          ...appendContent ? ['%</b>%</a>'] : [],\n          ...withSlideAfter ? ['/..'] : [],\n        ].join('')))\n\n      if (withFrontmatter || addFrontmatter) {\n        expect(data.slides[withSlideBefore ? 1 : 0].frontmatter)\n          .toEqual({\n            ...withFrontmatter ? { m: 'M', n: 'N' } : {},\n            ...addFrontmatter ? { adda: 'add', addb: 'add' } : {},\n          })\n      }\n    })\n  }\n\n  describe('extractImagesUsage', () => {\n    it('extracts from frontmatter image key', () => {\n      const images = extractImagesUsage('', { image: '/path/to/image.png' })\n      expect(images).toEqual(['/path/to/image.png'])\n    })\n\n    it('extracts from frontmatter backgroundImage key', () => {\n      const images = extractImagesUsage('', { backgroundImage: 'https://example.com/bg.jpg' })\n      expect(images).toEqual(['https://example.com/bg.jpg'])\n    })\n\n    it('extracts from frontmatter background key with image extension', () => {\n      const images = extractImagesUsage('', { background: '/assets/bg.webp' })\n      expect(images).toEqual(['/assets/bg.webp'])\n    })\n\n    it('extracts from frontmatter background key with URL', () => {\n      const images = extractImagesUsage('', { background: 'https://example.com/image.png' })\n      expect(images).toEqual(['https://example.com/image.png'])\n    })\n\n    it('ignores frontmatter background key without image extension or URL', () => {\n      const images = extractImagesUsage('', { background: 'gradient-to-r' })\n      expect(images).toEqual([])\n    })\n\n    it('ignores data URLs in frontmatter', () => {\n      const images = extractImagesUsage('', { image: 'data:image/png;base64,abc123' })\n      expect(images).toEqual([])\n    })\n\n    it('extracts markdown image syntax', () => {\n      const content = '![alt text](/images/photo.jpg)'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/images/photo.jpg'])\n    })\n\n    it('extracts multiple markdown images', () => {\n      const content = `\n![first](/img1.png)\nSome text\n![second](https://example.com/img2.jpg)\n![third](./relative/path.svg)\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/img1.png', 'https://example.com/img2.jpg', './relative/path.svg'])\n    })\n\n    it('ignores data URLs in markdown images', () => {\n      const content = '![inline](data:image/png;base64,abc123)'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual([])\n    })\n\n    it('extracts Vue component src prop', () => {\n      const content = '<img src=\"/assets/logo.png\" />'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/assets/logo.png'])\n    })\n\n    it('extracts Vue component image prop', () => {\n      const content = '<MyImage image=\"/path/to/image.jpg\" />'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/path/to/image.jpg'])\n    })\n\n    it('ignores Vue props without image extensions', () => {\n      const content = '<div src=\"/some/path\" />'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual([])\n    })\n\n    it('ignores Vue props with template expressions', () => {\n      const content = '<img src=\"{{imagePath}}.png\" />'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual([])\n    })\n\n    it('extracts Vue bound props with string literals', () => {\n      const content = `<img :src=\"'/static/image.png'\" />`\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/static/image.png'])\n    })\n\n    it('extracts CSS url()', () => {\n      const content = `\n<div style=\"background: url(/bg/image.png)\">\n<style>\n  .class { background-image: url('/another/image.jpg'); }\n</style>\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/bg/image.png', '/another/image.jpg'])\n    })\n\n    it('ignores CSS url() without image extensions', () => {\n      const content = `<div style=\"background: url(/fonts/font.woff2)\">`\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual([])\n    })\n\n    it('strips code blocks to avoid false positives', () => {\n      const content = `\nSome content\n![real image](/real.png)\n\n\\`\\`\\`markdown\n![fake image](/fake.png)\n<img src=\"/also-fake.jpg\" />\n\\`\\`\\`\n\n![another real](/real2.jpg)\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/real.png', '/real2.jpg'])\n    })\n\n    it('handles multiple sources combined', () => {\n      const content = `\n# Title\n![markdown](/md.png)\n<img src=\"/vue.jpg\" />\n<div style=\"background: url(/css.webp)\">\n      `\n      const frontmatter = {\n        image: '/frontmatter.png',\n        background: 'https://example.com/bg.svg',\n      }\n      const images = extractImagesUsage(content, frontmatter)\n      expect(images).toContain('/frontmatter.png')\n      expect(images).toContain('https://example.com/bg.svg')\n      expect(images).toContain('/md.png')\n      expect(images).toContain('/vue.jpg')\n      expect(images).toContain('/css.webp')\n      expect(images).toHaveLength(5)\n    })\n\n    it('deduplicates identical URLs', () => {\n      const content = `\n![img](/same.png)\n![img2](/same.png)\n<img src=\"/same.png\" />\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/same.png'])\n    })\n\n    it('trims whitespace from extracted URLs', () => {\n      const content = '![img]( /path/with/spaces.png )'\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/path/with/spaces.png'])\n    })\n\n    it('handles various image extensions', () => {\n      const content = `\n![png](/img.png)\n![jpg](/img.jpg)\n![jpeg](/img.jpeg)\n![gif](/img.gif)\n![svg](/img.svg)\n![webp](/img.webp)\n![avif](/img.avif)\n![ico](/img.ico)\n![bmp](/img.bmp)\n![tiff](/img.tiff)\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toHaveLength(10)\n    })\n\n    it('handles case-insensitive image extensions', () => {\n      const content = `\n<img src=\"/image.PNG\" />\n<img src=\"/image.JpG\" />\n<img src=\"/image.WEBP\" />\n      `\n      const images = extractImagesUsage(content, {})\n      expect(images).toEqual(['/image.PNG', '/image.JpG', '/image.WEBP'])\n    })\n  })\n})\n"
  },
  {
    "path": "test/transform-all.test.ts",
    "content": "import { expect, it } from 'vitest'\nimport { getMarkdownTransformers } from '../packages/slidev/node/syntax/transform'\nimport { createTransformContext } from './_tutils'\n\nit('transform-all', async () => {\n  const ctx = createTransformContext(`\n# Page \n\nDefault Slot\n\n<<< @/snippets/snippet.ts#snippet ts {2|3|4}{lines:true}\n\n::right::\n\nFoo \\`{{ code }}\\`\n\n::left::\n<div>Left Slot</div>\n\n\\`\\`\\`md\n<style>\n.text-red { color: red; }\n</style>\n\\`\\`\\`\n\n\\`\\`\\`js [with-title]\nconst a = 1\n\\`\\`\\`\n\n::code-group\n\n\\`\\`\\`sh [npm]\nnpm i slidev\n\\`\\`\\`\n\n\\`\\`\\`sh [yarn]\nyarn add slidev\n\\`\\`\\`\n\n\\`\\`\\`sh [pnpm]\npnpm add slidev\n\\`\\`\\`\n::\n\n<style>\n.text-green { color: green; }\n</style>\n\n<<< ./fixtures/snippets/snippet.ts#snippet\n`)\n\n  const transformers = await getMarkdownTransformers({\n    roots: [],\n    data: {\n      config: {\n        highlighter: 'shiki',\n      },\n      features: {\n        monaco: true,\n        katex: true,\n      },\n    },\n  } as any)\n\n  for (const transformer of transformers) {\n    if (!transformer)\n      continue\n    transformer(ctx)\n    if (!ctx.s.isEmpty())\n      ctx.s.commit()\n  }\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "test/transform-magic-move.test.ts",
    "content": "import { expect, it } from 'vitest'\nimport { transformMagicMove } from '../packages/slidev/node/syntax/transform/magic-move'\nimport { createTransformContext } from './_tutils'\n\nit('basic', async () => {\n  const code = `\n\nSome text before\n\n\\`\\`\\`\\`md magic-move\n\\`\\`\\`ts\n// This is a code block\nconsole.log('Hello, Slidev!')\n\\`\\`\\`\n\\`\\`\\`ts\nlet message = 'Hello, Slidev!'\n\\`\\`\\`\n\\`\\`\\`\\`\n\nSome text after\n  \n`\n\n  const ctx = createTransformContext(code)\n\n  await transformMagicMove(ctx)\n\n  expect(ctx.s.toString())\n    .toMatchInlineSnapshot(`\n      \"\n\n      Some text before\n\n      <ShikiMagicMove v-bind=\"{}\" steps-lz=\"NobwRAxg9gJgpmAXGA9CgBAFQBYEsDO6B6AhutPOgEYA2UEA1gDoB20L+UNcAdHQOYAKAOQAJODToAadAGUaueADcAhMICUYKWGwl82JGABSAMwBa2AGwBWWZgDCDAIrYA8gE0AciVwBZWQCingDMuAAs1gCqAFYACrg0AOo0SmyRAJKxAB78lgCeAO5aYAAuUAxwHEigkFAsJZUlhmhYeITEZBRw1HSMxVAmJvhwTYgADNrQdABOhgDElgCMlgEAHKvFJnUlsiV53EgTYBV5hqYWNnaOLh7efoEh4VFxCcmpEBnZuYUAtGNgAF8pOB2A16oZWP1BsNRgAmYLaE5ncxWWwOZxuLw+fxBUIRGLxJIpNKZHL5Ao/RaA4G1eqNQzsTgHbQDIYjJCwsKTLhQWbIOYAEVWAoCAQAnJttrt9ghxoi4KdkOdUVcMbdsQ88c9CW8SV9yT9YdSQdt6cgeFC2aNgotuTN5gF7AEAGLOsKS+rSg5y44K5EXNHXTF3HGPfEvInvT5k37BY20sGjMACS0wpDBWF23nzdb2MYC/7aLaevbeo5IpUoy7om5Y+64p4E17Ej6k74U91Ak108HIQSp9mIYLWLN8sCC4WisWuj07Uuy8t+ysB1W1kOaxsR3Wt/W/azx0FmsDCAfWyyjh1O13uotS+eHeWK4xVwNquuhrVNyN6mMUywH01ezAcRJCgGR5EUOBVFPdMAHYL35ABBYIACE1nsWcvQXR9/RVGtgw1Btwx1Fto3bH5YIAnskxPFloUHaxbVqe1+UdF03Uw+8fQrZ8V3w9V6zDbVmyjNsDQ2LsEyPTQ6KtJBrEzZjs35IURXFGdbxLGUH19J9lWrIMBI/TcSNE3cKQlSTDyAyFZLTRBrARXTcIMt91yI4Tvx3X9KX+AEAF1tAUFg4E8ABXABbKg4GmfAkBMEgaGGbQqH4eZYTgYIwjCMZBk2NL+RgVZ4DgMU8u0EpsDgCLQpIGrDBYXkYGKGgSBYArSjiqzYFlZMRnQGr8HwEh+G6ABedAxAkaQ5AUZQ1GKXR9EMJQIGCAAvABHex+GdAUoFwIxnQisKjCoJx0gyzb3CUAAHMIsmsdwAA11s8GAIBgFCnCUTaAHEBWsAJijKCoqkQGprKTbgmjswcjimZTx1WRZEMWewqU0udtO4pcwFWjbtt2/bDuO07zsuuBrruh6nte97Pu+36AaBv4qMTQx0BgocEPHVSpw0sBi2xsscOQAmtp2vaDqOk6zouq6bvux6Xrej6vp+/7AYCSl2aPQbhtG7muSUscJzUiUsawnSeIlonpdJuWKcVmmVfp9Wma11mjW66jOe5xYmMRs3+fU51OJxxcnztqWSdl8mFappXadVhmNeZ7WfjjX2OeQMaA8U4Oc1R9HMaFu9I7F/G1sl4mZbJ+XKep5W6bVxnNZZnXOxpKH/bh0ZFicouVMnMOI9F5zxZr+244b52k9d1u089zufn3HOj1osBWXsxYTeH8c2OvcfsMn6vCdj+uncT5uU/d9uM9Z/8N6AkCZog+aA5HU35mQtDVgwlbLiUcVrT0vo7BOTdk5uzbunL2OtKIvxotzWEYpeZzCPhxIBlcz4xzrhAxuLsW6pw9h3TOEke6ASTLZbe9FrQgKnhffB8dCEL2IffOBq9LKBWTLgEK4UooxTiogBKSU4ApQ6nMDKWUcrlSFpIoqJUyomBBlVGq3h6rIEatMZqQU2odRKF1fyQA===\" :title='\"\"' :step-ranges='[[],[]]' />\n\n      Some text after\n        \n      \"\n    `)\n})\n\nit('hyphenated code language', async () => {\n  const code = `\n\nSome text before\n\n\\`\\`\\`\\`md magic-move\n\\`\\`\\`angular-ts\nconsole.log('Hello, Angular!')\n\\`\\`\\`\n\\`\\`\\`angular-ts\nconsole.log('Hello, Angular #2!')\n\\`\\`\\`\n\\`\\`\\`\\`\n\nSome text after\n  \n`\n  const ctx = createTransformContext(code)\n\n  await transformMagicMove(ctx)\n\n  expect(ctx.s.toString())\n    .toMatchInlineSnapshot(`\n      \"\n\n      Some text before\n\n      <ShikiMagicMove v-bind=\"{}\" steps-lz=\"NobwRAxg9gJgpmAXJKA7AzlANnAdFqAcwAoByACTiwIBoACAQVUIFcsBDAJwEJSBKMDTAALdumFIwAcQAy7dgC0A1gBEsAZSUAHAAwB3AFYz06AMKEATAHYdBgOp6ALAEsAcs4CO6qwdQApAA0AMwBWIKCAMRCARUEwABcoJTgMJFAUVHiU+MloDGwEIShw9DgcxB0haAJOSQBiFQAOFQBRFoBOOKC0ePV4gE8cJEqwZP7JWXllNU1dQ2MzSxt7JzdPb19A0PCo6IBaHTAAXxpwPKzMyVw44qDS8qsq7Cha5DqW0xaIiMcunr7BggKkIxhM5IpVBptPojCZzNZbA4XO4vD5/MEwpEYnsAIzHU4ZC7lMAEQg3EplJCNJ41eqNRqmHQqQ5CbqZAFDYGjODjZCTCEzaHzOFLRGrFEbdHbLH7Cz4s49bKSYjku6UxA4nE0l71JqtDrfP7sgackagvng6ZQuawxYIlbI9ZoraY3Z7ADM8sJSuQpFV9yQOIs2teYHen2+v1Z/xNQLNPLBU0hsxhC3hyyRa1RmwxO2xvxOCsyPrAlGoUHoTFYHB4/vVOPdIfqDHdACEWgyjb1Y8MQQmLUnBTa06KHVnJS68/sQl7ziW/UUKeULNSULS3h8vj8uxy433edJLcmhbb02LHdmpa7sQA2WeKy7IASLtXL9pNt56trtQ3R42A3tuQPfkrRTYU7QzcUnRzaU3Sse9i0fMAAB1UDrcp3XjYCjyHVMRXtTMJWdXMZT2RpjgAXSELBnFQOBXBYABbAAjOBOHQJAgnYLBSiEZiyTeCw4HdRxHB0cIugEsMYEaeA4HaCShHiYQ4EY+j2DUyRUBeGA4g4ZhJHYZg2C4PZ4g4wsUHgXI0EwHB8CIMgy1oRhjJrOg6gsXhnxEMQJGQABVAAPcgAkYm9UACgItBxAB9GAABUAHkWnIKxWywQg9FMdB2AAaQATQgHF2kaYTYrgEIAgiVx3FbAK4kSZJUkQdI5yQvI7MKMBbgDLlqh1T9mm/Hcey5c0wGC0Lwsi6K4sSlK0oyrKcvyoqSrK90Kqqmq6oCg4EKJK50KQR410GsNN0jUaAPG/tJpCsKIqimL4uS1L0sy7LcsK4rSvKyrqtq5x6txQ6S1JE7EFXAbQzqelGWZG7TX3SQpqe2bXoWj7lu+ta/s27agb2vY5Us9riRVF8+s1D8wy/A0ImRvcgLRx6Zpe+b3qWr7Vt+jaAZ24HQc9cmH2JBceqXQNg3OuGru3P9u1urC2em565rexbPpWn71v+rbAd2kH9oLAkKckZyK1c6suA8ryoYbOm6hbdtOyV3dAIm9GOc17Ged1/GBcNoWSZnMXEIlqH3S1OX6gVqMwDZZWUdZwL2Y1rHuZ1vH+YNonjdBu8I6Op9o9l2HdWGxnma9+6fczrntdxvn9cJo3hf2+CS5LVDo8bNOHvVzGm5x3m9YJwXiZNsjKOo2j6KY1j2M47jeLAfj6iEkSxMUpOpLqGS5IUoJGpUtTXA0oEwG0zhdOooypMfkzODMiyKKAA\" :title='\"\"' :step-ranges='[[],[]]' />\n\n      Some text after\n        \n      \"\n    `)\n})\n"
  },
  {
    "path": "test/transform.test.ts",
    "content": "import { expect, it } from 'vitest'\nimport { transformCodeWrapper } from '../packages/slidev/node/syntax/transform/code-wrapper'\nimport { transformPageCSS } from '../packages/slidev/node/syntax/transform/in-page-css'\nimport { transformMermaid } from '../packages/slidev/node/syntax/transform/mermaid'\nimport { transformPlantUml } from '../packages/slidev/node/syntax/transform/plant-uml'\nimport { transformSlotSugar } from '../packages/slidev/node/syntax/transform/slot-sugar'\nimport { transformSnippet } from '../packages/slidev/node/syntax/transform/snippet'\nimport { createTransformContext } from './_tutils'\n\nit('slot-sugar', () => {\n  const ctx = createTransformContext(`\n# Page \n\nDefault Slot\n::right::\nRight Slot\n::left::\n<div>Left Slot</div>\n`)\n\n  transformCodeWrapper(ctx)\n  ctx.s.commit()\n  transformSlotSugar(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('slot-sugar with default', () => {\n  const ctx = createTransformContext(`\n:: right::\nRight Slot\n::left ::\n<div>Left Slot</div>\n:: default ::\n# Page \nDefault Slot\n`)\n\n  transformSlotSugar(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('slot-sugar with code', () => {\n  const ctx = createTransformContext(`\n# Page \n\nDefault Slot\n\n::code::\n\n\\`\\`\\`md\nSlot Usage\n::right::\n::left::\n\\`\\`\\`\n\n`)\n\n  transformCodeWrapper(ctx)\n  ctx.s.commit()\n  transformSlotSugar(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('slot-sugar with symbols in name', () => {\n  const ctx = createTransformContext(`\n# Page \n\nDefault Slot\n::slot::1::\nFirst Slot\n::slot.2::\nSecond Slot\n`)\n\n  transformSlotSugar(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('inline CSS', () => {\n  const ctx = createTransformContext(`\n# Page \n\n<style>\nh1 {\n  color: red;\n}\n</style>\n\n\\`\\`\\`css\n<style>\nh1 {\n  color: green;\n}\n</style>\n\\`\\`\\`\n`)\n\n  transformCodeWrapper(ctx)\n  ctx.s.commit()\n  transformPageCSS(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('mermaid', () => {\n  const ctx = createTransformContext(`\n# Page \n\n\\`\\`\\`mermaid\nsequenceDiagram\n  Alice->John: Hello John, how are you?\n  Note over Alice,John: A typical interaction\n\\`\\`\\`\n\n\\`\\`\\`mermaid {theme: 'neutral', scale: 0.8}\ngraph TD\nB[Text] --> C{Decision}\nC -->|One| D[Result 1]\nC -->|Two| E[Result 2]\n\\`\\`\\`\n`)\n\n  transformMermaid(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('plantUML', () => {\n  const ctx = createTransformContext(`\n# Page\n\n\\`\\`\\`plantuml\n@startuml\nAlice -> Bob : Hello\nAlice <- Bob : Hello, too!\n@enduml\n\\`\\`\\`\n\n\\`\\`\\`plantuml {scale: 0.5}\n@startmindmap\n* Debian\n** Ubuntu\n*** Linux Mint\n*** Kubuntu\n*** Lubuntu\n*** KDE Neon\n** LMDE\n** SolydXK\n** SteamOS\n** Raspbian with a very long name\n*** <s>Raspmbc</s> => OSMC\n*** <s>Raspyfi</s> => Volumio\n@endmindmap\n\\`\\`\\`\n`)\n\n  transformPlantUml(ctx)\n\n  expect(ctx.s.toString()).toContain(`<PlantUml :code=\"'JOzD`)\n\n  // TODO: not so sure on this,\n  // it seems the encode result of `plantuml-encoder` is different across platforms since Node 18\n  // we may need to find a better way to test this\n  // expect(result).toMatchSnapshot()\n})\n\nit('external snippet', () => {\n  const ctx = createTransformContext(`\n<<< @/snippets/snippet.ts#snippet ts {2|3|4}{lines:true}\n`)\n\n  transformSnippet(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('code-wrapper with square brackets in code', () => {\n  const ctx = createTransformContext(`\n\\`\\`\\`csharp\n[Flags]\nenum MyEnum {}\n\\`\\`\\`\n`)\n\n  transformCodeWrapper(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('code-wrapper with title and square brackets in code', () => {\n  const ctx = createTransformContext(`\n\\`\\`\\`csharp [MyEnum.cs]\n[Flags]\nenum MyEnum {}\n\\`\\`\\`\n`)\n\n  transformCodeWrapper(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n\nit('code-wrapper with twoslash', () => {\n  const ctx = createTransformContext(`\n\n\n\\`\\`\\`ts [filename.ts]\nconst a = 1\n\\`\\`\\`\n\n\\`\\`\\`ts twoslash [filename.ts]\nconst a = 1\n\\`\\`\\`\n\n`)\n\n  transformCodeWrapper(ctx)\n\n  expect(ctx.s.toString()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "test/utils.test.ts",
    "content": "import type { ResolvedFontOptions, SlideInfo } from '@slidev/types'\nimport { relative, resolve } from 'node:path'\nimport { slash } from '@antfu/utils'\nimport MarkdownIt from 'markdown-it'\nimport { describe, expect, it } from 'vitest'\nimport YAML from 'yaml'\nimport { parseAspectRatio, parseRangeString } from '../packages/parser/src'\nimport { getRoots } from '../packages/slidev/node/resolver'\nimport { generateCoollabsFontsUrl, generateGoogleFontsUrl, stringifyMarkdownTokens, updateFrontmatterPatch } from '../packages/slidev/node/utils'\n\ndescribe('utils', () => {\n  it('page-range', () => {\n    expect(parseRangeString(5)).toEqual([1, 2, 3, 4, 5])\n    expect(parseRangeString(5, 'all')).toEqual([1, 2, 3, 4, 5])\n    expect(parseRangeString(2, '*')).toEqual([1, 2])\n    expect(parseRangeString(5, '1')).toEqual([1])\n    expect(parseRangeString(10, '1,2-3,5')).toEqual([1, 2, 3, 5])\n    expect(parseRangeString(10, '1;2-3;5')).toEqual([1, 2, 3, 5])\n    expect(parseRangeString(10, '6-')).toEqual([6, 7, 8, 9, 10])\n  })\n\n  it('aspect-ratio', () => {\n    expect(parseAspectRatio(1)).toEqual(1)\n    expect(parseAspectRatio('16/9')).toEqual(16 / 9)\n    expect(parseAspectRatio('16 / 9 ')).toEqual(16 / 9)\n    expect(parseAspectRatio('3:4')).toEqual(3 / 4)\n    expect(parseAspectRatio('1x1')).toEqual(1)\n    expect(parseAspectRatio('1')).toEqual(1)\n\n    expect(() => parseAspectRatio('hello')).toThrow()\n    expect(() => parseAspectRatio('1/0')).toThrow()\n  })\n\n  it('stringify-markdown-tokens', () => {\n    const md = MarkdownIt({ html: true })\n    const stringify = (src: string) => stringifyMarkdownTokens(md.parseInline(src, {}))\n\n    expect(stringify('<span style=\"color:red\">Composable</span> Vue')).toBe('Composable Vue')\n    expect(stringify('<b>Whatever</b>')).toBe('Whatever')\n    expect(stringify('Talk about `<details/>`')).toBe('Talk about <details/>')\n    expect(stringify('What is Readonly\\\\<T\\\\> in TypeScript')).toBe('What is Readonly<T> in TypeScript')\n    expect(stringify('Welcome to<br />*Slidev*')).toBe('Welcome to Slidev')\n  })\n\n  it('google-fonts', () => {\n    expect(\n      generateGoogleFontsUrl({\n        webfonts: ['Fira Code', 'PT Serif'],\n        weights: ['200', '400', '600'],\n        provider: 'google',\n      } as ResolvedFontOptions),\n    ).toMatchSnapshot()\n\n    expect(\n      generateGoogleFontsUrl({\n        webfonts: ['Fira Code', 'PT Serif'],\n        weights: ['200', '400', '600'],\n        italic: true,\n        provider: 'google',\n      } as ResolvedFontOptions),\n    ).toMatchSnapshot()\n  })\n\n  it('coollabs-fonts', () => {\n    expect(\n      generateCoollabsFontsUrl({\n        webfonts: ['Fira Code', 'PT Serif'],\n        weights: ['200', '400', '600'],\n        provider: 'google',\n      } as ResolvedFontOptions),\n    ).toMatchSnapshot()\n\n    expect(\n      generateCoollabsFontsUrl({\n        webfonts: ['Fira Code', 'PT Serif'],\n        weights: ['200', '400', '600'],\n        italic: true,\n        provider: 'google',\n      } as ResolvedFontOptions),\n    ).toMatchSnapshot()\n  })\n\n  it('roots', async () => {\n    const { cliRoot, clientRoot, userRoot, userWorkspaceRoot } = await getRoots(resolve('slides.md'))\n    const expectRelative = (v: string) => expect(slash(relative(__dirname, v)))\n    expectRelative(cliRoot).toMatchInlineSnapshot(`\"../packages/slidev\"`)\n    expectRelative(clientRoot).toMatchInlineSnapshot(`\"../packages/client\"`)\n    expectRelative(userRoot).toMatchInlineSnapshot(`\"..\"`)\n    expectRelative(userWorkspaceRoot).toMatchInlineSnapshot(`\"..\"`)\n  })\n\n  it('update frontmatter patch', async () => {\n    const dragPos = {\n      foo: '1,2,3,4',\n    }\n    function createFakeSource(yaml: string) {\n      const doc = YAML.parseDocument(yaml)\n      return {\n        frontmatter: {},\n        source: {\n          frontmatter: doc.toJSON() || {},\n          frontmatterRaw: yaml,\n          frontmatterDoc: doc,\n        },\n      } as SlideInfo\n    }\n    function expectFrontmatter(slide: SlideInfo) {\n      return expect(slide.source.frontmatterDoc?.toString())\n    }\n\n    const slide1 = createFakeSource(``)\n    updateFrontmatterPatch(slide1.source, { dragPos })\n    expectFrontmatter(slide1).toMatchInlineSnapshot(`\n      \"dragPos:\n        foo: 1,2,3,4\n      \"\n    `)\n\n    const slide2 = createFakeSource(`\n      # comment\n      title: Hello  # another comment\n      dragPos:\n        bar: 5,6,7,8\n    `)\n    updateFrontmatterPatch(slide2.source, { dragPos })\n    expectFrontmatter(slide2).toMatchInlineSnapshot(`\n      \"# comment\n      title: Hello # another comment\n      dragPos:\n        foo: 1,2,3,4\n      \"\n    `)\n\n    // remove a field\n    const slide3 = createFakeSource(`\n      # comment\n      title: Hello  # another comment\n      dragPos:\n        bar: 5,6,7,8\n    `)\n    updateFrontmatterPatch(slide3.source, { title: null })\n    expectFrontmatter(slide3).toMatchInlineSnapshot(`\n      \"dragPos:\n        bar: 5,6,7,8\n      \"\n    `)\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"jsx\": \"preserve\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"baseUrl\": \".\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"paths\": {\n      \"@slidev/client\": [\"./packages/client/index.ts\"],\n      \"@slidev/client/*\": [\"./packages/client/*\"],\n      \"@slidev/types\": [\"./packages/types/src/index.ts\"],\n      \"@slidev/parser/fs\": [\"./packages/parser/src/fs.ts\"],\n      \"@slidev/parser/core\": [\"./packages/parser/src/core.ts\"],\n      \"@slidev/parser/utils\": [\"./packages/parser/src/utils.ts\"],\n      \"@slidev/parser\": [\"./packages/parser/src/index.ts\"]\n    },\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"vite\",\n      \"vite/client\",\n      \"node\",\n      \"vite-plugin-vue-server-ref/client\"\n    ],\n    \"allowImportingTsExtensions\": true,\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"noUnusedLocals\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\n    \"./*.ts\",\n    \"./docs/**/*.ts\",\n    \"./docs/**/*.vue\",\n    \"./packages/**/*.ts\",\n    \"./packages/**/*.vue\",\n    \"./demo/**/*.ts\",\n    \"./demo/**/*.vue\",\n    \"./test/**/*.ts\"\n  ],\n  \"exclude\": [\"**/dist/**\", \"**/node_modules/**\"]\n}\n"
  },
  {
    "path": "tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  format: [\n    'esm',\n  ],\n  target: 'node18',\n  dts: true,\n  clean: true,\n  shims: false,\n  external: [\n    /@slidev/,\n  ],\n})\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    projects: ['packages/*', 'test'],\n  },\n})\n"
  }
]