[
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n\t\"$schema\": \"https://unpkg.com/@changesets/config@3.0.3/schema.json\",\n\t\"changelog\": [\"@svitejs/changesets-changelog-github-compact\", { \"repo\": \"barvian/number-flow\" }],\n\t\"commit\": false,\n\t\"fixed\": [],\n\t\"linked\": [],\n\t\"access\": \"public\",\n\t\"baseBranch\": \"main\",\n\t\"updateInternalDependencies\": \"patch\",\n\t\"ignore\": [\"!(@number-flow/*|number-flow)\"]\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [barvian]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/0-bug.yml",
    "content": "name: '🐛 Report a bug'\ndescription: Report a reproducible bug or regression.\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for reporting :wave:.  Before filing, please [search](https://github.com/barvian/number-flow/issues?q=is%3Aissue) open & closed issues to see if a similar one exists.\n\n  # - type: input\n  #   id: numberflow-version\n  #   attributes:\n  #     label: NumberFlow version\n  #     description: |\n  #       - Please update to the [latest version](https://github.com/barvian/number-flow/releases) before filing to see if your bug has already been fixed.\n  #     placeholder: |\n  #       @number-flow/react@x.x.x\n  #   validations:\n  #     required: true\n\n  # - type: input\n  #   id: framework-library-version\n  #   attributes:\n  #     label: Framework version\n  #     # description: Which framework (and version) are you using?\n  #     placeholder: |\n  #       react@x.x.x\n  #   validations:\n  #     required: false\n\n  - type: input\n    id: link\n    attributes:\n      label: Minimal reproduction\n      description: |\n        - Links to starter sandboxes can be found in the [site](https://number-flow.barvian.me/) header\n        - Tips for creating minimal examples: https://stackoverflow.com/help/mcve\n      placeholder: |\n        https://codesandbox.io/p/...\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the bug and the steps to reproduce it\n      placeholder: |\n        You can drag screenshots or videos into this editor ↓\n    validations:\n      required: true\n\n  # - type: textarea\n  #   id: screenshots_or_videos\n  #   attributes:\n  #     label: Screenshots or videos\n  #     description: |\n  #       For more information on the supported file image/file types and the file size limits, see [GitHub docs](https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files).\n  #     placeholder: |\n  #       You can drag your video or image files inside of this editor ↓\n\n  # - type: dropdown\n  #   attributes:\n  #     options:\n  #       - No, because I do not know how\n  #       - No, because I do not have time to dig into it\n  #       - Maybe, I'll investigate and start debugging\n  #       - Yes, I think I know how to fix it and will discuss it in the comments of this issue\n  #       - Yes, I am also opening a PR that solves the problem along side this issue\n  #     label: Do you intend to try to help solve this bug with your own PR?\n  #     description: |\n  #       If you think you know the cause of the problem, the fastest way to get it fixed is to suggest a fix, or fix it yourself! However, it is ok if you cannot solve this yourself and are just wanting help.\n  # - type: checkboxes\n  #   id: agrees-to-terms\n  #   attributes:\n  #     label: Terms & Code of Conduct\n  #     description: By submitting this issue, you agree to follow our Code of Conduct and can verify that you have followed the requirements outlined above to the best of your ability.\n  #     options:\n  #       - label: I agree to follow this project's Code of Conduct\n  #         required: true\n  #       - label: I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.\n  #         required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-docs.yml",
    "content": "name: \"📖 Docs issue\"\ndescription: \"Report a typo or other issue on the docs.\"\ntitle: \"[Docs]: \"\n# labels: [\"type: documentation\"]\nbody:\n  - type: textarea\n    attributes:\n      label: Summary\n      description: |\n        A clear and concise summary of the issue.\n      placeholder: |\n        Example:\n        The Motion for React example isn't linked to the correct page.\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Affected page\n      # description: |\n      #   What page does this concern?\n      placeholder: |\n        https://number-flow.barvian.me\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: '💬 Get help'\n    url: https://github.com/barvian/number-flow/discussions/new?category=help\n    about: If you can't get something to work the way you'd like, open a question in the discussion forums.\n  - name: '💡 Request a feature'\n    url: https://github.com/barvian/number-flow/discussions/new?category=ideas\n    about: 'Suggest any ideas you have using the discussion forums.'\n  # - name: Bug Report\n  #   url: https://github.com/barvian/number-flow/issues/new\n  #   about: If something is clearly broken or not working as documented, create a bug report.\n  # - name: Documentation Issue\n  #   url: https://github.com/tailwindlabs/tailwindcss.com\n  #   about: 'For documentation issues, suggest changes on our documentation repository.'\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\nenv:\n  # we call `pnpm playwright install` instead\n  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'\n\n# cancel in-progress runs on new commits to same PR (gitub.event.number)\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read # to fetch code (actions/checkout)\n\njobs:\n  test:\n    timeout-minutes: 60\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v4.0.0\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 18.x\n\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm playwright install --with-deps\n      - run: pnpm build:packages\n      - run: pnpm test || exit 1\n\n      - uses: actions/upload-artifact@v4\n        if: ${{ !cancelled() }}\n        with:\n          name: playwright-report\n          path: '**/playwright-report/'\n          retention-days: 30\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  release:\n    # prevents this action from running on forks\n    if: github.repository == 'barvian/number-flow'\n    name: Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v4\n        with:\n          # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits\n          fetch-depth: 0\n\n      - uses: pnpm/action-setup@v4.0.0\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n\n      - run: pnpm install --frozen-lockfile\n\n      - name: Create Release Pull Request or Publish to npm\n        id: changesets\n        uses: changesets/action@v1\n        with:\n          publish: pnpm release\n          version: pnpm run version\n          commit: Version packages\n        env:\n          GITHUB_TOKEN: ${{ secrets.CHANGESET_GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n.turbo\ndist/\n.env\nbuild/\n.astro/\n.env.*\n!.env.example\nvite.config.js.timestamp-*\nvite.config.ts.timestamp-*\n.vercel\n"
  },
  {
    "path": ".npmrc",
    "content": "link-workspace-packages = true"
  },
  {
    "path": ".prettierignore",
    "content": "pnpm-lock.yaml\n\n**/*.mdx"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2024 Maxwell Barvian\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": "https://github.com/user-attachments/assets/fb49ac50-039e-41e6-a19b-64e74ebb5930\n\n# NumberFlow\n\nAn animated number component for React, Vue, Svelte, and TS/JS.\n\n[![NPM Version](https://img.shields.io/npm/v/number-flow.svg)](https://npmjs.com/package/number-flow)\n[![Follow @mbarvian](https://img.shields.io/twitter/follow/mbarvian.svg?style=social&label=Follow)](https://x.com/mbarvian)\n\n## Documentation\n\nFor full documentation, visit [number-flow.barvian.me](https://number-flow.barvian.me).\n\n## You may also like\n\n* [TextMorph](https://github.com/lochie/torph) - An animated text component by [Lochie Axon](https://x.com/lochieaxon).\n"
  },
  {
    "path": "lib/playwright.ts",
    "content": "import { defineConfig, devices } from '@playwright/test'\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// import dotenv from 'dotenv';\n// import path from 'path';\n// dotenv.config({ path: path.resolve(__dirname, '.env') });\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport const config = defineConfig({\n\ttestDir: './tests',\n\t/* Run tests in files in parallel */\n\tfullyParallel: true,\n\t/* Fail the build on CI if you accidentally left test.only in the source code. */\n\tforbidOnly: !!process.env.CI,\n\t/* Retry on CI only */\n\tretries: process.env.CI ? 2 : 0,\n\t/* Opt out of parallel tests on CI. */\n\tworkers: process.env.CI ? 1 : undefined,\n\t/* Reporter to use. See https://playwright.dev/docs/test-reporters */\n\treporter: 'html',\n\t/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n\tuse: {\n\t\t/* Base URL to use in actions like `await page.goto('/')`. */\n\t\tbaseURL: 'http://localhost:3039',\n\n\t\t/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n\t\ttrace: 'on-first-retry'\n\t},\n\n\t/* Configure projects for major browsers */\n\tprojects: [\n\t\t{\n\t\t\tname: 'chromium',\n\t\t\tuse: { ...devices['Desktop Chrome'] }\n\t\t},\n\t\t{\n\t\t\tname: 'chromium-no-js',\n\t\t\tuse: { ...devices['Desktop Chrome'], javaScriptEnabled: false }\n\t\t},\n\t\t{\n\t\t\tname: 'chromium-reduced-motion',\n\t\t\tuse: { ...devices['Desktop Chrome'], contextOptions: { reducedMotion: 'reduce' } }\n\t\t},\n\n\t\t{\n\t\t\tname: 'firefox',\n\t\t\tuse: { ...devices['Desktop Firefox'] }\n\t\t},\n\n\t\t{\n\t\t\tname: 'webkit',\n\t\t\tuse: { ...devices['Desktop Safari'] }\n\t\t}\n\n\t\t/* Test against mobile viewports. */\n\t\t// {\n\t\t//   name: 'Mobile Chrome',\n\t\t//   use: { ...devices['Pixel 5'] },\n\t\t// },\n\t\t// {\n\t\t//   name: 'Mobile Safari',\n\t\t//   use: { ...devices['iPhone 12'] },\n\t\t// },\n\n\t\t/* Test against branded browsers. */\n\t\t// {\n\t\t//   name: 'Microsoft Edge',\n\t\t//   use: { ...devices['Desktop Edge'], channel: 'msedge' },\n\t\t// },\n\t\t// {\n\t\t//   name: 'Google Chrome',\n\t\t//   use: { ...devices['Desktop Chrome'], channel: 'chrome' },\n\t\t// },\n\t],\n\n\t/* Build and run production server before starting tests */\n\twebServer: {\n\t\tcommand: 'pnpm build && pnpm start',\n\t\turl: 'http://localhost:3039',\n\t\tcwd: '.',\n\t\treuseExistingServer: !process.env.CI\n\t}\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"pnpm\": {\n\t\t\"overrides\": {\n\t\t\t\"@types/react\": \"^19.2.14\",\n\t\t\t\"@types/react-dom\": \"^19.2.3\"\n\t\t}\n\t},\n\t\"devDependencies\": {\n\t\t\"@changesets/cli\": \"^2.27.9\",\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@svitejs/changesets-changelog-github-compact\": \"^1.2.0\",\n\t\t\"playwright\": \"^1.48.0\",\n\t\t\"prettier\": \"^3.3.3\",\n\t\t\"prettier-plugin-astro\": \"^0.14.0\",\n\t\t\"prettier-plugin-svelte\": \"^3.2.7\",\n\t\t\"prettier-plugin-tailwindcss\": \"^0.6.5\",\n\t\t\"typescript\": \"^5.6.2\"\n\t},\n\t\"scripts\": {\n\t\t\"build\": \"pnpm -r --filter=\\\"!./packages/**/test/**\\\" build\",\n\t\t\"build:packages\": \"pnpm -r --filter=\\\"./packages/*\\\" build\",\n\t\t\"test\": \"pnpm -r --workspace-concurrency=1 test\",\n\t\t\"format\": \"prettier --write .\",\n\t\t\"version\": \"changeset version && git add --all\",\n\t\t\"release\": \"pnpm build:packages && changeset publish\"\n\t},\n\t\"packageManager\": \"pnpm@9.12.1\",\n\t\"engines\": {\n\t\t\"pnpm\": \"^9.0.0\"\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/CHANGELOG.md",
    "content": "# number-flow\n\n## 0.6.0\n\n### Minor Changes\n\n- Remove `--number-flow-char-height` CSS property in favor of `line-height` ([`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6))\n\n## 0.5.12\n\n### Patch Changes\n\n- Only animate when ownerDocument is visible (see [#165](https://github.com/barvian/number-flow/issues/165)) ([#173](https://github.com/barvian/number-flow/pull/173))\n\n## 0.5.11\n\n### Patch Changes\n\n- Add CSP strategies (see [#170](https://github.com/barvian/number-flow/issues/170)) ([`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67))\n\n## 0.5.10\n\n### Patch Changes\n\n- Fix Safari text alignment (see [#84](https://github.com/barvian/number-flow/issues/84)) ([`4a6c26e`](https://github.com/barvian/number-flow/commit/4a6c26efe13d6ffc1b84ea75accf511f63669eb9))\n\n## 0.5.9\n\n### Patch Changes\n\n- Fix Safari visual bug (see [#147](https://github.com/barvian/number-flow/issues/147)) ([`bdf8ce9`](https://github.com/barvian/number-flow/commit/bdf8ce92df67d6147d7f56998c625fc29e1b7571))\n\n## 0.5.8\n\n### Patch Changes\n\n- Fix \"custom element already defined\" bugs ([`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d))\n\n## 0.5.7\n\n### Patch Changes\n\n- Attempted fix for old versions of Safari (see [#131](https://github.com/barvian/number-flow/issues/131)) ([`ee6a0a2`](https://github.com/barvian/number-flow/commit/ee6a0a2f2f09ba187b8df24cdfe0992ee7883192))\n\n## 0.5.6\n\n### Patch Changes\n\n- Fix errors in browsers that don't support attachInternals (see [#127](https://github.com/barvian/number-flow/issues/127)) ([`2539c4b`](https://github.com/barvian/number-flow/commit/2539c4b653fd4aaa17ef6b2ffd77b7a41454da08))\n\n## 0.5.5\n\n### Patch Changes\n\n- Expose value on custom element ([`bc6476f`](https://github.com/barvian/number-flow/commit/bc6476f910ad58625491c23ed0a8768217f9ab57))\n\n## 0.5.4\n\n### Patch Changes\n\n- Release vanilla JS version ([`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d))\n\n## 0.5.3\n\n### Patch Changes\n\n- Revert mask-image change due to <1em char heights ([`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d))\n\n## 0.5.2\n\n### Patch Changes\n\n- Improve `::selection` display and accessibility during transitions ([`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393))\n\n## 0.5.1\n\n### Patch Changes\n\n- Add missing symbol part to SSR ([`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948))\n\n## 0.5.0\n\n### Minor Changes\n\n- Move `continuous` prop into importable plugin ([`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff))\n\n## 0.4.2\n\n### Patch Changes\n\n- Add symbol part for styling all symbols ([`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb))\n\n## 0.4.1\n\n### Patch Changes\n\n- Reduce bundle size ([`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3))\n\n## 0.4.0\n\n### Minor Changes\n\n- More flexible trend prop ([`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0))\n\n### Patch Changes\n\n- Add digits prop ([`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94))\n\n- Fix cursor and improve text selection ([`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809))\n\n## 0.3.10\n\n### Patch Changes\n\n- Switch to TS private properties to reduce bundle size ([`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd))\n\n## 0.3.9\n\n### Patch Changes\n\n- Expose parts for styling support ([`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4))\n\n## 0.3.8\n\n### Patch Changes\n\n- Minor performance optimizations ([`9854f77`](https://github.com/barvian/number-flow/commit/9854f77e11561fe119bf9009ae1369389a64ba15))\n\n- Add prefix & suffix props ([`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14))\n\n## 0.3.7\n\n### Patch Changes\n\n- Expose `created` property ([`0fac2f6`](https://github.com/barvian/number-flow/commit/0fac2f69b239048054755c556afc3f0eb65767c9))\n\n## 0.3.6\n\n### Patch Changes\n\n- More defensive checks on browser globals (see [#58](https://github.com/barvian/number-flow/issues/58)) ([#59](https://github.com/barvian/number-flow/pull/59))\n\n## 0.3.5\n\n### Patch Changes\n\n- Rename number-flow element to avoid conflicts between wrappers ([`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac))\n\n## 0.3.4\n\n### Patch Changes\n\n- automatically disable animation when hidden (see [#9](https://github.com/barvian/number-flow/issues/9)) ([`ff966f4`](https://github.com/barvian/number-flow/commit/ff966f489eaeeacc72b35a8ee4c8cc13fe894eb6))\n\n## 0.3.3\n\n### Patch Changes\n\n- attempted fix for #45 ([`be3f7da`](https://github.com/barvian/number-flow/commit/be3f7da7ee88b6ab35f67736c98edcfb6909543d))\n"
  },
  {
    "path": "packages/number-flow/README.md",
    "content": "[![NumberFlow](https://number-flow.barvian.me/preview.webp)](https://number-flow.barvian.me/vanilla)\n\n# NumberFlow\n\nAn animated number component.\n\n[![NPM Version](https://img.shields.io/npm/v/number-flow.svg)](https://npmjs.com/package/number-flow)\n[![Bundle size](https://badgen.net/bundlephobia/minzip/number-flow@latest)](https://bundlephobia.com/package/number-flow@latest)\n[![Follow @mbarvian](https://img.shields.io/twitter/follow/mbarvian.svg?style=social&label=Follow)](https://x.com/mbarvian)\n\n## Documentation\n\nFor full documentation, visit [number-flow.barvian.me/vanilla](https://number-flow.barvian.me/vanilla).\n"
  },
  {
    "path": "packages/number-flow/package.json",
    "content": "{\n\t\"name\": \"number-flow\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"version\": \"0.6.0\",\n\t\"author\": {\n\t\t\"name\": \"Maxwell Barvian\",\n\t\t\"email\": \"max@barvian.me\",\n\t\t\"url\": \"https://barvian.me\"\n\t},\n\t\"description\": \"A component to transition and format numbers.\",\n\t\"license\": \"MIT\",\n\t\"homepage\": \"https://number-flow.barvian.me/vanilla\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/barvian/number-flow\",\n\t\t\"directory\": \"src\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/barvian/number-flow/issues\"\n\t},\n\t\"keywords\": [\n\t\t\"accessible\",\n\t\t\"odometer\",\n\t\t\"animation\",\n\t\t\"number-format\",\n\t\t\"number-animation\",\n\t\t\"animated-number\"\n\t],\n\t\"files\": [\n\t\t\"dist\",\n\t\t\"README.md\"\n\t],\n\t\"main\": \"./dist/index.js\",\n\t\"module\": \"./dist/index.mjs\",\n\t\"types\": \"./dist/index.d.ts\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/index.d.ts\",\n\t\t\t\"import\": \"./dist/index.mjs\",\n\t\t\t\"require\": \"./dist/index.js\"\n\t\t},\n\t\t\"./lite\": {\n\t\t\t\"types\": \"./dist/lite.d.ts\",\n\t\t\t\"import\": \"./dist/lite.mjs\",\n\t\t\t\"require\": \"./dist/lite.js\"\n\t\t},\n\t\t\"./csp\": {\n\t\t\t\"types\": \"./dist/csp.d.ts\",\n\t\t\t\"import\": \"./dist/csp.mjs\",\n\t\t\t\"require\": \"./dist/csp.js\"\n\t\t},\n\t\t\"./group\": {\n\t\t\t\"types\": \"./dist/group.d.ts\",\n\t\t\t\"import\": \"./dist/group.mjs\",\n\t\t\t\"require\": \"./dist/group.js\"\n\t\t},\n\t\t\"./plugins\": {\n\t\t\t\"types\": \"./dist/plugins/index.d.ts\",\n\t\t\t\"import\": \"./dist/plugins.mjs\",\n\t\t\t\"require\": \"./dist/plugins.js\"\n\t\t}\n\t},\n\t\"scripts\": {\n\t\t\"build\": \"vite build --mode production\",\n\t\t\"dev\": \"vite build --mode development --watch\",\n\t\t\"test\": \"pnpm -r --workspace-concurrency 1 --filter=\\\"./test/apps/*\\\" test\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@rollup/plugin-typescript\": \"^12.1.0\",\n\t\t\"@testing-library/dom\": \"^10.4.0\",\n\t\t\"@vitest/browser\": \"^2.1.2\",\n\t\t\"babel-plugin-styled-components\": \"^2.1.4\",\n\t\t\"magic-string\": \"^0.30.11\",\n\t\t\"parse-literals\": \"^1.2.1\",\n\t\t\"playwright\": \"^1.48.0\",\n\t\t\"rollup-plugin-minify-html-literals-v3\": \"^1.3.4\",\n\t\t\"tslib\": \"^2.7.0\",\n\t\t\"typescript\": \"^5.6.2\",\n\t\t\"vite\": \"^5.4.3\",\n\t\t\"vitest\": \"^2.1.2\"\n\t},\n\t\"dependencies\": {\n\t\t\"esm-env\": \"^1.1.4\"\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/csp.ts",
    "content": "import runtimeStyles from './styles'\nimport { styles as ssrStyles, renderFallbackStyles } from './ssr'\n\nexport const buildStyles = (elementSuffix?: string) =>\n\t[ssrStyles, renderFallbackStyles(elementSuffix), runtimeStyles] as const\n"
  },
  {
    "path": "packages/number-flow/src/env.d.ts",
    "content": "// Fix types for Intl.NumberFormat\ndeclare namespace Intl {\n\tinterface NumberFormat {\n\t\tformatToParts(number?: number | bigint | string): NumberFormatPart[]\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/formatter.ts",
    "content": "// Merge the plus and minus sign types\nexport type NumberPartType =\n\t| Exclude<Intl.NumberFormatPartTypes, 'minusSign' | 'plusSign'>\n\t| 'sign'\n\t| 'prefix'\n\t| 'suffix'\n// These need to be separated for the discriminated union to work:\n// https://www.typescriptlang.org/play/?target=99&ssl=8&ssc=1&pln=9&pc=1#code/C4TwDgpgBAIglgczsKBeKBvKpIC4oDkcAdsBAhAE4FQA+hAZpQIYDGwcA9sQQNxQA3ZgBsArhHzFRAWwBGVKAF8AsACgc0AMIALZpTSZs4CYVa7q-QSPH4AzsEokEStWoaji7LsSgATTgDKwKIMDAAUYHrA+PBIKPQ6egCUmGpQUHAMUBFRAHQaaKjoRKTkVDS09JGUwPnGhcVMbBzcBCkYaelQ1cCdilAQwrbQHapd3VFQAPRTUAA8ALTY2nC2GbY8KImUADRQwnAA1tAAkgS+AwAekOzZAPxJfWqKQA\ntype IntegerPart = { type: NumberPartType & 'integer'; value: number }\ntype FractionPart = { type: NumberPartType & 'fraction'; value: number }\ntype DigitPart = IntegerPart | FractionPart\ntype SymbolPart = {\n\ttype: Exclude<NumberPartType, 'integer' | 'fraction'>\n\tvalue: string\n}\n\nexport type NumberPartKey = string\ntype KeyedPart = { key: NumberPartKey }\nexport type KeyedDigitPart = DigitPart & KeyedPart & { pos: number }\nexport type KeyedSymbolPart = SymbolPart & KeyedPart\nexport type KeyedNumberPart = KeyedDigitPart | KeyedSymbolPart\n\nexport type Format = Omit<Intl.NumberFormatOptions, 'notation'> & {\n\tnotation?: Exclude<Intl.NumberFormatOptions['notation'], 'scientific' | 'engineering'>\n}\n\nexport type Value = Exclude<\n\tParameters<typeof Intl.NumberFormat.prototype.formatToParts>[0],\n\tbigint | undefined\n>\n\nexport function formatToData(\n\tvalue: Value,\n\tformatter: Intl.NumberFormat,\n\tprefix?: string,\n\tsuffix?: string\n) {\n\tconst parts: Array<\n\t\tOmit<Intl.NumberFormatPart, 'type'> & { type: Intl.NumberFormatPartTypes | 'prefix' | 'suffix' }\n\t> = formatter.formatToParts(value)\n\tif (prefix) parts.unshift({ type: 'prefix', value: prefix })\n\tif (suffix) parts.push({ type: 'suffix', value: suffix })\n\n\tconst pre: KeyedNumberPart[] = []\n\tconst _integer: Array<IntegerPart | SymbolPart> = [] // we do a second pass to key these from RTL\n\tconst fraction: KeyedNumberPart[] = []\n\tconst post: KeyedNumberPart[] = []\n\n\tconst counts: Partial<Record<NumberPartType, number>> = {}\n\tconst generateKey = (type: NumberPartType) =>\n\t\t`${type}:${(counts[type] = (counts[type] ?? -1) + 1)}`\n\n\tlet valueAsString = ''\n\tlet seenInteger = false,\n\t\tseenDecimal = false\n\tfor (const part of parts) {\n\t\tvalueAsString += part.value\n\n\t\t// Merge plus and minus sign types (doing it this way appeases TypeScript)\n\t\tconst type: NumberPartType =\n\t\t\tpart.type === 'minusSign' || part.type === 'plusSign' ? 'sign' : part.type\n\n\t\tif (type === 'integer') {\n\t\t\tseenInteger = true\n\t\t\t_integer.push(...part.value.split('').map((d) => ({ type, value: parseInt(d) })))\n\t\t} else if (type === 'group') {\n\t\t\t_integer.push({ type, value: part.value })\n\t\t} else if (type === 'decimal') {\n\t\t\tseenDecimal = true\n\t\t\tfraction.push({ type, value: part.value, key: generateKey(type) })\n\t\t} else if (type === 'fraction') {\n\t\t\tfraction.push(\n\t\t\t\t...part.value.split('').map((d) => ({\n\t\t\t\t\ttype,\n\t\t\t\t\tvalue: parseInt(d),\n\t\t\t\t\tkey: generateKey(type),\n\t\t\t\t\tpos: -1 - counts[type]!\n\t\t\t\t}))\n\t\t\t)\n\t\t} else {\n\t\t\t;(seenInteger || seenDecimal ? post : pre).push({\n\t\t\t\ttype,\n\t\t\t\tvalue: part.value,\n\t\t\t\tkey: generateKey(type)\n\t\t\t})\n\t\t}\n\t}\n\n\tconst integer: KeyedNumberPart[] = []\n\t// Key the integer parts RTL, for better layout animations\n\tfor (let i = _integer.length - 1; i >= 0; i--) {\n\t\tconst p = _integer[i]!\n\t\tinteger.unshift(\n\t\t\tp.type === 'integer'\n\t\t\t\t? {\n\t\t\t\t\t\t...p,\n\t\t\t\t\t\tkey: generateKey(p.type),\n\t\t\t\t\t\tpos: counts[p.type]!\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\t...p,\n\t\t\t\t\t\tkey: generateKey(p.type)\n\t\t\t\t\t}\n\t\t)\n\t}\n\n\treturn {\n\t\tpre,\n\t\tinteger,\n\t\tfraction,\n\t\tpost,\n\t\tvalueAsString,\n\t\tvalue: typeof value == 'string' ? parseFloat(value) : value\n\t}\n}\n\nexport type Data = ReturnType<typeof formatToData>\n"
  },
  {
    "path": "packages/number-flow/src/group.ts",
    "content": "import { define } from './util/dom'\nimport { ServerSafeHTMLElement } from './ssr'\nimport { CONNECT_EVENT, UPDATE_EVENT } from '.'\nimport NumberFlow from '.'\n\nexport default class NumberFlowGroup extends ServerSafeHTMLElement {\n\tprivate _mutationObserver?: MutationObserver\n\n\tconnectedCallback() {\n\t\t// The descendants are probably already connected, so query the DOM first.\n\t\t// Note: this won't work with a custom-defined element, if that ever exists:\n\t\tthis.querySelectorAll<NumberFlow>('number-flow').forEach((flow) => {\n\t\t\tthis._addDescendant(flow)\n\t\t})\n\n\t\tthis.addEventListener(CONNECT_EVENT, this._onDescendantConnected)\n\t\tthis.addEventListener(UPDATE_EVENT, this._onDescendantUpdate)\n\n\t\t// We can't emit disconnection events, so use a mutation observer to track those\n\t\tthis._mutationObserver ??= new MutationObserver((mutations) => {\n\t\t\tmutations.forEach((mutation) => {\n\t\t\t\tmutation.removedNodes.forEach((node) => {\n\t\t\t\t\tif (node instanceof NumberFlow) {\n\t\t\t\t\t\tthis._removeDescendant(node)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t\tthis._mutationObserver.observe(this, { childList: true, subtree: true })\n\t}\n\n\tprivate _flows = new Set<NumberFlow>()\n\n\tprivate _addDescendant = (flow: NumberFlow) => {\n\t\tflow.batched = true\n\t\tthis._flows.add(flow)\n\t}\n\tprivate _removeDescendant = (flow: NumberFlow) => {\n\t\tflow.batched = false\n\t\tthis._flows.delete(flow)\n\t}\n\n\tprivate _onDescendantConnected = (event: Event) => {\n\t\tthis._addDescendant(event.target as NumberFlow)\n\t}\n\n\tprivate _updating = false\n\tprivate _onDescendantUpdate = () => {\n\t\tif (this._updating) return\n\t\tthis._updating = true\n\t\tthis._flows.forEach((flow) => {\n\t\t\tif (!flow.created) return\n\t\t\tflow.willUpdate()\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (flow.connected) flow.didUpdate()\n\t\t\t})\n\t\t})\n\t\tqueueMicrotask(() => {\n\t\t\tthis._updating = false\n\t\t})\n\t}\n\n\tdisconnectedCallback() {\n\t\tthis.removeEventListener(CONNECT_EVENT, this._onDescendantConnected)\n\t\tthis.removeEventListener(UPDATE_EVENT, this._onDescendantUpdate)\n\t\tthis._mutationObserver?.disconnect()\n\t}\n}\n\ndefine('number-flow-group', NumberFlowGroup)\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'number-flow-group': NumberFlowGroup\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/index.ts",
    "content": "import NumberFlowLite from './lite'\nimport { define } from './util/dom'\nimport { renderInnerHTML as defaultRenderInnerHTML } from './ssr'\nimport { formatToData, type Value, type Format } from './formatter'\nimport { buildStyles } from './csp'\nexport const styles = buildStyles()\nexport * from './lite'\n\nexport const CONNECT_EVENT = 'number-flow-connect'\nexport const UPDATE_EVENT = 'number-flow-update'\n\n// Override the export from ./lite\nexport const renderInnerHTML = (\n\tvalue: Value,\n\t{\n\t\tlocales,\n\t\tformat,\n\t\tnumberPrefix: prefix,\n\t\tnumberSuffix: suffix,\n\t\tnonce\n\t}: {\n\t\tlocales?: Intl.LocalesArgument\n\t\tformat?: Intl.NumberFormatOptions\n\t\tnumberPrefix?: string\n\t\tnumberSuffix?: string\n\t\tnonce?: string\n\t} = {}\n) => {\n\tconst data = formatToData(value, new Intl.NumberFormat(locales, format), prefix, suffix)\n\n\treturn defaultRenderInnerHTML(data, { nonce })\n}\n\nexport default class NumberFlow extends NumberFlowLite {\n\t/**\n\t * @internal for grouping\n\t */\n\tconnected = false\n\tconnectedCallback() {\n\t\tthis.connected = true\n\t\tthis.dispatchEvent(new Event(CONNECT_EVENT, { bubbles: true }))\n\t}\n\tdisconnectedCallback() {\n\t\tthis.connected = false\n\t}\n\n\tformat?: Format\n\tlocales?: Intl.LocalesArgument\n\t// This can't be called prefix because that conflicts:\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Element/prefix\n\tnumberPrefix?: string\n\tnumberSuffix?: string\n\n\tprivate _formatter?: Intl.NumberFormat\n\n\tprivate _prevFormat?: Format\n\tprivate _prevLocales?: Intl.LocalesArgument\n\n\tprivate _value?: Value\n\tget value() {\n\t\treturn this._value\n\t}\n\n\tupdate(value?: Value) {\n\t\t// Might want to do a deep-equal check here:\n\t\tif (\n\t\t\t!this._formatter ||\n\t\t\tthis._prevFormat !== this.format ||\n\t\t\tthis._prevLocales !== this.locales\n\t\t) {\n\t\t\tthis._formatter = new Intl.NumberFormat(this.locales, this.format)\n\t\t\tthis._prevFormat = this.format\n\t\t\tthis._prevLocales = this.locales\n\t\t}\n\t\tif (value != null) {\n\t\t\tthis._value = value\n\t\t}\n\n\t\t// For group, has to be before setting data:\n\t\tthis.dispatchEvent(new Event(UPDATE_EVENT, { bubbles: true }))\n\n\t\tthis.data = formatToData(this._value!, this._formatter!, this.numberPrefix, this.numberSuffix)\n\t}\n}\n\ndefine('number-flow', NumberFlow)\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'number-flow': NumberFlow\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/lite.ts",
    "content": "import { createElement, offset, visible, type HTMLProps, type Justify } from './util/dom'\nimport { forEach } from './util/iterable'\nimport {\n\ttype KeyedDigitPart,\n\ttype KeyedNumberPart,\n\ttype KeyedSymbolPart,\n\ttype NumberPartKey,\n\ttype Data\n} from './formatter'\nimport { ServerSafeHTMLElement } from './ssr'\nimport styles, {\n\tsupportsMod,\n\tsupportsLinear,\n\tdxVar,\n\topacityDeltaVar,\n\tprefersReducedMotion,\n\tsupportsAtProperty,\n\twidthDeltaVar,\n\tdeltaVar\n} from './styles'\nimport type { Mutable as MakeMutable } from './util/types'\nimport type { Plugin } from './plugins'\n\nexport { define } from './util/dom'\nexport { prefersReducedMotion } from './styles'\nexport { renderInnerHTML } from './ssr'\nexport * from './plugins'\nexport * from './formatter'\n\nexport const canAnimate = supportsMod && supportsLinear && supportsAtProperty\n\n// Hoping to use -1 | 0 | 1 in the future if Math.sign types ever get fixed.\n// Don't do ReturnType<Math['sign']> cause it breaks Vue prop types:\nexport type Trend = number | ((oldValue: number, value: number) => number)\n\nexport type DigitOptions = { max?: number }\nexport type Digits = Record<number, DigitOptions>\n\nexport interface Props {\n\ttransformTiming: EffectTiming\n\tspinTiming: EffectTiming | undefined\n\topacityTiming: EffectTiming\n\tanimated: boolean\n\trespectMotionPreference: boolean\n\ttrend: Trend\n\tplugins?: Plugin[]\n\tdigits: Digits | undefined\n}\n\n// Workaround for Object.assign in constructor and TS:\n// https://github.com/microsoft/TypeScript/issues/26792#issuecomment-617541464\nexport default interface NumberFlowLite extends Props {}\n\n// Workaround for no outside-readable/inside-writable members in TS:\n// https://github.com/microsoft/TypeScript/issues/37487\ntype Mutable = MakeMutable<NumberFlowLite>\n\n/**\n * @internal Used for framework wrappers\n */\nexport default class NumberFlowLite extends ServerSafeHTMLElement implements Props {\n\t/**\n\t * Use `private _private` properties instead of `#private` to avoid # polyfill and\n\t * reduce bundle size. Also, use `readonly` properties instead of getters to save on bundle\n\t * size, even though you have to do gross stuff like `(this as Mutable<...>)` until TS\n\t * supports e.g. https://github.com/microsoft/TypeScript/issues/37487\n\t */\n\n\tstatic defaultProps: Props = {\n\t\ttransformTiming: {\n\t\t\tduration: 900,\n\t\t\t// Make sure to keep this minified:\n\t\t\teasing: `linear(0,.005,.019,.039,.066,.096,.129,.165,.202,.24,.278,.316,.354,.39,.426,.461,.494,.526,.557,.586,.614,.64,.665,.689,.711,.731,.751,.769,.786,.802,.817,.831,.844,.856,.867,.877,.887,.896,.904,.912,.919,.925,.931,.937,.942,.947,.951,.955,.959,.962,.965,.968,.971,.973,.976,.978,.98,.981,.983,.984,.986,.987,.988,.989,.99,.991,.992,.992,.993,.994,.994,.995,.995,.996,.996,.9963,.9967,.9969,.9972,.9975,.9977,.9979,.9981,.9982,.9984,.9985,.9987,.9988,.9989,1)`\n\t\t},\n\t\tspinTiming: undefined,\n\t\topacityTiming: { duration: 450, easing: 'ease-out' },\n\t\tanimated: true,\n\t\ttrend: (oldValue, value) => Math.sign(value - oldValue),\n\t\trespectMotionPreference: true,\n\t\tplugins: undefined,\n\t\tdigits: undefined\n\t}\n\n\tconstructor() {\n\t\tsuper()\n\t\tconst { animated, ...props } = (this.constructor as typeof NumberFlowLite).defaultProps\n\t\tthis._animated = this.computedAnimated = animated\n\t\tObject.assign(this, props)\n\t}\n\n\tprivate _animated: boolean\n\tget animated() {\n\t\treturn this._animated\n\t}\n\tset animated(val: boolean) {\n\t\tif (this.animated === val) return\n\t\tthis._animated = val\n\t\t// Finish any in-flight animations (instead of cancel, which won't trigger their finish events):\n\t\tthis.shadowRoot?.getAnimations().forEach((a) => a.finish())\n\t}\n\n\treadonly created: boolean = false\n\n\tprivate _pre?: SymbolSection\n\tprivate _num?: Num\n\tprivate _post?: SymbolSection\n\n\treadonly computedTrend?: number\n\n\treadonly computedAnimated: boolean\n\n\tprivate _internals?: ElementInternals\n\n\tprivate _data?: Data\n\n\t/**\n\t * @internal\n\t */\n\tbatched = false\n\n\t/**\n\t * @internal\n\t */\n\tset data(data: Data | undefined) {\n\t\tif (data == null) {\n\t\t\treturn\n\t\t}\n\n\t\tconst { pre, integer, fraction, post, value } = data\n\n\t\t// Initialize if needed\n\t\tif (!this.created) {\n\t\t\tthis._data = data\n\n\t\t\t// This will overwrite the DSD if any:\n\t\t\tthis.attachShadow({ mode: 'open' })\n\n\t\t\ttry {\n\t\t\t\tthis._internals ??= this.attachInternals()\n\t\t\t\tthis._internals.role = 'img'\n\t\t\t} catch {\n\t\t\t\t// Don't error in old browsers that don't support ElementInternals\n\t\t\t\t// Try/catch is less code than an if check.\n\t\t\t}\n\n\t\t\t// Add stylesheet; don't use adoptedStylesheets because it works unreliably in Safari:\n\t\t\tconst style = document.createElement('style')\n\t\t\tif (this.nonce) style.nonce = this.nonce\n\t\t\tstyle.textContent = styles\n\t\t\tthis.shadowRoot!.appendChild(style)\n\n\t\t\tthis._pre = new SymbolSection(this, pre, {\n\t\t\t\tjustify: 'right',\n\t\t\t\tpart: 'left'\n\t\t\t})\n\t\t\tthis.shadowRoot!.appendChild(this._pre.el)\n\n\t\t\tthis._num = new Num(this, integer, fraction)\n\t\t\tthis.shadowRoot!.appendChild(this._num.el)\n\n\t\t\tthis._post = new SymbolSection(this, post, {\n\t\t\t\tjustify: 'left',\n\t\t\t\tpart: 'right'\n\t\t\t})\n\t\t\tthis.shadowRoot!.appendChild(this._post.el)\n\t\t\t;(this as Mutable).created = true\n\t\t} else {\n\t\t\tconst prev = this._data!\n\t\t\tthis._data = data\n\n\t\t\t// Compute trend\n\t\t\t;(this as Mutable).computedTrend =\n\t\t\t\ttypeof this.trend === 'function' ? this.trend(prev.value, value) : this.trend\n\t\t\t;(this as Mutable).computedAnimated =\n\t\t\t\tcanAnimate &&\n\t\t\t\tthis._animated &&\n\t\t\t\t(!this.respectMotionPreference || !prefersReducedMotion?.matches) &&\n\t\t\t\t// https://github.com/barvian/number-flow/issues/9\n\t\t\t\tvisible(this) &&\n\t\t\t\t// https://github.com/barvian/number-flow/issues/165\n\t\t\t\tthis.ownerDocument.visibilityState === 'visible'\n\n\t\t\tthis.plugins?.forEach((p) => p.onUpdate?.(data, prev, this))\n\n\t\t\tif (!this.batched) this.willUpdate()\n\n\t\t\tthis._pre!.update(pre)\n\t\t\tthis._num!.update({ integer, fraction })\n\t\t\tthis._post!.update(post)\n\n\t\t\tif (!this.batched) this.didUpdate()\n\t\t}\n\n\t\ttry {\n\t\t\tthis._internals!.ariaLabel = data.valueAsString\n\t\t} catch {\n\t\t\t// Don't error in old browsers that don't support ElementInternals\n\t\t\t// Try/catch is less code than an if check.\n\t\t}\n\t}\n\n\t/**\n\t * @internal\n\t */\n\twillUpdate() {\n\t\t// Not super safe to check animated here, b/c the prop may not have been updated yet:\n\t\tthis._pre!.willUpdate()\n\t\tthis._num!.willUpdate()\n\t\tthis._post!.willUpdate()\n\t}\n\n\tprivate _abortAnimationsFinish?: AbortController\n\n\t/**\n\t * @internal\n\t */\n\tdidUpdate() {\n\t\t// Safe to call this here because we know the animated prop is up-to-date\n\t\tif (!this.computedAnimated) return\n\n\t\t// If we're already animating, cancel the previous animationsfinish event:\n\t\tif (this._abortAnimationsFinish) this._abortAnimationsFinish.abort()\n\t\t// Otherwise, dispatch a start event:\n\t\telse this.dispatchEvent(new Event('animationsstart'))\n\n\t\tthis._pre!.didUpdate()\n\t\tthis._num!.didUpdate()\n\t\tthis._post!.didUpdate()\n\n\t\tconst controller = new AbortController()\n\t\tPromise.all(this.shadowRoot!.getAnimations().map((a) => a.finished)).then(() => {\n\t\t\tif (!controller.signal.aborted) {\n\t\t\t\tthis.dispatchEvent(new Event('animationsfinish'))\n\t\t\t\tthis._abortAnimationsFinish = undefined\n\t\t\t}\n\t\t})\n\t\tthis._abortAnimationsFinish = controller\n\t}\n}\n\nclass Num {\n\treadonly el: HTMLSpanElement\n\treadonly _inner: HTMLSpanElement\n\n\tprivate _integer: NumberSection\n\tprivate _fraction: NumberSection\n\n\tconstructor(\n\t\treadonly flow: NumberFlowLite,\n\t\tinteger: KeyedNumberPart[],\n\t\tfraction: KeyedNumberPart[],\n\t\t{ className, ...props }: HTMLProps<'span'> = {}\n\t) {\n\t\tthis._integer = new NumberSection(flow, integer, {\n\t\t\tjustify: 'right',\n\t\t\tpart: 'integer'\n\t\t})\n\t\tthis._fraction = new NumberSection(flow, fraction, {\n\t\t\tjustify: 'left',\n\t\t\tpart: 'fraction'\n\t\t})\n\n\t\tthis._inner = createElement(\n\t\t\t'span',\n\t\t\t{\n\t\t\t\tclassName: `number__inner`\n\t\t\t},\n\t\t\t[this._integer.el, this._fraction.el]\n\t\t)\n\t\tthis.el = createElement(\n\t\t\t'span',\n\t\t\t{\n\t\t\t\t...props,\n\t\t\t\tpart: 'number',\n\t\t\t\tclassName: `number ${className ?? ''}`\n\t\t\t},\n\t\t\t[this._inner]\n\t\t)\n\t}\n\n\tprivate _prevWidth?: number\n\tprivate _prevLeft?: number\n\n\twillUpdate() {\n\t\tthis._prevWidth = this.el.offsetWidth\n\t\tthis._prevLeft = this.el.getBoundingClientRect().left\n\n\t\tthis._integer.willUpdate()\n\t\tthis._fraction.willUpdate()\n\t}\n\n\tupdate({ integer, fraction }: Pick<Data, 'integer' | 'fraction'>) {\n\t\tthis._integer.update(integer)\n\t\tthis._fraction.update(fraction)\n\t}\n\n\tdidUpdate() {\n\t\tconst rect = this.el.getBoundingClientRect()\n\n\t\t// Do this before starting to animate:\n\t\tthis._integer.didUpdate()\n\t\tthis._fraction.didUpdate()\n\n\t\tconst dx = this._prevLeft! - rect.left\n\n\t\tconst width = this.el.offsetWidth\n\t\t// We convert scale to width delta in px to better handle interruptions and keep them in\n\t\t// sync with translations:\n\t\tconst dWidth = this._prevWidth! - width\n\t\tthis.el.style.setProperty('--width', String(width))\n\n\t\tthis.el.animate(\n\t\t\t{\n\t\t\t\t[dxVar]: [`${dx}px`, '0px'],\n\t\t\t\t[widthDeltaVar]: [dWidth, 0]\n\t\t\t},\n\t\t\t{\n\t\t\t\t...this.flow.transformTiming,\n\t\t\t\tcomposite: 'accumulate'\n\t\t\t}\n\t\t)\n\t}\n}\n\ntype SectionProps = { justify: Justify } & HTMLProps<'span'>\n\nabstract class Section {\n\treadonly el: HTMLSpanElement\n\treadonly justify: Justify\n\n\t// All children in the DOM:\n\tprotected children = new Map<NumberPartKey, Char>()\n\n\tconstructor(\n\t\treadonly flow: NumberFlowLite,\n\t\tparts: KeyedNumberPart[],\n\t\t{ justify, className, ...props }: SectionProps,\n\t\tchildren?: (chars: Node[]) => Node[]\n\t) {\n\t\tthis.justify = justify\n\t\tconst chars = parts.map<Node>((p) => this.addChar(p).el)\n\n\t\tthis.el = createElement(\n\t\t\t'span',\n\t\t\t{\n\t\t\t\t...props,\n\t\t\t\tclassName: `section section--justify-${justify} ${className ?? ''}`\n\t\t\t},\n\t\t\tchildren ? children(chars) : chars\n\t\t)\n\t}\n\n\tprotected addChar(\n\t\tpart: KeyedNumberPart,\n\t\t{\n\t\t\tstartDigitsAtZero = false,\n\t\t\t...props\n\t\t}: { startDigitsAtZero?: boolean } & Pick<AnimatePresenceProps, 'animateIn'> = {}\n\t) {\n\t\tconst comp =\n\t\t\tpart.type === 'integer' || part.type === 'fraction'\n\t\t\t\t? new Digit(this, part.type, startDigitsAtZero ? 0 : part.value, part.pos, {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tonRemove: this.onCharRemove(part.key)\n\t\t\t\t\t})\n\t\t\t\t: new Sym(this, part.type, part.value, {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tonRemove: this.onCharRemove(part.key)\n\t\t\t\t\t})\n\t\tthis.children.set(part.key, comp)\n\t\treturn comp\n\t}\n\n\tprivate onCharRemove =\n\t\t(key: NumberPartKey): OnRemove =>\n\t\t() => {\n\t\t\tthis.children.delete(key)\n\t\t}\n\n\tprotected unpop(char: Char) {\n\t\tchar.el.removeAttribute('inert')\n\t\tchar.el.style.top = ''\n\t\tchar.el.style[this.justify] = ''\n\t}\n\n\tprotected pop(chars: Map<any, Char>) {\n\t\t// Calculate offsets for removed before popping, to avoid layout thrashing:\n\t\tchars.forEach((char) => {\n\t\t\tchar.el.style.top = `${char.el.offsetTop}px`\n\t\t\tchar.el.style[this.justify] = `${offset(char.el, this.justify)}px`\n\t\t})\n\t\tchars.forEach((char) => {\n\t\t\tchar.el.setAttribute('inert', '')\n\t\t\tchar.present = false\n\t\t})\n\t}\n\n\tprotected addNewAndUpdateExisting(parts: KeyedNumberPart[]) {\n\t\tconst added = new Map<KeyedNumberPart, Char>()\n\t\tconst updated = new Map<KeyedNumberPart, Char>()\n\n\t\t// Add new parts before any other updates, so we can save their position correctly:\n\t\tconst reverse = this.justify === 'left'\n\t\tconst op = reverse ? 'prepend' : 'append'\n\t\tforEach(\n\t\t\tparts,\n\t\t\t(part) => {\n\t\t\t\tlet comp: Char\n\t\t\t\t// Already exists/needs update, so set aside for now\n\t\t\t\tif (this.children.has(part.key)) {\n\t\t\t\t\tcomp = this.children.get(part.key)!\n\t\t\t\t\tupdated.set(part, comp)\n\t\t\t\t\tthis.unpop(comp)\n\t\t\t\t\tcomp.present = true\n\t\t\t\t} else {\n\t\t\t\t\t// New part\n\t\t\t\t\tcomp = this.addChar(part, { startDigitsAtZero: true, animateIn: true })\n\t\t\t\t\tadded.set(part, comp)\n\t\t\t\t}\n\t\t\t\tthis.el[op](comp.el)\n\t\t\t},\n\t\t\t{ reverse }\n\t\t)\n\n\t\tif (this.flow.computedAnimated) {\n\t\t\tconst rect = this.el.getBoundingClientRect() // this should only cause a layout if there were added children (?)\n\t\t\tadded.forEach((comp) => {\n\t\t\t\tcomp.willUpdate(rect)\n\t\t\t})\n\t\t}\n\t\t// Update added children to their initial value (we start them at 0)\n\t\tadded.forEach((comp, part) => {\n\t\t\tcomp.update(part.value)\n\t\t})\n\n\t\t// Update any updated children\n\t\tupdated.forEach((comp, part) => {\n\t\t\tcomp.update(part.value)\n\t\t})\n\t}\n\n\tprivate _prevOffset?: number\n\n\twillUpdate() {\n\t\tconst rect = this.el.getBoundingClientRect()\n\t\tthis._prevOffset = rect[this.justify]\n\n\t\tthis.children.forEach((comp) => comp.willUpdate(rect))\n\t}\n\n\tdidUpdate() {\n\t\tconst rect = this.el.getBoundingClientRect()\n\n\t\t// Make sure to pass this in before starting to animate:\n\t\tthis.children.forEach((comp) => comp.didUpdate(rect))\n\n\t\tconst offset = rect[this.justify]\n\t\tconst dx = this._prevOffset! - offset\n\n\t\t// Technically checking for children could get weird during multiple interruptions\n\t\t// but probably still worth it;\n\t\tif (dx && this.children.size)\n\t\t\tthis.el.animate(\n\t\t\t\t{\n\t\t\t\t\ttransform: [`translateX(${dx}px)`, 'none']\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t...this.flow.transformTiming,\n\t\t\t\t\tcomposite: 'accumulate'\n\t\t\t\t}\n\t\t\t)\n\t}\n}\n\nclass NumberSection extends Section {\n\tupdate(parts: KeyedNumberPart[]) {\n\t\tconst removed = new Map<NumberPartKey, Char>()\n\n\t\tthis.children.forEach((comp, key) => {\n\t\t\t// Keep track of removed children:\n\t\t\tif (!parts.find((p) => p.key === key)) {\n\t\t\t\tremoved.set(key, comp)\n\t\t\t}\n\t\t\t// Put everything back into the flow briefly, to recompute offsets:\n\t\t\tthis.unpop(comp)\n\t\t})\n\n\t\tthis.addNewAndUpdateExisting(parts)\n\n\t\t// Set all removed digits to 0, for mathematical correctness:\n\t\tremoved.forEach((comp) => {\n\t\t\tif (comp instanceof Digit) comp.update(0)\n\t\t})\n\n\t\t// Then end with them popped out again:\n\t\tthis.pop(removed)\n\t}\n}\n\nclass SymbolSection extends Section {\n\tupdate(parts: KeyedNumberPart[]) {\n\t\tconst removed = new Map<NumberPartKey, Char>()\n\n\t\tthis.children.forEach((comp, key) => {\n\t\t\t// Keep track of removed children:\n\t\t\tif (!parts.find((p) => p.key === key)) {\n\t\t\t\tremoved.set(key, comp)\n\t\t\t}\n\t\t})\n\n\t\t// Pop them, before any additions\n\t\tthis.pop(removed)\n\n\t\tthis.addNewAndUpdateExisting(parts)\n\t}\n}\n\ntype OnRemove = () => void\ninterface AnimatePresenceProps {\n\tonRemove?: OnRemove\n\tanimateIn?: boolean\n}\n\nclass AnimatePresence {\n\tprivate _present = true\n\tprivate _onRemove?: OnRemove\n\n\tconstructor(\n\t\treadonly flow: NumberFlowLite,\n\t\treadonly el: HTMLElement,\n\t\t{ onRemove, animateIn = false }: AnimatePresenceProps = {}\n\t) {\n\t\tthis.el.classList.add('animate-presence')\n\t\t// This craziness is the only way I could figure out how to get the opacity\n\t\t// accumulation to work in all browsers. Accumulating -1 onto opacity directly\n\t\t// failed in both FF and Safari, and setting a delta to -1 still failed in FF\n\t\tif (this.flow.computedAnimated && animateIn) {\n\t\t\tthis.el.animate(\n\t\t\t\t{\n\t\t\t\t\t[opacityDeltaVar]: [-0.9999, 0]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t...this.flow.opacityTiming,\n\t\t\t\t\tcomposite: 'accumulate'\n\t\t\t\t}\n\t\t\t)\n\t\t}\n\n\t\tthis._onRemove = onRemove\n\t}\n\n\tget present() {\n\t\treturn this._present\n\t}\n\n\tprivate _remove = () => {\n\t\tthis.el.remove()\n\t\tthis._onRemove?.()\n\t}\n\n\tset present(val) {\n\t\tif (this._present === val) return\n\t\tthis._present = val\n\t\tif (val) this.el.removeAttribute('inert')\n\t\telse this.el.setAttribute('inert', '')\n\n\t\tif (!this.flow.computedAnimated) {\n\t\t\tif (!val) this._remove()\n\t\t\treturn\n\t\t}\n\n\t\tthis.el.style.setProperty('--_number-flow-d-opacity', val ? '0' : '-.999')\n\t\tthis.el.animate(\n\t\t\t{\n\t\t\t\t[opacityDeltaVar]: val ? [-0.9999, 0] : [0.999, 0]\n\t\t\t},\n\t\t\t{\n\t\t\t\t...this.flow.opacityTiming,\n\t\t\t\tcomposite: 'accumulate'\n\t\t\t}\n\t\t)\n\n\t\tif (val) this.flow.removeEventListener('animationsfinish', this._remove)\n\t\telse\n\t\t\tthis.flow.addEventListener('animationsfinish', this._remove, {\n\t\t\t\tonce: true\n\t\t\t})\n\t}\n}\n\ninterface CharProps extends AnimatePresenceProps {}\n\nabstract class Char<P extends KeyedNumberPart = KeyedNumberPart> extends AnimatePresence {\n\tconstructor(\n\t\treadonly section: Section,\n\t\tprotected value: P['value'],\n\t\toverride readonly el: HTMLSpanElement,\n\t\tprops?: AnimatePresenceProps\n\t) {\n\t\tsuper(section.flow, el, props)\n\t}\n\n\tabstract willUpdate(parentRect: DOMRect): void\n\tabstract update(value: P['value']): void\n\tabstract didUpdate(parentRect: DOMRect): void\n}\n\nexport class Digit extends Char<KeyedDigitPart> {\n\tprivate _numbers: HTMLSpanElement[]\n\treadonly length: number\n\n\tconstructor(\n\t\tsection: Section,\n\t\ttype: KeyedDigitPart['type'],\n\t\tvalue: KeyedDigitPart['value'],\n\t\treadonly pos: number,\n\t\tprops?: CharProps\n\t) {\n\t\tconst length = (section.flow.digits?.[pos]?.max ?? 9) + 1\n\t\tconst numbers = Array.from({ length }).map((_, i) => {\n\t\t\tconst num = createElement('span', { className: `digit__num` }, [\n\t\t\t\tdocument.createTextNode(String(i))\n\t\t\t])\n\t\t\t// Use the attribute for now because it has a little better browser support:\n\t\t\tif (i !== value) num.setAttribute('inert', '')\n\t\t\tnum.style.setProperty('--n', String(i))\n\t\t\treturn num\n\t\t})\n\t\tconst el = createElement(\n\t\t\t'span',\n\t\t\t{\n\t\t\t\tpart: `digit ${type}-digit`,\n\t\t\t\tclassName: `digit`\n\t\t\t},\n\t\t\tnumbers\n\t\t)\n\t\tel.style.setProperty('--current', String(value))\n\t\tel.style.setProperty('--length', String(length))\n\n\t\tsuper(section, value, el, props)\n\n\t\tthis._numbers = numbers\n\t\tthis.length = length\n\t}\n\n\tprivate _prevValue?: KeyedDigitPart['value']\n\n\t// Relative to parent:\n\tprivate _prevCenter?: number\n\n\twillUpdate(parentRect: DOMRect) {\n\t\tconst rect = this.el.getBoundingClientRect()\n\n\t\tthis._prevValue = this.value\n\n\t\tconst prevOffset = rect[this.section.justify] - parentRect[this.section.justify]\n\t\tconst halfWidth = rect.width / 2\n\t\tthis._prevCenter =\n\t\t\tthis.section.justify === 'left' ? prevOffset + halfWidth : prevOffset - halfWidth\n\t}\n\n\tupdate(value: KeyedDigitPart['value']) {\n\t\tthis.el.style.setProperty('--current', String(value))\n\t\tthis._numbers.forEach((num, i) =>\n\t\t\ti === value ? num.removeAttribute('inert') : num.setAttribute('inert', '')\n\t\t)\n\t\tthis.value = value\n\t}\n\n\tdidUpdate(parentRect: DOMRect) {\n\t\tconst rect = this.el.getBoundingClientRect()\n\t\tconst offset = rect[this.section.justify] - parentRect[this.section.justify]\n\t\tconst halfWidth = rect.width / 2\n\t\tconst center = this.section.justify === 'left' ? offset + halfWidth : offset - halfWidth\n\t\tconst dx = this._prevCenter! - center\n\n\t\tif (dx)\n\t\t\tthis.el.animate(\n\t\t\t\t{\n\t\t\t\t\ttransform: [`translateX(${dx}px)`, 'none']\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t...this.flow.transformTiming,\n\t\t\t\t\tcomposite: 'accumulate'\n\t\t\t\t}\n\t\t\t)\n\n\t\tconst delta = this.getDelta()\n\t\tif (!delta) return\n\n\t\tthis.el.classList.add('is-spinning')\n\t\tthis.el.animate(\n\t\t\t{\n\t\t\t\t[deltaVar]: [-delta, 0]\n\t\t\t},\n\t\t\t{\n\t\t\t\t...(this.flow.spinTiming ?? this.flow.transformTiming),\n\t\t\t\tcomposite: 'accumulate'\n\t\t\t}\n\t\t)\n\t\t// Hoisting the callback out prevents duplicates:\n\t\tthis.flow.addEventListener('animationsfinish', this._onAnimationsFinish, { once: true })\n\t}\n\n\tgetDelta() {\n\t\tif (this.flow.plugins)\n\t\t\tfor (const plugin of this.flow.plugins) {\n\t\t\t\tconst diff = plugin.getDelta?.(this.value, this._prevValue!, this)\n\t\t\t\tif (diff != null) return diff\n\t\t\t}\n\n\t\tconst diff = this.value - this._prevValue!\n\t\t// Make it per-digit if no root trend:\n\t\tconst trend = this.flow.computedTrend || Math.sign(diff)\n\t\t// Loop around if need be:\n\t\tif (trend < 0 && this.value > this._prevValue!)\n\t\t\treturn this.value - this.length - this._prevValue!\n\t\telse if (trend > 0 && this.value < this._prevValue!)\n\t\t\treturn this.length - this._prevValue! + this.value\n\n\t\treturn diff\n\t}\n\n\tprivate _onAnimationsFinish = () => {\n\t\tthis.el.classList.remove('is-spinning')\n\t}\n}\n\nclass Sym extends Char<KeyedSymbolPart> {\n\tconstructor(\n\t\tsection: Section,\n\t\tprivate type: KeyedSymbolPart['type'],\n\t\tvalue: KeyedSymbolPart['value'],\n\t\tprops?: CharProps\n\t) {\n\t\tconst val = createElement('span', {\n\t\t\tclassName: 'symbol__value',\n\t\t\ttextContent: value\n\t\t})\n\t\tsuper(\n\t\t\tsection,\n\t\t\tvalue,\n\t\t\tcreateElement(\n\t\t\t\t'span',\n\t\t\t\t{\n\t\t\t\t\tpart: `symbol ${type}`,\n\t\t\t\t\tclassName: `symbol`\n\t\t\t\t},\n\t\t\t\t[val]\n\t\t\t),\n\t\t\tprops\n\t\t)\n\t\tthis._children.set(\n\t\t\tvalue,\n\t\t\tnew AnimatePresence(this.flow, val, {\n\t\t\t\tonRemove: this._onChildRemove(value)\n\t\t\t})\n\t\t)\n\t}\n\n\tprivate _children = new Map<KeyedSymbolPart['value'], AnimatePresence>()\n\n\tprivate _prevOffset?: number\n\n\twillUpdate(parentRect: DOMRect) {\n\t\tif (this.type === 'decimal') return // decimal never needs animation b/c it's the first in a left aligned section and never moves\n\n\t\tconst rect = this.el.getBoundingClientRect()\n\t\tthis._prevOffset = rect[this.section.justify] - parentRect[this.section.justify]\n\t}\n\n\tprivate _onChildRemove =\n\t\t(key: KeyedSymbolPart['value']): OnRemove =>\n\t\t() => {\n\t\t\tthis._children.delete(key)\n\t\t}\n\n\tupdate(value: KeyedSymbolPart['value']) {\n\t\tif (this.value !== value) {\n\t\t\t// Pop the current value:\n\t\t\tconst current = this._children.get(this.value)\n\t\t\tif (current) current.present = false\n\n\t\t\t// If we already have the new value and it hasn't finished removing, reclaim it:\n\t\t\tconst prev = this._children.get(value)\n\t\t\tif (prev) {\n\t\t\t\tprev.present = true\n\t\t\t} else {\n\t\t\t\t// Otherwise, create a new one:\n\t\t\t\tconst newVal = createElement('span', {\n\t\t\t\t\tclassName: 'symbol__value',\n\t\t\t\t\ttextContent: value\n\t\t\t\t})\n\t\t\t\tthis.el.appendChild(newVal)\n\t\t\t\tthis._children.set(\n\t\t\t\t\tvalue,\n\t\t\t\t\tnew AnimatePresence(this.flow, newVal, {\n\t\t\t\t\t\tanimateIn: true,\n\t\t\t\t\t\tonRemove: this._onChildRemove(value)\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthis.value = value\n\t}\n\n\tdidUpdate(parentRect: DOMRect) {\n\t\tif (this.type === 'decimal') return\n\n\t\tconst rect = this.el.getBoundingClientRect()\n\t\tconst offset = rect[this.section.justify] - parentRect[this.section.justify]\n\t\tconst dx = this._prevOffset! - offset\n\n\t\tif (dx)\n\t\t\tthis.el.animate(\n\t\t\t\t{\n\t\t\t\t\ttransform: [`translateX(${dx}px)`, 'none']\n\t\t\t\t},\n\t\t\t\t{ ...this.flow.transformTiming, composite: 'accumulate' }\n\t\t\t)\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/plugins/continuous.ts",
    "content": "import { max } from '../util/math'\nimport type { Plugin } from '.'\nimport type NumberFlowLite from '../lite'\n\nconst startingPos = new WeakMap<NumberFlowLite, number | undefined>()\n\n/**\n * Makes number transitions appear to pass through in between numbers.\n */\nexport const continuous: Plugin = {\n\tonUpdate(data, prev, flow) {\n\t\tstartingPos.set(flow, undefined)\n\t\tif (!flow.computedTrend) return\n\n\t\t// Find the starting pos based on the parts, not the value,\n\t\t// to handle e.g. compact notation where value = 1000 and integer part = 1\n\t\tconst prevNumber = prev.integer\n\t\t\t.concat(prev.fraction)\n\t\t\t.filter((p) => p.type === 'integer' || p.type === 'fraction')\n\t\tconst number = data.integer\n\t\t\t.concat(data.fraction)\n\t\t\t.filter((p) => p.type === 'integer' || p.type === 'fraction')\n\t\tconst firstChangedPrev = prevNumber.find(\n\t\t\t(pp) => !number.find((p) => p.pos === pp.pos && p.value === pp.value)\n\t\t)\n\t\tconst firstChanged = number.find(\n\t\t\t(p) => !prevNumber.find((pp) => p.pos === pp.pos && p.value === pp.value)\n\t\t)\n\t\tstartingPos.set(flow, max(firstChangedPrev?.pos, firstChanged?.pos))\n\t},\n\tgetDelta(value, prev, digit) {\n\t\tconst diff = value - prev\n\t\tconst starting = startingPos.get(digit.flow)\n\t\t// Loop once if it's continuous:\n\t\tif (!diff && starting != null && starting >= digit.pos) {\n\t\t\treturn digit.length * digit.flow.computedTrend! // trend must exist if there's starting\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/plugins/index.ts",
    "content": "import type NumberFlowLite from '../lite'\nimport type { Digit } from '../lite'\nimport type { Data } from '../formatter'\n\nexport type Plugin = {\n\tonUpdate?(data: Data, prev: Data, context: NumberFlowLite): void\n\tgetDelta?(value: number, prev: number, context: Digit): number | void\n}\n\nexport { continuous } from './continuous'\n"
  },
  {
    "path": "packages/number-flow/src/ssr.ts",
    "content": "import type { Data, KeyedNumberPart } from './formatter'\nimport { css, html } from './util/string'\nimport { halfMaskHeight, maskHeight } from './styles'\nimport { BROWSER } from 'esm-env'\n\nexport const ServerSafeHTMLElement = BROWSER\n\t? HTMLElement\n\t: (class {} as unknown as typeof HTMLElement) // for types\n\nexport const styles = css`\n\t:host {\n\t\tdisplay: inline-block;\n\t\tdirection: ltr;\n\t\twhite-space: nowrap;\n\t\tline-height: 1;\n\t}\n\n\tspan {\n\t\tdisplay: inline-block;\n\t}\n\n\t:host([data-will-change]) span {\n\t\twill-change: transform;\n\t}\n\n\t.number,\n\t.digit {\n\t\tpadding: ${halfMaskHeight} 0;\n\t}\n\n\t.symbol {\n\t\twhite-space: pre; /* some symbols are spaces or thin spaces */\n\t}\n`\n\nconst renderPart = (part: KeyedNumberPart) =>\n\t`<span class=\"${part.type === 'integer' || part.type === 'fraction' ? 'digit' : 'symbol'}\" part=\"${part.type === 'integer' || part.type === 'fraction' ? `digit ${part.type}-digit` : `symbol ${part.type}`}\">${part.value}</span>`\n\nconst renderSection = (section: KeyedNumberPart[], part: string) =>\n\t`<span part=\"${part}\">${section.reduce((str, p) => str + renderPart(p), '')}</span>`\n\nexport const renderFallbackStyles = (elementSuffix = '') => css`\n\t:where(number-flow${elementSuffix}) {\n\t\tline-height: 1;\n\t}\n\n\tnumber-flow${elementSuffix} > span {\n\t\tfont-kerning: none;\n\t\tdisplay: inline-block;\n\t\tpadding: ${maskHeight} 0;\n\t}\n`\n\nexport const renderInnerHTML = (\n\tdata: Data,\n\t{ nonce, elementSuffix }: { nonce?: string; elementSuffix?: string } = {}\n) =>\n\t// shadowroot=\"open\" non-standard attribute for old Chrome:\n\thtml`<template shadowroot=\"open\" shadowrootmode=\"open\"\n\t\t\t><style${nonce ? ` nonce=\"${nonce}\"` : ''}>${styles}</style\n\t\t\t><span role=\"img\" aria-label=\"${data.valueAsString}\"\n\t\t\t\t>${renderSection(data.pre, 'left')}<span part=\"number\" class=\"number\"\n\t\t\t\t\t>${renderSection(data.integer, 'integer')}${renderSection(data.fraction, 'fraction')}</span\n\t\t\t\t>${renderSection(data.post, 'right')}</span\n\t\t\t></template\n\t\t><style${nonce ? ` nonce=\"${nonce}\"` : ''}>${renderFallbackStyles(elementSuffix)}</style\n\t\t><span>${data.valueAsString}</span>`\n"
  },
  {
    "path": "packages/number-flow/src/styles.ts",
    "content": "import { BROWSER } from 'esm-env'\nimport { css } from './util/string'\n\nexport const supportsLinear =\n\tBROWSER &&\n\t(() => {\n\t\ttry {\n\t\t\t// We can't use CSS.supports because it sometimes gives\n\t\t\t// false positives compared to .animate support:\n\t\t\tdocument.createElement('div').animate({ opacity: 0 }, { easing: 'linear(0, 1)' })\n\t\t} catch (e) {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})()\n\nexport const supportsMod =\n\tBROWSER && typeof CSS !== 'undefined' && CSS.supports && CSS.supports('line-height', 'mod(1,1)')\n\nexport const prefersReducedMotion =\n\tBROWSER && typeof matchMedia !== 'undefined'\n\t\t? matchMedia('(prefers-reduced-motion: reduce)')\n\t\t: null\n\n// Register animated vars:\nexport const opacityDeltaVar = '--_number-flow-d-opacity'\nexport const widthDeltaVar = '--_number-flow-d-width'\nexport const dxVar = '--_number-flow-dx'\nexport const deltaVar = '--_number-flow-d'\n\nexport const supportsAtProperty = (() => {\n\ttry {\n\t\tCSS.registerProperty({\n\t\t\tname: opacityDeltaVar,\n\t\t\tsyntax: '<number>',\n\t\t\tinherits: false,\n\t\t\tinitialValue: '0'\n\t\t})\n\n\t\tCSS.registerProperty({\n\t\t\tname: dxVar,\n\t\t\tsyntax: '<length>',\n\t\t\tinherits: true,\n\t\t\tinitialValue: '0px'\n\t\t})\n\n\t\tCSS.registerProperty({\n\t\t\tname: widthDeltaVar,\n\t\t\tsyntax: '<number>',\n\t\t\tinherits: false,\n\t\t\tinitialValue: '0'\n\t\t})\n\n\t\tCSS.registerProperty({\n\t\t\tname: deltaVar,\n\t\t\tsyntax: '<number>',\n\t\t\tinherits: true,\n\t\t\tinitialValue: '0'\n\t\t})\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n})()\n\n// Don't use CSS.registerProperty for vars needed during SSR:\n\n// Mask technique taken from:\n// https://expensive.toys/blog/blur-vignette\n\n// Use round() to avoid fractional pixels which fixes alignment in Safari:\nexport const halfMaskHeight = `round(nearest, calc(var(--number-flow-mask-height, 0.25em) / 2), 1px)`\nexport const maskHeight = `calc(${halfMaskHeight} * 2)`\nconst maskWidth = 'var(--number-flow-mask-width, 0.5em)'\nconst scaledMaskWidth = `calc(${maskWidth} / var(--scale-x))`\n\nconst cornerGradient = `#000 0, transparent 71%` // or transparent ${maskWidth}\n\nconst styles = css`\n\t:host {\n\t\tdisplay: inline-block;\n\t\tdirection: ltr;\n\t\twhite-space: nowrap;\n\t\tisolation: isolate; /* for .number z-index */\n\t\t/* Technically this is only needed on the .number, but applying it here makes the ::selection the same height for the whole element: */\n\t\tline-height: 1;\n\t}\n\n\t.number,\n\t.number__inner {\n\t\tdisplay: inline-block;\n\t\ttransform-origin: left top;\n\t}\n\n\t:host([data-will-change]) :is(.number, .number__inner, .section, .digit, .digit__num, .symbol) {\n\t\twill-change: transform;\n\t}\n\n\t.number {\n\t\t--scale-x: calc(1 + var(${widthDeltaVar}) / var(--width));\n\t\ttransform: translateX(var(${dxVar})) scaleX(var(--scale-x));\n\n\t\tmargin: 0 calc(-1 * ${maskWidth});\n\t\tposition: relative; /* for z-index */\n\n\t\t/* overflow: clip; /* helpful to not affect page layout, but breaks baseline alignment in Safari :/ */\n\t\t/* -webkit- prefixed properties have better support than unprefixed ones: */\n\t\t-webkit-mask-image:\n\t\t\t/* Horizontal: */\n\t\t\tlinear-gradient(\n\t\t\t\tto right,\n\t\t\t\ttransparent 0,\n\t\t\t\t#000 ${scaledMaskWidth},\n\t\t\t\t#000 calc(100% - ${scaledMaskWidth}),\n\t\t\t\ttransparent\n\t\t\t),\n\t\t\t/* Vertical: */\n\t\t\t\tlinear-gradient(\n\t\t\t\t\tto bottom,\n\t\t\t\t\ttransparent 0,\n\t\t\t\t\t#000 ${maskHeight},\n\t\t\t\t\t#000 calc(100% - ${maskHeight}),\n\t\t\t\t\ttransparent 100%\n\t\t\t\t),\n\t\t\t/* TL corner */ radial-gradient(at bottom right, ${cornerGradient}),\n\t\t\t/* TR corner */ radial-gradient(at bottom left, ${cornerGradient}),\n\t\t\t/* BR corner */ radial-gradient(at top left, ${cornerGradient}),\n\t\t\t/* BL corner */ radial-gradient(at top right, ${cornerGradient});\n\t\t-webkit-mask-size:\n\t\t\t100% calc(100% - ${maskHeight} * 2),\n\t\t\tcalc(100% - ${scaledMaskWidth} * 2) 100%,\n\t\t\t${scaledMaskWidth} ${maskHeight},\n\t\t\t${scaledMaskWidth} ${maskHeight},\n\t\t\t${scaledMaskWidth} ${maskHeight},\n\t\t\t${scaledMaskWidth} ${maskHeight};\n\t\t-webkit-mask-position:\n\t\t\tcenter,\n\t\t\tcenter,\n\t\t\ttop left,\n\t\t\ttop right,\n\t\t\tbottom right,\n\t\t\tbottom left;\n\t\t-webkit-mask-repeat: no-repeat;\n\t}\n\n\t/* Small improvement for ::selection when not animating: */\n\t/* Reverted because you can see it change when char height < 1em: */\n\t/*.number:not(:has(.digit.is-spinning)) {\n\t\t-webkit-mask-image: none;\n\t}*/\n\n\t.number__inner {\n\t\tpadding: ${halfMaskHeight} ${maskWidth};\n\t\t/* invert parent's: */\n\t\ttransform: scaleX(calc(1 / var(--scale-x))) translateX(calc(-1 * var(${dxVar})));\n\t}\n\n\t/* Put number underneath other sections. Negative z-index messed up text cursor and selection, weirdly: */\n\t:host > :not(.number) {\n\t\tz-index: 5;\n\t}\n\n\t.section,\n\t.symbol {\n\t\tdisplay: inline-block;\n\t\t/* for exiting (> [inert]): */\n\t\tposition: relative;\n\t\tisolation: isolate; /* also helpful for mix-blend-mode in symbol__value */\n\t}\n\n\t.section::after {\n\t\t/*\n\t\t * We seem to need some type of character to ensure baseline alignment continues working\n\t\t * even when empty\n\t\t */\n\t\tcontent: '\\200b'; /* zero-width space */\n\t\tdisplay: inline-block;\n\t}\n\n\t.section--justify-left {\n\t\ttransform-origin: center left;\n\t}\n\n\t.section--justify-right {\n\t\ttransform-origin: center right;\n\t}\n\n\t.section > [inert],\n\t.symbol > [inert] {\n\t\tmargin: 0 !important; /* to override any user styles */\n\t\tposition: absolute !important; /* ^ */\n\t\tz-index: -1;\n\t}\n\n\t.digit {\n\t\tdisplay: inline-block;\n\t\tposition: relative;\n\t\t--c: var(--current) + var(${deltaVar});\n\t}\n\n\t.digit__num,\n\t.number .section::after {\n\t\tpadding: ${halfMaskHeight} 0;\n\t}\n\n\t.digit__num {\n\t\tdisplay: inline-block;\n\t\t/* Claude + https://buildui.com/recipes/animated-counter */\n\t\t--offset-raw: mod(var(--length) + var(--n) - mod(var(--c), var(--length)), var(--length));\n\t\t--offset: calc(\n\t\t\tvar(--offset-raw) - var(--length) * round(down, var(--offset-raw) / (var(--length) / 2), 1)\n\t\t);\n\t\t/* Technically we just need var(--offset)*100%, but clamping should reduce the layer size: */\n\t\t--y: clamp(-100%, var(--offset) * 100%, 100%);\n\t\ttransform: translateY(var(--y));\n\t}\n\n\t.digit__num[inert] {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 50%;\n\t\ttransform: translateX(-50%) translateY(var(--y));\n\t}\n\n\t.digit:not(.is-spinning) .digit__num[inert] {\n\t\tdisplay: none;\n\t}\n\n\t.symbol__value {\n\t\tdisplay: inline-block;\n\t\tmix-blend-mode: plus-lighter; /* better crossfades e.g. + <-> - */\n\t\twhite-space: pre; /* some symbols are spaces or thin spaces */\n\t}\n\n\t.section--justify-left .symbol > [inert] {\n\t\tleft: 0;\n\t}\n\n\t.section--justify-right .symbol > [inert] {\n\t\tright: 0;\n\t}\n\n\t.animate-presence {\n\t\topacity: calc(1 + var(${opacityDeltaVar}));\n\t}\n`\n\nexport default styles\n"
  },
  {
    "path": "packages/number-flow/src/util/dom.ts",
    "content": "import { BROWSER } from 'esm-env'\n\ntype ExcludeReadonly<T> = {\n\t-readonly [K in keyof T as T[K] extends Readonly<any> ? never : K]: T[K]\n}\n\nexport type HTMLProps<K extends keyof HTMLElementTagNameMap> = Partial<\n\tExcludeReadonly<HTMLElementTagNameMap[K]> & { part: string }\n>\n\nexport const createElement = <K extends keyof HTMLElementTagNameMap>(\n\ttagName: K,\n\toptionsOrChildren?: HTMLProps<K> | Node[],\n\t_children?: Node[]\n): HTMLElementTagNameMap[K] => {\n\tconst element = document.createElement(tagName)\n\tconst [options, children] = Array.isArray(optionsOrChildren)\n\t\t? [undefined, optionsOrChildren]\n\t\t: [optionsOrChildren, _children]\n\tif (options) Object.assign(element, options)\n\tchildren?.forEach((child) => element.appendChild(child))\n\treturn element\n}\n\nexport type Justify = 'left' | 'right'\n\n// Makeshift .offsetRight\nexport const offset = (el: HTMLElement, justify: Justify) => {\n\treturn justify === 'left'\n\t\t? el.offsetLeft\n\t\t: ((el.offsetParent instanceof HTMLElement ? el.offsetParent : null)?.offsetWidth ?? 0) -\n\t\t\t\tel.offsetWidth -\n\t\t\t\tel.offsetLeft\n}\n\nexport const visible = (el: HTMLElement) => el.offsetWidth > 0 && el.offsetHeight > 0\n\n// HMR-safe customElements.define\nexport const define = (name: string, constructor: CustomElementConstructor) => {\n\t// Opt for the simpler check, the constructor check breaks in Next.js force-static,\n\t// Svelte REPL, and Webpack Module Federation:\n\tif (BROWSER && !customElements.get(name) /* !== constructor*/)\n\t\tcustomElements.define(name, constructor)\n}\n"
  },
  {
    "path": "packages/number-flow/src/util/iterable.ts",
    "content": "export function forEach<T>(\n\tarr: T[],\n\tfn: (item: T, index: number) => void,\n\t{ reverse = false } = {}\n) {\n\tconst len = arr.length\n\tfor (let i = reverse ? len - 1 : 0; reverse ? i >= 0 : i < len; reverse ? i-- : i++) {\n\t\tfn(arr[i]!, i)\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/src/util/math.ts",
    "content": "// Math.max that handles nullish numbers\nexport const max = (n1?: number, n2?: number) => {\n\tif (n1 == null) return n2\n\tif (n2 == null) return n1\n\treturn Math.max(n1, n2)\n}"
  },
  {
    "path": "packages/number-flow/src/util/string.ts",
    "content": "export const html = String.raw\nexport const css = String.raw\n"
  },
  {
    "path": "packages/number-flow/src/util/types.ts",
    "content": "export type Mutable<T> = {\n\t-readonly [P in keyof T]: T[P]\n}\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/.gitignore",
    "content": "# build output\ndist/\n\n# generated types\n.astro/\n\n# testing\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# jetbrains setting folder\n.idea/\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/README.md",
    "content": "# Astro Starter Kit: Basics\n\n```sh\npnpm create astro@latest -- --template basics\n```\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)\n[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)\n\n> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!\n\n![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)\n\n## 🚀 Project Structure\n\nInside of your Astro project, you'll see the following folders and files:\n\n```text\n/\n├── public/\n│   └── favicon.svg\n├── src/\n│   ├── layouts/\n│   │   └── Layout.astro\n│   └── pages/\n│       └── index.astro\n└── package.json\n```\n\nTo learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).\n\n## 🧞 Commands\n\nAll commands are run from the root of the project, from a terminal:\n\n| Command                   | Action                                           |\n| :------------------------ | :----------------------------------------------- |\n| `pnpm install`             | Installs dependencies                            |\n| `pnpm dev`             | Starts local dev server at `localhost:4321`      |\n| `pnpm build`           | Build your production site to `./dist/`          |\n| `pnpm preview`         | Preview your build locally, before deploying     |\n| `pnpm astro ...`       | Run CLI commands like `astro add`, `astro check` |\n| `pnpm astro -- --help` | Get help using the Astro CLI                     |\n\n## 👀 Want to learn more?\n\nFeel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/astro.config.mjs",
    "content": "import { defineConfig } from 'astro/config'\nimport tailwindcss from '@tailwindcss/vite'\nimport node from '@astrojs/node'\n\n// https://astro.build/config\nexport default defineConfig({\n\tdevToolbar: {\n\t\tenabled: false\n\t},\n\tvite: {\n\t\tplugins: [tailwindcss()]\n\t},\n\tadapter: node({\n\t\tmode: 'standalone'\n\t})\n})\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/package.json",
    "content": "{\n\t\"name\": \"test\",\n\t\"type\": \"module\",\n\t\"private\": true,\n\t\"version\": \"0.0.1\",\n\t\"scripts\": {\n\t\t\"dev\": \"astro dev --port 3039\",\n\t\t\"build\": \"astro build\",\n\t\t\"start\": \"astro preview --port 3039\",\n\t\t\"preview\": \"astro preview --port 3039\",\n\t\t\"astro\": \"astro\",\n\t\t\"test\": \"playwright test\",\n\t\t\"test:ui\": \"playwright test --ui\",\n\t\t\"test:update\": \"playwright test --update-snapshots\"\n\t},\n\t\"dependencies\": {\n\t\t\"number-flow\": \"workspace:*\",\n\t\t\"@astrojs/node\": \"^9.5.4\",\n\t\t\"@tailwindcss/vite\": \"^4.0.9\",\n\t\t\"astro\": \"^5.17.3\",\n\t\t\"tailwindcss\": \"^4.0.9\"\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/playwright.config.ts",
    "content": "export { config as default } from '../../../../../lib/playwright'\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/layouts/Layout.astro",
    "content": "---\nimport '../styles/global.css'\n---\n\n<!doctype html>\n<html lang=\"en\">\n\t<body>\n\t\t<slot />\n\t</body>\n</html>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/middleware.ts",
    "content": "import { defineMiddleware } from 'astro:middleware'\nimport { createHash } from 'node:crypto'\nimport { styles } from 'number-flow'\n\nconst nonceCsp = \"style-src 'nonce-test-nonce'\"\nconst hash = (style: string) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`\nconst hashesCsp = `style-src ${styles.map(hash).join(' ')}`\n\nexport const onRequest = defineMiddleware(async ({ url }, next) => {\n\tconst response = await next()\n\tif (url.pathname === '/nonce') {\n\t\tresponse.headers.set('Content-Security-Policy', nonceCsp)\n\t}\n\tif (url.pathname === '/hashes') {\n\t\tresponse.headers.set('Content-Security-Policy', hashesCsp)\n\t}\n\treturn response\n})\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/can-animate.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { canAnimate, prefersReducedMotion } from 'number-flow'\n---\n\n<Layout>\n\t<div>\n\t\t<p data-testid=\"default\">{String(!(prefersReducedMotion?.matches ?? false) && canAnimate)}</p>\n\t\t<p data-testid=\"disrespect-motion-preference\">\n\t\t\t{String(canAnimate)}\n\t\t</p>\n\t</div>\n</Layout>\n\n<script>\n\timport { canAnimate, prefersReducedMotion } from 'number-flow'\n\tconst def = document.querySelector('[data-testid=\"default\"]')\n\tconst disrespect = document.querySelector('[data-testid=\"disrespect-motion-preference\"]')\n\n\tif (def) def.textContent = String(!(prefersReducedMotion?.matches ?? false) && canAnimate)\n\tif (disrespect) disrespect.textContent = String(canAnimate)\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/group-1-unchanged.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { renderInnerHTML } from 'number-flow'\n---\n\n<Layout>\n\t<div>\n\t\t<number-flow-group>\n\t\t\t<number-flow set:html={renderInnerHTML(42)} /><number-flow set:html={renderInnerHTML(0)} />\n\t\t</number-flow-group>\n\t</div>\n\t<button id=\"change\"> Change and pause </button>\n\t<br />\n\t<button id=\"resume\"> Resume </button>\n</Layout>\n\n<script>\n\timport 'number-flow'\n\timport 'number-flow/group'\n\n\tconst [flow1, flow2] = document.getElementsByTagName('number-flow')\n\tconst changeBtn = document.getElementById('change')\n\tconst resumeBtn = document.getElementById('resume')\n\n\tflow1?.update(42)\n\tflow2?.update(0)\n\n\tchangeBtn?.addEventListener('click', () => {\n\t\tflow1?.update(152000)\n\t\tqueueMicrotask(() => {\n\t\t\t;[\n\t\t\t\t...(flow1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t...(flow2?.shadowRoot?.getAnimations() ?? [])\n\t\t\t].forEach((a) => {\n\t\t\t\ta.pause()\n\t\t\t\ta.currentTime = 300\n\t\t\t})\n\t\t})\n\t})\n\n\tresumeBtn?.addEventListener('click', () => {\n\t\t;[\n\t\t\t...(flow1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(flow2?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.play()\n\t\t})\n\t})\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/hashes.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { renderInnerHTML } from 'number-flow'\nexport const prerender = false\n---\n\n<Layout>\n\t<number-flow set:html={renderInnerHTML(42)} />\n</Layout>\n\n<script>\n\timport 'number-flow'\n\n\tconst flow = document.querySelector('number-flow')\n\tflow?.update(42)\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/index.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { renderInnerHTML } from 'number-flow'\n---\n\n<Layout>\n\t<div>\n\t\tText node{' '}\n\t\t<number-flow-group>\n\t\t\t<number-flow\n\t\t\t\tid=\"flow1\"\n\t\t\t\tdata-testid=\"flow1\"\n\t\t\t\tset:html={renderInnerHTML(42, {\n\t\t\t\t\tformat: { style: 'currency', currency: 'USD' },\n\t\t\t\t\tlocales: 'zh-CN',\n\t\t\t\t\tnumberPrefix: ':',\n\t\t\t\t\tnumberSuffix: '/mo'\n\t\t\t\t})}\n\t\t\t/><number-flow id=\"flow2\" data-testid=\"flow2\" set:html={renderInnerHTML(42)} />\n\t\t</number-flow-group>\n\t</div>\n\t<button id=\"change\"> Change and pause </button>\n\t<br />\n\t<button id=\"resume\"> Resume </button>\n</Layout>\n\n<script>\n\timport 'number-flow'\n\timport { continuous } from 'number-flow'\n\timport 'number-flow/group'\n\n\tconst [flow1, flow2] = document.getElementsByTagName('number-flow')\n\tconst changeBtn = document.getElementById('change')\n\tconst resumeBtn = document.getElementById('resume')\n\n\tif (flow1 && flow2) {\n\t\tflow1.addEventListener('animationsstart', () => {\n\t\t\tconsole.log('start')\n\t\t})\n\t\tflow1.addEventListener('animationsfinish', () => {\n\t\t\tconsole.log('finish')\n\t\t})\n\t\tflow1.transformTiming = { easing: 'linear', duration: 500 }\n\t\tflow1.spinTiming = { easing: 'linear', duration: 800 }\n\t\tflow1.opacityTiming = { easing: 'linear', duration: 500 }\n\t\tflow1.format = { style: 'currency', currency: 'USD' }\n\t\tflow1.trend = () => -1\n\t\tflow1.locales = 'zh-CN'\n\t\tflow1.numberPrefix = ':'\n\t\tflow1.numberSuffix = '/mo'\n\t\tflow1.update(42)\n\n\t\tflow2.respectMotionPreference = false\n\t\tflow2.plugins = [continuous]\n\t\tflow2.digits = { 0: { max: 2 } }\n\t\tflow2.transformTiming = { easing: 'linear', duration: 500 }\n\t\tflow2.spinTiming = { easing: 'linear', duration: 800 }\n\t\tflow2.opacityTiming = { easing: 'linear', duration: 500 }\n\t\tflow2.update(42)\n\t}\n\n\tchangeBtn?.addEventListener('click', () => {\n\t\tflow1?.update(152)\n\t\tflow2?.update(152)\n\t\tqueueMicrotask(() => {\n\t\t\t;[\n\t\t\t\t...(flow1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t...(flow2?.shadowRoot?.getAnimations() ?? [])\n\t\t\t].forEach((a) => {\n\t\t\t\ta.pause()\n\t\t\t\ta.currentTime = 300\n\t\t\t})\n\t\t})\n\t})\n\n\tresumeBtn?.addEventListener('click', () => {\n\t\t;[\n\t\t\t...(flow1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(flow2?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.play()\n\t\t})\n\t})\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/nonce.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { renderInnerHTML } from 'number-flow'\nexport const prerender = false\n---\n\n<Layout>\n\t<number-flow\n\t\tnonce=\"test-nonce\"\n\t\tset:html={renderInnerHTML(42, { nonce: 'test-nonce' })}\n\t/><number-flow nonce=\"test-nonce\" set:html={renderInnerHTML(42, { nonce: 'test-nonce' })} />\n</Layout>\n\n<script>\n\timport 'number-flow'\n\n\tdocument.querySelectorAll('number-flow').forEach((flow) => {\n\t\tflow.update(42)\n\t})\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/pages/thrashing.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\nimport { renderInnerHTML } from 'number-flow'\n---\n\n<Layout>\n\t<number-flow-group>\n\t\t<number-flow\n\t\t\tid=\"flow1\"\n\t\t\tdata-testid=\"flow1\"\n\t\t\tset:html={renderInnerHTML(42)}\n\t\t/><number-flow id=\"flow2\" data-testid=\"flow2\" set:html={renderInnerHTML(42)} />\n\t</number-flow-group>\n</Layout>\n\n<script>\n\timport 'number-flow'\n\timport 'number-flow/group'\n\n\tconst [flow1, flow2] = document.getElementsByTagName('number-flow')\n\n\tif (flow1 && flow2) {\n\t\tflow1.update(42)\n\t\tflow2.update(42)\n\n\t\twindow.addEventListener('load', () => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tflow1.format = { style: 'currency', currency: 'USD' }\n\t\t\t\tflow1.numberSuffix = '/mo'\n\t\t\t\tflow2.format = { style: 'currency', currency: 'USD' }\n\t\t\t\tflow2.numberSuffix = '/mo'\n\n\t\t\t\tflow1.update(1250.50)\n\t\t\t\tflow2.update(1250.50)\n\t\t\t}, 3000)\n\t\t})\n\t}\n</script>\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/src/styles/global.css",
    "content": "@import 'tailwindcss';\n"
  },
  {
    "path": "packages/number-flow/test/apps/astro/tsconfig.json",
    "content": "{\n\t\"extends\": \"astro/tsconfigs/strictest\",\n\t\"include\": [\".astro/types.d.ts\", \"**/*\"],\n\t\"exclude\": [\"dist\"]\n}\n"
  },
  {
    "path": "packages/number-flow/tsconfig.build.json",
    "content": "{\n\t\"extends\": [\"./tsconfig.json\", \"../../tsconfig.build.json\"]\n}\n"
  },
  {
    "path": "packages/number-flow/tsconfig.json",
    "content": "{\n\t\"extends\": \"../../tsconfig.json\",\n\t\"include\": [\"src\"],\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"declaration\": true,\n\t\t\"types\": [\"@vitest/browser/providers/playwright\"],\n\t\t\"outDir\": \"./dist\"\n\t}\n}\n"
  },
  {
    "path": "packages/number-flow/vite.config.mjs",
    "content": "import { resolve } from 'path'\nimport { defineConfig, createFilter } from 'vite'\nimport typescript from '@rollup/plugin-typescript'\n// import minifyLiterals from 'rollup-plugin-minify-html-literals-v3'\nimport { minifyRaw as minifyCSS } from 'babel-plugin-styled-components/lib/minify'\nimport MagicString from 'magic-string'\nimport * as pl from 'parse-literals'\n\nconst outDir = resolve(__dirname, 'dist')\n\nexport default defineConfig(({ mode }) => ({\n\tbuild: {\n\t\toutDir,\n\t\tlib: {\n\t\t\tentry: {\n\t\t\t\tindex: resolve(__dirname, 'src/index.ts'),\n\t\t\t\tlite: resolve(__dirname, 'src/lite.ts'),\n\t\t\t\tcsp: resolve(__dirname, 'src/csp.ts'),\n\t\t\t\tgroup: resolve(__dirname, 'src/group.ts'),\n\t\t\t\tplugins: resolve(__dirname, 'src/plugins/index.ts')\n\t\t\t},\n\t\t\tformats: ['es', 'cjs']\n\t\t},\n\t\trollupOptions: {\n\t\t\texternal: ['esm-env'],\n\t\t\tplugins: [\n\t\t\t\t// Caused issues with CSS:\n\t\t\t\t// minifyLiterals({\n\t\t\t\t// \t// Couldn't get the plugin to work with css``, so disable it and handle it separately:\n\t\t\t\t// \tminifyOptions: {\n\t\t\t\t// \t\tminifyCSS: false\n\t\t\t\t// \t}\n\t\t\t\t// }),\n\t\t\t\tminifyCSSLiterals(),\n\t\t\t\ttypescript({\n\t\t\t\t\ttsconfig: resolve(__dirname, `./tsconfig${mode === 'production' ? '.build' : ''}.json`)\n\t\t\t\t})\n\t\t\t]\n\t\t}\n\t}\n}))\n\n/** @satisfies {import('vite').Plugin} */\nfunction minifyCSSLiterals() {\n\tconst filter = createFilter(/\\.[jt]sx?$/)\n\n\treturn {\n\t\tname: 'vite-plugin-minify-css-literals',\n\t\tenforce: 'pre',\n\t\tapply: 'build',\n\t\ttransform(code, id) {\n\t\t\tif (!filter(id)) return null\n\n\t\t\tconst templates = pl.parseLiterals(code)\n\t\t\tif (!templates.length) return code\n\n\t\t\tconst ms = new MagicString(code)\n\t\t\ttemplates.forEach((template) => {\n\t\t\t\tif (template.tag !== 'css') return\n\t\t\t\ttemplate.parts.forEach((part) => {\n\t\t\t\t\tif (part.start < part.end) {\n\t\t\t\t\t\tconst mini = minifyCSS(part.text)[0]\n\t\t\t\t\t\t\t.replaceAll(';}', '}')\n\t\t\t\t\t\t\t// .replaceAll(/\\s+!important/g, '!important')\n\t\t\t\t\t\t\t.replaceAll(/linear-gradient\\(\\s+/g, 'linear-gradient(')\n\t\t\t\t\t\tms.overwrite(part.start, part.end, mini)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\treturn {\n\t\t\t\tcode: ms.toString(),\n\t\t\t\tmap: ms.generateMap({ hires: 'boundary' })\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/react/CHANGELOG.md",
    "content": "# @number-flow/react\n\n## 0.6.0\n\n### Minor Changes\n\n- Remove `--number-flow-char-height` CSS property in favor of `line-height` ([`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6))\n\n### Patch Changes\n\n- Updated dependencies [[`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6)]:\n  - number-flow@0.6.0\n\n## 0.5.14\n\n### Patch Changes\n\n- Updated dependencies [[`5cc3c9b`](https://github.com/barvian/number-flow/commit/5cc3c9b7f7c223719047b964b47dd9d3a42fa371)]:\n  - number-flow@0.5.12\n\n## 0.5.13\n\n### Patch Changes\n\n- Add CSP strategies (see [#170](https://github.com/barvian/number-flow/issues/170)) ([`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67))\n\n- Updated dependencies [[`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67)]:\n  - number-flow@0.5.11\n\n## 0.5.12\n\n### Patch Changes\n\n- Updated dependencies [[`4a6c26e`](https://github.com/barvian/number-flow/commit/4a6c26efe13d6ffc1b84ea75accf511f63669eb9)]:\n  - number-flow@0.5.10\n\n## 0.5.11\n\n### Patch Changes\n\n- Updated dependencies [[`bdf8ce9`](https://github.com/barvian/number-flow/commit/bdf8ce92df67d6147d7f56998c625fc29e1b7571)]:\n  - number-flow@0.5.9\n\n## 0.5.10\n\n### Patch Changes\n\n- Fix \"custom element already defined\" bugs ([`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d))\n\n- Updated dependencies [[`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d)]:\n  - number-flow@0.5.8\n\n## 0.5.9\n\n### Patch Changes\n\n- Updated dependencies [[`ee6a0a2`](https://github.com/barvian/number-flow/commit/ee6a0a2f2f09ba187b8df24cdfe0992ee7883192)]:\n  - number-flow@0.5.7\n\n## 0.5.8\n\n### Patch Changes\n\n- Updated dependencies [[`2539c4b`](https://github.com/barvian/number-flow/commit/2539c4b653fd4aaa17ef6b2ffd77b7a41454da08)]:\n  - number-flow@0.5.6\n\n## 0.5.7\n\n### Patch Changes\n\n- Updated dependencies [[`bc6476f`](https://github.com/barvian/number-flow/commit/bc6476f910ad58625491c23ed0a8768217f9ab57)]:\n  - number-flow@0.5.5\n\n## 0.5.6\n\n### Patch Changes\n\n- Release vanilla JS version ([`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d))\n\n- Updated dependencies [[`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d)]:\n  - number-flow@0.5.4\n\n## 0.5.5\n\n### Patch Changes\n\n- Revert mask-image change due to <1em char heights ([`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d))\n\n- Updated dependencies [[`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d)]:\n  - number-flow@0.5.3\n\n## 0.5.4\n\n### Patch Changes\n\n- Fix use within Server Components ([`1f27875`](https://github.com/barvian/number-flow/commit/1f278754acef7863fd81a27b22e739e2fdb009c0))\n\n## 0.5.3\n\n### Patch Changes\n\n- Fix Next.js 15.1.4 build errors (see [#95](https://github.com/barvian/number-flow/issues/95)) ([`4244809`](https://github.com/barvian/number-flow/commit/42448099b1652e658b0d5e4cb1968185753a16b6))\n\n## 0.5.2\n\n### Patch Changes\n\n- Improve `::selection` display and accessibility during transitions ([`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393))\n\n- Updated dependencies [[`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393)]:\n  - number-flow@0.5.2\n\n## 0.5.1\n\n### Patch Changes\n\n- Add missing symbol part to SSR ([`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948))\n\n- Updated dependencies [[`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948)]:\n  - number-flow@0.5.1\n\n## 0.5.0\n\n### Minor Changes\n\n- Move `continuous` prop into importable plugin ([`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff))\n\n### Patch Changes\n\n- Updated dependencies [[`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff)]:\n  - number-flow@0.5.0\n\n## 0.4.4\n\n### Patch Changes\n\n- Add symbol part for styling all symbols ([`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb))\n\n- Use useSyncExternalStore for hooks ([`e626a3f`](https://github.com/barvian/number-flow/commit/e626a3fc3776f06ad3b92d8f1afdb8586bf66a13))\n\n- Updated dependencies [[`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb)]:\n  - number-flow@0.4.2\n\n## 0.4.3\n\n### Patch Changes\n\n- Bump React peers ([`215a6bf`](https://github.com/barvian/number-flow/commit/215a6bf6f4d90a40114c5dd588fbf05c812b91f5))\n\n## 0.4.2\n\n### Patch Changes\n\n- Reduce bundle size ([`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3))\n\n- Fix group coming through as attribute ([`0efd6cd`](https://github.com/barvian/number-flow/commit/0efd6cd92af03d15e0010412b147d96eb30b1967))\n\n- Updated dependencies [[`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3)]:\n  - number-flow@0.4.1\n\n## 0.4.1\n\n### Patch Changes\n\n- Fix useCanAnimate hydration ([`e775131`](https://github.com/barvian/number-flow/commit/e775131a09628b98724dd1ec905d06ba78d06e21))\n\n## 0.4.0\n\n### Minor Changes\n\n- More flexible trend prop ([`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0))\n\n### Patch Changes\n\n- Add digits prop ([`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94))\n\n- Add `<NumberFlowGroup>` ([`228110c`](https://github.com/barvian/number-flow/commit/228110cf189e81fa030ffc61766290f02d273ff7))\n\n- Fix cursor and improve text selection ([`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809))\n\n- Updated dependencies [[`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0), [`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94), [`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809)]:\n  - number-flow@0.4.0\n\n## 0.3.5\n\n### Patch Changes\n\n- Switch to TS private properties to reduce bundle size ([`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd))\n\n- Updated dependencies [[`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd)]:\n  - number-flow@0.3.10\n\n## 0.3.4\n\n### Patch Changes\n\n- Expose parts for styling support ([`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4))\n\n- Updated dependencies [[`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4)]:\n  - number-flow@0.3.9\n\n## 0.3.3\n\n### Patch Changes\n\n- Add prefix & suffix props ([`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14))\n\n- Updated dependencies [[`9854f77`](https://github.com/barvian/number-flow/commit/9854f77e11561fe119bf9009ae1369389a64ba15), [`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14)]:\n  - number-flow@0.3.8\n\n## 0.3.2\n\n### Patch Changes\n\n- Updated dependencies [[`0fac2f6`](https://github.com/barvian/number-flow/commit/0fac2f69b239048054755c556afc3f0eb65767c9)]:\n  - number-flow@0.3.7\n\n## 0.3.1\n\n### Patch Changes\n\n- More defensive checks on browser globals (see [#58](https://github.com/barvian/number-flow/issues/58)) ([#59](https://github.com/barvian/number-flow/pull/59))\n\n- Updated dependencies [[`13a66fb`](https://github.com/barvian/number-flow/commit/13a66fba336c53687664ad9b859ec705891fce2a)]:\n  - number-flow@0.3.6\n\n## 0.3.0\n\n### Minor Changes\n\n- Rename number-flow element to avoid conflicts between wrappers ([`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac))\n\n### Patch Changes\n\n- fix animation handler types ([`7534f2e`](https://github.com/barvian/number-flow/commit/7534f2e35d6e871b1b13a008b8a923c23e1077e6))\n\n- Updated dependencies [[`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac)]:\n  - number-flow@0.3.5\n\n## 0.2.6\n\n### Patch Changes\n\n- Updated dependencies [[`ff966f4`](https://github.com/barvian/number-flow/commit/ff966f489eaeeacc72b35a8ee4c8cc13fe894eb6)]:\n  - number-flow@0.3.4\n\n## 0.2.5\n\n### Patch Changes\n\n- remove unused esm-env dep ([`46ffa56`](https://github.com/barvian/number-flow/commit/46ffa569d1a0fefec0219d0f5808be51b7f3f612))\n\n## 0.2.4\n\n### Patch Changes\n\n- Updated dependencies [[`be3f7da`](https://github.com/barvian/number-flow/commit/be3f7da7ee88b6ab35f67736c98edcfb6909543d)]:\n  - number-flow@0.3.3\n"
  },
  {
    "path": "packages/react/README.md",
    "content": "[![NumberFlow for React](https://number-flow.barvian.me/preview.webp)](https://number-flow.barvian.me)\n\n# NumberFlow for React\n\nAn animated number component.\n\n[![NPM Version](https://img.shields.io/npm/v/@number-flow/react.svg)](https://npmjs.com/package/@number-flow/react)\n[![Bundle size](https://badgen.net/bundlephobia/minzip/@number-flow/react@latest)](https://bundlephobia.com/package/@number-flow/react@latest)\n[![Follow @mbarvian](https://img.shields.io/twitter/follow/mbarvian.svg?style=social&label=Follow)](https://x.com/mbarvian)\n\n## Documentation\n\nFor full documentation, visit [number-flow.barvian.me](https://number-flow.barvian.me).\n"
  },
  {
    "path": "packages/react/package.json",
    "content": "{\n\t\"name\": \"@number-flow/react\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"version\": \"0.6.0\",\n\t\"author\": {\n\t\t\"name\": \"Maxwell Barvian\",\n\t\t\"email\": \"max@barvian.me\",\n\t\t\"url\": \"https://barvian.me\"\n\t},\n\t\"description\": \"A component to transition and format numbers.\",\n\t\"license\": \"MIT\",\n\t\"homepage\": \"https://number-flow.barvian.me\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/barvian/number-flow\",\n\t\t\"directory\": \"src\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/barvian/number-flow/issues\"\n\t},\n\t\"keywords\": [\n\t\t\"accessible\",\n\t\t\"odometer\",\n\t\t\"animation\",\n\t\t\"number-format\",\n\t\t\"number-animation\",\n\t\t\"animated-number\"\n\t],\n\t\"files\": [\n\t\t\"dist\",\n\t\t\"README.md\"\n\t],\n\t\"main\": \"./dist/index.js\",\n\t\"module\": \"./dist/index.mjs\",\n\t\"types\": \"./dist/index.d.ts\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"import\": {\n\t\t\t\t\"types\": \"./dist/index.d.mts\",\n\t\t\t\t\"default\": \"./dist/index.mjs\"\n\t\t\t},\n\t\t\t\"require\": {\n\t\t\t\t\"types\": \"./dist/index.d.ts\",\n\t\t\t\t\"default\": \"./dist/index.js\"\n\t\t\t}\n\t\t}\n\t},\n\t\"scripts\": {\n\t\t\"build\": \"bunchee --tsconfig tsconfig.build.json\",\n\t\t\"dev\": \"bunchee --watch\",\n\t\t\"test\": \"pnpm -r --workspace-concurrency 1 --filter=\\\"./test/apps/*\\\" test\"\n\t},\n\t\"dependencies\": {\n\t\t\"esm-env\": \"^1.1.4\",\n\t\t\"number-flow\": \"workspace:*\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@types/node\": \"^22.7.9\",\n\t\t\"@types/react\": \"^18.3.3\",\n\t\t\"@types/react-dom\": \"^18.3.0\",\n\t\t\"bunchee\": \"^6.3.1\",\n\t\t\"react\": \"^18.3.1\",\n\t\t\"react-dom\": \"^18.3.1\"\n\t},\n\t\"peerDependencies\": {\n\t\t\"react\": \"^18 || ^19\",\n\t\t\"react-dom\": \"^18 || ^19\"\n\t}\n}\n"
  },
  {
    "path": "packages/react/src/NumberFlow.tsx",
    "content": "'use client'\n\n// This has to be in a separate file for #95.\n//  Make sure tsup outputs both files.\n\nimport * as React from 'react'\nimport NumberFlowLite, {\n\ttype Value,\n\ttype Format,\n\ttype Props,\n\trenderInnerHTML,\n\tformatToData,\n\ttype Data,\n\tprefersReducedMotion as _prefersReducedMotion,\n\tcanAnimate as _canAnimate,\n\tdefine\n} from 'number-flow/lite'\nimport { BROWSER } from 'esm-env'\n\nconst REACT_MAJOR = parseInt(React.version.match(/^(\\d+)\\./)?.[1]!)\nconst isReact19 = REACT_MAJOR >= 19\n\n// Can't wait to not have to do this in React 19:\nconst OBSERVED_ATTRIBUTES = ['data', 'digits'] as const\ntype ObservedAttribute = (typeof OBSERVED_ATTRIBUTES)[number]\nexport class NumberFlowElement extends NumberFlowLite {\n\tstatic observedAttributes = isReact19 ? [] : OBSERVED_ATTRIBUTES\n\tattributeChangedCallback(attr: ObservedAttribute, _oldValue: string, newValue: string) {\n\t\tthis[attr] = JSON.parse(newValue)\n\t}\n}\n\ndefine('number-flow-react', NumberFlowElement)\n\ntype BaseProps = React.HTMLAttributes<NumberFlowElement> &\n\tPartial<Props> & {\n\t\tisolate?: boolean\n\t\twillChange?: boolean\n\t\tonAnimationsStart?: (e: CustomEvent<undefined>) => void\n\t\tonAnimationsFinish?: (e: CustomEvent<undefined>) => void\n\t}\n\ntype NumberFlowImplProps = BaseProps & {\n\tinnerRef: React.MutableRefObject<NumberFlowElement | undefined>\n\tgroup?: GroupContext\n\tdata: Data\n}\n\n// You're supposed to cache these between uses:\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString\n// Serialize to strings b/c React:\nconst formatters: Record<string, Intl.NumberFormat> = {}\n\n// Tiny workaround to support React 19 until it's released:\nfunction identity<T>(v: T) {\n\treturn v\n}\nconst serialize = isReact19 ? identity : JSON.stringify\n\nfunction splitProps<T extends Record<string, any>>(\n\tprops: T\n): [Omit<Props, 'digits'>, Omit<T, keyof Omit<Props, 'digits'>>] {\n\tconst {\n\t\ttransformTiming,\n\t\tspinTiming,\n\t\topacityTiming,\n\t\tanimated,\n\t\trespectMotionPreference,\n\t\ttrend,\n\t\tplugins,\n\t\t...rest\n\t} = props\n\n\treturn [\n\t\t{\n\t\t\ttransformTiming,\n\t\t\tspinTiming,\n\t\t\topacityTiming,\n\t\t\tanimated,\n\t\t\trespectMotionPreference,\n\t\t\ttrend,\n\t\t\tplugins\n\t\t},\n\t\trest\n\t]\n}\n\ntype NumberFlowImplState = {}\ntype NumberFlowImplSnapshot = (() => void) | null // React doesn't like undefined\n// We need a class component to use getSnapshotBeforeUpdate:\nclass NumberFlowImpl extends React.Component<\n\tNumberFlowImplProps,\n\tNumberFlowImplState,\n\tNumberFlowImplSnapshot\n> {\n\tconstructor(props: NumberFlowImplProps) {\n\t\tsuper(props)\n\t\tthis.handleRef = this.handleRef.bind(this)\n\t}\n\n\t// Update the non-`data` props to avoid JSON serialization\n\t// Data needs to be set in render still:\n\tupdateProperties(prevProps?: Readonly<NumberFlowImplProps>) {\n\t\tif (!this.el) return\n\n\t\tthis.el.batched = !this.props.isolate\n\t\tconst [nonData] = splitProps(this.props)\n\t\tObject.entries(nonData).forEach(([k, v]) => {\n\t\t\t// @ts-ignore\n\t\t\tthis.el![k] = v ?? NumberFlowElement.defaultProps[k]\n\t\t})\n\n\t\tif (prevProps?.onAnimationsStart)\n\t\t\tthis.el.removeEventListener('animationsstart', prevProps.onAnimationsStart as EventListener)\n\t\tif (this.props.onAnimationsStart)\n\t\t\tthis.el.addEventListener('animationsstart', this.props.onAnimationsStart as EventListener)\n\n\t\tif (prevProps?.onAnimationsFinish)\n\t\t\tthis.el.removeEventListener('animationsfinish', prevProps.onAnimationsFinish as EventListener)\n\t\tif (this.props.onAnimationsFinish)\n\t\t\tthis.el.addEventListener('animationsfinish', this.props.onAnimationsFinish as EventListener)\n\t}\n\n\toverride componentDidMount() {\n\t\tthis.updateProperties()\n\t\tif (isReact19 && this.el) {\n\t\t\t// React 19 needs this because the attributeChangedCallback isn't called:\n\t\t\tthis.el.digits = this.props.digits\n\t\t\tthis.el.data = this.props.data\n\t\t}\n\t}\n\n\toverride getSnapshotBeforeUpdate(prevProps: Readonly<NumberFlowImplProps>) {\n\t\tthis.updateProperties(prevProps)\n\t\tif (prevProps.data !== this.props.data) {\n\t\t\tif (this.props.group) {\n\t\t\t\tthis.props.group.willUpdate()\n\t\t\t\treturn () => this.props.group?.didUpdate()\n\t\t\t}\n\t\t\tif (!this.props.isolate) {\n\t\t\t\tthis.el?.willUpdate()\n\t\t\t\treturn () => this.el?.didUpdate()\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\toverride componentDidUpdate(\n\t\t_: Readonly<NumberFlowImplProps>,\n\t\t__: NumberFlowImplState,\n\t\tdidUpdate: NumberFlowImplSnapshot\n\t) {\n\t\tdidUpdate?.()\n\t}\n\n\tprivate el?: NumberFlowElement\n\n\thandleRef(el: NumberFlowElement) {\n\t\tif (this.props.innerRef) this.props.innerRef.current = el\n\t\tthis.el = el\n\t}\n\n\toverride render() {\n\t\tconst [\n\t\t\t_,\n\t\t\t{\n\t\t\t\tinnerRef,\n\t\t\t\tclassName,\n\t\t\t\tdata,\n\t\t\t\tnonce,\n\t\t\t\twillChange,\n\t\t\t\tisolate,\n\t\t\t\tgroup,\n\t\t\t\tdigits,\n\t\t\t\tonAnimationsStart,\n\t\t\t\tonAnimationsFinish,\n\t\t\t\t...rest\n\t\t\t}\n\t\t] = splitProps(this.props)\n\n\t\treturn (\n\t\t\t// @ts-expect-error missing types\n\t\t\t<number-flow-react\n\t\t\t\tref={this.handleRef}\n\t\t\t\tdata-will-change={willChange ? '' : undefined}\n\t\t\t\t// Have to rename this:\n\t\t\t\tclass={className}\n\t\t\t\tnonce={nonce}\n\t\t\t\t{...rest}\n\t\t\t\tdangerouslySetInnerHTML={{\n\t\t\t\t\t__html: BROWSER ? '' : renderInnerHTML(data, { nonce, elementSuffix: '-react' })\n\t\t\t\t}}\n\t\t\t\tsuppressHydrationWarning\n\t\t\t\tdigits={serialize(digits)}\n\t\t\t\t// Make sure data is set last, everything else is updated:\n\t\t\t\tdata={serialize(data)}\n\t\t\t/>\n\t\t)\n\t}\n}\n\nexport type NumberFlowProps = BaseProps & {\n\tvalue: Value\n\tlocales?: Intl.LocalesArgument\n\tformat?: Format\n\tprefix?: string\n\tsuffix?: string\n}\n\nconst NumberFlow = React.forwardRef<NumberFlowElement, NumberFlowProps>(function NumberFlow(\n\t{ value, locales, format, prefix, suffix, ...props },\n\t_ref\n) {\n\tReact.useImperativeHandle(_ref, () => ref.current!, [])\n\tconst ref = React.useRef<NumberFlowElement | undefined>(undefined)\n\tconst group = React.useContext(NumberFlowGroupContext)\n\tgroup?.useRegister(ref)\n\n\tconst localesString = React.useMemo(() => (locales ? JSON.stringify(locales) : ''), [locales])\n\tconst formatString = React.useMemo(() => (format ? JSON.stringify(format) : ''), [format])\n\tconst data = React.useMemo(() => {\n\t\tconst formatter = (formatters[`${localesString}:${formatString}`] ??= new Intl.NumberFormat(\n\t\t\tlocales,\n\t\t\tformat\n\t\t))\n\t\treturn formatToData(value, formatter, prefix, suffix)\n\t}, [value, localesString, formatString, prefix, suffix])\n\n\treturn <NumberFlowImpl {...props} group={group} data={data} innerRef={ref} />\n})\n\nexport default NumberFlow\n\n// NumberFlowGroup\n\ntype GroupContext = {\n\tuseRegister: (ref: React.MutableRefObject<NumberFlowElement | undefined>) => void\n\twillUpdate: () => void\n\tdidUpdate: () => void\n}\n\nconst NumberFlowGroupContext = React.createContext<GroupContext | undefined>(undefined)\n\nexport function NumberFlowGroup({ children }: { children: React.ReactNode }) {\n\tconst flows = React.useRef(new Set<React.MutableRefObject<NumberFlowElement | undefined>>())\n\tconst updating = React.useRef(false)\n\tconst pending = React.useRef(new WeakMap<NumberFlowElement, boolean>())\n\tconst value = React.useMemo<GroupContext>(\n\t\t() => ({\n\t\t\tuseRegister(ref) {\n\t\t\t\tReact.useEffect(() => {\n\t\t\t\t\tflows.current.add(ref)\n\t\t\t\t\treturn () => {\n\t\t\t\t\t\tflows.current.delete(ref)\n\t\t\t\t\t}\n\t\t\t\t}, [])\n\t\t\t},\n\t\t\twillUpdate() {\n\t\t\t\tif (updating.current) return\n\t\t\t\tupdating.current = true\n\t\t\t\tflows.current.forEach((ref) => {\n\t\t\t\t\tconst f = ref.current\n\t\t\t\t\tif (!f || !f.created) return\n\t\t\t\t\tf.willUpdate()\n\t\t\t\t\tpending.current.set(f, true)\n\t\t\t\t})\n\t\t\t},\n\t\t\tdidUpdate() {\n\t\t\t\tflows.current.forEach((ref) => {\n\t\t\t\t\tconst f = ref.current\n\t\t\t\t\tif (!f || !pending.current.get(f)) return\n\t\t\t\t\tf.didUpdate()\n\t\t\t\t\tpending.current.delete(f)\n\t\t\t\t})\n\t\t\t\tupdating.current = false\n\t\t\t}\n\t\t}),\n\t\t[]\n\t)\n\n\treturn <NumberFlowGroupContext.Provider value={value}>{children}</NumberFlowGroupContext.Provider>\n}\n"
  },
  {
    "path": "packages/react/src/index.tsx",
    "content": "import * as React from 'react'\nimport {\n\tprefersReducedMotion as _prefersReducedMotion,\n\tcanAnimate as _canAnimate\n} from 'number-flow/lite'\nimport { buildStyles } from 'number-flow/csp'\nexport const styles = buildStyles('-react')\nexport * from 'number-flow/plugins'\nexport { default } from './NumberFlow'\nexport * from './NumberFlow'\nexport type { Value, Format, Trend, NumberPartType } from 'number-flow/lite'\n\nexport const useIsSupported = () =>\n\tReact.useSyncExternalStore(\n\t\t() => () => {}, // this value doesn't change, but it's useful to specify a different SSR value:\n\t\t() => _canAnimate,\n\t\t() => false\n\t)\nexport const usePrefersReducedMotion = () =>\n\tReact.useSyncExternalStore(\n\t\t(cb) => {\n\t\t\t_prefersReducedMotion?.addEventListener('change', cb)\n\t\t\treturn () => _prefersReducedMotion?.removeEventListener('change', cb)\n\t\t},\n\t\t() => _prefersReducedMotion!.matches,\n\t\t() => false\n\t)\n\nexport function useCanAnimate({ respectMotionPreference = true } = {}) {\n\tconst isSupported = useIsSupported()\n\tconst reducedMotion = usePrefersReducedMotion()\n\n\treturn isSupported && (!respectMotionPreference || !reducedMotion)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/can-animate/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport { useCanAnimate } from '@number-flow/react'\n\nexport default function Page() {\n\tconst canAnimate = useCanAnimate()\n\tconst disrespectMotionPreference = useCanAnimate({ respectMotionPreference: false })\n\treturn (\n\t\t<div>\n\t\t\t<p data-testid=\"default\">{String(canAnimate)}</p>\n\t\t\t<p data-testid=\"disrespect-motion-preference\">{String(disrespectMotionPreference)}</p>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/group-1-unchanged/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport NumberFlow, { NumberFlowElement, NumberFlowGroup } from '@number-flow/react'\nimport { flushSync } from 'react-dom'\n\nexport default function Page() {\n\tconst [value, setValue] = React.useState(42)\n\tconst ref1 = React.useRef<NumberFlowElement>(null)\n\tconst ref2 = React.useRef<NumberFlowElement>(null)\n\treturn (\n\t\t<>\n\t\t\t<div>\n\t\t\t\t<NumberFlowGroup>\n\t\t\t\t\t<NumberFlow ref={ref1} value={value} />\n\t\t\t\t\t<NumberFlow ref={ref2} value={0} />\n\t\t\t\t</NumberFlowGroup>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\tflushSync(() => {\n\t\t\t\t\t\tsetValue(152000)\n\t\t\t\t\t})\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.pause()\n\t\t\t\t\t\ta.currentTime = 300\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tChange and pause\n\t\t\t</button>\n\t\t\t<br />\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.play()\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tResume\n\t\t\t</button>\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/hashes/page.tsx",
    "content": "import NumberFlow from '@number-flow/react'\n\nexport default function Page() {\n\treturn <NumberFlow value={42} />\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/layout.tsx",
    "content": "import type { Metadata } from 'next'\n// import { Inter } from 'next/font/google'\nimport './globals.css'\n\n// const inter = Inter({ subsets: ['latin'] })\n\nexport const metadata: Metadata = {\n\ttitle: 'NumberFlow tests'\n}\n\nexport default function RootLayout({\n\tchildren\n}: Readonly<{\n\tchildren: React.ReactNode\n}>) {\n\treturn (\n\t\t<html lang=\"en\">\n\t\t\t{/* <body className={`${inter.className} antialiased`}>{children}</body> */}\n\t\t\t<body>{children}</body>\n\t\t</html>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/nonce/page.tsx",
    "content": "import NumberFlow from '@number-flow/react'\n\nexport default function Page() {\n\treturn (\n\t\t<>\n\t\t\t<NumberFlow nonce=\"test-nonce\" value={42} />\n\t\t\t<NumberFlow nonce=\"test-nonce\" value={42} />\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/app/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport NumberFlow, { NumberFlowElement, NumberFlowGroup, continuous } from '@number-flow/react'\nimport { flushSync } from 'react-dom'\n\nexport default function Page() {\n\tconst [value, setValue] = React.useState(42)\n\tconst ref1 = React.useRef<NumberFlowElement>(null)\n\tconst ref2 = React.useRef<NumberFlowElement>(null)\n\treturn (\n\t\t<>\n\t\t\t<div>\n\t\t\t\tText node{' '}\n\t\t\t\t<NumberFlowGroup>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tid=\"flow1\"\n\t\t\t\t\t\tdata-testid=\"flow1\"\n\t\t\t\t\t\tref={ref1}\n\t\t\t\t\t\tvalue={value}\n\t\t\t\t\t\tformat={{ style: 'currency', currency: 'USD' }}\n\t\t\t\t\t\tlocales=\"zh-CN\"\n\t\t\t\t\t\ttrend={() => -1}\n\t\t\t\t\t\tprefix=\":\"\n\t\t\t\t\t\tsuffix=\"/mo\"\n\t\t\t\t\t\tonAnimationsStart={() => console.log('start')}\n\t\t\t\t\t\tonAnimationsFinish={() => console.log('finish')}\n\t\t\t\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\t\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t/>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tid=\"flow2\"\n\t\t\t\t\t\tdata-testid=\"flow2\"\n\t\t\t\t\t\tref={ref2}\n\t\t\t\t\t\tvalue={value}\n\t\t\t\t\t\trespectMotionPreference={false}\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\tdigits={{ 0: { max: 2 } }}\n\t\t\t\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\t\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t/>\n\t\t\t\t</NumberFlowGroup>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\tflushSync(() => {\n\t\t\t\t\t\tsetValue(152)\n\t\t\t\t\t})\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.pause()\n\t\t\t\t\t\ta.currentTime = 300\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tChange and pause\n\t\t\t</button>\n\t\t\t<br />\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.play()\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tResume\n\t\t\t</button>\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/next.config.mjs",
    "content": "import webpack from 'webpack'\nimport { createHash } from 'node:crypto'\nimport { styles } from '@number-flow/react'\n\nconst nonceCsp = \"style-src 'nonce-test-nonce'\"\nconst hashesCsp = `style-src ${styles.map((style) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`).join(' ')}`\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n\tasync headers() {\n\t\treturn [\n\t\t\t{\n\t\t\t\tsource: '/nonce',\n\t\t\t\theaders: [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'Content-Security-Policy',\n\t\t\t\t\t\tvalue: nonceCsp\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tsource: '/hashes',\n\t\t\t\theaders: [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'Content-Security-Policy',\n\t\t\t\t\t\tvalue: hashesCsp\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t},\n\twebpack(config, context) {\n\t\tconfig.plugins.push(\n\t\t\tnew webpack.DefinePlugin({\n\t\t\t\t__REACT_DEVTOOLS_GLOBAL_HOOK__: '({ isDisabled: true })'\n\t\t\t})\n\t\t)\n\t\treturn config\n\t}\n}\n\nexport default nextConfig\n"
  },
  {
    "path": "packages/react/test/apps/react-18/package.json",
    "content": "{\n\t\"name\": \"test\",\n\t\"version\": \"0.1.0\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"build\": \"next build\",\n\t\t\"start\": \"next start --port 3039\",\n\t\t\"dev\": \"next dev --port 3039\",\n\t\t\"lint\": \"next lint\",\n\t\t\"test\": \"playwright test\",\n\t\t\"test:ui\": \"playwright test --ui\",\n\t\t\"test:update\": \"playwright test --update-snapshots\"\n\t},\n\t\"dependencies\": {\n\t\t\"@number-flow/react\": \"workspace:^\",\n\t\t\"next\": \"14.2.15\",\n\t\t\"react\": \"^18\",\n\t\t\"react-dom\": \"^18\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@types/node\": \"^22.7.5\",\n\t\t\"@types/react\": \"^18\",\n\t\t\"@types/react-dom\": \"^18\",\n\t\t\"postcss\": \"^8\",\n\t\t\"tailwindcss\": \"^3.4.1\",\n\t\t\"tw-reset\": \"^0.0.5\",\n\t\t\"typescript\": \"^5\",\n\t\t\"webpack\": \"^5.96.1\"\n\t},\n\t\"packageManager\": \"pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228\"\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-18/playwright.config.ts",
    "content": "export { config as default } from '../../../../../lib/playwright'\n"
  },
  {
    "path": "packages/react/test/apps/react-18/postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n\tplugins: {\n\t\ttailwindcss: {}\n\t}\n}\n\nexport default config\n"
  },
  {
    "path": "packages/react/test/apps/react-18/tailwind.config.ts",
    "content": "import type { Config } from 'tailwindcss'\nimport reset from 'tw-reset'\n\nconst config: Config = {\n\tpresets: [reset],\n\tcontent: [\n\t\t'./pages/**/*.{js,ts,jsx,tsx,mdx}',\n\t\t'./components/**/*.{js,ts,jsx,tsx,mdx}',\n\t\t'./app/**/*.{js,ts,jsx,tsx,mdx}'\n\t],\n\ttheme: {\n\t\textend: {\n\t\t\tcolors: {\n\t\t\t\tbackground: 'var(--background)',\n\t\t\t\tforeground: 'var(--foreground)'\n\t\t\t}\n\t\t}\n\t},\n\tplugins: []\n}\nexport default config\n"
  },
  {
    "path": "packages/react/test/apps/react-18/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n\t\t\"allowJs\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"strict\": true,\n\t\t\"noEmit\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"jsx\": \"preserve\",\n\t\t\"incremental\": true,\n\t\t\"plugins\": [\n\t\t\t{\n\t\t\t\t\"name\": \"next\"\n\t\t\t}\n\t\t],\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./*\"]\n\t\t}\n\t},\n\t\"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n\t\"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# env files (can opt-in for commiting if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/can-animate/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport { useCanAnimate } from '@number-flow/react'\n\nexport default function Page() {\n\tconst canAnimate = useCanAnimate()\n\tconst disrespectMotionPreference = useCanAnimate({ respectMotionPreference: false })\n\treturn (\n\t\t<div>\n\t\t\t<p data-testid=\"default\">{String(canAnimate)}</p>\n\t\t\t<p data-testid=\"disrespect-motion-preference\">{String(disrespectMotionPreference)}</p>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/group-1-unchanged/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport NumberFlow, { NumberFlowElement, NumberFlowGroup } from '@number-flow/react'\nimport { flushSync } from 'react-dom'\n\nexport default function Page() {\n\tconst [value, setValue] = React.useState(42)\n\tconst ref1 = React.useRef<NumberFlowElement>(null)\n\tconst ref2 = React.useRef<NumberFlowElement>(null)\n\treturn (\n\t\t<>\n\t\t\t<div>\n\t\t\t\t<NumberFlowGroup>\n\t\t\t\t\t<NumberFlow ref={ref1} value={value} />\n\t\t\t\t\t<NumberFlow ref={ref2} value={0} />\n\t\t\t\t</NumberFlowGroup>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\tflushSync(() => {\n\t\t\t\t\t\tsetValue(152000)\n\t\t\t\t\t})\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.pause()\n\t\t\t\t\t\ta.currentTime = 300\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tChange and pause\n\t\t\t</button>\n\t\t\t<br />\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.play()\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tResume\n\t\t\t</button>\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/hashes/page.tsx",
    "content": "import NumberFlow from '@number-flow/react'\n\nexport default function Page() {\n\treturn <NumberFlow value={42} />\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/layout.tsx",
    "content": "import type { Metadata } from 'next'\n// import { Inter } from 'next/font/google'\nimport './globals.css'\n\n// const inter = Inter({ subsets: ['latin'] })\n\nexport const metadata: Metadata = {\n\ttitle: 'NumberFlow tests'\n}\n\nexport default function RootLayout({\n\tchildren\n}: Readonly<{\n\tchildren: React.ReactNode\n}>) {\n\treturn (\n\t\t<html lang=\"en\">\n\t\t\t{/* <body className={`${inter.className} antialiased`}>{children}</body> */}\n\t\t\t<body>{children}</body>\n\t\t</html>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/nonce/page.tsx",
    "content": "import NumberFlow from '@number-flow/react'\n\nexport default function Page() {\n\treturn (\n\t\t<>\n\t\t\t<NumberFlow nonce=\"test-nonce\" value={42} />\n\t\t\t<NumberFlow nonce=\"test-nonce\" value={42} />\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/page.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport NumberFlow, { NumberFlowElement, NumberFlowGroup, continuous } from '@number-flow/react'\nimport { flushSync } from 'react-dom'\n\nexport default function Page() {\n\tconst [value, setValue] = React.useState(42)\n\tconst ref1 = React.useRef<NumberFlowElement>(null)\n\tconst ref2 = React.useRef<NumberFlowElement>(null)\n\treturn (\n\t\t<>\n\t\t\t<div>\n\t\t\t\tText node{' '}\n\t\t\t\t<NumberFlowGroup>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tid=\"flow1\"\n\t\t\t\t\t\tdata-testid=\"flow1\"\n\t\t\t\t\t\tref={ref1}\n\t\t\t\t\t\tvalue={value}\n\t\t\t\t\t\tformat={{ style: 'currency', currency: 'USD' }}\n\t\t\t\t\t\tlocales=\"zh-CN\"\n\t\t\t\t\t\ttrend={() => -1}\n\t\t\t\t\t\tprefix=\":\"\n\t\t\t\t\t\tsuffix=\"/mo\"\n\t\t\t\t\t\tonAnimationsStart={() => console.log('start')}\n\t\t\t\t\t\tonAnimationsFinish={() => console.log('finish')}\n\t\t\t\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\t\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t/>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tid=\"flow2\"\n\t\t\t\t\t\tdata-testid=\"flow2\"\n\t\t\t\t\t\tref={ref2}\n\t\t\t\t\t\tvalue={value}\n\t\t\t\t\t\trespectMotionPreference={false}\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\tdigits={{ 0: { max: 2 } }}\n\t\t\t\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\t\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t\t\t\t/>\n\t\t\t\t</NumberFlowGroup>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\tflushSync(() => {\n\t\t\t\t\t\tsetValue(152)\n\t\t\t\t\t})\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.pause()\n\t\t\t\t\t\ta.currentTime = 300\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tChange and pause\n\t\t\t</button>\n\t\t\t<br />\n\t\t\t<button\n\t\t\t\tonClick={() => {\n\t\t\t\t\t;[\n\t\t\t\t\t\t...(ref1.current?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t\t...(ref2.current?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t\t].forEach((a) => {\n\t\t\t\t\t\ta.play()\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tResume\n\t\t\t</button>\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/app/sc/page.tsx",
    "content": "import NumberFlow from '@number-flow/react'\n\nexport default function SC() {\n\treturn <NumberFlow value={123} />\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/next.config.ts",
    "content": "import type { NextConfig } from 'next'\nimport webpack from 'webpack'\nimport { createHash } from 'node:crypto'\nimport { styles } from '@number-flow/react'\n\nconst nonceCsp = \"style-src 'nonce-test-nonce'\"\nconst hash = (style: string) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`\nconst hashesCsp = `style-src ${styles.map(hash).join(' ')}`\n\nconst nextConfig: NextConfig = {\n\tasync headers() {\n\t\treturn [\n\t\t\t{\n\t\t\t\tsource: '/nonce',\n\t\t\t\theaders: [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'Content-Security-Policy',\n\t\t\t\t\t\tvalue: nonceCsp\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tsource: '/hashes',\n\t\t\t\theaders: [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'Content-Security-Policy',\n\t\t\t\t\t\tvalue: hashesCsp\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t},\n\twebpack(config, context) {\n\t\tconfig.plugins.push(\n\t\t\tnew webpack.DefinePlugin({\n\t\t\t\t__REACT_DEVTOOLS_GLOBAL_HOOK__: '({ isDisabled: true })'\n\t\t\t})\n\t\t)\n\t\treturn config\n\t},\n\tdevIndicators: {\n\t\tappIsrStatus: false\n\t}\n}\n\nexport default nextConfig\n"
  },
  {
    "path": "packages/react/test/apps/react-19/package.json",
    "content": "{\n\t\"name\": \"react-19\",\n\t\"version\": \"0.1.0\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"dev\": \"next dev --port 3039\",\n\t\t\"build\": \"next build\",\n\t\t\"start\": \"next start --port 3039\",\n\t\t\"lint\": \"next lint\",\n\t\t\"test\": \"playwright test\",\n\t\t\"test:ui\": \"playwright test --ui\",\n\t\t\"test:update\": \"playwright test --update-snapshots\"\n\t},\n\t\"dependencies\": {\n\t\t\"@number-flow/react\": \"workspace:^\",\n\t\t\"next\": \"15.1.4\",\n\t\t\"react\": \"19.0.0\",\n\t\t\"react-dom\": \"19.0.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@types/node\": \"^20\",\n\t\t\"@types/react\": \"^18\",\n\t\t\"@types/react-dom\": \"^18\",\n\t\t\"postcss\": \"^8\",\n\t\t\"tailwindcss\": \"^3.4.1\",\n\t\t\"typescript\": \"^5\",\n\t\t\"webpack\": \"^5.96.1\"\n\t}\n}\n"
  },
  {
    "path": "packages/react/test/apps/react-19/playwright.config.ts",
    "content": "export { config as default } from '../../../../../lib/playwright'\n"
  },
  {
    "path": "packages/react/test/apps/react-19/postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n\tplugins: {\n\t\ttailwindcss: {}\n\t}\n}\n\nexport default config\n"
  },
  {
    "path": "packages/react/test/apps/react-19/tailwind.config.ts",
    "content": "import type { Config } from 'tailwindcss'\n\nconst config: Config = {\n\tcontent: [\n\t\t'./pages/**/*.{js,ts,jsx,tsx,mdx}',\n\t\t'./components/**/*.{js,ts,jsx,tsx,mdx}',\n\t\t'./app/**/*.{js,ts,jsx,tsx,mdx}'\n\t],\n\ttheme: {\n\t\textend: {\n\t\t\tcolors: {\n\t\t\t\tbackground: 'var(--background)',\n\t\t\t\tforeground: 'var(--foreground)'\n\t\t\t}\n\t\t}\n\t},\n\tplugins: []\n}\nexport default config\n"
  },
  {
    "path": "packages/react/test/apps/react-19/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ES2017\",\n\t\t\"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n\t\t\"allowJs\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"strict\": true,\n\t\t\"noEmit\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"jsx\": \"preserve\",\n\t\t\"incremental\": true,\n\t\t\"plugins\": [\n\t\t\t{\n\t\t\t\t\"name\": \"next\"\n\t\t\t}\n\t\t],\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./*\"]\n\t\t}\n\t},\n\t\"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n\t\"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/react/tsconfig.build.json",
    "content": "{\n\t\"extends\": [\"./tsconfig.json\", \"../../tsconfig.build.json\"]\n}\n"
  },
  {
    "path": "packages/react/tsconfig.json",
    "content": "{\n\t\"extends\": \"../../tsconfig.json\",\n\t\"include\": [\"src\"],\n\t\"compilerOptions\": { \"jsx\": \"react\" }\n}\n"
  },
  {
    "path": "packages/svelte/.gitignore",
    "content": "test-results\nnode_modules\n\n# Output\n.output\n.vercel\n/.svelte-kit\n/build\n/dist\n\n# testing\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# OS\n.DS_Store\nThumbs.db\n\n# Env\n.env\n.env.*\n!.env.example\n!.env.test\n\n# Vite\nvite.config.js.timestamp-*\nvite.config.ts.timestamp-*\n"
  },
  {
    "path": "packages/svelte/.npmrc",
    "content": "engine-strict=true\n"
  },
  {
    "path": "packages/svelte/CHANGELOG.md",
    "content": "# @number-flow/svelte\n\n## 0.4.0\n\n### Minor Changes\n\n- Remove `--number-flow-char-height` CSS property in favor of `line-height` ([`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6))\n\n### Patch Changes\n\n- Updated dependencies [[`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6)]:\n  - number-flow@0.6.0\n\n## 0.3.13\n\n### Patch Changes\n\n- Updated dependencies [[`5cc3c9b`](https://github.com/barvian/number-flow/commit/5cc3c9b7f7c223719047b964b47dd9d3a42fa371)]:\n  - number-flow@0.5.12\n\n## 0.3.12\n\n### Patch Changes\n\n- Add CSP strategies (see [#170](https://github.com/barvian/number-flow/issues/170)) ([`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67))\n\n- Updated dependencies [[`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67)]:\n  - number-flow@0.5.11\n\n## 0.3.11\n\n### Patch Changes\n\n- Updated dependencies [[`4a6c26e`](https://github.com/barvian/number-flow/commit/4a6c26efe13d6ffc1b84ea75accf511f63669eb9)]:\n  - number-flow@0.5.10\n\n## 0.3.10\n\n### Patch Changes\n\n- Updated dependencies [[`bdf8ce9`](https://github.com/barvian/number-flow/commit/bdf8ce92df67d6147d7f56998c625fc29e1b7571)]:\n  - number-flow@0.5.9\n\n## 0.3.9\n\n### Patch Changes\n\n- Fix Svelte 5 props (see [#136](https://github.com/barvian/number-flow/issues/136)) ([`605008d`](https://github.com/barvian/number-flow/commit/605008da694cb56ec4047daa06fef9a1d74ef0b6))\n\n## 0.3.8\n\n### Patch Changes\n\n- Fix \"custom element already defined\" bugs ([`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d))\n\n- Updated dependencies [[`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d)]:\n  - number-flow@0.5.8\n\n## 0.3.7\n\n### Patch Changes\n\n- Updated dependencies [[`ee6a0a2`](https://github.com/barvian/number-flow/commit/ee6a0a2f2f09ba187b8df24cdfe0992ee7883192)]:\n  - number-flow@0.5.7\n\n## 0.3.6\n\n### Patch Changes\n\n- Updated dependencies [[`2539c4b`](https://github.com/barvian/number-flow/commit/2539c4b653fd4aaa17ef6b2ffd77b7a41454da08)]:\n  - number-flow@0.5.6\n\n## 0.3.5\n\n### Patch Changes\n\n- Updated dependencies [[`bc6476f`](https://github.com/barvian/number-flow/commit/bc6476f910ad58625491c23ed0a8768217f9ab57)]:\n  - number-flow@0.5.5\n\n## 0.3.4\n\n### Patch Changes\n\n- Release vanilla JS version ([`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d))\n\n- Updated dependencies [[`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d)]:\n  - number-flow@0.5.4\n\n## 0.3.3\n\n### Patch Changes\n\n- Revert mask-image change due to <1em char heights ([`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d))\n\n- Updated dependencies [[`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d)]:\n  - number-flow@0.5.3\n\n## 0.3.2\n\n### Patch Changes\n\n- Improve `::selection` display and accessibility during transitions ([`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393))\n\n- Updated dependencies [[`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393)]:\n  - number-flow@0.5.2\n\n## 0.3.1\n\n### Patch Changes\n\n- Add missing symbol part to SSR ([`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948))\n\n- Updated dependencies [[`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948)]:\n  - number-flow@0.5.1\n\n## 0.3.0\n\n### Minor Changes\n\n- Move `continuous` prop into importable plugin ([`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff))\n\n### Patch Changes\n\n- Updated dependencies [[`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff)]:\n  - number-flow@0.5.0\n\n## 0.2.3\n\n### Patch Changes\n\n- Add symbol part for styling all symbols ([`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb))\n\n- Updated dependencies [[`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb)]:\n  - number-flow@0.4.2\n\n## 0.2.2\n\n### Patch Changes\n\n- Reduce bundle size ([`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3))\n\n- Updated dependencies [[`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3)]:\n  - number-flow@0.4.1\n\n## 0.2.1\n\n### Patch Changes\n\n- Fix getCanAnimate hydration ([`e775131`](https://github.com/barvian/number-flow/commit/e775131a09628b98724dd1ec905d06ba78d06e21))\n\n## 0.2.0\n\n### Minor Changes\n\n- More flexible trend prop ([`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0))\n\n### Patch Changes\n\n- Add digits prop ([`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94))\n\n- Fix cursor and improve text selection ([`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809))\n\n- Updated dependencies [[`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0), [`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94), [`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809)]:\n  - number-flow@0.4.0\n\n## 0.1.7\n\n### Patch Changes\n\n- Switch to TS private properties to reduce bundle size ([`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd))\n\n- Updated dependencies [[`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd)]:\n  - number-flow@0.3.10\n\n## 0.1.6\n\n### Patch Changes\n\n- Expose parts for styling support ([`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4))\n\n- Updated dependencies [[`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4)]:\n  - number-flow@0.3.9\n\n## 0.1.5\n\n### Patch Changes\n\n- Add prefix & suffix props ([`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14))\n\n- Updated dependencies [[`9854f77`](https://github.com/barvian/number-flow/commit/9854f77e11561fe119bf9009ae1369389a64ba15), [`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14)]:\n  - number-flow@0.3.8\n\n## 0.1.4\n\n### Patch Changes\n\n- Add `<NumberFlowGroup>` ([`b291400`](https://github.com/barvian/number-flow/commit/b2914009cf54d58604d3e34f0b6f16dc4b912a6a))\n\n- Updated dependencies [[`0fac2f6`](https://github.com/barvian/number-flow/commit/0fac2f69b239048054755c556afc3f0eb65767c9)]:\n  - number-flow@0.3.7\n\n## 0.1.3\n\n### Patch Changes\n\n- More defensive checks on browser globals (see [#58](https://github.com/barvian/number-flow/issues/58)) ([#59](https://github.com/barvian/number-flow/pull/59))\n\n- Updated dependencies [[`13a66fb`](https://github.com/barvian/number-flow/commit/13a66fba336c53687664ad9b859ec705891fce2a)]:\n  - number-flow@0.3.6\n\n## 0.1.2\n\n### Patch Changes\n\n- Use main field for bundlephobia ([`790e0c4`](https://github.com/barvian/number-flow/commit/790e0c4c672ffb473614b8c8eed33e4dece3aa2f))\n\n## 0.1.1\n\n### Patch Changes\n\n- Remove unused code ([`d2a1c7b`](https://github.com/barvian/number-flow/commit/d2a1c7b7fcc1523e0efd693acc8b971e35aac102))\n\n## 0.1.0\n\n### Minor Changes\n\n- Rename number-flow element to avoid conflicts between wrappers ([`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac))\n\n### Patch Changes\n\n- Updated dependencies [[`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac)]:\n  - number-flow@0.3.5\n"
  },
  {
    "path": "packages/svelte/README.md",
    "content": "[![NumberFlow for Svelte](https://number-flow.barvian.me/preview.webp)](https://number-flow.barvian.me/svelte)\n\n# NumberFlow for Svelte\n\nAn animated number component.\n\n[![NPM Version](https://img.shields.io/npm/v/@number-flow/svelte.svg)](https://npmjs.com/package/@number-flow/svelte)\n[![Bundle size](https://badgen.net/bundlephobia/minzip/@number-flow/svelte@latest)](https://bundlephobia.com/package/@number-flow/svelte@latest)\n[![Follow @mbarvian](https://img.shields.io/twitter/follow/mbarvian.svg?style=social&label=Follow)](https://x.com/mbarvian)\n\n## Documentation\n\nFor full documentation, visit [number-flow.barvian.me/svelte](https://number-flow.barvian.me/svelte).\n"
  },
  {
    "path": "packages/svelte/package.json",
    "content": "{\n\t\"name\": \"@number-flow/svelte\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"version\": \"0.4.0\",\n\t\"description\": \"A component to transition and format numbers.\",\n\t\"license\": \"MIT\",\n\t\"homepage\": \"https://number-flow.barvian.me/svelte\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/barvian/number-flow\",\n\t\t\"directory\": \"src\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/barvian/number-flow/issues\"\n\t},\n\t\"keywords\": [\n\t\t\"accessible\",\n\t\t\"odometer\",\n\t\t\"animation\",\n\t\t\"number-format\",\n\t\t\"number-animation\",\n\t\t\"animated-number\"\n\t],\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev\",\n\t\t\"build:watch\": \"svelte-package --watch\",\n\t\t\"build\": \"svelte-kit sync && svelte-package && publint\",\n\t\t\"build:site\": \"vite build && pnpm build\",\n\t\t\"preview\": \"vite preview --port 3039\",\n\t\t\"check\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json\",\n\t\t\"check:watch\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch\",\n\t\t\"test\": \"playwright test\",\n\t\t\"test:ui\": \"playwright test --ui\",\n\t\t\"test:update\": \"playwright test --update-snapshots\"\n\t},\n\t\"files\": [\n\t\t\"dist\",\n\t\t\"!dist/**/*.test.*\",\n\t\t\"!dist/**/*.spec.*\",\n\t\t\"README.md\"\n\t],\n\t\"main\": \"./dist/index.js\",\n\t\"svelte\": \"./dist/index.js\",\n\t\"types\": \"./dist/index.d.ts\",\n\t\"type\": \"module\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/index.d.ts\",\n\t\t\t\"svelte\": \"./dist/index.js\",\n\t\t\t\"default\": \"./dist/index.js\"\n\t\t}\n\t},\n\t\"dependencies\": {\n\t\t\"esm-env\": \"^1.1.4\",\n\t\t\"number-flow\": \"workspace:*\"\n\t},\n\t\"peerDependencies\": {\n\t\t\"svelte\": \"^4 || ^5\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.45.3\",\n\t\t\"@sveltejs/adapter-auto\": \"^3.0.0\",\n\t\t\"@sveltejs/kit\": \"^2.0.0\",\n\t\t\"@sveltejs/package\": \"^2.0.0\",\n\t\t\"@sveltejs/vite-plugin-svelte\": \"^3.0.0\",\n\t\t\"autoprefixer\": \"^10.4.20\",\n\t\t\"publint\": \"^0.2.0\",\n\t\t\"svelte\": \"^4.0.0\",\n\t\t\"svelte-check\": \"^4.0.0\",\n\t\t\"tailwindcss\": \"^3.4.9\",\n\t\t\"typescript\": \"^5.0.0\",\n\t\t\"vite\": \"^5.0.11\"\n\t}\n}\n"
  },
  {
    "path": "packages/svelte/playwright.config.ts",
    "content": "import { defineConfig } from '@playwright/test'\nimport { config } from '../../lib/playwright'\n\n// Use prod build cause it includes hydration errors but excludes random Vite stuff:\nexport default defineConfig({\n\t...config,\n\twebServer: {\n\t\t...config.webServer,\n\t\tcommand: 'pnpm build:site && pnpm preview'\n\t}\n})\n"
  },
  {
    "path": "packages/svelte/postcss.config.js",
    "content": "export default {\n\tplugins: {\n\t\ttailwindcss: {},\n\t\tautoprefixer: {}\n\t}\n}\n"
  },
  {
    "path": "packages/svelte/src/app.css",
    "content": "@import 'tailwindcss/base';\n@import 'tailwindcss/components';\n@import 'tailwindcss/utilities';\n"
  },
  {
    "path": "packages/svelte/src/app.d.ts",
    "content": "// See https://svelte.dev/docs/kit/types#app.d.ts\n// for information about these interfaces\ndeclare global {\n\tnamespace App {\n\t\t// interface Error {}\n\t\t// interface Locals {}\n\t\t// interface PageData {}\n\t\t// interface PageState {}\n\t\t// interface Platform {}\n\t}\n}\n\nexport {}\n"
  },
  {
    "path": "packages/svelte/src/app.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t%sveltekit.head%\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div>%sveltekit.body%</div>\n\t</body>\n</html>\n"
  },
  {
    "path": "packages/svelte/src/lib/NumberFlow.svelte",
    "content": "<script lang=\"ts\" context=\"module\">\n\timport NumberFlowLite, { define, type Data } from 'number-flow/lite'\n\t// Svelte only supports setters, but Svelte 4 didn't pick up inherited ones:\n\texport class NumberFlowElement extends NumberFlowLite {\n\t\tset __svelte_batched(batched: boolean) {\n\t\t\tthis.batched = batched\n\t\t}\n\t\toverride set data(data: Data | undefined) {\n\t\t\tsuper.data = data\n\t\t}\n\t}\n\tObject.keys(NumberFlowElement.defaultProps).forEach((key) => {\n\t\t// Use lowerCase for Svelte 5 for some reason:\n\t\tObject.defineProperty(NumberFlowElement.prototype, `__svelte_${key.toLowerCase()}`, {\n\t\t\tset(value) {\n\t\t\t\tthis[key] = value\n\t\t\t},\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true\n\t\t})\n\t})\n\n\tdefine('number-flow-svelte', NumberFlowElement)\n</script>\n\n<script lang=\"ts\">\n\timport {\n\t\ttype Value,\n\t\ttype Format,\n\t\trenderInnerHTML,\n\t\tformatToData,\n\t\ttype Props as NumberFlowProps\n\t} from 'number-flow/lite'\n\timport type { HTMLAttributes } from 'svelte/elements'\n\timport { writable } from 'svelte/store'\n\timport { getGroupContext } from './group.js'\n\timport { BROWSER } from 'esm-env'\n\n\texport let locales: Intl.LocalesArgument = undefined\n\texport let format: Format | undefined = undefined\n\texport let value: Value\n\texport let prefix: string | undefined = undefined\n\texport let suffix: string | undefined = undefined\n\texport let nonce: string | undefined = undefined\n\texport let willChange = false\n\n\t// Define these so they can be remapped. We set them to their defaults because\n\t// that makes them optional in Svelte\n\texport let transformTiming = NumberFlowElement.defaultProps.transformTiming\n\texport let spinTiming = NumberFlowElement.defaultProps.spinTiming\n\texport let opacityTiming = NumberFlowElement.defaultProps.opacityTiming\n\texport let animated = NumberFlowElement.defaultProps.animated\n\texport let respectMotionPreference = NumberFlowElement.defaultProps.respectMotionPreference\n\texport let trend = NumberFlowElement.defaultProps.trend\n\texport let plugins = NumberFlowElement.defaultProps.plugins\n\texport let digits = NumberFlowElement.defaultProps.digits\n\n\ttype $$Props = HTMLAttributes<HTMLElement> &\n\t\tPartial<NumberFlowProps> & {\n\t\t\tel?: NumberFlowElement\n\t\t\tlocales?: Intl.LocalesArgument\n\t\t\tformat?: Format\n\t\t\tvalue: Value\n\t\t\tprefix?: string\n\t\t\tsuffix?: string\n\t\t\tnonce?: string\n\t\t\twillChange?: boolean\n\t\t}\n\n\ttype $$Events = {\n\t\tanimationsstart: CustomEvent<undefined>\n\t\tanimationsfinish: CustomEvent<undefined>\n\t}\n\n\texport let el: NumberFlowElement | undefined = undefined\n\tconst elStore = writable<NumberFlowElement | undefined>(el)\n\t$: $elStore = el\n\n\t// You're supposed to cache these between uses:\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString\n\t$: formatter = new Intl.NumberFormat(locales, format)\n\t$: data = formatToData(value, formatter, prefix, suffix)\n\n\t// Handle grouping. Keep as much logic in NumberFlowGroup.vue as possible\n\t// for better tree-shaking:\n\tconst group = getGroupContext()\n\tgroup?.register?.(elStore)\n</script>\n\n<number-flow-svelte\n\tbind:this={el}\n\t{...$$restProps}\n\tdata-will-change={willChange ? '' : undefined}\n\ton:animationsstart\n\ton:animationsfinish\n\t__svelte_batched={Boolean(group)}\n\t__svelte_transformtiming={transformTiming}\n\t__svelte_spintiming={spinTiming}\n\t__svelte_opacitytiming={opacityTiming}\n\t__svelte_animated={animated}\n\t__svelte_respectmotionpreference={respectMotionPreference}\n\t__svelte_trend={trend}\n\t__svelte_plugins={plugins}\n\t__svelte_digits={digits}\n\t{nonce}\n\t{data}\n>\n\t{@html BROWSER ? undefined : renderInnerHTML(data, { nonce, elementSuffix: '-svelte' })}\n</number-flow-svelte>\n"
  },
  {
    "path": "packages/svelte/src/lib/NumberFlowGroup.svelte",
    "content": "<script lang=\"ts\">\n\timport type NumberFlowLite from 'number-flow/lite'\n\timport { type Readable, get } from 'svelte/store'\n\timport { beforeUpdate, onDestroy, tick } from 'svelte'\n\timport { type RegisterWithGroup, setGroupContext } from './group.js'\n\n\tconst flows = new Set<Readable<NumberFlowLite | undefined>>()\n\tlet updating = false\n\n\tconst registerWithGroup: RegisterWithGroup = (el) => {\n\t\tflows.add(el)\n\n\t\tbeforeUpdate(async () => {\n\t\t\tif (updating) return\n\t\t\tupdating = true\n\t\t\tflows.forEach(async (flow) => {\n\t\t\t\t{\n\t\t\t\t\tconst f = get(flow)\n\t\t\t\t\tif (!f || !f.created) return\n\t\t\t\t\tf.willUpdate()\n\t\t\t\t}\n\t\t\t\tawait tick()\n\t\t\t\t// Optional in case the element was removed after tick:\n\t\t\t\tget(flow)?.didUpdate()\n\t\t\t})\n\t\t\tawait tick()\n\t\t\tupdating = false\n\t\t})\n\n\t\tonDestroy(() => {\n\t\t\tflows.delete(el)\n\t\t})\n\t}\n\n\tsetGroupContext({ register: registerWithGroup })\n</script>\n\n<slot />\n"
  },
  {
    "path": "packages/svelte/src/lib/group.ts",
    "content": "import type NumberFlowLite from 'number-flow/lite'\nimport { getContext, setContext } from 'svelte'\nimport type { Readable } from 'svelte/store'\n\nlet groupKey = Symbol('group')\n\nexport type RegisterWithGroup = (el: Readable<NumberFlowLite | undefined>) => void\n\nexport type GroupContext = { register: RegisterWithGroup }\n\nexport function setGroupContext(ctx: GroupContext) {\n\tsetContext(groupKey, ctx)\n}\n\nexport function getGroupContext() {\n\treturn getContext(groupKey) as GroupContext | undefined\n}\n"
  },
  {
    "path": "packages/svelte/src/lib/index.ts",
    "content": "import {\n\tcanAnimate as _canAnimate,\n\tprefersReducedMotion as _prefersReducedMotion\n} from 'number-flow/lite'\nimport { onMount } from 'svelte'\nimport { derived, readable } from 'svelte/store'\nimport { buildStyles } from 'number-flow/csp'\n\nexport const styles = buildStyles('-svelte')\nexport type { Value, Format, Trend } from 'number-flow/lite'\nexport * from 'number-flow/plugins'\nexport { default as NumberFlowGroup } from './NumberFlowGroup.svelte'\nexport { default, NumberFlowElement } from './NumberFlow.svelte'\n\nconst canAnimate = readable(false, (set) => {\n\tonMount(() => {\n\t\tset(_canAnimate)\n\t})\n})\n\nconst prefersReducedMotion = readable(false, (set) => {\n\tonMount(() => {\n\t\tset(_prefersReducedMotion?.matches ?? false)\n\t\tconst onChange = ({ matches }: MediaQueryListEvent) => {\n\t\t\tset(matches)\n\t\t}\n\t\t_prefersReducedMotion?.addEventListener('change', onChange)\n\t\treturn () => {\n\t\t\t_prefersReducedMotion?.removeEventListener('change', onChange)\n\t\t}\n\t})\n})\n\nconst canAnimateWithPreference = derived(\n\t[canAnimate, prefersReducedMotion],\n\t([canAnimate, prefersReducedMotion]) => canAnimate && !prefersReducedMotion\n)\n\nexport const getCanAnimate = ({ respectMotionPreference = true } = {}) =>\n\trespectMotionPreference ? canAnimateWithPreference : canAnimate\n"
  },
  {
    "path": "packages/svelte/src/routes/+layout.svelte",
    "content": "<script lang=\"ts\">\n\timport '../app.css'\n</script>\n\n<slot></slot>\n"
  },
  {
    "path": "packages/svelte/src/routes/+page.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow, { NumberFlowGroup, NumberFlowElement, continuous } from '$lib/index.js'\n\timport { afterUpdate, tick } from 'svelte'\n\n\tconst initialValue = 42\n\n\tlet value = initialValue\n\tlet el1: NumberFlowElement | undefined\n\tlet el2: NumberFlowElement | undefined\n\n\tafterUpdate(async () => {\n\t\tawait tick()\n\t\tif (value !== initialValue) {\n\t\t\t;[\n\t\t\t\t...(el1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t...(el2?.shadowRoot?.getAnimations() ?? [])\n\t\t\t].forEach((a) => {\n\t\t\t\ta.pause()\n\t\t\t\ta.currentTime = 300\n\t\t\t})\n\t\t}\n\t})\n</script>\n\n<div>\n\tText node\n\t<NumberFlowGroup>\n\t\t<NumberFlow\n\t\t\tbind:el={el1}\n\t\t\tid=\"flow1\"\n\t\t\tdata-testid=\"flow1\"\n\t\t\t{value}\n\t\t\tformat={{ style: 'currency', currency: 'USD' }}\n\t\t\tlocales=\"zh-CN\"\n\t\t\ttrend={() => -1}\n\t\t\tprefix=\":\"\n\t\t\tsuffix=\"/mo\"\n\t\t\ton:animationsstart={() => console.log('start')}\n\t\t\ton:animationsfinish={() => console.log('finish')}\n\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t/><NumberFlow\n\t\t\tbind:el={el2}\n\t\t\tid=\"flow2\"\n\t\t\tdata-testid=\"flow2\"\n\t\t\t{value}\n\t\t\trespectMotionPreference={false}\n\t\t\tplugins={[continuous]}\n\t\t\tdigits={{ 0: { max: 2 } }}\n\t\t\ttransformTiming={{ easing: 'linear', duration: 500 }}\n\t\t\tspinTiming={{ easing: 'linear', duration: 800 }}\n\t\t\topacityTiming={{ easing: 'linear', duration: 500 }}\n\t\t/>\n\t</NumberFlowGroup>\n</div>\n<button on:click={() => (value = 152)}>Change and pause</button>\n<br />\n<button\n\ton:click={() => {\n\t\t;[\n\t\t\t...(el1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(el2?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.play()\n\t\t})\n\t}}\n>\n\tResume\n</button>\n"
  },
  {
    "path": "packages/svelte/src/routes/can-animate/+page.svelte",
    "content": "<script lang=\"ts\">\n\timport { getCanAnimate } from '$lib/index.js'\n\tconst canAnimate = getCanAnimate()\n\tconst disrespectMotionPreference = getCanAnimate({ respectMotionPreference: false })\n\n\t// Trigger runes mode:\n\t$: $canAnimate\n</script>\n\n<div>\n\t<p data-testid=\"default\">{String($canAnimate)}</p>\n\t<p data-testid=\"disrespect-motion-preference\">{String($disrespectMotionPreference)}</p>\n</div>\n"
  },
  {
    "path": "packages/svelte/src/routes/group-1-unchanged/+page.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow, { NumberFlowGroup, NumberFlowElement } from '$lib/index.js'\n\timport { afterUpdate, tick } from 'svelte'\n\n\tconst initialValue = 42\n\n\tlet value = initialValue\n\tlet el1: NumberFlowElement | undefined\n\tlet el2: NumberFlowElement | undefined\n\n\tafterUpdate(async () => {\n\t\tawait tick()\n\t\tif (value !== initialValue) {\n\t\t\t;[\n\t\t\t\t...(el1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t...(el2?.shadowRoot?.getAnimations() ?? [])\n\t\t\t].forEach((a) => {\n\t\t\t\ta.pause()\n\t\t\t\ta.currentTime = 300\n\t\t\t})\n\t\t}\n\t})\n</script>\n\n<div>\n\t<NumberFlowGroup>\n\t\t<NumberFlow bind:el={el1} {value} /><NumberFlow bind:el={el2} value={0} />\n\t</NumberFlowGroup>\n</div>\n<button on:click={() => (value = 152000)}>Change and pause</button>\n<br />\n<button\n\ton:click={() => {\n\t\t;[\n\t\t\t...(el1?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(el2?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.play()\n\t\t})\n\t}}\n>\n\tResume\n</button>\n"
  },
  {
    "path": "packages/svelte/src/routes/hashes/+page.server.ts",
    "content": "import type { PageServerLoad } from './$types'\nimport { createHash } from 'node:crypto'\nimport { styles } from '$lib/index.js'\n\nconst hash = (style: string) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`\n\nexport const load: PageServerLoad = ({ setHeaders }) => {\n\tsetHeaders({\n\t\t'Content-Security-Policy': `style-src ${styles.map(hash).join(' ')}`\n\t})\n\treturn {}\n}\n"
  },
  {
    "path": "packages/svelte/src/routes/hashes/+page.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow from '$lib/index.js'\n</script>\n\n<NumberFlow value={42} />\n"
  },
  {
    "path": "packages/svelte/src/routes/nonce/+page.server.ts",
    "content": "import type { PageServerLoad } from './$types'\n\nexport const load: PageServerLoad = ({ setHeaders }) => {\n\tsetHeaders({\n\t\t'Content-Security-Policy': \"style-src 'nonce-test-nonce'\"\n\t})\n\treturn {}\n}\n"
  },
  {
    "path": "packages/svelte/src/routes/nonce/+page.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow from '$lib/index.js'\n</script>\n\n<NumberFlow value={42} nonce=\"test-nonce\" /><NumberFlow value={42} nonce=\"test-nonce\" />\n"
  },
  {
    "path": "packages/svelte/svelte.config.js",
    "content": "import adapter from '@sveltejs/adapter-auto'\nimport { vitePreprocess } from '@sveltejs/vite-plugin-svelte'\n\n/** @type {import('@sveltejs/kit').Config} */\nconst config = {\n\t// Consult https://svelte.dev/docs/kit/integrations\n\t// for more information about preprocessors\n\tpreprocess: vitePreprocess(),\n\n\tkit: {\n\t\t// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.\n\t\t// If your environment is not supported, or you settled on a specific environment, switch out the adapter.\n\t\t// See https://svelte.dev/docs/kit/adapters for more information about adapters.\n\t\tadapter: adapter()\n\t}\n}\n\nexport default config\n"
  },
  {
    "path": "packages/svelte/tailwind.config.ts",
    "content": "import type { Config } from 'tailwindcss'\n\nexport default {\n\tcontent: ['./src/**/*.{html,js,svelte,ts}'],\n\n\ttheme: {\n\t\textend: {}\n\t},\n\n\tplugins: []\n} as Config\n"
  },
  {
    "path": "packages/svelte/tsconfig.json",
    "content": "{\n\t\"extends\": [\"./.svelte-kit/tsconfig.json\", \"../../tsconfig.json\"],\n\t\"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/svelte/vite.config.ts",
    "content": "import { sveltekit } from '@sveltejs/kit/vite'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n\tplugins: [sveltekit()],\n\tserver: {\n\t\tport: 3039,\n\t\tstrictPort: true\n\t}\n})\n"
  },
  {
    "path": "packages/vue/CHANGELOG.md",
    "content": "# @number-flow/vue\n\n## 0.5.0\n\n### Minor Changes\n\n- Remove `--number-flow-char-height` CSS property in favor of `line-height` ([`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6))\n\n### Patch Changes\n\n- Updated dependencies [[`e8a8904`](https://github.com/barvian/number-flow/commit/e8a890432ef7f78661fce88ce53ac8e277ba3aa6)]:\n  - number-flow@0.6.0\n\n## 0.4.12\n\n### Patch Changes\n\n- Updated dependencies [[`5cc3c9b`](https://github.com/barvian/number-flow/commit/5cc3c9b7f7c223719047b964b47dd9d3a42fa371)]:\n  - number-flow@0.5.12\n\n## 0.4.11\n\n### Patch Changes\n\n- Add CSP strategies (see [#170](https://github.com/barvian/number-flow/issues/170)) ([`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67))\n\n- Updated dependencies [[`a7b3b0b`](https://github.com/barvian/number-flow/commit/a7b3b0b581fc05b914ea9e1ab1441da75b30bb67)]:\n  - number-flow@0.5.11\n\n## 0.4.10\n\n### Patch Changes\n\n- Updated dependencies [[`4a6c26e`](https://github.com/barvian/number-flow/commit/4a6c26efe13d6ffc1b84ea75accf511f63669eb9)]:\n  - number-flow@0.5.10\n\n## 0.4.9\n\n### Patch Changes\n\n- Updated dependencies [[`bdf8ce9`](https://github.com/barvian/number-flow/commit/bdf8ce92df67d6147d7f56998c625fc29e1b7571)]:\n  - number-flow@0.5.9\n\n## 0.4.8\n\n### Patch Changes\n\n- Fix \"custom element already defined\" bugs ([`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d))\n\n- Updated dependencies [[`a0d2a09`](https://github.com/barvian/number-flow/commit/a0d2a0901c06c647152654068163202e988d1f5d)]:\n  - number-flow@0.5.8\n\n## 0.4.7\n\n### Patch Changes\n\n- Updated dependencies [[`ee6a0a2`](https://github.com/barvian/number-flow/commit/ee6a0a2f2f09ba187b8df24cdfe0992ee7883192)]:\n  - number-flow@0.5.7\n\n## 0.4.6\n\n### Patch Changes\n\n- Updated dependencies [[`2539c4b`](https://github.com/barvian/number-flow/commit/2539c4b653fd4aaa17ef6b2ffd77b7a41454da08)]:\n  - number-flow@0.5.6\n\n## 0.4.5\n\n### Patch Changes\n\n- Updated dependencies [[`bc6476f`](https://github.com/barvian/number-flow/commit/bc6476f910ad58625491c23ed0a8768217f9ab57)]:\n  - number-flow@0.5.5\n\n## 0.4.4\n\n### Patch Changes\n\n- Release vanilla JS version ([`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d))\n\n- Updated dependencies [[`3929e33`](https://github.com/barvian/number-flow/commit/3929e33e8dcef03462593428639d66134f84c51d)]:\n  - number-flow@0.5.4\n\n## 0.4.3\n\n### Patch Changes\n\n- Revert mask-image change due to <1em char heights ([`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d))\n\n- Updated dependencies [[`e5be284`](https://github.com/barvian/number-flow/commit/e5be2840dfd0858894463beb8e3ebcffefb48d5d)]:\n  - number-flow@0.5.3\n\n## 0.4.2\n\n### Patch Changes\n\n- Improve `::selection` display and accessibility during transitions ([`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393))\n\n- Updated dependencies [[`301a755`](https://github.com/barvian/number-flow/commit/301a755edd8bde8ad8a6fe680c1882e8f6230393)]:\n  - number-flow@0.5.2\n\n## 0.4.1\n\n### Patch Changes\n\n- Add missing symbol part to SSR ([`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948))\n\n- Updated dependencies [[`34ea785`](https://github.com/barvian/number-flow/commit/34ea7856d6a75fba420bf379656dc3c8a7018948)]:\n  - number-flow@0.5.1\n\n## 0.4.0\n\n### Minor Changes\n\n- Move `continuous` prop into importable plugin ([`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff))\n\n### Patch Changes\n\n- Updated dependencies [[`e40a15e`](https://github.com/barvian/number-flow/commit/e40a15e3df55727a196ba1dc9a1230139f4d69ff)]:\n  - number-flow@0.5.0\n\n## 0.3.4\n\n### Patch Changes\n\n- Use UMD for CJS build (see [#88](https://github.com/barvian/number-flow/issues/88)) ([`2f9495d`](https://github.com/barvian/number-flow/commit/2f9495dd4b69dbd4716cfbeb2a1cfb2d9ecd0a00))\n\n## 0.3.3\n\n### Patch Changes\n\n- Add symbol part for styling all symbols ([`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb))\n\n- Updated dependencies [[`46ab8bd`](https://github.com/barvian/number-flow/commit/46ab8bd96467b1e27383546ce67a9889263ad0eb)]:\n  - number-flow@0.4.2\n\n## 0.3.2\n\n### Patch Changes\n\n- Reduce bundle size ([`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3))\n\n- Updated dependencies [[`efd355d`](https://github.com/barvian/number-flow/commit/efd355dda6c5005f5dec8bba0c4a0ff705144ee3)]:\n  - number-flow@0.4.1\n\n## 0.3.1\n\n### Patch Changes\n\n- Fix useCanAnimate hydration ([`e775131`](https://github.com/barvian/number-flow/commit/e775131a09628b98724dd1ec905d06ba78d06e21))\n\n## 0.3.0\n\n### Minor Changes\n\n- More flexible trend prop ([`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0))\n\n### Patch Changes\n\n- Add digits prop ([`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94))\n\n- Fix cursor and improve text selection ([`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809))\n\n- Updated dependencies [[`6f53990`](https://github.com/barvian/number-flow/commit/6f539906a439f567d50667d9fe9d52de4e2a4bd0), [`05423bb`](https://github.com/barvian/number-flow/commit/05423bbe4f0f4dab8caf442032fae9ecfccdbf94), [`8c1f922`](https://github.com/barvian/number-flow/commit/8c1f92232375bc35cf4a3b5f8136206c70918809)]:\n  - number-flow@0.4.0\n\n## 0.2.7\n\n### Patch Changes\n\n- Switch to TS private properties to reduce bundle size ([`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd))\n\n- Updated dependencies [[`765e43b`](https://github.com/barvian/number-flow/commit/765e43b4f2670ec532b5ef69b745d5d350f51bdd)]:\n  - number-flow@0.3.10\n\n## 0.2.6\n\n### Patch Changes\n\n- Improve tree-shaking ([`7885ecd`](https://github.com/barvian/number-flow/commit/7885ecddd717822b48dc43c4ab0cccbb8c33cf6f))\n\n## 0.2.5\n\n### Patch Changes\n\n- Expose parts for styling support ([`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4))\n\n- Updated dependencies [[`27156cc`](https://github.com/barvian/number-flow/commit/27156cc3d4750d06293b7022afca492024f4bea4)]:\n  - number-flow@0.3.9\n\n## 0.2.4\n\n### Patch Changes\n\n- Add prefix & suffix props ([`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14))\n\n- Updated dependencies [[`9854f77`](https://github.com/barvian/number-flow/commit/9854f77e11561fe119bf9009ae1369389a64ba15), [`adcf50f`](https://github.com/barvian/number-flow/commit/adcf50f93eec1f6a469004ab58aae4b2799b3c14)]:\n  - number-flow@0.3.8\n\n## 0.2.3\n\n### Patch Changes\n\n- Fix for `<NumberFlowGroup>` and v-if ([`a57700c`](https://github.com/barvian/number-flow/commit/a57700c211d67d2d1d8ea228bae9bd427bee553c))\n\n- Updated dependencies [[`0fac2f6`](https://github.com/barvian/number-flow/commit/0fac2f69b239048054755c556afc3f0eb65767c9)]:\n  - number-flow@0.3.7\n\n## 0.2.2\n\n### Patch Changes\n\n- Add `<NumberFlowGroup>` ([`ad220fd`](https://github.com/barvian/number-flow/commit/ad220fdb95b524b451e11bfcddd1f86e768e007d))\n\n## 0.2.1\n\n### Patch Changes\n\n- More defensive checks on browser globals (see [#58](https://github.com/barvian/number-flow/issues/58)) ([#59](https://github.com/barvian/number-flow/pull/59))\n\n- Updated dependencies [[`13a66fb`](https://github.com/barvian/number-flow/commit/13a66fba336c53687664ad9b859ec705891fce2a)]:\n  - number-flow@0.3.6\n\n## 0.2.0\n\n### Minor Changes\n\n- use lowercase event names ([`8406974`](https://github.com/barvian/number-flow/commit/8406974cbef1948c675336255fdfecc3a0e4107e))\n\n- Rename number-flow element to avoid conflicts between wrappers ([`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac))\n\n### Patch Changes\n\n- Updated dependencies [[`19abcf8`](https://github.com/barvian/number-flow/commit/19abcf88f7d7bd34332f5e1c42e647a0e81725ac)]:\n  - number-flow@0.3.5\n\n## 0.1.1\n\n### Patch Changes\n\n- Updated dependencies [[`ff966f4`](https://github.com/barvian/number-flow/commit/ff966f489eaeeacc72b35a8ee4c8cc13fe894eb6)]:\n  - number-flow@0.3.4\n"
  },
  {
    "path": "packages/vue/README.md",
    "content": "[![NumberFlow for Vue](https://number-flow.barvian.me/preview.webp)](https://number-flow.barvian.me/vue)\n\n# NumberFlow for Vue\n\nAn animated number component.\n\n[![NPM Version](https://img.shields.io/npm/v/@number-flow/vue.svg)](https://npmjs.com/package/@number-flow/vue)\n[![Bundle size](https://badgen.net/bundlephobia/minzip/@number-flow/vue@latest)](https://bundlephobia.com/package/@number-flow/vue@latest)\n[![Follow @mbarvian](https://img.shields.io/twitter/follow/mbarvian.svg?style=social&label=Follow)](https://x.com/mbarvian)\n\n## Documentation\n\nFor full documentation, visit [number-flow.barvian.me/vue](https://number-flow.barvian.me/vue).\n"
  },
  {
    "path": "packages/vue/package.json",
    "content": "{\n\t\"name\": \"@number-flow/vue\",\n\t\"type\": \"module\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"version\": \"0.5.0\",\n\t\"author\": {\n\t\t\"name\": \"Maxwell Barvian\",\n\t\t\"email\": \"max@barvian.me\",\n\t\t\"url\": \"https://barvian.me\"\n\t},\n\t\"description\": \"A component to transition and format numbers.\",\n\t\"license\": \"MIT\",\n\t\"homepage\": \"https://number-flow.barvian.me/vue\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/barvian/number-flow\",\n\t\t\"directory\": \"src\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/barvian/number-flow/issues\"\n\t},\n\t\"keywords\": [\n\t\t\"accessible\",\n\t\t\"odometer\",\n\t\t\"animation\",\n\t\t\"number-format\",\n\t\t\"number-animation\",\n\t\t\"animated-number\"\n\t],\n\t\"files\": [\n\t\t\"dist\",\n\t\t\"README.md\"\n\t],\n\t\"main\": \"./dist/index.umd.cjs\",\n\t\"module\": \"./dist/index.js\",\n\t\"types\": \"./dist/index.d.ts\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/index.d.ts\",\n\t\t\t\"import\": \"./dist/index.js\",\n\t\t\t\"require\": \"./dist/index.umd.cjs\"\n\t\t}\n\t},\n\t\"scripts\": {\n\t\t\"build\": \"vite build --mode production\",\n\t\t\"dev\": \"vite build --mode development --watch\",\n\t\t\"test\": \"pnpm -r --workspace-concurrency 1 --filter=\\\"./test/apps/*\\\" test\"\n\t},\n\t\"dependencies\": {\n\t\t\"esm-env\": \"^1.1.4\",\n\t\t\"number-flow\": \"workspace:*\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@types/node\": \"^22.7.9\",\n\t\t\"@vitejs/plugin-vue\": \"^5.1.4\",\n\t\t\"typescript\": \"^5.6.2\",\n\t\t\"vite\": \"^5.4.3\",\n\t\t\"vite-plugin-dts\": \"^4.3.0\",\n\t\t\"vue\": \"^3.5.12\"\n\t},\n\t\"peerDependencies\": {\n\t\t\"vue\": \"^3\"\n\t}\n}\n"
  },
  {
    "path": "packages/vue/src/NumberFlowGroup.vue",
    "content": "<script lang=\"ts\" setup>\nimport type NumberFlowLite from 'number-flow/lite'\nimport { key, type RegisterWithGroup } from './group'\nimport { provide, watch, type Ref, nextTick, onUnmounted } from 'vue'\n\nconst flows = new Set<Ref<NumberFlowLite | undefined>>()\nlet updating = false\n\nconst registerWithGroup: RegisterWithGroup = (el, parts) => {\n\tflows.add(el)\n\n\twatch(\n\t\tparts,\n\t\tasync () => {\n\t\t\tif (updating) return\n\t\t\tupdating = true\n\t\t\tflows.forEach(async (flow) => {\n\t\t\t\tif (!flow.value || !flow.value.created) return\n\t\t\t\tflow.value.willUpdate()\n\t\t\t\tawait nextTick()\n\t\t\t\t// Optional in case the element was removed after tick:\n\t\t\t\tflow.value?.didUpdate()\n\t\t\t})\n\t\t\tawait nextTick()\n\t\t\tupdating = false\n\t\t}\n\t\t// { flush: 'pre' } // default\n\t)\n\n\tonUnmounted(() => {\n\t\tflows.delete(el)\n\t})\n}\n\nprovide(key, registerWithGroup)\n</script>\n<template>\n\t<slot />\n</template>\n"
  },
  {
    "path": "packages/vue/src/group.ts",
    "content": "import type NumberFlowLite from 'number-flow/lite'\nimport type { formatToData } from 'number-flow/lite'\nimport type { InjectionKey, Ref, ComputedRef } from 'vue'\n\nexport type RegisterWithGroup = (\n\tel: Ref<NumberFlowLite | undefined>,\n\tparts: ComputedRef<ReturnType<typeof formatToData>>\n) => void\n\nexport const key = Symbol() as InjectionKey<RegisterWithGroup | undefined>\n"
  },
  {
    "path": "packages/vue/src/index.ts",
    "content": "import { computed, onMounted, ref, toValue, watchEffect, type MaybeRefOrGetter } from 'vue'\nimport NumberFlowElement, {\n\tcanAnimate as _canAnimate,\n\tdefine,\n\tprefersReducedMotion\n} from 'number-flow/lite'\nimport { buildStyles } from 'number-flow/csp'\n\nexport const styles = buildStyles('-vue')\n\nexport { default as NumberFlowGroup } from './NumberFlowGroup.vue'\n\nexport type { Value, Format, Trend } from 'number-flow/lite'\nexport * from 'number-flow/plugins'\nexport { NumberFlowElement }\n\ndefine('number-flow-vue', NumberFlowElement)\n\nexport { default } from './index.vue'\n\n// SSR-safe canAnimate\nexport function useCanAnimate({\n\trespectMotionPreference = true\n}: {\n\trespectMotionPreference?: MaybeRefOrGetter<boolean>\n} = {}) {\n\tconst canAnimate = ref(false)\n\tconst reducedMotion = ref(false)\n\n\t// Handle SSR:\n\tonMounted(() => {\n\t\tcanAnimate.value = _canAnimate\n\t\treducedMotion.value = prefersReducedMotion?.matches ?? false\n\t})\n\n\t// Listen for reduced motion changes if needed:\n\twatchEffect((onCleanup) => {\n\t\tif (!toValue(respectMotionPreference)) return\n\t\tconst onChange = ({ matches }: MediaQueryListEvent) => {\n\t\t\treducedMotion.value = matches\n\t\t}\n\t\tprefersReducedMotion?.addEventListener('change', onChange)\n\t\tonCleanup(() => {\n\t\t\tprefersReducedMotion?.removeEventListener('change', onChange)\n\t\t})\n\t})\n\n\treturn computed(\n\t\t() => canAnimate.value && (!toValue(respectMotionPreference) || !reducedMotion.value)\n\t)\n}\n"
  },
  {
    "path": "packages/vue/src/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport NumberFlowLite, {\n\ttype Value,\n\ttype Format,\n\trenderInnerHTML,\n\tformatToData,\n\ttype Props as NumberFlowProps\n} from 'number-flow/lite'\nimport { computed, inject, ref } from 'vue'\nimport { key as groupKey } from './group'\nimport { BROWSER } from 'esm-env'\n\ntype Props = Partial<NumberFlowProps> & {\n\tlocales?: Intl.LocalesArgument\n\tformat?: Format\n\tvalue: Value\n\tprefix?: string\n\tsuffix?: string\n\tnonce?: string\n\twillChange?: boolean\n}\n\n// This is repetitive but I couldn't get it any cleaner using `withDefaults`,\n// because then you can't destructure,\n// and if you don't set defaults Vue will use its own for e.g. booleans.\nconst {\n\tlocales,\n\tformat,\n\tvalue,\n\tprefix,\n\tsuffix,\n\tnonce,\n\t// Couldn't find docs on this, but needs wrapper function to work:\n\ttrend = () => NumberFlowLite.defaultProps.trend,\n\tplugins = NumberFlowLite.defaultProps.plugins,\n\tanimated = NumberFlowLite.defaultProps.animated,\n\ttransformTiming = NumberFlowLite.defaultProps.transformTiming,\n\tspinTiming = NumberFlowLite.defaultProps.spinTiming,\n\topacityTiming = NumberFlowLite.defaultProps.opacityTiming,\n\trespectMotionPreference = NumberFlowLite.defaultProps.respectMotionPreference,\n\tdigits = NumberFlowLite.defaultProps.digits,\n\twillChange = false\n} = defineProps<Props>()\n\nconst el = ref<NumberFlowLite>()\n\ndefineExpose({ el })\n\ndefineOptions({\n\tinheritAttrs: false // set them manually to ensure `parts` updates last\n})\n\nconst emit = defineEmits<{\n\t(e: 'animationsstart'): void\n\t(e: 'animationsfinish'): void\n}>()\n\n// You're supposed to cache these between uses:\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString\nconst formatter = computed(() => new Intl.NumberFormat(locales, format))\nconst data = computed(() => formatToData(value, formatter.value, prefix, suffix))\n\n// Putting this in the v-html attribute ruined tree-shaking\nconst html = BROWSER ? undefined : renderInnerHTML(data.value, { nonce, elementSuffix: '-vue' })\n\n// Handle grouping. Keep as much logic in NumberFlowGroup.vue as possible\n// for better tree-shaking:\nconst register = inject(groupKey, undefined)\nregister?.(el, data)\n</script>\n<template>\n\t<!-- Make sure data is set last: -->\n\t<number-flow-vue\n\t\tref=\"el\"\n\t\tv-bind=\"$attrs\"\n\t\t:batched=\"Boolean(register)\"\n\t\t:trend\n\t\t:plugins\n\t\t:animated\n\t\t:transformTiming\n\t\t:spinTiming\n\t\t:opacityTiming\n\t\t:respectMotionPreference\n\t\t:nonce\n\t\t:data-will-change=\"willChange ? '' : undefined\"\n\t\t:digits\n\t\tv-html=\"html\"\n\t\tdata-allow-mismatch\n\t\t@animationsstart=\"emit('animationsstart')\"\n\t\t@animationsfinish=\"emit('animationsfinish')\"\n\t\t:data\n\t/>\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/.gitignore",
    "content": "# Nuxt dev/build outputs\n.output\n.data\n.nuxt\n.nitro\n.cache\ndist\n\n# Node dependencies\nnode_modules\n\n# testing\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\n# Logs\nlogs\n*.log\n\n# Misc\n.DS_Store\n.fleet\n.idea\n\n# Local env files\n.env\n.env.*\n!.env.example\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/README.md",
    "content": "# Nuxt Minimal Starter\n\nLook at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.\n\n## Setup\n\nMake sure to install dependencies:\n\n```bash\n# npm\nnpm install\n\n# pnpm\npnpm install\n\n# yarn\nyarn install\n\n# bun\nbun install\n```\n\n## Development Server\n\nStart the development server on `http://localhost:3000`:\n\n```bash\n# npm\nnpm run dev\n\n# pnpm\npnpm dev\n\n# yarn\nyarn dev\n\n# bun\nbun run dev\n```\n\n## Production\n\nBuild the application for production:\n\n```bash\n# npm\nnpm run build\n\n# pnpm\npnpm build\n\n# yarn\nyarn build\n\n# bun\nbun run build\n```\n\nLocally preview production build:\n\n```bash\n# npm\nnpm run preview\n\n# pnpm\npnpm preview\n\n# yarn\nyarn preview\n\n# bun\nbun run preview\n```\n\nCheck out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/nuxt.config.ts",
    "content": "import tailwindcss from '@tailwindcss/vite'\nimport { createHash } from 'node:crypto'\nimport { styles } from '@number-flow/vue'\n\nconst hash = (style: string) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`\nconst hashesCsp = `style-src ${styles.map(hash).join(' ')}`\n\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n\tcompatibilityDate: '2024-04-03',\n\tdevtools: { enabled: false },\n\tsrcDir: 'src/',\n\tdevServer: {\n\t\tport: 3039\n\t},\n\trouteRules: {\n\t\t'/nonce': {\n\t\t\theaders: {\n\t\t\t\t'Content-Security-Policy': \"style-src 'nonce-test-nonce'\"\n\t\t\t}\n\t\t},\n\t\t'/hashes': {\n\t\t\theaders: {\n\t\t\t\t'Content-Security-Policy': hashesCsp\n\t\t\t}\n\t\t}\n\t},\n\tmodules: ['@nuxt/fonts'],\n\tcss: [\n\t\t// CSS file in the project\n\t\t'~/assets/css/main.css'\n\t],\n\timports: {\n\t\t// Breaks stuff b/c monorepo?\n\t\t// https://github.com/nuxt/nuxt/issues/18823\n\t\tautoImport: false\n\t},\n\tfonts: {\n\t\tdefaults: {\n\t\t\tweights: [400],\n\t\t\tstyles: ['normal'],\n\t\t\tsubsets: ['latin']\n\t\t},\n\t\texperimental: {\n\t\t\tdisableLocalFallbacks: true\n\t\t}\n\t},\n\tvite: {\n\t\tplugins: [tailwindcss()]\n\t}\n})\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/package.json",
    "content": "{\n\t\"name\": \"nuxt-app\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"nuxt dev\",\n\t\t\"build\": \"nuxt build\",\n\t\t\"start\": \"PORT=3039 nuxt start\",\n\t\t\"generate\": \"nuxt generate\",\n\t\t\"test\": \"playwright test\",\n\t\t\"test:ui\": \"playwright test --ui\",\n\t\t\"test:update\": \"playwright test --update-snapshots\"\n\t},\n\t\"dependencies\": {\n\t\t\"@number-flow/vue\": \"workspace:*\",\n\t\t\"@nuxt/fonts\": \"^0.10.2\",\n\t\t\"nuxt\": \"^3.15.4\",\n\t\t\"vue\": \"^3.5.13\",\n\t\t\"vue-router\": \"latest\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@playwright/test\": \"^1.48.0\",\n\t\t\"@tailwindcss/vite\": \"^4.0.9\",\n\t\t\"tailwindcss\": \"^4.0.10\"\n\t},\n\t\"packageManager\": \"pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228\"\n}\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/playwright.config.ts",
    "content": "export { config as default } from '../../../../../lib/playwright'\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/assets/css/main.css",
    "content": "@import 'tailwindcss';\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/pages/can-animate.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useCanAnimate } from '@number-flow/vue'\n\nconst canAnimate = useCanAnimate()\nconst disrespectMotionPreference = useCanAnimate({ respectMotionPreference: false })\n</script>\n<template>\n\t<div>\n\t\t<p data-testid=\"default\">{{ String(canAnimate) }}</p>\n\t\t<p data-testid=\"disrespect-motion-preference\">{{ String(disrespectMotionPreference) }}</p>\n\t</div>\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/pages/group-1-unchanged.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { NumberFlowGroup } from '@number-flow/vue'\nimport { nextTick, ref, useTemplateRef, watch } from 'vue'\n\nconst flow1 = useTemplateRef('flow1')\nconst flow2 = useTemplateRef('flow2')\nconst value = ref(42)\nwatch(\n\tvalue,\n\tasync () => {\n\t\tawait nextTick()\n\t\t;[\n\t\t\t...(flow1.value?.el?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(flow2.value?.el?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.pause()\n\t\t\ta.currentTime = 300\n\t\t})\n\t},\n\t{ flush: 'post' }\n)\n</script>\n<template>\n\t<div>\n\t\t<NumberFlowGroup>\n\t\t\t<NumberFlow ref=\"flow1\" :value />\n\t\t\t<NumberFlow ref=\"flow2\" :value=\"0\" />\n\t\t</NumberFlowGroup>\n\t</div>\n\t<button @click=\"value = 152000\">Change and pause</button><br />\n\t<button\n\t\t@click=\"\n\t\t\t() => {\n\t\t\t\t;[\n\t\t\t\t\t...(flow1?.el?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t...(flow2?.el?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t].forEach((a) => {\n\t\t\t\t\ta.play()\n\t\t\t\t})\n\t\t\t}\n\t\t\"\n\t>\n\t\tResume\n\t</button>\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/pages/hashes.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow from '@number-flow/vue'\n</script>\n<template>\n\t<NumberFlow :value=\"42\" />\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { NumberFlowGroup, continuous } from '@number-flow/vue'\nimport { nextTick, ref, useTemplateRef, watch } from 'vue'\n\nconst flow1 = useTemplateRef('flow1')\nconst flow2 = useTemplateRef('flow2')\nconst value = ref(42)\nwatch(\n\tvalue,\n\tasync () => {\n\t\tawait nextTick()\n\t\t;[\n\t\t\t...(flow1.value?.el?.shadowRoot?.getAnimations() ?? []),\n\t\t\t...(flow2.value?.el?.shadowRoot?.getAnimations() ?? [])\n\t\t].forEach((a) => {\n\t\t\ta.pause()\n\t\t\ta.currentTime = 300\n\t\t})\n\t},\n\t{ flush: 'post' }\n)\n\nconst handleStart = () => console.log('start')\nconst handleFinish = () => console.log('finish')\n</script>\n<template>\n\t<div>\n\t\tText node\n\t\t<NumberFlowGroup>\n\t\t\t<NumberFlow\n\t\t\t\tid=\"flow1\"\n\t\t\t\tdata-testid=\"flow1\"\n\t\t\t\tref=\"flow1\"\n\t\t\t\t:value\n\t\t\t\t:format=\"{ style: 'currency', currency: 'USD' }\"\n\t\t\t\tlocales=\"zh-CN\"\n\t\t\t\t:trend=\"() => -1\"\n\t\t\t\tprefix=\":\"\n\t\t\t\tsuffix=\"/mo\"\n\t\t\t\t@animationsstart=\"handleStart\"\n\t\t\t\t@animationsfinish=\"handleFinish\"\n\t\t\t\t:transformTiming=\"{ easing: 'linear', duration: 500 }\"\n\t\t\t\t:spinTiming=\"{ easing: 'linear', duration: 800 }\"\n\t\t\t\t:opacityTiming=\"{ easing: 'linear', duration: 500 }\"\n\t\t\t/>\n\t\t\t<NumberFlow\n\t\t\t\tid=\"flow2\"\n\t\t\t\tdata-testid=\"flow2\"\n\t\t\t\tref=\"flow2\"\n\t\t\t\t:value\n\t\t\t\t:respectMotionPreference=\"false\"\n\t\t\t\t:plugins=\"[continuous]\"\n\t\t\t\t:digits=\"{ 0: { max: 2 } }\"\n\t\t\t\t:transformTiming=\"{ easing: 'linear', duration: 500 }\"\n\t\t\t\t:spinTiming=\"{ easing: 'linear', duration: 800 }\"\n\t\t\t\t:opacityTiming=\"{ easing: 'linear', duration: 500 }\"\n\t\t\t/>\n\t\t</NumberFlowGroup>\n\t</div>\n\t<button @click=\"value = 152\">Change and pause</button><br />\n\t<button\n\t\t@click=\"\n\t\t\t() => {\n\t\t\t\t;[\n\t\t\t\t\t...(flow1?.el?.shadowRoot?.getAnimations() ?? []),\n\t\t\t\t\t...(flow2?.el?.shadowRoot?.getAnimations() ?? [])\n\t\t\t\t].forEach((a) => {\n\t\t\t\t\ta.play()\n\t\t\t\t})\n\t\t\t}\n\t\t\"\n\t>\n\t\tResume\n\t</button>\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/pages/nonce.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow from '@number-flow/vue'\n</script>\n<template>\n\t<NumberFlow :value=\"42\" nonce=\"test-nonce\" />\n\t<NumberFlow :value=\"42\" nonce=\"test-nonce\" />\n</template>\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/src/server/tsconfig.json",
    "content": "{\n\t\"extends\": \"../../.nuxt/tsconfig.server.json\"\n}\n"
  },
  {
    "path": "packages/vue/test/apps/nuxt3/tsconfig.json",
    "content": "{\n\t// https://nuxt.com/docs/guide/concepts/typescript\n\t\"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "packages/vue/tsconfig.build.json",
    "content": "{\n\t\"extends\": [\"./tsconfig.json\", \"../../tsconfig.build.json\"]\n}\n"
  },
  {
    "path": "packages/vue/tsconfig.json",
    "content": "{\n\t\"extends\": \"../../tsconfig.json\",\n\t\"include\": [\"src\"],\n\t\"compilerOptions\": {\n\t\t\"declaration\": true,\n\t\t\"outDir\": \"./dist\"\n\t}\n}\n"
  },
  {
    "path": "packages/vue/vite.config.mjs",
    "content": "import { resolve } from 'path'\nimport { defineConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport vue from '@vitejs/plugin-vue'\n\nconst outDir = resolve(__dirname, 'dist')\n\nexport default defineConfig(({ mode }) => ({\n\tplugins: [\n\t\tvue({\n\t\t\ttemplate: {\n\t\t\t\tcompilerOptions: {\n\t\t\t\t\tisCustomElement: (tag) => tag === 'number-flow-vue'\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tdts({\n\t\t\ttsconfigPath: resolve(__dirname, `./tsconfig${mode === 'production' ? '.build' : ''}.json`),\n\t\t\trollupTypes: true,\n\t\t\tinclude: ['src']\n\t\t})\n\t],\n\tbuild: {\n\t\toutDir,\n\t\tlib: {\n\t\t\tname: 'number-flow-vue', // required for UMD build\n\t\t\tentry: {\n\t\t\t\tindex: resolve(__dirname, 'src/index.ts')\n\t\t\t},\n\t\t\tfileName: (format, name) => {\n\t\t\t\treturn `${name}.${format === 'es' ? 'js' : 'umd.cjs'}`\n\t\t\t}\n\t\t},\n\t\trollupOptions: {\n\t\t\texternal: ['vue', 'number-flow/lite', 'number-flow/plugins', 'esm-env'],\n\t\t\toutput: {\n\t\t\t\t// Names for UMD builds\n\t\t\t\tglobals: {\n\t\t\t\t\tvue: 'Vue',\n\t\t\t\t\t'esm-env': 'Env',\n\t\t\t\t\t'number-flow': 'NumberFlow'\n\t\t\t\t},\n\t\t\t\texports: 'named'\n\t\t\t}\n\t\t}\n\t}\n}))\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'packages/*'\n  - 'packages/number-flow/test/apps/*'\n  - 'packages/react/test/apps/*'\n  - 'packages/vue/test/apps/*'\n  - 'site'\n"
  },
  {
    "path": "prettier.config.js",
    "content": "/** @type {import('prettier').Config} */\nexport default {\n\tuseTabs: true,\n\tsingleQuote: true,\n\tsemi: false,\n\ttrailingComma: 'none',\n\tprintWidth: 100,\n\tplugins: ['prettier-plugin-astro', 'prettier-plugin-svelte', 'prettier-plugin-tailwindcss'],\n\toverrides: [\n\t\t{\n\t\t\tfiles: '*.astro',\n\t\t\toptions: {\n\t\t\t\tparser: 'astro'\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tfiles: '*.svelte',\n\t\t\toptions: {\n\t\t\t\tparser: 'svelte'\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "site/.gitignore",
    "content": "# build output\ndist/\n\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# jetbrains setting folder\n.idea/\n.vercel\n"
  },
  {
    "path": "site/.vscode/extensions.json",
    "content": "{\n\t\"recommendations\": [\"astro-build.astro-vscode\"],\n\t\"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": "site/.vscode/launch.json",
    "content": "{\n\t\"version\": \"0.2.0\",\n\t\"configurations\": [\n\t\t{\n\t\t\t\"command\": \"./node_modules/.bin/astro dev\",\n\t\t\t\"name\": \"Development server\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"type\": \"node-terminal\"\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "site/astro.config.mjs",
    "content": "import { defineConfig, envField } from 'astro/config'\nimport pkg from '/../packages/number-flow/package.json'\nimport mdx from '@astrojs/mdx'\nimport vercel from '@astrojs/vercel'\nimport shikiTheme from './highlighter-theme.json'\nimport react from '@astrojs/react'\nimport vue from '@astrojs/vue'\nimport svelte from '@astrojs/svelte'\n\n// https://astro.build/config\nexport default defineConfig({\n\tsite: pkg.homepage,\n\tmarkdown: {\n\t\tshikiConfig: {\n\t\t\t// @ts-ignore\n\t\t\ttheme: shikiTheme\n\t\t}\n\t},\n\tvite: {\n\t\tplugins: [\n\t\t\t{\n\t\t\t\tname: 'transform-vanilla-examples',\n\t\t\t\ttransform(code, id) {\n\t\t\t\t\t// Make .astro examples look like valid HTML:\n\t\t\t\t\tif (id.endsWith('.astro?raw')) {\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\t\t.replaceAll('<script>', '<script type=\\\\\"module\\\\\">')\n\t\t\t\t\t\t\t\t// Remove @ts-nocheck comments\n\t\t\t\t\t\t\t\t.replaceAll(/(\\\\t)*\\/\\/ @ts-nocheck\\\\n/g, '')\n\t\t\t\t\t\t\t\t.replaceAll(/(\\\\t)*\\/\\/ prettier-ignore\\\\n/g, '')\n\t\t\t\t\t\t\t\t// Clean up IDs\n\t\t\t\t\t\t\t\t.replaceAll(/vanilla-example-.*?-/g, '')\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\tssr: {\n\t\t\t// Fixes build issue on macOS\n\t\t\texternal: ['fsevents']\n\t\t}\n\t},\n\tenv: {\n\t\tschema: {\n\t\t\tGITHUB_TOKEN: envField.string({ context: 'server', access: 'secret' })\n\t\t}\n\t},\n\texperimental: {\n\t\tsvg: true\n\t},\n\tintegrations: [\n\t\treact(),\n\t\tmdx(),\n\t\t{\n\t\t\tname: 'watch-shiki-theme',\n\t\t\thooks: {\n\t\t\t\t'astro:config:setup'({ addWatchFile, config }) {\n\t\t\t\t\taddWatchFile(new URL('./highlighter-theme.json', config.root))\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tvue({\n\t\t\ttemplate: {\n\t\t\t\tcompilerOptions: {\n\t\t\t\t\t// isCustomElement: (tag) => tag === 'number-flow'\n\t\t\t\t}\n\t\t\t}\n\t\t\t// ...\n\t\t}),\n\t\tsvelte()\n\t],\n\toutput: 'static',\n\tadapter: vercel({\n\t\twebAnalytics: {\n\t\t\tenabled: true\n\t\t}\n\t})\n})\n"
  },
  {
    "path": "site/highlighter-theme.json",
    "content": "{\n\t\"name\": \"Lambda Studio — Blackout\",\n\t\"semanticHighlighting\": true,\n\t\"colors\": {\n\t\t\"editorLink.activeForeground\": \"#ca8a0488\",\n\t\t\"foreground\": \"#fff9\",\n\t\t\"button.background\": \"#fff\",\n\t\t\"button.foreground\": \"#000\",\n\t\t\"button.hoverBackground\": \"#fffb\",\n\t\t\"list.highlightForeground\": \"#fff\",\n\t\t\"textLink.foreground\": \"#fff\",\n\t\t\"scrollbar.shadow\": \"#000\",\n\t\t\"textLink.activeForeground\": \"#fff9\",\n\t\t\"editor.lineHighlightBackground\": \"#8881\",\n\t\t\"editor.lineHighlightBorder\": \"#8882\",\n\t\t\"editorCursor.foreground\": \"#fff\",\n\t\t\"editor.findMatchBackground\": \"#fff9\",\n\t\t\"editor.findMatchHighlightBackground\": \"#fff2\",\n\t\t\"list.activeSelectionForeground\": \"#fff\",\n\t\t\"list.focusForeground\": \"#fff\",\n\t\t\"list.hoverForeground\": \"#fff\",\n\t\t\"list.inactiveSelectionForeground\": \"#fff\",\n\t\t\"list.inactiveSelectionBackground\": \"#000\",\n\t\t\"list.focusBackground\": \"#000\",\n\t\t\"list.focusAndSelectionOutline\": \"#000\",\n\t\t\"list.focusHighlightForeground\": \"#fff\",\n\t\t\"list.hoverBackground\": \"#000\",\n\t\t\"list.focusOutline\": \"#000\",\n\t\t\"list.activeSelectionBackground\": \"#000\",\n\t\t\"editorIndentGuide.background\": \"#fff2\",\n\t\t\"editor.background\": \"#000\",\n\t\t\"editor.foreground\": \"#fff\",\n\t\t\"editor.foldBackground\": \"#000\",\n\t\t\"editor.hoverHighlightBackground\": \"#000\",\n\t\t\"editor.selectionBackground\": \"#8888\",\n\t\t\"editor.inactiveSelectionBackground\": \"#8882\",\n\t\t\"gitDecoration.modifiedResourceForeground\": \"#fff\",\n\t\t\"gitDecoration.untrackedResourceForeground\": \"#a7cb7b\",\n\t\t\"gitDecoration.conflictingResourceForeground\": \"#ca8a04\",\n\t\t\"gitDecoration.deletedResourceForeground\": \"#c97b89\",\n\t\t\"listFilterWidget.background\": \"#000\",\n\t\t\"input.background\": \"#fff1\",\n\t\t\"titleBar.activeForeground\": \"#fff\",\n\t\t\"editorWidget.background\": \"#000\",\n\t\t\"editorGutter.background\": \"#000\",\n\t\t\"debugToolBar.background\": \"#000\",\n\t\t\"commandCenter.background\": \"#000\",\n\t\t\"sideBarSectionHeader.background\": \"#000\",\n\t\t\"focusBorder\": \"#fff9\",\n\t\t\"titleBar.activeBackground\": \"#000\",\n\t\t\"titleBar.inactiveBackground\": \"#000\",\n\t\t\"breadcrumb.background\": \"#000\",\n\t\t\"activityBar.background\": \"#000\",\n\t\t\"activityBar.foreground\": \"#fff9\",\n\t\t\"panel.background\": \"#000\",\n\t\t\"sideBar.background\": \"#000\",\n\t\t\"sideBarTitle.foreground\": \"#fff9\",\n\t\t\"tab.hoverBackground\": \"#000\",\n\t\t\"terminal.background\": \"#000\",\n\t\t\"statusBar.background\": \"#000\",\n\t\t\"statusBar.foreground\": \"#fff9\",\n\t\t\"selection.background\": \"#fff2\",\n\t\t\"editorPane.background\": \"#000\",\n\t\t\"badge.background\": \"#000\",\n\t\t\"banner.background\": \"#000\",\n\t\t\"menu.background\": \"#000\",\n\t\t\"activityBarBadge.background\": \"#000\",\n\t\t\"activityBarBadge.foreground\": \"#fff9\",\n\t\t\"editorLineNumber.foreground\": \"#fff2\",\n\t\t\"editorLineNumber.activeForeground\": \"#fff9\",\n\t\t\"statusBarItem.errorBackground\": \"#f43f5e\"\n\t},\n\t\"semanticTokenColors\": {\n\t\t\"comment\": {\n\t\t\t\"foreground\": \"#fff5\"\n\t\t},\n\t\t\"keyword\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"string\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"selfKeyword\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"method.declaration\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"method.definition\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"method\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"function.declaration\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"function.definition\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"function\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"property\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"enumMember\": {\n\t\t\t\"foreground\": \"#fff9\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"enum\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"boolean\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"number\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"type\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"typeAlias\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"class\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"selfTypeKeyword\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"builtinType\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"interface\": {\n\t\t\t\"foreground\": \"#fff9\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"typeParameter\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t},\n\t\t\"lifetime\": {\n\t\t\t\"foreground\": \"#fff9\",\n\t\t\t\"italic\": false,\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"namespace\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"macro\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"decorator\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"builtinAttribute\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": false\n\t\t},\n\t\t\"generic.attribute\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"derive\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"operator\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"variable\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"variable.readonly\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"parameter\": {\n\t\t\t\"foreground\": \"#fff\"\n\t\t},\n\t\t\"variable.mutable\": {\n\t\t\t\"underline\": true\n\t\t},\n\t\t\"parameter.mutable\": {\n\t\t\t\"underline\": true\n\t\t},\n\t\t\"selfKeyword.mutable\": {\n\t\t\t\"underline\": true\n\t\t},\n\t\t\"variable.constant\": {\n\t\t\t\"foreground\": \"#fff9\"\n\t\t},\n\t\t\"struct\": {\n\t\t\t\"foreground\": \"#fff\",\n\t\t\t\"bold\": true\n\t\t}\n\t},\n\t\"tokenColors\": [\n\t\t{\n\t\t\t\"name\": \"Fallback Operator\",\n\t\t\t\"scope\": [\"keyword.operator\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback keywords\",\n\t\t\t\"scope\": [\n\t\t\t\t\"storage.type.ts\",\n\t\t\t\t\"keyword\",\n\t\t\t\t\"keyword.other\",\n\t\t\t\t\"keyword.control\",\n\t\t\t\t\"storage.type\",\n\t\t\t\t\"storage.modifier\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback strings\",\n\t\t\t\"scope\": [\"string\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback JSON Properties\",\n\t\t\t\"scope\": [\"support.type.property-name.json\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback string variables\",\n\t\t\t\"scope\": [\"string variable\", \"string meta.interpolation\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback comments\",\n\t\t\t\"scope\": [\"comment\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff5\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback constants\",\n\t\t\t\"scope\": [\"constant\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback self/this\",\n\t\t\t\"scope\": [\"variable.language.this\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback types\",\n\t\t\t\"scope\": [\n\t\t\t\t\"entity.other.alias\",\n\t\t\t\t\"source.php support.class\",\n\t\t\t\t\"entity.name.type\",\n\t\t\t\t\"meta.function-call support.class\",\n\t\t\t\t\"keyword.other.type\",\n\t\t\t\t\"entity.other.inherited-class\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback method calls\",\n\t\t\t\"scope\": [\"meta.method-call entity.name.function\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\",\n\t\t\t\t\"fontStyle\": \"\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback function calls\",\n\t\t\t\"scope\": [\n\t\t\t\t\"meta.function-call entity.name.function\",\n\t\t\t\t\"meta.function-call support.function\",\n\t\t\t\t\"meta.function.call entity.name.function\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\",\n\t\t\t\t\"fontStyle\": \"\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback enums & constants\",\n\t\t\t\"scope\": [\"constant.enum\", \"constant.other\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback Properties & func arguments\",\n\t\t\t\"scope\": [\n\t\t\t\t\"variable.other.property\",\n\t\t\t\t\"entity.name.goto-label\",\n\t\t\t\t\"entity.name.variable.parameter\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Fallback functions & methods declarations\",\n\t\t\t\"scope\": [\n\t\t\t\t\"entity.name.function\",\n\t\t\t\t\"support.function\",\n\t\t\t\t\"support.function.constructor\",\n\t\t\t\t\"entity.name.function meta.function-call meta.method-call\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\",\n\t\t\t\t\"fontStyle\": \"bold\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"HTML Tags\",\n\t\t\t\"scope\": [\"meta.tag entity.name.tag.html\", \"entity.name.tag.template.html\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"HTML Attributes\",\n\t\t\t\"scope\": [\"entity.other.attribute-name.html\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"HTML Custom Tag\",\n\t\t\t\"scope\": [\"meta.tag.other.unrecognized.html entity.name.tag.html\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"HTML Keywords\",\n\t\t\t\"scope\": [\"text.html keyword\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Punctuations\",\n\t\t\t\"scope\": [\"punctuation\", \"meta.brace\"],\n\t\t\t\"settings\": {\n\t\t\t\t\"foreground\": \"#fff9\"\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "site/package.json",
    "content": "{\n\t\"name\": \"site\",\n\t\"private\": true,\n\t\"version\": \"0.0.1\",\n\t\"scripts\": {\n\t\t\"dev\": \"astro dev\",\n\t\t\"start\": \"astro dev\",\n\t\t\"build\": \"astro check && astro build && sharp -i ./src/assets/preview.png -o ./.vercel/output/static/preview.webp -f webp\",\n\t\t\"preview\": \"astro preview\",\n\t\t\"astro\": \"astro\"\n\t},\n\t\"dependencies\": {\n\t\t\"@astrojs/check\": \"^0.9.4\",\n\t\t\"@astrojs/mdx\": \"^4.0.2\",\n\t\t\"@astrojs/react\": \"^4.4.2\",\n\t\t\"@astrojs/svelte\": \"^7.0.1\",\n\t\t\"@astrojs/vue\": \"^5.0.2\",\n\t\t\"@nanostores/react\": \"^0.8.2\",\n\t\t\"@nanostores/vue\": \"^0.11.0\",\n\t\t\"@number-flow/react\": \"workspace:*\",\n\t\t\"@number-flow/svelte\": \"workspace:*\",\n\t\t\"@number-flow/vue\": \"workspace:*\",\n\t\t\"@radix-ui/react-slider\": \"^1.2.2\",\n\t\t\"@react-aria/utils\": \"^3.31.0\",\n\t\t\"@types/lodash\": \"^4.17.13\",\n\t\t\"@types/react\": \"^19.2.14\",\n\t\t\"@types/react-dom\": \"^19.2.3\",\n\t\t\"astro\": \"^5.0.5\",\n\t\t\"astro-font\": \"^0.1.81\",\n\t\t\"bits-ui\": \"^0.21.16\",\n\t\t\"clsx\": \"^2.1.1\",\n\t\t\"date-fns\": \"^2.30.0\",\n\t\t\"lodash\": \"^4.17.21\",\n\t\t\"lucide-react\": \"^0.468.0\",\n\t\t\"lucide-svelte\": \"^0.468.0\",\n\t\t\"lucide-vue-next\": \"^0.468.0\",\n\t\t\"motion\": \"^11.14.4\",\n\t\t\"nanostores\": \"^0.11.3\",\n\t\t\"number-flow\": \"workspace:*\",\n\t\t\"react\": \"19.3.0-canary-e0cc7202-20260227\",\n\t\t\"react-aria-components\": \"^1.15.1\",\n\t\t\"react-dom\": \"19.3.0-canary-e0cc7202-20260227\",\n\t\t\"svelte\": \"^5\",\n\t\t\"tailwindcss\": \"^3.4.16\",\n\t\t\"tailwindcss-spring\": \"^1.0.1\",\n\t\t\"typescript\": \"^5.7.2\",\n\t\t\"vue\": \"^3.5.13\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@astrojs/vercel\": \"^8.0.1\",\n\t\t\"@astropub/context\": \"^0.1.0\",\n\t\t\"@tailwindcss/typography\": \"^0.5.15\",\n\t\t\"fluid-tailwind\": \"^1.0.4\",\n\t\t\"github-slugger\": \"^2.0.0\",\n\t\t\"postcss-easing-gradients\": \"^3.0.1\",\n\t\t\"radix-vue\": \"^1.9.11\",\n\t\t\"sharp\": \"^0.33.5\",\n\t\t\"sharp-cli\": \"^5.1.0\",\n\t\t\"tw-reset\": \"^0.0.5\",\n\t\t\"unist-util-find-after\": \"^5.0.0\",\n\t\t\"unist-util-visit\": \"^5.0.0\"\n\t}\n}\n"
  },
  {
    "path": "site/postcss.config.cjs",
    "content": "module.exports = {\n\tplugins: [\n\t\trequire('tailwindcss'),\n\t\t// @ts-expect-error no types\n\t\trequire('postcss-easing-gradients')\n\t]\n}\n"
  },
  {
    "path": "site/src/assets/main.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@font-face {\n\tfont-weight: 100 900;\n\tfont-style: normal;\n\tfont-family: Inter;\n\tfont-display: block;\n\tsrc: url('./fonts/Inter-roman-latin.var.woff2');\n}\n\n/* Generated by AstroFont, but injected to avoid View Transitions weirdness with <style> tags */\n@font-face {\n\tfont-family: '_font_fallback_732902278794';\n\tsize-adjust: 107.64%;\n\tsrc: local('Arial');\n\tascent-override: 90%;\n\tdescent-override: 22.43%;\n\tline-gap-override: 0%;\n}\n\n@layer base {\n\tnumber-flow,\n\tnumber-flow-react,\n\tnumber-flow-vue,\n\tnumber-flow-svelte {\n\t\t@apply font-mac-ui !leading-[.85];\n\t}\n\n\t/* Undo Tailwind preflight button cursor pointer */\n\tbutton {\n\t\tcursor: default;\n\t}\n\n\t[data-rac]:focus-visible {\n\t\toutline: none;\n\t}\n\n\t:focus-visible,\n\t[data-rac][data-focus-visible] {\n\t\t@apply outline;\n\t}\n}\n\n@layer components {\n\t.container {\n\t\t@apply ~px-6/10 mx-auto w-full;\n\t}\n\n\t.link-underline {\n\t\t@apply ease-out-quad hover:text-primary underline decoration-zinc-300 decoration-[1px] underline-offset-[0.3em] transition-[color,text-decoration-color] hover:decoration-current dark:decoration-zinc-600;\n\t}\n\n\t.btn {\n\t\t@apply inline-flex h-11 items-center gap-2 whitespace-nowrap rounded-full px-5 text-sm font-medium transition duration-[.16s] ease-[cubic-bezier(.4,0,.2,1)] active:scale-[98%] active:brightness-[98%] active:duration-[25ms] dark:hover:brightness-125;\n\t}\n\n\t.btn-primary {\n\t\t@apply bg-zinc-900 text-zinc-50 hover:brightness-125;\n\t}\n\n\t.btn-callout {\n\t\t@apply btn-primary dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-white dark:hover:brightness-100;\n\t}\n\n\t.btn-secondary {\n\t\t@apply hover:bg-zinc-100 aria-expanded:bg-zinc-100 dark:hover:bg-zinc-900 dark:aria-expanded:bg-zinc-900;\n\t}\n\n\t/* We can't define this in the tailwind config because .Demo has .not-prose on it: */\n\t.prose .Demo {\n\t\t@apply my-5;\n\t}\n}\n\n@layer utilities {\n\t.outline {\n\t\t@apply outline-offset [:where(&)]:rounded;\n\t}\n\n\t.text-primary {\n\t\t@apply text-zinc-950 dark:text-zinc-50;\n\t}\n\n\t.caret-primary {\n\t\t@apply caret-zinc-950 dark:caret-zinc-50;\n\t}\n\n\t.text-muted {\n\t\t@apply text-zinc-500 dark:text-zinc-400;\n\t}\n\n\t.bg-muted {\n\t\t@apply bg-zinc-500 dark:bg-zinc-400;\n\t}\n\n\t.bg-faint {\n\t\t@apply bg-zinc-150 dark:bg-zinc-700;\n\t}\n\n\t.bg-mask-white {\n\t\tbackground-image: linear-gradient(to top, theme(colors.white), ease-out, transparent);\n\t}\n\n\t.bg-mask-zinc-950 {\n\t\tbackground-image: linear-gradient(to top, theme(colors.zinc.950), ease-out, transparent);\n\t}\n\n\t.scrollbar-none {\n\t\t-ms-overflow-style: none; /* IE */\n\t\tscrollbar-width: none; /* Firefox */\n\t\t&::-webkit-scrollbar {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t.spin-hide {\n\t\tappearance: textfield;\n\t\t&::-webkit-inner-spin-button {\n\t\t\tappearance: none;\n\t\t\tmargin: 0;\n\t\t}\n\t\t&::-webkit-outer-spin-button {\n\t\t\tappearance: none;\n\t\t\tmargin: 0;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "site/src/components/Alert.astro",
    "content": "<div\n\trole=\"alert\"\n\tclass:list={[\n\t\t'text-primary border-faint relative rounded-lg border px-4 py-3 text-sm' /*, 'border-amber-200 bg-amber-50  dark:border-yellow-950 dark:bg-yellow-950/50'*/\n\t]}\n>\n\t<slot />\n</div>\n"
  },
  {
    "path": "site/src/components/AnimateHeightFragment.tsx",
    "content": "import { spring } from '@/lib/spring'\nimport { usePrefersReducedMotion } from '@number-flow/react'\nimport { cloneElement, useLayoutEffect, useRef } from 'react'\nimport { mergeRefs } from '@react-aria/utils'\nimport { Snapshotter, type SnapshotterProps } from './Snapshotter'\n\nexport type AnimateHeightProps = Pick<SnapshotterProps, 'dependencies'> & {\n\tchildren: React.ReactElement<{ ref: React.Ref<HTMLElement> }>\n}\n\nexport default function AnimateHeightFragment(props: AnimateHeightProps) {\n\tconst prefersReducedMotion = usePrefersReducedMotion()\n\tif (prefersReducedMotion) {\n\t\treturn props.children\n\t}\n\n\treturn <AnimateHeightImpl {...props} />\n}\n\nconst timing = spring(0.15, 0)\nconsole.log(timing)\n\nfunction AnimateHeightImpl({ children, dependencies }: AnimateHeightProps) {\n\tconst ref = useRef<HTMLElement>(null)\n\tconst heightSnapshotRef = useRef<number | undefined>(undefined)\n\n\tuseLayoutEffect(() => {\n\t\tif (!ref.current) return\n\n\t\tlet animation: Animation | undefined\n\t\tconst frame = requestAnimationFrame(() => {\n\t\t\tif (!ref.current) return\n\t\t\tconst newHeight = ref.current.offsetHeight\n\t\t\tif (heightSnapshotRef.current === newHeight) return\n\t\t\tanimation = ref.current.animate(\n\t\t\t\t{\n\t\t\t\t\theight: [`${heightSnapshotRef.current}px`, `${newHeight}px`]\n\t\t\t\t},\n\t\t\t\ttiming\n\t\t\t)\n\t\t})\n\t\t// Use WAAPI because motion writes to inline styles which complicates\n\t\t// measuring:\n\n\t\treturn () => {\n\t\t\tcancelAnimationFrame(frame)\n\t\t\tanimation?.cancel()\n\t\t}\n\t}, dependencies)\n\n\treturn (\n\t\t<>\n\t\t\t<Snapshotter\n\t\t\t\tdependencies={dependencies}\n\t\t\t\tonSnapshot={() => {\n\t\t\t\t\tif (!ref.current) return\n\t\t\t\t\theightSnapshotRef.current = ref.current.offsetHeight\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{cloneElement(children, {\n\t\t\t\tref: mergeRefs(ref, children.props.ref)\n\t\t\t})}\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/AnimationsOnTheWeb.astro",
    "content": "---\nimport { Image } from 'astro:assets'\nimport headshot from '@/assets/images/headshot.jpeg'\nimport { ArrowUpRight } from 'lucide-react'\n---\n\n<aside class=\"border-faint my-16 border-y py-8\">\n\t<h2 class=\"m-0 text-lg font-medium\">Learn to build components like NumberFlow</h2>\n\t<blockquote class=\"text-pretty not-italic text-[unset] [font-weight:unset]\">\n\t\t<!-- prettier-ignore -->\n\t\t<p>Emil Kowalski's Animations on the Web course taught me everything I know about UI animation. I can't\n\t\t\timagine having built NumberFlow without it.</p>\n\t</blockquote>\n\t<p class=\"not-prose text-muted -mt-1 mb-7 flex items-center gap-2 text-sm\">\n\t\t<Image\n\t\t\tsrc={headshot}\n\t\t\talt=\"Headshot of Maxwell Barvian\"\n\t\t\twidth={64}\n\t\t\theight={64}\n\t\t\tclass=\"size-6 rounded-full\"\n\t\t/>\n\t\tMaxwell Barvian, NumberFlow creator\n\t</p>\n\t<a href=\"https://animations.dev\" rel=\"external\" target=\"_blank\" class=\"btn btn-callout not-prose\">\n\t\tTake the course\n\t\t<ArrowUpRight className=\"size-4\" />\n\t</a>\n</aside>\n"
  },
  {
    "path": "site/src/components/Code.astro",
    "content": "---\nimport styles from './code.module.css'\nimport { Code as AstroCode } from 'astro:components'\nimport theme from '/highlighter-theme.json'\nimport type { ComponentProps } from 'astro/types'\n\nexport type Props = ComponentProps<typeof AstroCode> & {\n\tfirstLine?: string\n}\nconst { code: _code, inline, firstLine, ...props } = Astro.props\n\n// Clean up code a little bit:\nconst code = _code\n\t.split('\\n')\n\t// Remove prettier-ignore comments:\n\t.filter((line) => !/^\\s*\\/\\/ prettier-ignore\\s*$/.test(line))\n\nif (firstLine) code.unshift(firstLine)\n---\n\n<AstroCode\n\t{...props}\n\tcode={code.join('\\n')}\n\t{inline}\n\tclass:list={[!inline && styles.code]}\n\ttheme={theme as any}\n/>\n"
  },
  {
    "path": "site/src/components/Comp.mdx",
    "content": "import Match from './Match.astro'\n\n<Match><Fragment slot=\"vanilla\">`<number-flow>`</Fragment>`<NumberFlow>`</Match>"
  },
  {
    "path": "site/src/components/Demo.tsx",
    "content": "import * as React from 'react'\n// import { atom, useAtom } from 'jotai'\nimport { clsx } from 'clsx/lite'\nimport { inView, motion, MotionConfig } from 'motion/react'\nimport { useId } from 'react'\nimport {\n\tMenuTrigger,\n\tButton,\n\tPopover,\n\tMenu,\n\tMenuItem,\n\tSwitch,\n\tTabs,\n\tTabList,\n\tTab,\n\tTabPanel\n} from 'react-aria-components'\nimport { Check, ChevronDown } from 'lucide-react'\nimport AnimateHeightFragment from './AnimateHeightFragment'\nimport Freeze from './Freeze'\n\ntype TabValue = 'preview' | 'code'\n\nexport type DemoProps = {\n\tchildren: React.ReactNode\n\tclassName?: string\n\trootClassName?: string\n\tdefaultValue?: TabValue\n\tcode?: React.ReactNode\n\tminHeight?: string\n\ttitle?: React.ReactNode\n}\n\ntype Props = DemoProps & {\n\tonClick?: () => void\n\tonIntersect?: (entry: IntersectionObserverEntry) => void\n\tref?: React.Ref<HTMLDivElement>\n}\n\nexport default function Demo({\n\tchildren,\n\trootClassName,\n\tclassName,\n\tdefaultValue = 'preview',\n\tcode,\n\ttitle,\n\tonClick,\n\tonIntersect,\n\tminHeight = 'min-h-[20rem]',\n\tref\n}: Props) {\n\t// const [knowsToClick, setKnowsToClick] = useAtom(knowsToClickAtom)\n\tconst [knowsToClick, setKnowsToClick] = React.useState(false)\n\tconst [active, setActive] = React.useState(defaultValue)\n\n\tconst id = useId()\n\n\tfunction handleClick() {\n\t\tsetKnowsToClick(true)\n\t\tonClick?.()\n\t}\n\tfunction handleMouseDown(event: React.MouseEvent<HTMLElement>) {\n\t\t// Prevent selection of text:\n\t\t// https://stackoverflow.com/a/43321596\n\t\tif (event.detail > 1) {\n\t\t\tevent.preventDefault()\n\t\t}\n\t}\n\n\tconst demoRef = React.useRef<HTMLDivElement>(null)\n\tReact.useEffect(() => {\n\t\tif (!onIntersect || !demoRef.current) return\n\t\treturn inView(demoRef.current, (e) => {\n\t\t\tonIntersect(e)\n\t\t\treturn onIntersect\n\t\t})\n\t}, [onIntersect])\n\n\treturn (\n\t\t<AnimateHeightFragment dependencies={[active]}>\n\t\t\t<Tabs\n\t\t\t\tref={ref}\n\t\t\t\tclassName={clsx(\n\t\t\t\t\tactive === 'code' && 'dark',\n\t\t\t\t\trootClassName,\n\t\t\t\t\t'Demo text-primary not-prose border-faint relative isolate overflow-clip rounded-lg border',\n\t\t\t\t\tactive === 'code' && 'bg-zinc-950 dark:bg-zinc-900'\n\t\t\t\t)} // reset text color if inside prose\n\t\t\t\tselectedKey={active}\n\t\t\t\tonSelectionChange={(key) => setActive(key as TabValue)}\n\t\t\t>\n\t\t\t\t{code && (\n\t\t\t\t\t<MotionConfig transition={{ layout: { type: 'spring', duration: 0.25, bounce: 0 } }}>\n\t\t\t\t\t\t<TabList className=\"bg-zinc-150/90 absolute right-3 top-3 z-10 flex gap-1 rounded-full p-1 backdrop-blur-lg dark:bg-black/60\">\n\t\t\t\t\t\t\t<Tab\n\t\t\t\t\t\t\t\tid=\"preview\"\n\t\t\t\t\t\t\t\tclassName=\"dark:text-muted data-[hovered]:text-primary aria-selected:text-primary relative cursor-default rounded-full px-2 py-1 text-xs/4 font-medium text-zinc-600\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{active === 'preview' && (\n\t\t\t\t\t\t\t\t\t<motion.div\n\t\t\t\t\t\t\t\t\t\tclassName=\"prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25\"\n\t\t\t\t\t\t\t\t\t\tstyle={{ borderRadius: 999 }}\n\t\t\t\t\t\t\t\t\t\tlayout\n\t\t\t\t\t\t\t\t\t\tlayoutId={`${id}active`}\n\t\t\t\t\t\t\t\t\t></motion.div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tPreview\n\t\t\t\t\t\t\t</Tab>\n\t\t\t\t\t\t\t<Tab\n\t\t\t\t\t\t\t\tid=\"code\"\n\t\t\t\t\t\t\t\tclassName=\"dark:text-muted data-[hovered]:text-primary aria-selected:text-primary relative cursor-default rounded-full px-2 py-1 text-xs/4 font-medium text-zinc-600\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{active === 'code' && (\n\t\t\t\t\t\t\t\t\t<motion.div\n\t\t\t\t\t\t\t\t\t\tclassName=\"prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25\"\n\t\t\t\t\t\t\t\t\t\tstyle={{ borderRadius: 999 }}\n\t\t\t\t\t\t\t\t\t\tlayout\n\t\t\t\t\t\t\t\t\t\tlayoutId={`${id}active`}\n\t\t\t\t\t\t\t\t\t></motion.div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t</Tab>\n\t\t\t\t\t\t</TabList>\n\t\t\t\t\t</MotionConfig>\n\t\t\t\t)}\n\t\t\t\t<TabPanel\n\t\t\t\t\tid=\"preview\"\n\t\t\t\t\tshouldForceMount\n\t\t\t\t\tclassName={clsx(className, 'relative data-[inert]:hidden')}\n\t\t\t\t>\n\t\t\t\t\t{title && <div className=\"absolute left-3 top-3\">{title}</div>}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t\tminHeight,\n\t\t\t\t\t\t\t'flex flex-col items-center justify-center p-5 pb-6 [&_button]:cursor-pointer'\n\t\t\t\t\t\t)}\n\t\t\t\t\t\tref={demoRef}\n\t\t\t\t\t\tonClick={onClick && handleClick}\n\t\t\t\t\t\tonMouseDown={onClick && handleMouseDown}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t\t{onClick && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t\t\t\t'text-muted absolute bottom-5 left-0 w-full text-center text-sm transition-opacity duration-200 ease-out',\n\t\t\t\t\t\t\t\t\tknowsToClick && 'opacity-0'\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span className=\"can-hover:inline hidden\">Click</span>\n\t\t\t\t\t\t\t\t<span className=\"can-hover:hidden\">Tap</span> anywhere to change numbers\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</TabPanel>\n\t\t\t\t{code && (\n\t\t\t\t\t// Pad the right side of the first line to make room for tabs:\n\t\t\t\t\t<TabPanel\n\t\t\t\t\t\tclassName=\"[&_.line:first-child]:pr-[9.75rem] [&_pre]:!rounded-none [&_pre]:!border-none\"\n\t\t\t\t\t\tid=\"code\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{code}\n\t\t\t\t\t</TabPanel>\n\t\t\t\t)}\n\t\t\t</Tabs>\n\t\t</AnimateHeightFragment>\n\t)\n}\n\nexport function DemoTitle({\n\tclassName,\n\tchildren,\n\t...rest\n}: React.ComponentPropsWithoutRef<'span'> & { children: string }) {\n\treturn (\n\t\t<span {...rest} className={clsx(className, 'px-2 py-1.5 text-sm')}>\n\t\t\t{children}\n\t\t</span>\n\t)\n}\n\nexport function DemoMenu({ children }: { children: React.ReactNode }) {\n\treturn <MenuTrigger>{children}</MenuTrigger>\n}\n\nexport function DemoMenuButton({\n\tchildren,\n\tclassName\n}: {\n\tchildren: React.ReactNode\n\tclassName?: string\n}) {\n\treturn (\n\t\t<Button\n\t\t\tclassName={clsx(\n\t\t\t\tclassName,\n\t\t\t\t'btn-secondary group flex h-8 items-center rounded-md px-2 text-xs transition duration-[.16s] ease-[cubic-bezier(.4,0,.2,1)]'\n\t\t\t)}\n\t\t>\n\t\t\t{children}\n\t\t\t<ChevronDown\n\t\t\t\tclassName=\"spring-bounce-0 spring-duration-150 ml-0.5 size-3.5 shrink-0 group-aria-expanded:rotate-180\"\n\t\t\t\tstrokeWidth={2}\n\t\t\t/>\n\t\t</Button>\n\t)\n}\n\nexport function DemoMenuItems({\n\tclassName,\n\tchildren\n}: {\n\tclassName?: string\n\tchildren: React.ReactNode\n}) {\n\treturn (\n\t\t<Popover\n\t\t\tplacement=\"bottom start\"\n\t\t\toffset={6}\n\t\t\tclassName={clsx(\n\t\t\t\tclassName,\n\t\t\t\t'min-w-[--trigger-width] origin-top-left rounded-lg bg-white p-1.5 shadow-sm ring ring-inset ring-black/[8%] transition-[opacity,transform] duration-[.12s] ease-out data-[entering]:scale-[.96] data-[entering]:opacity-0 dark:bg-zinc-950 dark:shadow-none dark:ring-white/10'\n\t\t\t)}\n\t\t>\n\t\t\t{({ isExiting }) => (\n\t\t\t\t<Freeze frozen={isExiting}>\n\t\t\t\t\t<Menu>{children}</Menu>\n\t\t\t\t</Freeze>\n\t\t\t)}\n\t\t</Popover>\n\t)\n}\n\nexport function DemoMenuItem({\n\tclassName,\n\tchildren,\n\tisDisabled,\n\tonAction,\n\ttextValue\n}: {\n\tclassName?: string\n\tchildren: React.ReactNode\n\tisDisabled?: boolean\n\tonAction?: () => void\n\ttextValue?: string\n}) {\n\treturn (\n\t\t<MenuItem\n\t\t\tisDisabled={isDisabled}\n\t\t\tonAction={onAction}\n\t\t\ttextValue={textValue}\n\t\t\tclassName={clsx(\n\t\t\t\tclassName,\n\t\t\t\tisDisabled ? 'pr-2' : 'pr-4',\n\t\t\t\t'dark:data-[focused]:bg-white/12.5 flex w-full items-center gap-2 rounded-lg py-2 pl-2 text-xs font-medium data-[disabled]:cursor-default data-[focused]:bg-black/5'\n\t\t\t)}\n\t\t>\n\t\t\t{children}\n\t\t\t{isDisabled && <Check className=\"ml-auto h-4 w-4\" />}\n\t\t</MenuItem>\n\t)\n}\n\nexport function DemoSwitch({\n\tclassName,\n\tchildren,\n\tisSelected,\n\tonChange\n}: {\n\tclassName?: string\n\tchildren: React.ReactNode\n\tisSelected?: boolean\n\tonChange?: (isSelected: boolean) => void\n}) {\n\treturn (\n\t\t<Switch\n\t\t\tisSelected={isSelected}\n\t\t\tonChange={onChange}\n\t\t\tclassName={clsx(\n\t\t\t\tclassName,\n\t\t\t\t'group flex items-center gap-2 p-1 data-[focus-visible]:outline-none'\n\t\t\t)}\n\t\t>\n\t\t\t<div className=\"dark:group-data-[hovered]:bg-zinc-750 p-0.75 relative flex h-6 w-10 shrink-0 rounded-full bg-zinc-200 transition-colors duration-200 ease-in-out group-data-[hovered]:bg-zinc-300 group-data-[selected]:bg-zinc-950 group-data-[selected]:group-data-[hovered]:bg-zinc-700 group-data-[focus-visible]:outline dark:bg-zinc-800 dark:group-data-[selected]:bg-zinc-50 dark:group-data-[selected]:group-data-[hovered]:bg-zinc-300\">\n\t\t\t\t<span\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\tclassName=\"spring-bounce-0 spring-duration-200 size-4.5 pointer-events-none inline-block rounded-full bg-white shadow-lg ring-0 transition-transform group-data-[selected]:translate-x-4 dark:bg-zinc-950\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<span className=\"text-xs\">{children}</span>\n\t\t</Switch>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/FrameworkMenu.tsx",
    "content": "import { MenuTrigger, Button, Popover, Menu, MenuItem } from 'react-aria-components'\nimport { FRAMEWORKS, toFrameworkPath, type Framework } from '@/lib/framework'\nimport { ChevronDown } from 'lucide-react'\nimport * as React from 'react'\nimport { Check } from 'lucide-react'\nimport clsx from 'clsx/lite'\nimport Freeze from './Freeze'\n\nconst icons = import.meta.glob<React.FC<React.HTMLAttributes<SVGElement>>>(\n\t'./icons/frameworks/*.tsx',\n\t{\n\t\timport: 'default',\n\t\teager: true\n\t}\n)\n\nexport default function FrameworkMenu({\n\tvalue,\n\turl,\n\tclassName\n}: {\n\tvalue: Framework\n\turl: URL\n\tclassName?: string\n}) {\n\tconst Icon = icons[`./icons/frameworks/${value}.tsx`]!\n\n\treturn (\n\t\t<MenuTrigger>\n\t\t\t<Button\n\t\t\t\tclassName={clsx(\n\t\t\t\t\tclassName,\n\t\t\t\t\t'text-primary btn-secondary group -mx-1 -my-1 -mr-3.5 inline-flex items-baseline rounded-lg px-2 py-1 pr-1.5 font-medium transition duration-[.16s] ease-out'\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<Icon className=\"size-4.5 relative me-1.5 shrink-0 self-center\" />\n\t\t\t\t{FRAMEWORKS[value].name}\n\t\t\t\t<ChevronDown\n\t\t\t\t\tclassName=\"spring-bounce-0 spring-duration-150 ml-1 size-4 shrink-0 self-center group-aria-expanded:rotate-180\"\n\t\t\t\t\tstrokeWidth={2}\n\t\t\t\t/>\n\t\t\t</Button>\n\t\t\t<Popover\n\t\t\t\tplacement=\"bottom start\"\n\t\t\t\toffset={6}\n\t\t\t\tcrossOffset={-6}\n\t\t\t\tclassName=\"min-w-32 origin-top-left rounded-xl bg-white p-1.5 shadow-sm ring ring-black/[8%] transition-[opacity,transform] duration-[.12s] ease-out data-[entering]:scale-[.96] data-[entering]:opacity-0 dark:bg-zinc-950 dark:ring-inset dark:ring-white/10\"\n\t\t\t>\n\t\t\t\t{({ isExiting }) => (\n\t\t\t\t\t<Freeze frozen={isExiting}>\n\t\t\t\t\t\t<Menu>\n\t\t\t\t\t\t\t{Object.entries(FRAMEWORKS).map(([id, framework]) => {\n\t\t\t\t\t\t\t\tconst Icon = icons[`./icons/frameworks/${id}.tsx`]!\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\t\t\t\tid={id}\n\t\t\t\t\t\t\t\t\t\ttextValue={framework.name}\n\t\t\t\t\t\t\t\t\t\tisDisabled={id === value}\n\t\t\t\t\t\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t\t\t\t\t\tid === value ? 'pr-2' : 'pr-4',\n\t\t\t\t\t\t\t\t\t\t\t'dark:data-[focused]:bg-white/12.5 text-primary flex items-center gap-1.5 rounded-lg py-2 pl-2 text-sm font-medium data-[disabled]:cursor-default data-[focused]:bg-black/[8%]'\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\thref={toFrameworkPath(url.pathname, id as Framework)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon className=\"size-4.5\" />\n\t\t\t\t\t\t\t\t\t\t{framework.name}\n\t\t\t\t\t\t\t\t\t\t{id === value && <Check className=\"ml-auto h-4 w-4\" strokeWidth={2.5} />}\n\t\t\t\t\t\t\t\t\t</MenuItem>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</Menu>\n\t\t\t\t\t</Freeze>\n\t\t\t\t)}\n\t\t\t</Popover>\n\t\t</MenuTrigger>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/Freeze.tsx",
    "content": "import * as React from 'react'\n\ntype FreezeProps = {\n\tfrozen: boolean\n\tchildren: React.ReactNode\n}\n\ntype FragmentObserver = {\n\tobserve(element: HTMLElement): void\n\tunobserve(element: HTMLElement): void\n\tdisconnect(): void\n}\n\ntype ObservableFragmentInstance = React.FragmentInstance & {\n\tobserveUsing(observer: FragmentObserver): void\n\tunobserveUsing(observer: FragmentObserver): void\n}\n\nclass ElementsObserver implements FragmentObserver {\n\tconstructor(private readonly elementsRef: React.RefObject<Set<HTMLElement>>) {}\n\n\tobserve(element: HTMLElement) {\n\t\tthis.elementsRef.current.add(element)\n\t}\n\n\tunobserve(element: HTMLElement) {\n\t\tthis.elementsRef.current.delete(element)\n\t}\n\n\tdisconnect() {\n\t\tthis.elementsRef.current.clear()\n\t}\n}\n\nconst infinitePromise = new Promise<never>(() => {})\n\nfunction Suspend() {\n\tReact.use(infinitePromise)\n\treturn null\n}\n\nexport default function Freeze({ frozen, children }: FreezeProps) {\n\tconst elementsRef = React.useRef(new Set<HTMLElement>())\n\n\tconst fragmentRef = React.useCallback((fragment: React.FragmentInstance | null) => {\n\t\tif (!fragment) return\n\n\t\tconst observer = new ElementsObserver(elementsRef)\n\t\tconst observableFragment = fragment as ObservableFragmentInstance\n\t\tobservableFragment.observeUsing(observer)\n\n\t\treturn () => {\n\t\t\tobservableFragment.unobserveUsing(observer)\n\t\t\tobserver.disconnect()\n\t\t}\n\t}, [])\n\n\t// Undo Suspense's `display: none` so the exiting subtree stays visible while frozen.\n\tReact.useInsertionEffect(() => {\n\t\tif (!frozen) return\n\t\telementsRef.current.forEach((element) => {\n\t\t\telement.style.display = ''\n\t\t})\n\t}, [frozen])\n\n\treturn (\n\t\t<React.Fragment ref={fragmentRef}>\n\t\t\t<React.Suspense>\n\t\t\t\t{frozen && <Suspend />}\n\t\t\t\t{children}\n\t\t\t</React.Suspense>\n\t\t</React.Fragment>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/GroupComp.mdx",
    "content": "import Match from './Match.astro'\n\n<Match><Fragment slot=\"vanilla\">`<number-flow-group>`</Fragment>`<NumberFlowGroup>`</Match>"
  },
  {
    "path": "site/src/components/Heading.astro",
    "content": "---\nimport { getTOCContext } from '@/context/toc.ts'\nimport type { HTMLAttributes } from 'astro/types'\nimport { slug } from 'github-slugger'\n\ntype Props = HTMLAttributes<'h2'> & {\n\tvalue: string\n}\n\nlet { value, ...props } = Astro.props\n\nconst context = getTOCContext()\n\nconst id = slug(value)\n\ncontext.headings.push({ title: value, id })\n---\n\n<h2 {...props} {id}>\n\t{Astro.slots.default ? <slot /> : value}\n</h2>\n"
  },
  {
    "path": "site/src/components/Link.astro",
    "content": "---\nimport ReactLink from './Link'\nexport type { Props } from './Link'\n---\n\n<ReactLink {...Astro.props} client:load><slot /></ReactLink>\n"
  },
  {
    "path": "site/src/components/Link.tsx",
    "content": "import * as React from 'react'\nimport { useStore } from '@nanostores/react'\nimport { $url, $pageFramework } from '@/stores/url'\nimport type { AnchorHTMLAttributes } from 'react'\nimport { isActive } from '../lib/url'\nimport { type Framework, toFrameworkPath } from '@/lib/framework'\nimport { ArrowUpRight } from 'lucide-react'\nimport clsx from 'clsx/lite'\n\nexport type Props = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {\n\tframeworked?: boolean\n\tactive?: React.ReactNode\n\tchildren?: React.ReactNode | ((renderProps: { isActive: boolean }) => React.ReactNode)\n}\n\nexport default function Link({\n\thref: _href,\n\tclassName,\n\tchildren,\n\ttarget,\n\tframeworked = true,\n\tactive: activeChildren,\n\t...props\n}: Props) {\n\tconst pageFramework = useStore($pageFramework)\n\tconst ref = React.useRef<HTMLAnchorElement>(null)\n\tconst [savedFramework, setSavedFramework] = React.useState<Framework | null>(null)\n\tReact.useEffect(() => {\n\t\tif (!pageFramework) setSavedFramework(localStorage.getItem('framework') as Framework)\n\t}, [pageFramework])\n\tconst framework = React.useMemo(\n\t\t() => pageFramework ?? savedFramework,\n\t\t[pageFramework, savedFramework]\n\t)\n\tconst url = useStore($url)\n\n\tconst isExternal = _href && url && new URL(_href, url.origin).origin !== url.origin\n\tconst href = !isExternal && frameworked && framework ? toFrameworkPath(_href, framework) : _href\n\tReact.useEffect(() => {\n\t\t// Double-set for a weird Astro VT bug I think:\n\t\tif (href) ref.current?.setAttribute('href', href)\n\t}, [href])\n\n\tconst active = isActive(href, url)\n\n\treturn (\n\t\t<a\n\t\t\t{...props}\n\t\t\tref={ref}\n\t\t\tclassName={clsx(className, 'group/link')}\n\t\t\ttarget={isExternal ? '_blank' : target}\n\t\t\tdata-active={active ? '' : undefined}\n\t\t\thref={href}\n\t\t\tdata-framework={framework}\n\t\t>\n\t\t\t{typeof children === 'function' ? children({ isActive: active }) : children}\n\t\t\t{isExternal && (\n\t\t\t\t<span className=\"whitespace-nowrap\">\n\t\t\t\t\t&#8288;\n\t\t\t\t\t<ArrowUpRight className=\"group-hover/link:text-primary text-muted ml-[.125em] inline-block size-[1em] align-[-0.2em] no-underline transition duration-[inherit] ease-[inherit] group-hover/link:-translate-y-px group-hover/link:translate-x-px\" />\n\t\t\t\t</span>\n\t\t\t)}\n\t\t</a>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/LogoWall/LogoWall.astro",
    "content": "---\nimport Wall from './Wall.astro'\nimport type { HTMLAttributes } from 'astro/types'\n\ntype Props = HTMLAttributes<'div'>\n\nconst { class: cls, ...props } = Astro.props\n---\n\n<div {...props} class:list={[cls /*'container max-w-2xl'*/]}>\n\t<!-- <h2 class=\"text-muted whitespace-nowrap text-left text-sm\">Used by</h2> -->\n\t<div\n\t\tclass=\"flex items-center\"\n\t\tstyle=\"-webkit-mask-image: linear-gradient(to right, transparent 0, rgba(0,0,0,0.2) 1rem, black 2rem, black calc(100% - 2rem), rgba(0,0,0,0) calc(100% - 1rem), transparent calc(100% - 1rem))\"\n\t>\n\t\t<Wall />\n\t\t<Wall />\n\t</div>\n</div>\n"
  },
  {
    "path": "site/src/components/LogoWall/Wall.astro",
    "content": "---\nimport Intercom from '@/assets/images/logos/intercom.svg'\nimport Polymarket from '@/assets/images/logos/polymarket.svg'\nimport Linear from '@/assets/images/logos/linear.svg'\nimport X from '@/assets/images/logos/x.svg'\nimport SupabaseDark from '@/assets/images/logos/supabase-dark.svg'\nimport SupabaseLight from '@/assets/images/logos/supabase-light.svg'\nimport Whop from '@/assets/images/logos/whop.svg'\nimport Basedash from '@/assets/images/logos/basedash.svg'\nimport Layers from '@/assets/images/logos/layers.svg'\nimport Clerk from '@/assets/images/logos/clerk.svg'\nimport Resend from '@/assets/images/logos/resend.svg'\nimport Dub from '@/assets/images/logos/dub.svg'\nimport Midday from '@/assets/images/logos/midday.svg'\nimport OpenSea from '@/assets/images/logos/opensea.svg'\nimport Aave from '@/assets/images/logos/aave.svg'\nimport GhostLight from '@/assets/images/logos/ghost-light.png'\nimport GhostDark from '@/assets/images/logos/ghost-dark.png'\nimport { Image } from 'astro:assets'\n---\n\n<div class=\"animate-logo-wall ~gap-12/14 ~pr-12/14 flex items-center\">\n\t<Intercom class=\"h-6\" role=\"img\" aria-label=\"Intercom logo\" />\n\t<X class=\"h-6\" role=\"img\" aria-label=\"Intercom logo\" />\n\t<Polymarket class=\"h-5\" role=\"img\" aria-label=\"Polymarket logo\" />\n\t<OpenSea class=\"h-6\" role=\"img\" aria-label=\"OpenSea logo\" />\n\t<Aave class=\"h-4\" role=\"img\" aria-label=\"Aave logo\" />\n\t<SupabaseLight role=\"img\" class=\"h-6 grayscale-[1] dark:hidden\" aria-label=\"Supabase logo\" />\n\t<SupabaseDark role=\"img\" class=\"hidden h-6 grayscale-[1] dark:block\" aria-label=\"Supabase logo\" />\n\t<Image src={GhostLight} class=\"h-7 w-auto dark:hidden\" alt=\"Ghost logo\" />\n\t<Image src={GhostDark} class=\"hidden h-7 w-auto dark:block\" alt=\"Ghost logo\" />\n\t<Linear class=\"h-6\" role=\"img\" aria-label=\"Linear logo\" />\n\t<Whop class=\"h-6\" role=\"img\" aria-label=\"Whop logo\" />\n\t<Layers class=\"h-7\" role=\"img\" aria-label=\"Layers.to logo\" />\n\t<Clerk class=\"h-6\" role=\"img\" aria-label=\"Clerk logo\" />\n\t<Resend class=\"h-5\" role=\"img\" aria-label=\"Resend logo\" />\n\t<Dub class=\"h-6\" role=\"img\" aria-label=\"Dub logo\" />\n\t<Midday class=\"h-6\" role=\"img\" aria-label=\"Midday logo\" />\n\t<Basedash class=\"h-6\" role=\"img\" aria-label=\"Basedash logo\" />\n</div>\n"
  },
  {
    "path": "site/src/components/Match.astro",
    "content": "---\nimport { getFramework, type Framework } from '@/lib/framework'\nimport type { HTMLTag, Polymorphic } from 'astro/types'\n\ntype Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> &\n\t(\n\t\t| ({\n\t\t\t\t[framework in Framework]?: string\n\t\t  } & { default?: string })\n\t\t| {\n\t\t\t\t[framework in Framework]?: true\n\t\t  }\n\t)\n\n// We can't use a loop here because Astro needs the slot names to be static strings\nconst { as: _Tag, react, vue, svelte, default: def, vanilla, ...props } = Astro.props\nlet Tag = _Tag ?? (Object.keys(props).length ? 'div' : Fragment)\nconst framework = getFramework(Astro.params)\nconst noBool =\n\ttypeof react !== 'boolean' &&\n\ttypeof vue !== 'boolean' &&\n\ttypeof svelte !== 'boolean' &&\n\ttypeof vanilla !== 'boolean'\n---\n\n<Tag {...props}\n\t>{\n\t\tframework === 'react' &&\n\t\t\t(typeof react == 'string' ? (\n\t\t\t\treact\n\t\t\t) : Astro.slots.has('react') ? (\n\t\t\t\t<slot name=\"react\" />\n\t\t\t) : (\n\t\t\t\t(def ?? ((react || noBool) && <slot />))\n\t\t\t))\n\t}{\n\t\tframework === 'vue' &&\n\t\t\t(typeof vue == 'string' ? (\n\t\t\t\tvue\n\t\t\t) : Astro.slots.has('vue') ? (\n\t\t\t\t<slot name=\"vue\" />\n\t\t\t) : (\n\t\t\t\t(def ?? ((vue || noBool) && <slot />))\n\t\t\t))\n\t}{\n\t\tframework === 'svelte' &&\n\t\t\t(typeof svelte == 'string' ? (\n\t\t\t\tsvelte\n\t\t\t) : Astro.slots.has('svelte') ? (\n\t\t\t\t<slot name=\"svelte\" />\n\t\t\t) : (\n\t\t\t\t(def ?? ((svelte || noBool) && <slot />))\n\t\t\t))\n\t}{\n\t\tframework === 'vanilla' &&\n\t\t\t(typeof vanilla == 'string' ? (\n\t\t\t\tvanilla\n\t\t\t) : Astro.slots.has('vanilla') ? (\n\t\t\t\t<slot name=\"vanilla\" />\n\t\t\t) : (\n\t\t\t\t(def ?? ((vanilla || noBool) && <slot />))\n\t\t\t))\n\t}</Tag\n>\n"
  },
  {
    "path": "site/src/components/Meta.astro",
    "content": "<span class=\"caption text-muted mt-[.375rem] block text-xs font-normal\"><slot /></span>\n"
  },
  {
    "path": "site/src/components/Nav.astro",
    "content": "---\nimport { GITHUB_TOKEN } from 'astro:env/server'\nimport ReactNav from './Nav'\nimport pkg from '/../packages/number-flow/package.json'\n\nconst repoPath = 'barvian/number-flow'\nconst repo = await fetch(`https://api.github.com/repos/${repoPath}`, {\n\theaders: {\n\t\tAuthorization: `Bearer ${GITHUB_TOKEN}`,\n\t\t'X-GitHub-Api-Version': '2022-11-28'\n\t}\n}).then((r) =>\n\tr.ok ? r.json() : Promise.reject(`Could not fetch repo ${repoPath}: ${r.statusText}`)\n)\nconst stargazers: number = repo.stargazers_count\nconst formattedStars = stargazers.toLocaleString('en-US', {\n\tnotation: 'compact',\n\tcompactDisplay: 'short'\n\t// GitHub rounds these (Twitter truncates)\n\t// roundingMode: 'trunc'\n})\n---\n\n<ReactNav\n\tclient:load\n\tstargazers={formattedStars}\n\trepo={pkg.repository.url}\n\ttransition:persist=\"nav\"\n/>\n\n<style is:global>\n\t::view-transition-old(nav) {\n\t\t/* This is a persistent element, so let the new always show on top. See https://developer.chrome.com/docs/web-platform/view-transitions#making_the_most_of_content_you_already_have */\n\t\tanimation: none;\n\t\topacity: 0;\n\t}\n\n\t::view-transition-new(nav) {\n\t\tanimation: none;\n\t}\n</style>\n"
  },
  {
    "path": "site/src/components/Nav.tsx",
    "content": "import Link from '@/components/Link'\nimport { BookOpen, Shapes, GalleryVerticalEnd } from 'lucide-react'\nimport { AnimatePresence, motion, MotionConfig } from 'motion/react'\nimport * as React from 'react'\nimport clsx from 'clsx/lite'\n\ntype Props = React.ComponentPropsWithoutRef<'nav'> & {\n\tstargazers: number | string\n\trepo?: string\n}\n\n// const maskWidth = '2.5rem'\n\nexport default function Nav({ stargazers, className, repo, ...props }: Props) {\n\t// Fix scroll positions after view transitions:\n\tconst scrollableRef = React.useRef<HTMLDivElement>(null)\n\tReact.useEffect(() => {\n\t\tconst beforeSwap = () => {\n\t\t\tconst scrollLeft = scrollableRef.current?.scrollLeft\n\t\t\tdocument.addEventListener(\n\t\t\t\t'astro:after-swap',\n\t\t\t\t() => {\n\t\t\t\t\tscrollableRef.current?.scrollTo({ left: scrollLeft, behavior: 'instant' })\n\t\t\t\t},\n\t\t\t\t{ once: true }\n\t\t\t)\n\t\t}\n\t\tdocument.addEventListener('astro:before-swap', beforeSwap)\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('astro:before-swap', beforeSwap)\n\t\t}\n\t}, [])\n\n\t// Overflow mask. Disable for now b/c of Chrome bug with mask-image and VTs:\n\t// const leftOverflow = useMotionValue(0)\n\t// const leftOverflowPx = useMotionTemplate`${leftOverflow}px`\n\t// const rightOverflow = useMotionValue(0)\n\t// const rightOverflowPx = useMotionTemplate`${rightOverflow}px`\n\t// React.useEffect(() => {\n\t// \tconst onScroll = () => {\n\t// \t\tleftOverflow.set(scrollableRef.current!.scrollLeft)\n\t// \t\trightOverflow.set(\n\t// \t\t\tscrollableRef.current!.scrollWidth -\n\t// \t\t\t\tscrollableRef.current!.clientWidth -\n\t// \t\t\t\tscrollableRef.current!.scrollLeft\n\t// \t\t)\n\t// \t}\n\t// \tonScroll()\n\t// \tscrollableRef.current?.addEventListener('scroll', onScroll)\n\t// \treturn () => {\n\t// \t\tscrollableRef.current?.removeEventListener('scroll', onScroll)\n\t// \t}\n\t// }, [])\n\n\treturn (\n\t\t<MotionConfig transition={{ layout: { duration: 0.35, type: 'spring', bounce: 0 } }}>\n\t\t\t<nav\n\t\t\t\t{...props}\n\t\t\t\tclassName={clsx(\n\t\t\t\t\tclassName,\n\t\t\t\t\t'~pb-6/10 pointer-events-none fixed bottom-0 left-0 z-10 w-full pt-12'\n\t\t\t\t)}\n\t\t\t\tid=\"nav\"\n\t\t\t>\n\t\t\t\t<div className=\"bg-mask-white dark:bg-mask-zinc-950 absolute bottom-0 left-0 h-[13rem] w-full\"></div>\n\t\t\t\t<div className=\"container flex justify-center\">\n\t\t\t\t\t{/* The backdrop blur broke with VTs when it was on the scrolling div: */}\n\t\t\t\t\t<div className=\"max-w-full rounded-[1.375rem] bg-zinc-100/90 ring ring-black/[5%] backdrop-blur-xl backdrop-saturate-[140%] dark:border dark:border-white/[8%] dark:bg-zinc-950/90 dark:ring-0\">\n\t\t\t\t\t\t<motion.div\n\t\t\t\t\t\t\tref={scrollableRef}\n\t\t\t\t\t\t\tclassName=\"scrollbar-none vt/nav pointer-events-auto overflow-x-auto scroll-smooth rounded-[inherit] p-1.5\"\n\t\t\t\t\t\t\t// style={{\n\t\t\t\t\t\t\t// \tWebkitMaskImage: `linear-gradient(to right, transparent min(-${maskWidth} + var(--left-overflow), 0px), black min(var(--left-overflow), ${maskWidth}), black calc(100% - min(var(--right-overflow), ${maskWidth})), transparent calc(100% - min(-${maskWidth} + var(--right-overflow), 0px)))`,\n\t\t\t\t\t\t\t// \t'--left-overflow': leftOverflowPx,\n\t\t\t\t\t\t\t// \t'--right-overflow': rightOverflowPx\n\t\t\t\t\t\t\t// }}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"isolate grid grid-cols-[repeat(5,5.6875em)]\">\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\thref=\"/\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-muted hover:text-primary data-[active]:text-primary relative flex flex-col items-center gap-1.5 rounded-2xl px-4 pb-1.5 pt-2.5 text-xs font-medium outline-none transition-[color] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{({ isActive }) => (\n\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t<AnimatePresence initial={false}>\n\t\t\t\t\t\t\t\t\t\t\t\t{isActive && <NavLinkActive />}\n\t\t\t\t\t\t\t\t\t\t\t</AnimatePresence>\n\t\t\t\t\t\t\t\t\t\t\t<BookOpen className=\"size-6\" absoluteStrokeWidth />\n\t\t\t\t\t\t\t\t\t\t\tDocs\n\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\thref=\"/examples\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-muted hover:text-primary data-[active]:text-primary relative flex flex-col items-center gap-1.5 rounded-2xl px-4 pb-1.5 pt-2.5 text-xs font-medium outline-none transition-[color] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{({ isActive }) => (\n\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t<AnimatePresence initial={false}>\n\t\t\t\t\t\t\t\t\t\t\t\t{isActive && <NavLinkActive />}\n\t\t\t\t\t\t\t\t\t\t\t</AnimatePresence>\n\t\t\t\t\t\t\t\t\t\t\t<Shapes className=\"size-6\" absoluteStrokeWidth />\n\t\t\t\t\t\t\t\t\t\t\tExamples\n\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\thref=\"/showcase\"\n\t\t\t\t\t\t\t\t\tframeworked={false}\n\t\t\t\t\t\t\t\t\tclassName=\"text-muted hover:text-primary data-[active]:text-primary relative flex flex-col items-center gap-1.5 rounded-2xl px-4 pb-1.5 pt-2.5 text-xs font-medium outline-none transition-[color] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{({ isActive }) => (\n\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t<AnimatePresence initial={false}>\n\t\t\t\t\t\t\t\t\t\t\t\t{isActive && <NavLinkActive />}\n\t\t\t\t\t\t\t\t\t\t\t</AnimatePresence>\n\t\t\t\t\t\t\t\t\t\t\t<GalleryVerticalEnd className=\"size-6 -scale-y-100\" absoluteStrokeWidth />\n\t\t\t\t\t\t\t\t\t\t\tShowcase\n\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\tclassName=\"text-muted hover:text-primary data-[active]:text-primary flex flex-col items-center gap-1.5 rounded-2xl px-4 pb-1.5 pt-2.5 text-xs font-medium lowercase outline-none transition-[color] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\"\n\t\t\t\t\t\t\t\t\thref={repo}\n\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\taria-label=\"Star on GitHub\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 16 16\" className=\"size-6 fill-current\" role=\"none\">\n\t\t\t\t\t\t\t\t\t\t<path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z\"></path>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t{stargazers}\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\thref=\"https://x.com/mbarvian\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-muted hover:text-primary data-[active]:text-primary flex flex-col items-center gap-1.5 rounded-2xl px-4 pb-1.5 pt-2.5 text-xs font-medium outline-none transition-[color] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\"\n\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 24 24\" className=\"size-6 fill-current\">\n\t\t\t\t\t\t\t\t\t\t<path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\"></path>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\tFollow\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</motion.div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</nav>\n\t\t</MotionConfig>\n\t)\n}\n\nfunction NavLinkActive() {\n\treturn (\n\t\t<div className=\"dark:bg-white/12.5 absolute inset-0 -z-10 size-full rounded-[inherit] bg-white shadow-lg dark:shadow-none\" />\n\t)\n}\n"
  },
  {
    "path": "site/src/components/Note.astro",
    "content": "---\nimport { Info } from 'lucide-react'\nimport type { HTMLAttributes } from 'astro/types'\n\nexport type Props = HTMLAttributes<'div'>\n\nconst { class: cls, ...props } = Astro.props\n---\n\n<!-- <p class=\"bg-accent/10 border-accent/30 dark:border-accent/10 rounded-lg border p-5\"> -->\n<div\n\t{...props}\n\tclass:list={[\n\t\tcls,\n\t\t'border-faint py-4.5 flex gap-3 rounded-lg border bg-zinc-50 px-5 text-sm dark:bg-zinc-900'\n\t]}\n>\n\t<Info className=\"mt-1 size-4 shrink-0\" />\n\t<div class=\"min-w-0 flex-1\">\n\t\t<div class=\"prose-zinc prose-sm prose dark:prose-invert\">\n\t\t\t<slot />\n\t\t</div>\n\t</div>\n</div>\n"
  },
  {
    "path": "site/src/components/Pre.astro",
    "content": "---\nimport styles from './code.module.css'\nconst { class: _, style: __, ...props } = Astro.props\n---\n\n<pre {...props} class:list={[styles.code]}><slot /></pre>\n"
  },
  {
    "path": "site/src/components/Snapshotter.tsx",
    "content": "import React from 'react'\n\nexport type SnapshotterProps = {\n\tdependencies?: React.DependencyList\n\tonSnapshot: () => void\n}\n\nexport class Snapshotter extends React.Component<SnapshotterProps> {\n\toverride getSnapshotBeforeUpdate(prevProps: SnapshotterProps) {\n\t\t// If the dependencies have changed, run the snapshot function\n\t\tif (\n\t\t\t!this.props.dependencies ||\n\t\t\t!prevProps.dependencies ||\n\t\t\tthis.props.dependencies.length !== prevProps.dependencies.length ||\n\t\t\tthis.props.dependencies.some((dep, d) => !Object.is(dep, prevProps.dependencies![d]))\n\t\t) {\n\t\t\tthis.props.onSnapshot()\n\t\t}\n\t\t// Need to return null to satisfy React\n\t\treturn null\n\t}\n\toverride componentDidUpdate() {\n\t\t// we don't use this but React will complain if we don't implement it\n\t}\n\toverride render() {\n\t\treturn null\n\t}\n}\n"
  },
  {
    "path": "site/src/components/Supported.tsx",
    "content": "import { useIsSupported, usePrefersReducedMotion } from '@number-flow/react'\nimport clsx from 'clsx/lite'\nimport Link from './Link'\n\nexport default function Supported() {\n\tconst isSupported = useIsSupported()\n\tconst reducedMotion = usePrefersReducedMotion()\n\tif (isSupported && !reducedMotion) return null\n\n\treturn (\n\t\t<>\n\t\t\t{/* The only way I could get the blend mode working was to separate the divs which requires fixed sizes */}\n\t\t\t<div\n\t\t\t\trole=\"presentation\"\n\t\t\t\tclassName={clsx(\n\t\t\t\t\treducedMotion ? 'h-11.5' : 'h-16.5',\n\t\t\t\t\t'~top-4/8 fixed left-1/2 z-40 -ml-36 w-72 rounded-lg mix-blend-multiply shadow-lg shadow-amber-500/10 dark:shadow-amber-950/20'\n\t\t\t\t)}\n\t\t\t/>\n\t\t\t<div\n\t\t\t\trole=\"alert\"\n\t\t\t\tclassName={clsx(\n\t\t\t\t\treducedMotion ? 'h-11.5' : 'h-16.5',\n\t\t\t\t\t'~top-4/8 prose prose-current fixed left-1/2 z-50 -ml-36 w-72 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-center text-sm text-amber-800 dark:border-amber-800/30 dark:bg-[#271202] dark:text-amber-50'\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{reducedMotion ? (\n\t\t\t\t\t<p>Reduced motion is on</p>\n\t\t\t\t) : (\n\t\t\t\t\t<p>\n\t\t\t\t\t\tYour browser doesn't{' '}\n\t\t\t\t\t\t<Link href=\"https://caniuse.com/mdn-css_types_mod\">\n\t\t\t\t\t\t\tsupport NumberFlow’s animations\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/TOC.astro",
    "content": "---\nimport TOCReact from './TOC'\nimport { getTOCContext } from '@/context/toc.ts'\n\nexport type { Props } from './TOC'\n\nconst context = getTOCContext()\n---\n\n<TOCReact client:load {...Astro.props} headings={context.headings} />\n"
  },
  {
    "path": "site/src/components/TOC.tsx",
    "content": "import type { Heading } from '@/context/toc'\nimport * as React from 'react'\n\nexport type Props = React.ComponentPropsWithoutRef<'nav'> & {\n\theadings: Heading[]\n}\n\nconst tops = new WeakMap<Heading, number>()\n\nfunction getTop(id: Heading['id']) {\n\tlet el = document.getElementById(id)\n\treturn el ? el.getBoundingClientRect().top + window.scrollY : 0\n}\n\nexport default function TOC({ headings, ...props }: Props) {\n\tconst [active, setActive] = React.useState(headings[0]?.id)\n\n\tReact.useEffect(() => {\n\t\tlet scrollPt = 0\n\t\tfunction onResize() {\n\t\t\tfor (const heading of headings) {\n\t\t\t\ttops.set(heading, getTop(heading.id))\n\t\t\t}\n\t\t\tconst rootStyle = window.getComputedStyle(document.documentElement)\n\t\t\tscrollPt = parseFloat(rootStyle.getPropertyValue('scroll-padding-top').match(/[\\d.]+/)?.[0]!)\n\t\t}\n\t\twindow.addEventListener('resize', onResize)\n\t\tonResize()\n\n\t\tfunction onScroll() {\n\t\t\t// let fontSize = parseFloat(style.fontSize.match(/[\\d.]+/)?.[0] ?? 16)\n\t\t\t// scrollMt = scrollMt * fontSize\n\n\t\t\t// let sortedHeadings = headings.sort((a, b) => tops.get(a)! - tops.get(b)!)\n\t\t\tlet top = window.scrollY // + scrollMt + 1\n\t\t\tconst current = headings.findLast((h) => top >= tops.get(h)! - scrollPt - 1) ?? headings[0]\n\t\t\tsetActive(current!.id)\n\t\t}\n\n\t\twindow.addEventListener('scroll', onScroll, {\n\t\t\tcapture: true\n\t\t\t// passive: true\n\t\t})\n\t\tonScroll()\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener('resize', onResize)\n\t\t\twindow.removeEventListener('scroll', onScroll, {\n\t\t\t\tcapture: true\n\t\t\t})\n\t\t}\n\t}, [])\n\n\treturn (\n\t\t<nav {...props} id=\"toc\" aria-label=\"Table Of Contents\">\n\t\t\t<ol className=\"space-y-4 text-sm font-[425]\">\n\t\t\t\t{headings.map((h) => (\n\t\t\t\t\t<li key={h.id}>\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tdata-active={h.id === active || undefined}\n\t\t\t\t\t\t\tclassName=\"opacity-40 transition-opacity hover:opacity-70 data-[active]:opacity-100 dark:opacity-50\"\n\t\t\t\t\t\t\thref={`#${h.id}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{h.title}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ol>\n\t\t</nav>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/Tweet/Tweet.astro",
    "content": "---\nimport TweetContent from './TweetContent.astro'\n\ninterface Props {\n\tid: string\n\tautoplay?: boolean\n\tfetchOptions?: RequestInit\n\tonError?: ((error: any) => any) | undefined\n}\n---\n\n<TweetContent {...Astro.props} />\n"
  },
  {
    "path": "site/src/components/Tweet/TweetContent.astro",
    "content": "---\nimport { getTweet } from './api'\nimport EmbeddedTweet from './twitter-theme/EmbeddedTweet.astro'\ninterface Props {\n\tid: string\n\tautoplay?: boolean\n\tfetchOptions?: RequestInit\n\tonError?: ((error: any) => any) | undefined\n}\n\nconst { id, fetchOptions, autoplay } = Astro.props\nconst tweet = await getTweet(id, fetchOptions)\n---\n\n<EmbeddedTweet {autoplay} tweet={tweet} />\n"
  },
  {
    "path": "site/src/components/Tweet/api/get-oembed.ts",
    "content": "export async function getOEmbed(url: string): Promise<any> {\n\tconst res = await fetch(`https://publish.twitter.com/oembed?url=${url}`)\n\n\tif (res.ok) return res.json()\n\n\tthrow new Error(`Fetch for embedded tweet failed with code: ${res.status}`)\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/get-tweet.ts",
    "content": "import type { Tweet } from './types/index.js'\n\nconst SYNDICATION_URL = 'https://cdn.syndication.twimg.com'\n\nexport class TwitterApiError extends Error {\n\tstatus: number\n\tdata: any\n\n\tconstructor({ message, status, data }: { message: string; status: number; data: any }) {\n\t\tsuper(message)\n\t\tthis.name = 'TwitterApiError'\n\t\tthis.status = status\n\t\tthis.data = data\n\t}\n}\n\nconst TWEET_ID = /^[0-9]+$/\n\nfunction getToken(id: string) {\n\treturn ((Number(id) / 1e15) * Math.PI).toString(6 ** 2).replace(/(0+|\\.)/g, '')\n}\n\n/**\n * Fetches a tweet from the Twitter syndication API.\n */\nexport async function getTweet(id: string, fetchOptions?: RequestInit): Promise<Tweet> {\n\tif (id.length > 40 || !TWEET_ID.test(id)) {\n\t\tthrow new Error(`Invalid tweet id: ${id}`)\n\t}\n\n\tconst url = new URL(`${SYNDICATION_URL}/tweet-result`)\n\n\turl.searchParams.set('id', id)\n\turl.searchParams.set('lang', 'en')\n\turl.searchParams.set(\n\t\t'features',\n\t\t[\n\t\t\t'tfw_timeline_list:',\n\t\t\t'tfw_follower_count_sunset:true',\n\t\t\t'tfw_tweet_edit_backend:on',\n\t\t\t'tfw_refsrc_session:on',\n\t\t\t'tfw_fosnr_soft_interventions_enabled:on',\n\t\t\t'tfw_show_birdwatch_pivots_enabled:on',\n\t\t\t'tfw_show_business_verified_badge:on',\n\t\t\t'tfw_duplicate_scribes_to_settings:on',\n\t\t\t'tfw_use_profile_image_shape_enabled:on',\n\t\t\t'tfw_show_blue_verified_badge:on',\n\t\t\t'tfw_legacy_timeline_sunset:true',\n\t\t\t'tfw_show_gov_verified_badge:on',\n\t\t\t'tfw_show_business_affiliate_badge:on',\n\t\t\t'tfw_tweet_edit_frontend:on'\n\t\t].join(';')\n\t)\n\turl.searchParams.set('token', getToken(id))\n\n\tconst res = await fetch(url.toString(), fetchOptions)\n\tconst isJson = res.headers.get('content-type')?.includes('application/json')\n\tconst data = isJson ? await res.json() : undefined\n\n\tif (res.ok) return data\n\tif (res.status === 404) {\n\t\tthrow new TwitterApiError({\n\t\t\tmessage: typeof data?.error === 'string' ? data.error : `Tweet ${id} not found.`,\n\t\t\tstatus: res.status,\n\t\t\tdata\n\t\t})\n\t}\n\n\tthrow new TwitterApiError({\n\t\tmessage: typeof data?.error === 'string' ? data.error : 'Bad request.',\n\t\tstatus: res.status,\n\t\tdata\n\t})\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/index.ts",
    "content": "export * from './types/index.js'\nexport * from './get-tweet.js'\nexport * from './get-oembed.js'\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/edit.ts",
    "content": "export interface TweetEditControl {\n\tedit_tweet_ids: string[]\n\teditable_until_msecs: string\n\tis_edit_eligible: boolean\n\tedits_remaining: string\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/entities.ts",
    "content": "export type Indices = [number, number]\n\nexport interface HashtagEntity {\n\tindices: Indices\n\ttext: string\n}\n\nexport interface UserMentionEntity {\n\tid_str: string\n\tindices: Indices\n\tname: string\n\tscreen_name: string\n}\n\nexport interface MediaEntity {\n\tdisplay_url: string\n\texpanded_url: string\n\tindices: Indices\n\turl: string\n}\n\nexport interface UrlEntity {\n\tdisplay_url: string\n\texpanded_url: string\n\tindices: Indices\n\turl: string\n}\n\nexport interface SymbolEntity {\n\tindices: Indices\n\ttext: string\n}\n\nexport interface TweetEntities {\n\thashtags: HashtagEntity[]\n\turls: UrlEntity[]\n\tuser_mentions: UserMentionEntity[]\n\tsymbols: SymbolEntity[]\n\tmedia?: MediaEntity[]\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/index.ts",
    "content": "export * from './edit.js'\nexport * from './entities.js'\nexport * from './media.js'\nexport * from './photo.js'\nexport * from './tweet.js'\nexport * from './user.js'\nexport * from './video.js'\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/media.ts",
    "content": "import type { Indices } from './entities.js'\n\nexport type RGB = {\n\tred: number\n\tgreen: number\n\tblue: number\n}\n\nexport type Rect = {\n\tx: number\n\ty: number\n\tw: number\n\th: number\n}\n\nexport type Size = {\n\th: number\n\tw: number\n\tresize: string\n}\n\nexport interface VideoInfo {\n\taspect_ratio: [number, number]\n\tvariants: {\n\t\tbitrate?: number\n\t\tcontent_type: 'video/mp4' | 'application/x-mpegURL'\n\t\turl: string\n\t}[]\n}\n\ninterface MediaBase {\n\tdisplay_url: string\n\texpanded_url: string\n\text_media_availability: {\n\t\tstatus: string\n\t}\n\text_media_color: {\n\t\tpalette: {\n\t\t\tpercentage: number\n\t\t\trgb: RGB\n\t\t}[]\n\t}\n\tindices: Indices\n\tmedia_url_https: string\n\toriginal_info: {\n\t\theight: number\n\t\twidth: number\n\t\tfocus_rects: Rect[]\n\t}\n\tsizes: {\n\t\tlarge: Size\n\t\tmedium: Size\n\t\tsmall: Size\n\t\tthumb: Size\n\t}\n\turl: string\n}\n\nexport interface MediaPhoto extends MediaBase {\n\ttype: 'photo'\n\text_alt_text?: string\n}\n\nexport interface MediaAnimatedGif extends MediaBase {\n\ttype: 'animated_gif'\n\tvideo_info: VideoInfo\n}\n\nexport interface MediaVideo extends MediaBase {\n\ttype: 'video'\n\tvideo_info: VideoInfo\n}\n\nexport type MediaDetails = MediaPhoto | MediaAnimatedGif | MediaVideo\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/photo.ts",
    "content": "import type { Rect, RGB } from './media.js'\n\nexport interface TweetPhoto {\n\tbackgroundColor: RGB\n\tcropCandidates: Rect[]\n\texpandedUrl: string\n\turl: string\n\twidth: number\n\theight: number\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/tweet.ts",
    "content": "import type { TweetEditControl } from './edit.js'\nimport type { Indices, TweetEntities } from './entities.js'\nimport type { MediaDetails } from './media.js'\nimport type { TweetPhoto } from './photo.js'\nimport type { TweetUser } from './user.js'\nimport type { TweetVideo } from './video.js'\n\n/**\n * Base tweet information shared by a tweet, a parent tweet and a quoted tweet.\n */\nexport interface TweetBase {\n\t/**\n\t * Language code of the tweet. E.g \"en\", \"es\".\n\t */\n\tlang: string\n\t/**\n\t * Creation date of the tweet in the format ISO 8601.\n\t */\n\tcreated_at: string\n\t/**\n\t * Text range of the tweet text.\n\t */\n\tdisplay_text_range: Indices\n\t/**\n\t * All the entities that are part of the tweet. Like hashtags, mentions, urls, etc.\n\t */\n\tentities: TweetEntities\n\t/**\n\t * The unique identifier of the tweet.\n\t */\n\tid_str: string\n\t/**\n\t * The tweet text, including the raw text from the entities.\n\t */\n\ttext: string\n\t/**\n\t * Information about the user who posted the tweet.\n\t */\n\tuser: TweetUser\n\t/**\n\t * Edit information about the tweet.\n\t */\n\tedit_control: TweetEditControl\n\tisEdited: boolean\n\tisStaleEdit: boolean\n}\n\n/**\n * A tweet as returned by the the Twitter syndication API.\n */\nexport interface Tweet extends TweetBase {\n\t__typename: 'Tweet'\n\tfavorite_count: number\n\tmediaDetails?: MediaDetails[]\n\tphotos?: TweetPhoto[]\n\tvideo?: TweetVideo\n\tconversation_count: number\n\tnews_action_type: 'conversation'\n\tquoted_tweet?: QuotedTweet\n\tin_reply_to_screen_name?: string\n\tin_reply_to_status_id_str?: string\n\tin_reply_to_user_id_str?: string\n\tparent?: TweetParent\n\tpossibly_sensitive?: boolean\n}\n\n/**\n * The parent tweet of a tweet reply.\n */\nexport interface TweetParent extends TweetBase {\n\treply_count: number\n\tretweet_count: number\n\tfavorite_count: number\n}\n\n/**\n * A tweet quoted by another tweet.\n */\nexport interface QuotedTweet extends TweetBase {\n\treply_count: number\n\tretweet_count: number\n\tfavorite_count: number\n\tmediaDetails?: MediaDetails[]\n\tself_thread: {\n\t\tid_str: string\n\t}\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/user.ts",
    "content": "export interface TweetUser {\n\tid_str: string\n\tname: string\n\tprofile_image_url_https: string\n\tprofile_image_shape: 'Circle' | 'Square'\n\tscreen_name: string\n\tverified: boolean\n\tverified_type?: 'Business' | 'Government'\n\tis_blue_verified: boolean\n}\n"
  },
  {
    "path": "site/src/components/Tweet/api/types/video.ts",
    "content": "export interface TweetVideo {\n\taspectRatio: [number, number]\n\tcontentType: string\n\tdurationMs: number\n\tmediaAvailability: {\n\t\tstatus: string\n\t}\n\tposter: string\n\tvariants: {\n\t\ttype: string\n\t\tsrc: string\n\t}[]\n\tvideoId: {\n\t\ttype: string\n\t\tid: string\n\t}\n\tviewCount: number\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/AvatarImg.astro",
    "content": "---\ninterface Props {\n\tsrc: string\n\talt: string\n\twidth: number\n\theight: number\n}\n---\n\n<img {...Astro.props} />\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/EmbeddedTweet.astro",
    "content": "---\nimport type { Tweet } from '../api/index.js'\nimport TweetContainer from './TweetContainer.astro'\nimport TweetHeader from './TweetHeader.astro'\nimport TweetInReplyTo from './TweetInReplyTo.astro'\nimport TweetBody from './TweetBody.astro'\nimport TweetMedia from './TweetMedia.astro'\n// import TweetInfo from './TweetInfo.astro'\nimport TweetActions from './TweetActions.astro'\n// import TweetReplies from './TweetReplies.astro'\nimport QuotedTweet from './quoted-tweet/QuotedTweet.astro'\nimport { enrichTweet } from '../utils.js'\n\ntype Props = {\n\tautoplay?: boolean\n\ttweet: Tweet\n}\n\nconst { tweet: t, autoplay } = Astro.props\nconst tweet = enrichTweet(t)\n---\n\n<TweetContainer className=\"Tweet\">\n\t<TweetHeader tweet={tweet} />\n\t{tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />}\n\t<TweetBody tweet={tweet} />\n\t{tweet.mediaDetails?.length ? <TweetMedia {autoplay} tweet={tweet} /> : null}\n\t{tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />}\n\t<!-- <TweetInfo tweet={tweet} /> -->\n\t<TweetActions tweet={tweet} />\n\t<!-- <TweetReplies tweet={tweet} /> -->\n</TweetContainer>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/MediaImg.astro",
    "content": "---\ninterface Props {\n\tsrc: string\n\talt: string\n\tclass?: string\n\tdraggable?: boolean\n}\n---\n\n<img {...Astro.props} />\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/Skeleton.astro",
    "content": "---\nimport styles from './skeleton.module.css'\n---\n\n<span class={styles.skeleton} style={Astro.props.style}></span>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetActions.astro",
    "content": "---\nimport { type EnrichedTweet, formatNumber } from '../utils.js'\n// import { TweetActionsCopy } from './tweet-actions-copy.jsx'\nimport styles from './tweet-actions.module.css'\n\ninterface Props {\n\ttweet: EnrichedTweet\n}\n\nconst { tweet } = Astro.props\nconst favoriteCount = formatNumber(tweet.favorite_count)\n---\n\n<div class={styles.actions}>\n\t<a\n\t\tclass={styles.like}\n\t\thref={tweet.like_url}\n\t\ttarget=\"_blank\"\n\t\trel=\"noopener noreferrer\"\n\t\taria-label={`Like. This Tweet has ${favoriteCount} likes`}\n\t>\n\t\t<div class={styles.likeIconWrapper}>\n\t\t\t<svg viewBox=\"0 0 24 24\" class={styles.likeIcon} aria-hidden=\"true\">\n\t\t\t\t<g>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z\"\n\t\t\t\t\t></path>\n\t\t\t\t</g>\n\t\t\t</svg>\n\t\t</div>\n\t\t<span class={styles.likeCount}>{favoriteCount}</span>\n\t</a>\n\t<!-- <a\n    class={styles.reply}\n    href={tweet.reply_url}\n    target=\"_blank\"\n    rel=\"noopener noreferrer\"\n    aria-label=\"Reply to this Tweet on Twitter\"\n  >\n    <div class={styles.replyIconWrapper}>\n      <svg viewBox=\"0 0 24 24\" class={styles.replyIcon} aria-hidden=\"true\">\n        <g>\n          <path\n            d=\"M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01z\"\n          ></path>\n        </g>\n      </svg>\n    </div>\n    <span class={styles.replyText}>Reply</span>\n  </a> -->\n\t<!-- <TweetActionsCopy tweet={tweet} /> -->\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetBody.astro",
    "content": "---\nimport type { EnrichedTweet } from '../utils.js'\nimport TweetLink from './TweetLink.astro'\nimport styles from './tweet-body.module.css'\ninterface Props {\n\ttweet: EnrichedTweet\n}\nconst { tweet } = Astro.props\n---\n\n<p class={styles.root}>\n\t{\n\t\ttweet.entities.map((item) => {\n\t\t\tswitch (item.type) {\n\t\t\t\tcase 'hashtag':\n\t\t\t\tcase 'mention':\n\t\t\t\tcase 'url':\n\t\t\t\tcase 'symbol':\n\t\t\t\t\treturn <TweetLink href={item.href}>{item.text}</TweetLink>\n\t\t\t\tcase 'media':\n\t\t\t\t\t// Media text is currently never displayed, some tweets however might have indices\n\t\t\t\t\t// that do match `display_text_range` so for those cases we ignore the content.\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t\t// We use `dangerouslySetInnerHTML` to preserve the text encoding.\n\t\t\t\t\t// https://github.com/vercel-labs/react-tweet/issues/29\n\t\t\t\t\treturn <span set:html={item.text} />\n\t\t\t}\n\t\t})\n\t}\n</p>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetContainer.astro",
    "content": "---\nimport clsx from 'clsx'\nimport styles from './tweet-container.module.css'\nimport './theme.css'\n\ninterface Props {\n\tclassName?: string\n}\n\nconst { className } = Astro.props\n---\n\n<div class={clsx('astro-tweet-theme', styles.root, className)}>\n\t<article class={styles.article}>\n\t\t<slot />\n\t</article>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetHeader.astro",
    "content": "---\nimport clsx from 'clsx'\nimport type { EnrichedTweet } from '../utils.js'\nimport AvatarImg from './AvatarImg.astro'\nimport styles from './tweet-header.module.css'\nimport VerifiedBadge from './VerifiedBadge.astro'\n\ninterface Props {\n\ttweet: EnrichedTweet\n}\n\nconst { tweet } = Astro.props\nconst { user } = tweet\n---\n\n<div class={styles.header}>\n\t<a href={tweet.url} class={styles.avatar} target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t<div\n\t\t\tclass={clsx(\n\t\t\t\tstyles.avatarOverflow,\n\t\t\t\tuser.profile_image_shape === 'Square' && styles.avatarSquare\n\t\t\t)}\n\t\t>\n\t\t\t<AvatarImg src={user.profile_image_url_https} alt={user.name} width={48} height={48} />\n\t\t</div>\n\t\t<div class={styles.avatarOverflow}>\n\t\t\t<div class={styles.avatarShadow}></div>\n\t\t</div>\n\t</a>\n\t<div class={styles.author}>\n\t\t<a href={tweet.url} class={styles.authorLink} target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t<div class={styles.authorLinkText}>\n\t\t\t\t<span title={user.name}>{user.name}</span>\n\t\t\t</div>\n\t\t\t<VerifiedBadge user={user} className={styles.authorVerified} />\n\t\t</a>\n\t\t<div class={styles.authorMeta}>\n\t\t\t<a href={tweet.url} class={styles.username} target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t\t<span title={`@${user.screen_name}`}>@{user.screen_name}</span>\n\t\t\t</a>\n\t\t\t<!-- <div class={styles.authorFollow}>\n        <span class={styles.separator}>·</span>\n        <a\n          href={user.follow_url}\n          class={styles.follow}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          Follow\n        </a>\n      </div> -->\n\t\t</div>\n\t</div>\n\t<a\n\t\thref={tweet.url}\n\t\tclass={styles.brand}\n\t\ttarget=\"_blank\"\n\t\trel=\"noopener noreferrer\"\n\t\taria-label=\"View on Twitter\"\n\t>\n\t\t<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" class={styles.twitterIcon}>\n\t\t\t<g>\n\t\t\t\t<path\n\t\t\t\t\td=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\"\n\t\t\t\t></path>\n\t\t\t</g>\n\t\t</svg>\n\t</a>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetInReplyTo.astro",
    "content": "---\nimport type { EnrichedTweet } from '../utils.js'\nimport s from './tweet-in-reply-to.module.css'\ninterface Props {\n\ttweet: EnrichedTweet\n}\nconst { tweet } = Astro.props\n---\n\n<a href={tweet.in_reply_to_url} class={s.root} target=\"_blank\" rel=\"noopener noreferrer\">\n\tReplying to @{tweet.in_reply_to_screen_name}\n</a>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetInfo.astro",
    "content": "---\nimport type { EnrichedTweet } from '../utils.js'\nimport TweetInfoCreatedAt from './TweetInfoCreatedAt.astro'\nimport styles from './tweet-info.module.css'\n\ninterface Props {\n\ttweet: EnrichedTweet\n}\nconst { tweet } = Astro.props\n---\n\n<div class={styles.info}>\n\t<TweetInfoCreatedAt tweet={tweet} />\n\t<a\n\t\tclass={styles.infoLink}\n\t\thref=\"https://help.twitter.com/en/twitter-for-websites-ads-info-and-privacy\"\n\t\ttarget=\"_blank\"\n\t\trel=\"noopener noreferrer\"\n\t\taria-label=\"Twitter for Websites, Ads Information and Privacy\"\n\t>\n\t\t<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" class={styles.infoIcon}>\n\t\t\t<g>\n\t\t\t\t<path\n\t\t\t\t\td=\"M13.5 8.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5S11.17 7 12 7s1.5.67 1.5 1.5zM13 17v-5h-2v5h2zm-1 5.25c5.66 0 10.25-4.59 10.25-10.25S17.66 1.75 12 1.75 1.75 6.34 1.75 12 6.34 22.25 12 22.25zM20.25 12c0 4.56-3.69 8.25-8.25 8.25S3.75 16.56 3.75 12 7.44 3.75 12 3.75s8.25 3.69 8.25 8.25z\"\n\t\t\t\t></path>\n\t\t\t</g>\n\t\t</svg>\n\t</a>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetInfoCreatedAt.astro",
    "content": "---\nimport format from 'date-fns/format/index.js'\nimport type { EnrichedTweet } from '../utils.js'\nimport styles from './tweet-info-created-at.module.css'\n\ninterface Props {\n\ttweet: EnrichedTweet\n}\nconst { tweet } = Astro.props\nconst createdAt = new Date(tweet.created_at)\n---\n\n<a\n\tclass={styles.root}\n\thref={tweet.url}\n\ttarget=\"_blank\"\n\trel=\"noopener noreferrer\"\n\taria-label={format(createdAt!, 'h:mm a · MMM d, y')}\n>\n\t<time datetime={createdAt?.toISOString()}>\n\t\t{format(createdAt!, 'h:mm a · MMM d, y')}\n\t</time>\n</a>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetLink.astro",
    "content": "---\nimport s from './tweet-link.module.css'\ninterface Props {\n\thref: string\n}\n---\n\n<a href={Astro.props.href} class={s.root} target=\"_blank\" rel=\"noopener noreferrer\"><slot /></a>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetMedia.astro",
    "content": "---\nimport clsx from 'clsx'\nimport { type EnrichedTweet, type EnrichedQuotedTweet, getMediaUrl } from '../utils.js'\nimport { type MediaDetails } from '../api/index'\n// import { TweetMediaVideo } from \"./TweetMediaVideo.astro\";\nimport MediaImg from './MediaImg.astro'\nimport styles from './tweet-media.module.css'\nimport TweetMediaVideo from './TweetMediaVideo.astro'\n\nconst getSkeletonStyle = (media: MediaDetails, itemCount: number) => {\n\tlet paddingBottom = 56.25 // default of 16x9\n\n\t// if we only have 1 item, show at original ratio\n\tif (itemCount === 1)\n\t\tpaddingBottom = (100 / media.original_info.width) * media.original_info.height\n\n\t// if we have 2 items, double the default to be 16x9 total\n\tif (itemCount === 2) paddingBottom = paddingBottom * 2\n\n\treturn {\n\t\twidth: media.type === 'photo' ? undefined : 'unset',\n\t\tpaddingBottom: `${paddingBottom}%`\n\t}\n}\ninterface Props {\n\tautoplay?: boolean\n\ttweet: EnrichedTweet | EnrichedQuotedTweet\n\tquoted?: boolean\n}\nconst { tweet, quoted, autoplay } = Astro.props\nconst length = tweet.mediaDetails?.length ?? 0\n---\n\n<div class={clsx(styles.root, !quoted && styles.rounded)}>\n\t<div\n\t\tclass={clsx(\n\t\t\tstyles.mediaWrapper,\n\t\t\tlength > 1 && styles.grid2Columns,\n\t\t\tlength === 3 && styles.grid3,\n\t\t\tlength > 4 && styles.grid2x2\n\t\t)}\n\t>\n\t\t{\n\t\t\ttweet.mediaDetails?.map((media: any) => (\n\t\t\t\t<Fragment>\n\t\t\t\t\t{media.type === 'photo' ? (\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref={tweet.url}\n\t\t\t\t\t\t\tclass={clsx(styles.mediaContainer, styles.mediaLink)}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div class={styles.skeleton} style={getSkeletonStyle(media, length)} />\n\t\t\t\t\t\t\t<MediaImg\n\t\t\t\t\t\t\t\tsrc={getMediaUrl(media, 'small')}\n\t\t\t\t\t\t\t\talt={media.ext_alt_text || 'Image'}\n\t\t\t\t\t\t\t\tclass={styles.image}\n\t\t\t\t\t\t\t\tdraggable\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div class={styles.mediaContainer}>\n\t\t\t\t\t\t\t<div class={styles.skeleton} style={getSkeletonStyle(media, length)} />\n\t\t\t\t\t\t\t<TweetMediaVideo {autoplay} tweet={tweet} media={media} />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</Fragment>\n\t\t\t))\n\t\t}\n\t</div>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetMediaVideo.astro",
    "content": "---\nimport type { MediaAnimatedGif, MediaVideo } from '../api/index.js'\nimport { type EnrichedQuotedTweet, type EnrichedTweet, getMediaUrl, getMp4Video } from '../utils.js'\nimport mediaStyles from './tweet-media.module.css'\nimport Video from './TweetMediaVideo'\n\ninterface Props {\n\tautoplay?: boolean\n\ttweet: EnrichedTweet | EnrichedQuotedTweet\n\tmedia: MediaAnimatedGif | MediaVideo\n}\n\nconst { media, autoplay } = Astro.props\nconst mp4Video = getMp4Video(media)\n---\n\n<Video\n\tclassName={mediaStyles.image}\n\tposter={getMediaUrl(media, 'small')}\n\tautoPlay={autoplay}\n\tclient:load\n\tplaysInline\n\tloop\n\tmuted\n\tsrc={mp4Video?.url}\n\tpreload=\"metadata\"\n/>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetMediaVideo.tsx",
    "content": "import * as React from 'react'\n\nconst all = new Map<string, HTMLVideoElement>()\n\nexport default function TweetMediaVideo(props: React.ComponentPropsWithoutRef<'video'>) {\n\tconst ref = React.useRef<HTMLVideoElement | null>(null)\n\tReact.useEffect(() => {\n\t\tif (!ref.current) return\n\t\tconst io = new IntersectionObserver(\n\t\t\t(entry) => {\n\t\t\t\tif (!entry?.[0]) return\n\t\t\t\tconst { isIntersecting, target } = entry[0]\n\t\t\t\tif (isIntersecting) (target as HTMLVideoElement).play()\n\t\t\t},\n\t\t\t{ threshold: 1 }\n\t\t)\n\t\tio.observe(ref.current)\n\t\treturn () => {\n\t\t\tio.disconnect()\n\t\t}\n\t}, [])\n\tconst id = React.useId()\n\n\treturn (\n\t\t<video\n\t\t\tref={(el) => {\n\t\t\t\tref.current = el\n\t\t\t\tif (el) all.set(id, el)\n\t\t\t\telse all.delete(id)\n\t\t\t}}\n\t\t\t{...props}\n\t\t\tonPlay={() => {\n\t\t\t\tall.forEach((el) => {\n\t\t\t\t\tif (el !== ref.current) el.pause()\n\t\t\t\t})\n\t\t\t}}\n\t\t\tonClick={({ currentTarget }) => currentTarget[currentTarget.paused ? 'play' : 'pause']()}\n\t\t/>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetNotFound.astro",
    "content": "---\nimport TweetContainer from './TweetContainer.astro'\nimport styles from './tweet-not-found.module.css'\n\ninterface Props {\n\terror?: any\n}\n---\n\n<TweetContainer>\n\t<div class={styles.root}>\n\t\t<h3>Tweet not found</h3>\n\t\t<p>The embedded tweet could not be found…</p>\n\t</div>\n</TweetContainer>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetReplies.astro",
    "content": "---\nimport { type EnrichedTweet, formatNumber } from '../utils.js'\nimport s from './tweet-replies.module.css'\n\ninterface Props {\n\ttweet: EnrichedTweet\n}\nconst { tweet } = Astro.props\n---\n\n<div class={s.replies}>\n\t<a class={s.link} href={tweet.url} target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t<span class={s.text}>\n\t\t\t{\n\t\t\t\ttweet.conversation_count === 0\n\t\t\t\t\t? 'Read more on Twitter'\n\t\t\t\t\t: tweet.conversation_count === 1\n\t\t\t\t\t\t? `Read ${formatNumber(tweet.conversation_count)} reply`\n\t\t\t\t\t\t: `Read ${formatNumber(tweet.conversation_count)} replies`\n\t\t\t}\n\t\t</span>\n\t</a>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/TweetSkeleton.astro",
    "content": "---\nimport TweetContainer from './TweetContainer.astro'\nimport Skeleton from './Skeleton.astro'\nimport styles from './tweet-skeleton.module.css'\n---\n\n<TweetContainer className={styles.root}>\n\t<Skeleton style={{ height: '3rem', marginBottom: '0.75rem' }} />\n\t<Skeleton style={{ height: '6rem', margin: '0.5rem 0' }} />\n\t<div style={{ borderTop: 'var(--tweet-border)', margin: '0.5rem 0' }}></div>\n\t<Skeleton\n\t\tstyle={{\n\t\t\theight: '2rem'\n\t\t}}\n\t/>\n\t<Skeleton\n\t\tstyle={{\n\t\t\theight: '2rem',\n\t\t\tborderRadius: '9999px',\n\t\t\tmarginTop: '0.5rem'\n\t\t}}\n\t/>\n</TweetContainer>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/VerifiedBadge.astro",
    "content": "---\nimport clsx from 'clsx'\nimport type { TweetUser } from '../api/index.js'\nimport { Verified, VerifiedBusiness, VerifiedGovernment } from './icons/index'\nimport styles from './verified-badge.module.css'\n\ninterface Props {\n\tuser: TweetUser\n\tclassName?: string\n}\n\nconst { user, className } = Astro.props\nconst verified = user.verified || user.is_blue_verified || user.verified_type\nlet Icon = Verified\nlet iconClassName: string | undefined = styles.verifiedBlue\n\nif (verified) {\n\tif (!user.is_blue_verified) {\n\t\ticonClassName = styles.verifiedOld\n\t}\n\tswitch (user.verified_type) {\n\t\tcase 'Government':\n\t\t\tIcon = VerifiedGovernment\n\t\t\ticonClassName = styles.verifiedGovernment\n\t\t\tbreak\n\t\tcase 'Business':\n\t\t\tIcon = VerifiedBusiness\n\t\t\ticonClassName = undefined\n\t\t\tbreak\n\t}\n}\n---\n\n{\n\tverified ? (\n\t\t<div class={clsx(className, iconClassName)}>\n\t\t\t<Icon />\n\t\t</div>\n\t) : null\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/components.ts",
    "content": "export * from './types.js'\nexport * from './icons/index.js'\nexport * from './EmbeddedTweet.astro'\n// export * from \"./TweetActionsCopy.astro\";\nexport * from './TweetActions.astro'\nexport * from './TweetBody.astro'\nexport * from './TweetContainer.astro'\nexport * from './TweetHeader.astro'\nexport * from './TweetInReplyTo.astro'\nexport * from './TweetInfoCreatedAt.astro'\nexport * from './TweetInfo.astro'\nexport * from './TweetLink.astro'\nexport * from './TweetMediaVideo.astro'\nexport * from './TweetNotFound.astro'\nexport * from './TweetReplies.astro'\nexport * from './TweetSkeleton.astro'\nexport * from './quoted-tweet/index.js'\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/icons/Verified.astro",
    "content": "---\nimport styles from './icons.module.css'\n---\n\n<svg viewBox=\"0 0 24 24\" aria-label=\"Verified account\" role=\"img\" class={styles.verified}>\n\t<g>\n\t\t<path\n\t\t\td=\"M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z\"\n\t\t></path>\n\t</g>\n</svg>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/icons/VerifiedBusiness.astro",
    "content": "---\nimport styles from './icons.module.css'\n---\n\n<svg viewBox=\"0 0 22 22\" aria-label=\"Verified account\" role=\"img\" class={styles.verified}>\n\t<g>\n\t\t<linearGradient\n\t\t\tgradientUnits=\"userSpaceOnUse\"\n\t\t\tid=\"0-a\"\n\t\t\tx1=\"4.411\"\n\t\t\tx2=\"18.083\"\n\t\t\ty1=\"2.495\"\n\t\t\ty2=\"21.508\"\n\t\t>\n\t\t\t<stop offset=\"0\" stop-color=\"#f4e72a\"></stop>\n\t\t\t<stop offset=\".539\" stop-color=\"#cd8105\"></stop>\n\t\t\t<stop offset=\".68\" stop-color=\"#cb7b00\"></stop>\n\t\t\t<stop offset=\"1\" stop-color=\"#f4ec26\"></stop>\n\t\t\t<stop offset=\"1\" stop-color=\"#f4e72a\"></stop>\n\t\t</linearGradient>\n\t\t<linearGradient\n\t\t\tgradientUnits=\"userSpaceOnUse\"\n\t\t\tid=\"0-b\"\n\t\t\tx1=\"5.355\"\n\t\t\tx2=\"16.361\"\n\t\t\ty1=\"3.395\"\n\t\t\ty2=\"19.133\"\n\t\t>\n\t\t\t<stop offset=\"0\" stop-color=\"#f9e87f\"></stop>\n\t\t\t<stop offset=\".406\" stop-color=\"#e2b719\"></stop>\n\t\t\t<stop offset=\".989\" stop-color=\"#e2b719\"></stop>\n\t\t</linearGradient>\n\t\t<g clip-rule=\"evenodd\" fill-rule=\"evenodd\">\n\t\t\t<path\n\t\t\t\td=\"M13.324 3.848L11 1.6 8.676 3.848l-3.201-.453-.559 3.184L2.06 8.095 3.48 11l-1.42 2.904 2.856 1.516.559 3.184 3.201-.452L11 20.4l2.324-2.248 3.201.452.559-3.184 2.856-1.516L18.52 11l1.42-2.905-2.856-1.516-.559-3.184zm-7.09 7.575l3.428 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z\"\n\t\t\t\tfill=\"url(#0-a)\"></path>\n\t\t\t<path\n\t\t\t\td=\"M13.101 4.533L11 2.5 8.899 4.533l-2.895-.41-.505 2.88-2.583 1.37L4.2 11l-1.284 2.627 2.583 1.37.505 2.88 2.895-.41L11 19.5l2.101-2.033 2.895.41.505-2.88 2.583-1.37L17.8 11l1.284-2.627-2.583-1.37-.505-2.88zm-6.868 6.89l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z\"\n\t\t\t\tfill=\"url(#0-b)\"></path>\n\t\t\t<path\n\t\t\t\td=\"M6.233 11.423l3.429 3.428 5.65-6.17.038-.033-.005 1.398-5.683 6.206-3.429-3.429-.003-1.405.005.003z\"\n\t\t\t\tfill=\"#d18800\"></path>\n\t\t</g>\n\t</g>\n</svg>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/icons/VerifiedGovernment.astro",
    "content": "---\nimport styles from './icons.module.css'\n---\n\n<svg viewBox=\"0 0 22 22\" aria-label=\"Verified account\" role=\"img\" class={styles.verified}>\n\t<g>\n\t\t<path\n\t\t\tclip-rule=\"evenodd\"\n\t\t\td=\"M12.05 2.056c-.568-.608-1.532-.608-2.1 0l-1.393 1.49c-.284.303-.685.47-1.1.455L5.42 3.932c-.832-.028-1.514.654-1.486 1.486l.069 2.039c.014.415-.152.816-.456 1.1l-1.49 1.392c-.608.568-.608 1.533 0 2.101l1.49 1.393c.304.284.47.684.456 1.1l-.07 2.038c-.027.832.655 1.514 1.487 1.486l2.038-.069c.415-.014.816.152 1.1.455l1.392 1.49c.569.609 1.533.609 2.102 0l1.393-1.49c.283-.303.684-.47 1.099-.455l2.038.069c.832.028 1.515-.654 1.486-1.486L18 14.542c-.015-.415.152-.815.455-1.099l1.49-1.393c.608-.568.608-1.533 0-2.101l-1.49-1.393c-.303-.283-.47-.684-.455-1.1l.068-2.038c.029-.832-.654-1.514-1.486-1.486l-2.038.07c-.415.013-.816-.153-1.1-.456zm-5.817 9.367l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z\"\n\t\t\tfill-rule=\"evenodd\"></path>\n\t</g>\n</svg>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/icons/icons.module.css",
    "content": ".verified {\n\tmargin-left: 0.125rem;\n\tmax-width: 20px;\n\tmax-height: 20px;\n\theight: 1.25em;\n\tfill: currentColor;\n\tuser-select: none;\n\tvertical-align: text-bottom;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/icons/index.ts",
    "content": "import Verified from './Verified.astro'\nimport VerifiedBusiness from './VerifiedBusiness.astro'\nimport VerifiedGovernment from './VerifiedGovernment.astro'\n\nexport { Verified, VerifiedBusiness, VerifiedGovernment }\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/QuotedTweet.astro",
    "content": "---\nimport type { EnrichedQuotedTweet } from '../../utils'\n// import QuotedTweetContainer from './QuotedTweetContainer.astro'\n// import QuotedTweetHeader from './QuotedTweetHeader.astro'\n// import QuotedTweetBody from './QuotedTweetBody.astro'\n// import TweetMedia from '../TweetMedia.astro'\n\ninterface Props {\n\ttweet: EnrichedQuotedTweet\n}\n// const { tweet } = Astro.props\n---\n\n<!--<QuotedTweetContainer tweet={tweet}>\n\t<QuotedTweetHeader tweet={tweet} />\n\t<QuotedTweetBody tweet={tweet} />\n\t{tweet.mediaDetails?.length ? <TweetMedia quoted tweet={tweet} /> : null}\n</QuotedTweetContainer> -->\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/QuotedTweetBody.astro",
    "content": "---\nimport type { EnrichedQuotedTweet } from '../../utils.js'\nimport styles from './quoted-tweet-body.module.css'\n\ninterface Props {\n\ttweet: EnrichedQuotedTweet\n}\n---\n\n<p class={styles.root}>\n\t{Astro.props.tweet.entities.map((item) => <span set:html={item.text} />)}\n</p>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/QuotedTweetContainer.astro",
    "content": "---\nimport type { EnrichedQuotedTweet } from '../../utils.js'\nimport styles from './quoted-tweet-container.module.css'\n\ntype Props = { tweet: EnrichedQuotedTweet }\n---\n\n<div class={styles.root}>\n\t<article class={styles.article}>\n\t\t<slot />\n\t</article>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/QuotedTweetHeader.astro",
    "content": "---\nimport clsx from 'clsx'\nimport AvatarImg from '../AvatarImg.astro'\nimport styles from './quoted-tweet-header.module.css'\nimport type { EnrichedQuotedTweet } from '../../utils.js'\nimport VerifiedBadge from '../VerifiedBadge.astro'\n\ninterface Props {\n\ttweet: EnrichedQuotedTweet\n}\nconst { user } = Astro.props.tweet\nconst { tweet } = Astro.props\n---\n\n<div class={styles.header}>\n\t<a href={tweet.url} class={styles.avatar} target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t<div\n\t\t\tclass={clsx(\n\t\t\t\tstyles.avatarOverflow,\n\t\t\t\tuser.profile_image_shape === 'Square' && styles.avatarSquare\n\t\t\t)}\n\t\t>\n\t\t\t<AvatarImg src={user.profile_image_url_https} alt={user.name} width={20} height={20} />\n\t\t</div>\n\t</a>\n\t<div class={styles.author}>\n\t\t<div class={styles.authorText}>\n\t\t\t<span title={user.name}>{user.name}</span>\n\t\t</div>\n\t\t<VerifiedBadge user={user} />\n\t\t<div class={styles.username}>\n\t\t\t<span title={`@${user.screen_name}`}>@{user.screen_name}</span>\n\t\t</div>\n\t</div>\n</div>\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/index.ts",
    "content": "export * from './QuotedTweet.astro'\nexport * from './QuotedTweetContainer.astro'\nexport * from './QuotedTweetHeader.astro'\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/quoted-tweet-body.module.css",
    "content": ".root {\n\tfont-size: var(--tweet-quoted-body-font-size);\n\tfont-weight: var(--tweet-quoted-body-font-weight);\n\tline-height: var(--tweet-quoted-body-line-height);\n\tmargin: var(--tweet-quoted-body-margin);\n\toverflow-wrap: break-word;\n\twhite-space: pre-wrap;\n\tpadding: 0 0.75rem;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/quoted-tweet-container.module.css",
    "content": ".root {\n\twidth: 100%;\n\toverflow: hidden;\n\tborder: var(--tweet-border);\n\tborder-radius: 12px;\n\tmargin: var(--tweet-quoted-container-margin);\n\ttransition-property: background-color, box-shadow;\n\ttransition-duration: 0.2s;\n\t/* cursor: pointer; */\n}\n\n.root:hover {\n\tbackground-color: var(--tweet-quoted-bg-color-hover);\n}\n\n.article {\n\tposition: relative;\n\tbox-sizing: inherit;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/quoted-tweet/quoted-tweet-header.module.css",
    "content": ".header {\n\tdisplay: flex;\n\tpadding: 0.75rem 0.75rem 0 0.75rem;\n\tline-height: var(--tweet-header-line-height);\n\tfont-size: var(--tweet-header-font-size);\n\twhite-space: nowrap;\n\toverflow-wrap: break-word;\n\toverflow: hidden;\n}\n\n.avatar {\n\tposition: relative;\n\theight: 20px;\n\twidth: 20px;\n\tflex-shrink: 0;\n}\n\n.avatar img {\n\tborder-radius: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center;\n\twidth: 100%;\n}\n\n.avatarSquare {\n\tborder-radius: 4px;\n}\n\n.author {\n\tdisplay: flex;\n\tmargin: 0 0.5rem;\n}\n\n.authorText {\n\tfont-weight: 700;\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n\twhite-space: nowrap;\n}\n\n.username {\n\tcolor: var(--tweet-font-color-secondary);\n\ttext-decoration: none;\n\ttext-overflow: ellipsis;\n\tmargin-left: 0.125rem;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/skeleton.module.css",
    "content": ".skeleton {\n\tdisplay: block;\n\twidth: 100%;\n\tborder-radius: 5px;\n\tbackground-image: var(--tweet-skeleton-gradient);\n\tbackground-size: 400% 100%;\n\tanimation: loading 8s ease-in-out infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.skeleton {\n\t\tanimation: none;\n\t\tbackground-position: 200% 0;\n\t}\n}\n\n@keyframes loading {\n\t0% {\n\t\tbackground-position: 200% 0;\n\t}\n\t100% {\n\t\tbackground-position: -200% 0;\n\t}\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/theme.css",
    "content": ".astro-tweet-theme {\n\t--tweet-container-margin: 1.5rem 0;\n\n\t/* Header */\n\t--tweet-header-font-size: 0.9375rem;\n\t--tweet-header-line-height: 1.25rem;\n\n\t/* Text */\n\t--tweet-body-font-size: 1.25rem;\n\t--tweet-body-font-weight: 400;\n\t--tweet-body-line-height: 1.5rem;\n\t--tweet-body-margin: 0;\n\n\t/* Quoted Tweet */\n\t--tweet-quoted-container-margin: 0.75rem 0;\n\t--tweet-quoted-body-font-size: 0.938rem;\n\t--tweet-quoted-body-font-weight: 400;\n\t--tweet-quoted-body-line-height: 1.25rem;\n\t--tweet-quoted-body-margin: 0.25rem 0 0.75rem 0;\n\n\t/* Info */\n\t--tweet-info-font-size: 0.9375rem;\n\t--tweet-info-line-height: 1.25rem;\n\n\t/* Actions like the like, reply and copy buttons */\n\t--tweet-actions-font-size: 0.875rem;\n\t--tweet-actions-line-height: 1rem;\n\t--tweet-actions-font-weight: 700;\n\t--tweet-actions-icon-size: 1.25em;\n\t--tweet-actions-icon-wrapper-size: calc(var(--tweet-actions-icon-size) + 0.75em);\n\n\t/* Reply button */\n\t--tweet-replies-font-size: 0.875rem;\n\t--tweet-replies-line-height: 1rem;\n\t--tweet-replies-font-weight: 700;\n}\n\n:where(.astro-tweet-theme) * {\n\tmargin: 0;\n\tpadding: 0;\n\tbox-sizing: border-box;\n}\n\n:is([data-theme='light'], .light) :where(.astro-tweet-theme),\n:where(.astro-tweet-theme) {\n\t--tweet-skeleton-gradient: linear-gradient(270deg, #fafafa, #eaeaea, #eaeaea, #fafafa);\n\t--tweet-border: 1px solid rgb(207, 217, 222);\n\t--tweet-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,\n\t\tsans-serif;\n\t--tweet-font-color: rgb(15, 20, 25);\n\t--tweet-font-color-secondary: rgb(83, 100, 113);\n\t--tweet-bg-color: #fff;\n\t--tweet-bg-color-hover: rgb(247, 249, 249);\n\t--tweet-quoted-bg-color-hover: rgba(0, 0, 0, 0.03);\n\t--tweet-color-blue-primary: rgb(29, 155, 240);\n\t--tweet-color-blue-primary-hover: rgb(26, 140, 216);\n\t--tweet-color-blue-secondary: rgb(0, 111, 214);\n\t--tweet-color-blue-secondary-hover: rgba(0, 111, 214, 0.1);\n\t--tweet-color-red-primary: rgb(249, 24, 128);\n\t--tweet-color-red-primary-hover: rgba(249, 24, 128, 0.1);\n\t--tweet-color-green-primary: rgb(0, 186, 124);\n\t--tweet-color-green-primary-hover: rgba(0, 186, 124, 0.1);\n\t--tweet-twitter-icon-color: var(--tweet-font-color);\n\t--tweet-verified-old-color: rgb(130, 154, 171);\n\t--tweet-verified-blue-color: var(--tweet-color-blue-primary);\n}\n\n:is([data-theme='dark'], .dark) :where(.astro-tweet-theme) {\n\t--tweet-skeleton-gradient: linear-gradient(\n\t\t270deg,\n\t\t#15202b,\n\t\trgb(30, 39, 50),\n\t\trgb(30, 39, 50),\n\t\trgb(21, 32, 43)\n\t);\n\t--tweet-border: 1px solid rgb(66, 83, 100);\n\t--tweet-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,\n\t\tsans-serif;\n\t--tweet-font-color: rgb(247, 249, 249);\n\t--tweet-font-color-secondary: rgb(139, 152, 165);\n\t--tweet-bg-color: rgb(21, 32, 43);\n\t--tweet-bg-color-hover: rgb(30, 39, 50);\n\t--tweet-quoted-bg-color-hover: rgba(255, 255, 255, 0.03);\n\t--tweet-color-blue-primary: rgb(29, 155, 240);\n\t--tweet-color-blue-primary-hover: rgb(26, 140, 216);\n\t--tweet-color-blue-secondary: rgb(107, 201, 251);\n\t--tweet-color-blue-secondary-hover: rgba(107, 201, 251, 0.1);\n\t--tweet-color-red-primary: rgb(249, 24, 128);\n\t--tweet-color-red-primary-hover: rgba(249, 24, 128, 0.1);\n\t--tweet-color-green-primary: rgb(0, 186, 124);\n\t--tweet-color-green-primary-hover: rgba(0, 186, 124, 0.1);\n\t--tweet-twitter-icon-color: var(--tweet-font-color);\n\t--tweet-verified-old-color: rgb(130, 154, 171);\n\t--tweet-verified-blue-color: #fff;\n}\n\n@media (prefers-color-scheme: dark) {\n\t:where(.astro-tweet-theme) {\n\t\t--tweet-skeleton-gradient: linear-gradient(\n\t\t\t270deg,\n\t\t\t#15202b,\n\t\t\trgb(30, 39, 50),\n\t\t\trgb(30, 39, 50),\n\t\t\trgb(21, 32, 43)\n\t\t);\n\t\t--tweet-border: 1px solid rgb(66, 83, 100);\n\t\t--tweet-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,\n\t\t\tsans-serif;\n\t\t--tweet-font-color: rgb(247, 249, 249);\n\t\t--tweet-font-color-secondary: rgb(139, 152, 165);\n\t\t--tweet-bg-color: rgb(21, 32, 43);\n\t\t--tweet-bg-color-hover: rgb(30, 39, 50);\n\t\t--tweet-color-blue-primary: rgb(29, 155, 240);\n\t\t--tweet-color-blue-primary-hover: rgb(26, 140, 216);\n\t\t--tweet-color-blue-secondary: rgb(107, 201, 251);\n\t\t--tweet-color-blue-secondary-hover: rgba(107, 201, 251, 0.1);\n\t\t--tweet-color-red-primary: rgb(249, 24, 128);\n\t\t--tweet-color-red-primary-hover: rgba(249, 24, 128, 0.1);\n\t\t--tweet-color-green-primary: rgb(0, 186, 124);\n\t\t--tweet-color-green-primary-hover: rgba(0, 186, 124, 0.1);\n\t\t--tweet-twitter-icon-color: var(--tweet-font-color);\n\t\t--tweet-verified-old-color: rgb(130, 154, 171);\n\t\t--tweet-verified-blue-color: #fff;\n\t}\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-actions.module.css",
    "content": ".actions {\n\tdisplay: flex;\n\talign-items: center;\n\tcolor: var(--tweet-font-color-secondary);\n\tpadding-top: 0.25rem;\n\tmargin-top: 0.25rem;\n\tborder-top: var(--tweet-border);\n\toverflow-wrap: break-word;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n}\n\n.like,\n.reply,\n.copy {\n\ttext-decoration: none;\n\tcolor: inherit;\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-right: 1.25rem;\n}\n.like:hover,\n.reply:hover,\n.copy:hover {\n\tbackground-color: rgba(0, 0, 0, 0);\n}\n.like:hover > .likeIconWrapper {\n\tbackground-color: var(--tweet-color-red-primary-hover);\n}\n.like:hover > .likeCount {\n\tcolor: var(--tweet-color-red-primary);\n\ttext-decoration-line: underline;\n}\n.likeIconWrapper,\n.replyIconWrapper,\n.copyIconWrapper {\n\twidth: var(--tweet-actions-icon-wrapper-size);\n\theight: var(--tweet-actions-icon-wrapper-size);\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tmargin-left: -0.25rem;\n\tborder-radius: 9999px;\n}\n.likeIcon,\n.replyIcon,\n.copyIcon {\n\theight: var(--tweet-actions-icon-size);\n\tfill: currentColor;\n\tuser-select: none;\n}\n.likeIcon {\n\tcolor: var(--tweet-color-red-primary);\n}\n.likeCount,\n.replyText,\n.copyText {\n\tfont-size: var(--tweet-actions-font-size);\n\tfont-weight: var(--tweet-actions-font-weight);\n\tline-height: var(--tweet-actions-line-height);\n\tmargin-left: 0.25rem;\n}\n\n.reply:hover > .replyIconWrapper {\n\tbackground-color: var(--tweet-color-blue-secondary-hover);\n}\n.reply:hover > .replyText {\n\tcolor: var(--tweet-color-blue-secondary);\n\ttext-decoration-line: underline;\n}\n.replyIcon {\n\tcolor: var(--tweet-color-blue-primary);\n}\n\n.copy {\n\tfont: inherit;\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n}\n.copy:hover > .copyIconWrapper {\n\tbackground-color: var(--tweet-color-green-primary-hover);\n}\n.copy:hover .copyIcon {\n\tcolor: var(--tweet-color-green-primary);\n}\n.copy:hover > .copyText {\n\tcolor: var(--tweet-color-green-primary);\n\ttext-decoration-line: underline;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-body.module.css",
    "content": ".root {\n\tfont-size: var(--tweet-body-font-size);\n\tfont-weight: var(--tweet-body-font-weight);\n\tline-height: var(--tweet-body-line-height);\n\tmargin: var(--tweet-body-margin);\n\toverflow-wrap: break-word;\n\twhite-space: pre-wrap;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-container.module.css",
    "content": ".root {\n\twidth: 100%;\n\tmin-width: 250px;\n\tmax-width: 550px;\n\toverflow: hidden;\n\t/* Base font styles */\n\tcolor: var(--tweet-font-color);\n\tfont-family: var(--tweet-font-family);\n\tfont-weight: 400;\n\tbox-sizing: border-box;\n\tborder: var(--tweet-border);\n\tborder-radius: 12px;\n\tmargin: var(--tweet-container-margin);\n\tbackground-color: var(--tweet-bg-color);\n\ttransition-property: background-color, box-shadow;\n\ttransition-duration: 0.2s;\n}\n.root:hover {\n\tbackground-color: var(--tweet-bg-color-hover);\n}\n.article {\n\tposition: relative;\n\tbox-sizing: inherit;\n\tpadding: 0.75rem 1rem;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-header.module.css",
    "content": ".header {\n\tdisplay: flex;\n\tpadding-bottom: 0.75rem;\n\tline-height: var(--tweet-header-line-height);\n\tfont-size: var(--tweet-header-font-size);\n\twhite-space: nowrap;\n\toverflow-wrap: break-word;\n\toverflow: hidden;\n}\n\n.avatar {\n\tposition: relative;\n\theight: 48px;\n\twidth: 48px;\n\tflex-shrink: 0;\n}\n\n.avatar img {\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center;\n\twidth: 100%;\n}\n\n.avatarOverflow {\n\theight: 100%;\n\twidth: 100%;\n\tposition: absolute;\n\toverflow: hidden;\n\tborder-radius: 9999px;\n}\n.avatarSquare {\n\tborder-radius: 4px;\n}\n.avatarShadow {\n\theight: 100%;\n\twidth: 100%;\n\ttransition-property: background-color;\n\ttransition-duration: 0.2s;\n\tbox-shadow: rgb(0 0 0 / 3%) 0px 0px 2px inset;\n}\n.avatarShadow:hover {\n\tbackground-color: rgba(26, 26, 26, 0.15);\n}\n\n.author {\n\tmax-width: calc(100% - 84px);\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\tmargin: 0 0.5rem;\n}\n.authorLink {\n\ttext-decoration: none;\n\tcolor: inherit;\n\tdisplay: flex;\n\talign-items: center;\n}\n.authorLink:hover {\n\ttext-decoration-line: underline;\n}\n.authorVerified {\n\tdisplay: inline-flex;\n}\n.authorLinkText {\n\tfont-weight: 700;\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n\twhite-space: nowrap;\n}\n\n.authorMeta {\n\tdisplay: flex;\n}\n.authorFollow {\n\tdisplay: flex;\n}\n.username {\n\tcolor: var(--tweet-font-color-secondary);\n\ttext-decoration: none;\n\ttext-overflow: ellipsis;\n}\n.follow {\n\tcolor: var(--tweet-color-blue-secondary);\n\ttext-decoration: none;\n\tfont-weight: 700;\n}\n.follow:hover {\n\ttext-decoration-line: underline;\n}\n.separator {\n\tpadding: 0 0.25rem;\n}\n\n.brand {\n\tmargin-inline-start: auto;\n}\n\n.twitterIcon {\n\twidth: 23.75px;\n\theight: 23.75px;\n\tcolor: var(--tweet-twitter-icon-color);\n\tfill: currentColor;\n\tuser-select: none;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-in-reply-to.module.css",
    "content": ".root {\n\ttext-decoration: none;\n\tcolor: var(--tweet-font-color-secondary);\n\tfont-size: 0.9375rem;\n\tline-height: 1.25rem;\n\tmargin-bottom: 0.25rem;\n\toverflow-wrap: break-word;\n\twhite-space: pre-wrap;\n}\n.root:hover {\n\ttext-decoration-thickness: 1px;\n\ttext-decoration-line: underline;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-info-created-at.module.css",
    "content": ".root {\n\tcolor: inherit;\n\ttext-decoration: none;\n\tfont-size: var(--tweet-info-font-size);\n\tline-height: var(--tweet-info-line-height);\n}\n.root:hover {\n\ttext-decoration-thickness: 1px;\n\ttext-decoration-line: underline;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-info.module.css",
    "content": ".info {\n\tdisplay: flex;\n\talign-items: center;\n\tcolor: var(--tweet-font-color-secondary);\n\tmargin-top: 0.125rem;\n\toverflow-wrap: break-word;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n}\n.infoLink {\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n.infoLink {\n\theight: var(--tweet-actions-icon-wrapper-size);\n\twidth: var(--tweet-actions-icon-wrapper-size);\n\tfont: inherit;\n\tmargin-left: auto;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tmargin-right: -4px;\n\tborder-radius: 9999px;\n\ttransition-property: background-color;\n\ttransition-duration: 0.2s;\n}\n.infoLink:hover {\n\tbackground-color: var(--tweet-color-blue-secondary-hover);\n}\n.infoIcon {\n\tcolor: inherit;\n\tfill: currentColor;\n\theight: var(--tweet-actions-icon-size);\n\tuser-select: none;\n}\n.infoLink:hover > .infoIcon {\n\tcolor: var(--tweet-color-blue-secondary);\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-link.module.css",
    "content": ".root {\n\tfont-weight: inherit;\n\tcolor: var(--tweet-color-blue-secondary);\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n.root:hover {\n\ttext-decoration-thickness: 1px;\n\ttext-decoration-line: underline;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-media-video.module.css",
    "content": ".anchor {\n\tdisplay: flex;\n\talign-items: center;\n\tcolor: white;\n\tpadding: 0 1rem;\n\tborder: 1px solid transparent;\n\tborder-radius: 9999px;\n\tfont-weight: 700;\n\ttransition: background-color 0.2s;\n\tcursor: pointer;\n\tuser-select: none;\n\toutline-style: none;\n\ttext-decoration: none;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.videoButton {\n\tposition: relative;\n\theight: 67px;\n\twidth: 67px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground-color: var(--tweet-color-blue-primary);\n\ttransition-property: background-color;\n\ttransition-duration: 0.2s;\n\tborder: 4px solid #fff;\n\tborder-radius: 9999px;\n\tcursor: pointer;\n}\n.videoButton:hover,\n.videoButton:focus-visible {\n\tbackground-color: var(--tweet-color-blue-primary-hover);\n}\n.videoButtonIcon {\n\tmargin-left: 3px;\n\twidth: calc(50% + 4px);\n\theight: calc(50% + 4px);\n\tmax-width: 100%;\n\tcolor: #fff;\n\tfill: currentColor;\n\tuser-select: none;\n}\n.watchOnTwitter {\n\tposition: absolute;\n\ttop: 12px;\n\tright: 8px;\n}\n.watchOnTwitter > a {\n\tmin-width: 2rem;\n\tmin-height: 2rem;\n\tfont-size: 0.875rem;\n\tline-height: 1rem;\n\tbackdrop-filter: blur(4px);\n\tbackground-color: rgba(15, 20, 25, 0.75);\n}\n.watchOnTwitter > a:hover {\n\tbackground-color: rgba(39, 44, 48, 0.75);\n}\n.viewReplies {\n\tposition: relative;\n\tmin-height: 2rem;\n\tbackground-color: var(--tweet-color-blue-primary);\n\tborder-color: var(--tweet-color-blue-primary);\n\tfont-size: 0.9375rem;\n\tline-height: 1.25rem;\n}\n.viewReplies:hover {\n\tbackground-color: var(--tweet-color-blue-primary-hover);\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-media.module.css",
    "content": ".root {\n\tmargin-top: 0.75rem;\n\toverflow: hidden;\n\tposition: relative;\n}\n.rounded {\n\tborder: var(--tweet-border);\n\tborder-radius: 12px;\n}\n.mediaWrapper {\n\tdisplay: grid;\n\tgrid-auto-rows: 1fr;\n\tgap: 2px;\n\theight: 100%;\n\twidth: 100%;\n}\n.grid2Columns {\n\tgrid-template-columns: repeat(2, 1fr);\n}\n.grid3 > a:first-child {\n\tgrid-row: span 2;\n}\n.grid2x2 {\n\tgrid-template-rows: repeat(2, 1fr);\n}\n.mediaContainer {\n\tposition: relative;\n\theight: 100%;\n\twidth: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n.mediaLink {\n\ttext-decoration: none;\n\toutline-style: none;\n}\n.skeleton {\n\tpadding-bottom: 56.25%;\n\twidth: 100%;\n\tdisplay: block;\n}\n.image {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\tbottom: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tmargin: 0;\n\tobject-fit: cover;\n\tobject-position: center;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-not-found.module.css",
    "content": ".root {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tpadding-bottom: 0.75rem;\n}\n.root > h3 {\n\tfont-size: 1.25rem;\n\tmargin-bottom: 0.5rem;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-replies.module.css",
    "content": ".replies {\n\tpadding: 0.25rem 0;\n}\n.link {\n\ttext-decoration: none;\n\tcolor: var(--tweet-color-blue-secondary);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-width: 32px;\n\tmin-height: 32px;\n\tuser-select: none;\n\toutline-style: none;\n\ttransition-property: background-color;\n\ttransition-duration: 0.2s;\n\tpadding: 0 1rem;\n\tborder: var(--tweet-border);\n\tborder-radius: 9999px;\n}\n.link:hover {\n\tbackground-color: var(--tweet-color-blue-secondary-hover);\n}\n.text {\n\tfont-weight: var(--tweet-replies-font-weight);\n\tfont-size: var(--tweet-replies-font-size);\n\tline-height: var(--tweet-replies-line-height);\n\toverflow-wrap: break-word;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/tweet-skeleton.module.css",
    "content": ".root {\n\tpointer-events: none;\n\tpadding-bottom: 0.25rem;\n}\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/types.ts",
    "content": "/**\n * Custom components that the default Twitter theme allows.\n *\n * Note: We only use these components in Server Components, because the root `Tweet`\n * component that uses them is a Server Component and you can't pass down functions to a\n * client component unless they're Server Actions.\n */\nexport type TwitterComponents = {\n\tTweetNotFound?: typeof import('./TweetNotFound.astro')\n\tAvatarImg?: typeof import('./AvatarImg.astro')\n\tMediaImg?: typeof import('./MediaImg.astro')\n}\n\n/**\n * @deprecated Use `TwitterComponents` instead.\n */\nexport type TweetComponents = TwitterComponents\n"
  },
  {
    "path": "site/src/components/Tweet/twitter-theme/verified-badge.module.css",
    "content": ".verifiedOld {\n\tcolor: var(--tweet-verified-old-color);\n}\n.verifiedBlue {\n\tcolor: var(--tweet-verified-blue-color);\n}\n.verifiedGovernment {\n\t/* color: var(--tweet-verified-government-color); */\n\tcolor: rgb(130, 154, 171);\n}\n"
  },
  {
    "path": "site/src/components/Tweet/utils.ts",
    "content": "import type {\n\tTweetBase,\n\tTweet,\n\tQuotedTweet,\n\tMediaDetails,\n\tHashtagEntity,\n\tSymbolEntity,\n\tIndices,\n\tUserMentionEntity,\n\tUrlEntity,\n\tMediaEntity,\n\tMediaAnimatedGif,\n\tMediaVideo\n} from './api/index.js'\n\nexport type TweetCoreProps = {\n\tid: string\n\tonError?(error: any): any\n}\n\nconst getTweetUrl = (tweet: TweetBase) =>\n\t`https://twitter.com/${tweet.user.screen_name}/status/${tweet.id_str}`\n\nconst getUserUrl = (usernameOrTweet: string | TweetBase) =>\n\t`https://twitter.com/${\n\t\ttypeof usernameOrTweet === 'string' ? usernameOrTweet : usernameOrTweet.user.screen_name\n\t}`\n\nconst getLikeUrl = (tweet: TweetBase) => `https://twitter.com/intent/like?tweet_id=${tweet.id_str}`\n\nconst getReplyUrl = (tweet: TweetBase) =>\n\t`https://twitter.com/intent/tweet?in_reply_to=${tweet.id_str}`\n\nconst getFollowUrl = (tweet: TweetBase) =>\n\t`https://twitter.com/intent/follow?screen_name=${tweet.user.screen_name}`\n\nconst getHashtagUrl = (hashtag: HashtagEntity) => `https://twitter.com/hashtag/${hashtag.text}`\n\nconst getSymbolUrl = (symbol: SymbolEntity) => `https://twitter.com/search?q=%24${symbol.text}`\n\nconst getInReplyToUrl = (tweet: Tweet) =>\n\t`https://twitter.com/${tweet.in_reply_to_screen_name}/status/${tweet.in_reply_to_status_id_str}`\n\nexport const getMediaUrl = (media: MediaDetails, size: 'small' | 'medium' | 'large'): string => {\n\tconst url = new URL(media.media_url_https)\n\tconst extension = url.pathname.split('.').pop()\n\n\tif (!extension) return media.media_url_https\n\n\turl.pathname = url.pathname.replace(`.${extension}`, '')\n\turl.searchParams.set('format', extension)\n\turl.searchParams.set('name', size)\n\n\treturn url.toString()\n}\n\nexport const getMp4Videos = (media: MediaAnimatedGif | MediaVideo) => {\n\tconst { variants } = media.video_info\n\tconst sortedMp4Videos = variants\n\t\t.filter((vid) => vid.content_type === 'video/mp4')\n\t\t.sort((a, b) => (b.bitrate ?? 0) - (a.bitrate ?? 0))\n\n\treturn sortedMp4Videos\n}\n\nexport const getMp4Video = (media: MediaAnimatedGif | MediaVideo) => {\n\tconst mp4Videos = getMp4Videos(media)\n\t// Skip the highest quality video and use the next quality\n\treturn mp4Videos.length > 1 ? mp4Videos[1] : mp4Videos[0]\n}\n\nexport const formatNumber = (n: number): string => {\n\tif (n > 999999) return `${(n / 1000000).toFixed(1)}M`\n\tif (n > 999) return `${(n / 1000).toFixed(1)}K`\n\treturn n.toString()\n}\n\ntype TextEntity = {\n\tindices: Indices\n\ttype: 'text'\n}\n\ntype TweetEntity = HashtagEntity | UserMentionEntity | UrlEntity | MediaEntity | SymbolEntity\n\ntype EntityWithType =\n\t| TextEntity\n\t| (HashtagEntity & { type: 'hashtag' })\n\t| (UserMentionEntity & { type: 'mention' })\n\t| (UrlEntity & { type: 'url' })\n\t| (MediaEntity & { type: 'media' })\n\t| (SymbolEntity & { type: 'symbol' })\n\ntype Entity = {\n\ttext: string\n} & (\n\t| TextEntity\n\t| (HashtagEntity & { type: 'hashtag'; href: string })\n\t| (UserMentionEntity & { type: 'mention'; href: string })\n\t| (UrlEntity & { type: 'url'; href: string })\n\t| (MediaEntity & { type: 'media'; href: string })\n\t| (SymbolEntity & { type: 'symbol'; href: string })\n)\n\nfunction getEntities(tweet: TweetBase): Entity[] {\n\tconst textMap = Array.from(tweet.text)\n\tconst result: EntityWithType[] = [{ indices: tweet.display_text_range, type: 'text' }]\n\n\taddEntities(result, 'hashtag', tweet.entities.hashtags)\n\taddEntities(result, 'mention', tweet.entities.user_mentions)\n\taddEntities(result, 'url', tweet.entities.urls)\n\taddEntities(result, 'symbol', tweet.entities.symbols)\n\tif (tweet.entities.media) {\n\t\taddEntities(result, 'media', tweet.entities.media)\n\t}\n\tfixRange(tweet, result)\n\n\treturn result.map((entity) => {\n\t\tconst text = textMap.slice(entity.indices[0], entity.indices[1]).join('')\n\t\tswitch (entity.type) {\n\t\t\tcase 'hashtag':\n\t\t\t\treturn Object.assign(entity, { href: getHashtagUrl(entity), text })\n\t\t\tcase 'mention':\n\t\t\t\treturn Object.assign(entity, {\n\t\t\t\t\thref: getUserUrl(entity.screen_name),\n\t\t\t\t\ttext\n\t\t\t\t})\n\t\t\tcase 'url':\n\t\t\tcase 'media':\n\t\t\t\treturn Object.assign(entity, {\n\t\t\t\t\thref: entity.expanded_url,\n\t\t\t\t\ttext: entity.display_url\n\t\t\t\t})\n\t\t\tcase 'symbol':\n\t\t\t\treturn Object.assign(entity, { href: getSymbolUrl(entity), text })\n\t\t\tdefault:\n\t\t\t\treturn Object.assign(entity, { text })\n\t\t}\n\t})\n}\n\nfunction addEntities(\n\tresult: EntityWithType[],\n\ttype: EntityWithType['type'],\n\tentities: TweetEntity[]\n) {\n\tfor (const entity of entities) {\n\t\tfor (const [i, item] of result.entries()) {\n\t\t\tif (item.indices[0] > entity.indices[0] || item.indices[1] < entity.indices[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst items = [{ ...entity, type }] as EntityWithType[]\n\n\t\t\tif (item.indices[0] < entity.indices[0]) {\n\t\t\t\titems.unshift({\n\t\t\t\t\tindices: [item.indices[0], entity.indices[0]],\n\t\t\t\t\ttype: 'text'\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (item.indices[1] > entity.indices[1]) {\n\t\t\t\titems.push({\n\t\t\t\t\tindices: [entity.indices[1], item.indices[1]],\n\t\t\t\t\ttype: 'text'\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tresult.splice(i, 1, ...items)\n\t\t\tbreak // Break out of the loop to avoid iterating over the new items\n\t\t}\n\t}\n}\n\n/**\n * Update display_text_range to work w/ Array.from\n * Array.from is unicode aware, unlike string.slice()\n */\nfunction fixRange(tweet: TweetBase, entities: EntityWithType[]) {\n\tif (\n\t\ttweet.entities.media &&\n\t\ttweet.entities.media[0] &&\n\t\ttweet.entities.media[0].indices[0] < tweet.display_text_range[1]\n\t) {\n\t\ttweet.display_text_range[1] = tweet.entities.media[0].indices[0]\n\t}\n\tconst lastEntity = entities.at(-1)\n\tif (lastEntity && lastEntity.indices[1] > tweet.display_text_range[1]) {\n\t\tlastEntity.indices[1] = tweet.display_text_range[1]\n\t}\n}\n\nexport type EnrichedTweet = Omit<Tweet, 'entities' | 'quoted_tweet'> & {\n\turl: string\n\tuser: {\n\t\turl: string\n\t\tfollow_url: string\n\t}\n\tlike_url: string\n\treply_url: string\n\tin_reply_to_url?: string\n\tentities: Entity[]\n\tquoted_tweet?: EnrichedQuotedTweet\n}\n\nexport type EnrichedQuotedTweet = Omit<QuotedTweet, 'entities'> & {\n\turl: string\n\tentities: Entity[]\n}\n\n/**\n * Enriches a tweet with additional data used to more easily use the tweet in a UI.\n */\nexport const enrichTweet = (tweet: Tweet): EnrichedTweet => ({\n\t...tweet,\n\turl: getTweetUrl(tweet),\n\tuser: {\n\t\t...tweet.user,\n\t\turl: getUserUrl(tweet),\n\t\tfollow_url: getFollowUrl(tweet)\n\t},\n\tlike_url: getLikeUrl(tweet),\n\treply_url: getReplyUrl(tweet),\n\tin_reply_to_url: tweet.in_reply_to_screen_name ? getInReplyToUrl(tweet) : undefined,\n\tentities: getEntities(tweet),\n\tquoted_tweet: tweet.quoted_tweet\n\t\t? {\n\t\t\t\t...tweet.quoted_tweet,\n\t\t\t\turl: getTweetUrl(tweet.quoted_tweet),\n\t\t\t\tentities: getEntities(tweet.quoted_tweet)\n\t\t\t}\n\t\t: undefined\n})\n"
  },
  {
    "path": "site/src/components/Tweet.astro",
    "content": "---\nimport AstroTweet from './Tweet/Tweet.astro'\nimport type { ComponentProps } from 'astro/types'\nexport type Props = ComponentProps<typeof AstroTweet>\n---\n\n<AstroTweet {...Astro.props} />\n\n<style is:global>\n\t.Tweet {\n\t\t--tweet-bg-color-hover: var(--tweet-bg-color);\n\t\t--tweet-font-family: inherit;\n\t\t--tweet-body-font-size: 1rem;\n\t\t--tweet-container-margin: 0;\n\t\t--tweet-border: 1px solid theme(colors.zinc.200);\n\t\t--tweet-font-color-secondary: theme(colors.zinc.500);\n\t\tmax-width: none;\n\n\t\t@apply dark:[--tweet-bg-color:theme(colors.zinc.900)] dark:[--tweet-border:1px_solid_theme(colors.zinc.800)] dark:[--tweet-font-color-secondary:theme(colors.zinc.400)];\n\t}\n\n\t.Tweet [class*='actions'] {\n\t\tborder-top: none;\n\t}\n\t.Tweet [class*='rounded'] {\n\t\tborder: none;\n\t\tposition: relative;\n\t}\n\n\t.Tweet [class*='like']:hover > [class*='likeCount'] {\n\t\ttext-decoration: none;\n\t}\n\t.Tweet [class*='rounded'] {\n\t\t@apply rounded-lg;\n\t}\n\n\t.Tweet [class*='rounded']::after {\n\t\tcontent: '';\n\t\tinset: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tposition: absolute;\n\t\tborder-radius: inherit;\n\t\tpointer-events: none;\n\t\t--border-color: theme(colors.black/.1);\n\t\tbox-shadow: inset 0 0 0 1px var(--border-color);\n\n\t\t@apply dark:[--border-color:theme(colors.white/.1)];\n\t}\n\t.Tweet p {\n\t\twhite-space: pre-line !important;\n\t}\n</style>\n"
  },
  {
    "path": "site/src/components/Type.astro",
    "content": "---\nimport type { HTMLTag, Polymorphic } from 'astro/types'\n\ntype Props<Tag extends HTMLTag = 'span'> = Polymorphic<{ as: Tag }>\n\nconst { as: Tag = 'span', class: cls, ...props } = Astro.props\n---\n\n<Tag {...props} class:list={[cls, 'text-muted']}><slot /></Tag>\n"
  },
  {
    "path": "site/src/components/Union.astro",
    "content": "---\nimport type { HTMLAttributes } from 'astro/types'\nimport Type from './Type.astro'\n\nexport interface Props extends HTMLAttributes<'code'> {\n\ttypes: string[]\n}\nconst { types, class: cls, ...props } = Astro.props\n---\n\n<Type {...props} class:list={[cls, 'leading-snug']}>\n\t{\n\t\ttypes.map((type, i) => (\n\t\t\t<span\n\t\t\t\tclass:list={[\n\t\t\t\t\t'mr-[0.5em] inline-block last:mr-0',\n\t\t\t\t\ti !== types.length - 1 && 'border-r-[0.125em] pr-[0.5em]'\n\t\t\t\t]}\n\t\t\t>\n\t\t\t\t{type}\n\t\t\t</span>\n\t\t))\n\t}\n</Type>\n"
  },
  {
    "path": "site/src/components/code.module.css",
    "content": ".code {\n\t/* Make sure there's space to scroll around it: */\n\tmax-height: clamp(5rem, 100vh - var(--fluid), 30rem);\n\toverflow-x: auto;\n\ttab-size: 2;\n\n\t@apply dark:border-faint ~fluid-[13rem]/[15rem] rounded-lg !bg-zinc-950 !px-0 !py-5 !text-sm [scrollbar-color:theme(colors.white/.3)_transparent] [scrollbar-width:thin] dark:border dark:!bg-zinc-900 dark:[scrollbar-color:theme(colors.white/.1)_transparent];\n}\n\n.code :global(code) {\n\tdisplay: grid;\n\tmin-width: 100%;\n}\n\n.code :global(.line) {\n\t@apply inline-block px-5;\n}\n\n.code :global(.line:empty):before {\n\tcontent: ' ';\n}\n\n.code :global(.line:last-of-type:empty) {\n\tdisplay: none;\n}\n\n.code :global(span[style*='font-weight']) {\n\tfont-weight: normal !important;\n}\n"
  },
  {
    "path": "site/src/components/icons/frameworks/react.tsx",
    "content": "export default function React(props: React.SVGProps<SVGSVGElement>) {\n\treturn (\n\t\t<svg {...props} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-11.5 -10.23 23 20.46\">\n\t\t\t<title> React Logo </title>\n\t\t\t<circle r=\"2.05\" fill=\"#61dafb\" />\n\t\t\t<g fill=\"none\" stroke=\"#61dafb\">\n\t\t\t\t<ellipse rx=\"11\" ry=\"4.2\" />\n\t\t\t\t<ellipse rx=\"11\" ry=\"4.2\" transform=\"rotate(60)\" />\n\t\t\t\t<ellipse rx=\"11\" ry=\"4.2\" transform=\"rotate(120)\" />\n\t\t\t</g>\n\t\t</svg>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/icons/frameworks/svelte.tsx",
    "content": "export default function Web(props: React.SVGProps<SVGSVGElement>) {\n\treturn (\n\t\t<svg {...props} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 98.1 118\">\n\t\t\t<path\n\t\t\t\tfill=\"#FF3E00\"\n\t\t\t\td=\"M91.8,15.6C80.9-0.1,59.2-4.7,43.6,5.2L16.1,22.8C8.6,27.5,3.4,35.2,1.9,43.9c-1.3,7.3-0.2,14.8,3.3,21.3\n\tc-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1\n\tc1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.1,22.9,91.8,15.6\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\tfill=\"#FFFFFF\"\n\t\t\t\td=\"M40.9,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1\n\tc3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7L65.5,72\n\tc1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7\n\tc-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.5-17.5\n\tc1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1\n\tc-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.4,46.1\n\tc-1.4,0.9-2.3,2.2-2.6,3.8s0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7c1.7-1.1,3.6-1.9,5.6-2.5\n\tc8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.5,17.5C44.8,102.5,42.9,103.3,40.9,103.9\"\n\t\t\t/>\n\t\t</svg>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/icons/frameworks/vanilla.tsx",
    "content": "export default function Web(props: React.SVGProps<SVGSVGElement>) {\n\treturn (\n\t\t<svg {...props} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 630 630\">\n\t\t\t<rect width=\"630\" height=\"630\" fill=\"#f7df1e\" />\n\t\t\t<path d=\"m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z\" />\n\t\t</svg>\n\t)\n}\n"
  },
  {
    "path": "site/src/components/icons/frameworks/vue.tsx",
    "content": "export default function Vue(props: React.SVGProps<SVGSVGElement>) {\n\treturn (\n\t\t<svg {...props} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 196.32 170.02\">\n\t\t\t<path fill=\"#42b883\" d=\"M120.83 0L98.16 39.26 75.49 0H0l98.16 170.02L196.32 0h-75.49z\" />\n\t\t\t<path fill=\"#35495e\" d=\"M120.83 0L98.16 39.26 75.49 0H39.26l58.9 102.01L157.06 0h-36.23z\" />\n\t\t</svg>\n\t)\n}\n"
  },
  {
    "path": "site/src/context/toc.ts",
    "content": "import { createContext } from '@astropub/context'\nimport GitHubSlugger from 'github-slugger'\n\n// type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6\n\nexport type Heading = { title: string; id: string }\n\nexport const [TOCProvider, getTOCContext] = createContext<{\n\theadings: Heading[]\n\tslugger: GitHubSlugger\n}>()\n"
  },
  {
    "path": "site/src/env.d.ts",
    "content": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"react/canary\" />\n/// <reference types=\"react-dom/canary\" />\n"
  },
  {
    "path": "site/src/hooks/useCycle.ts",
    "content": "import * as React from 'react'\n\nexport default function useCycle<T>(options: Array<T>, defaultValue?: T) {\n\tconst [index, setIndex] = React.useState(defaultValue ? undefined : 0)\n\tconst next = () => setIndex((i) => ((i ?? -1) + 1) % options.length)\n\n\treturn [index == null && defaultValue ? defaultValue : options[index ?? 0]!, next] as const\n}\n"
  },
  {
    "path": "site/src/layouts/Docs.astro",
    "content": "---\nimport Layout from '@/layouts/Layout.astro'\nimport TOCLayout from '@/layouts/TOC.astro'\nimport Supported from '@/components/Supported'\nimport TOC from '@/components/TOC.astro'\nimport type { ComponentProps } from 'astro/types'\nimport { TOCProvider } from '@/context/toc'\nimport GitHubSlugger from 'github-slugger'\n\ntype Props = ComponentProps<typeof Layout> & {}\n---\n\n<Layout {...Astro.props} paddingBottom=\"\">\n\t<Supported transition:persist=\"supported\" client:only=\"react\" />\n\t<slot name=\"hero\" />\n\t<div\n\t\tclass=\"contents xl:container xl:grid xl:max-w-7xl xl:grid-cols-[1fr_minmax(auto,theme(maxWidth.2xl))_1fr] xl:items-start\"\n\t>\n\t\t<TOCProvider headings={[]} slugger={new GitHubSlugger()}>\n\t\t\t<TOCLayout>\n\t\t\t\t<article class=\"~pb-40/52 container max-w-2xl\">\n\t\t\t\t\t<div class=\"prose prose-zinc dark:prose-invert\">\n\t\t\t\t\t\t<slot />\n\t\t\t\t\t</div>\n\t\t\t\t\t<slot name=\"footer\" />\n\t\t\t\t</article>\n\t\t\t\t<div class=\"~top-6/10 sticky max-xl:hidden\" slot=\"toc\">\n\t\t\t\t\t<TOC />\n\t\t\t\t</div>\n\t\t\t</TOCLayout>\n\t\t</TOCProvider>\n\t</div>\n</Layout>\n"
  },
  {
    "path": "site/src/layouts/Layout.astro",
    "content": "---\nimport '@/assets/main.css'\nimport preview from '@/assets/preview.png'\nimport { getImage } from 'astro:assets'\n// import pkg from '/../packages/number-flow/package.json'\nimport FrameworkMenu from '@/components/FrameworkMenu'\nimport Link from '@/components/Link.astro'\nimport Nav from '@/components/Nav.astro'\n// import { AstroFont } from 'astro-font'\n// import * as path from 'node:path'\nimport { ClientRouter } from 'astro:transitions'\nimport { isActive } from '@/lib/url'\nimport { FRAMEWORKS, getFramework } from '@/lib/framework'\n\nconst frameworkId = getFramework(Astro.params)\nconst framework = frameworkId && FRAMEWORKS[frameworkId]\n\nconst canonicalURL = new URL(Astro.url.pathname, Astro.site)\n\nconst previewOptimized = await getImage({ src: preview, format: 'png' })\nconst image = import.meta.env.URL\n\t? new URL(previewOptimized.src, import.meta.env.URL)\n\t: previewOptimized.src\n\ntype Props = {\n\ttitle: string\n\tdescription: string\n\tpaddingBottom?: string\n}\n\nconst { title, description, paddingBottom = '~pb-40/52' } = Astro.props\n\nconst app = {\n\t'@context': 'https://schema.org',\n\t'@type': 'WebSite',\n\tname: 'NumberFlow',\n\talternateName: ['number-flow'],\n\turl: Astro.site\n}\n---\n\n<html\n\tlang=\"en\"\n\tdata-framework={frameworkId || undefined}\n\tclass=\"text-primary min-h-screen scroll-pt-10 overflow-x-hidden bg-white antialiased [--accent:var(--framework-light,theme(colors.blue.500))] motion-safe:scroll-smooth dark:bg-zinc-950 dark:[--accent:var(--framework-dark,theme(colors.blue.500))]\"\n\tstyle={{ '--framework-light': framework?.lightColor, '--framework-dark': framework?.darkColor }}\n>\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width\" />\n\t\t<link rel=\"shortcut icon\" href=\"/favicon.ico\" />\n\t\t<meta name=\"twitter:creator\" content=\"@mbarvian\" />\n\t\t<meta property=\"og:type\" content=\"article\" />\n\t\t<meta property=\"og:image\" content={image} />\n\t\t<meta property=\"og:site_name\" content=\"NumberFlow\" />\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" />\n\t\t<meta property=\"twitter:image\" content={image} />\n\t\t<meta name=\"generator\" content={Astro.generator} />\n\t\t<title>\n\t\t\t{title}\n\t\t</title>\n\t\t<!-- <AstroFont\n\t\t\tconfig={[\n\t\t\t\t{\n\t\t\t\t\tname: 'Inter',\n\t\t\t\t\tsrc: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tweight: '100 900',\n\t\t\t\t\t\t\tstyle: 'normal',\n\t\t\t\t\t\t\tpath: path.join(process.cwd(), 'public', 'fonts', 'Inter-roman-latin.var.woff2')\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tpreload: true,\n\t\t\t\t\tdisplay: 'block', // for View Transitions\n\t\t\t\t\tcssVariable: 'font-inter',\n\t\t\t\t\tfallback: 'sans-serif'\n\t\t\t\t}\n\t\t\t]}\n\t\t/> -->\n\t\t<link rel=\"canonical\" href={canonicalURL} />\n\t\t<meta property=\"og:title\" content={title} />\n\t\t<meta name=\"twitter:title\" content={title} />\n\t\t<meta name=\"description\" content={description} />\n\t\t<meta property=\"og:description\" content={description} />\n\t\t<meta name=\"twitter:description\" content={description} />\n\t\t<script is:inline>\n\t\t\tif (\n\t\t\t\tHTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode') ||\n\t\t\t\tHTMLTemplateElement.prototype.hasOwnProperty('shadowRoot')\n\t\t\t)\n\t\t\t\tdocument.documentElement.setAttribute('data-supports-dsd', '')\n\t\t</script>\n\t\t<script type=\"application/ld+json\" is:inline set:html={JSON.stringify(app)} />\n\n\t\t<ClientRouter />\n\t</head>\n\t<body class:list={[paddingBottom, '~pt-12/28']}>\n\t\t<div class=\"container inline-flex items-baseline justify-center whitespace-nowrap text-center\">\n\t\t\t{\n\t\t\t\tisActive('/', Astro.url) ? (\n\t\t\t\t\t<h1 class=\"font-medium\">\n\t\t\t\t\t\t<Link className=\"font-medium\" href=\"/\">\n\t\t\t\t\t\t\tNumberFlow\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t{/* <span class=\"inline-block rounded-sm border px-1 py-0.5 align-top text-[10px] font-medium\"> */}\n\t\t\t\t\t\t{/* <span class=\"text-muted\"> */}\n\t\t\t\t\t\t{/* v{pkg.version} */}\n\t\t\t\t\t\t{/* </span> */}\n\t\t\t\t\t</h1>\n\t\t\t\t) : (\n\t\t\t\t\t<Link className=\"font-medium\" href=\"/\">\n\t\t\t\t\t\tNumberFlow\n\t\t\t\t\t</Link>\n\t\t\t\t)\n\t\t\t}\n\t\t\t{\n\t\t\t\tframeworkId && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t&nbsp;<span class=\"text-muted\">for</span>&nbsp;\n\t\t\t\t\t\t<FrameworkMenu url={Astro.url} value={frameworkId} client:load />\n\t\t\t\t\t</>\n\t\t\t\t)\n\t\t\t}\n\t\t</div>\n\t\t<Nav />\n\n\t\t<main class=\"overflow-x-clip\">\n\t\t\t<slot />\n\t\t</main>\n\t</body>\n</html>\n\n<style is:global>\n\t/* Disable default view transitions for root */\n\t::view-transition-group(root),\n\t::view-transition-image-pair(root),\n\t::view-transition-old(root),\n\t::view-transition-new(root) {\n\t\tanimation: none;\n\t}\n</style>\n\n<script>\n\timport type { Framework } from '@/lib/framework'\n\timport { $pageFramework } from '@/stores/url'\n\n\t// Disable smooth scroll when transitioning, which also fixes anchor links for some reason:\n\tdocument.addEventListener('astro:before-swap', (event) => {\n\t\tevent.newDocument.documentElement.style.scrollBehavior = 'auto'\n\t\tevent.newDocument.documentElement.dataset.supportsDsd =\n\t\t\tdocument.documentElement.dataset.supportsDsd\n\n\t\tdocument.addEventListener(\n\t\t\t'astro:after-swap',\n\t\t\t() => {\n\t\t\t\tdocument.documentElement.style.scrollBehavior = ''\n\t\t\t},\n\t\t\t{ once: true }\n\t\t)\n\t})\n\n\t// Update framework on page load\n\tdocument.addEventListener('astro:page-load', () => {\n\t\tconst framework = document.documentElement.dataset.framework as Framework\n\t\tif (framework) {\n\t\t\tlocalStorage.setItem('framework', framework)\n\t\t\t$pageFramework.set(framework)\n\t\t}\n\t})\n</script>\n"
  },
  {
    "path": "site/src/layouts/TOC.astro",
    "content": "<!-- Flip the order -->{\n\t(async function () {\n\t\t// Render TOC last (for context to work) but return its markup first\n\t\tconst def = await Astro.slots.render('default')\n\t\tconst toc = await Astro.slots.render('toc')\n\t\treturn <Fragment set:html={toc + def} />\n\t})()\n}\n"
  },
  {
    "path": "site/src/lib/dom.ts",
    "content": "// Used for vanilla JS examples:\nexport const onReady = (cb: () => () => void) => {\n\tdocument.addEventListener('astro:page-load', () => {\n\t\t// Lazy try/catch to prevent errors on inapplicable pages:\n\t\ttry {\n\t\t\tconst destroy = cb()\n\t\t\t// Use after-preparation because our hydratable nanostores use before-swap:\n\t\t\tdocument.addEventListener('astro:after-preparation', destroy, {\n\t\t\t\tonce: true\n\t\t\t})\n\t\t} catch {}\n\t})\n}\n"
  },
  {
    "path": "site/src/lib/framework.ts",
    "content": "import { trimSlash } from './url'\nimport { name as vanillaPkgName } from '/../packages/number-flow/package.json'\nimport { name as reactPkgName } from '/../packages/react/package.json'\nimport { name as vuePkgName } from '/../packages/vue/package.json'\nimport { name as sveltePkgName } from '/../packages/svelte/package.json'\n\nexport type FrameworkData = {\n\tname: string | undefined\n\tpkg: string\n\tpkgName: string\n\tcomponentType: string\n\tsandbox: string\n\tlightColor: string\n\tdarkColor: string\n}\n\nexport const FRAMEWORKS = {\n\treact: {\n\t\tname: 'React',\n\t\tpkg: reactPkgName,\n\t\tpkgName: 'NumberFlow for React',\n\t\tcomponentType: 'React component',\n\t\tsandbox: 'https://codesandbox.io/p/sandbox/r47dcw',\n\t\tlightColor: '#0A7EA4',\n\t\tdarkColor: '#58C4DC'\n\t},\n\tvue: {\n\t\tname: 'Vue',\n\t\tpkg: vuePkgName,\n\t\tpkgName: 'NumberFlow for Vue',\n\t\tcomponentType: 'Vue component',\n\t\tsandbox: 'https://stackblitz.com/edit/vitejs-vite-4prbhc?file=src%2FApp.vue',\n\t\tlightColor: '#42B883',\n\t\tdarkColor: '#42B883'\n\t},\n\tsvelte: {\n\t\tname: 'Svelte',\n\t\tpkg: sveltePkgName,\n\t\tpkgName: 'NumberFlow for Svelte',\n\t\tcomponentType: 'Svelte component',\n\t\tsandbox: 'https://stackblitz.com/edit/vitejs-vite-5czxuc?file=src%2FApp.svelte',\n\t\tlightColor: '#FF3E00',\n\t\tdarkColor: '#F96844'\n\t},\n\tvanilla: {\n\t\tname: 'Vanilla',\n\t\tpkg: vanillaPkgName,\n\t\tpkgName: 'NumberFlow',\n\t\tcomponentType: 'web component',\n\t\tsandbox: 'https://stackblitz.com/edit/vitejs-vite-ec8hg3dz?file=index.html,src%2Fmain.ts',\n\t\tlightColor: '#F7DF1E',\n\t\tdarkColor: '#F7DF1E'\n\t}\n} satisfies Record<string, FrameworkData>\n\nexport type Framework = keyof typeof FRAMEWORKS\n\nexport const DEFAULT_FRAMEWORK: Framework = 'react'\n\nexport const getFramework = (params: Record<string, string | undefined>) =>\n\t'framework' in params ? ((params.framework as Framework) ?? DEFAULT_FRAMEWORK) : null\n\nexport const getStaticPaths = () =>\n\tObject.keys(FRAMEWORKS).map((id) => ({\n\t\tparams: { framework: id === DEFAULT_FRAMEWORK ? undefined : id }\n\t}))\n\nexport const toFrameworkPath = (\n\turlOrPathname?: string | URL | Location | null,\n\tid?: Framework | false | null\n) => {\n\tif (!urlOrPathname) return\n\tconst path = typeof urlOrPathname === 'string' ? urlOrPathname : urlOrPathname.pathname\n\tif (!id) return path\n\tconst [_, firstSegment, ...segments] = path.split('/')\n\n\t// New prefix to prepend, based on new framework:\n\tconst prefix = id === DEFAULT_FRAMEWORK ? '' : '/' + id\n\n\tif (firstSegment && Object.keys(FRAMEWORKS).includes(firstSegment)) {\n\t\treturn trimSlash(prefix + '/' + segments.join('/'))\n\t}\n\t// It was on the default framework\n\treturn trimSlash(prefix + path)\n}\n"
  },
  {
    "path": "site/src/lib/spring.ts",
    "content": "import { spring as motionSpring } from 'motion'\n\nexport const spring = (...args: Parameters<typeof motionSpring>) => {\n\tconst string = motionSpring(...args).toString()\n\tconst [, duration, easing] = string.match(/^(.*?ms)\\s(.*)$/)!\n\treturn {\n\t\tduration: parseFloat(duration!),\n\t\teasing,\n\t\ttoString() {\n\t\t\treturn string\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "site/src/lib/stores.ts",
    "content": "import {\n\tatom,\n\tcomputed,\n\tonStart,\n\ttype PreinitializedWritableAtom,\n\ttype ReadableAtom\n} from 'nanostores'\n\nexport const isCyclableAtom = Symbol()\n\nexport type CyclableAtom<T> = ReadableAtom<T> & {\n\tcycle: () => void\n\treset: () => void\n\t[isCyclableAtom]: true\n}\n\nexport function cyclable<T>(...options: Array<T>): CyclableAtom<T> {\n\tconst $index = atom(0)\n\tconst $value = computed($index, (i) => options[i]!)\n\treturn Object.assign($value, {\n\t\tcycle: () => $index.set(($index.get() + 1) % options.length),\n\t\treset: () => $index.set(0),\n\t\t[isCyclableAtom]: true\n\t} as const)\n}\n\nexport function hydratable<T, A extends CyclableAtom<T> | PreinitializedWritableAtom<T>>(\n\t$atom: A\n): A {\n\tconst initial = $atom.get()\n\tonStart($atom, () => {\n\t\tconst beforeSwap = () => {\n\t\t\tif (isCyclableAtom in $atom) $atom.reset()\n\t\t\telse $atom.set(initial)\n\t\t}\n\t\ttypeof document !== 'undefined' && document.addEventListener('astro:before-swap', beforeSwap)\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('astro:before-swap', beforeSwap)\n\t\t}\n\t})\n\treturn $atom\n}\n"
  },
  {
    "path": "site/src/lib/types.ts",
    "content": "export type Rename<T, K extends keyof T, N extends string> = {\n\t[P in keyof T as P extends K ? N : P]: T[P]\n}\n"
  },
  {
    "path": "site/src/lib/url.ts",
    "content": "import { DEFAULT_FRAMEWORK, toFrameworkPath } from './framework'\n\nconst _isActive = (path: string | undefined, urlOrPathname: URL | string | undefined) => {\n\tif (!path || !urlOrPathname) return false\n\tconst currentPath = typeof urlOrPathname === 'string' ? urlOrPathname : urlOrPathname.pathname\n\tif (currentPath === path || currentPath.startsWith(path + '/')) return true\n\treturn false\n}\n\nexport const isActive = (\n\tpath: string | undefined,\n\turlOrPathname: URL | Location | string | undefined\n) =>\n\t_isActive(\n\t\ttoFrameworkPath(path, DEFAULT_FRAMEWORK),\n\t\ttoFrameworkPath(urlOrPathname, DEFAULT_FRAMEWORK)\n\t)\n\nexport const trimSlash = (path: string | undefined) => path?.replace(/(.)\\/$/, '$1')\n"
  },
  {
    "path": "site/src/middleware.ts",
    "content": "import type { MiddlewareHandler } from 'astro'\nimport { $url, $pageFramework } from '@/stores/url'\nimport { getFramework } from '@/lib/framework'\n\nexport const onRequest: MiddlewareHandler = ({ url, params }, next) => {\n\t// Expose the request URL for framework components SSR (equivalent to Astro.url in Astro component)\n\t$url.set(url)\n\t$pageFramework.set(getFramework(params))\n\n\treturn next()\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_CSP.astro",
    "content": "---\nimport Code from '@/components/Code.astro'\nimport { FRAMEWORKS, getFramework } from '@/lib/framework'\nimport cspInstructions from './_csp.txt?raw'\n\nconst { pkg } = FRAMEWORKS[getFramework(Astro.params)!]\n---\n\n<Code code={cspInstructions.replaceAll('[pkg]', pkg)} lang=\"ts\" />\n"
  },
  {
    "path": "site/src/pages/[...framework]/_Digits.astro",
    "content": "<span class=\"*:text-muted whitespace-nowrap\"\n\t>3<sup>2</sup>4<sup>1</sup>2<sup>0</sup>.5<sup>-1</sup></span\n>\n"
  },
  {
    "path": "site/src/pages/[...framework]/_Hero.tsx",
    "content": "import NumberFlow, { type Format } from '@number-flow/react'\nimport useCycle from '@/hooks/useCycle'\nimport { useEffect, useRef } from 'react'\nimport { useInView } from 'motion/react'\nimport { ArrowUpRight } from 'lucide-react'\n\nconst NUMBERS = [431.1, -3243.6, 42, 398.43, -3243.5, 1435237.2, 12348.43, -3243.6, 54231.2]\nconst LOCALES = ['en-US', 'en-US', 'fr-FR', 'en-US', 'en-US', 'zh-CN', 'en-US', 'en-US', 'fr-FR']\nconst FORMATS = [\n\t{\n\t\tminimumFractionDigits: 2\n\t},\n\t{\n\t\tstyle: 'currency',\n\t\tcurrency: 'USD',\n\t\tcurrencySign: 'accounting',\n\t\tsignDisplay: 'always'\n\t},\n\t{},\n\t{\n\t\tstyle: 'percent',\n\t\tsignDisplay: 'always'\n\t},\n\t{},\n\t{\n\t\tstyle: 'unit',\n\t\tunit: 'meter',\n\t\tnotation: 'compact',\n\t\tminimumFractionDigits: 2,\n\t\tmaximumFractionDigits: 2,\n\t\tsignDisplay: 'never'\n\t},\n\t{\n\t\tstyle: 'currency',\n\t\tcurrency: 'USD'\n\t},\n\t{},\n\t{\n\t\t// style: \"percent\",\n\t\tsignDisplay: 'always'\n\t}\n] as Format[]\n\nexport default function Hero({ sandbox }: { sandbox: string }) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\tconst [locale, cycleLocale] = useCycle(LOCALES)\n\tconst [format, cycleFormat] = useCycle(FORMATS)\n\n\tconst ref = useRef<HTMLElement>(null)\n\tconst inView = useInView(ref, { once: true })\n\tconst timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\tuseEffect(() => {\n\t\tif (!inView) return\n\t\tif (sessionStorage.getItem('hero-did-animate')) return\n\t\ttimeoutRef.current = setTimeout(() => {\n\t\t\tsessionStorage.setItem('hero-did-animate', 'true')\n\t\t\tcycleValue()\n\t\t\tcycleLocale()\n\t\t\tcycleFormat()\n\t\t}, 750)\n\t\treturn () => {\n\t\t\tif (timeoutRef.current !== null) clearTimeout(timeoutRef.current)\n\t\t}\n\t}, [inView])\n\n\treturn (\n\t\t<header\n\t\t\tref={ref}\n\t\t\tclassName=\"~mb-12/24 container flex w-full max-w-2xl flex-col items-center text-center\"\n\t\t>\n\t\t\t<NumberFlow\n\t\t\t\tclassName=\"~text-5xl/7xl mb-4 mt-3.5 font-[550]\"\n\t\t\t\ttrend={0}\n\t\t\t\tvalue={value}\n\t\t\t\tlocales={locale}\n\t\t\t\tformat={format}\n\t\t\t\twillChange\n\t\t\t/>\n\t\t\t<p className=\"~text-base/lg prose prose-muted dark:prose-invert text-balance\">\n\t\t\t\tAn animated number component. Dependency-free. Accessible. Customizable.\n\t\t\t</p>\n\t\t\t<div className=\"~mt-6/8 flex w-full flex-wrap items-stretch justify-center gap-3\">\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"btn btn-primary\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tif (timeoutRef.current !== null) clearTimeout(timeoutRef.current)\n\n\t\t\t\t\t\tcycleValue()\n\t\t\t\t\t\tcycleLocale()\n\t\t\t\t\t\tcycleFormat()\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<svg className=\"size-4\" strokeLinejoin=\"round\" viewBox=\"0 0 16 16\">\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\td=\"M2.72876 6.42462C3.40596 4.15488 5.51032 2.5 8.00002 2.5C10.0902 2.5 11.9092 3.66566 12.8405 5.38592L13.1975 6.04548L14.5166 5.33138L14.1596 4.67183C12.9767 2.48677 10.6625 1 8.00002 1C5.05453 1 2.53485 2.81872 1.50122 5.39447V3.75V3H0.0012207V3.75V7.17462C0.0012207 7.58883 0.337007 7.92462 0.751221 7.92462H4.17584H4.92584V6.42462H4.17584H2.72876ZM13.2713 9.57538H11.8243H11.0743V8.07538H11.8243H15.2489C15.6631 8.07538 15.9989 8.41117 15.9989 8.82538V12.25V13H14.4989V12.25V10.6053C13.4653 13.1812 10.9456 15 8.00002 15C5.35065 15 3.04619 13.5279 1.85809 11.3605L1.49757 10.7029L2.8129 9.98181L3.17342 10.6395C4.10882 12.3458 5.92017 13.5 8.00002 13.5C10.4897 13.5 12.5941 11.8451 13.2713 9.57538Z\"\n\t\t\t\t\t\t\tfill=\"currentColor\"\n\t\t\t\t\t\t></path>\n\t\t\t\t\t</svg>\n\t\t\t\t\tShuffle\n\t\t\t\t</button>\n\t\t\t\t<a href={sandbox} target=\"_blank\" className=\"btn btn-secondary\">\n\t\t\t\t\tOpen sandbox\n\t\t\t\t\t<ArrowUpRight className=\"size-4\" />\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</header>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_Home.astro",
    "content": "---\n// import LogoWall from '@/components/LogoWall/LogoWall.astro'\n// import Match from '@/components/Match.astro'\nimport DocsLayout from '@/layouts/Docs.astro'\nimport type { MDXLayoutProps } from 'astro'\nimport Hero from './_Hero'\nimport Link from '@/components/Link.astro'\nimport { FRAMEWORKS, getFramework } from '@/lib/framework'\n\ntype Props = MDXLayoutProps<{}>\n\nconst { sandbox, pkgName, componentType } = FRAMEWORKS[getFramework(Astro.params)!]\n---\n\n<DocsLayout\n\ttitle={`${pkgName} - An animated number component`}\n\tdescription={`A ${componentType} to animate numbers. Dependency-free. Accessible. Customizable.`}\n>\n\t<Hero transition:persist=\"home-hero\" client:load {sandbox} slot=\"hero\" />\n\t<!-- <Match react slot=\"hero\">\n\t\t<LogoWall />\n\t</Match> -->\n\t<slot />\n\t<footer\n\t\tslot=\"footer\"\n\t\tclass=\"prose prose-muted dark:prose-invert border-faint mt-16 border-t pt-16\"\n\t>\n\t\t<p>\n\t\t\tBuilt by <Link href=\"https://x.com/mbarvian\">Max Barvian</Link>. Heavily inspired by the\n\t\t\t<Link href=\"https://family.co\">Family</Link> app. <Link\n\t\t\t\thref=\"https://expensive.toys/blog/blur-vignette\"><code>mask-image</code> technique</Link\n\t\t\t> by <Link href=\"https://x.com/artur_bien\">Artur Bień</Link>. <Link\n\t\t\t\thref=\"https://buildui.com/recipes/animated-counter\">Digit looping technique</Link\n\t\t\t> by <Link href=\"https://x.com/samselikoff\">Sam Selikoff</Link>.\n\t\t</p>\n\t</footer>\n</DocsLayout>\n"
  },
  {
    "path": "site/src/pages/[...framework]/_csp.txt",
    "content": "import { styles } from '[pkg]'\nimport { createHash } from 'node:crypto'\n\nconst headers = {\n\t'Content-Security-Policy': `style-src ${styles.map((style) => `'sha256-${createHash('sha256').update(style).digest('base64')}'`).join(' ')}`\n}"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Continuous.tsx",
    "content": "import Demo, { DemoSwitch, type DemoProps } from '@/components/Demo'\nimport NumberFlow, { continuous } from '@number-flow/react'\nimport * as React from 'react'\nimport useCycle from '@/hooks/useCycle'\nimport type { Rename } from '@/lib/types'\n\nconst NUMBERS = [120, 140]\n\nexport default function DemoHOC({\n\tchildren,\n\t...rest\n}: Rename<Omit<DemoProps, 'children'>, 'code', 'children'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\tconst [useContinuous, setUseContinuous] = React.useState(true)\n\n\treturn (\n\t\t<Demo\n\t\t\tcode={children}\n\t\t\t{...rest}\n\t\t\ttitle={\n\t\t\t\t<DemoSwitch isSelected={useContinuous} onChange={setUseContinuous}>\n\t\t\t\t\t<code className=\"font-semibold\">continuous</code>\n\t\t\t\t</DemoSwitch>\n\t\t\t}\n\t\t\tonClick={cycleValue}\n\t\t>\n\t\t\t<div className=\"~text-3xl/4xl flex items-center gap-4\">\n\t\t\t\t<NumberFlow\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tplugins={useContinuous ? [continuous] : undefined}\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tclassName=\"font-semibold\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Isolate.tsx",
    "content": "import Demo, { DemoSwitch, type DemoProps } from '@/components/Demo'\nimport NumberFlow from '@number-flow/react'\nimport * as React from 'react'\n\nexport default function DemoHOC({ ...rest }: Omit<DemoProps, 'children' | 'code'>) {\n\tconst [increased, setIncreased] = React.useState(false)\n\tconst [isolate, setIsolate] = React.useState(false)\n\n\treturn (\n\t\t<Demo\n\t\t\t{...rest}\n\t\t\ttitle={\n\t\t\t\t<DemoSwitch isSelected={isolate} onChange={setIsolate}>\n\t\t\t\t\t<code className=\"font-semibold\">isolate</code>\n\t\t\t\t</DemoSwitch>\n\t\t\t}\n\t\t\tonClick={() => setIncreased((o) => !o)}\n\t\t>\n\t\t\t<div className=\"~text-3xl/4xl flex items-center gap-4\">\n\t\t\t\t{increased && <div className=\"bg-faint ~w-20/40 h-[1em] rounded-sm\" />}\n\t\t\t\t<NumberFlow\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tisolate={isolate}\n\t\t\t\t\tvalue={increased ? 1.2423 : 0.4175}\n\t\t\t\t\tformat={{ style: 'percent' }}\n\t\t\t\t\tclassName=\"font-semibold\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Styling.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport type { Rename } from '@/lib/types'\nimport NumberFlow, { type Value } from '@number-flow/react'\nimport useCycle from '@/hooks/useCycle'\n\nconst NUMBERS: Value[] = [3, 15, 50]\n\nexport default function DemoHOC({\n\tchildren,\n\t...rest\n}: Rename<Omit<DemoProps, 'children'>, 'code', 'children'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\n\treturn (\n\t\t<Demo {...rest} code={children} onClick={cycleValue}>\n\t\t\t<NumberFlow\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\tvalue={value}\n\t\t\t\tformat={{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }}\n\t\t\t\tsuffix=\"/mo\"\n\t\t\t\tclassName=\"~text-3xl/4xl part-[suffix]:font-normal part-[suffix]:text-muted part-[suffix]:text-[0.75em] part-[suffix]:ml-[0.0625em] font-semibold\"\n\t\t\t/>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Suffix.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport type { Rename } from '@/lib/types'\nimport NumberFlow, { type Value } from '@number-flow/react'\nimport useCycle from '@/hooks/useCycle'\n\nconst NUMBERS: Value[] = [3, 15, 50]\n\nexport default function DemoHOC({\n\tchildren,\n\t...rest\n}: Rename<Omit<DemoProps, 'children'>, 'code', 'children'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\n\treturn (\n\t\t<Demo {...rest} code={children} onClick={cycleValue}>\n\t\t\t<NumberFlow\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\tvalue={value}\n\t\t\t\tformat={{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }}\n\t\t\t\tsuffix=\"/mo\"\n\t\t\t\tclassName=\"~text-3xl/4xl font-semibold\"\n\t\t\t/>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/TabularNums.tsx",
    "content": "import Demo, { DemoSwitch, type DemoProps } from '@/components/Demo'\nimport NumberFlow from '@number-flow/react'\nimport * as React from 'react'\n\nexport default function DemoHOC({ ...rest }: Omit<DemoProps, 'children' | 'code'>) {\n\tconst [value, setValue] = React.useState(10)\n\tconst [tabularNums, setTabularNums] = React.useState(false)\n\n\treturn (\n\t\t<Demo\n\t\t\t{...rest}\n\t\t\ttitle={\n\t\t\t\t<DemoSwitch isSelected={tabularNums} onChange={setTabularNums}>\n\t\t\t\t\t<code className=\"font-semibold\">tabular-nums</code>\n\t\t\t\t</DemoSwitch>\n\t\t\t}\n\t\t\tonClick={() => setValue((v) => v + 1)}\n\t\t>\n\t\t\t<div className=\"~text-3xl/4xl flex items-center gap-4\">\n\t\t\t\t<NumberFlow\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tfontVariantNumeric: tabularNums ? 'tabular-nums' : undefined\n\t\t\t\t\t}}\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tclassName=\"font-semibold\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Timings.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport type { Rename } from '@/lib/types'\nimport NumberFlow from '@number-flow/react'\nimport useCycle from '@/hooks/useCycle'\n\nconst bouncySpring: EffectTiming = {\n\tduration: 750,\n\teasing:\n\t\t'linear(0 0%, 0.0058021823078800595 1.1235955056179776%, 0.022019245228978974 2.247191011235955%, 0.04697426784552192 3.370786516853933%, 0.07913194654911777 4.49438202247191%, 0.11709565568904509 5.617977528089888%, 0.15960336091615873 6.741573033707866%, 0.2055225959731031 7.865168539325843%, 0.25384469405525506 8.98876404494382%, 0.30367844589280607 10.112359550561798%, 0.35424333850373657 11.235955056179776%, 0.40486251123515354 12.359550561797754%, 0.45495554931807397 13.483146067415731%, 0.5040312197589397 14.606741573033709%, 0.5516802400120898 15.730337078651687%, 0.5975681565371418 16.853932584269664%, 0.6414283980456571 17.97752808988764%, 0.6830555569725216 19.10112359550562%, 0.7222989424488457 20.224719101123597%, 0.7590564387760397 21.348314606741575%, 0.7932686950692723 22.471910112359552%, 0.8249136643114104 23.59550561797753%, 0.8540015034900891 24.719101123595507%, 0.8805698407319795 25.842696629213485%, 0.9046794103485847 26.966292134831463%, 0.9264100524147755 28.08988764044944%, 0.9458570698619917 29.213483146067418%, 0.9631279330300166 30.337078651685395%, 0.9783393191326047 31.460674157303373%, 0.9916144721023972 32.58426966292135%, 1.0030808667404794 33.70786516853933%, 1.0128681599585279 34.831460674157306%, 1.0211064111219177 35.95505617977528%, 1.0279245530378938 37.07865168539326%, 1.033449094943999 38.20224719101124%, 1.0378030389010637 39.325842696629216%, 1.0411049912474741 40.449438202247194%, 1.0434684511951837 41.57303370786517%, 1.0450012592136182 42.69662921348315%, 1.0458051885285111 43.82022471910113%, 1.0459756638345723 44.943820224719104%, 1.0456015921619513 46.06741573033708%, 1.044765291727289 47.19101123595506%, 1.0435425055235072 48.31460674157304%, 1.0420024873433105 49.438202247191015%, 1.0402081488764594 50.56179775280899%, 1.0382162574589653 51.68539325842697%, 1.0360776749738179 52.80898876404495%, 1.0338376292996567 53.932584269662925%, 1.0315360105693312 55.0561797752809%, 1.029207685329244 56.17977528089888%, 1.0268828224786164 57.30337078651686%, 1.02458722561227 58.426966292134836%, 1.022342667089039 59.55056179775281%, 1.020167219799199 60.67415730337079%, 1.0180755832077413 61.79775280898877%, 1.0160794008059388 62.921348314606746%, 1.0141875666120557 64.04494382022472%, 1.0124065188242148 65.16853932584269%, 1.0107405191458168 66.29213483146067%, 1.0091919166781451 67.41573033707866%, 1.0077613956078837 68.53932584269663%, 1.0064482062113422 69.6629213483146%, 1.005250378954478 70.78651685393258%, 1.0041649216907218 71.91011235955057%, 1.003188000149523 73.03370786516854%, 1.002315102069899 74.15730337078651%, 1.001541185467481 75.28089887640449%, 1.0008608116329931 76.40449438202248%, 1.0002682635470859 77.52808988764045%, 0.9997576504632046 78.65168539325842%, 0.9993229994588604 79.7752808988764%, 0.998958334788324 80.89887640449439%, 0.9986577458883016 82.02247191011236%, 0.9984154448944083 83.14606741573033%, 0.9982258145219031 84.26966292134831%, 0.9980834471507738 85.3932584269663%, 0.9979831759342885 86.51685393258427%, 0.9979200987229117 87.64044943820224%, 0.9978895955632022 88.76404494382022%, 0.9978873404950641 89.88764044943821%, 0.9979093083314955 91.01123595505618%, 0.9979517770636321 92.13483146067415%, 0.9980113264912046 93.25842696629213%, 0.9980848336351612 94.38202247191012%, 0.9981694654457833 95.50561797752809%, 0.9982626692766015 96.62921348314606%, 0.9983621615522535 97.75280898876404%, 0.9984659150174638 98.87640449438203%, 1 100%)'\n}\n\nconst opacityTiming: EffectTiming = { duration: 350, easing: 'ease-out' }\n\nconst NUMBERS = [124.23, 41.75, 2125.95]\n\nexport default function DemoHOC({\n\tchildren,\n\t...rest\n}: Rename<Omit<DemoProps, 'children'>, 'code', 'children'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\n\treturn (\n\t\t<Demo {...rest} code={children} onClick={cycleValue}>\n\t\t\t<NumberFlow\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\tvalue={value}\n\t\t\t\ttransformTiming={bouncySpring}\n\t\t\t\topacityTiming={opacityTiming}\n\t\t\t\tclassName=\"~text-3xl/4xl font-semibold\"\n\t\t\t/>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/_demos/Trend.tsx",
    "content": "import * as React from 'react'\nimport Demo, {\n\tDemoMenu,\n\tDemoMenuButton,\n\tDemoMenuItem,\n\tDemoMenuItems,\n\ttype DemoProps\n} from '@/components/Demo'\nimport NumberFlow, { type Trend } from '@number-flow/react'\nimport useCycle from '@/hooks/useCycle'\n\nconst NUMBERS = [20, 19]\n\nconst TRENDS: Record<string, Trend | undefined> = {\n\tdefault: undefined,\n\t'+1': 1,\n\t'0': 0,\n\t'-1': -1\n}\n\nexport default function DemoHOC({ ...rest }: Omit<DemoProps, 'children' | 'code'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\n\tconst [option, setOption] = React.useState<keyof typeof TRENDS>('default')\n\tconst trend = TRENDS[option]\n\n\treturn (\n\t\t<Demo\n\t\t\t{...rest}\n\t\t\ttitle={\n\t\t\t\t<DemoMenu>\n\t\t\t\t\t<DemoMenuButton className=\"gap-1\">\n\t\t\t\t\t\t<code className=\"text-muted\">trend:</code>\n\t\t\t\t\t\t<code className=\"font-semibold\">{option}</code>\n\t\t\t\t\t</DemoMenuButton>\n\t\t\t\t\t<DemoMenuItems className=\"min-w-[20rem]\">\n\t\t\t\t\t\t<DemoMenuItem\n\t\t\t\t\t\t\ttextValue=\"default\"\n\t\t\t\t\t\t\tonAction={() => setOption('default')}\n\t\t\t\t\t\t\tisDisabled={option === 'default'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<code className=\"font-semibold\">default</code>\n\t\t\t\t\t\t</DemoMenuItem>\n\t\t\t\t\t\t<DemoMenuItem\n\t\t\t\t\t\t\ttextValue=\"+1\"\n\t\t\t\t\t\t\tonAction={() => setOption('+1')}\n\t\t\t\t\t\t\tisDisabled={option === '+1'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<code className=\"font-semibold\">+1</code>\n\t\t\t\t\t\t</DemoMenuItem>\n\t\t\t\t\t\t<DemoMenuItem textValue=\"0\" onAction={() => setOption('0')} isDisabled={option === '0'}>\n\t\t\t\t\t\t\t<code className=\"font-semibold\">0</code>\n\t\t\t\t\t\t</DemoMenuItem>\n\t\t\t\t\t\t<DemoMenuItem\n\t\t\t\t\t\t\ttextValue=\"-1\"\n\t\t\t\t\t\t\tonAction={() => setOption('-1')}\n\t\t\t\t\t\t\tisDisabled={option === '-1'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<code className=\"font-semibold\">-1</code>\n\t\t\t\t\t\t</DemoMenuItem>\n\t\t\t\t\t</DemoMenuItems>\n\t\t\t\t</DemoMenu>\n\t\t\t}\n\t\t\tonClick={cycleValue}\n\t\t>\n\t\t\t<NumberFlow\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\ttrend={trend}\n\t\t\t\tvalue={value}\n\t\t\t\tclassName=\"~text-3xl/4xl text-primary font-semibold\"\n\t\t\t/>\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/index.astro",
    "content": "---\nimport Activity from '.'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\n// Could probably do a client:only={framework} but then we'd lose SSR:\nimport React from './react'\nimport react from './react/Component.tsx?raw'\nimport Vue from './vue/index.vue'\nimport vue from './vue/Component.vue?raw'\nimport Svelte from './svelte/index.svelte'\nimport svelte from './svelte/Component.svelte?raw'\nimport Vanilla from './vanilla/index.astro'\nimport vanilla from './vanilla/Component.astro?raw'\n---\n\n<Activity client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match vue><Vue client:load /></Match>\n\t<Match svelte><Svelte client:load /></Match>\n\t<Match vanilla><Vanilla /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t\t<Code code={vue} slot=\"vue\" lang=\"vue\" />\n\t\t<Code code={svelte} slot=\"svelte\" lang=\"svelte\" />\n\t\t<Code code={vanilla} slot=\"vanilla\" lang=\"html\" />\n\t</Match>\n</Activity>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/index.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport { $inView } from './stores'\n\nexport default function Activity(props: DemoProps) {\n\treturn <Demo {...props} onIntersect={({ isIntersecting }) => $inView.set(isIntersecting)} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/react/Component.tsx",
    "content": "import NumberFlow, { continuous, type Format } from '@number-flow/react'\nimport clsx from 'clsx/lite'\nimport { Bookmark, ChartNoAxesColumn, Heart, Repeat, Share } from 'lucide-react'\nimport type { ComponentPropsWithoutRef } from 'react'\n\nconst format: Format = {\n\tnotation: 'compact',\n\tcompactDisplay: 'short',\n\troundingMode: 'trunc'\n}\n\ntype Props = ComponentPropsWithoutRef<'div'> & {\n\tlikes: number\n\treposts: number\n\tviews: number\n\tbookmarks: number\n\tliked: boolean\n\treposted: boolean\n\tbookmarked: boolean\n\tonLike: () => void\n\tonBookmark: () => void\n\tonRepost: () => void\n}\n\nexport default function Activity({\n\tclassName,\n\tlikes,\n\treposts,\n\tviews,\n\tbookmarks,\n\tonLike,\n\tonRepost,\n\tonBookmark,\n\tliked,\n\treposted,\n\tbookmarked,\n\t...rest\n}: Props) {\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tclassName={clsx(\n\t\t\t\tclassName,\n\t\t\t\t'flex w-full select-none items-center text-zinc-600 dark:text-zinc-300'\n\t\t\t)}\n\t\t>\n\t\t\t<div className=\"flex flex-1 items-center gap-1.5\">\n\t\t\t\t<ChartNoAxesColumn absoluteStrokeWidth className=\"~size-4/5\" />\n\t\t\t\t<NumberFlow\n\t\t\t\t\twillChange\n\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\tvalue={views}\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tformat={format}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div className=\"flex-1\">\n\t\t\t\t<button\n\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-emerald-500',\n\t\t\t\t\t\treposted && 'text-emerald-500'\n\t\t\t\t\t)}\n\t\t\t\t\tonClick={onRepost}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-emerald-500/10\">\n\t\t\t\t\t\t<Repeat\n\t\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\t\tclassName=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\twillChange\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\tvalue={reposts}\n\t\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\t\tformat={format}\n\t\t\t\t\t/>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div className=\"flex-1\">\n\t\t\t\t<button\n\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-pink-500',\n\t\t\t\t\t\tliked && 'text-pink-500'\n\t\t\t\t\t)}\n\t\t\t\t\tonClick={onLike}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-pink-500/10\">\n\t\t\t\t\t\t<Heart\n\t\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-[65] spring-duration-300 transition-transform group-active:scale-[80%]',\n\t\t\t\t\t\t\t\tliked && 'fill-current'\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\twillChange\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\tvalue={likes}\n\t\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\t\tformat={format}\n\t\t\t\t\t/>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div className=\"min-[30rem]:flex-1 max-[24rem]:hidden flex shrink-0 items-center gap-1.5\">\n\t\t\t\t<button\n\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-blue-500',\n\t\t\t\t\t\tbookmarked && 'text-blue-500'\n\t\t\t\t\t)}\n\t\t\t\t\tonClick={onBookmark}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-blue-500/10\">\n\t\t\t\t\t\t<Bookmark\n\t\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]',\n\t\t\t\t\t\t\t\tbookmarked && 'fill-current'\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tclassName=\"max-[30rem]:hidden\"\n\t\t\t\t\t\twillChange\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\tvalue={bookmarks}\n\t\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\t\tformat={format}\n\t\t\t\t\t/>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<Share absoluteStrokeWidth className=\"~size-4/5 shrink-0\" />\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/react/index.tsx",
    "content": "import Component from './Component'\nimport { useStore } from '@nanostores/react'\nimport { $bookmarks, $likes, $reposts, $views } from '../stores'\n\nexport default function () {\n\tconst reposts = useStore($reposts)\n\tconst bookmarks = useStore($bookmarks)\n\tconst likes = useStore($likes)\n\tconst views = useStore($views)\n\n\treturn (\n\t\t<Component\n\t\t\tclassName=\"~px-0/16\"\n\t\t\tlikes={likes.count}\n\t\t\tonLike={$likes.toggle}\n\t\t\tliked={likes.hasIncremented}\n\t\t\treposts={reposts.count}\n\t\t\tonRepost={$reposts.toggle}\n\t\t\treposted={reposts.hasIncremented}\n\t\t\tbookmarks={bookmarks.count}\n\t\t\tonBookmark={$bookmarks.toggle}\n\t\t\tbookmarked={bookmarks.hasIncremented}\n\t\t\tviews={views.count}\n\t\t/>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/stores.ts",
    "content": "import { atom, map, onMount, type ReadableAtom } from 'nanostores'\nimport { hydratable } from '@/lib/stores'\n\nexport const $inView = atom(false)\n\ninterface CounterState {\n\tcount: number\n\thasIncremented: boolean\n}\n\nfunction countable(\n\tinitialValue: number,\n\tactive: ReadableAtom<boolean>,\n\tmin: number,\n\tmax: number,\n\trate = 1\n) {\n\tconst state = map<CounterState>({\n\t\tcount: initialValue,\n\t\thasIncremented: false\n\t})\n\n\tonMount(state, () => {\n\t\tlet timeout: NodeJS.Timeout | null = null\n\t\tconst unsubscribe = active.subscribe((active) => {\n\t\t\tif (timeout != null) clearTimeout(timeout)\n\t\t\tif (!active) return\n\t\t\tconst randomlyIncrease = (delay: number) => {\n\t\t\t\ttimeout = setTimeout(() => {\n\t\t\t\t\tstate.setKey('count', state.get().count + randomBetween(min, max) * rate)\n\t\t\t\t\trandomlyIncrease(3500)\n\t\t\t\t}, delay)\n\t\t\t}\n\t\t\trandomlyIncrease(1500)\n\t\t})\n\t\treturn () => {\n\t\t\tif (timeout != null) clearTimeout(timeout)\n\t\t\tunsubscribe()\n\t\t}\n\t})\n\n\treturn Object.assign(state, {\n\t\ttoggle: () => {\n\t\t\tconst s = state.get()\n\t\t\tstate.set({\n\t\t\t\tcount: s.hasIncremented ? s.count - 1 : s.count + 1,\n\t\t\t\thasIncremented: !s.hasIncremented\n\t\t\t})\n\t\t}\n\t})\n}\n\n// Generate a random number between two numbers:\nfunction randomBetween(min: number, max: number) {\n\treturn Math.floor(Math.random() * (max - min + 1) + min)\n}\n\nexport const $reposts = hydratable(countable(2, $inView, 0, 2))\nexport const $likes = hydratable(countable(50, $inView, 0, 3, 5))\nexport const $bookmarks = hydratable(countable(40, $inView, 0, 3, 3))\nexport const $views = hydratable(countable(995, $inView, 1, 3, 50))\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/svelte/Component.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow, { continuous, type Format } from '@number-flow/svelte'\n\timport clsx from 'clsx/lite'\n\timport { Bookmark, ChartNoAxesColumn, Heart, Repeat, Share } from 'lucide-svelte'\n\timport type { HTMLAttributes } from 'svelte/elements'\n\n\ttype Props = HTMLAttributes<HTMLDivElement> & {\n\t\tlikes: number\n\t\treposts: number\n\t\tviews: number\n\t\tbookmarks: number\n\t\tliked: boolean\n\t\treposted: boolean\n\t\tbookmarked: boolean\n\t\tonlike: () => void\n\t\tonrepost: () => void\n\t\tonbookmark: () => void\n\t}\n\n\tconst format: Format = {\n\t\tnotation: 'compact',\n\t\tcompactDisplay: 'short',\n\t\troundingMode: 'trunc'\n\t}\n\n\tconst {\n\t\tlikes,\n\t\treposts,\n\t\tviews,\n\t\tbookmarks,\n\t\tliked,\n\t\treposted,\n\t\tbookmarked,\n\t\tonlike,\n\t\tonrepost,\n\t\tonbookmark,\n\t\tclass: cls,\n\t\t...props\n\t}: Props = $props()\n</script>\n\n<div\n\t{...props}\n\tclass={clsx(cls, 'flex w-full select-none items-center text-zinc-600 dark:text-zinc-300')}\n>\n\t<div class=\"flex flex-1 items-center gap-1.5\">\n\t\t<ChartNoAxesColumn absoluteStrokeWidth class=\"~size-4/5\" />\n\t\t<NumberFlow willChange plugins={[continuous]} value={views} locales=\"en-US\" {format} />\n\t</div>\n\t<div class=\"flex-1\">\n\t\t<button\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-emerald-500\"\n\t\t\tclass:text-emerald-500={reposted}\n\t\t\tonclick={onrepost}\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-emerald-500/10\"\n\t\t\t>\n\t\t\t\t<Repeat\n\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\tclass=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<NumberFlow willChange plugins={[continuous]} value={reposts} locales=\"en-US\" {format} />\n\t\t</button>\n\t</div>\n\t<div class=\"flex-1\">\n\t\t<button\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-pink-500\"\n\t\t\tclass:text-pink-500={liked}\n\t\t\tonclick={onlike}\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-pink-500/10\"\n\t\t\t>\n\t\t\t\t<Heart\n\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\tclass={clsx(\n\t\t\t\t\t\tliked && 'fill-current',\n\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-[65] spring-duration-300 transition-transform group-active:scale-[80%]'\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<NumberFlow willChange plugins={[continuous]} value={likes} locales=\"en-US\" {format} />\n\t\t</button>\n\t</div>\n\t<div class=\"min-[30rem]:flex-1 max-[24rem]:hidden flex shrink-0 items-center gap-1.5\">\n\t\t<button\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-blue-500\"\n\t\t\tclass:text-blue-500={bookmarked}\n\t\t\tonclick={onbookmark}\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-blue-500/10\"\n\t\t\t>\n\t\t\t\t<Bookmark\n\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\tclass={clsx(\n\t\t\t\t\t\tbookmarked && 'fill-current',\n\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]'\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<NumberFlow\n\t\t\t\tclass=\"max-[30rem]:hidden\"\n\t\t\t\twillChange\n\t\t\t\tplugins={[continuous]}\n\t\t\t\tvalue={bookmarks}\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\t{format}\n\t\t\t/>\n\t\t</button>\n\t</div>\n\t<Share absoluteStrokeWidth class=\"~size-4/5 shrink-0\" />\n</div>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/svelte/index.svelte",
    "content": "<script lang=\"ts\">\n\timport Component from './Component.svelte'\n\timport {\n\t\t$bookmarks as bookmarks,\n\t\t$likes as likes,\n\t\t$reposts as reposts,\n\t\t$views as views\n\t} from '../stores'\n</script>\n\n<Component\n\tclass=\"~px-0/16\"\n\tlikes={$likes.count}\n\tonlike={likes.toggle}\n\tliked={$likes.hasIncremented}\n\treposts={$reposts.count}\n\tonrepost={reposts.toggle}\n\treposted={$reposts.hasIncremented}\n\tbookmarks={$bookmarks.count}\n\tonbookmark={bookmarks.toggle}\n\tbookmarked={$bookmarks.hasIncremented}\n\tviews={$views.count}\n/>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/vanilla/Component.astro",
    "content": "<div class=\"flex w-full select-none items-center text-zinc-600 dark:text-zinc-300\">\n\t<div class=\"flex flex-1 items-center gap-1.5\">\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstroke-width=\"2\"\n\t\t\tstroke-linecap=\"round\"\n\t\t\tstroke-linejoin=\"round\"\n\t\t\tclass=\"~size-4/5\"\n\t\t\t><line x1=\"18\" x2=\"18\" y1=\"20\" y2=\"10\"></line><line x1=\"12\" x2=\"12\" y1=\"20\" y2=\"4\"\n\t\t\t></line><line x1=\"6\" x2=\"6\" y1=\"20\" y2=\"14\"></line></svg\n\t\t>\n\t\t<number-flow id=\"vanilla-example-activity-views\" data-will-change></number-flow>\n\t</div>\n\t<div class=\"flex-1\">\n\t\t<button\n\t\t\tid=\"vanilla-example-activity-repost\"\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-emerald-500\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-emerald-500/10\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]\"\n\t\t\t\t\t><path d=\"m17 2 4 4-4 4\"></path><path d=\"M3 11v-1a4 4 0 0 1 4-4h14\"></path><path\n\t\t\t\t\t\td=\"m7 22-4-4 4-4\"></path><path d=\"M21 13v1a4 4 0 0 1-4 4H3\"></path></svg\n\t\t\t\t>\n\t\t\t</div>\n\t\t\t<number-flow id=\"vanilla-example-activity-reposts\" data-will-change></number-flow>\n\t\t</button>\n\t</div>\n\t<div class=\"flex-1\">\n\t\t<button\n\t\t\tid=\"vanilla-example-activity-like\"\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-pink-500\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-pink-500/10\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\tid=\"vanilla-example-activity-heart\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-[65] spring-duration-300 transition-transform group-active:scale-[80%]\"\n\t\t\t\t\t><path\n\t\t\t\t\t\td=\"M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z\"\n\t\t\t\t\t></path></svg\n\t\t\t\t>\n\t\t\t</div>\n\t\t\t<number-flow id=\"vanilla-example-activity-likes\" data-will-change></number-flow>\n\t\t</button>\n\t</div>\n\t<div class=\"min-[30rem]:flex-1 max-[24rem]:hidden flex shrink-0 items-center gap-1.5\">\n\t\t<button\n\t\t\tid=\"vanilla-example-activity-bookmark\"\n\t\t\tclass=\"group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-blue-500\"\n\t\t>\n\t\t\t<div\n\t\t\t\tclass=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-blue-500/10\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\tid=\"vanilla-example-activity-ribbon\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]\"\n\t\t\t\t\t><path d=\"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z\"></path></svg\n\t\t\t\t>\n\t\t\t</div>\n\t\t\t<number-flow\n\t\t\t\tid=\"vanilla-example-activity-bookmarks\"\n\t\t\t\tclass=\"max-[30rem]:hidden\"\n\t\t\t\tdata-will-change></number-flow>\n\t\t</button>\n\t</div>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\tviewBox=\"0 0 24 24\"\n\t\tfill=\"none\"\n\t\tstroke=\"currentColor\"\n\t\tstroke-width=\"2\"\n\t\tstroke-linecap=\"round\"\n\t\tstroke-linejoin=\"round\"\n\t\tclass=\"~size-4/5 shrink-0\"\n\t\t><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"></path><polyline points=\"16 6 12 2 8 6\"\n\t\t></polyline><line x1=\"12\" x2=\"12\" y1=\"2\" y2=\"15\"></line></svg\n\t>\n</div>\n<script>\n\t// @ts-nocheck\n\timport { continuous } from 'number-flow'\n\timport { $views, $reposts, $likes, $bookmarks } from '../stores'\n\timport { onReady } from '@/lib/dom'\n\n\tconst format = {\n\t\tnotation: 'compact',\n\t\tcompactDisplay: 'short',\n\t\troundingMode: 'trunc'\n\t}\n\n\tonReady(() => {\n\t\tconst views = document.getElementById('vanilla-example-activity-views')\n\t\tviews.format = format\n\t\tviews.plugins = [continuous]\n\t\tconst unsubscribeViews = $views.subscribe(({ count }) => {\n\t\t\tviews.update(count)\n\t\t})\n\n\t\tconst repost = document.getElementById('vanilla-example-activity-repost')\n\t\trepost.addEventListener('click', $reposts.toggle)\n\t\tconst reposts = document.getElementById('vanilla-example-activity-reposts')\n\t\treposts.format = format\n\t\treposts.plugins = [continuous]\n\t\tconst unsubscribeReposts = $reposts.subscribe(({ count, hasIncremented }) => {\n\t\t\trepost.classList.toggle('text-emerald-500', hasIncremented)\n\t\t\treposts.update(count)\n\t\t})\n\n\t\tconst like = document.getElementById('vanilla-example-activity-like')\n\t\tlike.addEventListener('click', $likes.toggle)\n\t\tconst heart = document.getElementById('vanilla-example-activity-heart')\n\t\tconst likes = document.getElementById('vanilla-example-activity-likes')\n\t\tlikes.format = format\n\t\tlikes.plugins = [continuous]\n\t\tconst unsubscribeLikes = $likes.subscribe(({ count, hasIncremented }) => {\n\t\t\tlike.classList.toggle('text-pink-500', hasIncremented)\n\t\t\theart.classList.toggle('fill-current', hasIncremented)\n\t\t\tlikes.update(count)\n\t\t})\n\n\t\tconst bookmark = document.getElementById('vanilla-example-activity-bookmark')\n\t\tbookmark.addEventListener('click', $bookmarks.toggle)\n\t\tconst ribbon = document.getElementById('vanilla-example-activity-ribbon')\n\t\tconst bookmarks = document.getElementById('vanilla-example-activity-bookmarks')\n\t\tbookmarks.format = format\n\t\tbookmarks.plugins = [continuous]\n\t\tconst unsubscribeBookmarks = $bookmarks.subscribe(({ count, hasIncremented }) => {\n\t\t\tbookmark.classList.toggle('text-blue-500', hasIncremented)\n\t\t\tribbon.classList.toggle('fill-current', hasIncremented)\n\t\t\tbookmarks.update(count)\n\t\t})\n\n\t\treturn () => {\n\t\t\tunsubscribeViews()\n\t\t\trepost.removeEventListener('click', $reposts.toggle)\n\t\t\tunsubscribeReposts()\n\t\t\tlike.removeEventListener('click', $likes.toggle)\n\t\t\tunsubscribeLikes()\n\t\t\tbookmark.removeEventListener('click', $bookmarks.toggle)\n\t\t\tunsubscribeBookmarks()\n\t\t}\n\t})\n</script>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/vanilla/index.astro",
    "content": "---\nimport Component from './Component.astro'\n---\n\n<div class=\"~px-0/16 w-full\"><Component /></div>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/vue/Component.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { type Format, continuous } from '@number-flow/vue'\nimport { Bookmark, ChartNoAxesColumn, Heart, Repeat, Share } from 'lucide-vue-next'\n\nconst format: Format = {\n\tnotation: 'compact',\n\tcompactDisplay: 'short',\n\troundingMode: 'trunc'\n}\n\nconst { likes, reposts, views, bookmarks, liked, reposted, bookmarked } = defineProps<{\n\tlikes: number\n\treposts: number\n\tviews: number\n\tbookmarks: number\n\tliked: boolean\n\treposted: boolean\n\tbookmarked: boolean\n}>()\n\nconst emit = defineEmits<{\n\t(e: 'like'): void\n\t(e: 'repost'): void\n\t(e: 'bookmark'): void\n}>()\n</script>\n\n<template>\n\t<div class=\"flex w-full select-none items-center text-zinc-600 dark:text-zinc-300\">\n\t\t<div class=\"flex flex-1 items-center gap-1.5\">\n\t\t\t<ChartNoAxesColumn absoluteStrokeWidth class=\"~size-4/5\" />\n\t\t\t<NumberFlow willChange :plugins=\"[continuous]\" :value=\"views\" locales=\"en-US\" :format />\n\t\t</div>\n\t\t<div class=\"flex-1\">\n\t\t\t<button\n\t\t\t\t:class=\"[\n\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-emerald-500',\n\t\t\t\t\treposted && 'text-emerald-500'\n\t\t\t\t]\"\n\t\t\t\t@click=\"emit('repost')\"\"\n\t\t\t>\n\t\t\t\t<div class=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-emerald-500/10\">\n\t\t\t\t\t<Repeat\n\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\tclass=\"~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<NumberFlow willChange :plugins=\"[continuous]\" :value=\"reposts\" locales=\"en-US\" :format />\n\t\t\t</button>\n\t\t</div>\n\t\t<div class=\"flex-1\">\n\t\t\t<button\n\t\t\t\t:class=\"[\n\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-pink-500',\n\t\t\t\t\tliked && 'text-pink-500'\n\t\t\t\t]\"\n\t\t\t\t@click=\"emit('like')\"\n\t\t\t>\n\t\t\t\t<div class=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-pink-500/10\">\n\t\t\t\t\t<Heart\n\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\t:class=\"[\n\t\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-[65] spring-duration-300 transition-transform group-active:scale-[80%]',\n\t\t\t\t\t\t\tliked && 'fill-current'\n\t\t\t\t\t\t]\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<NumberFlow willChange :plugins=\"[continuous]\" :value=\"likes\" locales=\"en-US\" :format />\n\t\t\t</button>\n\t\t</div>\n\t\t<div class=\"flex shrink-0 min-[30rem]:flex-1 items-center gap-1.5 max-[24rem]:hidden\">\n\t\t\t<button\n\t\t\t\t:class=\"[\n\t\t\t\t\t'group flex items-center gap-1.5 pr-1.5 transition-[color] hover:text-blue-500',\n\t\t\t\t\tbookmarked && 'text-blue-500'\n\t\t\t\t]\"\n\t\t\t\t@click=\"emit('bookmark')\"\n\t\t\t>\n\t\t\t\t<div class=\"relative before:absolute before:-inset-2.5 before:rounded-full before:transition-[background-color] before:group-hover:bg-blue-500/10\">\n\t\t\t\t\t<Bookmark\n\t\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\t\t:class=\"[\n\t\t\t\t\t\t\t'~size-4/5 group-active:spring-duration-[25] spring-bounce-50 spring-duration-300 transition-transform group-active:scale-[85%]',\n\t\t\t\t\t\t\tbookmarked && 'fill-current'\n\t\t\t\t\t\t]\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<NumberFlow class=\"max-[30rem]:hidden\" willChange :plugins=\"[continuous]\" :value=\"bookmarks\" locales=\"en-US\" :format />\n\t\t\t</button>\n\t\t</div>\n\t\t<Share absoluteStrokeWidth class=\"~size-4/5 shrink-0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Activity/vue/index.vue",
    "content": "<script setup lang=\"ts\">\nimport Component from './Component.vue'\nimport { useStore } from '@nanostores/vue'\nimport { $bookmarks, $likes, $reposts, $views } from '../stores'\n\nconst reposts = useStore($reposts)\nconst bookmarks = useStore($bookmarks)\nconst likes = useStore($likes)\nconst views = useStore($views)\n</script>\n\n<template>\n\t<Component\n\t\tclass=\"~px-0/16\"\n\t\t:likes=\"likes.count\"\n\t\t@like=\"$likes.toggle\"\n\t\t:liked=\"likes.hasIncremented\"\n\t\t:reposts=\"reposts.count\"\n\t\t@repost=\"$reposts.toggle\"\n\t\t:reposted=\"reposts.hasIncremented\"\n\t\t:bookmarks=\"bookmarks.count\"\n\t\t@bookmark=\"$bookmarks.toggle\"\n\t\t:bookmarked=\"bookmarks.hasIncremented\"\n\t\t:views=\"views.count\"\n\t/>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_ColoredTrends/Example.tsx",
    "content": "import NumberFlow, { NumberFlowElement } from '@number-flow/react'\nimport * as React from 'react'\n\ntype Props = {\n\tvalue: number\n}\n\nexport default function PriceWithColoredTrend({ value }: Props) {\n\tconst ref = React.useRef<NumberFlowElement>(null)\n\n\tconst prevValue = React.useRef(value)\n\tReact.useEffect(() => {\n\t\tif (value > prevValue.current)\n\t\t\tref.current?.animate(\n\t\t\t\t{ color: ['unset', '#34d399', 'unset'] },\n\t\t\t\t{ easing: 'ease', duration: 300 }\n\t\t\t)\n\t\telse if (value < prevValue.current)\n\t\t\tref.current?.animate(\n\t\t\t\t{ color: ['unset', '#f87171', 'unset'] },\n\t\t\t\t{ easing: 'ease', duration: 300 }\n\t\t\t)\n\n\t\treturn () => {\n\t\t\tprevValue.current = value\n\t\t}\n\t}, [value])\n\n\treturn (\n\t\t<NumberFlow\n\t\t\tref={ref}\n\t\t\tvalue={value}\n\t\t\tlocales=\"en-US\"\n\t\t\tformat={{\n\t\t\t\tstyle: 'currency',\n\t\t\t\tcurrency: 'USD'\n\t\t\t}}\n\t\t\tclassName=\"~text-3xl/4xl font-semibold\"\n\t\t/>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_ColoredTrends/index.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport useCycle from '@/hooks/useCycle'\nimport Example from './Example'\nimport type { Rename } from '@/lib/types'\n\nconst NUMBERS = [12398.432, -3243.6, 543.2]\n\nexport default function DemoHOC({\n\tchildren,\n\t...rest\n}: Rename<Omit<DemoProps, 'children'>, 'code', 'children'>) {\n\tconst [value, cycleValue] = useCycle(NUMBERS)\n\n\tfunction onClick() {\n\t\tcycleValue()\n\t}\n\n\treturn (\n\t\t<Demo {...rest} code={children} onClick={onClick}>\n\t\t\t<Example value={value} />\n\t\t</Demo>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/index.astro",
    "content": "---\nimport Activity from '.'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\n// Could probably do a client:only={framework} but then we'd lose SSR:\nimport React from './react'\nimport react from './react/Component.tsx?raw'\nimport Vue from './vue/index.vue'\nimport vue from './vue/Component.vue?raw'\nimport Svelte from './svelte/index.svelte'\nimport svelte from './svelte/Component.svelte?raw'\nimport Vanilla from './vanilla/index.astro'\nimport vanilla from './vanilla/index.astro?raw'\n---\n\n<Activity client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match vue><Vue client:load /></Match>\n\t<Match svelte><Svelte client:load /></Match>\n\t<Match vanilla><Vanilla /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t\t<Code code={vue} slot=\"vue\" lang=\"vue\" />\n\t\t<Code code={svelte} slot=\"svelte\" lang=\"svelte\" />\n\t\t<Code code={vanilla} slot=\"vanilla\" lang=\"html\" />\n\t</Match>\n</Activity>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/index.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport { $inView } from './stores'\n\nexport default function Activity(props: DemoProps) {\n\treturn <Demo {...props} onIntersect={({ isIntersecting }) => $inView.set(isIntersecting)} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/react/Component.tsx",
    "content": "import NumberFlow, { NumberFlowGroup } from '@number-flow/react'\n\ntype Props = {\n\tseconds: number\n}\n\nexport default function Countdown({ seconds }: Props) {\n\tconst hh = Math.floor(seconds / 3600)\n\tconst mm = Math.floor((seconds % 3600) / 60)\n\tconst ss = seconds % 60\n\n\treturn (\n\t\t<NumberFlowGroup>\n\t\t\t<div\n\t\t\t\tstyle={{ fontVariantNumeric: 'tabular-nums' }}\n\t\t\t\tclassName=\"~text-3xl/4xl flex items-baseline font-semibold\"\n\t\t\t>\n\t\t\t\t<NumberFlow trend={-1} value={hh} format={{ minimumIntegerDigits: 2 }} />\n\t\t\t\t<NumberFlow\n\t\t\t\t\tprefix=\":\"\n\t\t\t\t\ttrend={-1}\n\t\t\t\t\tvalue={mm}\n\t\t\t\t\tdigits={{ 1: { max: 5 } }}\n\t\t\t\t\tformat={{ minimumIntegerDigits: 2 }}\n\t\t\t\t/>\n\t\t\t\t<NumberFlow\n\t\t\t\t\tprefix=\":\"\n\t\t\t\t\ttrend={-1}\n\t\t\t\t\tvalue={ss}\n\t\t\t\t\tdigits={{ 1: { max: 5 } }}\n\t\t\t\t\tformat={{ minimumIntegerDigits: 2 }}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</NumberFlowGroup>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/react/index.tsx",
    "content": "import Component from './Component'\nimport { useStore } from '@nanostores/react'\nimport { $seconds } from '../stores'\n\nexport default function () {\n\tconst seconds = useStore($seconds)\n\n\treturn <Component seconds={seconds} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/stores.ts",
    "content": "import { atom, onMount, type ReadableAtom } from 'nanostores'\nimport { hydratable } from '@/lib/stores'\n\nexport const $inView = atom(false)\n\nfunction countdownable(\n\tinitialValue: number,\n\tactive: ReadableAtom<boolean>,\n\trate = 1,\n\teveryMs = 1000\n) {\n\tconst state = atom(initialValue)\n\n\tonMount(state, () => {\n\t\tlet timeout: NodeJS.Timeout | null = null\n\t\tconst unsubscribe = active.subscribe((active) => {\n\t\t\tif (timeout != null) clearInterval(timeout)\n\t\t\tif (!active) return\n\t\t\ttimeout = setInterval(() => {\n\t\t\t\tstate.set(state.get() - rate)\n\t\t\t}, everyMs)\n\t\t})\n\t\treturn () => {\n\t\t\tif (timeout != null) clearInterval(timeout)\n\t\t\tunsubscribe()\n\t\t}\n\t})\n\n\treturn state\n}\n\nexport const $seconds = hydratable(countdownable(3600, $inView))\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/svelte/Component.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'\n\n\ttype Props = {\n\t\tseconds: number\n\t}\n\n\tconst { seconds }: Props = $props()\n\n\tconst hh = $derived(Math.floor(seconds / 3600))\n\tconst mm = $derived(Math.floor((seconds % 3600) / 60))\n\tconst ss = $derived(seconds % 60)\n</script>\n\n<NumberFlowGroup>\n\t<div\n\t\tstyle=\"font-variant-numeric: tabular-nums\"\n\t\tclass=\"~text-3xl/4xl flex items-baseline font-semibold\"\n\t>\n\t\t<NumberFlow trend={-1} value={hh} format={{ minimumIntegerDigits: 2 }} />\n\t\t<NumberFlow\n\t\t\tprefix=\":\"\n\t\t\ttrend={-1}\n\t\t\tvalue={mm}\n\t\t\tdigits={{ 1: { max: 5 } }}\n\t\t\tformat={{ minimumIntegerDigits: 2 }}\n\t\t/>\n\t\t<NumberFlow\n\t\t\tprefix=\":\"\n\t\t\ttrend={-1}\n\t\t\tvalue={ss}\n\t\t\tdigits={{ 1: { max: 5 } }}\n\t\t\tformat={{ minimumIntegerDigits: 2 }}\n\t\t/>\n\t</div>\n</NumberFlowGroup>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/svelte/index.svelte",
    "content": "<script lang=\"ts\">\n\timport Component from './Component.svelte'\n\timport { $seconds as seconds } from '../stores'\n</script>\n\n<Component seconds={$seconds} />\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/vanilla/index.astro",
    "content": "<number-flow-group\n\tstyle=\"font-variant-numeric: tabular-nums\"\n\tclass=\"~text-3xl/4xl flex items-baseline font-semibold\"\n>\n\t<number-flow id=\"vanilla-example-countdown-hours\"></number-flow>\n\t<number-flow id=\"vanilla-example-countdown-minutes\"></number-flow>\n\t<number-flow id=\"vanilla-example-countdown-seconds\"></number-flow>\n</number-flow-group>\n\n<script>\n\t// @ts-nocheck\n\timport 'number-flow'\n\timport 'number-flow/group'\n\timport { $seconds } from '../stores'\n\timport { onReady } from '@/lib/dom'\n\n\tonReady(() => {\n\t\tconst hours = document.getElementById('vanilla-example-countdown-hours')\n\t\tconst minutes = document.getElementById('vanilla-example-countdown-minutes')\n\t\tconst seconds = document.getElementById('vanilla-example-countdown-seconds')\n\n\t\thours.trend = -1\n\t\thours.format = { minimumIntegerDigits: 2 }\n\n\t\tminutes.numberPrefix = ':'\n\t\tminutes.trend = -1\n\t\tminutes.digits = { 1: { max: 5 } }\n\t\tminutes.format = { minimumIntegerDigits: 2 }\n\n\t\tseconds.numberPrefix = ':'\n\t\tseconds.trend = -1\n\t\tseconds.digits = { 1: { max: 5 } }\n\t\tseconds.format = { minimumIntegerDigits: 2 }\n\n\t\treturn $seconds.subscribe((s) => {\n\t\t\tconst hh = Math.floor(s / 3600)\n\t\t\tconst mm = Math.floor((s % 3600) / 60)\n\t\t\tconst ss = s % 60\n\n\t\t\thours.update(hh)\n\t\t\tminutes.update(mm)\n\t\t\tseconds.update(ss)\n\t\t})\n\t})\n</script>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/vue/Component.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { NumberFlowGroup } from '@number-flow/vue'\nimport { computed } from 'vue'\n\nconst { seconds } = defineProps<{ seconds: number }>()\n\nconst hh = computed(() => Math.floor(seconds / 3600))\nconst mm = computed(() => Math.floor((seconds % 3600) / 60))\nconst ss = computed(() => seconds % 60)\n</script>\n\n<template>\n\t<NumberFlowGroup>\n\t\t<div\n\t\t\tstyle=\"font-variant-numeric: tabular-nums\"\n\t\t\tclass=\"~text-3xl/4xl flex items-baseline font-semibold\"\n\t\t>\n\t\t\t<NumberFlow :trend=\"-1\" :value=\"hh\" :format=\"{ minimumIntegerDigits: 2 }\" />\n\t\t\t<NumberFlow\n\t\t\t\tprefix=\":\"\n\t\t\t\t:trend=\"-1\"\n\t\t\t\t:value=\"mm\"\n\t\t\t\t:digits=\"{ 1: { max: 5 } }\"\n\t\t\t\t:format=\"{ minimumIntegerDigits: 2 }\"\n\t\t\t/>\n\t\t\t<NumberFlow\n\t\t\t\tprefix=\":\"\n\t\t\t\t:trend=\"-1\"\n\t\t\t\t:value=\"ss\"\n\t\t\t\t:digits=\"{ 1: { max: 5 } }\"\n\t\t\t\t:format=\"{ minimumIntegerDigits: 2 }\"\n\t\t\t/>\n\t\t</div>\n\t</NumberFlowGroup>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Countdown/vue/index.vue",
    "content": "<script setup lang=\"ts\">\nimport Component from './Component.vue'\nimport { useStore } from '@nanostores/vue'\nimport { $seconds } from '../stores'\n\nconst seconds = useStore($seconds)\n</script>\n\n<template>\n\t<Component :seconds />\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Examples.astro",
    "content": "---\nimport DocsLayout from '@/layouts/Docs.astro'\nimport type { MDXLayoutProps } from 'astro'\nimport { ArrowUpRight } from 'lucide-react'\nimport { FRAMEWORKS, getFramework } from '@/lib/framework'\n\ntype Props = MDXLayoutProps<{}>\n\nconst { sandbox, pkgName } = FRAMEWORKS[getFramework(Astro.params)!]\n---\n\n<DocsLayout title={`${pkgName} Examples`} description={`Official ${pkgName} templates to get you started.`}>\n\t<header slot=\"hero\" class=\"~mb-12/24 ~mt-4/6 container max-w-2xl text-center\">\n\t\t<h1 class=\"~text-3xl/5xl font-semibold tracking-tight\">Examples</h1>\n\t\t<div class=\"~text-base/lg ~mt-4/6 prose prose-muted dark:prose-invert text-pretty\">\n\t\t\t<p>Official templates to get you started.</p>\n\t\t</div>\n\t\t<div class=\"~mt-6/8 flex w-full flex-wrap items-stretch justify-center gap-3\">\n\t\t\t<a href={sandbox} target=\"_blank\" class=\"btn btn-primary\">\n\t\t\t\tOpen sandbox\n\t\t\t\t<ArrowUpRight className=\"size-4\" />\n\t\t\t</a>\n\t\t\t<a\n\t\t\t\thref=\"https://github.com/barvian/number-flow/discussions/new?category=example-request\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\tclass=\"btn btn-secondary\"\n\t\t\t>\n\t\t\t\tRequest an example\n\t\t\t\t<ArrowUpRight className=\"size-4\" />\n\t\t\t</a>\n\t\t</div>\n\t</header>\n\t<slot />\n</DocsLayout>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/index.astro",
    "content": "---\nimport Group from '.'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\n// Could probably do a client:only={framework} but then we'd lose SSR:\nimport React from './react'\nimport react from './react/Component.tsx?raw'\nimport Vue from './vue/index.vue'\nimport vue from './vue/Component.vue?raw'\nimport Svelte from './svelte/index.svelte'\nimport svelte from './svelte/Component.svelte?raw'\nimport Vanilla from './vanilla/index.astro'\nimport vanilla from './vanilla/index.astro?raw'\n---\n\n<Group client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match vue><Vue client:load /></Match>\n\t<Match svelte><Svelte client:load /></Match>\n\t<Match vanilla><Vanilla /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t\t<Code code={vue} slot=\"vue\" lang=\"vue\" />\n\t\t<Code code={svelte} slot=\"svelte\" lang=\"svelte\" />\n\t\t<Code code={vanilla} slot=\"vanilla\" lang=\"html\" />\n\t</Match>\n</Group>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/index.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport { $number, $diff } from './stores'\n\nexport default function Group(props: DemoProps) {\n\tfunction onClick() {\n\t\t$number.cycle()\n\t\t$diff.cycle()\n\t}\n\n\treturn <Demo {...props} onClick={onClick} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/react/Component.tsx",
    "content": "import NumberFlow, { NumberFlowGroup } from '@number-flow/react'\nimport clsx from 'clsx/lite'\n\ntype Props = {\n\tvalue: number\n\tdiff: number\n}\n\nexport default function PriceWithDiff({ value, diff }: Props) {\n\treturn (\n\t\t<NumberFlowGroup>\n\t\t\t<div className=\"flex items-center gap-4 font-semibold\">\n\t\t\t\t<NumberFlow\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tformat={{ style: 'currency', currency: 'USD' }}\n\t\t\t\t\tclassName=\"~text-2xl/4xl\"\n\t\t\t\t/>\n\t\t\t\t<NumberFlow\n\t\t\t\t\tvalue={diff}\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tformat={{ style: 'percent', maximumFractionDigits: 2, signDisplay: 'always' }}\n\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\t'~text-lg/2xl transition-colors duration-300',\n\t\t\t\t\t\tdiff < 0 ? 'text-red-500' : 'text-emerald-500'\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</NumberFlowGroup>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/react/index.tsx",
    "content": "import { useStore } from '@nanostores/react'\nimport Component from './Component'\nimport { $diff, $number } from '../stores'\n\nexport default function () {\n\tconst number = useStore($number)\n\tconst diff = useStore($diff)\n\treturn <Component value={number} diff={diff} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/stores.ts",
    "content": "import { cyclable, hydratable } from '@/lib/stores'\n\nexport const $number = hydratable(cyclable(124.23, 41.75, 2125.95))\nexport const $diff = hydratable(cyclable(0.0564, -0.3912, 0.0029))\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/svelte/Component.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'\n\timport clsx from 'clsx/lite'\n\n\tconst {\n\t\tvalue,\n\t\tdiff\n\t}: {\n\t\tvalue: number\n\t\tdiff: number\n\t} = $props()\n</script>\n\n<NumberFlowGroup>\n\t<div class=\"flex items-center gap-4 font-semibold\">\n\t\t<NumberFlow\n\t\t\t{value}\n\t\t\tlocales=\"en-US\"\n\t\t\tformat={{ style: 'currency', currency: 'USD' }}\n\t\t\tclass=\"~text-2xl/4xl\"\n\t\t/>\n\t\t<NumberFlow\n\t\t\tvalue={diff}\n\t\t\tlocales=\"en-US\"\n\t\t\tformat={{ style: 'percent', maximumFractionDigits: 2, signDisplay: 'always' }}\n\t\t\tclass={clsx(\n\t\t\t\t'~text-lg/2xl transition-colors duration-300',\n\t\t\t\tdiff < 0 ? 'text-red-500' : 'text-emerald-500'\n\t\t\t)}\n\t\t/>\n\t</div>\n</NumberFlowGroup>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/svelte/index.svelte",
    "content": "<script lang=\"ts\">\n\timport Component from './Component.svelte'\n\timport { $number as number, $diff as diff } from '../stores'\n</script>\n\n<Component value={$number} diff={$diff} />\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/vanilla/index.astro",
    "content": "<number-flow-group class=\"flex items-center gap-4 font-semibold\">\n\t<number-flow id=\"vanilla-example-group-number\" class=\"~text-2xl/4xl\"></number-flow>\n\t<number-flow id=\"vanilla-example-group-diff\" class=\"~text-lg/2xl transition-colors duration-300\"\n\t></number-flow>\n</number-flow-group>\n\n<script>\n\t// @ts-nocheck\n\timport 'number-flow'\n\timport 'number-flow/group' // note the separate import\n\timport { $number, $diff } from '../stores'\n\timport { onReady } from '@/lib/dom'\n\n\tonReady(() => {\n\t\tconst number = document.getElementById('vanilla-example-group-number')\n\t\tnumber.locales = 'en-US'\n\t\tnumber.format = { style: 'currency', currency: 'USD' }\n\n\t\tconst diff = document.getElementById('vanilla-example-group-diff')\n\t\tdiff.locales = 'en-US'\n\t\tdiff.format = { style: 'percent', maximumFractionDigits: 2, signDisplay: 'always' }\n\n\t\tconst unsubscribeNumber = $number.subscribe((value) => number.update(value))\n\t\tconst unsubscribeDiff = $diff.subscribe((value) => {\n\t\t\tdiff.classList.toggle('text-red-500', value < 0)\n\t\t\tdiff.classList.toggle('text-emerald-500', value >= 0)\n\t\t\tdiff.update(value)\n\t\t})\n\t\treturn () => {\n\t\t\tunsubscribeNumber()\n\t\t\tunsubscribeDiff()\n\t\t}\n\t})\n</script>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/vue/Component.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { NumberFlowGroup } from '@number-flow/vue'\nconst { value, diff } = defineProps<{\n\tvalue: number\n\tdiff: number\n}>()\n</script>\n\n<template>\n\t<NumberFlowGroup>\n\t\t<div class=\"flex items-center gap-4 font-semibold\">\n\t\t\t<NumberFlow\n\t\t\t\t:value\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\t:format=\"{ style: 'currency', currency: 'USD' }\"\n\t\t\t\tclass=\"~text-2xl/4xl\"\n\t\t\t/>\n\t\t\t<NumberFlow\n\t\t\t\t:value=\"diff\"\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\t:format=\"{ style: 'percent', maximumFractionDigits: 2, signDisplay: 'always' }\"\n\t\t\t\t:class=\"[\n\t\t\t\t\t'~text-lg/2xl transition-colors duration-300',\n\t\t\t\t\tdiff < 0 ? 'text-red-500' : 'text-emerald-500'\n\t\t\t\t]\"\n\t\t\t/>\n\t\t</div>\n\t</NumberFlowGroup>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Group/vue/index.vue",
    "content": "<script setup lang=\"ts\">\nimport Comp from './Component.vue'\nimport { useStore } from '@nanostores/vue'\nimport { $number, $diff } from '../stores'\n\nconst number = useStore($number)\nconst diff = useStore($diff)\n</script>\n\n<template>\n\t<Comp :value=\"number\" :diff=\"diff\" />\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/index.astro",
    "content": "---\nexport { type DemoProps as Props } from '@/components/Demo'\n\nimport Demo from '@/components/Demo'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\nimport React from './react'\nimport react from './react/Component.tsx?raw'\nimport Vue from './vue/index.vue'\nimport vue from './vue/Component.vue?raw'\nimport Svelte from './svelte/index.svelte'\nimport svelte from './svelte/Component.svelte?raw'\n---\n\n<Demo {...Astro.props} className=\"font-mac-ui\" client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match vue><Vue client:load /></Match>\n\t<Match svelte><Svelte client:load /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t\t<Code code={vue} slot=\"vue\" lang=\"vue\" />\n\t\t<Code code={svelte} slot=\"svelte\" lang=\"svelte\" />\n\t</Match>\n</Demo>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/react/Component.tsx",
    "content": "import NumberFlow from '@number-flow/react'\nimport clsx from 'clsx/lite'\nimport { Minus, Plus } from 'lucide-react'\nimport * as React from 'react'\n\ntype Props = {\n\tvalue?: number\n\tmin?: number\n\tmax?: number\n\tonChange?: (value: number) => void\n}\n\nexport default function Input({ value = 0, min = -Infinity, max = Infinity, onChange }: Props) {\n\tconst defaultValue = React.useRef(value)\n\tconst inputRef = React.useRef<HTMLInputElement>(null)\n\tconst [animated, setAnimated] = React.useState(true)\n\t// Hide the caret during transitions so you can't see it shifting around:\n\tconst [showCaret, setShowCaret] = React.useState(true)\n\n\tconst handleInput: React.InputEventHandler<HTMLInputElement> = ({ currentTarget: el }) => {\n\t\tsetAnimated(false)\n\t\tlet next = value\n\t\tif (el.value === '') {\n\t\t\tnext = defaultValue.current\n\t\t} else {\n\t\t\tconst num = el.valueAsNumber\n\t\t\tif (!isNaN(num) && min <= num && num <= max) next = num\n\t\t}\n\t\t// Manually update the input.value in case the number stays the same e.g. 09 == 9\n\t\tel.value = String(next)\n\t\tonChange?.(next)\n\t}\n\n\tconst handlePointerDown = (diff: number) => (event: React.PointerEvent<HTMLButtonElement>) => {\n\t\tsetAnimated(true)\n\t\tif (event.pointerType === 'mouse') {\n\t\t\tevent?.preventDefault()\n\t\t\tinputRef.current?.focus()\n\t\t}\n\t\tconst newVal = Math.min(Math.max(value + diff, min), max)\n\t\tonChange?.(newVal)\n\t}\n\n\treturn (\n\t\t<div className=\"group flex items-stretch rounded-md text-3xl font-semibold ring ring-zinc-200 transition-[box-shadow] focus-within:ring-2 focus-within:ring-blue-500 dark:ring-zinc-800\">\n\t\t\t<button\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\ttabIndex={-1}\n\t\t\t\tclassName=\"flex items-center pl-[.5em] pr-[.325em]\"\n\t\t\t\tdisabled={min != null && value <= min}\n\t\t\t\tonPointerDown={handlePointerDown(-1)}\n\t\t\t>\n\t\t\t\t<Minus className=\"size-4\" absoluteStrokeWidth strokeWidth={3.5} />\n\t\t\t</button>\n\t\t\t<div className=\"relative grid items-center justify-items-center text-center [grid-template-areas:'overlap'] *:[grid-area:overlap]\">\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tclassName={clsx(\n\t\t\t\t\t\tshowCaret ? 'caret-primary' : 'caret-transparent',\n\t\t\t\t\t\t'spin-hide w-[1.5em] bg-transparent py-2 text-center font-[inherit] text-transparent outline-none'\n\t\t\t\t\t)}\n\t\t\t\t\t// Make sure to disable kerning, to match NumberFlow:\n\t\t\t\t\tstyle={{ fontKerning: 'none' }}\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tmin={min}\n\t\t\t\t\tstep={1}\n\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\tinputMode=\"numeric\"\n\t\t\t\t\tmax={max}\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tonInput={handleInput}\n\t\t\t\t/>\n\t\t\t\t<NumberFlow\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\tformat={{ useGrouping: false }}\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\tanimated={animated}\n\t\t\t\t\tonAnimationsStart={() => setShowCaret(false)}\n\t\t\t\t\tonAnimationsFinish={() => setShowCaret(true)}\n\t\t\t\t\tclassName=\"pointer-events-none\"\n\t\t\t\t\twillChange\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\ttabIndex={-1}\n\t\t\t\tclassName=\"flex items-center pl-[.325em] pr-[.5em]\"\n\t\t\t\tdisabled={max != null && value >= max}\n\t\t\t\tonPointerDown={handlePointerDown(1)}\n\t\t\t>\n\t\t\t\t<Plus className=\"size-4\" absoluteStrokeWidth strokeWidth={3.5} />\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/react/index.tsx",
    "content": "import Component from './Component'\nimport * as React from 'react'\n\nexport default function Input() {\n\tconst [value, setValue] = React.useState(0)\n\treturn <Component value={value} min={0} max={99} onChange={setValue} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/svelte/Component.svelte",
    "content": "<script lang=\"ts\">\n\timport NumberFlow from '@number-flow/svelte'\n\timport clsx from 'clsx/lite'\n\timport { Minus, Plus } from 'lucide-svelte'\n\n\tlet {\n\t\tmin = 0,\n\t\tvalue = $bindable(0),\n\t\tmax = 99\n\t}: { min?: number; value?: number; max?: number } = $props()\n\tconst defaultValue = value\n\n\tlet input: HTMLInputElement\n\n\tlet animated = $state(true)\n\t// Hide the caret during transitions so you can't see it shifting around:\n\tlet showCaret = $state(true)\n\n\tfunction handleInput() {\n\t\tanimated = false\n\t\tlet next = value\n\t\tif (input.value === '') {\n\t\t\tnext = defaultValue\n\t\t} else {\n\t\t\tconst num = input.valueAsNumber\n\t\t\tif (!isNaN(num) && min <= num && num <= max) next = num\n\t\t}\n\t\t// Manually update the input.value in case the number stays the same e.g. 09 == 9\n\t\tinput.value = String(next)\n\t\tvalue = next\n\t}\n\n\tfunction handlePointerDown(event: PointerEvent, diff: number) {\n\t\tanimated = true\n\t\tif (event.pointerType === 'mouse') {\n\t\t\tevent?.preventDefault()\n\t\t\tinput.focus()\n\t\t}\n\t\tconst newVal = Math.min(Math.max(value + diff, min), max)\n\t\tvalue = newVal\n\t}\n</script>\n\n<div\n\tclass=\"focus-within:ring-accent group flex items-stretch rounded-md text-3xl font-semibold ring ring-zinc-200 transition-[box-shadow] focus-within:ring-2 dark:ring-zinc-800\"\n>\n\t<button\n\t\taria-hidden=\"true\"\n\t\ttabindex={-1}\n\t\tclass=\"flex items-center pl-[.5em] pr-[.325em]\"\n\t\tdisabled={min != null && value <= min}\n\t\tonpointerdown={(event) => handlePointerDown(event, -1)}\n\t>\n\t\t<Minus class=\"size-4\" absoluteStrokeWidth strokeWidth=\"3.5\" />\n\t</button>\n\t<div\n\t\tclass=\"relative grid items-center justify-items-center text-center [grid-template-areas:'overlap'] *:[grid-area:overlap]\"\n\t>\n\t\t<input\n\t\t\tbind:this={input}\n\t\t\tclass={clsx(\n\t\t\t\tshowCaret ? 'caret-primary' : 'caret-transparent',\n\t\t\t\t'spin-hide w-[1.5em] bg-transparent py-2 text-center font-[inherit] text-transparent outline-none'\n\t\t\t)}\n\t\t\tstyle=\"font-kerning: none\"\n\t\t\ttype=\"number\"\n\t\t\t{min}\n\t\t\tstep=\"1\"\n\t\t\tautocomplete=\"off\"\n\t\t\tinputmode=\"numeric\"\n\t\t\t{max}\n\t\t\t{value}\n\t\t\toninput={handleInput}\n\t\t/>\n\t\t<NumberFlow\n\t\t\t{value}\n\t\t\tlocales=\"en-US\"\n\t\t\tformat={{ useGrouping: false }}\n\t\t\taria-hidden=\"true\"\n\t\t\t{animated}\n\t\t\ton:animationsstart={() => (showCaret = false)}\n\t\t\ton:animationsfinish={() => (showCaret = true)}\n\t\t\tclass=\"pointer-events-none\"\n\t\t\twillChange\n\t\t/>\n\t</div>\n\t<button\n\t\taria-hidden=\"true\"\n\t\ttabindex=\"-1\"\n\t\tclass=\"flex items-center pl-[.325em] pr-[.5em]\"\n\t\tdisabled={max != null && value >= max}\n\t\tonpointerdown={(event) => handlePointerDown(event, 1)}\n\t>\n\t\t<Plus class=\"size-4\" absoluteStrokeWidth strokeWidth=\"3.5\" />\n\t</button>\n</div>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/svelte/index.svelte",
    "content": "<script lang=\"ts\">\n\timport Comp from './Component.svelte'\n</script>\n\n<Comp />\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/vue/Component.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow from '@number-flow/vue'\nimport { Minus, Plus } from 'lucide-vue-next'\nimport { ref, useTemplateRef } from 'vue'\n\nconst { min = 0, max = 99 } = defineProps<{\n\tmin?: number\n\tmax?: number\n}>()\n\nconst modelValue = defineModel({ default: 0 })\nconst defaultValue = modelValue.value\n\nconst inputRef = useTemplateRef('input')\nconst animated = ref(true)\n// Hide the caret during transitions so you can't see it shifting around:\nconst showCaret = ref(true)\n\nfunction handleInput({ currentTarget }: Event) {\n\tconst input = currentTarget as HTMLInputElement // nicer than inputRef.value.value\n\n\tanimated.value = false\n\tlet next = modelValue.value\n\tif (input.value === '') {\n\t\tnext = defaultValue\n\t} else {\n\t\tconst num = input.valueAsNumber\n\t\tif (!isNaN(num) && min <= num && num <= max) next = num\n\t}\n\t// Manually update the input.value in case the number stays the same e.g. 09 == 9\n\tinput.value = String(next)\n\tmodelValue.value = next\n}\n\nfunction handlePointerDown(event: PointerEvent, diff: number) {\n\tanimated.value = true\n\tif (event.pointerType === 'mouse') {\n\t\tevent?.preventDefault()\n\t\tinputRef.value?.focus()\n\t}\n\tconst newVal = Math.min(Math.max(modelValue.value + diff, min), max)\n\tmodelValue.value = newVal\n}\n</script>\n\n<template>\n\t<div\n\t\tclass=\"focus-within:ring-accent group flex items-stretch rounded-md text-3xl font-semibold ring ring-zinc-200 transition-[box-shadow] focus-within:ring-2 dark:ring-zinc-800\"\n\t>\n\t\t<button\n\t\t\taria-hidden=\"true\"\n\t\t\ttabindex=\"{-1}\"\n\t\t\tclass=\"flex items-center pl-[.5em] pr-[.325em]\"\n\t\t\t:disabled=\"min != null && modelValue <= min\"\n\t\t\t@pointerdown=\"handlePointerDown($event, -1)\"\n\t\t>\n\t\t\t<Minus class=\"size-4\" absoluteStrokeWidth strokeWidth=\"3.5\" />\n\t\t</button>\n\t\t<div\n\t\t\tclass=\"relative grid items-center justify-items-center text-center [grid-template-areas:'overlap'] *:[grid-area:overlap]\"\n\t\t>\n\t\t\t<input\n\t\t\t\tref=\"input\"\n\t\t\t\t:class=\"[\n\t\t\t\t\tshowCaret ? 'caret-primary' : 'caret-transparent',\n\t\t\t\t\t'spin-hide w-[1.5em] bg-transparent py-2 text-center font-[inherit] text-transparent outline-none'\n\t\t\t\t]\"\n\t\t\t\t:style=\"{ fontKerning: 'none' /* match NumberFlow */ }\"\n\t\t\t\ttype=\"number\"\n\t\t\t\t:min\n\t\t\t\tstep=\"1\"\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tinputmode=\"numeric\"\n\t\t\t\t:max\n\t\t\t\t:value=\"modelValue\"\n\t\t\t\t@input=\"handleInput\"\n\t\t\t/>\n\t\t\t<NumberFlow\n\t\t\t\t:value=\"modelValue\"\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\t:format=\"{ useGrouping: false }\"\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\t:animated\n\t\t\t\t@animationsstart=\"showCaret = false\"\n\t\t\t\t@animationsfinish=\"showCaret = true\"\n\t\t\t\tclass=\"pointer-events-none\"\n\t\t\t\twillChange\n\t\t\t/>\n\t\t</div>\n\t\t<button\n\t\t\taria-hidden=\"true\"\n\t\t\ttabindex=\"-1\"\n\t\t\tclass=\"flex items-center pl-[.325em] pr-[.5em]\"\n\t\t\t:disabled=\"max != null && modelValue >= max\"\n\t\t\t@pointerdown=\"handlePointerDown($event, 1)\"\n\t\t>\n\t\t\t<Plus class=\"size-4\" absoluteStrokeWidth strokeWidth=\"3.5\" />\n\t\t</button>\n\t</div>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Input/vue/index.vue",
    "content": "<script setup lang=\"ts\">\nimport Comp from './Component.vue'\n</script>\n\n<template>\n\t<Comp />\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Motion/index.astro",
    "content": "---\nimport Group from '.'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\n// Could probably do a client:only={framework} but then we'd lose SSR:\nimport React from './react'\nimport react from './react/Component.tsx?raw'\n---\n\n<Group client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t</Match>\n</Group>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Motion/index.tsx",
    "content": "import Demo, { type DemoProps } from '@/components/Demo'\nimport { $value } from './stores'\n\nexport default function Group(props: DemoProps) {\n\treturn <Demo {...props} onClick={() => $value.cycle()} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Motion/react/Component.tsx",
    "content": "import { motion, MotionConfig } from 'motion/react'\nimport NumberFlow, { useCanAnimate } from '@number-flow/react'\nimport { ArrowUp } from 'lucide-react'\nimport clsx from 'clsx/lite'\nimport type { CSSProperties } from 'react'\n\nconst MotionNumberFlow = motion.create(NumberFlow)\nconst MotionArrowUp = motion.create(ArrowUp)\n\ntype Props = {\n\tvalue: number\n}\n\nexport default function MotionExample({ value }: Props) {\n\tconst canAnimate = useCanAnimate()\n\n\treturn (\n\t\t<MotionConfig\n\t\t\t// Disable layout animations if NumberFlow can't animate.\n\t\t\t// This worked better than setting layout={canAnimate}\n\t\t\ttransition={{\n\t\t\t\tlayout: canAnimate ? { duration: 0.9, bounce: 0, type: 'spring' } : { duration: 0 }\n\t\t\t}}\n\t\t>\n\t\t\t<motion.span\n\t\t\t\tclassName={clsx(\n\t\t\t\t\tvalue > 0 ? 'bg-emerald-400' : 'bg-red-500',\n\t\t\t\t\t'inline-flex items-center px-[0.3em] text-2xl text-white transition-colors duration-300'\n\t\t\t\t)}\n\t\t\t\tlayout\n\t\t\t\tstyle={{ borderRadius: 999 }}\n\t\t\t>\n\t\t\t\t<MotionArrowUp\n\t\t\t\t\tclassName=\"mr-0.5 size-[0.75em]\"\n\t\t\t\t\tabsoluteStrokeWidth\n\t\t\t\t\tstrokeWidth={3}\n\t\t\t\t\tlayout // undo parent\n\t\t\t\t\ttransition={{\n\t\t\t\t\t\trotate: canAnimate ? { type: 'spring', duration: 0.5, bounce: 0 } : { duration: 0 }\n\t\t\t\t\t}}\n\t\t\t\t\tanimate={{ rotate: value > 0 ? 0 : -180 }}\n\t\t\t\t\tinitial={false}\n\t\t\t\t/>\n\t\t\t\t<MotionNumberFlow\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tclassName=\"font-semibold\"\n\t\t\t\t\tformat={{ style: 'percent', maximumFractionDigits: 2 }}\n\t\t\t\t\tstyle={{ '--number-flow-mask-height': '0.3em' } as CSSProperties}\n\t\t\t\t\t// Important, see note below:\n\t\t\t\t\tlayout\n\t\t\t\t\tlayoutRoot\n\t\t\t\t/>\n\t\t\t</motion.span>\n\t\t</MotionConfig>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Motion/react/index.tsx",
    "content": "import { useStore } from '@nanostores/react'\nimport Component from './Component'\nimport { $value } from '../stores'\n\nexport default function () {\n\tconst value = useStore($value)\n\treturn <Component value={value} />\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Motion/stores.ts",
    "content": "import { cyclable, hydratable } from '@/lib/stores'\n\nexport const $value = hydratable(cyclable(0.0564, -0.3912, 0.0029))\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/index.astro",
    "content": "---\nimport Demo from '@/components/Demo'\nimport Match from '@/components/Match.astro'\nimport Code from '@/components/Code.astro'\n\n// Can't glob these b/c Astro needs the import :/\nimport React from './react'\nimport react from './react/Component.tsx?raw'\nimport Vue from './vue/index.vue'\nimport vue from './vue/Component.vue?raw'\nimport Svelte from './svelte/index.svelte'\nimport svelte from './svelte/Component.svelte?raw'\n\nimport clsx from 'clsx/lite'\nimport { getFramework } from '@/lib/framework'\nconst framework = getFramework(Astro.params)\n---\n\n<Demo className={clsx(framework !== 'svelte' && 'pt-12')} client:visible>\n\t<!-- @ts-ignore too complex -->\n\t<Match react><React client:load /></Match>\n\t<Match vue><Vue client:load /></Match>\n\t<Match svelte><Svelte client:load /></Match>\n\t<Match slot=\"code\">\n\t\t<Code code={react} slot=\"react\" lang=\"tsx\" />\n\t\t<Code code={vue} slot=\"vue\" lang=\"vue\" />\n\t\t<Code code={svelte} slot=\"svelte\" lang=\"svelte\" />\n\t</Match>\n</Demo>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/react/Component.tsx",
    "content": "import NumberFlow, { continuous } from '@number-flow/react'\nimport * as RadixSlider from '@radix-ui/react-slider'\nimport clsx from 'clsx/lite'\n\nexport default function Slider({ value, className, ...props }: RadixSlider.SliderProps) {\n\treturn (\n\t\t<RadixSlider.Root\n\t\t\t{...props}\n\t\t\tvalue={value}\n\t\t\tclassName={clsx(className, 'relative flex h-5 w-[200px] touch-none select-none items-center')}\n\t\t>\n\t\t\t<RadixSlider.Track className=\"relative h-[3px] grow rounded-full bg-zinc-100 dark:bg-zinc-800\">\n\t\t\t\t<RadixSlider.Range className=\"absolute h-full rounded-full bg-black dark:bg-white\" />\n\t\t\t</RadixSlider.Track>\n\t\t\t<RadixSlider.Thumb\n\t\t\t\tclassName=\"relative block h-5 w-5 rounded-[1rem] bg-white shadow-md ring ring-black/10\"\n\t\t\t\taria-label=\"Volume\"\n\t\t\t>\n\t\t\t\t{value?.[0] != null && (\n\t\t\t\t\t<NumberFlow\n\t\t\t\t\t\tlocales=\"en-US\"\n\t\t\t\t\t\twillChange\n\t\t\t\t\t\tvalue={value[0]}\n\t\t\t\t\t\tisolate\n\t\t\t\t\t\tplugins={[continuous]}\n\t\t\t\t\t\topacityTiming={{\n\t\t\t\t\t\t\tduration: 250,\n\t\t\t\t\t\t\teasing: 'ease-out'\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttransformTiming={{\n\t\t\t\t\t\t\teasing: `linear(0, 0.0033 0.8%, 0.0263 2.39%, 0.0896 4.77%, 0.4676 15.12%, 0.5688, 0.6553, 0.7274, 0.7862, 0.8336 31.04%, 0.8793, 0.9132 38.99%, 0.9421 43.77%, 0.9642 49.34%, 0.9796 55.71%, 0.9893 62.87%, 0.9952 71.62%, 0.9983 82.76%, 0.9996 99.47%)`,\n\t\t\t\t\t\t\tduration: 500\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"absolute bottom-8 left-1/2 -translate-x-1/2 text-lg font-semibold\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</RadixSlider.Thumb>\n\t\t</RadixSlider.Root>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/react/index.tsx",
    "content": "import Example from './Component'\nimport * as React from 'react'\n\nexport default function Slider() {\n\tconst [value, setValue] = React.useState(50)\n\treturn (\n\t\t<Example\n\t\t\tvalue={[value]}\n\t\t\tonValueChange={([value]) => value != null && setValue(value)}\n\t\t\tmin={0}\n\t\t\tmax={100}\n\t\t\tstep={1}\n\t\t/>\n\t)\n}\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/svelte/Component.svelte",
    "content": "<script lang=\"ts\">\n\timport { Slider, type SliderProps } from 'bits-ui'\n\timport NumberFlow, { continuous } from '@number-flow/svelte'\n\n\tlet { value = $bindable([0]), ...props }: SliderProps = $props()\n</script>\n\n<div class=\"flex items-center gap-6\">\n\t<Slider.Root\n\t\t{...props}\n\t\tclass=\"relative flex h-5 w-[10rem] touch-none select-none items-center\"\n\t\tbind:value\n\t\tlet:thumbs\n\t>\n\t\t<span class=\"relative h-[3px] grow rounded-full bg-zinc-100 dark:bg-zinc-800\">\n\t\t\t<Slider.Range class=\"absolute h-full rounded-full bg-black dark:bg-white\" />\n\t\t</span>\n\t\t{#each thumbs as thumb}\n\t\t\t<Slider.Thumb\n\t\t\t\tclass=\"relative block h-5 w-5 rounded-[1rem] bg-white shadow-md ring ring-black/10\"\n\t\t\t\t{thumb}\n\t\t\t/>\n\t\t{/each}\n\t</Slider.Root>\n\t{#if value[0] != null}\n\t\t<div class=\"w-8 shrink-0 text-center\">\n\t\t\t<NumberFlow\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\twillChange\n\t\t\t\tvalue={value[0]}\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tplugins={[continuous]}\n\t\t\t\topacityTiming={{\n\t\t\t\t\tduration: 250,\n\t\t\t\t\teasing: 'ease-out'\n\t\t\t\t}}\n\t\t\t\ttransformTiming={{\n\t\t\t\t\teasing: `linear(0, 0.0033 0.8%, 0.0263 2.39%, 0.0896 4.77%, 0.4676 15.12%, 0.5688, 0.6553, 0.7274, 0.7862, 0.8336 31.04%, 0.8793, 0.9132 38.99%, 0.9421 43.77%, 0.9642 49.34%, 0.9796 55.71%, 0.9893 62.87%, 0.9952 71.62%, 0.9983 82.76%, 0.9996 99.47%)`,\n\t\t\t\t\tduration: 500\n\t\t\t\t}}\n\t\t\t\tclass=\"text-xl font-semibold\"\n\t\t\t/>\n\t\t</div>\n\t{/if}\n</div>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/svelte/index.svelte",
    "content": "<script lang=\"ts\">\n\timport Comp from './Component.svelte'\n</script>\n\n<Comp min={0} value={[50]} max={100} step={1} />\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/vue/Component.vue",
    "content": "<script setup lang=\"ts\">\nimport NumberFlow, { continuous } from '@number-flow/vue'\nimport { SliderRange, SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'\n</script>\n\n<template>\n\t<SliderRoot\n\t\tclass=\"relative flex h-5 w-[200px] touch-none select-none items-center\"\n\t\tv-slot=\"{ modelValue }\"\n\t>\n\t\t<SliderTrack class=\"relative h-[3px] grow rounded-full bg-zinc-100 dark:bg-zinc-800\">\n\t\t\t<SliderRange class=\"absolute h-full rounded-full bg-black dark:bg-white\" />\n\t\t</SliderTrack>\n\t\t<SliderThumb\n\t\t\tclass=\"relative block h-5 w-5 rounded-[1rem] bg-white shadow-md ring ring-black/10\"\n\t\t\taria-label=\"Volume\"\n\t\t>\n\t\t\t<NumberFlow\n\t\t\t\tv-if=\"modelValue[0] != null\"\n\t\t\t\tlocales=\"en-US\"\n\t\t\t\twillChange\n\t\t\t\t:value=\"modelValue[0]\"\n\t\t\t\t:plugins=\"[continuous]\"\n\t\t\t\t:opacityTiming=\"{\n\t\t\t\t\tduration: 250,\n\t\t\t\t\teasing: 'ease-out'\n\t\t\t\t}\"\n\t\t\t\t:transformTiming=\"{\n\t\t\t\t\teasing: `linear(0, 0.0033 0.8%, 0.0263 2.39%, 0.0896 4.77%, 0.4676 15.12%, 0.5688, 0.6553, 0.7274, 0.7862, 0.8336 31.04%, 0.8793, 0.9132 38.99%, 0.9421 43.77%, 0.9642 49.34%, 0.9796 55.71%, 0.9893 62.87%, 0.9952 71.62%, 0.9983 82.76%, 0.9996 99.47%)`,\n\t\t\t\t\tduration: 500\n\t\t\t\t}\"\n\t\t\t\tclass=\"absolute bottom-8 left-1/2 -translate-x-1/2 text-lg font-semibold\"\n\t\t\t/>\n\t\t</SliderThumb>\n\t</SliderRoot>\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/_Slider/vue/index.vue",
    "content": "<script setup lang=\"ts\">\nimport Comp from './Component.vue'\n</script>\n\n<template>\n\t<Comp :min=\"0\" :defaultValue=\"[50]\" :max=\"100\" :step=\"1\" />\n</template>\n"
  },
  {
    "path": "site/src/pages/[...framework]/examples/index.mdx",
    "content": "---\nlayout: './_Examples.astro'\n---\nexport { getStaticPaths } from '@/lib/framework'\nimport { DemoTitle } from '@/components/Demo'\nimport Activity from './_Activity/index.astro'\nimport Slider from './_Slider/index.astro'\nimport Input from './_Input/index.astro'\nimport Countdown from './_Countdown/index.astro'\nimport Heading from '@/components/Heading.astro';\nimport Match from '@/components/Match.astro';\nimport Code from '@/components/Code.astro';\nimport Link from '@/components/Link.astro'\nimport Note from '@/components/Note.astro'\nimport Pre from '@/components/Pre.astro'\nimport Motion from './_Motion/index.astro'\nexport const components = {a: Link, pre: Pre}\n\n<Match react vue svelte>\n<Heading class=\"sr-only\" value=\"Input\" />\n\n<Input rootClassName=\"!mt-0\" />\n</Match>\n\n<Match>\n<Heading value=\"Activity\" />\n</Match>\n\n<Activity />\n\n<Match react vue svelte>\n<Heading value=\"Slider\" />\n\n<Slider />\n</Match>\n\n<Match>\n<Heading value=\"Countdown\" />\n</Match>\n\n<Countdown />\n\n<Match react>\n    <Heading value=\"Motion for React\" />\n\n    NumberFlow was designed to work with [Motion's layout animations](https://motion.dev/docs/react-layout-animations).\n    You can use a `{ type: 'spring', duration: 0.9, bounce: 0 }` transition to match NumberFlow's default transform timing:\n\n    <Motion />\n\n    <Note>\n    When NumberFlow is used within a layout animation it should be (or be in) a\n    [`motion` component](https://motion.dev/docs/react-motion-component) with the\n    `layout layoutRoot` props.\n    </Note>\n</Match>\n"
  },
  {
    "path": "site/src/pages/[...framework]/index.mdx",
    "content": "---\nlayout: './_Home.astro'\n---\nimport Pre from '@/components/Pre.astro';\nimport NumberFlow from '@number-flow/react'\nimport TimingsDemo from './_demos/Timings'\nimport IsolateDemo from './_demos/Isolate'\nimport ContinuousDemo from './_demos/Continuous'\nimport Comp from '@/components/Comp.mdx'\nimport GroupComp from '@/components/GroupComp.mdx'\nimport SuffixDemo from './_demos/Suffix'\nimport TrendDemo from './_demos/Trend'\nimport StylingDemo from './_demos/Styling'\nimport Digits from './_Digits.astro'\nimport Match from '@/components/Match.astro'\nimport Meta from '@/components/Meta.astro'\nimport AnimationsOnTheWeb from '@/components/AnimationsOnTheWeb.astro'\nimport Type from '@/components/Type.astro'\nimport Heading from '@/components/Heading.astro'\nimport Union from '@/components/Union.astro'\nimport Link from '@/components/Link.astro'\nimport Note from '@/components/Note.astro'\nimport Group from './examples/_Group/index.astro'\nimport CSP from './_CSP.astro'\n\nexport { getStaticPaths } from '@/lib/framework'\nexport const components = {a: Link, pre: Pre}\n\n{/* We need an empty match for a complicated reason related to async and collecting the TOC headers in order */}\n<Match>\n<Heading class=\"sr-only\" value=\"Basic usage\" />\n</Match>\n\n<div className='xl:pre-first-line:hidden first:*:mt-0'>\n<Match react>\n```jsx\n// Basic usage\nimport NumberFlow from '@number-flow/react'\n\n<NumberFlow value={123} />\n```\n</Match>\n<Match vue>\n```vue\n<!-- Basic usage -->\n<script setup>\nimport NumberFlow from '@number-flow/vue'\n</script>\n<template>\n\t<NumberFlow :value=\"123\" />\n</template>\n```\n</Match>\n<Match svelte>\n```svelte\n<!-- Basic usage -->\n<script>\nimport NumberFlow from '@number-flow/svelte'\n</script>\n\n<NumberFlow value={123} />\n```\n</Match>\n<Match vanilla>\n```html\n<!-- Basic usage -->\n<number-flow></number-flow>\n\n<script type=\"module\">\n\timport 'number-flow'\n\t// or, if not using a bundler:\n\t// import 'https://esm.sh/number-flow'\n\n\tconst flow = document.querySelector('number-flow')\n\t// Sets the initial value:\n\tflow.update(123)\n</script>\n```\n</Match>\n</div>\n\n<Match>\n<Fragment slot=\"vanilla\">\nSubsequent calls to `.update()` will trigger animations.\n</Fragment>\n<Comp/> will automatically transition when the `value` prop changes.\n</Match>\n\n<AnimationsOnTheWeb />\n\n<Match>\n<Heading slot=\"vanilla\" value=\"Properties\" />\n<Heading value=\"Props\" />\n</Match>\n\n<h3 id=\"format\">\n\t<code>format<Type>: <Link href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options\">Intl.NumberFormatOptions</Link></Type></code>\n</h3>\n\nFormatting options for the number.\n\n<Match react>\n```jsx\n<NumberFlow format={{ notation: 'compact' }} value={value} />\n```\n</Match>\n<Match vue>\n```vue\n<NumberFlow :format=\"{ notation: 'compact' }\" :value />\n```\n</Match>\n<Match svelte>\n```svelte\n<NumberFlow format={{ notation: 'compact' }} {value} />\n```\n</Match>\n<Match vanilla>\n```js\nflow.format = { notation: 'compact' }\n```\n</Match>\n\n<h3 id=\"locales\">\n<code>locales<Type>: <Link href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#locales\">Intl.LocalesArgument</Link></Type></code>\n</h3>\n\nThe locale(s) for the number.\n\n<h3>\n\t<code><Match default=\"p\" vanilla=\"numberP\"/>refix<Type>: string</Type>, <Match default=\"s\" vanilla=\"numberS\"/>uffix<Type>: string</Type></code>\n</h3>\n\nA custom prefix or suffix for the number.\n\n<SuffixDemo client:visible defaultValue='code'>\n<Match react>\n```jsx\n<NumberFlow\n\tvalue={value}\n\tformat={{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }}\n\tsuffix=\"/mo\"\n/>\n```\n</Match>\n<Match vue>\n```vue\n<NumberFlow\n\t:value\n\t:format=\"{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }\"\n\tsuffix=\"/mo\"\n/>\n```\n</Match>\n<Match svelte>\n```svelte\n<NumberFlow\n\t{value}\n\tformat={{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }}\n\tsuffix=\"/mo\"\n/>\n```\n</Match>\n<Match vanilla>\n```js\nflow.format = { style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }\nflow.numberSuffix = \"/mo\"\n```\n</Match>\n</SuffixDemo>\n\n### Timings\n\nThere are three <Match vanilla=\"properties\" default=\"props\" /> to customize the animation timings. Each accept an [`EffectTiming`](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect/getTiming#return_value) object:\n\n<TimingsDemo client:visible defaultValue='code'>\n<Match react>\n```jsx\n<NumberFlow\n\t// Used for layout-related transforms:\n\ttransformTiming={{ duration: 750, easing: 'linear(...)' }}\n\t// Used for the digit spin animations.\n\t// Will fall back to `transformTiming` if unset:\n\tspinTiming={{ duration: 750, easing: 'linear(...)' }}\n\t// Used for fading in/out characters:\n\topacityTiming={{ duration: 350, easing: 'ease-out' }}\n/>\n```\n</Match>\n<Match vue>\n```vue\n<NumberFlow\n\t:transformTiming=\"{\n\t\t// Used for layout-related transforms:\n\t\tduration: 750, easing: 'linear(...)'\n\t}\"\n\t:spinTiming=\"{\n\t\t// Used for the digit spin animations.\n\t\t// Will fall back to `transformTiming` if unset:\n\t\tduration: 750, easing: 'linear(...)'\n\t}\"\n\t:opacityTiming=\"{\n\t\t// Used for fading in/out characters:\n\t\tduration: 350, easing: 'ease-out'\n\t}\"\n/>\n```\n</Match>\n<Match svelte>\n```svelte\n<NumberFlow\n\ttransformTiming={{\n\t\t// Used for layout-related transforms:\n\t\tduration: 750, easing: 'linear(...)'\n\t}}\n\tspinTiming={{\n\t\t// Used for the digit spin animations.\n\t\t// Will fall back to `transformTiming` if unset:\n\t\tduration: 750, easing: 'linear(...)'\n\t}}\n\topacityTiming={{\n\t\t// Used for fading in/out characters:\n\t\tduration: 350, easing: 'ease-out'\n\t}}\n/>\n```\n</Match>\n<Match vanilla>\n```js\n// Used for layout-related transforms:\nflow.transformTiming = {\n\tduration: 700, easing: 'linear(...)'\n}\n// Used for the digit spin animations.\n// Will fall back to `transformTiming` if unset:\nflow.spinTiming = {\n\tduration: 700, easing: 'linear(...)'\n}\n// Used for fading in/out characters:\nflow.opacityTiming = {\n\tduration: 350, easing: 'ease-out'\n}\n```\n</Match>\n</TimingsDemo>\n\nFor spring-based easings, I'd recommend [Kevin Grajeda's generator](https://www.kvin.me/css-springs)\nor [easing.dev](https://www.easing.dev/).\n\n<h3>\n\t<code>trend<Type>: <Union types={[\"number\", \"(oldValue: number, value: number) => number\"]} /></Type></code>\n\t<Meta>Default: `(oldValue, value) => Math.sign(value - oldValue)`</Meta>\n</h3>\n\nControls the direction of the digits. If `trend` is or returns\n\n- `+1:` the digits always go up.\n- `0:` each digit goes up if it increases and down if it decreases. This can be useful if you\nwant to animate number changes without conveying an overall trend ([example](https://x.com/pontusab/status/1825941664189526067)).\n- `-1:` The digits always go down.\n\n<TrendDemo client:visible />\n\n<Match react>\n<h3>\n\t<code>isolate<Type>: boolean</Type></code>\n\t<Meta>Default: `false`</Meta>\n</h3>\n\nIf `isolate` is set, <Comp/>'s transitions are isolated from any other layout changes\nthat may occur in the same update. Has no effect when inside a [`<NumberFlowGroup>`](#grouping).\n\n<IsolateDemo client:visible />\n</Match>\n\n<h3>\n\t<code>animated<Type>: boolean</Type></code>\n\t<Meta>Default: `true`</Meta>\n</h3>\n\nCan be set to `false` to disable all animations and finish any current ones.\n<Match><Fragment slot=\"vanilla\"/>See the [input example](/examples/#input) for a usage scenario.</Match>\n\n<h3>\n\t<code>digits<Type>: Record{'<number, { max?: number }>'}</Type></code>\n</h3>\n\nConfigure digits based on their position in the number (i.e. for 342.5, the positions are: <Digits />). This can be helpful for time-related displays,\nto ensure e.g. 59 -> 00. See the [countdown example](/examples/#countdown) for a demo.\n\n<Note>\n`digits` is not reactive to save on bundle size.\nIf you need it to be reactive, please submit a [feature request](https://github.com/barvian/number-flow/discussions/new?category=ideas).\n</Note>\n\n<h3 id=\"respect-motion-preference\">\n\t<code>respectMotionPreference<Type>: boolean</Type></code>\n\t<Meta>Default: `true`</Meta>\n</h3>\n\nCan be set to `false` to animate regardless of the user's reduced motion preference.\n\n<h3>\n\t<code>plugins<Type>: Plugin[]</Type></code>\n</h3>\n\nPlugins to apply to the component. Currently there's only one plugin, `continuous`, which\nmakes the number transitions appear to pass through in-between numbers:\n\n<ContinuousDemo client:visible>\n<Match react>\n```jsx\nimport NumberFlow, { continuous } from '@number-flow/react'\n\n<NumberFlow\n\tplugins={[continuous]}\n\tvalue={value}\n/>\n```\n</Match>\n<Match vue>\n```vue\n<script setup>\nimport NumberFlow, { continuous } from '@number-flow/vue'\n</script>\n<template>\n\t<NumberFlow\n\t\t:plugins=\"[continuous]\"\n\t\t:value=\"123\"\n\t/>\n</template>\n```\n</Match>\n<Match svelte>\n```svelte\n<script>\nimport NumberFlow, { continuous } from '@number-flow/svelte'\n</script>\n\n<NumberFlow\n\tplugins={[continuous]}\n\tvalue={123}\n/>\n```\n</Match>\n<Match vanilla>\n```js\nimport { continuous } from 'number-flow'\n\nflow.plugins = [continuous]\n```\n</Match>\n</ContinuousDemo>\n\nThis plugin has no effect if `trend` is `0`.\n\n<Match>\n<Fragment slot=\"vanilla\">\n---\n<Heading value=\"Attributes\"/>\n<h3><code>data-will-change</code></h3>\n</Fragment>\n<h3>\n\t<code>willChange<Type>: boolean</Type></code>\n\t<Meta>Default: `false`</Meta>\n</h3>\n</Match>\n\nIf set, NumberFlow applies [`will-change` properties](https://developer.mozilla.org/en-US/docs/Web/CSS/will-change) to relevant elements.\nThis can be useful if:\n* Your number is guaranteed to change frequently\n* You experience unwanted repositioning when a transition completes\n\nNote that \"excessive use of `will-change` will result in excessive memory use\" (source: [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/will-change)).\n\n<Match vanilla>\n```html\n<number-flow data-will-change></number-flow>\n```\n</Match>\n\n<h3>\n\t<code>nonce<Type>: string</Type></code>\n</h3>\n\nPasses a [CSP nonce](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce) through to NumberFlow's inline `<style>` elements<Match><Fragment slot='vanilla' /> during SSR and hydration</Match>.\n\n<Match react>\n```jsx\n<NumberFlow value={123} nonce={myNonce} />\n```\n</Match>\n<Match vue>\n```vue\n<NumberFlow :value=\"123\" :nonce=\"myNonce\" />\n```\n</Match>\n<Match svelte>\n```svelte\n<NumberFlow value={123} nonce={myNonce} />\n```\n</Match>\n<Match vanilla>\n```html\n<number-flow nonce=\"...\" />\n```\n</Match>\n\n<details>\n<summary>Hash-based CSP instructions</summary>\nNumberFlow exports its styles as strings so you can hash them\nfor your `Content-Security-Policy`. Here's an example:\n\n<CSP />\n</details>\n\n<Match vue>\n---\n<Heading value=\"Emits\"/>\n</Match>\n<Match svelte vanilla>\n---\n<Heading value=\"Events\"/>\n</Match>\n\n<h3><code><Match react=\"onAnimationsStart\" vue=\"animationsstart\" default=\"animationsstart\" /><Type>: (e: CustomEvent) => void</Type></code></h3>\n\nTriggered when update animations start. <Match vue react vanilla>Not to be confused with the built-in\n<Match react>`onAnimationStart`</Match><Match vue vanilla>[`animationstart` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event)</Match>, which\nwould trigger for animations on the <Comp/> element itself.</Match>\n\n<h3><code><Match react=\"onAnimationsFinish\" vue=\"animationsfinish\" default=\"animationsfinish\" /><Type>: (e: CustomEvent) => void</Type></code></h3>\n\nTriggered when update animations finish.\n\n---\n\n<Match>\n<Heading value=\"Styling\" />\n</Match>\n\nNumberFlow <Match svelte vue react>uses a [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) under the hood, and</Match> exposes [parts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part)\nfor styling purposes:\n\n<StylingDemo client:visible>\n<Match react>\n```css\nnumber-flow-react::part(suffix) {\n\tmargin-left: .0625em;\n\tfont-weight: normal;\n\tfont-size: 0.75em;\n\tcolor: var(--muted);\n}\n```\n</Match>\n<Match vue>\n```css\nnumber-flow-vue::part(suffix) {\n\tmargin-left: .0625em;\n\tfont-weight: normal;\n\tfont-size: 0.75em;\n\tcolor: var(--muted);\n}\n```\n</Match>\n<Match svelte>\n```css\nnumber-flow-svelte::part(suffix) {\n\tmargin-left: .0625em;\n\tfont-weight: normal;\n\tfont-size: 0.75em;\n\tcolor: var(--muted);\n}\n```\n</Match>\n<Match vanilla>\n```css\nnumber-flow::part(suffix) {\n\tmargin-left: .0625em;\n\tfont-weight: normal;\n\tfont-size: 0.75em;\n\tcolor: var(--muted);\n}\n```\n</Match>\n</StylingDemo>\n\nYou can use your browser's inspector to see which [`part` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part)\nare available to style. Note that changing the `font-size` of digits is difficult\ndue to the CSS techniques NumberFlow uses.\n\n<Note class=\"mb-[2.7em]\">\n`::part` styles may cause a flash of unstyled content in [old browsers](https://caniuse.com/declarative-shadow-dom).\n<details>\n<summary>See workaround</summary>\nYou can use feature detection\nto apply `::part` styles only to browsers that support [Declarative Shadow DOM](https://web.dev/articles/declarative-shadow-dom) (DSD). Add the following snippet to your `<head>`:\n\n```html\n<script>\n\tif (\n\t\tHTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode') ||\n\t\tHTMLTemplateElement.prototype.hasOwnProperty('shadowRoot') // old Chrome/Edge\n\t)\n\t\tdocument.documentElement.setAttribute('data-supports-dsd', '')\n</script>\n```\n\nThen ensure your `::part` styles use it:\n\n<Match react>\n```css\n:root[data-supports-dsd] number-flow-react::part(suffix) {\n\tfont-size: 0.75rem;\n}\n```\n</Match>\n<Match vue>\n```css\n:root[data-supports-dsd] number-flow-vue::part(suffix) {\n\tfont-size: 0.75rem;\n}\n```\n</Match>\n<Match svelte>\n```css\n:root[data-supports-dsd] number-flow-svelte::part(suffix) {\n\tfont-size: 0.75rem;\n}\n```\n</Match>\nIf you're using Tailwind, you can do this with a custom variant:\n```js\n// tailwind.config.js\nimport plugin from 'tailwindcss/plugin'\n\nexport default {\n\t// ...\n\tplugins: [\n\t\tplugin(({ matchVariant }) => {\n\t\t\tmatchVariant('part', (p) => `:root[data-supports-dsd] &::part(${p})`)\n\t\t})\n\t]\n}\n```\n\n<Match react>\n```tsx\n<NumberFlow className=\"part-[suffix]:text-xs\" />\n```\n</Match>\n<Match vue>\n```vue\n<NumberFlow class=\"part-[suffix]:text-xs\" />\n```\n</Match>\n<Match svelte>\n```svelte\n<NumberFlow class=\"part-[suffix]:text-xs\" />\n```\n</Match>\n</details>\n</Note>\n\nThere's also some CSS properties you can use to style the component:\n\n<h3>\n\t<code>{'--'}number-flow-mask-[height|width]<Type>: {'<length>'}</Type></code>\n\t<Meta>Default: `.25em` | `.5em`</Meta>\n</h3>\n\nThese adjust the height and width of the gradient fade-out masks at the edges of the number.\n`--number-flow-mask-height` also gets used as the top and bottom padding for the number.\n\n<h3>\n\t<code>line-height</code>\n\t<Meta>Default: `1`</Meta>\n</h3>\n\nCan be used to adjust the spacing between digits during spin animations.\nThe examples on this site all use `0.85`.\n\n### <code>font-variant-numeric<span className=\"text-muted\">:</span> tabular-nums</code>\n\nEnsures all numbers are the same width, which can prevent digits from shifting during transitions.\nSee [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-numeric#numeric-spacing-values) for more information.\n\n{/* <TabularNumsDemo client:visible /> */}\n\n---\n\n<Heading value=\"Grouping\"/>\n\nIf a <Comp/> affects another <Comp/>'s position, you can wrap them in a <GroupComp/> to properly sync their transitions<Match vanilla>. Make sure to `import 'number-flow/group'`</Match>:\n\n<Group />\n\n<Match svelte vue react>\n<GroupComp/> doesn't render an element or accept any props.\n</Match>\n<Match vanilla>\n<GroupComp/> syncs all descendant <Comp/>s, not just children.\n</Match>\n\n---\n\n<Match>\n<Heading slot=\"react\" value=\"Hooks\" />\n<Heading slot=\"vue\" value=\"Composables\" />\n<Heading slot=\"svelte\" value=\"Stores\" />\n<Heading slot=\"vanilla\" value=\"Utilities\" />\n</Match>\n\n<Match vanilla>\n<h3><code>renderInnerHTML<Type>: (value: Value, opts?: RenderOptions) => string</Type></code></h3>\nRenders the inner HTML of a <Comp/> for SSR (server-side rendering). Here's a usage example with [Astro](https://astro.build):\n```astro\n---\nimport { renderInnerHTML } from 'number-flow'\n---\n<number-flow\n\tnonce=\"...\"\n\tset:html={renderInnerHTML(123, {\n\t\t// Make sure to pass in the `locales`, `format`,\n\t\t// `numberPrefix`, and `numberSuffix`\n\t\t// properties you're using:\n\t\tlocales: 'en-US',\n\t\tformat: { notation: 'compact' },\n\t\tnonce: '...'\n\t})}\n/>\n\n<script>\n\timport 'number-flow'\n\n\tconst flow = document.querySelector('number-flow')\n\tflow.locales = 'en-US'\n\tflow.format = { notation: 'compact' }\n\t\n\t// The first .update() call hydrates the component.\n\t// Make sure to pass the same value as you did in `renderInnerHTML()`:\n\tflow.update(123)\n</script>\n```\n\nIf you use `number-flow/lite`, `renderInnerHTML` also supports\n`renderInnerHTML(data, { nonce })`.\n</Match>\n\n<Match as=\"h3\">\n<code slot=\"react\">useCanAnimate(opts<Type>?: {'{'} respectMotionPreference?: boolean {'}'}</Type>)<Type>: boolean</Type></code>\n<code slot=\"vue\">useCanAnimate(opts<Type>?: {'{'} respectMotionPreference?: {'MaybeRefOrGetter<boolean> }'}</Type>)<Type>: {'ComputedRef<boolean>'}</Type></code>\n<code slot=\"svelte\">getCanAnimate(opts<Type>?: {'{'} respectMotionPreference?: boolean {'}'}</Type>)<Type>: {'Readable<boolean>'}</Type></code>\n<code slot=\"vanilla\">canAnimate<Type>: boolean</Type></code>\n</Match>\n\n<Match react>\nReturns `true` if NumberFlow can animate, i.e. the browser supports the [required features](https://caniuse.com/mdn-css_types_mod)\nand (optionally) the user is [okay with motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).\nSee the [Motion for React example](/examples#motion-for-react) for a usage scenario.\n</Match>\n<Match vue>\nReturns a computed ref whose value is `true`  if NumberFlow can animate, i.e. the browser supports the [required features](https://caniuse.com/mdn-css_types_mod)\nand (optionally) the user is [okay with motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).\n</Match>\n<Match svelte>\nReturns a readable store whose value is `true` if NumberFlow can animate, i.e. the browser supports the [required features](https://caniuse.com/mdn-css_types_mod)\nand (optionally) the user is [okay with motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).\n</Match>\n<Match vanilla>\n`true` if NumberFlow can animate, i.e. the browser supports the supports the [required features](https://caniuse.com/mdn-css_types_mod).\n```js\nimport { canAnimate } from 'number-flow'\n```\n\n<h3><code>prefersReducedMotion<Type>: <Link href=\"https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList\">MediaQueryList</Link></Type></code></h3>\nWhether the user prefers reduced motion.\n```js\nimport { prefersReducedMotion } from 'number-flow'\n\nif (prefersReducedMotion.matches) {\n\t// User prefers reduced motion\n}\n```\n</Match>\n\n---\n\n<Match>\n<Heading value=\"Limitations\" />\n</Match>\n\n* [Scientific](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#scientific) and [engineering](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#engineering) notations aren't supported.\n* [Non-Latin digits](https://github.com/barvian/number-flow/issues/8) and [RTL locales](https://github.com/barvian/number-flow/issues/93) aren't currently supported.\n* Backgrounds and borders on <Comp/> won't scale smoothly during transitions.\n<Match react>I'd recommend using [Motion for React](https://motion.dev/docs/react-quick-start) for these, as it's more composable than any built-in solution could be. See the Motion for React layout animations [example](/examples#motion-for-react).</Match>\n"
  },
  {
    "path": "site/src/pages/showcase.astro",
    "content": "---\nimport Layout from '@/layouts/Layout.astro'\nimport Tweet from '@/components/Tweet.astro'\nimport LogoWall from '@/components/LogoWall/LogoWall.astro'\nimport Link from '@/components/Link.astro'\n---\n\n<Layout title=\"NumberFlow Showcase\" description=\"Real-life uses of NumberFlow from across the web.\">\n\t<header class=\"~mb-10/20 ~mt-4/6 container max-w-2xl text-center\">\n\t\t<h1 class=\"~text-3xl/5xl font-semibold tracking-tight\">Showcase</h1>\n\t\t<div class=\"~text-base/lg ~mt-4/6 prose prose-muted dark:prose-invert text-pretty\">\n\t\t\t<p>\n\t\t\t\tI'd love to see how you're using NumberFlow. Tag\n\t\t\t\t<Link href=\"https://x.com/mbarvian\" target=\"_blank\">@mbarvian</Link>\n\t\t\t\tin a post on 𝕏 and I might include it here. Note: NumberFlow was previously called MotionNumber.\n\t\t\t</p>\n\t\t</div>\n\t\t<LogoWall class=\"~mt-10/20\" />\n\t</header>\n\t<!-- <div class=\"lg:max-w-9xl container max-w-2xl gap-8 *:mb-8 lg:columns-2 2xl:columns-3\"> -->\n\t<div class=\"~space-y-6/8 container max-w-2xl\">\n\t\t<Tweet id=\"1861891285000577303\" autoplay />\n\t\t<Tweet id=\"1900480380097933349\" />\n\t\t<Tweet id=\"1860956737974804866\" />\n\t\t<Tweet id=\"1861923025131888901\" />\n\t\t<Tweet id=\"1837159796564447380\" />\n\t\t<Tweet id=\"1862193333550162190\" />\n\t\t<Tweet id=\"1856379776849907797\" />\n\t\t<Tweet id=\"1827742960672489594\" />\n\t\t<!-- <Tweet id=\"1850571081977090151\" /> -->\n\t</div>\n</Layout>\n"
  },
  {
    "path": "site/src/react.d.ts",
    "content": "import 'react'\n\ndeclare module 'react' {\n\tinterface CSSProperties {\n\t\t[key: `--${string}`]: string | number\n\t}\n\n\tnamespace JSX {\n\t\tinterface IntrinsicAttributes {\n\t\t\t'client:visible'?: boolean\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "site/src/stores/url.ts",
    "content": "import { isTransitionBeforePreparationEvent } from 'astro:transitions/client'\nimport { atom } from 'nanostores'\nimport type { Framework } from '../lib/framework'\n\nexport const $url = atom<URL | Location | undefined>(\n\t// Set in middleware:\n\timport.meta.env.SSR ? undefined : window.location\n)\nif (!import.meta.env.SSR)\n\tdocument.addEventListener('astro:before-preparation', (event) => {\n\t\tif (!isTransitionBeforePreparationEvent(event)) return\n\t\t$url.set(event.to)\n\t})\n\nexport const $pageFramework = atom<Framework | null>(\n\t// Set in middleware:\n\timport.meta.env.SSR ? null : (document.documentElement.dataset.framework as Framework)\n)\n"
  },
  {
    "path": "site/svelte.config.mjs",
    "content": "import { vitePreprocess } from '@astrojs/svelte'\n\nexport default {\n\tpreprocess: vitePreprocess()\n}\n"
  },
  {
    "path": "site/tailwind.config.ts",
    "content": "import type { Config } from 'tailwindcss'\nimport reset from 'tw-reset'\nimport fluid, { extract, fontSize, screens, type FluidThemeConfig } from 'fluid-tailwind'\nimport typography from '@tailwindcss/typography'\nimport defaultTheme from 'tailwindcss/defaultTheme'\nimport plugin from 'tailwindcss/plugin'\n// @ts-expect-error types not working\nimport spring from 'tailwindcss-spring'\nimport type { PluginUtils } from 'tailwindcss/types/config'\nimport defaultColors from 'tailwindcss/colors'\n\nconst sans = ['Inter', '_font_fallback_732902278794', 'sans-serif']\n\nexport default {\n\tpresets: [reset({ hoverOnlyWhenSupported: true })],\n\tcontent: {\n\t\tfiles: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],\n\t\ttransform: {\n\t\t\tmdx: (src) =>\n\t\t\t\tsrc\n\t\t\t\t\t// Ignore classes in code blocks\n\t\t\t\t\t.replace(/```.*?```/gs, '')\n\t\t\t\t\t// Only return stuff in <component>s\n\t\t\t\t\t.match(/<[^/].*?>/g)\n\t\t\t\t\t?.join() ?? ''\n\t\t},\n\t\textract\n\t},\n\tcorePlugins: {\n\t\tcontainer: false\n\t},\n\tdarkMode: ['variant', ['@media (prefers-color-scheme: dark) { & }', '&:where(.dark *)']],\n\ttheme: {\n\t\tscreens,\n\t\tfontSize,\n\t\tfontFamily: {\n\t\t\tsans,\n\t\t\t'mac-ui': ['-apple-system', 'BlinkMacSystemFont', ...sans],\n\t\t\tmono: defaultTheme.fontFamily.mono\n\t\t},\n\t\tfluid: (({ theme }) => ({\n\t\t\tdefaultScreens: [, theme('screens.md')]\n\t\t})) satisfies FluidThemeConfig,\n\t\textend: {\n\t\t\tboxShadow: {\n\t\t\t\tpx: '0 0 0 1px rgb(0 0 0 / 0.05)'\n\t\t\t},\n\t\t\toutlineWidth: {\n\t\t\t\tDEFAULT: '2px'\n\t\t\t},\n\t\t\toutlineColor: {\n\t\t\t\tDEFAULT: 'theme(colors.blue.500)'\n\t\t\t},\n\t\t\toutlineOffset: {\n\t\t\t\tDEFAULT: '2px'\n\t\t\t},\n\t\t\tcolors: {\n\t\t\t\taccent: 'var(--accent)',\n\t\t\t\tzinc: {\n\t\t\t\t\t125: '##f0f0f1',\n\t\t\t\t\t150: '#ececee',\n\t\t\t\t\t750: `color-mix(in srgb, ${defaultColors.zinc['700']} 50%, ${defaultColors.zinc['800']})`,\n\t\t\t\t\t850: '#1f1f22'\n\t\t\t\t}\n\t\t\t},\n\t\t\tkeyframes: {\n\t\t\t\t'logo-wall': {\n\t\t\t\t\tto: {\n\t\t\t\t\t\ttransform: 'translateX(-100%)'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tanimation: {\n\t\t\t\t'logo-wall': 'logo-wall 60s linear infinite'\n\t\t\t},\n\t\t\tscreens: {\n\t\t\t\txs: '20rem',\n\t\t\t\txl: '74rem'\n\t\t\t},\n\t\t\tspacing: {\n\t\t\t\t'0.75': '0.1875rem',\n\t\t\t\t'4.5': '1.125rem',\n\t\t\t\t'5.5': '1.375rem',\n\t\t\t\t'11.5': '2.875rem',\n\t\t\t\t'16.5': '4.125rem',\n\t\t\t\t18: '4.5rem'\n\t\t\t},\n\t\t\topacity: {\n\t\t\t\t'12.5': '12.5%'\n\t\t\t},\n\t\t\ttransitionTimingFunction: {\n\t\t\t\t'out-quad': 'cubic-bezier(.25, .46, .45, .94)'\n\t\t\t},\n\t\t\tmaxWidth: ({ theme }) => ({\n\t\t\t\t'9xl': theme('screens.2xl')\n\t\t\t}),\n\t\t\ttypography: ({ theme }: PluginUtils) => ({\n\t\t\t\tDEFAULT: {\n\t\t\t\t\tcss: {\n\t\t\t\t\t\t'line-height': '1.7',\n\n\t\t\t\t\t\tth: {\n\t\t\t\t\t\t\t'@apply font-semibold': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\ta: {\n\t\t\t\t\t\t\t'font-weight': 'unset',\n\t\t\t\t\t\t\t'@apply link-underline': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\thr: {\n\t\t\t\t\t\t\t'@apply my-16 border-faint': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t'code::before': {\n\t\t\t\t\t\t\tcontent: 'none'\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t'code a': {\n\t\t\t\t\t\t\t'@apply text-muted': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tsummary: {\n\t\t\t\t\t\t\t'@apply font-medium text-[--tw-prose-links] cursor-pointer': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t'code::after': {\n\t\t\t\t\t\t\tcontent: 'none'\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\th2: {\n\t\t\t\t\t\t\t'@apply mt-16 mb-[1.25em] font-semibold text-xl': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\th3: {\n\t\t\t\t\t\t\t'@apply font-semibold text-base': {},\n\t\t\t\t\t\t\t'margin-top': '2.4em',\n\t\t\t\t\t\t\t'margin-bottom': '1em'\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\th4: {\n\t\t\t\t\t\t\t'@apply font-semibold': {},\n\t\t\t\t\t\t\t'margin-bottom': '0.7em',\n\t\t\t\t\t\t\t'margin-top': '1.25em'\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tblockquote: {\n\t\t\t\t\t\t\t'@apply border-faint': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t'[role=alert]': {\n\t\t\t\t\t\t\t'@apply my-[1.25em]': {}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t'[role=alert] > :first-child': {\n\t\t\t\t\t\t\t'margin-top': '0 !important'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'[role=alert] > :last-child': {\n\t\t\t\t\t\t\t'margin-bottom': '0 !important'\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tzinc: {\n\t\t\t\t\tcss: {\n\t\t\t\t\t\t'--tw-prose-links': theme('colors.zinc.950'),\n\t\t\t\t\t\t'--tw-prose-invert-links': theme('colors.zinc.50'),\n\t\t\t\t\t\t'--tw-prose-code': theme('colors.zinc.950'),\n\t\t\t\t\t\t'--tw-prose-invert-code': theme('colors.zinc.50'),\n\t\t\t\t\t\t'--tw-prose-headings': theme('colors.zinc.950'),\n\t\t\t\t\t\t'--tw-prose-invert-headings': theme('colors.zinc.50'),\n\t\t\t\t\t\t'--tw-prose-invert-hr': theme('colors.zinc.900'),\n\t\t\t\t\t\t'--tw-prose-invert-body': theme('colors.zinc.300')\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tmuted: {\n\t\t\t\t\tcss: {\n\t\t\t\t\t\t'--tw-prose-body': theme('colors.zinc.500'),\n\t\t\t\t\t\t'--tw-prose-invert-body': theme('colors.zinc.400'),\n\t\t\t\t\t\t// '--tw-prose-links': 'currentColor',\n\t\t\t\t\t\t// '--tw-prose-invert-links': 'currentColor',\n\t\t\t\t\t\t'--tw-prose-links': theme('colors.zinc.600'),\n\t\t\t\t\t\t'--tw-prose-invert-links': theme('colors.zinc.400')\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcurrent: {\n\t\t\t\t\tcss: {\n\t\t\t\t\t\t'--tw-prose-links': 'currentColor',\n\t\t\t\t\t\t'--tw-prose-invert-links': 'currentColor'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t},\n\tplugins: [\n\t\tspring,\n\t\tfluid,\n\t\ttypography,\n\t\tplugin(({ matchUtilities, addUtilities, addVariant, matchVariant, theme }) => {\n\t\t\tmatchUtilities(\n\t\t\t\t{\n\t\t\t\t\tvt: (_, { modifier }) => ({\n\t\t\t\t\t\t'view-transition-name': modifier\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t{ values: { DEFAULT: '' }, modifiers: 'any' }\n\t\t\t)\n\n\t\t\tmatchVariant('part', (p) => `:root[data-supports-dsd] &::part(${p})`)\n\n\t\t\tmatchUtilities(\n\t\t\t\t{\n\t\t\t\t\tfluid: (val) => ({\n\t\t\t\t\t\t'--fluid': val\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t{ values: theme('spacing') }\n\t\t\t)\n\n\t\t\tconst resolveOpacity = (opacity: string | number) => {\n\t\t\t\tconst num = typeof opacity === 'string' ? parseFloat(opacity) : opacity\n\t\t\t\tif (!isNaN(num)) return `${num * 100}%`\n\t\t\t\treturn opacity\n\t\t\t}\n\t\t\tmatchUtilities(\n\t\t\t\t{\n\t\t\t\t\t'text-current': (_, { modifier }) =>\n\t\t\t\t\t\tmodifier && {\n\t\t\t\t\t\t\tcolor: `color-mix(in srgb, transparent, currentColor ${resolveOpacity(modifier)})`\n\t\t\t\t\t\t},\n\t\t\t\t\t'decoration-current': (_, { modifier }) =>\n\t\t\t\t\t\tmodifier && {\n\t\t\t\t\t\t\t'text-decoration-color': `color-mix(in srgb, transparent, currentColor ${resolveOpacity(modifier)})`\n\t\t\t\t\t\t},\n\t\t\t\t\t'border-current': (_, { modifier }) =>\n\t\t\t\t\t\tmodifier && {\n\t\t\t\t\t\t\t'border-color': `color-mix(in srgb, transparent, currentColor ${resolveOpacity(modifier)})`\n\t\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ values: { DEFAULT: '' }, modifiers: theme('opacity')! }\n\t\t\t)\n\n\t\t\taddUtilities({\n\t\t\t\t'.border-faint': {\n\t\t\t\t\t'@apply border-zinc-150 dark:border-zinc-800': {}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\taddVariant('hover', [\n\t\t\t\t'@media (hover: hover) and (pointer: fine) { &:hover }',\n\t\t\t\t'@media not all and (hover: hover) and (pointer: fine) { &:active }'\n\t\t\t])\n\t\t\taddVariant('can-hover', '@media (hover: hover)')\n\t\t\taddVariant('pre-first-line', ['pre& .line:first-of-type', '& pre .line:first-of-type'])\n\t\t\taddVariant('prefers-dark', ['@media (prefers-color-scheme: dark) { & }'])\n\t\t})\n\t]\n} satisfies Config\n"
  },
  {
    "path": "site/tsconfig.json",
    "content": "{\n\t\"extends\": [\"astro/tsconfigs/strictest\", \"../tsconfig.json\"],\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"preserve\",\n\t\t\"jsxImportSource\": \"react\",\n\t\t\"exactOptionalPropertyTypes\": false,\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"/*\": [\"./*\"],\n\t\t\t\"@/*\": [\"./src/*\"]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test-suites/wrapper/can-animate.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest('performs correctly', async ({ page, javaScriptEnabled, contextOptions }) => {\n\tawait page.goto('/can-animate', { waitUntil: 'networkidle' })\n\n\t// Check for hydration errors:\n\tconst logs: string[] = []\n\tpage.on('console', (msg) => logs.push(msg.text()))\n\n\tconst def = page.getByTestId('default')\n\tconst disrespect = page.getByTestId('disrespect-motion-preference')\n\n\tif (!javaScriptEnabled) {\n\t\tawait expect(def).toHaveText('false')\n\t\tawait expect(disrespect).toHaveText('false')\n\t\treturn\n\t}\n\n\tawait expect(def).toHaveText(String(contextOptions.reducedMotion !== 'reduce'))\n\tawait expect(disrespect).toHaveText('true')\n\n\texpect(logs).toEqual([])\n})\n"
  },
  {
    "path": "test-suites/wrapper/group-1-unchanged.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.skip(\n\t({ javaScriptEnabled, contextOptions }) =>\n\t\t!javaScriptEnabled || contextOptions.reducedMotion === 'reduce'\n)\n\ntest('transitions correctly', async ({ page, contextOptions }) => {\n\t// Not sure why this is necessary for Svelte Chromium/Safari but I couldn't get it to work without it:\n\t// https://www.reddit.com/r/sveltejs/comments/15m9jch/how_do_you_wait_for_sveltekit_to_be_completely/\n\tawait page.goto('/group-1-unchanged', { waitUntil: 'networkidle' })\n\n\tawait page.getByRole('button', { name: 'Change and pause' }).click()\n\tawait expect(page).toHaveScreenshot({ animations: 'allow' })\n})\n"
  },
  {
    "path": "test-suites/wrapper/hashes.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest('supports CSP hashes for SSR and hydration styles', async ({ page }) => {\n\tconst response = await page.goto('/hashes', { waitUntil: 'networkidle' })\n\n\tawait expect(page).toHaveScreenshot()\n})\n"
  },
  {
    "path": "test-suites/wrapper/nonce.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest('supports nonce for SSR and hydration styles', async ({ page }) => {\n\tconst response = await page.goto('/nonce', { waitUntil: 'networkidle' })\n\tconst headers = response?.headers()\n\texpect(headers?.['content-security-policy'] ?? headers?.['Content-Security-Policy']).toContain(\n\t\t\"style-src 'nonce-test-nonce'\"\n\t)\n\n\tawait expect(page).toHaveScreenshot()\n})\n"
  },
  {
    "path": "test-suites/wrapper/parts.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest('renders the correct parts', async ({ page }) => {\n\tawait page.goto('/')\n\n\tconst flow = await page.getByTestId('flow1')\n\n\t// Check for parts\n\tawait expect(flow.locator('[part~=left]')).toBeAttached()\n\tawait expect(flow.locator('[part~=currency]')).toBeAttached()\n\tawait expect(flow.locator('[part~=symbol]')).toHaveCount(4)\n\tawait expect(flow.locator('[part~=number]')).toBeAttached()\n\tawait expect(flow.locator('[part~=integer]')).toBeAttached()\n\tawait expect(flow.locator('[part~=fraction]')).toBeAttached()\n\tawait expect(flow.locator('[part~=integer-digit]')).toHaveCount(2)\n\tawait expect(flow.locator('[part~=fraction-digit]')).toHaveCount(2)\n\tawait expect(flow.locator('[part~=digit]')).toHaveCount(4)\n\tawait expect(flow.locator('[part~=suffix]')).toBeAttached()\n\tawait expect(flow.locator('[part~=right]')).toBeAttached()\n})\n"
  },
  {
    "path": "test-suites/wrapper/render.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.skip(({ javaScriptEnabled }) => !javaScriptEnabled)\n\ntest('renders correctly', async ({ page }) => {\n\tawait page.goto('/', { waitUntil: 'networkidle' })\n\tawait expect(page).toHaveScreenshot()\n\n\t// Check for console errors:\n\tconst logs: string[] = []\n\tpage.on('console', (msg) => logs.push(msg.text()))\n\n\t// Ensure correct role and aria-label:\n\tconst flow = await page.evaluateHandle('window.flow1')\n\t// @ts-expect-error private _internals\n\tconst role = await page.evaluate((flow) => flow._internals.role, flow)\n\t// @ts-expect-error private _internals\n\tconst ariaLabel = await page.evaluate((flow) => flow._internals.ariaLabel, flow)\n\texpect(role).toBe('img')\n\texpect(ariaLabel).toBe(':US$42.00/mo')\n\n\texpect(logs).toEqual([])\n})\n"
  },
  {
    "path": "test-suites/wrapper/ssr.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.skip(({ javaScriptEnabled }) => javaScriptEnabled)\n\ntest('server-side renders correctly', async ({ page }) => {\n\tawait page.goto('/')\n\tawait expect(page).toHaveScreenshot()\n\n\tconst flow = await page.getByTestId('flow1')\n\tconst wrapper = await page.getByLabel(':US$42.00/mo')\n\texpect(wrapper).toBeAttached()\n\texpect(await wrapper.getAttribute('role')).toBe('img')\n})\n"
  },
  {
    "path": "test-suites/wrapper/update.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.skip(({ javaScriptEnabled }) => !javaScriptEnabled)\n\ntest('updates correctly', async ({ page, contextOptions }) => {\n\tawait page.goto('/', { waitUntil: 'networkidle' })\n\n\tconst logs: string[] = []\n\tpage.on('console', (msg) => logs.push(msg.text()))\n\n\tawait page.getByRole('button', { name: 'Change and pause' }).click()\n\tawait expect(page).toHaveScreenshot({ animations: 'allow' })\n\n\tconst flow = await page.evaluateHandle('window.flow1')\n\t// @ts-expect-error private _internals\n\tconst ariaLabel = await page.evaluate((flow) => flow._internals.ariaLabel, flow)\n\texpect(ariaLabel).toBe(':US$152.00/mo')\n\n\tawait page.getByRole('button', { name: 'Resume' }).click()\n\tawait expect(page).toHaveScreenshot({ animations: 'allow' })\n\n\tif (contextOptions.reducedMotion === 'reduce') expect(logs).toEqual([])\n\telse expect(logs).toEqual(['start', 'finish'])\n})\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t// Stricter linting during builds:\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t// \"exactOptionalPropertyTypes\": true,\n\t\t\"allowUnreachableCode\": false,\n\t\t\"allowUnusedLabels\": false,\n\t\t// \"stripInternal\": true,\n\t\t\"noEmitOnError\": true\n\t}\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es2021\", // compile private setters to WeakMap\n\t\t\"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"Bundler\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"isolatedModules\": true,\n\n\t\t/* Linting */\n\t\t\"strict\": true,\n\t\t\"noImplicitReturns\": false,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noUncheckedIndexedAccess\": true\n\t},\n\t\"exclude\": [\"**/node_modules\", \"**/dist\", \"**/build\"]\n}\n"
  }
]