[
  {
    "path": ".github/CODEOWNERS",
    "content": "* @darkroomengineering/devs\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [darkroomengineering]\npolar: darkroomengineering\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "## Before to submit your issue\nRead the [Troubleshooting](https://github.com/darkroomengineering/lenis#troubleshooting) section.\n\n## Describe the bug\nA clear and concise description of what the bug is.\n\n## To Reproduce\nTry to reproduce your issue by forking this [codepen](https://codepen.io/ClementRoche/pen/VwxgZEP). If you can't reproduce it, it means there is something wrong on your initial environment, please read the documentation again. If your issue doesn't include any reproduction link, it will take more time to be treaten.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n.next/\n/out/\n\n# production\n/build\n/docs/dist\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\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n.eslintcache\n\n.npmrc\n\n\npackages/core/dist/\npackages/react/dist/\npackages/snap/dist/\npackages/vue/dist/\n\ndist-new/\ndist/\n\n.tldr/\n.tldrignore"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    // Formatting & Linting\n    \"biomejs.biome\",\n\n    // CSS & Styling\n    \"bradlc.vscode-tailwindcss\",\n    \"csstools.postcss\",\n\n    // GraphQL (Shopify, etc.)\n    \"graphql.vscode-graphql-syntax\",\n\n    // Sanity CMS (GROQ syntax + validation)\n    \"sanity-io.vscode-sanity\",\n\n    // DX Enhancements\n    \"yoavbls.pretty-ts-errors\",\n    \"waderyan.gitblame\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  // Formatting\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"biomejs.biome\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.biome\": \"always\",\n    \"source.organizeImports.biome\": \"always\"\n  },\n\n  // TypeScript\n  \"typescript.suggest.autoImports\": true,\n  \"typescript.preferences.importModuleSpecifier\": \"non-relative\",\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  // \"typescript.experimental.useTsgo\": true, // Disabled: tsgo doesn't support Next.js plugin\n  \"javascript.suggest.autoImports\": true,\n\n  // Editor labels (fixed for .tsx files)\n  \"workbench.editor.customLabels.patterns\": {\n    \"**/app/**/page.tsx\": \"${dirname(1)}/${dirname} <page>\",\n    \"**/app/**/layout.tsx\": \"${dirname(1)}/${dirname} <layout>\",\n    \"**/app/api/**/route.ts\": \"${dirname(1)}/${dirname} <route>\",\n    \"**/components/**/index.tsx\": \"${dirname} <component>\"\n  },\n\n  // File associations\n  \"files.associations\": {\n    \"*.json\": \"jsonc\"\n  },\n\n  // CSS (Tailwind v4)\n  \"css.lint.validProperties\": [\"user-drag\"],\n  \"css.lint.unknownAtRules\": \"ignore\",\n  \"tailwindCSS.experimental.configFile\": \"lib/styles/css/tailwind.css\",\n  \"tailwindCSS.includeLanguages\": {\n    \"typescriptreact\": \"html\"\n  },\n\n  // Search exclusions (performance)\n  \"search.exclude\": {\n    \"**/node_modules\": true,\n    \"**/.next\": true,\n    \"**/bun.lock\": true,\n    \"**/.vercel\": true,\n    \"**/lib/integrations/sanity/sanity.types.ts\": true\n  },\n\n  // Language-specific formatters\n  \"[css]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[javascriptreact]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[typescriptreact]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[jsonc]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n\n  // General\n  \"files.eol\": \"\\n\",\n  \"workbench.editorAssociations\": {\n    \"*.svg\": \"default\"\n  }\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Lenis Contributing Guide\n\nYooo! We're really excited that you're interested in contributing to Lenis! Before submitting your contribution, please read through the following guide.\n\n## Repo Setup\n\nTo develop locally, fork the Lenis repository and clone it in your local machine. The Lenis repo is a monorepo using pnpm workspaces. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/).\n\nTo start developing Lenis, run the following commands in the root of the repository:\n\n1. Run `pnpm i` in Lenis's root folder.\n\n2. Run `pnpm dev` in Lenis's root folder.\n\n3. Open http://localhost:4321 in your browser, which has a playground for Lenis.\n\nThe dev server will automatically rebuild Lenis whenever you change its code no matter what package you are working on.\nAt the same time the playground will automatically reload when you change the code of any package.\n\n\n## Pull Request Guidelines\n\n- Checkout a topic branch from a base branch (e.g. `main`), and merge back against that branch.\n\n- If adding a new feature:\n\n  - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first, and have it approved before working on it.\n\n- If fixing a bug:\n\n  - Provide a detailed description of the bug in the PR. Codepen demo preferred.\n\n- Make sure to enable prettier in your editor to format the code.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) 2024 darkroom.engineering\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "MANIFESTO.md",
    "content": "# For the Nerds 🧠\nAlright, let's get nerdy for a minute because you probably installed Lenis for smooth scrolling and don’t even know the whole real story behind it. Originally, Lenis wasn’t built just to make your site scroll like butter (even though that’s a pretty nice side effect). No, the real mission was to tackle a major pain point in web development that most folks don't realize exists—synchronizing WebGL and the DOM while scrolling.\n\n## The Real Problem 🤔\nYou see, WebGL and the DOM don’t play nicely together when you’re scrolling. With native scrolling, trying to keep WebGL animations in sync with DOM elements is like trying to teach a cat to fetch—it just doesn’t want to cooperate. There’s a constant fight over control, which means you end up with janky animations, weird timing issues, and an overall frustrating experience for developers and users alike. Lenis came in as the referee, letting us manage the scroll position smoothly and precisely, so WebGL and the DOM can finally share the spotlight.\n\n## The Happy Mistake 🎉\nBut here’s the kicker—when we made Lenis to solve that problem, something interesting happened. Thanks to its ability to interpolate (or “lerp,” for the cool kids) the scroll position, it also created a super-smooth scrolling experience. And as it turns out, everyone just loves smooth scrolling. So much so that this “happy little accident” quickly overshadowed the original problem Lenis was built to solve. People started adopting it just for the silky smooth scrolling, completely unaware that Lenis was originally the secret weapon for complex WebGL-DOM synchronization.\n\n## So… What’s the Point?\nIf you’re here thinking, “I just wanted my site to scroll like butter,” don’t worry—you’re not alone! Smooth scrolling is awesome, and Lenis does it really well. But for those of you who really want to know, Lenis is more than just a pretty face. It’s here to handle the hard stuff under the hood and to give you the control you need to pull off those super-synced, glitch-free animations.\n\nIn short: Lenis is the smooth scroll library that became famous by accident. So, next time you add it to your project, just know that it's not just a scrolling effect—it's a powerhouse tool for handling the impossible."
  },
  {
    "path": "README.md",
    "content": "[![LENIS](https://assets.darkroom.engineering/lenis/banner.gif)](https://github.com/darkroomengineering/lenis)\n\n[![npm](https://img.shields.io/npm/v/lenis?colorA=E30613&colorB=000000\n)](https://www.npmjs.com/package/lenis)\n[![downloads](https://img.shields.io/npm/dm/lenis?colorA=E30613&colorB=000000\n)](https://www.npmjs.com/package/lenis)\n[![size](https://img.shields.io/bundlephobia/minzip/lenis?label=size&colorA=E30613&colorB=000000)](https://bundlephobia.com/package/lenis)\n\n## Introduction\n\nLenis (\"smooth\" in latin) is a lightweight, robust, and performant smooth scroll library. It's designed by [@darkroom.engineering](https://twitter.com/darkroomdevs) to be simple to use and easy to integrate into your projects. It's built with performance in mind and is optimized for modern browsers. It's perfect for creating smooth scrolling experiences on your website such as WebGL scroll syncing, parallax effects, and much more, see [Demo](https://lenis.darkroom.engineering/) and [Showcase](https://www.lenis.dev/showcase).\n\nRead our [Manifesto](https://github.com/darkroomengineering/lenis/blob/main/MANIFESTO.md) to learn more about the inspiration behind Lenis.\n\n<br/>\n\n- [Sponsors](#sponsors)\n- [Packages](#packages)\n- [Showcase](https://www.lenis.dev/showcase)\n- [Installation](#installation)\n- [Setup](#setup)\n- [Settings](#settings)\n- [Properties](#properties)\n- [Methods](#methods)\n- [Events](#events)\n- [Considerations](#considerations)\n- [Limitations](#limitations)\n- [Troubleshooting](#troubleshooting)\n- [Tutorials](#tutorials)\n- [Plugins](#plugins)\n- [License](#license)\n\n<br/>\n\n## Sponsors\n\nIf you’ve used Lenis and it made your site feel just a little more alive, consider [sponsoring](https://github.com/sponsors/darkroomengineering).\n\nYour support helps us smooth out the internet one library at a time—and lets us keep building tools that care about the details most folks overlook.\n\n<a href=\"https://www.osmo.supply/?utm_source=lenis.dev\"><img src=\"https://www.lenis.dev/sponsors/osmo.png\" width=\"128\"/></a>\n<br/>\n\n<!-- sponsors -->\n[![Jesse Winton](https://img.logo.dev/cosmos.so?size=64&token=pk_E-KcYZmdT--jxwGY3dAs1Q&fallback=404)](mailto:jesse@cosmos.so) [![smsunarto](https://github.com/smsunarto.png?size=64)](https://github.com/smsunarto) [![bizarro](https://github.com/bizarro.png?size=64)](https://github.com/bizarro) [![itsoffbrand](https://github.com/itsoffbrand.png?size=64)](https://github.com/itsoffbrand) [![arkconclave](https://github.com/arkconclave.png?size=64)](https://github.com/arkconclave) [![Tamas Bodo](https://img.logo.dev/framerpod.com?size=64&token=pk_E-KcYZmdT--jxwGY3dAs1Q&fallback=404)](mailto:hello@framerpod.com) [![glauber-sampaio](https://github.com/glauber-sampaio.png?size=64)](https://github.com/glauber-sampaio) [![cachet-studio](https://github.com/cachet-studio.png?size=64)](https://github.com/cachet-studio) [![OHO-Design](https://github.com/OHO-Design.png?size=64)](https://github.com/OHO-Design) [![joevingracien](https://github.com/joevingracien.png?size=64)](https://github.com/joevingracien) [![Lazar Filipovic](https://ui-avatars.com/api/?name=Lazar+Filipovic&size=64)](mailto:webdesignbylazar@gmail.com)\n<!-- sponsors -->\n\n<br/>\n<a href=\"https://vercel.com/oss\">\n  <img alt=\"Vercel OSS Program\" src=\"https://vercel.com/oss/program-badge.svg\" />\n</a>\n\n<br/>\n\n## Packages\n\n- [lenis](https://github.com/darkroomengineering/lenis/blob/main/README.md)\n- [lenis/react](https://github.com/darkroomengineering/lenis/blob/main/packages/react/README.md)\n- [lenis/vue](https://github.com/darkroomengineering/lenis/tree/main/packages/vue/README.md)\n- [lenis/framer](https://lenis.framer.website/)\n- [lenis/snap](https://github.com/darkroomengineering/lenis/tree/main/packages/snap/README.md)\n\n\n<br/>\n\n## Installation\n\nUsing a package manager:\n\n```bash\nnpm i lenis\n# or\nyarn add lenis\n# or\npnpm add lenis\n```\n\n```js\nimport Lenis from 'lenis'\n```\n\n<br/>\n\nUsing scripts:\n\n```html\n<script src=\"https://unpkg.com/lenis@1.3.19/dist/lenis.min.js\"></script> \n```\n\n\n<br/>\n\n## Setup\n\n### Basic:\n\n```js\n// Initialize Lenis\nconst lenis = new Lenis({\n  autoRaf: true,\n});\n\n// Listen for the scroll event and log the event data\nlenis.on('scroll', (e) => {\n  console.log(e);\n});\n```\n\n### Custom raf loop:\n\n```js\n// Initialize Lenis\nconst lenis = new Lenis();\n\n// Use requestAnimationFrame to continuously update the scroll\nfunction raf(time) {\n  lenis.raf(time);\n  requestAnimationFrame(raf);\n}\n\nrequestAnimationFrame(raf);\n```\n\n### Recommended CSS:\n\n**Import stylesheet:**\n```js\nimport 'lenis/dist/lenis.css'\n```\n\n**Or link the CSS file:**\n\n```html\n<link rel=\"stylesheet\" href=\"https://unpkg.com/lenis@1.3.19/dist/lenis.css\">\n```\n\n**Or add it manually:**\n\n[See lenis.css stylesheet](./packages/core/lenis.css)\n\n### GSAP ScrollTrigger:\n```js\n// Initialize a new Lenis instance for smooth scrolling\nconst lenis = new Lenis();\n\n// Synchronize Lenis scrolling with GSAP's ScrollTrigger plugin\nlenis.on('scroll', ScrollTrigger.update);\n\n// Add Lenis's requestAnimationFrame (raf) method to GSAP's ticker\n// This ensures Lenis's smooth scroll animation updates on each GSAP tick\ngsap.ticker.add((time) => {\n  lenis.raf(time * 1000); // Convert time from seconds to milliseconds\n});\n\n// Disable lag smoothing in GSAP to prevent any delay in scroll animations\ngsap.ticker.lagSmoothing(0);\n\n```\n\n### React:\n[See documentation for lenis/react](https://github.com/darkroomengineering/lenis/blob/main/packages/react/README.md).\n\n\n\n\n<br/>\n\n\n## Settings\n\n| Option                  | Type                       | Default                                            | Description                                                                                                                                                                                                                                                                          |\n|-------------------------|----------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `wrapper`               | `HTMLElement, Window`      | `window`                                           | The element that will be used as the scroll container.                                                                                                                                                                                                                               |\n| `content`               | `HTMLElement`              | `document.documentElement`                         | The element that contains the content that will be scrolled, usually `wrapper`'s direct child.                                                                                                                                                                                       |\n| `eventsTarget`          | `HTMLElement, Window`      | `wrapper`                                          | The element that will listen to `wheel` and `touch` events.                                                                                                                                                                                                                          |\n| `smoothWheel`           | `boolean`                  | `true`                                             | Smooth the scroll initiated by `wheel` events.                                                                                                                                                                                                                                       |\n| `lerp`                  | `number`                   | `0.1`                                              | Linear interpolation (lerp) intensity (between 0 and 1).                                                                                                                                                                                                                             |\n| `duration`              | `number`                   | `1.2`                                              | The duration of scroll animation (in seconds). Useless if lerp defined.                                                                                                                                                                                                              |\n| `easing`                | `function`                 | `(t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))` | The easing function to use for the scroll animation, our default is custom but you can pick one from [Easings.net](https://easings.net/en). Useless if lerp defined.                                                                                                                 |\n| `orientation`           | `string`                   | `vertical`                                         | The orientation of the scrolling. Can be `vertical` or `horizontal`.                                                                                                                                                                                                                 |\n| `gestureOrientation`    | `string`                   | `vertical`                                         | The orientation of the gestures. Can be `vertical`, `horizontal` or `both`.                                                                                                                                                                                                          |\n| `syncTouch`             | `boolean`                  | `false`                                            | Mimic touch device scroll while allowing scroll sync (can be unstable on iOS<16).                                                                                                                                                                                                    |\n| `syncTouchLerp`         | `number`                   | `0.075`                                            | Lerp applied during `syncTouch` inertia.                                                                                                                                                                                                                                             |\n| `touchInertiaExponent`  | `number`                   | `1.7`                                              | Manage the strength of syncTouch inertia.                                                                                                                                                                                                                                            |\n| `wheelMultiplier`       | `number`                   | `1`                                                | The multiplier to use for mouse wheel events.                                                                                                                                                                                                                                        |\n| `touchMultiplier`       | `number`                   | `1`                                                | The multiplier to use for touch events.                                                                                                                                                                                                                                              |\n| `infinite`              | `boolean`                  | `false`                                            | Enable infinite scrolling! `syncTouch: true` is required on touch devices ([See example](https://codepen.io/ClementRoche/pen/OJqBLod)).                                                                                                                                              |\n| `autoResize`            | `boolean`                  | `true`                                             | Resize instance automatically       based on `ResizeObserver`. If `false` you must resize manually using `.resize()`.                                                                                                                                                                |\n| `prevent`               | `function`                 | `undefined`                                        | Manually prevent scroll to be smoothed based on elements traversed by events. If `true` is returned, it will prevent the scroll to be smoothed. Example: `(node) =>  node.classList.contains('cookie-modal')`.                                                                       |\n| `virtualScroll`         | `function`                 | `undefined`                                        | Manually modify the events before they get consumed. If `false` is returned, the scroll will not be smoothed. Examples: `(e) => { e.deltaY /= 2 }` (to slow down vertical scroll) or `({ event }) => !event.shiftKey` (to prevent scroll to be smoothed if shift key is pressed).    |\n| `overscroll`            | `boolean`                  | `true`                                             | Similar to CSS overscroll-behavior (https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior).                                                                                                                                                                           |\n| `autoRaf`               | `boolean`                  | `false`                                            | Whether or not to automatically run `requestAnimationFrame` loop.                                                                                                                                                                                                                    |\n| `anchors`               | `boolean, ScrollToOptions` | `false`                                            | Scroll to anchor links when clicked. If `true` is passed, it will enable anchor links with default options. If `ScrollToOptions` is passed, it will enable anchor links with the given options.                                                                                      |\n| `autoToggle`            | `boolean`                  | `false`                                            | Automatically start or stop the lenis instance based on the wrapper's overflow property, ⚠️ this requires Lenis recommended CSS. Safari > 17.3, Chrome > 116 and Firefox > 128 ([https://caniuse.com/?search=transition-behavior](https://caniuse.com/?search=transition-behavior)). |\n| `allowNestedScroll`     | `boolean`                  | `false`                                            | Automatically allow nested scrollable elements to scroll natively. This is the simplest way to handle nested scroll. ⚠️ Can create performance issues since it checks the DOM tree on every scroll event. If that's a concern, use `data-lenis-prevent` attributes instead.          |\n| `naiveDimensions`       | `boolean`                  | `false`                                            | If `true`, Lenis will use naive dimensions calculation. ⚠️ Be careful, this has a performance impact.                                                                                                                                                                                |\n| `stopInertiaOnNavigate` | `boolean`                  | `false`                                            | If `true`, Lenis will stop inertia when an internal link is clicked.                                                                                                                                                                                                                 |\n<br/>\n\n<!-- `target`: goal to reach\n- `number`: value to scroll in pixels\n- `string`: CSS selector or keyword (`top`, `left`, `start`, `bottom`, `right`, `end`)\n- `HTMLElement`: DOM element\n\n<br/>\n\n`options`:\n- `offset`(`number`): equivalent to [`scroll-padding-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top)\n- `lerp`(`number`): animation lerp intensity\n- `duration`(`number`): animation duration (in seconds)\n- `easing`(`function`): animation easing\n- `immediate`(`boolean`): ignore duration, easing and lerp\n- `lock`(`boolean`): whether or not to prevent user from scrolling until target reached\n- `onComplete`(`function`): called when target is reached -->\n\n## Properties\n\n| Property                | Type              | Description                                                                |\n|-------------------------|-------------------|----------------------------------------------------------------------------|\n| `animatedScroll`        | `number`          | Current scroll value                                                       |\n| `dimensions`            | `object`          | Dimensions instance                                                        |\n| `direction`             | `number`          | `1`: scrolling up, `-1`: scrolling down                                    |\n `options`               | `object`          | Instance options                                                           |\n| `targetScroll`          | `number`          | Target scroll value                                                        |\n| `time`                  | `number`          | Time elapsed since instance creation                                       |\n| `actualScroll`          | `number`          | Current scroll value registered by the browser                             |\n| `lastVelocity`          | `number`          | last scroll velocity                                                       |\n| `velocity`              | `number`          | Current scroll velocity                                                    |\n| `isHorizontal` (getter) | `boolean`         | Whether or not the instance is horizontal                                  |\n| `isScrolling` (getter)  | `boolean, string` | Whether or not the scroll is being animated, `smooth`, `native` or `false` |\n| `isStopped` (getter)    | `boolean`         | Whether or not the user should be able to scroll                           |\n| `limit` (getter)        | `number`          | Maximum scroll value                                                       |\n| `progress` (getter)     | `number`          | Scroll progress from `0` to `1`                                            |\n| `rootElement` (getter)  | `HTMLElement`     | Element on which Lenis is instanced                                        |\n| `scroll` (getter)       | `number`          | Current scroll value (handles infinite scroll if activated)                |\n| `className` (getter)    | `string`          | `rootElement` className                                                    |\n\n<br/>\n\n## Methods\n\n| Method                      | Description                                                                     | Arguments                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n|-----------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `raf(time)`                 | Must be called every frame for internal usage.                                  | `time`: in ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| `scrollTo(target, options)` | Scroll to target.                                                               | `target`: goal to reach<ul><li>`number`: value to scroll in pixels</li><li>`string`: CSS selector or keyword (`top`, `left`, `start`, `bottom`, `right`, `end`)</li><li>`HTMLElement`: DOM element</li></ul>`options`<ul><li>`offset`(`number`): equivalent to [`scroll-padding-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top)</li><li>`lerp`(`number`): animation lerp intensity</li><li>`duration`(`number`): animation duration (in seconds)</li><li>`easing`(`function`): animation easing</li><li>`immediate`(`boolean`): ignore duration, easing and lerp</li><li>`lock`(`boolean`): whether or not to prevent the user from scrolling until the target is reached</li><li>`force`(`boolean`): reach target even if instance is stopped</li><li>`onComplete`(`function`): called when the target is reached</li><li>`userData`(`object`): this object will be forwarded through `scroll` events</li></ul> |\n| `on(id, function)`          | `id` can be any of the following [instance events](#instance-events) to listen. |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `stop()`                    | Pauses the scroll                                                               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `start()`                   | Resumes the scroll                                                              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `resize()`                  | Compute internal sizes, it has to be used if `autoResize` option is `false`.    |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| `destroy()`                 | Destroys the instance and removes all events.                                   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n\n\n\n## Events\n\n| Event            | Callback Arguments        |\n|------------------|---------------------------|\n| `scroll`         | Lenis instance            |\n| `virtual-scroll` | `{deltaX, deltaY, event}` |\n\n\n<br/>\n\n## Considerations\n\n### Nested scroll\n\nThe simplest and most reliable way to handle nested scrollable elements is to use the `allowNestedScroll` option:\n\n```js\nconst lenis = new Lenis({\n  allowNestedScroll: true,\n})\n```\n\nThis automatically detects nested scrollable elements and lets them scroll natively. However, this can create performance issues since Lenis needs to check the DOM tree on every scroll event. If you experience performance problems, use `data-lenis-prevent` instead.\n\n#### Using HTML attributes\n\n```html\n<div data-lenis-prevent>scrollable content</div>\n```\n\n[See example](https://codepen.io/ClementRoche/pen/PoLdjpw)\n\n| Attribute                       | Description                          |\n|---------------------------------|--------------------------------------|\n| `data-lenis-prevent`            | Prevent all smooth scroll events     |\n| `data-lenis-prevent-wheel`      | Prevent wheel events only            |\n| `data-lenis-prevent-touch`      | Prevent touch events only            |\n| `data-lenis-prevent-vertical`   | Prevent vertical scroll events only  |\n| `data-lenis-prevent-horizontal` | Prevent horizontal scroll events only|\n\n#### Using Javascript\n\n```html\n<div id=\"modal\">scrollable content</div>\n```\n\n```js\nconst lenis = new Lenis({\n  prevent: (node) => node.id === 'modal',\n})\n```\n\n[See example](https://codepen.io/ClementRoche/pen/emONGYN)\n\n\n\n### Anchor links\nBy default, Lenis will prevent anchor links from working while scrolling. To enable them, you must set `anchors: true`.\n\n```js\nnew Lenis({\n  anchors: true\n})\n```\n\nYou can also use `scrollTo` options:\n\n```js\nnew Lenis({\n  anchors: {\n    offset: 100,\n    onComplete: ()=>{\n      console.log('scrolled to anchor')\n    }\n  }\n})\n```\n\n<br/>\n\n## Limitations\n\n- no support for CSS scroll-snap, you must use ([lenis/snap](https://github.com/darkroomengineering/lenis/tree/main/packages/snap/README.md))\n- capped to 60fps on Safari ([source](https://bugs.webkit.org/show_bug.cgi?id=173434)) and 30fps on low power mode\n- smooth scroll will stop working over iframe since they don't forward wheel events\n- position fixed seems to lag on MacOS Safari pre-M1 ([source](https://github.com/darkroomengineering/lenis/issues/103))\n- touch events may behave unexpectedly when `syncTouch` is enabled on iOS < 16\n- nested scroll containers require proper configuration to work correctly\n\n<br/>\n\n## Troubleshooting\n- Make sure you use the latest version of [Lenis](https://www.npmjs.com/package/lenis?activeTab=versions)\n- Include the recommended CSS\n- If using GSAP ScrollTrigger, ensure proper integration (see [GSAP ScrollTrigger setup](#setup) section)\n- Test without Lenis to ensure your element/page is scrollable\n- Be sure to use `autoRaf: true` or manually call `lenis.raf(time)` in your animation loop\n\n<br/>\n\n## Tutorials\n\n- [Scroll Animation Ideas for Image Grids](https://tympanus.net/Development/ScrollAnimationsGrid/) by [Codrops](https://tympanus.net/codrops)\n- [How to Animate SVG Shapes on Scroll](https://tympanus.net/codrops/2022/06/08/how-to-animate-svg-shapes-on-scroll) by [Codrops](https://tympanus.net/codrops)\n- [The BEST smooth scrolling library for your Webflow website! (Lenis)](https://www.youtube.com/watch?v=VtCqTLRRMII) by [Diego Toda de Oliveira](https://www.diegoliv.works/)\n- [Easy smooth scroll in @Webflow with Lenis + GSAP ScrollTrigger tutorial](https://www.youtube.com/watch?v=gRKuzQTXq74) by [También Studio](https://www.tambien.studio/)\n\n<br/>\n\n## Plugins\n\n- [r3f-scroll-rig](https://github.com/14islands/r3f-scroll-rig) by [14islands](https://14islands.com/)\n- [locomotive-scroll](https://github.com/locomotivemtl/locomotive-scroll) by [Locomotive](https://locomotive.ca/)\n\n<br/>\n\n## License\n\nMIT © [darkroom.engineering](https://github.com/darkroomengineering)\n"
  },
  {
    "path": "biome.json",
    "content": "{\n  \"$schema\": \"node_modules/@biomejs/biome/configuration_schema.json\",\n\n  \"vcs\": {\n    \"enabled\": true,\n    \"clientKind\": \"git\",\n    \"useIgnoreFile\": true\n  },\n\n  \"files\": {\n    \"ignoreUnknown\": true,\n    \"includes\": [\n      \"**\",\n      \"!node_modules\",\n      \"!**/.next\",\n      \"!**/dist\",\n      \"!**/public\",\n      \"!.github\",\n      \"!.vercel\",\n      \"!pnpm-lock.yaml\",\n      \"!bun.lock\",\n      \"!**/*.md\",\n      \"!**/*.mdx\",\n      \"!**/tailwind.css\",\n      \"!**/root.css\",\n      \"!**/*.grit\"\n    ]\n  },\n\n  \"formatter\": {\n    \"enabled\": true,\n    \"indentStyle\": \"space\",\n    \"indentWidth\": 2,\n    \"lineEnding\": \"lf\",\n    \"lineWidth\": 80\n  },\n\n  \"assist\": {\n    \"actions\": {\n      \"source\": {\n        \"organizeImports\": \"on\"\n      }\n    }\n  },\n\n  \"linter\": {\n    \"enabled\": true,\n    \"domains\": {\n      \"next\": \"recommended\",\n      \"react\": \"recommended\",\n      \"project\": \"recommended\"\n    },\n    \"rules\": {\n      \"correctness\": {\n        \"noUnusedImports\": \"error\",\n        \"noUnusedVariables\": \"error\",\n        \"noUnusedFunctionParameters\": \"warn\",\n        \"useExhaustiveDependencies\": \"warn\",\n        \"noUnknownMediaFeatureName\": \"off\",\n        \"noInvalidUseBeforeDeclaration\": \"error\"\n      },\n\n      \"style\": {\n        \"noNonNullAssertion\": \"off\",\n        \"noUnusedTemplateLiteral\": \"off\",\n        \"noParameterAssign\": \"error\",\n        \"useAsConstAssertion\": \"error\",\n        \"useDefaultParameterLast\": \"error\",\n        \"useEnumInitializers\": \"error\",\n        \"useSelfClosingElements\": \"error\",\n        \"useSingleVarDeclarator\": \"error\",\n        \"useNumberNamespace\": \"error\",\n        \"noInferrableTypes\": \"error\",\n        \"noUselessElse\": \"error\",\n        \"useConsistentArrayType\": \"error\",\n        \"useForOf\": \"warn\",\n        \"useShorthandAssign\": \"error\",\n        \"useTemplate\": \"warn\",\n        \"useCollapsedElseIf\": \"warn\",\n        \"useExponentiationOperator\": \"error\",\n        \"useConsistentBuiltinInstantiation\": \"error\",\n        \"useFilenamingConvention\": {\n          \"level\": \"warn\",\n          \"options\": {\n            \"filenameCases\": [\"kebab-case\", \"camelCase\"],\n            \"strictCase\": false\n          }\n        },\n        \"noNestedTernary\": \"error\"\n      },\n\n      \"suspicious\": {\n        \"noExplicitAny\": \"error\",\n        \"noEmptyBlockStatements\": \"warn\",\n        \"noDoubleEquals\": \"error\",\n        \"noDebugger\": \"warn\",\n        \"noGlobalIsFinite\": \"error\",\n        \"noGlobalIsNan\": \"error\",\n        \"noMisleadingCharacterClass\": \"error\",\n        \"noPrototypeBuiltins\": \"warn\",\n        \"noSelfCompare\": \"error\",\n        \"noSparseArray\": \"error\",\n        \"useAwait\": \"off\"\n      },\n\n      \"complexity\": {\n        \"noForEach\": \"off\",\n        \"useSimplifiedLogicExpression\": \"warn\",\n        \"useFlatMap\": \"warn\"\n      },\n\n      \"security\": {\n        \"noGlobalEval\": \"error\",\n        \"noDangerouslySetInnerHtml\": \"warn\",\n        \"noDangerouslySetInnerHtmlWithChildren\": \"error\"\n      },\n\n      \"a11y\": {\n        \"useKeyWithClickEvents\": \"warn\",\n        \"useValidAnchor\": \"warn\",\n        \"useAltText\": \"error\",\n        \"useButtonType\": \"error\",\n        \"useValidAriaProps\": \"error\",\n        \"useValidAriaRole\": \"error\",\n        \"useValidAriaValues\": \"error\",\n        \"noAriaUnsupportedElements\": \"error\",\n        \"noAutofocus\": \"warn\",\n        \"noDistractingElements\": \"error\",\n        \"noRedundantAlt\": \"error\",\n        \"useSemanticElements\": \"warn\"\n      },\n\n      \"performance\": {\n        \"noImgElement\": \"error\"\n      },\n\n      \"nursery\": {\n        \"useSortedClasses\": {\n          \"level\": \"error\",\n          \"fix\": \"safe\",\n          \"options\": {\n            \"attributes\": [\"class\", \"className\"],\n            \"functions\": [\"cn\", \"clsx\"]\n          }\n        }\n      }\n    }\n  },\n\n  \"javascript\": {\n    \"formatter\": {\n      \"enabled\": true,\n      \"quoteStyle\": \"single\",\n      \"semicolons\": \"asNeeded\",\n      \"trailingCommas\": \"es5\"\n    }\n  },\n\n  \"json\": {\n    \"parser\": {\n      \"allowComments\": true\n    }\n  },\n\n  \"css\": {\n    \"linter\": {\n      \"enabled\": true\n    },\n    \"formatter\": {\n      \"enabled\": true\n    },\n    \"parser\": {\n      \"cssModules\": true\n    }\n  },\n\n  \"overrides\": [\n    {\n      \"includes\": [\"**/*.css\"],\n      \"linter\": {\n        \"rules\": {\n          \"correctness\": {\n            \"noUnknownFunction\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"**/*.tsx\", \"**/*.jsx\"],\n      \"linter\": {\n        \"rules\": {\n          \"correctness\": {\n            \"useJsxKeyInIterable\": \"error\"\n          },\n          \"a11y\": {\n            \"useValidAnchor\": \"error\",\n            \"useKeyWithClickEvents\": \"error\",\n            \"useKeyWithMouseEvents\": \"error\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"**/*.ts\", \"**/*.tsx\"],\n      \"linter\": {\n        \"rules\": {\n          \"style\": {\n            \"useImportType\": \"error\",\n            \"useExportType\": \"error\",\n            \"useConsistentArrayType\": \"error\"\n          },\n          \"correctness\": {\n            \"noUndeclaredVariables\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\n        \"app/**/*.tsx\",\n        \"app/**/*.ts\",\n        \"app/**/*.jsx\",\n        \"app/**/*.js\"\n      ],\n      \"linter\": {\n        \"rules\": {\n          \"style\": {\n            \"noDefaultExport\": \"off\"\n          },\n          \"suspicious\": {\n            \"useAwait\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"**/*.module.css\"],\n      \"linter\": {\n        \"rules\": {\n          \"correctness\": {\n            \"noUnknownProperty\": \"off\"\n          },\n          \"style\": {\n            \"noDescendingSpecificity\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"lib/styles/css/root.css\"],\n      \"linter\": {\n        \"rules\": {\n          \"suspicious\": {\n            \"noDuplicateCustomProperties\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"**/*.vue\"],\n      \"linter\": {\n        \"rules\": {\n          \"correctness\": {\n            \"useHookAtTopLevel\": \"off\"\n          },\n          \"style\": {\n            \"useFilenamingConvention\": \"off\"\n          }\n        }\n      }\n    },\n    {\n      \"includes\": [\"**/*.astro\"],\n      \"linter\": {\n        \"rules\": {\n          \"correctness\": {\n            \"noUnusedImports\": \"off\",\n            \"noUnusedVariables\": \"off\"\n          },\n          \"style\": {\n            \"useFilenamingConvention\": \"off\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lenis\",\n  \"version\": \"1.3.19\",\n  \"description\": \"How smooth scroll should be\",\n  \"type\": \"module\",\n  \"sideEffects\": false,\n  \"author\": \"darkroom.engineering\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/darkroomengineering/lenis.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/darkroomengineering/lenis/issues\"\n  },\n  \"homepage\": \"https://github.com/darkroomengineering/lenis\",\n  \"funding\": {\n    \"type\": \"github\",\n    \"url\": \"https://github.com/sponsors/darkroomengineering\"\n  },\n  \"keywords\": [\n    \"scroll\",\n    \"smooth\",\n    \"lenis\",\n    \"react\",\n    \"vue\"\n  ],\n  \"workspaces\": [\n    \"packages/*\",\n    \"playground\",\n    \"playground/*\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"dev\": \"bun run --parallel dev:build dev:playground\",\n    \"dev:build\": \"tsdown --watch\",\n    \"dev:playground\": \"bun --filter playground dev\",\n    \"dev:nuxt\": \"bun --filter playground-nuxt dev\",\n    \"readme\": \"node ./scripts/update-readme.js\",\n    \"version:dev\": \"npm version prerelease --preid dev --force --no-git-tag-version\",\n    \"version:patch\": \"npm version patch --force --no-git-tag-version\",\n    \"version:minor\": \"npm version minor --force --no-git-tag-version\",\n    \"version:major\": \"npm version major --force --no-git-tag-version\",\n    \"postversion\": \"bun run build && bun run readme\",\n    \"publish:dev\": \"npm publish --tag dev\",\n    \"publish:main\": \"npm publish\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@biomejs/biome\": \"^2.4.2\",\n    \"tsdown\": \"^0.21.4\",\n    \"typescript\": \"^5.7.3\"\n  },\n  \"peerDependencies\": {\n    \"@nuxt/kit\": \">=3.0.0\",\n    \"react\": \">=17.0.0\",\n    \"vue\": \">=3.0.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"react\": {\n      \"optional\": true\n    },\n    \"vue\": {\n      \"optional\": true\n    },\n    \"@nuxt/kit\": {\n      \"optional\": true\n    }\n  },\n  \"unpkg\": \"./dist/lenis.mjs\",\n  \"main\": \"./dist/lenis.mjs\",\n  \"module\": \"./dist/lenis.mjs\",\n  \"types\": \"./dist/lenis.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/lenis.d.ts\",\n      \"default\": \"./dist/lenis.mjs\"\n    },\n    \"./react\": {\n      \"types\": \"./dist/lenis-react.d.ts\",\n      \"default\": \"./dist/lenis-react.mjs\"\n    },\n    \"./snap\": {\n      \"types\": \"./dist/lenis-snap.d.ts\",\n      \"default\": \"./dist/lenis-snap.mjs\"\n    },\n    \"./vue\": {\n      \"types\": \"./dist/lenis-vue.d.ts\",\n      \"default\": \"./dist/lenis-vue.mjs\"\n    },\n    \"./nuxt\": {\n      \"default\": \"./dist/lenis-vue-nuxt.mjs\"\n    },\n    \"./nuxt/runtime/*\": {\n      \"default\": \"./dist/nuxt/runtime/*.mjs\"\n    },\n    \"./dist/*\": \"./dist/*\"\n  }\n}\n"
  },
  {
    "path": "packages/core/browser.ts",
    "content": "// This file serves as an entry point for the package\nimport { Lenis } from './src/lenis'\n\n// @ts-expect-error\nglobalThis.Lenis = Lenis\n// @ts-expect-error\nglobalThis.Lenis.prototype = Lenis.prototype\n"
  },
  {
    "path": "packages/core/index.ts",
    "content": "// This file serves as an entry point for the package\nexport { Lenis as default } from './src/lenis'\nexport * from './src/types'\n"
  },
  {
    "path": "packages/core/lenis.css",
    "content": "html.lenis,\nhtml.lenis body {\n  height: auto;\n}\n\n.lenis:not(.lenis-autoToggle).lenis-stopped {\n  overflow: clip;\n}\n\n.lenis [data-lenis-prevent],\n.lenis [data-lenis-prevent-wheel],\n.lenis [data-lenis-prevent-touch],\n.lenis [data-lenis-prevent-vertical],\n.lenis [data-lenis-prevent-horizontal] {\n  overscroll-behavior: contain;\n}\n\n.lenis.lenis-smooth iframe {\n  pointer-events: none;\n}\n\n.lenis.lenis-autoToggle {\n  transition-property: overflow;\n  transition-duration: 1ms;\n  transition-behavior: allow-discrete;\n}\n"
  },
  {
    "path": "packages/core/package.json",
    "content": "{\n  \"name\": \"lenis-core\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/core/src/animate.ts",
    "content": "import { clamp, damp } from './maths'\nimport type { EasingFunction, FromToOptions, OnUpdateCallback } from './types'\n\n/**\n * Animate class to handle value animations with lerping or easing\n *\n * @example\n * const animate = new Animate()\n * animate.fromTo(0, 100, { duration: 1, easing: (t) => t })\n * animate.advance(0.5) // 50\n */\nexport class Animate {\n  isRunning = false\n  value = 0\n  from = 0\n  to = 0\n  currentTime = 0\n\n  // These are instanciated in the fromTo method\n  lerp?: number\n  duration?: number\n  easing?: EasingFunction\n  onUpdate?: OnUpdateCallback\n\n  /**\n   * Advance the animation by the given delta time\n   *\n   * @param deltaTime - The time in seconds to advance the animation\n   */\n  advance(deltaTime: number) {\n    if (!this.isRunning) return\n\n    let completed = false\n\n    if (this.duration && this.easing) {\n      this.currentTime += deltaTime\n      const linearProgress = clamp(0, this.currentTime / this.duration, 1)\n\n      completed = linearProgress >= 1\n      const easedProgress = completed ? 1 : this.easing(linearProgress)\n      this.value = this.from + (this.to - this.from) * easedProgress\n    } else if (this.lerp) {\n      this.value = damp(this.value, this.to, this.lerp * 60, deltaTime)\n      if (Math.round(this.value) === this.to) {\n        this.value = this.to\n        completed = true\n      }\n    } else {\n      // If no easing or lerp, just jump to the end value\n      this.value = this.to\n      completed = true\n    }\n\n    if (completed) {\n      this.stop()\n    }\n\n    // Call the onUpdate callback with the current value and completed status\n    this.onUpdate?.(this.value, completed)\n  }\n\n  /** Stop the animation */\n  stop() {\n    this.isRunning = false\n  }\n\n  /**\n   * Set up the animation from a starting value to an ending value\n   * with optional parameters for lerping, duration, easing, and onUpdate callback\n   *\n   * @param from - The starting value\n   * @param to - The ending value\n   * @param options - Options for the animation\n   */\n  fromTo(\n    from: number,\n    to: number,\n    { lerp, duration, easing, onStart, onUpdate }: FromToOptions\n  ) {\n    this.from = this.value = from\n    this.to = to\n    this.lerp = lerp\n    this.duration = duration\n    this.easing = easing\n    this.currentTime = 0\n    this.isRunning = true\n\n    onStart?.()\n    this.onUpdate = onUpdate\n  }\n}\n"
  },
  {
    "path": "packages/core/src/debounce.ts",
    "content": "export function debounce<CB extends (...args: unknown[]) => void>(\n  callback: CB,\n  delay: number\n) {\n  let timer: number | undefined\n  return function <T>(this: T, ...args: Parameters<typeof callback>) {\n    clearTimeout(timer)\n    timer = setTimeout(() => {\n      timer = undefined\n      callback.apply(this, args)\n    }, delay)\n  }\n}\n"
  },
  {
    "path": "packages/core/src/dimensions.ts",
    "content": "import { debounce } from './debounce'\n\n/**\n * Dimensions class to handle the size of the content and wrapper\n *\n * @example\n * const dimensions = new Dimensions(wrapper, content)\n * dimensions.on('resize', (e) => {\n *   console.log(e.width, e.height)\n * })\n */\nexport class Dimensions {\n  width = 0\n  height = 0\n  scrollHeight = 0\n  scrollWidth = 0\n\n  // These are instanciated in the constructor as they need information from the options\n  private debouncedResize?: (...args: unknown[]) => void\n  private wrapperResizeObserver?: ResizeObserver\n  private contentResizeObserver?: ResizeObserver\n\n  constructor(\n    private wrapper: HTMLElement | Window | Element,\n    private content: HTMLElement | Element,\n    { autoResize = true, debounce: debounceValue = 250 } = {}\n  ) {\n    if (autoResize) {\n      this.debouncedResize = debounce(this.resize, debounceValue)\n\n      if (this.wrapper instanceof Window) {\n        window.addEventListener('resize', this.debouncedResize)\n      } else {\n        this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize)\n        this.wrapperResizeObserver.observe(this.wrapper)\n      }\n\n      this.contentResizeObserver = new ResizeObserver(this.debouncedResize)\n      this.contentResizeObserver.observe(this.content)\n    }\n\n    this.resize()\n  }\n\n  destroy() {\n    this.wrapperResizeObserver?.disconnect()\n    this.contentResizeObserver?.disconnect()\n\n    if (this.wrapper === window && this.debouncedResize) {\n      window.removeEventListener('resize', this.debouncedResize)\n    }\n  }\n\n  resize = () => {\n    this.onWrapperResize()\n    this.onContentResize()\n  }\n\n  onWrapperResize = () => {\n    if (this.wrapper instanceof Window) {\n      this.width = window.innerWidth\n      this.height = window.innerHeight\n    } else {\n      this.width = this.wrapper.clientWidth\n      this.height = this.wrapper.clientHeight\n    }\n  }\n\n  onContentResize = () => {\n    if (this.wrapper instanceof Window) {\n      this.scrollHeight = this.content.scrollHeight\n      this.scrollWidth = this.content.scrollWidth\n    } else {\n      this.scrollHeight = this.wrapper.scrollHeight\n      this.scrollWidth = this.wrapper.scrollWidth\n    }\n  }\n\n  get limit() {\n    return {\n      x: this.scrollWidth - this.width,\n      y: this.scrollHeight - this.height,\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/emitter.ts",
    "content": "/**\n * Emitter class to handle events\n * @example\n * const emitter = new Emitter()\n * emitter.on('event', (data) => {\n *   console.log(data)\n * })\n * emitter.emit('event', 'data')\n */\nexport class Emitter {\n  private events: Record<\n    string,\n    Array<(...args: unknown[]) => void> | undefined\n  > = {}\n\n  /**\n   * Emit an event with the given data\n   * @param event Event name\n   * @param args Data to pass to the event handlers\n   */\n  emit(event: string, ...args: unknown[]) {\n    const callbacks = this.events[event] || []\n    for (let i = 0, length = callbacks.length; i < length; i++) {\n      callbacks[i]?.(...args)\n    }\n  }\n\n  /**\n   * Add a callback to the event\n   * @param event Event name\n   * @param cb Callback function\n   * @returns Unsubscribe function\n   */\n  on<CB extends (...args: unknown[]) => void>(event: string, cb: CB) {\n    // Add the callback to the event's callback list, or create a new list with the callback\n    if (this.events[event]) {\n      this.events[event].push(cb)\n    } else {\n      this.events[event] = [cb]\n    }\n\n    // Return an unsubscribe function\n    return () => {\n      this.events[event] = this.events[event]?.filter((i) => cb !== i)\n    }\n  }\n\n  /**\n   * Remove a callback from the event\n   * @param event Event name\n   * @param callback Callback function\n   */\n  off<CB extends (...args: unknown[]) => void>(event: string, callback: CB) {\n    this.events[event] = this.events[event]?.filter((i) => callback !== i)\n  }\n\n  /**\n   * Remove all event listeners and clean up\n   */\n  destroy() {\n    this.events = {}\n  }\n}\n"
  },
  {
    "path": "packages/core/src/lenis.ts",
    "content": "import { version } from '../../../package.json'\nimport { Animate } from './animate'\nimport { Dimensions } from './dimensions'\nimport { Emitter } from './emitter'\nimport { clamp, modulo } from './maths'\nimport type {\n  LenisEvent,\n  LenisOptions,\n  ScrollCallback,\n  Scrolling,\n  ScrollToOptions,\n  UserData,\n  VirtualScrollCallback,\n  VirtualScrollData,\n} from './types'\nimport { VirtualScroll } from './virtual-scroll'\n\n// Technical explanation\n// - listen to 'wheel' events\n// - prevent 'wheel' event to prevent scroll\n// - normalize wheel delta\n// - add delta to targetScroll\n// - animate scroll to targetScroll (smooth context)\n// - if animation is not running, listen to 'scroll' events (native context)\n\ntype OptionalPick<T, F extends keyof T> = Omit<T, F> & Partial<Pick<T, F>>\n\nconst defaultEasing = (t: number) => Math.min(1, 1.001 - 2 ** (-10 * t))\n\nexport class Lenis {\n  private _isScrolling: Scrolling = false // true when scroll is animating\n  private _isStopped = false // true if user should not be able to scroll - enable/disable programmatically\n  private _isLocked = false // same as isStopped but enabled/disabled when scroll reaches target\n  private _preventNextNativeScrollEvent = false\n  private _resetVelocityTimeout: ReturnType<typeof setTimeout> | null = null\n  private _rafId: number | null = null\n\n  /**\n   * Whether or not the user is touching the screen\n   */\n  isTouching?: boolean\n  /**\n   * The time in ms since the lenis instance was created\n   */\n  time = 0\n  /**\n   * User data that will be forwarded through the scroll event\n   *\n   * @example\n   * lenis.scrollTo(100, {\n   *   userData: {\n   *     foo: 'bar'\n   *   }\n   * })\n   */\n  userData: UserData = {}\n  /**\n   * The last velocity of the scroll\n   */\n  lastVelocity = 0\n  /**\n   * The current velocity of the scroll\n   */\n  velocity = 0\n  /**\n   * The direction of the scroll\n   */\n  direction: 1 | -1 | 0 = 0\n  /**\n   * The options passed to the lenis instance\n   */\n  options: OptionalPick<\n    Required<LenisOptions>,\n    | 'duration'\n    | 'easing'\n    | 'prevent'\n    | 'virtualScroll'\n    | '__experimental__naiveDimensions'\n  >\n  /**\n   * The target scroll value\n   */\n  targetScroll: number\n  /**\n   * The animated scroll value\n   */\n  animatedScroll: number\n\n  // These are instanciated here as they don't need information from the options\n  private readonly animate = new Animate()\n  private readonly emitter = new Emitter()\n  // These are instanciated in the constructor as they need information from the options\n  readonly dimensions: Dimensions // This is not private because it's used in the Snap class\n  private readonly virtualScroll: VirtualScroll\n\n  constructor({\n    wrapper = window,\n    content = document.documentElement,\n    eventsTarget = wrapper,\n    smoothWheel = true,\n    syncTouch = false,\n    syncTouchLerp = 0.075,\n    touchInertiaExponent = 1.7,\n    duration, // in seconds\n    easing,\n    lerp = 0.1,\n    infinite = false,\n    orientation = 'vertical', // vertical, horizontal\n    gestureOrientation = orientation === 'horizontal' ? 'both' : 'vertical', // vertical, horizontal, both\n    touchMultiplier = 1,\n    wheelMultiplier = 1,\n    autoResize = true,\n    prevent,\n    virtualScroll,\n    overscroll = true,\n    autoRaf = false,\n    anchors = false,\n    autoToggle = false, // https://caniuse.com/?search=transition-behavior\n    allowNestedScroll = false,\n    __experimental__naiveDimensions = false,\n    naiveDimensions = __experimental__naiveDimensions,\n    stopInertiaOnNavigate = false,\n  }: LenisOptions = {}) {\n    // Set version (deprecated)\n    window.lenisVersion = version\n\n    if (!window.lenis) {\n      window.lenis = {}\n    }\n\n    window.lenis.version = version\n\n    if (orientation === 'horizontal') {\n      window.lenis.horizontal = true\n    }\n\n    if (syncTouch === true) {\n      window.lenis.touch = true\n    }\n\n    // Check if wrapper is <html>, fallback to window\n    if (!wrapper || wrapper === document.documentElement) {\n      wrapper = window\n    }\n\n    // flip to easing/time based animation if at least one of them is provided\n    if (typeof duration === 'number' && typeof easing !== 'function') {\n      easing = defaultEasing\n    } else if (typeof easing === 'function' && typeof duration !== 'number') {\n      duration = 1\n    }\n\n    // Setup options\n    this.options = {\n      wrapper,\n      content,\n      eventsTarget,\n      smoothWheel,\n      syncTouch,\n      syncTouchLerp,\n      touchInertiaExponent,\n      duration,\n      easing,\n      lerp,\n      infinite,\n      gestureOrientation,\n      orientation,\n      touchMultiplier,\n      wheelMultiplier,\n      autoResize,\n      prevent,\n      virtualScroll,\n      overscroll,\n      autoRaf,\n      anchors,\n      autoToggle,\n      allowNestedScroll,\n      naiveDimensions,\n      stopInertiaOnNavigate,\n    }\n\n    // Setup dimensions instance\n    this.dimensions = new Dimensions(wrapper, content, { autoResize })\n\n    // Setup class name\n    this.updateClassName()\n\n    // Set the initial scroll value for all scroll information\n    this.targetScroll = this.animatedScroll = this.actualScroll\n\n    // Add event listeners\n    this.options.wrapper.addEventListener('scroll', this.onNativeScroll)\n\n    this.options.wrapper.addEventListener('scrollend', this.onScrollEnd, {\n      capture: true,\n    })\n\n    if (this.options.anchors || this.options.stopInertiaOnNavigate) {\n      this.options.wrapper.addEventListener(\n        'click',\n        this.onClick as EventListener\n      )\n    }\n\n    this.options.wrapper.addEventListener(\n      'pointerdown',\n      this.onPointerDown as EventListener\n    )\n\n    // Setup virtual scroll instance\n    this.virtualScroll = new VirtualScroll(eventsTarget as HTMLElement, {\n      touchMultiplier,\n      wheelMultiplier,\n    })\n    this.virtualScroll.on('scroll', this.onVirtualScroll)\n\n    if (this.options.autoToggle) {\n      this.checkOverflow()\n      this.rootElement.addEventListener('transitionend', this.onTransitionEnd)\n    }\n\n    if (this.options.autoRaf) {\n      this._rafId = requestAnimationFrame(this.raf)\n    }\n  }\n\n  /**\n   * Destroy the lenis instance, remove all event listeners and clean up the class name\n   */\n  destroy() {\n    this.emitter.destroy()\n\n    this.options.wrapper.removeEventListener('scroll', this.onNativeScroll)\n\n    this.options.wrapper.removeEventListener('scrollend', this.onScrollEnd, {\n      capture: true,\n    })\n\n    this.options.wrapper.removeEventListener(\n      'pointerdown',\n      this.onPointerDown as EventListener\n    )\n\n    if (this.options.anchors || this.options.stopInertiaOnNavigate) {\n      this.options.wrapper.removeEventListener(\n        'click',\n        this.onClick as EventListener\n      )\n    }\n\n    this.virtualScroll.destroy()\n    this.dimensions.destroy()\n\n    this.cleanUpClassName()\n\n    if (this._rafId) {\n      cancelAnimationFrame(this._rafId)\n    }\n  }\n\n  /**\n   * Add an event listener for the given event and callback\n   *\n   * @param event Event name\n   * @param callback Callback function\n   * @returns Unsubscribe function\n   */\n  on(event: 'scroll', callback: ScrollCallback): () => void\n  on(event: 'virtual-scroll', callback: VirtualScrollCallback): () => void\n  on(event: LenisEvent, callback: ScrollCallback | VirtualScrollCallback) {\n    return this.emitter.on(event, callback as (...args: unknown[]) => void)\n  }\n\n  /**\n   * Remove an event listener for the given event and callback\n   *\n   * @param event Event name\n   * @param callback Callback function\n   */\n  off(event: 'scroll', callback: ScrollCallback): void\n  off(event: 'virtual-scroll', callback: VirtualScrollCallback): void\n  off(event: LenisEvent, callback: ScrollCallback | VirtualScrollCallback) {\n    return this.emitter.off(event, callback as (...args: unknown[]) => void)\n  }\n\n  private onScrollEnd = (e: Event | CustomEvent) => {\n    if (!(e instanceof CustomEvent)) {\n      if (this.isScrolling === 'smooth' || this.isScrolling === false) {\n        e.stopPropagation()\n      }\n    }\n  }\n\n  private dispatchScrollendEvent = () => {\n    this.options.wrapper.dispatchEvent(\n      new CustomEvent('scrollend', {\n        bubbles: this.options.wrapper === window,\n        // cancelable: false,\n        detail: {\n          lenisScrollEnd: true,\n        },\n      })\n    )\n  }\n\n  get overflow() {\n    const property = this.isHorizontal ? 'overflow-x' : 'overflow-y'\n    return getComputedStyle(this.rootElement)[\n      property as keyof CSSStyleDeclaration\n    ] as string\n  }\n\n  private checkOverflow() {\n    if (['hidden', 'clip'].includes(this.overflow)) {\n      this.internalStop()\n    } else {\n      this.internalStart()\n    }\n  }\n\n  private onTransitionEnd = (event: TransitionEvent) => {\n    if (event.propertyName.includes('overflow')) {\n      this.checkOverflow()\n    }\n  }\n\n  private setScroll(scroll: number) {\n    // behavior: 'instant' bypasses the scroll-behavior CSS property\n\n    if (this.isHorizontal) {\n      this.options.wrapper.scrollTo({ left: scroll, behavior: 'instant' })\n    } else {\n      this.options.wrapper.scrollTo({ top: scroll, behavior: 'instant' })\n    }\n  }\n\n  private onClick = (event: PointerEvent | MouseEvent) => {\n    const path = event.composedPath()\n\n    // filter anchor elements (elements with a valid href attribute)\n    const linkElements = path.filter(\n      (node) => node instanceof HTMLAnchorElement && node.href\n    ) as HTMLAnchorElement[]\n    const linkElementsUrls = linkElements.map(\n      (element) => new URL(element.href)\n    )\n\n    const currentUrl = new URL(window.location.href)\n\n    if (this.options.anchors) {\n      const anchorElementUrl = linkElementsUrls.find(\n        (targetUrl) =>\n          currentUrl.host === targetUrl.host &&\n          currentUrl.pathname === targetUrl.pathname &&\n          targetUrl.hash\n      )\n\n      if (anchorElementUrl) {\n        const options =\n          typeof this.options.anchors === 'object' && this.options.anchors\n            ? this.options.anchors\n            : undefined\n\n        const target = `#${anchorElementUrl.hash.split('#')[1]}`\n\n        this.scrollTo(target, options)\n        return\n      }\n    }\n\n    if (this.options.stopInertiaOnNavigate) {\n      const hasPageLinkElementUrl = linkElementsUrls.some(\n        (targetUrl) =>\n          currentUrl.host === targetUrl.host &&\n          currentUrl.pathname !== targetUrl.pathname\n      )\n\n      if (hasPageLinkElementUrl) {\n        this.reset()\n        return\n      }\n    }\n  }\n\n  private onPointerDown = (event: PointerEvent | MouseEvent) => {\n    if (event.button === 1) {\n      this.reset()\n    }\n  }\n\n  private onVirtualScroll = (data: VirtualScrollData) => {\n    if (\n      typeof this.options.virtualScroll === 'function' &&\n      this.options.virtualScroll(data) === false\n    )\n      return\n\n    const { deltaX, deltaY, event } = data\n\n    this.emitter.emit('virtual-scroll', { deltaX, deltaY, event })\n\n    // keep zoom feature\n    if (event.ctrlKey) return\n    // @ts-expect-error\n    if (event.lenisStopPropagation) return\n\n    const isTouch = event.type.includes('touch')\n    const isWheel = event.type.includes('wheel')\n\n    this.isTouching = event.type === 'touchstart' || event.type === 'touchmove'\n\n    const isClickOrTap = deltaX === 0 && deltaY === 0\n\n    const isTapToStop =\n      this.options.syncTouch &&\n      isTouch &&\n      event.type === 'touchstart' &&\n      isClickOrTap &&\n      !this.isStopped &&\n      !this.isLocked\n\n    if (isTapToStop) {\n      this.reset()\n      return\n    }\n\n    // const isPullToRefresh =\n    //   this.options.gestureOrientation === 'vertical' &&\n    //   this.scroll === 0 &&\n    //   !this.options.infinite &&\n    //   deltaY <= 5 // touch pull to refresh, not reliable yet\n\n    // most likely a touchpad gesture, this keep prev/next page navigation working\n    const isUnknownGesture =\n      (this.options.gestureOrientation === 'vertical' && deltaY === 0) ||\n      (this.options.gestureOrientation === 'horizontal' && deltaX === 0)\n\n    if (isClickOrTap || isUnknownGesture) {\n      return\n    }\n\n    // catch if scrolling on nested scroll elements\n    let composedPath = event.composedPath()\n    composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement)) // remove parents elements\n\n    const prevent = this.options.prevent\n\n    const gestureOrientation =\n      Math.abs(deltaX) >= Math.abs(deltaY) ? 'horizontal' : 'vertical'\n\n    if (\n      composedPath.find(\n        (node) =>\n          node instanceof HTMLElement &&\n          ((typeof prevent === 'function' && prevent?.(node)) ||\n            node.hasAttribute?.('data-lenis-prevent') ||\n            (gestureOrientation === 'vertical' &&\n              node.hasAttribute?.('data-lenis-prevent-vertical')) ||\n            (gestureOrientation === 'horizontal' &&\n              node.hasAttribute?.('data-lenis-prevent-horizontal')) ||\n            (isTouch && node.hasAttribute?.('data-lenis-prevent-touch')) ||\n            (isWheel && node.hasAttribute?.('data-lenis-prevent-wheel')) ||\n            (this.options.allowNestedScroll &&\n              this.hasNestedScroll(node, {\n                deltaX,\n                deltaY,\n              })))\n      )\n    )\n      return\n\n    if (this.isStopped || this.isLocked) {\n      if (event.cancelable) {\n        event.preventDefault() // this will stop forwarding the event to the parent, this is problematic\n      }\n      return\n    }\n\n    const isSmooth =\n      (this.options.syncTouch && isTouch) ||\n      (this.options.smoothWheel && isWheel)\n\n    if (!isSmooth) {\n      this.isScrolling = 'native'\n      this.animate.stop()\n      // @ts-expect-error\n      event.lenisStopPropagation = true\n      return\n    }\n\n    let delta = deltaY\n    if (this.options.gestureOrientation === 'both') {\n      delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX\n    } else if (this.options.gestureOrientation === 'horizontal') {\n      delta = deltaX\n    }\n\n    if (\n      !this.options.overscroll ||\n      this.options.infinite ||\n      (this.options.wrapper !== window &&\n        this.limit > 0 &&\n        ((this.animatedScroll > 0 && this.animatedScroll < this.limit) ||\n          (this.animatedScroll === 0 && deltaY > 0) ||\n          (this.animatedScroll === this.limit && deltaY < 0)))\n    ) {\n      // @ts-expect-error\n      event.lenisStopPropagation = true\n      // event.stopPropagation()\n    }\n\n    if (event.cancelable) {\n      event.preventDefault()\n    }\n\n    const isSyncTouch = isTouch && this.options.syncTouch\n    const isTouchEnd = isTouch && event.type === 'touchend'\n\n    const hasTouchInertia = isTouchEnd\n\n    if (hasTouchInertia) {\n      delta =\n        Math.sign(this.velocity) *\n        Math.abs(this.velocity) ** this.options.touchInertiaExponent\n    }\n\n    this.scrollTo(this.targetScroll + delta, {\n      programmatic: false,\n      ...(isSyncTouch\n        ? {\n            lerp: hasTouchInertia ? this.options.syncTouchLerp : 1,\n          }\n        : {\n            lerp: this.options.lerp,\n            duration: this.options.duration,\n            easing: this.options.easing,\n          }),\n    })\n  }\n\n  /**\n   * Force lenis to recalculate the dimensions\n   */\n  resize() {\n    this.dimensions.resize()\n    this.animatedScroll = this.targetScroll = this.actualScroll\n    this.emit()\n  }\n\n  private emit() {\n    this.emitter.emit('scroll', this)\n  }\n\n  private onNativeScroll = () => {\n    if (this._resetVelocityTimeout !== null) {\n      clearTimeout(this._resetVelocityTimeout)\n      this._resetVelocityTimeout = null\n    }\n\n    if (this._preventNextNativeScrollEvent) {\n      this._preventNextNativeScrollEvent = false\n      return\n    }\n\n    if (this.isScrolling === false || this.isScrolling === 'native') {\n      const lastScroll = this.animatedScroll\n      this.animatedScroll = this.targetScroll = this.actualScroll\n      this.lastVelocity = this.velocity\n      this.velocity = this.animatedScroll - lastScroll\n      this.direction = Math.sign(\n        this.animatedScroll - lastScroll\n      ) as Lenis['direction']\n\n      if (!this.isStopped) {\n        this.isScrolling = 'native'\n      }\n\n      this.emit()\n\n      if (this.velocity !== 0) {\n        this._resetVelocityTimeout = setTimeout(() => {\n          this.lastVelocity = this.velocity\n          this.velocity = 0\n          this.isScrolling = false\n          this.emit()\n        }, 400)\n      }\n    }\n  }\n\n  private reset() {\n    this.isLocked = false\n    this.isScrolling = false\n    this.animatedScroll = this.targetScroll = this.actualScroll\n    this.lastVelocity = this.velocity = 0\n    this.animate.stop()\n  }\n\n  /**\n   * Start lenis scroll after it has been stopped\n   */\n  start() {\n    if (!this.isStopped) return\n\n    if (this.options.autoToggle) {\n      this.rootElement.style.removeProperty('overflow')\n      return\n    }\n\n    this.internalStart()\n  }\n\n  private internalStart() {\n    if (!this.isStopped) return\n\n    this.reset()\n    this.isStopped = false\n    this.emit()\n  }\n\n  /**\n   * Stop lenis scroll\n   */\n  stop() {\n    if (this.isStopped) return\n\n    if (this.options.autoToggle) {\n      this.rootElement.style.setProperty('overflow', 'clip')\n      return\n    }\n\n    this.internalStop()\n  }\n\n  private internalStop() {\n    if (this.isStopped) return\n\n    this.reset()\n    this.isStopped = true\n    this.emit()\n  }\n\n  /**\n   * RequestAnimationFrame for lenis\n   *\n   * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus\n   */\n  raf = (time: number) => {\n    const deltaTime = time - (this.time || time)\n    this.time = time\n\n    this.animate.advance(deltaTime * 0.001)\n\n    if (this.options.autoRaf) {\n      this._rafId = requestAnimationFrame(this.raf)\n    }\n  }\n\n  /**\n   * Scroll to a target value\n   *\n   * @param target The target value to scroll to\n   * @param options The options for the scroll\n   *\n   * @example\n   * lenis.scrollTo(100, {\n   *   offset: 100,\n   *   duration: 1,\n   *   easing: (t) => 1 - Math.cos((t * Math.PI) / 2),\n   *   lerp: 0.1,\n   *   onStart: () => {\n   *     console.log('onStart')\n   *   },\n   *   onComplete: () => {\n   *     console.log('onComplete')\n   *   },\n   * })\n   */\n  scrollTo(\n    _target: number | string | HTMLElement,\n    {\n      offset = 0,\n      immediate = false,\n      lock = false,\n      programmatic = true, // called from outside of the class\n      lerp = programmatic ? this.options.lerp : undefined,\n      duration = programmatic ? this.options.duration : undefined,\n      easing = programmatic ? this.options.easing : undefined,\n      onStart,\n      onComplete,\n      force = false, // scroll even if stopped\n      userData,\n    }: ScrollToOptions = {}\n  ) {\n    if ((this.isStopped || this.isLocked) && !force) return\n\n    let target: number | string | HTMLElement = _target\n    let adjustedOffset = offset\n\n    // keywords\n    if (\n      typeof target === 'string' &&\n      ['top', 'left', 'start', '#'].includes(target)\n    ) {\n      target = 0\n    } else if (\n      typeof target === 'string' &&\n      ['bottom', 'right', 'end'].includes(target)\n    ) {\n      target = this.limit\n    } else {\n      let node: Element | null = null\n\n      if (typeof target === 'string') {\n        // CSS selector\n        node = document.querySelector(target)\n\n        if (!node) {\n          if (target === '#top') {\n            target = 0\n          } else {\n            console.warn('Lenis: Target not found', target)\n          }\n        }\n      } else if (target instanceof HTMLElement && target?.nodeType) {\n        // Node element\n        node = target\n      }\n\n      if (node) {\n        if (this.options.wrapper !== window) {\n          // nested scroll offset correction\n          const wrapperRect = this.rootElement.getBoundingClientRect()\n          adjustedOffset -= this.isHorizontal\n            ? wrapperRect.left\n            : wrapperRect.top\n        }\n\n        const rect = node.getBoundingClientRect()\n\n        target =\n          (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll\n      }\n    }\n\n    if (typeof target !== 'number') return\n\n    target += adjustedOffset\n    target = Math.round(target)\n\n    if (this.options.infinite) {\n      if (programmatic) {\n        this.targetScroll = this.animatedScroll = this.scroll\n\n        const distance = target - this.animatedScroll\n\n        if (distance > this.limit / 2) {\n          target -= this.limit\n        } else if (distance < -this.limit / 2) {\n          target += this.limit\n        }\n      }\n    } else {\n      target = clamp(0, target, this.limit)\n    }\n\n    if (target === this.targetScroll) {\n      onStart?.(this)\n      onComplete?.(this)\n      return\n    }\n\n    this.userData = userData ?? {}\n\n    if (immediate) {\n      this.animatedScroll = this.targetScroll = target\n      this.setScroll(this.scroll)\n      this.reset()\n      this.preventNextNativeScrollEvent()\n      this.emit()\n      onComplete?.(this)\n      this.userData = {}\n\n      requestAnimationFrame(() => {\n        this.dispatchScrollendEvent()\n      })\n      return\n    }\n\n    if (!programmatic) {\n      this.targetScroll = target\n    }\n\n    // flip to easing/time based animation if at least one of them is provided\n    if (typeof duration === 'number' && typeof easing !== 'function') {\n      easing = defaultEasing\n    } else if (typeof easing === 'function' && typeof duration !== 'number') {\n      duration = 1\n    }\n\n    this.animate.fromTo(this.animatedScroll, target, {\n      duration,\n      easing,\n      lerp,\n      onStart: () => {\n        // started\n        if (lock) this.isLocked = true\n        this.isScrolling = 'smooth'\n        onStart?.(this)\n      },\n      onUpdate: (value: number, completed: boolean) => {\n        this.isScrolling = 'smooth'\n\n        // updated\n        this.lastVelocity = this.velocity\n        this.velocity = value - this.animatedScroll\n        this.direction = Math.sign(this.velocity) as Lenis['direction']\n\n        this.animatedScroll = value\n        this.setScroll(this.scroll)\n\n        if (programmatic) {\n          // wheel during programmatic should stop it\n          this.targetScroll = value\n        }\n\n        if (!completed) this.emit()\n\n        if (completed) {\n          this.reset()\n          this.emit()\n          onComplete?.(this)\n          this.userData = {}\n\n          requestAnimationFrame(() => {\n            this.dispatchScrollendEvent()\n          })\n\n          // avoid emitting event twice\n          this.preventNextNativeScrollEvent()\n        }\n      },\n    })\n  }\n\n  private preventNextNativeScrollEvent() {\n    this._preventNextNativeScrollEvent = true\n\n    requestAnimationFrame(() => {\n      this._preventNextNativeScrollEvent = false\n    })\n  }\n\n  private hasNestedScroll(\n    node: HTMLElement,\n    { deltaX, deltaY }: { deltaX: number; deltaY: number }\n  ) {\n    const time = Date.now()\n\n    // @ts-expect-error - _lenis is a custom cache property\n    if (!node._lenis) node._lenis = {}\n    // @ts-expect-error\n    const cache = node._lenis\n\n    let hasOverflowX: boolean | undefined\n    let hasOverflowY: boolean | undefined\n    let isScrollableX: boolean | undefined\n    let isScrollableY: boolean | undefined\n    let hasOverscrollBehaviorX: boolean | undefined\n    let hasOverscrollBehaviorY: boolean | undefined\n    let scrollWidth: number\n    let scrollHeight: number\n    let clientWidth: number\n    let clientHeight: number\n\n    if (time - (cache.time ?? 0) > 2000) {\n      cache.time = Date.now()\n\n      const computedStyle = window.getComputedStyle(node)\n      cache.computedStyle = computedStyle\n\n      hasOverflowX = ['auto', 'overlay', 'scroll'].includes(\n        computedStyle.overflowX\n      )\n      hasOverflowY = ['auto', 'overlay', 'scroll'].includes(\n        computedStyle.overflowY\n      )\n\n      hasOverscrollBehaviorX = ['auto'].includes(\n        computedStyle.overscrollBehaviorX\n      )\n      hasOverscrollBehaviorY = ['auto'].includes(\n        computedStyle.overscrollBehaviorY\n      )\n\n      cache.hasOverflowX = hasOverflowX\n      cache.hasOverflowY = hasOverflowY\n\n      if (!(hasOverflowX || hasOverflowY)) return false // if no overflow, it's not scrollable no matter what, early return saves some computations\n\n      scrollWidth = node.scrollWidth\n      scrollHeight = node.scrollHeight\n\n      clientWidth = node.clientWidth\n      clientHeight = node.clientHeight\n\n      isScrollableX = scrollWidth > clientWidth\n      isScrollableY = scrollHeight > clientHeight\n\n      cache.isScrollableX = isScrollableX\n      cache.isScrollableY = isScrollableY\n      cache.scrollWidth = scrollWidth\n      cache.scrollHeight = scrollHeight\n      cache.clientWidth = clientWidth\n      cache.clientHeight = clientHeight\n      cache.hasOverscrollBehaviorX = hasOverscrollBehaviorX\n      cache.hasOverscrollBehaviorY = hasOverscrollBehaviorY\n    } else {\n      isScrollableX = cache.isScrollableX\n      isScrollableY = cache.isScrollableY\n      hasOverflowX = cache.hasOverflowX\n      hasOverflowY = cache.hasOverflowY\n      scrollWidth = cache.scrollWidth\n      scrollHeight = cache.scrollHeight\n      clientWidth = cache.clientWidth\n      clientHeight = cache.clientHeight\n      hasOverscrollBehaviorX = cache.hasOverscrollBehaviorX\n      hasOverscrollBehaviorY = cache.hasOverscrollBehaviorY\n    }\n\n    if (!((hasOverflowX && isScrollableX) || (hasOverflowY && isScrollableY))) {\n      return false\n    }\n\n    const orientation =\n      Math.abs(deltaX) >= Math.abs(deltaY) ? 'horizontal' : 'vertical'\n\n    let scroll: number | undefined\n    let maxScroll: number | undefined\n    let delta: number | undefined\n    let hasOverflow: boolean | undefined\n    let isScrollable: boolean | undefined\n    let hasOverscrollBehavior: boolean | undefined\n\n    if (orientation === 'horizontal') {\n      scroll = Math.round(node.scrollLeft)\n      maxScroll = scrollWidth - clientWidth\n      delta = deltaX\n\n      hasOverflow = hasOverflowX\n      isScrollable = isScrollableX\n      hasOverscrollBehavior = hasOverscrollBehaviorX\n    } else if (orientation === 'vertical') {\n      scroll = Math.round(node.scrollTop)\n      maxScroll = scrollHeight - clientHeight\n      delta = deltaY\n\n      hasOverflow = hasOverflowY\n      isScrollable = isScrollableY\n      hasOverscrollBehavior = hasOverscrollBehaviorY\n    } else {\n      return false\n    }\n\n    if (!hasOverscrollBehavior && (scroll >= maxScroll || scroll <= 0)) {\n      return true\n    }\n\n    const willScroll = delta > 0 ? scroll < maxScroll : scroll > 0\n\n    return willScroll && hasOverflow && isScrollable\n  }\n\n  /**\n   * The root element on which lenis is instanced\n   */\n  get rootElement() {\n    return (\n      this.options.wrapper === window\n        ? document.documentElement\n        : this.options.wrapper\n    ) as HTMLElement\n  }\n\n  /**\n   * The limit which is the maximum scroll value\n   */\n  get limit() {\n    if (this.options.naiveDimensions) {\n      if (this.isHorizontal) {\n        return this.rootElement.scrollWidth - this.rootElement.clientWidth\n      }\n      return this.rootElement.scrollHeight - this.rootElement.clientHeight\n    }\n    return this.dimensions.limit[this.isHorizontal ? 'x' : 'y']\n  }\n\n  /**\n   * Whether or not the scroll is horizontal\n   */\n  get isHorizontal() {\n    return this.options.orientation === 'horizontal'\n  }\n\n  /**\n   * The actual scroll value\n   */\n  get actualScroll() {\n    // value browser takes into account\n    // it has to be this way because of DOCTYPE declaration\n    const wrapper = this.options.wrapper as Window | HTMLElement\n\n    return this.isHorizontal\n      ? ((wrapper as Window).scrollX ?? (wrapper as HTMLElement).scrollLeft)\n      : ((wrapper as Window).scrollY ?? (wrapper as HTMLElement).scrollTop)\n  }\n\n  /**\n   * The current scroll value\n   */\n  get scroll() {\n    return this.options.infinite\n      ? modulo(this.animatedScroll, this.limit)\n      : this.animatedScroll\n  }\n\n  /**\n   * The progress of the scroll relative to the limit\n   */\n  get progress() {\n    // avoid progress to be NaN\n    return this.limit === 0 ? 1 : this.scroll / this.limit\n  }\n\n  /**\n   * Current scroll state\n   */\n  get isScrolling() {\n    return this._isScrolling\n  }\n\n  private set isScrolling(value: Scrolling) {\n    if (this._isScrolling !== value) {\n      this._isScrolling = value\n      this.updateClassName()\n    }\n  }\n\n  /**\n   * Check if lenis is stopped\n   */\n  get isStopped() {\n    return this._isStopped\n  }\n\n  private set isStopped(value: boolean) {\n    if (this._isStopped !== value) {\n      this._isStopped = value\n      this.updateClassName()\n    }\n  }\n\n  /**\n   * Check if lenis is locked\n   */\n  get isLocked() {\n    return this._isLocked\n  }\n\n  private set isLocked(value: boolean) {\n    if (this._isLocked !== value) {\n      this._isLocked = value\n      this.updateClassName()\n    }\n  }\n\n  /**\n   * Check if lenis is smooth scrolling\n   */\n  get isSmooth() {\n    return this.isScrolling === 'smooth'\n  }\n\n  /**\n   * The class name applied to the wrapper element\n   */\n  get className() {\n    let className = 'lenis'\n    if (this.options.autoToggle) className += ' lenis-autoToggle'\n    if (this.isStopped) className += ' lenis-stopped'\n    if (this.isLocked) className += ' lenis-locked'\n    if (this.isScrolling) className += ' lenis-scrolling'\n    if (this.isScrolling === 'smooth') className += ' lenis-smooth'\n    return className\n  }\n\n  private updateClassName() {\n    this.cleanUpClassName()\n\n    this.rootElement.className =\n      `${this.rootElement.className} ${this.className}`.trim()\n  }\n\n  private cleanUpClassName() {\n    this.rootElement.className = this.rootElement.className\n      .replace(/lenis(-\\w+)?/g, '')\n      .trim()\n  }\n}\n"
  },
  {
    "path": "packages/core/src/maths.ts",
    "content": "/**\n * Clamp a value between a minimum and maximum value\n *\n * @param min Minimum value\n * @param input Value to clamp\n * @param max Maximum value\n * @returns Clamped value\n */\nexport function clamp(min: number, input: number, max: number) {\n  return Math.max(min, Math.min(input, max))\n}\n\n/**\n * Truncate a floating-point number to a specified number of decimal places\n *\n * @param value Value to truncate\n * @param decimals Number of decimal places to truncate to\n * @returns Truncated value\n */\nexport function truncate(value: number, decimals = 0) {\n  return Number.parseFloat(value.toFixed(decimals))\n}\n\n/**\n *  Linearly interpolate between two values using an amount (0 <= t <= 1)\n *\n * @param x First value\n * @param y Second value\n * @param t Amount to interpolate (0 <= t <= 1)\n * @returns Interpolated value\n */\nexport function lerp(x: number, y: number, t: number) {\n  return (1 - t) * x + t * y\n}\n\n/**\n * Damp a value over time using a damping factor\n * {@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}\n *\n * @param x Initial value\n * @param y Target value\n * @param lambda Damping factor\n * @param dt Time elapsed since the last update\n * @returns Damped value\n */\nexport function damp(x: number, y: number, lambda: number, deltaTime: number) {\n  return lerp(x, y, 1 - Math.exp(-lambda * deltaTime))\n}\n\n/**\n * Calculate the modulo of the dividend and divisor while keeping the result within the same sign as the divisor\n * {@link https://anguscroll.com/just/just-modulo}\n *\n * @param n Dividend\n * @param d Divisor\n * @returns Modulo\n */\nexport function modulo(n: number, d: number) {\n  return ((n % d) + d) % d\n}\n"
  },
  {
    "path": "packages/core/src/types.ts",
    "content": "import type { Lenis } from './lenis'\n\nexport type OnUpdateCallback = (value: number, completed: boolean) => void\nexport type OnStartCallback = () => void\n\nexport type FromToOptions = {\n  /**\n   * Linear interpolation (lerp) intensity (between 0 and 1)\n   * @default 0.1\n   */\n  lerp?: number\n  /**\n   * The duration of the scroll animation (in s)\n   * @default 1\n   */\n  duration?: number\n  /**\n   * The easing function to use for the scroll animation\n   * @default (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))\n   */\n  easing?: EasingFunction\n  /**\n   * Called when the scroll starts\n   */\n  onStart?: OnStartCallback\n  /**\n   * Called when the scroll progress changes\n   */\n  onUpdate?: OnUpdateCallback\n}\n\nexport type UserData = Record<string, unknown>\n\nexport type Scrolling = boolean | 'native' | 'smooth'\n\nexport type LenisEvent = 'scroll' | 'virtual-scroll'\nexport type ScrollCallback = (lenis: Lenis) => void\nexport type VirtualScrollCallback = (data: VirtualScrollData) => void\n\nexport type VirtualScrollData = {\n  deltaX: number\n  deltaY: number\n  event: WheelEvent | TouchEvent\n}\n\nexport type Orientation = 'vertical' | 'horizontal'\nexport type GestureOrientation = 'vertical' | 'horizontal' | 'both'\nexport type EasingFunction = (time: number) => number\n\nexport type ScrollToOptions = {\n  /**\n   * The offset to apply to the target value\n   * @default 0\n   */\n  offset?: number\n  /**\n   * Skip the animation and jump to the target value immediately\n   * @default false\n   */\n  immediate?: boolean\n  /**\n   * Lock the scroll to the target value\n   * @default false\n   */\n  lock?: boolean\n  /**\n   * The duration of the scroll animation (in s)\n   */\n  duration?: number\n  /**\n   * The easing function to use for the scroll animation\n   * @default (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))\n   */\n  easing?: EasingFunction\n  /**\n   * Linear interpolation (lerp) intensity (between 0 and 1)\n   * @default 0.1\n   */\n  lerp?: number\n  /**\n   * Called when the scroll starts\n   */\n  onStart?: (lenis: Lenis) => void\n  /**\n   * Called when the scroll completes\n   */\n  onComplete?: (lenis: Lenis) => void\n  /**\n   * Scroll even if stopped\n   * @default false\n   */\n  force?: boolean\n  /**\n   * Scroll initiated from outside of the lenis instance\n   * @default false\n   */\n  programmatic?: boolean\n  /**\n   * User data that will be forwarded through the scroll event\n   */\n  userData?: UserData\n}\n\nexport type LenisOptions = {\n  /**\n   * The element that will be used as the scroll container\n   * @default window\n   */\n  wrapper?: Window | HTMLElement | Element\n  /**\n   * The element that contains the content that will be scrolled, usually `wrapper`'s direct child\n   * @default document.documentElement\n   */\n  content?: HTMLElement | Element\n  /**\n   * The element that will listen to `wheel` and `touch` events\n   * @default window\n   */\n  eventsTarget?: Window | HTMLElement | Element\n  /**\n   * Smooth the scroll initiated by `wheel` events\n   * @default true\n   */\n  smoothWheel?: boolean\n  /**\n   * Mimic touch device scroll while allowing scroll sync\n   * @default false\n   */\n  syncTouch?: boolean\n  /**\n   * Linear interpolation (lerp) intensity (between 0 and 1)\n   * @default 0.075\n   */\n  syncTouchLerp?: number\n  /**\n   * Manage the the strength of `syncTouch` inertia\n   * @default 1.7\n   */\n  touchInertiaExponent?: number\n  /**\n   * Scroll duration in seconds\n   */\n  duration?: number\n  /**\n   * Scroll easing function\n   * @default (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))\n   */\n  easing?: EasingFunction\n  /**\n   * Linear interpolation (lerp) intensity (between 0 and 1)\n   * @default 0.1\n   */\n  lerp?: number\n  /**\n   * Enable infinite scrolling\n   * @default false\n   */\n  infinite?: boolean\n  /**\n   * The orientation of the scrolling. Can be `vertical` or `horizontal`\n   * @default vertical\n   */\n  orientation?: Orientation\n  /**\n   * The orientation of the gestures. Can be `vertical`, `horizontal` or `both`\n   * @default vertical\n   */\n  gestureOrientation?: GestureOrientation\n  /**\n   * The multiplier to use for touch events\n   * @default 1\n   */\n  touchMultiplier?: number\n  /**\n   * The multiplier to use for mouse wheel events\n   * @default 1\n   */\n  wheelMultiplier?: number\n  /**\n   * Resize instance automatically\n   * @default true\n   */\n  autoResize?: boolean\n  /**\n   * Manually prevent scroll to be smoothed based on elements traversed by events\n   */\n  prevent?: (node: HTMLElement) => boolean\n  /**\n   * Manually modify the events before they get consumed\n   */\n  virtualScroll?: (data: VirtualScrollData) => boolean\n  /**\n   * Wether or not to enable overscroll on a nested Lenis instance, similar to CSS overscroll-behavior (https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior)\n   * @default true\n   */\n  overscroll?: boolean\n  /**\n   * If `true`, Lenis will automatically run `requestAnimationFrame` loop\n   * @default false\n   */\n  autoRaf?: boolean\n  /**\n   * If `true`, Lenis will handle anchor links automatically\n   * @default false\n   */\n  anchors?: boolean | ScrollToOptions\n  /**\n   * If `true`, Lenis will automatically start/stop based on wrapper's overflow property\n   * @default false\n   */\n  autoToggle?: boolean\n  /**\n   * If `true`, Lenis will allow nested scroll\n   * @default false\n   */\n  allowNestedScroll?: boolean\n  /**\n   * @deprecated use `naiveDimensions` instead\n   */\n  __experimental__naiveDimensions?: boolean\n  /**\n   * If `true`, Lenis will use naive dimensions calculation, be careful this has a performance impact\n   * @default false\n   */\n  naiveDimensions?: boolean\n  /**\n   * If `true`, Lenis will stop inertia when an internal link is clicked\n   * @default false\n   */\n  stopInertiaOnNavigate?: boolean\n}\n\ndeclare global {\n  interface Window {\n    lenisVersion: string\n    lenis: {\n      version?: string\n      horizontal?: boolean\n      snap?: boolean\n      touch?: boolean\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/virtual-scroll.ts",
    "content": "import { Emitter } from './emitter'\nimport type { VirtualScrollCallback } from './types'\n\nconst LINE_HEIGHT = 100 / 6\nconst listenerOptions: AddEventListenerOptions = { passive: false }\n\nfunction getDeltaMultiplier(deltaMode: number, size: number): number {\n  if (deltaMode === 1) return LINE_HEIGHT\n  if (deltaMode === 2) return size\n  return 1\n}\n\nexport class VirtualScroll {\n  touchStart = {\n    x: 0,\n    y: 0,\n  }\n  lastDelta = {\n    x: 0,\n    y: 0,\n  }\n  window = {\n    width: 0,\n    height: 0,\n  }\n  private emitter = new Emitter()\n\n  constructor(\n    private element: HTMLElement,\n    private options = { wheelMultiplier: 1, touchMultiplier: 1 }\n  ) {\n    window.addEventListener('resize', this.onWindowResize)\n    this.onWindowResize()\n\n    this.element.addEventListener('wheel', this.onWheel, listenerOptions)\n    this.element.addEventListener(\n      'touchstart',\n      this.onTouchStart,\n      listenerOptions\n    )\n    this.element.addEventListener(\n      'touchmove',\n      this.onTouchMove,\n      listenerOptions\n    )\n    this.element.addEventListener('touchend', this.onTouchEnd, listenerOptions)\n  }\n\n  /**\n   * Add an event listener for the given event and callback\n   *\n   * @param event Event name\n   * @param callback Callback function\n   */\n  on(event: string, callback: VirtualScrollCallback) {\n    return this.emitter.on(event, callback as (...args: unknown[]) => void)\n  }\n\n  /** Remove all event listeners and clean up */\n  destroy() {\n    this.emitter.destroy()\n\n    window.removeEventListener('resize', this.onWindowResize)\n\n    this.element.removeEventListener('wheel', this.onWheel, listenerOptions)\n    this.element.removeEventListener(\n      'touchstart',\n      this.onTouchStart,\n      listenerOptions\n    )\n    this.element.removeEventListener(\n      'touchmove',\n      this.onTouchMove,\n      listenerOptions\n    )\n    this.element.removeEventListener(\n      'touchend',\n      this.onTouchEnd,\n      listenerOptions\n    )\n  }\n\n  /**\n   * Event handler for 'touchstart' event\n   *\n   * @param event Touch event\n   */\n  onTouchStart = (event: TouchEvent) => {\n    // @ts-expect-error - event.targetTouches is not defined\n    const { clientX, clientY } = event.targetTouches\n      ? event.targetTouches[0]\n      : event\n\n    this.touchStart.x = clientX\n    this.touchStart.y = clientY\n\n    this.lastDelta = {\n      x: 0,\n      y: 0,\n    }\n\n    this.emitter.emit('scroll', {\n      deltaX: 0,\n      deltaY: 0,\n      event,\n    })\n  }\n\n  /** Event handler for 'touchmove' event */\n  onTouchMove = (event: TouchEvent) => {\n    // @ts-expect-error - event.targetTouches is not defined\n    const { clientX, clientY } = event.targetTouches\n      ? event.targetTouches[0]\n      : event\n\n    const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier\n    const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier\n\n    this.touchStart.x = clientX\n    this.touchStart.y = clientY\n\n    this.lastDelta = {\n      x: deltaX,\n      y: deltaY,\n    }\n\n    this.emitter.emit('scroll', {\n      deltaX,\n      deltaY,\n      event,\n    })\n  }\n\n  onTouchEnd = (event: TouchEvent) => {\n    this.emitter.emit('scroll', {\n      deltaX: this.lastDelta.x,\n      deltaY: this.lastDelta.y,\n      event,\n    })\n  }\n\n  /** Event handler for 'wheel' event */\n  onWheel = (event: WheelEvent) => {\n    let { deltaX, deltaY, deltaMode } = event\n\n    const multiplierX = getDeltaMultiplier(deltaMode, this.window.width)\n    const multiplierY = getDeltaMultiplier(deltaMode, this.window.height)\n\n    deltaX *= multiplierX\n    deltaY *= multiplierY\n\n    deltaX *= this.options.wheelMultiplier\n    deltaY *= this.options.wheelMultiplier\n\n    this.emitter.emit('scroll', { deltaX, deltaY, event })\n  }\n\n  onWindowResize = () => {\n    this.window = {\n      width: window.innerWidth,\n      height: window.innerHeight,\n    }\n  }\n}\n"
  },
  {
    "path": "packages/react/README.md",
    "content": "# lenis/react\n\n## Introduction\nlenis/react provides a `<ReactLenis>` component that creates a [Lenis](https://github.com/darkroomengineering/lenis) instance and provides it to its children via context. This allows you to use Lenis in your React app without worrying about passing the instance down through props. It also provides a `useLenis` hook that allows you to access the Lenis instance from any component in your app.\n\n\n## Installation\n\n```bash\nnpm i lenis\n```\n\n## Usage\n\n### Basic\n\n```jsx\nimport { ReactLenis, useLenis } from 'lenis/react'\n\nfunction App() {\n  const lenis = useLenis((lenis) => {\n    // called every scroll\n    console.log(lenis)\n  })\n\n  return (\n    <>\n      <ReactLenis root />\n      { /* content */ }\n    </>\n  )\n}\n```\n\n## Props\n- `options`: [Lenis options](https://github.com/darkroomengineering/lenis#instance-settings).\n- `root`: When `true`, makes the Lenis instance globally accessible via `useLenis` from anywhere in your app (even outside the provider tree). Lenis will use the default `<html>` scroll container. When `'asChild'`, renders wrapper elements for custom scroll containers while still making the instance globally accessible. Default: `false`.\n\n## Hooks\nOnce the Lenis context is set (components mounted inside `<ReactLenis>`) you can use these handy hooks:\n\n`useLenis` is a hook that returns the Lenis instance\n\nThe hook takes three arguments:\n- `callback`: The function to be called whenever a scroll event is emitted\n- `deps`: Trigger callback on change\n- `priority`: Manage callback execution order\n\n\n\n\n\n## Examples\n\n### Custom requestAnimationFrame loop:\n\n```jsx\nimport { ReactLenis } from 'lenis/react'\nimport { useEffect, useRef } from 'react'\n\nfunction App() {\n  const lenisRef = useRef()\n  \n  useEffect(() => {\n    function update(time) {\n      lenisRef.current?.lenis?.raf(time)\n    }\n  \n    const rafId = requestAnimationFrame(update)\n  \n    return () => cancelAnimationFrame(rafId)\n  }, [])\n  \n  return (\n    <ReactLenis root options={{ autoRaf: false }} ref={lenisRef} />\n  )\n}\n```\n\n\n### GSAP integration\n\n```jsx\nimport gsap from 'gsap'\nimport { ReactLenis } from 'lenis/react'\nimport { useEffect, useRef } from 'react'\n\nfunction App() {\n  const lenisRef = useRef()\n  \n  useEffect(() => {\n    function update(time) {\n      lenisRef.current?.lenis?.raf(time * 1000)\n    }\n  \n    gsap.ticker.add(update)\n  \n    return () => gsap.ticker.remove(update)\n  }, [])\n  \n  return (\n    <ReactLenis root options={{ autoRaf: false }} ref={lenisRef} />\n  )\n}\n```\n\n### Framer Motion integration:\n```jsx\nimport { ReactLenis } from 'lenis/react';\nimport type { LenisRef } from 'lenis/react';\nimport { cancelFrame, frame } from 'framer-motion';\nimport { useEffect, useRef } from 'react';\n\nfunction App() {\n  const lenisRef = useRef<LenisRef>(null)\n\n  useEffect(() => {\n    function update(data: { timestamp: number }) {\n      const time = data.timestamp\n      lenisRef.current?.lenis?.raf(time)\n    }\n\n    frame.update(update, true)\n\n    return () => cancelFrame(update)\n  }, [])\n\n\n  return (\n    <ReactLenis root options={{ autoRaf: false }} ref={lenisRef} />\n  )\n}\n```\n\n## lenis/react in use\n\n- [@darkroom.engineering/satus](https://github.com/darkroomengineering/satus) Our starter kit.\n\n<br/>\n\n## License\n\nMIT © [darkroom.engineering](https://github.com/darkroomengineering)\n"
  },
  {
    "path": "packages/react/index.ts",
    "content": "// This file serves as an entry point for the package\nexport {\n  LenisContext,\n  ReactLenis as default,\n  ReactLenis as Lenis,\n  ReactLenis,\n} from './src/provider'\nexport * from './src/types'\nexport { useLenis } from './src/use-lenis'\n"
  },
  {
    "path": "packages/react/package.json",
    "content": "{\n  \"name\": \"lenis-react\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.7\",\n    \"react\": \"^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/react/src/provider.tsx",
    "content": "import Lenis, { type ScrollCallback } from 'lenis'\nimport {\n  type ForwardRefExoticComponent,\n  type RefAttributes,\n  createContext,\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useRef,\n  useState,\n} from 'react'\nimport { Store } from './store'\nimport type { LenisContextValue, LenisProps, LenisRef } from './types'\n\nexport const LenisContext = createContext<LenisContextValue | null>(null)\n\n/**\n * The root store for the lenis context\n *\n * This store serves as a fallback for the context if it is not available\n * and allows us to use the global lenis instance above a provider\n */\nexport const rootLenisContextStore = new Store<LenisContextValue | null>(null)\n\n/**\n * React component to setup a Lenis instance\n */\nexport const ReactLenis: ForwardRefExoticComponent<\n  LenisProps & RefAttributes<LenisRef>\n> = forwardRef<LenisRef, LenisProps>(\n  (\n    {\n      children,\n      root = false,\n      options = {},\n      autoRaf = true,\n      className = '',\n      ...props\n    }: LenisProps,\n    ref\n  ) => {\n    const wrapperRef = useRef<HTMLDivElement>(null)\n    const contentRef = useRef<HTMLDivElement>(null)\n\n    const [lenis, setLenis] = useState<Lenis | undefined>(undefined)\n\n    // Setup ref\n    useImperativeHandle(\n      ref,\n      () => ({\n        wrapper: wrapperRef.current,\n        content: contentRef.current,\n        lenis,\n      }),\n      [lenis]\n    )\n\n    // Setup lenis instance\n    useEffect(() => {\n      const lenis = new Lenis({\n        ...options,\n        ...(wrapperRef.current &&\n          contentRef.current && {\n            wrapper: wrapperRef.current!,\n            content: contentRef.current!,\n          }),\n        autoRaf: options?.autoRaf ?? autoRaf, // this is to avoid breaking the autoRaf prop if it's still used (require breaking change)\n      })\n\n      setLenis(lenis)\n\n      return () => {\n        lenis.destroy()\n        setLenis(undefined)\n      }\n    }, [autoRaf, JSON.stringify({ ...options, wrapper: null, content: null })])\n\n    // Handle callbacks\n    const callbacksRefs = useRef<\n      {\n        callback: ScrollCallback\n        priority: number\n      }[]\n    >([])\n\n    const addCallback: LenisContextValue['addCallback'] = useCallback(\n      (callback, priority) => {\n        callbacksRefs.current.push({ callback, priority })\n        callbacksRefs.current.sort((a, b) => a.priority - b.priority)\n      },\n      []\n    )\n\n    const removeCallback: LenisContextValue['removeCallback'] = useCallback(\n      (callback) => {\n        callbacksRefs.current = callbacksRefs.current.filter(\n          (cb) => cb.callback !== callback\n        )\n      },\n      []\n    )\n\n    // This makes sure to set the global context if the root is true\n    useEffect(() => {\n      if (root && lenis) {\n        rootLenisContextStore.set({ lenis, addCallback, removeCallback })\n\n        return () => rootLenisContextStore.set(null)\n      }\n    }, [root, lenis, addCallback, removeCallback])\n\n    // Setup callback listeners\n    useEffect(() => {\n      if (!lenis) return\n\n      const onScroll: ScrollCallback = (data) => {\n        for (const { callback } of callbacksRefs.current) {\n          callback(data)\n        }\n      }\n\n      lenis.on('scroll', onScroll)\n\n      return () => {\n        lenis.off('scroll', onScroll)\n      }\n    }, [lenis])\n\n    if (!children) return null\n\n    return (\n      <LenisContext.Provider\n        value={{ lenis: lenis!, addCallback, removeCallback }}\n      >\n        {root && root !== 'asChild' ? (\n          children\n        ) : (\n          <div\n            ref={wrapperRef}\n            className={`${className} ${lenis?.className ?? ''}`.trim()}\n            {...props}\n          >\n            <div ref={contentRef}>{children}</div>\n          </div>\n        )}\n      </LenisContext.Provider>\n    )\n  }\n)\n"
  },
  {
    "path": "packages/react/src/store.ts",
    "content": "import { useEffect, useState } from 'react'\n\ntype Listener<S> = (state: S) => void\n\nexport class Store<S> {\n  private listeners: Listener<S>[] = []\n\n  constructor(private state: S) {}\n\n  set(state: S) {\n    this.state = state\n\n    for (const listener of this.listeners) {\n      listener(this.state)\n    }\n  }\n\n  subscribe(listener: Listener<S>) {\n    this.listeners = [...this.listeners, listener]\n    return () => {\n      this.listeners = this.listeners.filter((l) => l !== listener)\n    }\n  }\n\n  get() {\n    return this.state\n  }\n}\n\nexport function useStore<S>(store: Store<S>) {\n  const [state, setState] = useState(store.get())\n\n  useEffect(() => {\n    return store.subscribe((state) => setState(state))\n  }, [store])\n\n  return state\n}\n"
  },
  {
    "path": "packages/react/src/types.ts",
    "content": "import type Lenis from 'lenis'\nimport type { LenisOptions, ScrollCallback } from 'lenis'\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react'\n\nexport type LenisContextValue = {\n  lenis: Lenis\n  addCallback: (callback: ScrollCallback, priority: number) => void\n  removeCallback: (callback: ScrollCallback) => void\n}\n\nexport type LenisProps = ComponentPropsWithoutRef<'div'> & {\n  /**\n   * Setup a global instance of Lenis\n   * if `asChild`, the component will render wrapper and content divs\n   * @default false\n   */\n  root?: boolean | 'asChild'\n  /**\n   * Lenis options\n   */\n  options?: LenisOptions\n  /**\n   * Auto-setup requestAnimationFrame\n   * @default true\n   * @deprecated use options.autoRaf instead\n   */\n  autoRaf?: boolean\n  /**\n   * Children\n   */\n  children?: ReactNode\n\n  /**\n   * Class name to be applied to the wrapper div\n   * @default ''\n   */\n  className?: string | undefined\n}\n\nexport type LenisRef = {\n  /**\n   * The wrapper div element\n   *\n   * Will only be defined if `root` is `false` or `root` is `asChild`\n   */\n  wrapper: HTMLDivElement | null\n  /**\n   * The content div element\n   *\n   * Will only be defined if `root` is `false` or `root` is `asChild`\n   */\n  content: HTMLDivElement | null\n  /**\n   * The lenis instance\n   */\n  lenis?: Lenis\n}\n"
  },
  {
    "path": "packages/react/src/use-lenis.ts",
    "content": "import type Lenis from 'lenis'\nimport type { ScrollCallback } from 'lenis'\nimport { useContext, useEffect } from 'react'\nimport { LenisContext, rootLenisContextStore } from './provider'\nimport { useStore } from './store'\nimport type { LenisContextValue } from './types'\n\n// Fall back to an empty object if both context and store are not available\nconst fallbackContext: Partial<LenisContextValue> = {}\n\n/**\n * Hook to access the Lenis instance and its methods\n *\n * @example <caption>Scroll callback</caption>\n *          useLenis((lenis) => {\n *            if (lenis.isScrolling) {\n *              console.log('Scrolling...')\n *            }\n *\n *            if (lenis.progress === 1) {\n *              console.log('At the end!')\n *            }\n *          })\n *\n * @example <caption>Scroll callback with dependencies</caption>\n *          useLenis((lenis) => {\n *            if (lenis.isScrolling) {\n *              console.log('Scrolling...', someDependency)\n *            }\n *          }, [someDependency])\n * @example <caption>Scroll callback with priority</caption>\n *          useLenis((lenis) => {\n *            if (lenis.isScrolling) {\n *              console.log('Scrolling...')\n *            }\n *          }, [], 1)\n * @example <caption>Instance access</caption>\n *          const lenis = useLenis()\n *\n *          handleClick() {\n *            lenis.scrollTo(100, {\n *              lerp: 0.1,\n *              duration: 1,\n *              easing: (t) => t,\n *              onComplete: () => {\n *                console.log('Complete!')\n *              }\n *            })\n *          }\n */\nexport function useLenis(\n  callback?: ScrollCallback,\n  deps: unknown[] = [],\n  priority = 0\n): Lenis | undefined {\n  // Try to get the lenis instance from the context first\n  const localContext = useContext(LenisContext)\n  // Fall back to the root store if the context is not available\n  const rootContext = useStore(rootLenisContextStore)\n  // Fall back to the fallback context if all else fails\n  const currentContext = localContext ?? rootContext ?? fallbackContext\n\n  const { lenis, addCallback, removeCallback } = currentContext\n\n  useEffect(() => {\n    if (!(callback && addCallback && removeCallback && lenis)) return\n\n    addCallback(callback, priority)\n    callback(lenis)\n\n    return () => {\n      removeCallback(callback)\n    }\n  }, [lenis, addCallback, removeCallback, priority, ...deps, callback])\n\n  return lenis\n}\n"
  },
  {
    "path": "packages/snap/README.md",
    "content": "# lenis/snap\r\n\r\n## Introduction\r\nlenis/snap provides a partial support for CSS scroll snap with [Lenis](https://github.com/darkroomengineering/lenis), see [Demo](https://lenis.darkroom.engineering/snap)\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm i lenis\r\n```\r\n\r\n## Usage\r\n\r\n### Basic\r\n\r\n```jsx\r\n    import Lenis from 'lenis'\r\n    import Snap from 'lenis/snap'\r\n\r\n    const lenis = new Lenis()\r\n\r\n    function raf(time) {\r\n        lenis.raf(time)\r\n        requestAnimationFrame(raf)\r\n    }\r\n\r\n    requestAnimationFrame(raf)\r\n\r\n    const snap = new Snap(lenis)\r\n\r\n    // add snaps points\r\n    snap.add(500) // snap at 500px\r\n    snap.add(1000) // snap at 1000px\r\n    snap.add(1500) // snap at 1500px\r\n\r\n    // or add an element to snap to\r\n    snap.addElement(document.querySelector('.element'), {\r\n      align: ['start', 'end'], // 'start', 'center', 'end'\r\n    })\r\n\r\n    snap.addElement(document.querySelector('.element1'), {\r\n      align: 'center', // 'start', 'center', 'end'\r\n    })\r\n\r\n    // or add elements at once\r\n    snap.addElements(document.querySelectorAll('.section'), {\r\n      align: ['start', 'end'], // 'start', 'center', 'end'\r\n    })\r\n    \r\n    \r\n```\r\n\r\n### Slideshow\r\n\r\n```jsx\r\n    const snap = new Snap(lenis, {\r\n      type: 'lock',\r\n      distanceThreshold: '100%',\r\n      debounce: 0,\r\n    })\r\n```\r\n\r\n## Options\r\n\r\n- `type`: `proximity` (default), `mandatory` see [scroll-snap-type](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type) or `lock`.\r\n- `distanceThreshold`: `string | number` (default: '50%'). The distance threshold from the snap point to the scroll position. Ignored when `type` is `mandatory`. If a percentage, it is relative to the viewport size. If a number, it is absolute.\r\n- `debounce`: `number` (default: 500). The debounce time for the snap.\r\n- `onSnapStart`: `function`. Callback when snap starts.\r\n- `onSnapComplete`: `function`. Callback when snap completes.\r\n- `lerp`: `number` Lerp value for snapping. (default: lenis lerp). \r\n- `easing`: `function`. Easing function for snapping. (default: lenis easing).\r\n- `duration`: `number`. Duration for snapping. (default: lenis duration).\r\n\r\n\r\n## Methods\r\n\r\n- `add(value: number)`: Add a snap point.\r\n- `addElement(element: HTMLElement, options: SnapElementOptions = {})`: Add an element to snap to.\r\n- `addElements(elements: HTMLElement[], options: SnapElementOptions = {})`: Add elements at once.\r\n- `next()`: Go to the next snap point.\r\n- `previous()`: Go to the previous snap point.\r\n- `goTo(index: number)`: Go to a specific snap point.\r\n- `start()`: Start the snap.\r\n- `stop()`: Stop the snap.\r\n- `resize()`: Force recalculate the snap points."
  },
  {
    "path": "packages/snap/browser.ts",
    "content": "// This file serves as an entry point for the package\nimport { Snap } from './src/snap'\n\n// @ts-expect-error\nglobalThis.Snap = Snap\n"
  },
  {
    "path": "packages/snap/index.ts",
    "content": "// This file serves as an entry point for the package\nexport { Snap as default } from './src/snap'\nexport * from './src/types'\n"
  },
  {
    "path": "packages/snap/package.json",
    "content": "{\n  \"name\": \"lenis-snap\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/snap/src/debounce.ts",
    "content": "export function debounce<CB extends (...args: unknown[]) => void>(\n  callback: CB,\n  delay: number\n) {\n  let timer: ReturnType<typeof setTimeout> | undefined\n  return function <T>(this: T, ...args: Parameters<typeof callback>): void {\n    clearTimeout(timer)\n    timer = setTimeout(() => {\n      timer = undefined\n      callback.apply(this, args)\n    }, delay)\n  }\n}\n"
  },
  {
    "path": "packages/snap/src/element.ts",
    "content": "import { debounce } from './debounce'\n\nfunction removeParentSticky(element: HTMLElement) {\n  const position = getComputedStyle(element).position\n\n  const isSticky = position === 'sticky'\n\n  if (isSticky) {\n    element.style.setProperty('position', 'static')\n    element.dataset.sticky = 'true'\n  }\n\n  if (element.offsetParent) {\n    removeParentSticky(element.offsetParent as HTMLElement)\n  }\n}\n\nfunction addParentSticky(element: HTMLElement) {\n  if (element?.dataset?.sticky === 'true') {\n    element.style.removeProperty('position')\n    delete element.dataset.sticky\n  }\n\n  if (element.offsetParent) {\n    addParentSticky(element.offsetParent as HTMLElement)\n  }\n}\n\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\n  const top = accumulator + element.offsetTop\n  if (element.offsetParent) {\n    return offsetTop(element.offsetParent as HTMLElement, top)\n  }\n  return top\n}\n\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\n  const left = accumulator + element.offsetLeft\n  if (element.offsetParent) {\n    return offsetLeft(element.offsetParent as HTMLElement, left)\n  }\n  return left\n}\n\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\n  const top = accumulator + element.scrollTop\n  if (element.offsetParent) {\n    return scrollTop(element.offsetParent as HTMLElement, top)\n  }\n  return top + window.scrollY\n}\n\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\n  const left = accumulator + element.scrollLeft\n  if (element.offsetParent) {\n    return scrollLeft(element.offsetParent as HTMLElement, left)\n  }\n  return left + window.scrollX\n}\n\nexport type SnapElementOptions = {\n  align?: string | string[]\n  ignoreSticky?: boolean\n  ignoreTransform?: boolean\n}\n\ntype Rect = {\n  top: number\n  left: number\n  width: number\n  height: number\n  x: number\n  y: number\n  bottom: number\n  right: number\n  element: HTMLElement\n}\n\nexport class SnapElement {\n  element: HTMLElement\n  options: SnapElementOptions\n  align: string[]\n  // @ts-expect-error\n  rect: Rect = {}\n  wrapperResizeObserver: ResizeObserver\n  resizeObserver: ResizeObserver\n  debouncedWrapperResize: () => void\n\n  constructor(\n    element: HTMLElement,\n    {\n      align = ['start'],\n      ignoreSticky = true,\n      ignoreTransform = false,\n    }: SnapElementOptions = {}\n  ) {\n    this.element = element\n\n    this.options = { align, ignoreSticky, ignoreTransform }\n\n    this.align = [align].flat()\n\n    this.debouncedWrapperResize = debounce(this.onWrapperResize, 500)\n\n    this.wrapperResizeObserver = new ResizeObserver(this.debouncedWrapperResize)\n    this.wrapperResizeObserver.observe(document.body)\n    this.onWrapperResize()\n\n    this.resizeObserver = new ResizeObserver(this.onResize)\n    this.resizeObserver.observe(this.element)\n    this.setRect({\n      width: this.element.offsetWidth,\n      height: this.element.offsetHeight,\n    })\n  }\n\n  destroy() {\n    this.wrapperResizeObserver.disconnect()\n    this.resizeObserver.disconnect()\n  }\n\n  setRect({\n    top,\n    left,\n    width,\n    height,\n    element,\n  }: {\n    top?: number\n    left?: number\n    width?: number\n    height?: number\n    element?: HTMLElement\n  } = {}) {\n    top = top ?? this.rect.top\n    left = left ?? this.rect.left\n    width = width ?? this.rect.width\n    height = height ?? this.rect.height\n    element = element ?? this.rect.element\n\n    if (\n      top === this.rect.top &&\n      left === this.rect.left &&\n      width === this.rect.width &&\n      height === this.rect.height &&\n      element === this.rect.element\n    )\n      return\n\n    this.rect.top = top\n    this.rect.y = top\n    this.rect.width = width\n    this.rect.height = height\n    this.rect.left = left\n    this.rect.x = left\n    this.rect.bottom = top + height\n    this.rect.right = left + width\n  }\n\n  onWrapperResize = () => {\n    let top: number | undefined\n    let left: number | undefined\n\n    if (this.options.ignoreSticky) removeParentSticky(this.element)\n    if (this.options.ignoreTransform) {\n      top = offsetTop(this.element)\n      left = offsetLeft(this.element)\n    } else {\n      const rect = this.element.getBoundingClientRect()\n      top = rect.top + scrollTop(this.element)\n      left = rect.left + scrollLeft(this.element)\n    }\n    if (this.options.ignoreSticky) addParentSticky(this.element)\n\n    this.setRect({ top, left })\n  }\n\n  onResize = ([entry]: ResizeObserverEntry[]) => {\n    if (!entry?.borderBoxSize[0]) return\n    const width = entry.borderBoxSize[0].inlineSize\n    const height = entry.borderBoxSize[0].blockSize\n\n    this.setRect({ width, height })\n  }\n}\n"
  },
  {
    "path": "packages/snap/src/snap.ts",
    "content": "import type Lenis from 'lenis'\nimport type { VirtualScrollData } from 'lenis'\nimport { debounce } from './debounce'\nimport type { SnapElementOptions } from './element'\nimport { SnapElement } from './element'\nimport type { SnapItem, SnapOptions } from './types'\nimport type { UID } from './uid'\nimport { uid } from './uid'\n\n// TODO:\n// - fix wheel scrolling after limits (see console scroll to)\n// - arrow, spacebar\n\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\n\n/**\n * Snap class to handle the snap functionality\n *\n * @example\n * const snap = new Snap(lenis, {\n *   type: 'mandatory', // 'mandatory', 'proximity' or 'lock'\n *   onSnapStart: (snap) => {\n *     console.log('onSnapStart', snap)\n *   },\n *   onSnapComplete: (snap) => {\n *     console.log('onSnapComplete', snap)\n *   },\n * })\n *\n * snap.add(500) // snap at 500px\n *\n * const removeSnap = snap.add(500)\n *\n * if (someCondition) {\n *   removeSnap()\n * }\n */\nexport class Snap {\n  options: RequiredPick<SnapOptions, 'type' | 'debounce'>\n  elements = new Map<UID, SnapElement>()\n  snaps = new Map<UID, SnapItem>()\n  viewport: { width: number; height: number } = {\n    width: window.innerWidth,\n    height: window.innerHeight,\n  }\n  isStopped = false\n  onSnapDebounced: (e: VirtualScrollData) => void\n  currentSnapIndex?: number\n\n  constructor(\n    private lenis: Lenis,\n    {\n      type = 'proximity',\n      lerp,\n      easing,\n      duration,\n      distanceThreshold = '50%', // useless when type is \"mandatory\"\n      debounce: debounceDelay = 500,\n      onSnapStart,\n      onSnapComplete,\n    }: SnapOptions = {}\n  ) {\n    if (!window.lenis) {\n      window.lenis = {}\n    }\n\n    window.lenis.snap = true\n\n    this.options = {\n      type,\n      lerp,\n      easing,\n      duration,\n      distanceThreshold,\n      debounce: debounceDelay,\n      onSnapStart,\n      onSnapComplete,\n    }\n\n    this.onWindowResize()\n    window.addEventListener('resize', this.onWindowResize)\n\n    this.onSnapDebounced = debounce(\n      this.onSnap as (...args: unknown[]) => void,\n      this.options.debounce\n    )\n\n    this.lenis.on('virtual-scroll', this.onSnapDebounced)\n  }\n\n  /**\n   * Destroy the snap instance\n   */\n  destroy() {\n    this.lenis.off('virtual-scroll', this.onSnapDebounced)\n    window.removeEventListener('resize', this.onWindowResize)\n    this.elements.forEach((element) => {\n      element.destroy()\n    })\n  }\n\n  /**\n   * Start the snap after it has been stopped\n   */\n  start() {\n    this.isStopped = false\n  }\n\n  /**\n   * Stop the snap\n   */\n  stop() {\n    this.isStopped = true\n  }\n\n  /**\n   * Add a snap to the snap instance\n   *\n   * @param value The value to snap to\n   * @param userData User data that will be forwarded through the snap event\n   * @returns Unsubscribe function\n   */\n  add(value: number): () => void {\n    const id = uid()\n\n    this.snaps.set(id, { value })\n\n    return () => this.snaps.delete(id)\n  }\n\n  /**\n   * Add an element to the snap instance\n   *\n   * @param element The element to add\n   * @param options The options for the element\n   * @returns Unsubscribe function\n   */\n  addElement(\n    element: HTMLElement,\n    options: SnapElementOptions = {}\n  ): () => void {\n    const id = uid()\n\n    this.elements.set(id, new SnapElement(element, options))\n\n    return () => this.elements.delete(id)\n  }\n\n  addElements(\n    elements: HTMLElement[],\n    options: SnapElementOptions = {}\n  ): () => void {\n    const map = [...elements].map((element) =>\n      this.addElement(element, options)\n    )\n    return () => {\n      map.forEach((remove) => {\n        remove()\n      })\n    }\n  }\n\n  private onWindowResize = () => {\n    this.viewport.width = window.innerWidth\n    this.viewport.height = window.innerHeight\n  }\n\n  private computeSnaps = () => {\n    const { isHorizontal } = this.lenis\n\n    let snaps = [...this.snaps.values()] as SnapItem[]\n\n    this.elements.forEach(({ rect, align }) => {\n      let value: number | undefined\n\n      align.forEach((align) => {\n        if (align === 'start') {\n          value = rect.top\n        } else if (align === 'center') {\n          value = isHorizontal\n            ? rect.left + rect.width / 2 - this.viewport.width / 2\n            : rect.top + rect.height / 2 - this.viewport.height / 2\n        } else if (align === 'end') {\n          value = isHorizontal\n            ? rect.left + rect.width - this.viewport.width\n            : rect.top + rect.height - this.viewport.height\n        }\n\n        if (typeof value === 'number') {\n          snaps.push({ value: Math.ceil(value) })\n        }\n      })\n    })\n\n    snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\n\n    return snaps\n  }\n\n  previous() {\n    this.goTo((this.currentSnapIndex ?? 0) - 1)\n  }\n\n  next() {\n    this.goTo((this.currentSnapIndex ?? 0) + 1)\n  }\n\n  goTo(index: number) {\n    const snaps = this.computeSnaps()\n\n    if (snaps.length === 0) return\n\n    this.currentSnapIndex = Math.max(0, Math.min(index, snaps.length - 1))\n\n    const currentSnap = snaps[this.currentSnapIndex]\n    if (currentSnap === undefined) return\n\n    this.lenis.scrollTo(currentSnap.value, {\n      duration: this.options.duration,\n      easing: this.options.easing,\n      lerp: this.options.lerp,\n      lock: this.options.type === 'lock',\n      userData: { initiator: 'snap' },\n      onStart: () => {\n        this.options.onSnapStart?.({\n          index: this.currentSnapIndex,\n          ...currentSnap,\n        })\n      },\n      onComplete: () => {\n        this.options.onSnapComplete?.({\n          index: this.currentSnapIndex,\n          ...currentSnap,\n        })\n      },\n    })\n  }\n\n  get distanceThreshold() {\n    let distanceThreshold = Number.POSITIVE_INFINITY\n    if (this.options.type === 'mandatory') return Number.POSITIVE_INFINITY\n\n    const { isHorizontal } = this.lenis\n\n    const axis = isHorizontal ? 'width' : 'height'\n\n    if (\n      typeof this.options.distanceThreshold === 'string' &&\n      this.options.distanceThreshold.endsWith('%')\n    ) {\n      distanceThreshold =\n        (Number(this.options.distanceThreshold.replace('%', '')) / 100) *\n        this.viewport[axis]\n    } else if (typeof this.options.distanceThreshold === 'number') {\n      distanceThreshold = this.options.distanceThreshold\n    } else {\n      distanceThreshold = this.viewport[axis]\n    }\n\n    return distanceThreshold\n  }\n\n  private onSnap = (e: VirtualScrollData) => {\n    if (this.isStopped) return\n\n    if (e.event.type === 'touchmove') return\n\n    if (\n      this.options.type === 'lock' &&\n      this.lenis.userData?.initiator === 'snap'\n    )\n      return\n\n    let { scroll, isHorizontal } = this.lenis\n    const delta = isHorizontal ? e.deltaX : e.deltaY\n    scroll = Math.ceil(this.lenis.scroll + delta)\n\n    const snaps = this.computeSnaps()\n\n    if (snaps.length === 0) return\n\n    let snapIndex: number | undefined\n\n    const prevSnapIndex = snaps.findLastIndex(({ value }) => value < scroll)\n    const nextSnapIndex = snaps.findIndex(({ value }) => value > scroll)\n\n    if (this.options.type === 'lock') {\n      if (delta > 0) {\n        snapIndex = nextSnapIndex\n      } else if (delta < 0) {\n        snapIndex = prevSnapIndex\n      }\n    } else {\n      const prevSnap = snaps[prevSnapIndex]!\n      const distanceToPrevSnap = prevSnap\n        ? Math.abs(scroll - prevSnap.value)\n        : Number.POSITIVE_INFINITY\n\n      const nextSnap = snaps[nextSnapIndex]!\n      const distanceToNextSnap = nextSnap\n        ? Math.abs(scroll - nextSnap.value)\n        : Number.POSITIVE_INFINITY\n      snapIndex =\n        distanceToPrevSnap < distanceToNextSnap ? prevSnapIndex : nextSnapIndex\n    }\n\n    if (snapIndex === undefined) return\n    if (snapIndex === -1) return\n\n    snapIndex = Math.max(0, Math.min(snapIndex, snaps.length - 1))\n\n    const snap = snaps[snapIndex]!\n\n    const distance = Math.abs(scroll - snap.value)\n\n    if (distance <= this.distanceThreshold) {\n      this.goTo(snapIndex)\n    }\n  }\n\n  resize() {\n    this.elements.forEach((element) => {\n      element.onWrapperResize()\n    })\n  }\n}\n"
  },
  {
    "path": "packages/snap/src/types.ts",
    "content": "import type { EasingFunction } from 'lenis'\n\nexport type SnapItem = {\n  value: number\n}\n\nexport type OnSnapCallback = (item: SnapItem & { index?: number }) => void\n\nexport type SnapOptions = {\n  /**\n   * Snap type\n   * @default 'proximity'\n   */\n  type?: 'mandatory' | 'proximity' | 'lock'\n  /**\n   * @description Linear interpolation (lerp) intensity (between 0 and 1)\n   */\n  lerp?: number\n  /**\n   * @description The easing function to use for the snap animation\n   */\n  easing?: EasingFunction\n  /**\n   * @description The duration of the snap animation (in s)\n   */\n  duration?: number\n  /**\n   * @default '50%'\n   * @description The distance threshold from the snap point to the scroll position. Ignored when `type` is `mandatory`. If a percentage, it is relative to the viewport size. If a number, it is absolute.\n   */\n  distanceThreshold?: number | `${number}%`\n  /**\n   * @default 500\n   * @description The debounce delay (in ms) to prevent snapping too often.\n   */\n  debounce?: number\n  /**\n   * @description Called when the snap starts\n   */\n  onSnapStart?: OnSnapCallback\n  /**\n   * @description Called when the snap completes\n   */\n  onSnapComplete?: OnSnapCallback\n}\n"
  },
  {
    "path": "packages/snap/src/uid.ts",
    "content": "let index = 0\n\nexport type UID = number\n\nexport function uid(): UID {\n  return index++\n}\n"
  },
  {
    "path": "packages/vue/README.md",
    "content": "# lenis/vue\n\n## Introduction\nlenis/vue provides a `<VueLenis>` component that creates a [Lenis](https://github.com/darkroomengineering/lenis) instance and provides it to its children via context. This allows you to use Lenis in your Vue app without worrying about passing the instance down through props. It also provides a `useLenis` hook that allows you to access the Lenis instance from any component in your app. lenis/vue provides a vueLenisPlugin that you can use to register the component globally. This allows you to use the component in your templates without having to import it, by using the `vue-lenis` template tag.\n\n\n## Installation\n\n```bash\nnpm i lenis\n```\n\n### Vue\n```js\n// main.js\nimport { createApp } from 'vue'\nimport LenisVue from 'lenis/vue'\n\nconst app = createApp({})\n\napp.use(LenisVue)\n```\n\n### Nuxt\n\n```js\n// nuxt.config.js\nexport default defineNuxtConfig({\n  modules: ['lenis/nuxt'],\n})\n```\n\n## Usage\n\n```vue\n<script setup>\nimport { VueLenis, useLenis } from 'lenis/vue' // Also available as global imports, no need to import them manually\nimport { watch } from 'vue'\n\nconst lenisOptions = {\n  // lenis options (optional)\n}\n\nconst lenis = useLenis((lenis) => {\n  // called every scroll\n  console.log(lenis)\n})\n\nwatch(\n  lenis,\n  (lenis) => {\n    // lenis instance\n    console.log(lenis)\n  },\n  { immediate: true }\n)\n</script>\n\n<template>\n  <VueLenis root :options=\"lenisOptions\" />\n  <!-- content -->\n</template>\n```\n\n## Props\n- `options`: [Lenis options](https://github.com/darkroomengineering/lenis#instance-settings).\n- `root`: if `true`, Lenis will be instanciate using `<html>` scroll, then you can use the `useLenis` hook to access the Lenis instance from anywhere in your app. Default: `false`.\n\n## Hooks\nOnce the Lenis context is set (components mounted inside `<VueLenis>` or `<vue-lenis>`) you can use these handy hooks:\n\n`useLenis` is a hook that returns the Lenis instance\n\nThe hook takes two arguments:\n- `callback`: The function to be called whenever a scroll event is emitted\n- `priority`: Manage callback execution order\n\n```vue\n<script setup>\nimport { VueLenis, useLenis } from 'lenis/vue'\nimport { watch } from 'vue'\n\nconst scrollCallback = (lenis) => {\n  // called on every scroll\n  // useLenis provides the lenis instance as an argument\n}\n\nconst lenis = useLenis(scrollCallback, 0) // where 0 is the default callback priority\n</script>\n\n<template>\n  <VueLenis root />\n  <!-- content -->\n</template>\n```\n\n\n\n\n\n## Examples\n\n### GSAP integration\n\n```vue\n<script setup>\nimport { ref, watchEffect } from 'vue'\nimport { VueLenis, useLenis } from 'lenis/vue'\nimport gsap from 'gsap'\nimport { ScrollTrigger } from 'gsap/ScrollTrigger'\n\nconst lenisRef = ref()\n\nwatchEffect((onInvalidate) => {\n   if (!lenisRef.value?.lenis) return\n\n  //  if using GSAP ScrollTrigger, update ScrollTrigger on scroll\n  lenisRef.value.lenis.on('scroll', ScrollTrigger.update)\n\n  // add the Lenis requestAnimationFrame (raf) method to GSAP's ticker\n  // this ensures Lenis's smooth scroll animation updates on each GSAP tick\n  function update(time) {\n    lenisRef.value.lenis.raf(time * 1000)\n  }\n  gsap.ticker.add(update)\n\n  // disable lag smoothing in GSAP to prevent any delay in scroll animations\n  gsap.ticker.lagSmoothing(0)\n\n  // clean up GSAP's ticker from the previous execution of watchEffect, or when the effect is stopped\n  onInvalidate(() => {\n    gsap.ticker.remove(update)\n  })\n})\n\n// if using GSAP ScrollTrigger, remember to register the plugin\nonMounted(() => {\n  gsap.registerPlugin(ScrollTrigger)\n})\n</script>\n\n<template>\n  <VueLenis root ref=\"lenisRef\" :options=\"{ autoRaf: false }\" />\n  <!-- content -->\n</template>\n```\n\n### Motion Integration\n```vue\n<script setup>\nimport { VueLenis } from 'lenis/vue'\nimport { cancelFrame, frame } from 'motion-v'\nimport { onMounted, onUnmounted, ref } from 'vue'\n\nconst lenisRef = ref()\n\nfunction update({ timestamp }) {\n  lenisRef.value?.lenis?.raf(timestamp)\n}\n\nonMounted(() => {\n  frame.update(update, true)\n})\n\nonUnmounted(() => {\n  cancelFrame(update)\n})\n</script>\n\n<template>\n  <VueLenis ref=\"lenisRef\" root :options=\"{ autoRaf: false }\">\n  <!-- content -->\n</template>\n```\n\n<br/>\n\n## License\n\nMIT © [darkroom.engineering](https://github.com/darkroomengineering)\n"
  },
  {
    "path": "packages/vue/index.ts",
    "content": "// This file serves as an entry point for the package\nexport {\n  VueLenis as Lenis,\n  VueLenis,\n  vueLenisPlugin as default,\n} from './src/provider'\nexport { useLenis } from './src/use-lenis'\n"
  },
  {
    "path": "packages/vue/nuxt/module.ts",
    "content": "import {\n  addComponent,\n  addImports,\n  addPlugin,\n  createResolver,\n  defineNuxtModule,\n} from '@nuxt/kit'\n\n// Module options TypeScript interface definition\nexport type ModuleOptions = Record<string, never>\n\nconst nuxtModule = defineNuxtModule<ModuleOptions>({\n  meta: {\n    name: 'lenis/nuxt',\n    configKey: 'lenis',\n  },\n  // Default configuration options of the Nuxt module\n  defaults: {},\n  setup(_options, _nuxt) {\n    const { resolve } = createResolver(import.meta.url)\n\n    addPlugin({\n      src: resolve('./nuxt/runtime/lenis.mjs'),\n      name: 'lenis',\n    })\n\n    addImports({ name: 'useLenis', from: 'lenis/vue' })\n    addComponent({\n      name: 'VueLenis',\n      filePath: 'lenis/vue',\n      global: true,\n      export: 'VueLenis',\n    })\n  },\n})\n\nexport default nuxtModule\nexport * from 'lenis/vue'\n"
  },
  {
    "path": "packages/vue/nuxt/runtime/lenis.ts",
    "content": "import vuePlugin from 'lenis/vue'\nimport type { Plugin } from '#app'\nimport { defineNuxtPlugin } from '#imports'\n\nconst plugin = defineNuxtPlugin({\n  name: 'lenis',\n  setup(nuxtApp: unknown) {\n    ;(nuxtApp as { vueApp: { use: (plugin: unknown) => void } }).vueApp.use(\n      vuePlugin\n    )\n  },\n}) satisfies Plugin\n\nexport default plugin\n"
  },
  {
    "path": "packages/vue/nuxt/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"#imports\": [\"types/imports.d.ts\"],\n      \"#app\": [\"types/app.d.ts\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vue/nuxt/types/app.d.ts",
    "content": "declare module '#app' {\n  export interface Plugin {\n    name?: string\n    setup: (nuxtApp: unknown) => void\n  }\n}\n"
  },
  {
    "path": "packages/vue/nuxt/types/imports.d.ts",
    "content": "import type { Plugin } from '#app'\n\ndeclare module '#imports' {\n  export function defineNuxtPlugin(plugin: Plugin): Plugin\n}\n"
  },
  {
    "path": "packages/vue/package.json",
    "content": "{\n  \"name\": \"lenis-vue\",\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"vue\": \"^3.5.13\"\n  }\n}\n"
  },
  {
    "path": "packages/vue/src/provider.ts",
    "content": "import Lenis, { type LenisOptions, type ScrollCallback } from 'lenis'\nimport type {\n  HTMLAttributes,\n  InjectionKey,\n  Plugin,\n  PropType,\n  ShallowRef,\n  ToRefs,\n} from 'vue'\nimport {\n  defineComponent,\n  h,\n  onWatcherCleanup,\n  provide,\n  reactive,\n  ref,\n  shallowRef,\n  watch,\n} from 'vue'\nimport { globalAddCallback, globalLenis, globalRemoveCallback } from './store'\n\nexport const LenisSymbol: InjectionKey<ShallowRef<Lenis | undefined>> =\n  Symbol('LenisContext')\nexport const AddCallbackSymbol: InjectionKey<\n  ((callback: ScrollCallback, priority: number) => void) | undefined\n> = Symbol('AddCallback')\nexport const RemoveCallbackSymbol: InjectionKey<\n  ((callback: ScrollCallback) => void) | undefined\n> = Symbol('RemoveCallback')\n\nexport type LenisExposed = {\n  wrapper?: HTMLDivElement\n  content?: HTMLDivElement\n  lenis?: Lenis\n}\n\nconst VueLenisImpl = defineComponent({\n  name: 'VueLenis',\n  props: {\n    root: {\n      type: Boolean as PropType<boolean>,\n      default: false,\n    },\n    autoRaf: {\n      type: Boolean as PropType<boolean>,\n      default: true,\n    },\n    options: {\n      type: Object as PropType<LenisOptions>,\n      default: () => ({}),\n    },\n    props: {\n      type: Object as PropType<HTMLAttributes>,\n      default: () => ({}),\n    },\n  },\n  setup(props, { slots, expose }) {\n    const lenisRef = shallowRef<Lenis>()\n    const wrapper = ref<HTMLDivElement>()\n    const content = ref<HTMLDivElement>()\n    // Setup exposed properties\n    expose<ToRefs<LenisExposed>>({\n      lenis: lenisRef,\n      wrapper,\n      content,\n    })\n\n    // Sync options\n    watch(\n      [() => props.options, wrapper, content],\n      () => {\n        const isClient = typeof window !== 'undefined'\n\n        if (!isClient) return\n\n        if (!(props.root || (wrapper.value && content.value))) return\n\n        lenisRef.value = new Lenis({\n          ...props.options,\n          ...(!props.root\n            ? {\n                wrapper: wrapper.value,\n                content: content.value,\n              }\n            : {}),\n          autoRaf: props.options?.autoRaf ?? props.autoRaf,\n        })\n\n        onWatcherCleanup(() => {\n          lenisRef.value?.destroy()\n          lenisRef.value = undefined\n        })\n      },\n      { deep: true, immediate: true }\n    )\n\n    const callbacks = reactive<\n      { callback: ScrollCallback; priority: number }[]\n    >([])\n\n    function addCallback(callback: ScrollCallback, priority: number) {\n      callbacks.push({ callback, priority })\n      callbacks.sort((a, b) => a.priority - b.priority)\n    }\n\n    function removeCallback(callback: ScrollCallback) {\n      callbacks.splice(\n        callbacks.findIndex((cb) => cb.callback === callback),\n        1\n      )\n    }\n\n    const onScroll: ScrollCallback = (data) => {\n      for (const { callback } of callbacks) {\n        callback(data)\n      }\n    }\n\n    watch(\n      [lenisRef, () => props.root],\n      ([lenis, root]) => {\n        lenis?.on('scroll', onScroll)\n\n        if (root) {\n          globalLenis.value = lenis\n          globalAddCallback.value = addCallback\n          globalRemoveCallback.value = removeCallback\n\n          onWatcherCleanup(() => {\n            globalLenis.value = undefined\n            globalAddCallback.value = undefined\n            globalRemoveCallback.value = undefined\n          })\n        }\n      },\n      { immediate: true }\n    )\n\n    if (!props.root) {\n      provide(LenisSymbol, lenisRef)\n      provide(AddCallbackSymbol, addCallback)\n      provide(RemoveCallbackSymbol, removeCallback)\n    }\n\n    return () => {\n      if (props.root) {\n        return slots.default?.()\n      }\n      return h('div', { ref: wrapper, ...props?.props }, [\n        h('div', { ref: content }, slots.default?.()),\n      ])\n    }\n  },\n})\n\nexport const VueLenis = VueLenisImpl as typeof VueLenisImpl & {\n  new (): LenisExposed\n}\n\nexport const vueLenisPlugin: Plugin = (app) => {\n  app.component('vue-lenis', VueLenis)\n  // Setup a global provide to silence top level useLenis injection warning\n  app.provide(LenisSymbol, shallowRef(undefined))\n  app.provide(AddCallbackSymbol, undefined)\n  app.provide(RemoveCallbackSymbol, undefined)\n}\n\n// @ts-expect-error\ndeclare module '@vue/runtime-core' {\n  export interface GlobalComponents {\n    'vue-lenis': typeof VueLenis\n  }\n}\n"
  },
  {
    "path": "packages/vue/src/store.ts",
    "content": "import type Lenis from 'lenis'\nimport type { ScrollCallback } from 'lenis'\nimport { shallowRef } from 'vue'\n\nexport const globalLenis = shallowRef<Lenis>()\nexport const globalAddCallback =\n  shallowRef<(callback: ScrollCallback, priority: number) => void>()\nexport const globalRemoveCallback =\n  shallowRef<(callback: ScrollCallback) => void>()\n"
  },
  {
    "path": "packages/vue/src/use-lenis.ts",
    "content": "import type Lenis from 'lenis'\nimport type { ScrollCallback } from 'lenis'\nimport { type ComputedRef, computed, inject, nextTick, onWatcherCleanup, watch } from 'vue'\nimport {\n  AddCallbackSymbol,\n  LenisSymbol,\n  RemoveCallbackSymbol,\n} from './provider'\nimport { globalAddCallback, globalLenis, globalRemoveCallback } from './store'\n\nexport function useLenis(callback?: ScrollCallback, priority = 0): ComputedRef<Lenis | undefined> {\n  const lenisInjection = inject(LenisSymbol)\n  const addCallbackInjection = inject(AddCallbackSymbol)\n  const removeCallbackInjection = inject(RemoveCallbackSymbol)\n\n  const addCallback = computed(() =>\n    addCallbackInjection ? addCallbackInjection : globalAddCallback.value\n  )\n  const removeCallback = computed(() =>\n    removeCallbackInjection\n      ? removeCallbackInjection\n      : globalRemoveCallback.value\n  )\n\n  const lenis = computed(() =>\n    lenisInjection?.value ? lenisInjection.value : globalLenis.value\n  )\n\n  if (typeof window !== 'undefined') {\n    // Wait two ticks to make sure the lenis instance is mounted\n    nextTick(() => {\n      nextTick(() => {\n        // @ts-expect-error - import.meta.env is available in vite and nuxt\n        if (!lenis.value && import.meta.env.DEV) {\n          console.warn(\n            'No lenis instance found, either mount a root lenis instance or wrap your component in a lenis provider'\n          )\n        }\n      })\n    })\n  }\n\n  watch(\n    [lenis, addCallback, removeCallback],\n    ([lenis, addCallback, removeCallback]) => {\n      if (!(lenis && addCallback && removeCallback && callback)) return\n\n      addCallback?.(callback, priority)\n      callback?.(lenis!)\n\n      onWatcherCleanup(() => {\n        removeCallback?.(callback)\n      })\n    },\n    {\n      immediate: true,\n    }\n  )\n  return lenis\n}\n"
  },
  {
    "path": "playground/.gitignore",
    "content": "# build output\ndist/\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\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# jetbrains setting folder\n.idea/\n\nenv.d.ts"
  },
  {
    "path": "playground/astro.config.mjs",
    "content": "import path from 'node:path'\nimport react from '@astrojs/react'\nimport vue from '@astrojs/vue'\nimport { defineConfig } from 'astro/config'\n\nconst root = path.resolve('..')\n\nexport default defineConfig({\n  integrations: [react(), vue({ appEntrypoint: './vue/setup' })],\n  devToolbar: {\n    enabled: false,\n  },\n  srcDir: './www',\n  vite: {\n    resolve: {\n      alias: {\n        'lenis/dist/lenis.css': path.resolve(root, 'dist/lenis.css'),\n        'lenis/react': path.resolve(root, 'dist/lenis-react.mjs'),\n        'lenis/snap': path.resolve(root, 'dist/lenis-snap.mjs'),\n        'lenis/vue': path.resolve(root, 'dist/lenis-vue.mjs'),\n        lenis: path.resolve(root, 'dist/lenis.mjs'),\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "playground/core/browser.js",
    "content": "const lenis = new Lenis()\n\nlenis.on('scroll', (e) => {\n  console.log(e)\n})\n\nfunction raf(time) {\n  lenis.raf(time)\n  requestAnimationFrame(raf)\n}\n\nrequestAnimationFrame(raf)\n"
  },
  {
    "path": "playground/core/static.html",
    "content": "<html lang=\"en\">\r\n  <head>\r\n    <style>\r\n      body {\r\n        height: 300vh;\r\n      }\r\n    </style>\r\n    <script src=\"../../dist/lenis.min.js\"></script>\r\n    <script>\r\n      const lenis = new Lenis({\r\n        autoRaf: true,\r\n      })\r\n      console.log(lenis)\r\n    </script>\r\n  </head>\r\n  <body>\r\n    Lorem ipsum dolor, sit amet consectetur adipisicing elit. Inventore aliquid\r\n    aliquam quam officiis hic ut voluptatibus dicta perferendis, voluptate iure\r\n    modi iste ratione explicabo architecto impedit ipsa. Blanditiis, tenetur\r\n    itaque.\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "playground/core/style.css",
    "content": "#scroll-to-top {\n  position: fixed;\n  bottom: 20px;\n  right: 20px;\n  padding: 10px;\n  border: 1px solid #ccc;\n  background-color: #fff;\n  cursor: pointer;\n}\n\n#nested-horizontal {\n  width: 50vw;\n  margin: 0 auto;\n  /* height: 200px; */\n  overflow-x: auto;\n  overscroll-behavior: contain;\n\n  #nested-horizontal-content {\n    width: 1000px;\n    height: 100%;\n  }\n}\n\n#nested {\n  width: 50vw;\n  margin: 0 auto;\n  height: 200px;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  /* overflow-x: auto; */\n}\n\n#debug {\n  position: fixed;\n  top: 4rem;\n}\n\n/* html {\n  overflow: hidden;\n  height: 100%;\n}\n\nbody {\n  overflow-y: auto;\n  height: 100%;\n} */\n\n/* this prevents UI to collapse on scroll */\n/* html,\nbody {\n  overflow: hidden;\n  height: 100%;\n}\n\nmain {\n  height: 100%;\n  overflow-y: auto;\n} */\n\n.lenis-stopped {\n  background-color: red;\n}\n\n/* html {\n  overflow: hidden;\n} */\n"
  },
  {
    "path": "playground/core/test.ts",
    "content": "import Lenis from 'lenis'\nimport { LoremIpsum } from 'lorem-ipsum'\n\ndocument.querySelector('#nested-content')!.innerHTML =\n  new LoremIpsum().generateParagraphs(60)\ndocument.querySelector('#nested-horizontal-content')!.innerHTML =\n  new LoremIpsum().generateParagraphs(3)\ndocument\n  .querySelector('#app')!\n  .insertAdjacentText('afterbegin', new LoremIpsum().generateParagraphs(20))\ndocument\n  .querySelector('#app')!\n  .insertAdjacentText(\n    'beforeend',\n    `${new LoremIpsum().generateParagraphs(40)}test123`\n  )\n\n// document.querySelector('main')?.addEventListener('scrollend', () => {\n//   console.log('scrollend')\n// })\n\nwindow.addEventListener('scroll', (_e) => {\n  // console.log('window scroll', e)\n})\n\nwindow.addEventListener('scrollend', (e) => {\n  // console.log('window scrollend', e)\n})\n\ndocument.querySelector('#nested')?.addEventListener('scrollend', (_e) => {\n  // console.log('nested scrollend', e)\n})\n\nwindow.addEventListener('hashchange', () => {\n  console.log('hashchange')\n})\n\nconst lenis = new Lenis({\n  smoothWheel: true,\n  autoRaf: true,\n  anchors: {\n    // onStart: () => {\n    //   console.log('onStart')\n    // },\n    // onComplete: () => {\n    //   console.log('onComplete')\n    // },\n  },\n  autoToggle: true,\n  allowNestedScroll: true,\n  syncTouch: true,\n  infinite: true,\n  stopInertiaOnNavigate: true,\n  // virtualScroll: ({ event }) => {\n  //   console.log(lenis.options.syncTouch)\n\n  //   return true\n  // },\n  // duration: 1,\n  // infinite: true,\n  // lerp: 0.5,\n  // duration: 10,\n  // easing: (t) => t,\n  // syncTouch: true,\n  // lerp: 0.01,\n  // wrapper: document.body,\n  // content: document.querySelector('main'),\n  // wrapper: document.querySelector('main')!,\n  // content: document.querySelector('main')?.children[0],\n  // autoResize: false,\n  // lerp: 0.9,\n  // virtualScroll: (e) => {\n  //   // e.deltaY *= 10\n  //   return !e.event.shiftKey\n  //   // return true\n  // },\n  // duration: 2,\n  // easing: (t) => t,\n  // prevent: () => {\n  //   return true\n  // },\n  // prevent: (node) => {\n  //   return (\n  //     node.classList?.contains('lenis-scrolling') &&\n  //     node.classList?.contains('lenis-smooth') &&\n  //     !node.classList?.contains('lenis-stopped')\n  //   )\n  // },\n})\n\n// const _nestedLenis = new Lenis({\n//   wrapper: document.querySelector('#nested')!,\n//   content: document.querySelector('#nested-content')!,\n//   autoRaf: true,\n//   // overscroll: false,\n//   // orientation: 'horizontal',\n//   // gestureOrientation: 'horizontal',\n//   // infinite: true,\n// })\n\nlenis.on('scroll', (_e) => {\n  // console.log('scroll', e)\n})\n\n// document.querySelectorAll('a[href*=\"#\"]').forEach((node) => {\n//   node.addEventListener('click', (e) => {\n//     // lenis.reset()\n//     // e.preventDefault()\n//     // console.log(node.href)\n//   })\n// })\n\n// window.addEventListener('hashchange', () => {\n//   console.log('hashchange')\n// })\n\n// const nestedLenis = new Lenis({\n//   wrapper: document.querySelector('#nested')!,\n//   content: document.querySelector('#nested-content')!,\n//   autoRaf: true,\n//   // overscroll: false,\n//   // orientation: 'horizontal',\n//   // gestureOrientation: 'horizontal',\n//   // infinite: true,\n\n//   // smoothWheel: false,\n// })\n\n// window.nestedLenis = nestedLenis\n\n// console.log(lenis.dimensions.height)\nlenis.on('scroll', (_e) => {\n  // console.log(e.isScrolling)\n  // console.log(e.scroll, e.velocity)\n  // console.log(e.scroll, e.velocity, e.isScrolling, e.userData)\n})\n// lenis.on('virtual-scroll', (e) => {\n//   // console.log(e)\n//   // e.deltaY *= 10\n//   // e.cancel = true\n// })\n// window.lenis = lenis\n\ndeclare global {\n  interface Window {\n    lenis: Lenis\n  }\n}\n\n// window.addEventListener('resize', () => {\n//   lenis.resize()\n\n//   console.log(lenis.actualScroll, lenis.scroll, window.scrollY)\n// })\n\n// Proxy test for lenis\n// const proxyLenis = new Proxy(lenis, {})\n\n// const scroll100 = document.getElementById('scroll-100')\n\n// scroll100?.addEventListener('click', () => {\n//   // proxyLenis?.scrollTo(100, {\n//   //   lerp: 0.1,\n//   // })\n//   lenis.scrollTo(100, {\n//     lerp: 0.1,\n//   })\n// })\n\n// document.documentElement.addEventListener('wheel', (e) => {\n//   console.log('wheel')\n// })\n\n// function raf(time: number) {\n//   lenis.raf(time)\n//   nestedLenis.raf(time)\n//   requestAnimationFrame(raf)\n// }\n\n// requestAnimationFrame(raf)\n\ndocument.getElementById('stop')?.addEventListener('click', () => {\n  // document.documentElement.style.overflow = 'hidden'\n  lenis.stop()\n})\n\ndocument.getElementById('start')?.addEventListener('click', () => {\n  // document.documentElement.style.overflow = 'auto'\n  lenis.start()\n})\n\ndocument.getElementById('scroll-start')?.addEventListener('click', () => {\n  lenis.scrollTo(100, {\n    lock: true,\n    duration: 1,\n    onComplete: () => {\n      console.log('onComplete')\n    },\n  })\n})\n\ndocument.getElementById('scroll-center')?.addEventListener('click', () => {\n  lenis.scrollTo(lenis.limit / 2, {\n    // duration: 10,\n    // easing: (t) => t,\n  })\n})\n\ndocument.getElementById('scroll-end')?.addEventListener('click', () => {\n  lenis.scrollTo(lenis.limit - 100)\n})\n\n// const stopButton = document.getElementById('stop')\n// const startButton = document.getElementById('start')\n\n// stopButton?.addEventListener('click', () => {\n//   lenis.stop()\n//   console.log('stop')\n// })\n\n// startButton?.addEventListener('click', () => {\n//   lenis.start()\n// })\n"
  },
  {
    "path": "playground/horizontal/browser.js",
    "content": "const lenis = new Lenis()\n\nlenis.on('scroll', (e) => {\n  console.log(e)\n})\n\nfunction raf(time) {\n  lenis.raf(time)\n  requestAnimationFrame(raf)\n}\n\nrequestAnimationFrame(raf)\n"
  },
  {
    "path": "playground/horizontal/static.html",
    "content": "<html lang=\"en\">\r\n  <head>\r\n    <style>\r\n      body {\r\n        height: 300vh;\r\n      }\r\n    </style>\r\n    <script src=\"../../dist/lenis.min.js\"></script>\r\n    <script>\r\n      const lenis = new Lenis({\r\n        autoRaf: true,\r\n      })\r\n      console.log(lenis)\r\n    </script>\r\n  </head>\r\n  <body>\r\n    Lorem ipsum dolor, sit amet consectetur adipisicing elit. Inventore aliquid\r\n    aliquam quam officiis hic ut voluptatibus dicta perferendis, voluptate iure\r\n    modi iste ratione explicabo architecto impedit ipsa. Blanditiis, tenetur\r\n    itaque.\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "playground/horizontal/style.css",
    "content": "html {\n  overflow-y: clip;\n}\n\n#wrapper {\n  display: flex;\n  height: 100vh;\n  align-items: center;\n}\n\n#about,\n#work,\n#work2,\n#contact {\n  width: 80vw;\n  flex-shrink: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n}\n\n#about {\n  background-color: rgb(255, 152, 162);\n}\n\n#work {\n  background-color: rgb(213, 133, 169);\n  overflow-x: auto;\n  justify-content: flex-start;\n  height: 50%;\n  /* background: repeating-radial-gradient(circle, #000, #111 1px, #000 2px); */\n  /* filter: blur(2px) brightness(1.2) contrast(2) saturate(3);\n  mix-blend-mode: difference;\n  animation: insane-rotate 0.1s infinite linear;\n  transform: scale(1.001); */\n}\n\n#work2 {\n  background-color: rgb(255, 152, 162);\n  height: 50%;\n  overflow-y: auto;\n\n  #work2-content {\n    display: block;\n  }\n}\n\n#contact {\n  background-color: rgb(163, 120, 164);\n}\n\n#work-content {\n  min-width: 100vw;\n  height: 50%;\n}\n"
  },
  {
    "path": "playground/horizontal/test.ts",
    "content": "import Lenis from 'lenis'\nimport { LoremIpsum } from 'lorem-ipsum'\n\ndocument.querySelector('#work2-content')!.innerHTML =\n  new LoremIpsum().generateParagraphs(30)\n\nnew Lenis({\n  orientation: 'horizontal',\n  // gestureOrientation: 'vertical',\n  autoRaf: true,\n  allowNestedScroll: true,\n  syncTouch: true,\n  anchors: true,\n  stopInertiaOnNavigate: true,\n  // virtualScroll: (data) => {\n  //   data.deltaX = 0\n  //   // data.deltaY =  0.00001\n  //   if (data.deltaY === 0 && data.deltaX === 0) {\n  //     data.deltaY = 0.00001\n  //   }\n  //   console.log(data)\n  //   return true\n  // },\n})\n\n// setInterval(() => {\n//   document.querySelector('#work').style.width = `${50 + Math.random() * 30}vw`\n// }, 1000)\n"
  },
  {
    "path": "playground/infinite/browser.js",
    "content": "const lenis = new Lenis()\n\nlenis.on('scroll', (e) => {\n  console.log(e)\n})\n\nfunction raf(time) {\n  lenis.raf(time)\n  requestAnimationFrame(raf)\n}\n\nrequestAnimationFrame(raf)\n"
  },
  {
    "path": "playground/infinite/static.html",
    "content": "<html lang=\"en\">\r\n  <head>\r\n    <style>\r\n      body {\r\n        height: 300vh;\r\n      }\r\n    </style>\r\n    <script src=\"../../dist/lenis.min.js\"></script>\r\n    <script>\r\n      const lenis = new Lenis({\r\n        autoRaf: true,\r\n      })\r\n      console.log(lenis)\r\n    </script>\r\n  </head>\r\n  <body>\r\n    Lorem ipsum dolor, sit amet consectetur adipisicing elit. Inventore aliquid\r\n    aliquam quam officiis hic ut voluptatibus dicta perferendis, voluptate iure\r\n    modi iste ratione explicabo architecto impedit ipsa. Blanditiis, tenetur\r\n    itaque.\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "playground/infinite/style.css",
    "content": "html {\n  overflow-x: clip;\n}\n\nimg {\n  width: 100%;\n}\n"
  },
  {
    "path": "playground/infinite/test.ts",
    "content": "import Lenis from 'lenis'\nimport Stats from 'stats-js'\n\nnew Lenis({\n  infinite: true,\n  autoRaf: true,\n  syncTouch: true,\n})\n\nfunction isPrime(num: number) {\n  if (num < 2) return false\n  for (let i = 2; i * i <= num; i++) {\n    if (num % i === 0) return false\n  }\n  return true\n}\n\nfunction sumPrimes(limit: number) {\n  let sum = 0\n  for (let i = 2; i <= limit; i++) {\n    if (isPrime(i)) {\n      sum += i\n    }\n  }\n  return sum\n}\n\n// function raf() {\n//   // const sum = sumPrimes(Math.random() * 500000)\n//   // console.log(sum)\n//   requestAnimationFrame(raf)\n// }\n\n// raf()\n\n// setInterval(() => {\n//   document.querySelector('#work').style.width = `${50 + Math.random() * 30}vw`\n// }, 1000)\n\nvar stats = new Stats()\nstats.showPanel(0) // 0: fps, 1: ms, 2: mb, 3+: custom\ndocument.body.appendChild(stats.dom)\n\nfunction animate() {\n  stats.begin()\n\n  sumPrimes(300000)\n\n  // monitored code goes here\n\n  stats.end()\n\n  requestAnimationFrame(animate)\n}\n\nrequestAnimationFrame(animate)\n"
  },
  {
    "path": "playground/nuxt/.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# 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": "playground/nuxt/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": "playground/nuxt/app.vue",
    "content": "<script setup lang=\"ts\">\nimport gsap from 'gsap'\nimport { watch, watchEffect } from 'vue'\n\nconst lenis = useLenis((lenis) => {\n  console.log('page callback', lenis)\n})\n\nwatch(\n  lenis,\n  (lenis) => {\n    console.log('page', lenis)\n  },\n  { immediate: true }\n)\n\nconst lenisRef = useTemplateRef('lenisRef')\n\nwatchEffect((onInvalidate) => {\n  function update(time: number) {\n    lenisRef.value?.lenis?.raf(time * 1000)\n  }\n  gsap.ticker.add(update)\n\n  onInvalidate(() => {\n    gsap.ticker.remove(update)\n  })\n})\n</script>\n\n<template>\n  <nav>\n    <NuxtLink to=\"/\">Home</NuxtLink>\n    <NuxtLink to=\"/about\">About</NuxtLink>\n  </nav>\n  <VueLenis root :options=\"{ autoRaf: false }\" ref=\"lenisRef\" />\n  <NuxtPage />\n</template>\n\n<style>\n* {\n  margin: 0;\n}\n</style>\n\n<style scoped>\n#app {\n  padding-top: 24px;\n}\n\nnav {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  display: flex;\n  gap: 1rem;\n}\n\n.scroller {\n  height: 100vh;\n  overflow-y: auto;\n}\n</style>\n"
  },
  {
    "path": "playground/nuxt/components/inner.vue",
    "content": "<script setup>\n// import { useLenis } from 'lenis/vue'\n\n// const lenis = useLenis((lenis) => {\n//   console.log('inner callback', lenis)\n// })\n\n// watch(lenis, (lenis) => {\n//   console.log('inner lenis', lenis)\n// })\n</script>\n\n<template>\n  <div>Inner</div>\n</template>\n"
  },
  {
    "path": "playground/nuxt/nuxt.config.ts",
    "content": "// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: '2024-11-01',\n  devtools: { enabled: true },\n  modules: ['lenis/nuxt'],\n})\n"
  },
  {
    "path": "playground/nuxt/package.json",
    "content": "{\n  \"name\": \"playground-nuxt\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"nuxt build\",\n    \"dev\": \"nuxt dev\",\n    \"generate\": \"nuxt generate\",\n    \"preview\": \"nuxt preview\"\n  },\n  \"dependencies\": {\n    \"lenis\": \"*\",\n    \"nuxt\": \"^3.15.3\",\n    \"vue\": \"latest\",\n    \"vue-router\": \"latest\",\n    \"gsap\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "playground/nuxt/pages/about.vue",
    "content": "<script setup>\nimport { useLenis } from 'lenis/vue'\n\nconst lenis = useLenis((lenis) => {\n  console.log('page callback', lenis)\n})\n\nwatch(lenis, (lenis) => {\n  console.log('page', lenis)\n})\n</script>\n\n<template>\n  <!-- <vue-lenis root /> -->\n  <!-- <vue-lenis class=\"scroller\"> -->\n  <div class=\"content\">About <Inner /></div>\n  <!-- </vue-lenis> -->\n</template>\n\n<style scoped>\n.content {\n  /* min-height: 200vh; */\n  padding-top: 24px;\n  min-height: 200vh;\n}\n\n.scroller {\n  height: 100vh;\n  overflow-y: auto;\n}\n</style>\n"
  },
  {
    "path": "playground/nuxt/pages/index.vue",
    "content": "//\n<script setup>\n// import Inner from '../components/inner.vue'\n// import { useLenis } from 'lenis/vue'\n// import { watchEffect } from 'vue'\n\n// const lenisRef = ref()\n\n// watchEffect(() => {\n//   console.log('lenisRef', lenisRef.value?.lenis)\n// })\n//\n</script>\n\n<template>\n  <!-- <vue-lenis class=\"scroller\" ref=\"lenisRef\"> -->\n  <div class=\"content\">Home <Inner /></div>\n  <!-- </vue-lenis> -->\n</template>\n\n<style scoped>\n.content {\n  /* min-height: 200vh; */\n  padding-top: 24px;\n  min-height: 200vh;\n}\n\n.scroller {\n  /* height: 100vh;\n  overflow-y: auto; */\n  pointer-events: none;\n}\n</style>\n"
  },
  {
    "path": "playground/nuxt/plugins/lenis.ts",
    "content": "export default defineNuxtPlugin((_nuxtApp) => {\n  //   nuxtApp.vueApp.use(LenisVue)\n})\n"
  },
  {
    "path": "playground/nuxt/public/robots.txt",
    "content": "\n"
  },
  {
    "path": "playground/nuxt/server/tsconfig.json",
    "content": "{\n  \"extends\": \"../.nuxt/tsconfig.server.json\"\n}\n"
  },
  {
    "path": "playground/nuxt/tsconfig.json",
    "content": "{\n  // https://nuxt.com/docs/guide/concepts/typescript\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"type\": \"module\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"dev\": \"astro dev --host\",\n    \"start\": \"astro dev\",\n    \"build\": \"astro check && astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\"\n  },\n  \"dependencies\": {\n    \"@astrojs/check\": \"^0.9.4\",\n    \"@astrojs/react\": \"^4.1.6\",\n    \"@astrojs/vue\": \"^5.0.6\",\n    \"@types/react\": \"^19.0.7\",\n    \"@types/react-dom\": \"^19.0.3\",\n    \"astro\": \"^5.1.8\",\n    \"lenis\": \"*\",\n    \"lorem-ipsum\": \"^2.0.8\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"stats-js\": \"1.0.1\",\n    \"typescript\": \"^5.7.3\",\n    \"vue\": \"^3.5.13\"\n  }\n}\n"
  },
  {
    "path": "playground/react/app.tsx",
    "content": "import { type LenisRef, ReactLenis, useLenis } from 'lenis/react'\nimport { LoremIpsum } from 'lorem-ipsum'\nimport { useRef, useState } from 'react'\n\nfunction App() {\n  const [lorem] = useState(() => new LoremIpsum().generateParagraphs(200))\n  const [count, setCount] = useState(0)\n\n  useLenis()\n\n  const lenisRef = useRef<LenisRef>(null)\n\n  return (\n    <ReactLenis\n      className={`wrapper a-${count}`}\n      root=\"asChild\"\n      ref={lenisRef}\n      options={{ autoToggle: true }}\n    >\n      <div className=\"debug-panel\">\n        <button type=\"button\" onClick={() => setCount((c) => c + 1)}>\n          Re-render ({count})\n        </button>\n        <p>\n          <strong>DOM className:</strong>\n        </p>\n        <code id=\"class-display\" />\n        <p className=\"hint\">\n          Scroll, then click the button. Lenis classes should persist.\n        </p>\n      </div>\n      {lorem}\n    </ReactLenis>\n  )\n}\n\n// Poll DOM className outside React to avoid extra renders\nsetInterval(() => {\n  const wrapper = document.querySelector('.lenis')\n  const display = document.getElementById('class-display')\n  if (wrapper && display) {\n    display.textContent = wrapper.className\n  }\n}, 100)\n\nexport default App\n"
  },
  {
    "path": "playground/react/style.css",
    "content": "html:not(.lenis) {\n  body,\n  & {\n    margin: 0;\n    padding: 0;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n  }\n  body {\n    position: fixed;\n    overscroll-behavior-y: none;\n    overscroll-behavior-x: none;\n  }\n\n  .lenis {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n    overflow-y: scroll;\n    -ms-scroll-chaining: none;\n    overscroll-behavior: contain;\n    background: #0b41cd;\n  }\n}\n\n.wrapper.lenis-scrolling {\n  background: #1a6b2a;\n}\n\n.debug-panel {\n  position: fixed;\n  bottom: 12px;\n  right: 12px;\n  z-index: 100;\n  background: rgba(0, 0, 0, 0.85);\n  color: #fff;\n  padding: 16px;\n  border-radius: 8px;\n  font-family: monospace;\n  font-size: 13px;\n  max-width: 400px;\n}\n\n.debug-panel button {\n  padding: 8px 16px;\n  font-size: 14px;\n  cursor: pointer;\n  font-family: monospace;\n  border: 1px solid #555;\n  background: #222;\n  color: #fff;\n  border-radius: 4px;\n}\n\n.debug-panel button:hover {\n  background: #444;\n}\n\n.debug-panel code {\n  display: block;\n  padding: 6px 8px;\n  background: #111;\n  border-radius: 4px;\n  word-break: break-all;\n  color: #4fc3f7;\n}\n\n.debug-panel p {\n  margin: 8px 0 4px;\n}\n\n.debug-panel .hint {\n  color: #999;\n  font-size: 11px;\n  margin-top: 10px;\n}\n"
  },
  {
    "path": "playground/snap/style.css",
    "content": "/* html {\n  scroll-snap-type: y mandatory;\n  scroll-behavior: smooth;\n}\n\n.section {\n  scroll-snap-align: start;\n} */\n\n/* palette from https://twitter.com/tranmautritam/status/1783420779709026785 */\n\n.section {\n  padding: 24px;\n\n  .inner {\n    height: 100%;\n    width: 100%;\n    border-radius: 16px;\n  }\n}\n\n.section:not(:last-child) {\n  margin-bottom: -24px;\n}\n\n.section-1 {\n  height: 150vh;\n\n  .inner {\n    background-color: #255bec;\n  }\n}\n\n.section-2 {\n  height: 50vh;\n\n  .inner {\n    background-color: #f6c74d;\n  }\n}\n\n.section-3 {\n  height: 250vh;\n\n  .inner {\n    background-color: #b49df8;\n  }\n}\n\n.section-4 {\n  height: 80vh;\n\n  .inner {\n    background-color: #ea6e38;\n  }\n}\n\n.section-5 {\n  height: 50vh;\n\n  .inner {\n    background-color: #255bec;\n  }\n}\n\n.section-6 {\n  height: 100vh;\n\n  .inner {\n    background-color: #f6c74d;\n  }\n}\n\n.inner {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n/* #wrapper {\n  height: 80vh;\n  overflow-y: auto;\n  position: relative;\n  top: 10vh;\n} */\n"
  },
  {
    "path": "playground/snap/test.ts",
    "content": "// import { LoremIpsum } from 'lorem-ipsum'\nimport Lenis from 'lenis'\nimport Snap from 'lenis/snap'\n\n// import Snap from '../src/index.ts'\n\n// document.querySelector('#app').innerHTML = new LoremIpsum().generateParagraphs(\n//   200\n// )\n\nconst lenis = new Lenis({\n  // wrapper: document.querySelector('#wrapper'),\n  // content: document.querySelector('#content'),\n  lerp: 0.1,\n  syncTouch: true,\n})\n\nconst _i = 0\n\nconst snap = new Snap(lenis, {\n  type: 'lock', // 'mandatory', 'proximity', 'lock'\n  // velocityThreshold: 1.2,\n  duration: 1,\n  distanceThreshold: '50%',\n  debounce: 500,\n  // duration: 2,\n  // easing: (t) => t,\n  // onSnapStart: (snap) => {\n  //   console.log('onSnapStart', snap)\n  // },\n  // onSnapComplete: (snap) => {\n  //   console.log('onSnapComplete', snap)\n  // },\n})\ndeclare global {\n  interface Window {\n    snap: Snap\n  }\n}\n\nwindow.snap = snap\n\nconst _section1 = document.querySelector<HTMLDivElement>('.section-1')!\nconst section2 = document.querySelector<HTMLDivElement>('.section-2')!\nconst section3 = document.querySelector<HTMLDivElement>('.section-3')!\nconst section4 = document.querySelector<HTMLDivElement>('.section-4')!\nconst section5 = document.querySelector<HTMLDivElement>('.section-5')!\nconst _section6 = document.querySelector<HTMLDivElement>('.section-6')!\n\n// snap.add(0, {\n//   index: 0,\n// })\n\n// snap.add(643, {\n//   index: 1,\n// })\n\n// snap.addElement(section1, {\n//   align: ['start', 'end'],\n// })\n\nconst _unsub1 = snap.addElement(section2, {\n  align: 'center',\n})\n\n// console.log('unsub1', unsub1)\n// unsub1()\n\nsnap.addElement(section3, {\n  align: ['start', 'end'],\n})\n\n// snap.addElement(section4, {\n//   align: ['center'],\n// })\n\n// snap.addElement(section5, {\n//   align: ['center'],\n// })\n\nconst _unsubs = snap.addElements([section4, section5], {\n  align: ['center'],\n})\n\n// console.log('unsubs', unsubs)\n// unsubs()\n\n// snap.addElement(section6, {\n//   align: ['end'],\n// })\n\n// snap.addElement(section4, {\n//   align: ['start', 'end'], // 'start', 'center', 'end'\n// })\n\nfunction raf(time: number) {\n  lenis.raf(time)\n  requestAnimationFrame(raf)\n}\n\nrequestAnimationFrame(raf)\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "playground/vue/App.vue",
    "content": "<script setup>\nimport { useLenis } from 'lenis/vue'\nimport { LoremIpsum } from 'lorem-ipsum'\nimport { ref, watch } from 'vue'\n\nconst _lorem = new LoremIpsum().generateParagraphs(200)\n\nconst _lerp = ref(0.1)\nconst _autoRaf = ref(true)\n\nconst lenis = useLenis(\n  (lenis) => {\n    console.log('root scroll', lenis.options.lerp, lenis.scroll)\n  },\n  0,\n  'root'\n)\n\nconst lenisRef = ref()\n\nwatch(lenis, (lenis) => {\n  console.log('lenis in callback', lenis)\n})\n\nwatch(lenisRef, (lenisRef) => {\n  console.log('lenis in ref', lenisRef.lenis)\n})\n</script>\n\n<template>\n  <vue-lenis ref=\"lenisRef\" root :options=\"{ lerp, autoRaf }\">\n    <Child />\n    <button @click=\"lerp += 0.1\">more lerp</button>\n    <button @click=\"lerp -= 0.1\">less lerp</button>\n    <button @click=\"lenis.scrollTo(200)\">scroll to 200</button>\n    <button @click=\"lenisRef.lenis.scrollTo(200)\">ref scroll to 200</button>\n    <vue-lenis\n      :options=\"{ lerp: 0.2, autoRaf }\"\n      style=\"height: 50svh; overflow: scroll\"\n      class=\"inner\"\n    >\n      <InnerChild />\n    </vue-lenis>\n    <p>\n      {{ lorem }}\n    </p>\n  </vue-lenis>\n</template>\n\n<style scoped>\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: filter 300ms;\n}\n.logo:hover {\n  filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.vue:hover {\n  filter: drop-shadow(0 0 2em #42b883aa);\n}\n</style>\n"
  },
  {
    "path": "playground/vue/Child.vue",
    "content": "<script setup>\nimport { useLenis } from 'lenis/vue'\nimport { watch } from 'vue'\n\nconst lenis = useLenis(\n  (lenis) => {\n    // TODO: This only works after hot reloading so lenis is not defined on mount here\n    console.log('child scroll', lenis.options.lerp, lenis.scroll)\n  },\n  0,\n  'child'\n)\n\nwatch(lenis, (lenis) => {\n  console.log('Child Lenis lerp:', lenis.options.lerp)\n})\n</script>\n\n<template>\n  <button type=\"button\" @click=\"lenis?.scrollTo(100)\">scroll</button>\n</template>\n"
  },
  {
    "path": "playground/vue/InnerChild.vue",
    "content": "<script setup>\nimport { useLenis } from 'lenis/vue'\nimport { LoremIpsum } from 'lorem-ipsum'\n\nuseLenis((lenis) => {\n  console.log('innerchild scroll', lenis.options.lerp, lenis.scroll)\n})\n\nconst _lorem = new LoremIpsum().generateParagraphs(100)\n</script>\n\n<template>\n  <p>\n    {{ lorem }}\n  </p>\n</template>\n"
  },
  {
    "path": "playground/vue/setup.ts",
    "content": "import LenisVue from 'lenis/vue'\nimport type { App } from 'vue'\n\nexport default (app: App) => {\n  app.use(LenisVue)\n}\n"
  },
  {
    "path": "playground/vue/style.css",
    "content": ""
  },
  {
    "path": "playground/www/layouts/Layout.astro",
    "content": "---\r\nimport 'lenis/dist/lenis.css'\ninterface Props {\n  title: string\n}\n\nconst { title } = Astro.props\n---\r\n\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"utf-8\" />\r\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\r\n    <meta name=\"viewport\" content=\"width=device-width\" />\r\n    <meta name=\"generator\" content={Astro.generator} />\r\n    <title>{title}</title>\r\n  </head>\r\n  <body>\r\n    <nav>\r\n      <a href=\"/\">\r\n        <svg\r\n          width=\"15\"\r\n          height=\"16\"\r\n          viewBox=\"0 0 15 16\"\r\n          fill=\"none\"\r\n          xmlns=\"http://www.w3.org/2000/svg\"\r\n        >\r\n          <path\r\n            d=\"M8.6273 0C13.4362 0 15 2.35534 15 6.37565C15 11.4112 12.2502 16 6.58123 16L0.221572 16L0 15.7699L2.64556 0.216595L2.86707 0L8.6273 0Z\"\r\n            fill=\"#E30613\"></path>\r\n        </svg>\r\n      </a>\r\n      <a href=\"/core\">Core</a>\r\n      <a href=\"/snap\">Snap</a>\r\n      <a href=\"/react\">React</a>\r\n      <a href=\"/vue\">Vue</a>\r\n      <a href=\"/horizontal\">Horizontal</a>\r\n      <a href=\"/infinite\">Infinite</a>\r\n    </nav>\r\n    <slot />\r\n  </body>\r\n</html>\r\n\r\n<style is:global>\r\n  :root {\r\n    font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\r\n    line-height: 1.5;\r\n    font-weight: 400;\r\n\r\n    font-synthesis: none;\r\n    text-rendering: optimizeLegibility;\r\n    -webkit-font-smoothing: antialiased;\r\n    -moz-osx-font-smoothing: grayscale;\r\n  }\r\n\r\n  * {\r\n    box-sizing: border-box;\r\n  }\r\n\r\n  /* html {\r\n\toverflow-y: scroll;\r\n} */\r\n\r\n  body {\r\n    margin: 0;\r\n    /* min-width: 320px; */\r\n    /* min-height: 100vh; */\r\n  }\r\n</style>\r\n\r\n<style>\r\n  nav {\r\n    position: fixed;\r\n    top: 2px;\r\n    left: 2px;\r\n    align-items: center;\r\n    border-radius: 0.5rem;\r\n    height: 3rem;\r\n    display: flex;\r\n    gap: 2px;\r\n    z-index: 1;\r\n  }\r\n\r\n  a {\r\n    min-width: 60px;\r\n    height: 100%;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n    font-family: monospace;\r\n    text-transform: uppercase;\r\n    color: white;\r\n    background-color: #0e0e0e;\r\n    text-decoration: none;\r\n    border-radius: 4px;\r\n    transition: all 0.2s ease-in-out;\r\n    padding: 0 1rem;\r\n  }\r\n\r\n  a:hover {\r\n    color: #e30613;\r\n    border-radius: 12px;\r\n  }\r\n\r\n  body {\r\n    /* padding-top: 4rem; */\r\n  }\r\n</style>\r\n"
  },
  {
    "path": "playground/www/pages/core.astro",
    "content": "---\nimport '~/core/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n\n<Layout title=\"Lenis\">\n  <div id=\"debug\">\n    <button id=\"scroll-start\">Scroll start</button>\n    <button id=\"scroll-center\">Scroll center</button>\n    <button id=\"scroll-end\">Scroll end</button>\n    <button id=\"stop\">Stop</button>\n    <button id=\"start\">Start</button>\n  </div>\n  <div id=\"app\">\n    <div id=\"about\">\n      <h2>about</h2>\n      <p>\n        Lorem ipsum, dolor sit amet consectetur adipisicing elit. Dolorum, amet\n        illum repellendus vitae nulla provident eveniet totam laborum neque odio\n        veritatis accusantium, hic quam et qui ipsum eos excepturi molestiae?\n      </p>\n    </div>\n    <div id=\"work\">\n      <h2>work</h2>\n      <p>\n        Lorem ipsum, dolor sit amet consectetur adipisicing elit. Dolorum, amet\n        illum repellendus vitae nulla provident eveniet totam laborum neque odio\n        veritatis accusantium, hic quam et qui ipsum eos excepturi molestiae?\n      </p>\n    </div>\n\n    <div id=\"contact\">\n      <h2>contact</h2>\n      <p>\n        Lorem ipsum, dolor sit amet consectetur adipisicing elit. Dolorum, amet\n        illum repellendus vitae nulla provident eveniet totam laborum neque odio\n        veritatis accusantium, hic quam et qui ipsum eos excepturi molestiae?\n      </p>\n    </div>\n    <div>\n      <a href=\"#\"> <span>#</span></a>\n      <a href=\"#top\"> <span>top</span></a>\n      <a href=\"#about\"> <span>about</span></a>\n      <a href=\"#work\"> <span>work</span></a>\n      <a href=\"#contact\"> <span>contact</span></a>\n      <a href=\"/react#a\"> <span>void</span></a>\n      <a\n        href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement\"\n        target=\"_blank\"\n      >\n        <span>mdn</span></a\n      >\n    </div>\n    <div id=\"nested-horizontal\">\n      <div id=\"nested-horizontal-content\"></div>\n    </div>\n    <div id=\"nested\">\n      <div id=\"nested-content\"></div>\n    </div>\n  </div>\n  <script src=\"../../core/test.ts\"></script>\n  <!-- THESE ARE HERE TO TEST THE BROWSER VERSION BUNDLE -->\n  <!-- <script is:raw src=\"../../node_modules/lenis/dist/lenis.min.js\"></script> -->\n  <!-- <script is:raw src=\"../../core/browser.js\"></script> -->\n  <!-- <script>\n\t\timport { LoremIpsum } from 'lorem-ipsum'\n\t\tdocument.querySelector('#app').innerHTML = new LoremIpsum().generateParagraphs(30)\n\t</script> -->\n</Layout>\n"
  },
  {
    "path": "playground/www/pages/horizontal.astro",
    "content": "---\nimport '~/horizontal/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n\n<Layout title=\"Lenis\">\n  <div id=\"wrapper\">\n    <a href=\"twitter.com\" id=\"about\">\n      <h2>about</h2>\n    </a>\n    <div id=\"work\">\n      <div id=\"work-content\">\n        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore unde\n        hic atque qui nobis tempore quaerat illum laborum, reiciendis maxime\n        natus vero dignissimos. Magnam accusantium sint nulla velit neque magni?\n        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore unde\n        hic atque qui nobis tempore quaerat illum laborum, reiciendis maxime\n        natus vero dignissimos. Magnam accusantium sint nulla velit neque magni?\n      </div>\n    </div>\n\n    <div id=\"work2\">\n      <div id=\"work2-content\">\n        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore unde\n        hic atque qui nobis tempore quaerat illum laborum, reiciendis maxime\n        natus vero dignissimos. Magnam accusantium sint nulla velit neque magni?\n        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore unde\n        hic atque qui nobis tempore quaerat illum laborum, reiciendis maxime\n        natus vero dignissimos. Magnam accusantium sint nulla velit neque magni?\n      </div>\n    </div>\n\n    <div id=\"contact\">\n      <h2>contact</h2>\n    </div>\n  </div>\n  <script src=\"../../horizontal/test.ts\"></script>\n  <!-- THESE ARE HERE TO TEST THE BROWSER VERSION BUNDLE -->\n  <!-- <script is:raw src=\"../../node_modules/lenis/dist/lenis.min.js\"></script> -->\n  <!-- <script is:raw src=\"../../core/browser.js\"></script> -->\n  <!-- <script>\n\t\timport { LoremIpsum } from 'lorem-ipsum'\n\t\tdocument.querySelector('#app').innerHTML = new LoremIpsum().generateParagraphs(30)\n\t</script> -->\n</Layout>\n"
  },
  {
    "path": "playground/www/pages/index.astro",
    "content": "---\nimport Layout from '../layouts/Layout.astro'\n---\n\n<Layout title=\"Lenis Playground\">\n  <main>\n    <h1>Welcome to the Lenis Playground!</h1>\n    <p>\n      <br />\n      This is a playground for the Lenis library. It's a great way to test and experiment\n      with the library without having to set up a project.\n      <br />\n      <br />\n      If you see this, it means you are trying to contribute to the project and we\n      appreciate that!\n      <br />\n      Playground and build pipeline for all packages run at the same time, so you\n      can change the code and see the changes in real-time in the playground.\n    </p>\n  </main>\n</Layout>\n\n<style>\n  main {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n    height: 100svh;\n    width: 50vw;\n    margin: 0 auto;\n  }\n\n  h1,\n  p {\n    margin: 0;\n    font-family: monospace;\n    text-align: center;\n  }\n\n  h1 {\n    text-transform: uppercase;\n  }\n</style>\n"
  },
  {
    "path": "playground/www/pages/infinite.astro",
    "content": "---\nimport '~/infinite/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n\n<Layout title=\"Lenis\">\n  <div id=\"wrapper\">\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1746637010097-5e79e6283d99?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718105-bf9beb57ba3a?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718070-df9e38a96a53?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747851402163-9183f38a658c?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747751013539-b1d2a2fdeb7a?q=80&w=2016&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747862240252-50191a60c21d?q=80&w=1965&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1746637010097-5e79e6283d99?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718105-bf9beb57ba3a?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718070-df9e38a96a53?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747851402163-9183f38a658c?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747751013539-b1d2a2fdeb7a?q=80&w=2016&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747862240252-50191a60c21d?q=80&w=1965&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1746637010097-5e79e6283d99?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718105-bf9beb57ba3a?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747901718070-df9e38a96a53?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747851402163-9183f38a658c?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://plus.unsplash.com/premium_photo-1747751013539-b1d2a2fdeb7a?q=80&w=2016&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n    <img\n      src=\"https://images.unsplash.com/photo-1747862240252-50191a60c21d?q=80&w=1965&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n    />\n  </div>\n  <script src=\"../../infinite/test.ts\"></script>\n  <!-- THESE ARE HERE TO TEST THE BROWSER VERSION BUNDLE -->\n  <!-- <script is:raw src=\"../../node_modules/lenis/dist/lenis.min.js\"></script> -->\n  <!-- <script is:raw src=\"../../core/browser.js\"></script> -->\n  <!-- <script>\n\t\timport { LoremIpsum } from 'lorem-ipsum'\n\t\tdocument.querySelector('#app').innerHTML = new LoremIpsum().generateParagraphs(30)\n\t</script> -->\n</Layout>\n"
  },
  {
    "path": "playground/www/pages/react.astro",
    "content": "---\nimport App from '~/react/app'\nimport '~/react/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n<Layout title=\"Lenis + React\">\n\t<App client:only=\"react\" />\n</Layout>\n"
  },
  {
    "path": "playground/www/pages/snap.astro",
    "content": "---\nimport '~/snap/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n\n<Layout title=\"Lenis Snap\">\n  <div id=\"wrapper\">\n    <div id=\"content\">\n      <section class=\"section section-1\">\n        <div class=\"inner\">0</div>\n      </section>\n      <section class=\"section section-2\">\n        <div class=\"inner\">1</div>\n      </section>\n      <section class=\"section section-3\">\n        <div class=\"inner\">2</div>\n      </section>\n      <section class=\"section section-4\">\n        <div class=\"inner\">3</div>\n      </section>\n      <section class=\"section section-5\">\n        <div class=\"inner\">4</div>\n      </section>\n      <section class=\"section section-6\">\n        <div class=\"inner\">5</div>\n      </section>\n    </div>\n  </div>\n  <script src=\"../../snap/test.ts\"></script>\n</Layout>\n"
  },
  {
    "path": "playground/www/pages/vue.astro",
    "content": "---\nimport App from '~/vue/App.vue'\nimport '~/vue/style.css'\nimport Layout from '../layouts/Layout.astro'\n---\n<Layout title=\"Lenis + Vue\">\n\t<App client:only=\"vue\" />\n</Layout>\n"
  },
  {
    "path": "scripts/update-readme.js",
    "content": "import fs from 'node:fs'\nimport packageJson from '../package.json' with { type: 'json' }\n\nconst readmePath = './README.md'\n\nfunction updateVersion() {\n  return new Promise((resolve, reject) => {\n    // update version in README\n    fs.readFile(readmePath, 'utf8', (err, data) => {\n      if (err) {\n        console.log(`Error reading README file: ${err}`)\n        return reject(err)\n      }\n\n      const updatedReadme = data.replace(\n        /\\/lenis@([^/]+)\\//g,\n        `/lenis@${packageJson.version}/`\n      )\n\n      fs.writeFile(readmePath, updatedReadme, 'utf8', (err) => {\n        resolve()\n\n        if (err) {\n          return reject(err)\n        }\n      })\n    })\n  })\n}\n\nif (!packageJson.version.includes('-dev')) {\n  updateVersion()\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"exclude\": [\"dist\", \"playground\", \"website\"],\n  \"compilerOptions\": {\n    /* Base Options: */\n    \"skipLibCheck\": true,\n    \"target\": \"es2022\",\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleResolution\": \"Bundler\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"rootDir\": \".\",\n    \"jsx\": \"react-jsx\",\n\n    /* Strictness */\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n\n    /* If transpiling with TypeScript: */\n    \"module\": \"ESNext\",\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n\n    /* AND if you're building for a library: */\n    \"declaration\": true\n  }\n}\n"
  },
  {
    "path": "tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown'\n\nconst shared = {\n  outDir: 'dist',\n  target: 'es2022' as const,\n  platform: 'browser' as const,\n  format: 'esm' as const,\n  sourcemap: true,\n  outExtensions: () => ({ js: '.mjs', dts: '.d.ts' }),\n}\n\nconst iife = (globalName: string, minify = false) =>\n  ({\n    ...shared,\n    format: 'iife' as const,\n    dts: false,\n    clean: false,\n    globalName,\n    minify,\n    outExtensions: undefined,\n    outputOptions: {\n      entryFileNames: minify ? '[name].min.js' : '[name].js',\n    },\n  }) as const\n\nexport default defineConfig([\n  // Core + Snap ESM\n  {\n    ...shared,\n    entry: {\n      lenis: 'packages/core/index.ts',\n      'lenis-snap': 'packages/snap/index.ts',\n    },\n    dts: true,\n    clean: true,\n    copy: [{ from: 'packages/core/lenis.css', to: 'dist', flatten: true }],\n    deps: { neverBundle: ['lenis'] },\n  },\n\n  // React ESM\n  {\n    ...shared,\n    entry: { 'lenis-react': 'packages/react/index.ts' },\n    dts: { resolver: 'tsc' },\n    clean: false,\n    banner: '\"use client\";',\n    deps: { neverBundle: ['react', 'lenis'] },\n  },\n\n  // Vue ESM\n  {\n    ...shared,\n    entry: { 'lenis-vue': 'packages/vue/index.ts' },\n    dts: { resolver: 'tsc' },\n    clean: false,\n    deps: { neverBundle: ['vue', 'lenis'] },\n  },\n\n  // Nuxt ESM\n  {\n    ...shared,\n    entry: {\n      'lenis-vue-nuxt': 'packages/vue/nuxt/module.ts',\n      'nuxt/runtime/lenis': 'packages/vue/nuxt/runtime/lenis.ts',\n    },\n    dts: false,\n    sourcemap: false,\n    clean: false,\n    deps: { neverBundle: ['lenis', 'lenis/vue', '#imports', '#app', '@nuxt/kit'] },\n  },\n\n  // Browser IIFE builds\n  { entry: { lenis: 'packages/core/browser.ts' }, ...iife('Lenis') },\n  { entry: { lenis: 'packages/core/browser.ts' }, ...iife('Lenis', true) },\n  { entry: { 'lenis-snap': 'packages/snap/browser.ts' }, ...iife('Snap') },\n  { entry: { 'lenis-snap': 'packages/snap/browser.ts' }, ...iife('Snap', true) },\n])\n"
  }
]