Repository: locomotivemtl/locomotive-scroll Branch: main Commit: a8d86f2fbdcd Files: 231 Total size: 986.1 KB Directory structure: gitextract_2ed8twsc/ ├── .editorconfig ├── .eslintrc.json ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .nvmrc ├── .prettierrc.json ├── LICENSE ├── README.md ├── context7.json ├── package.json ├── packages/ │ ├── demo/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .nvmrc │ │ ├── .prettierignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ ├── launch.json │ │ │ ├── settings.json │ │ │ └── tailwind.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── astro.config.ts │ │ ├── package.json │ │ ├── public/ │ │ │ └── fonts/ │ │ │ └── .gitkeep │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ └── ScrollToggler/ │ │ │ │ └── ScrollToggler.astro │ │ │ ├── env.d.ts │ │ │ ├── layouts/ │ │ │ │ └── Layout.astro │ │ │ ├── pages/ │ │ │ │ ├── horizontal.astro │ │ │ │ └── index.astro │ │ │ ├── scripts/ │ │ │ │ ├── app.ts │ │ │ │ ├── classes/ │ │ │ │ │ └── Scroll.ts │ │ │ │ └── utils/ │ │ │ │ ├── maths.ts │ │ │ │ ├── setViewportSize.ts │ │ │ │ └── string.ts │ │ │ ├── stores/ │ │ │ │ ├── screen.ts │ │ │ │ └── scroll.ts │ │ │ └── styles/ │ │ │ ├── main.scss │ │ │ └── tools/ │ │ │ ├── functions.scss │ │ │ └── maths.scss │ │ ├── tailwind.config.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ ├── global.d.ts │ │ └── swup.d.ts │ ├── docs/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── docs/ │ │ │ ├── documentation/ │ │ │ │ ├── attributes.md │ │ │ │ ├── methods.md │ │ │ │ └── options.md │ │ │ ├── examples.md │ │ │ ├── extras/ │ │ │ │ ├── limitations.md │ │ │ │ ├── migration-guide.md │ │ │ │ └── showcase.md │ │ │ ├── getting-started/ │ │ │ │ ├── installation.md │ │ │ │ └── usage.md │ │ │ └── intro.md │ │ ├── docusaurus.config.js │ │ ├── package.json │ │ ├── sidebars.js │ │ ├── src/ │ │ │ └── css/ │ │ │ └── custom.css │ │ └── static/ │ │ └── .nojekyll │ ├── landing/ │ │ ├── .browserslistrc │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .npmrc │ │ ├── .nvmrc │ │ ├── LICENSE │ │ ├── README.md │ │ ├── assets/ │ │ │ ├── images/ │ │ │ │ ├── .gitkeep │ │ │ │ └── sprite/ │ │ │ │ └── .gitkeep │ │ │ ├── scripts/ │ │ │ │ ├── app.js │ │ │ │ ├── config.js │ │ │ │ ├── globals.js │ │ │ │ ├── modules/ │ │ │ │ │ ├── Example.js │ │ │ │ │ ├── FadeInText.js │ │ │ │ │ ├── HoverShuffle.js │ │ │ │ │ ├── Load.js │ │ │ │ │ ├── MaskLines.js │ │ │ │ │ ├── Rail.js │ │ │ │ │ ├── Randomize.js │ │ │ │ │ └── Scroll.js │ │ │ │ ├── modules.js │ │ │ │ ├── utils/ │ │ │ │ │ ├── dom.js │ │ │ │ │ ├── fonts.js │ │ │ │ │ ├── grid-helper.js │ │ │ │ │ ├── html.js │ │ │ │ │ ├── image.js │ │ │ │ │ ├── is.js │ │ │ │ │ ├── maths.js │ │ │ │ │ ├── tickers.js │ │ │ │ │ ├── transform.js │ │ │ │ │ └── visibility.js │ │ │ │ └── vendors/ │ │ │ │ └── .gitkeep │ │ │ └── styles/ │ │ │ ├── _core.scss │ │ │ ├── components/ │ │ │ │ ├── _button.scss │ │ │ │ ├── _cascade.scss │ │ │ │ ├── _fadeInText.scss │ │ │ │ ├── _features-grid.scss │ │ │ │ ├── _footer.scss │ │ │ │ ├── _form.scss │ │ │ │ ├── _header.scss │ │ │ │ ├── _heading.scss │ │ │ │ ├── _hero.scss │ │ │ │ ├── _list.scss │ │ │ │ ├── _perks-list.scss │ │ │ │ ├── _preloader.scss │ │ │ │ ├── _rail.scss │ │ │ │ ├── _scrollbar.scss │ │ │ │ ├── _section-heading.scss │ │ │ │ ├── _sticky-heading.scss │ │ │ │ ├── _text.scss │ │ │ │ └── _tool.scss │ │ │ ├── critical.scss │ │ │ ├── elements/ │ │ │ │ └── _document.scss │ │ │ ├── generic/ │ │ │ │ ├── _button.scss │ │ │ │ ├── _form.scss │ │ │ │ ├── _generic.scss │ │ │ │ └── _media.scss │ │ │ ├── main.scss │ │ │ ├── objects/ │ │ │ │ ├── _container.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _icons.scss │ │ │ │ ├── _layout.scss │ │ │ │ ├── _ratio.scss │ │ │ │ └── _table.scss │ │ │ ├── settings/ │ │ │ │ ├── _config.breakpoints.scss │ │ │ │ ├── _config.colors.scss │ │ │ │ ├── _config.eases.scss │ │ │ │ ├── _config.fonts.scss │ │ │ │ ├── _config.scss │ │ │ │ ├── _config.spacers.scss │ │ │ │ ├── _config.timings.scss │ │ │ │ ├── _config.variables.scss │ │ │ │ └── _config.zindexes.scss │ │ │ ├── tools/ │ │ │ │ ├── _family.scss │ │ │ │ ├── _functions.scss │ │ │ │ ├── _layout.scss │ │ │ │ ├── _maths.scss │ │ │ │ ├── _mixins.scss │ │ │ │ └── _widths.scss │ │ │ ├── utilities/ │ │ │ │ ├── _align.scss │ │ │ │ ├── _grid-column.scss │ │ │ │ ├── _helpers.scss │ │ │ │ ├── _print.scss │ │ │ │ ├── _ratio.scss │ │ │ │ ├── _spacing.scss │ │ │ │ ├── _states.scss │ │ │ │ ├── _theme.scss │ │ │ │ └── _widths.scss │ │ │ └── vendors/ │ │ │ └── .gitkeep │ │ ├── assets.json │ │ ├── build/ │ │ │ ├── build.js │ │ │ ├── helpers/ │ │ │ │ ├── config.js │ │ │ │ ├── glob.js │ │ │ │ ├── message.js │ │ │ │ ├── notification.js │ │ │ │ ├── postcss.js │ │ │ │ └── template.js │ │ │ ├── migrate_imports.js │ │ │ ├── tasks/ │ │ │ │ ├── concats.js │ │ │ │ ├── eleventy.js │ │ │ │ ├── scripts.js │ │ │ │ ├── styles.js │ │ │ │ ├── svgs.js │ │ │ │ └── versions.js │ │ │ ├── utils/ │ │ │ │ └── index.js │ │ │ └── watch.js │ │ ├── data/ │ │ │ ├── features.json │ │ │ ├── general.json │ │ │ ├── metadata.json │ │ │ ├── perks.json │ │ │ ├── showcase.json │ │ │ └── tools.json │ │ ├── docs/ │ │ │ ├── development.md │ │ │ ├── grid.md │ │ │ └── technologies.md │ │ ├── eleventy.config.cjs │ │ ├── loconfig.example.json │ │ ├── loconfig.json │ │ ├── package.json │ │ └── views/ │ │ ├── layouts/ │ │ │ ├── base.twig │ │ │ ├── features.twig │ │ │ ├── hero.twig │ │ │ ├── perks.twig │ │ │ ├── showcase.twig │ │ │ └── tools.twig │ │ ├── partials/ │ │ │ ├── footer.twig │ │ │ ├── header.twig │ │ │ ├── list.twig │ │ │ ├── rail.twig │ │ │ └── section-heading.twig │ │ ├── snippets/ │ │ │ ├── button.twig │ │ │ └── icon.twig │ │ └── templates/ │ │ └── index.twig │ └── lib/ │ ├── README.md │ ├── bundled/ │ │ ├── locomotive-scroll.css │ │ └── locomotive-scroll.js │ ├── core/ │ │ ├── Core.ts │ │ ├── IO.ts │ │ └── ScrollElement.ts │ ├── dist/ │ │ ├── locomotive-scroll.cjs │ │ ├── locomotive-scroll.css │ │ ├── locomotive-scroll.mjs │ │ ├── locomotive-scroll.modern.mjs │ │ ├── locomotive-scroll.umd.js │ │ └── types/ │ │ ├── core/ │ │ │ ├── Core.d.ts │ │ │ ├── IO.d.ts │ │ │ └── ScrollElement.d.ts │ │ ├── index.d.ts │ │ ├── types.d.ts │ │ └── utils/ │ │ └── maths.d.ts │ ├── index.ts │ ├── package.json │ ├── styles/ │ │ ├── locomotive-scroll.css │ │ └── main.css │ ├── tsconfig.json │ ├── types.ts │ └── utils/ │ └── maths.ts ├── postcss.config.cjs ├── scripts/ │ └── ignore-build-step.js ├── turbo.json ├── vercel.json └── www/ └── landing/ ├── assets/ │ ├── images/ │ │ └── favicons/ │ │ └── browserconfig.xml │ ├── scripts/ │ │ ├── app.js │ │ └── vendors.js │ ├── site.webmanifest │ └── styles/ │ ├── critical.css │ └── main.css ├── assets.json └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_size = 2 indent_style = space trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{js,json,d.ts,ts}] indent_size = 4 [Makefile] indent_style = tab ================================================ FILE: .eslintrc.json ================================================ { "root": true, "parserOptions": { "sourceType": "module" }, "env": { "browser": true, "es2020": true, "node": true }, "extends": [ "eslint:recommended" ], "overrides": [ { "files": [ "*.ts" ], "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint" ], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended" ] } ] } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- Hello 👋 **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] Thank you 👊 ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .gitignore ================================================ node_modules .DS_Store Thumbs.db # Turborepo .turbo # Ignore nested folders: https://stackoverflow.com/questions/3203228/git-ignore-exception/72380673#72380673 /tmp/* !/tmp/cjs /tmp/cjs/* !/tmp/dts /tmp/dts/* !/tmp/esm /tmp/esm/* !tmp/**/package.json !tmp/**/tsconfig.json # generated types .astro .docusaurus # logs npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # environment variables .env .env.production # macOS-specific files .DS_Store # jetbrains setting folder .idea # packages build output !/www/demo /www/demo/* !/www/docs /www/docs/* ================================================ FILE: .nvmrc ================================================ v20.14 ================================================ FILE: .prettierrc.json ================================================ { "printWidth": 80, "tabWidth": 4, "trailingComma": "es5", "semi": true, "singleQuote": true } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2026 Locomotive Inc. Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: README.md ================================================ # Locomotive Scroll [![npm version](https://img.shields.io/npm/v/locomotive-scroll.svg)](https://www.npmjs.com/package/locomotive-scroll) [![npm downloads](https://img.shields.io/npm/dm/locomotive-scroll.svg)](https://www.npmjs.com/package/locomotive-scroll) [![bundle size](https://img.shields.io/bundlephobia/minzip/locomotive-scroll)](https://bundlephobia.com/package/locomotive-scroll) A **lightweight** & **modern** scroll library for detection, animation, and smooth scrolling. Built on top of [Lenis](https://github.com/darkroomengineering/lenis). ## Documentation Full documentation available at [scroll.locomotive.ca/docs](https://scroll.locomotive.ca/docs). ## Quick Start ```bash npm install locomotive-scroll ``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll(); ``` ```css @import 'locomotive-scroll/dist/locomotive-scroll.css'; ``` ```html
I move at half speed
``` ## Features - **Lightweight** — Only 9.4kB gzipped - **TypeScript First** — Fully typed - **Built on Lenis** — Latest stable release with improved performance - **Dual Intersection Observers** — Optimized detection for triggers vs. animations - **Smart Touch Detection** — Parallax auto-disabled on mobile - **Accessible** — Native scrollbar, keyboard navigation, proper ARIA support ## Demo Check out the [examples and playground](https://scroll.locomotive.ca/docs/examples) ## Support [GitHub Issues](https://github.com/locomotivemtl/locomotive-scroll/issues) ================================================ FILE: context7.json ================================================ { "url": "https://context7.com/locomotivemtl/locomotive-scroll", "public_key": "pk_fFYCHZCsmlAjABaNmHyFM", "foldersInclude": ["packages/lib"], "foldersExclude": ["packages/docs", "packages/demo", "packages/landing", "node_modules"], "customRules": [ "locomotive-scroll v5 wraps Lenis for smooth scrolling - use lenisOptions for scroll physics (lerp, duration, easing)", "Elements with data-scroll-speed, data-scroll-css-progress, or data-scroll-offset trigger RAF updates every frame - minimize these for performance", "data-scroll-speed is relative to container size, not absolute pixels - speed: 0.5 means 50% of container height displacement", "Parallax is auto-disabled on touch devices - add data-scroll-enable-touch-speed to force it on mobile", "scrollPosition defines WHERE in viewport to trigger (start/middle/end), scrollOffset defines WHEN with a px/% offset - they are different", "Elements visible at page load (in fold) have different progress mapping (0→1 vs -1→1) - use data-scroll-ignore-fold to disable", "Dynamically added elements need locomotiveScroll.addScrollElements($container) - just adding HTML to DOM won't register them", "Custom scroll containers need fixed height + overflow hidden/auto on wrapper, with content as direct child", "Use data-scroll-repeat for elements that should re-trigger on each scroll in/out - default is fire once", "Access Lenis instance via locomotiveScroll.lenisInstance for programmatic scrollTo, stop(), start()" ] } ================================================ FILE: package.json ================================================ { "private": true, "name": "locomotive-scroll", "description": "Monorepo for Locomotive Scroll", "license": "MIT", "homepage": "https://github.com/locomotivemtl/locomotive-scroll", "repository": { "type": "git", "url": "https://github.com/locomotivemtl/locomotive-scroll.git" }, "author": { "name": "Locomotive", "email": "info@locomotive.ca", "homepage": "https://locomotive.ca" }, "devDependencies": { "turbo": "^2.7.4", "typescript": "^4.8.3" }, "scripts": { "build": "turbo run build", "build:vercel": "turbo run build --filter=./packages/lib --filter=./packages/demo --filter=./packages/docs", "build:landing": "npm run build --workspace=packages/landing", "dev": "npm run dev --workspace=packages/demo --if-present", "publish": "npm run publish --workspace=packages/lib --if-present", "publish:next": "npm run publish:next --workspace=packages/lib --if-present" }, "workspaces": [ "packages/lib", "packages/demo", "packages/landing", "packages/docs" ], "version": "5.0.0-rc.1", "packageManager": "npm@10.9.4" } ================================================ FILE: packages/demo/.editorconfig ================================================ # editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.{md,markdown}] trim_trailing_whitespace = false ================================================ FILE: packages/demo/.gitignore ================================================ # build output dist/ # SVG sprite public/assets/images/sprite.svg ================================================ FILE: packages/demo/.nvmrc ================================================ v20.14 ================================================ FILE: packages/demo/.prettierignore ================================================ node_modules/** ================================================ FILE: packages/demo/.prettierrc ================================================ { "useTabs": false, "tabWidth": 4, "singleQuote": true, "trailingComma": "none", "semi": true, "printWidth": 100, "plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"] } ================================================ FILE: packages/demo/.vscode/extensions.json ================================================ { "recommendations": ["astro-build.astro-vscode"], "unwantedRecommendations": [] } ================================================ FILE: packages/demo/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "command": "./node_modules/.bin/astro dev", "name": "Development server", "request": "launch", "type": "node-terminal" } ] } ================================================ FILE: packages/demo/.vscode/settings.json ================================================ { "css.customData": [".vscode/tailwind.json"], "scss.lint.unknownAtRules": "ignore", } ================================================ FILE: packages/demo/.vscode/tailwind.json ================================================ { "version": 1.1, "atDirectives": [ { "name": "@tailwind", "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" } ] }, { "name": "@apply", "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#apply" } ] }, { "name": "@responsive", "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" } ] }, { "name": "@screen", "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#screen" } ] }, { "name": "@variants", "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", "references": [ { "name": "Tailwind Documentation", "url": "https://tailwindcss.com/docs/functions-and-directives#variants" } ] } ] } ================================================ FILE: packages/demo/LICENSE ================================================ The MIT License (MIT) Copyright (c) Locomotive, Inc. Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: packages/demo/README.md ================================================

Locomotive Astro Boilerplate

Front-end Astro boilerplate for projects by Locomotive.

## Features * Uses [Astro] for all-in-one web framework. * Uses [Sass] for a feature rich superset of CSS. * Uses [Tailwind CSS] for a sane and scalable CSS architecture. * Uses [Locomotive Scroll] for smooth scrolling with parallax effects. * Uses [Swup] for versatile and extensible page transition. * Uses [Prettier] for a formatted and easy to maintain codebase. ## Getting started Make sure you have the following installed: * [Node] — at least 20.14, the latest LTS is recommended. * [NPM] — at least 8.0, the latest LTS is recommended. > 💡 You can use [NVM] to install and use different versions of Node via the command-line. ```sh # Clone the repository. git clone https://github.com/locomotivemtl/astro-boilerplate.git my-new-project # Enter the newly-cloned directory. cd my-new-project ``` ## Installation ```sh # Switch to recommended Node version from .nvmrc nvm use # Install dependencies from package.json npm install ``` ## Development ```sh # Start development server, watch for changes, and compile assets npm start # Compile and minify assets npm run build ``` ## Project Structure Inside of your project, you'll see the following folders and files: ```text / ├── public/ │ └── favicon.svg ├── src/ │ ├── components/ │ │ └── Card/ │ │ ├── Card.astro │ │ └── Card.scss │ ├── layouts/ │ │ └── Layout.astro │ ├── pages/ │ │ └── index.astro │ ├── styles/ │ │ └── main.scss │ └── scripts/ │ ├── components/ │ ├── utils/ │ ├── app.ts │ └── config.ts └── package.json ``` ## Commands All commands are run from the root of the project, from a terminal: | Command | Action | | :------------------------ | :----------------------------------------------- | | `npm install` | Installs dependencies | | `npm run dev` | Starts local dev server at `localhost:4321` | | `npm run build` | Build your production site to `./dist/` | | `npm run preview` | Preview your build locally, before deploying | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | | `npm run astro -- --help` | Get help using the Astro CLI | | `npm run format` | Format files using prettier | ## Documentation * [Astro] * [Locomotive Scroll] * [Tailwind CSS] * [Swup] * [Prettier] [Astro]: https://docs.astro.build/en/getting-started/ [Tailwind CSS]: https://tailwindcss.com/docs/installation [Locomotive Scroll]: https://scroll.locomotive.ca/docs [Sass]: https://sass-lang.com/ [Swup]: https://swup.js.org/getting-started/ [Node]: https://nodejs.org/ [NPM]: https://npmjs.com/ [NVM]: https://github.com/nvm-sh/nvm [Prettier]: https://prettier.io/ ================================================ FILE: packages/demo/astro.config.ts ================================================ import { defineConfig } from 'astro/config'; import tailwind from '@astrojs/tailwind'; import tailwindConfig from './tailwind.config'; import postcssTailwindShortcuts from '@locomotivemtl/postcss-tailwind-shortcuts'; // https://astro.build/config export default defineConfig({ site: 'https://scroll.locomotive.ca/demo', base: '/demo', outDir: '../../www/demo', vite: { css: { preprocessorOptions: { scss: { additionalData: ` @use "sass:math"; @use "sass:list"; @use "@styles/tools/maths" as *; @use "@styles/tools/functions" as *; ` } }, postcss: { plugins: [ postcssTailwindShortcuts(tailwindConfig.theme), ], } } }, integrations: [ tailwind({ applyBaseStyles: false, }), ], devToolbar: { enabled: false }, image: { domains: ['locomotive.ca'], remotePatterns: [{ protocol: 'https' }], } }); ================================================ FILE: packages/demo/package.json ================================================ { "private": true, "name": "@locomotivemtl/astro-boilerplate", "title": "Locomotive Boilerplate", "type": "module", "version": "0.1.0", "engines": { "node": "20.x", "npm": ">=8.0" }, "repository": { "type": "git", "url": "https://github.com/locomotivemtl/astro-boilerplate.git" }, "author": { "name": "Locomotive", "email": "info@locomotive.ca", "homepage": "https://locomotive.ca" }, "scripts": { "dev": "astro dev --host", "start": "astro dev --host", "build": "astro check && astro build", "preview": "astro preview", "astro": "astro", "format": "prettier --write \"src/**/*.{astro,js,ts,css,scss}\"" }, "dependencies": { "@astrojs/check": "^0.9.2", "@astrojs/tailwind": "^5.1.0", "astro": "^4.13.4", "astro-seo": "^0.8.4", "locomotive-scroll": "*", "nanostores": "^0.10.3", "sass": "^1.77.4", "swup": "^4.7.0", "ts-debounce": "^4.0.0" }, "devDependencies": { "@locomotivemtl/postcss-tailwind-shortcuts": "^1.0.0", "prettier": "^3.3.1", "prettier-plugin-astro": "^0.14.0", "prettier-plugin-tailwindcss": "^0.6.1" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5" } } ================================================ FILE: packages/demo/public/fonts/.gitkeep ================================================ ================================================ FILE: packages/demo/src/components/ScrollToggler/ScrollToggler.astro ================================================ ================================================ FILE: packages/demo/src/env.d.ts ================================================ /// ================================================ FILE: packages/demo/src/layouts/Layout.astro ================================================ --- import '@styles/main.scss'; import { SEO } from 'astro-seo'; interface Props { title: string; seo?: Seo; scrollOrientation?: 'vertical' | 'horizontal'; } const { title, scrollOrientation = 'vertical' } = Astro.props; const FONTS: string[] = [ // 'WebfontRegular.woff2', // 'WebfontBold.woff2', ] --- {FONTS.map(font => )}
================================================ FILE: packages/demo/src/pages/horizontal.astro ================================================ --- import Layout from '@layouts/Layout.astro'; ---

Locomotive Scroll

V

.

0

X

data-scroll-speed

-.1

0

.5

-.2

data-scroll-class

c-scroll-opacity

c-scroll-rotate

data-scroll-repeat

c-scroll-opacity

REPEAT

data-scroll-css-progress

css progress variable

data-scroll-event-progress: Custom Event

data-scroll-position

Position Enter: start

Position Leave: end

Position Enter: middle

Position Leave: middle

Position Enter: end

Position Leave: start

data-scroll-call: Custom Event

Lenis scroll calback: direction

================================================ FILE: packages/demo/src/pages/index.astro ================================================ --- import Layout from "../layouts/Layout.astro" import ScrollToggler from '@components/ScrollToggler/ScrollToggler.astro'; ---

Locomotive Scroll

V

.

5

data-scroll-speed

-.1

0

.5

-.2

data-scroll-class

c-scroll-opacity

c-scroll-rotate

data-scroll-repeat

c-scroll-opacity

REPEAT

data-scroll-css-progress

css progress variable

data-scroll-event-progress: Custom Event

data-scroll-position

Position Enter: start

Position Leave: end

Position Enter: middle

Position Leave: middle

Position Enter: end

Position Leave: start

data-scroll-call: Custom Event

Lenis scroll calback: direction

================================================ FILE: packages/demo/src/scripts/app.ts ================================================ import { Scroll } from '@scripts/classes/Scroll'; import { $screenDebounce } from "../stores/screen"; import { setViewportSize } from './utils/setViewportSize'; // Initialize the Scroll class Scroll.init(); $screenDebounce.subscribe(() => { setViewportSize(); }); // Progress event const $progressEventLabel = document.querySelector('[data-custom-event="progress"]') as HTMLElement const onProgressEventCall = (e: CustomEvent) => { const { progress } = e.detail; $progressEventLabel.textContent = `${Math.round( (progress + Number.EPSILON) * 100 )}%`; } window.addEventListener('progressEvent', onProgressEventCall as EventListener); // Progress position const $positionProgresses = Array.from(document.querySelectorAll('[data-position-progress]')) const onProgressPositionCall = (e: CustomEvent) => { const { target, progress } = e.detail; const $positionProgress = $positionProgresses.find(($el) => $el.parentElement === target) as HTMLElement; $positionProgress.textContent = `${Math.round( (progress + Number.EPSILON) * 100 )}%`; } window.addEventListener('progressPositionEvent', onProgressPositionCall as EventListener); // Custom event const $customEventLabel = document.querySelector('[data-custom-event="event"]') as HTMLElement const onCustomEventCall = (e: CustomEvent) => { const { way } = e.detail; $customEventLabel.textContent = `scrollEvent ${way}`; } window.addEventListener('scrollEvent', onCustomEventCall as EventListener); ================================================ FILE: packages/demo/src/scripts/classes/Scroll.ts ================================================ import { isScrollStopped } from '@root/src/stores/scroll'; import LocomotiveScroll from '../../../../lib/index'; import type { ILenisScrollToOptions, lenisTargetScrollTo } from '../../../../lib/types'; export class Scroll { static locomotiveScroll: LocomotiveScroll; static lastProgress: number; static scrollOrientation: number; // ============================================================================= // Lifecycle // ============================================================================= static init() { const scrollOrientation = document.documentElement.dataset.scrollOrientationSettings as 'vertical' | 'horizontal'; this.locomotiveScroll = new LocomotiveScroll({ lenisOptions: { orientation: scrollOrientation, }, scrollCallback: ({ progress }) => { if (progress > this.lastProgress) { if (this.scrollOrientation != 1) { this.scrollOrientation = 1; document.documentElement.style.setProperty( '--scroll-direction', this.scrollOrientation.toString() ); } } else if (this.scrollOrientation != -1) { this.scrollOrientation = -1; document.documentElement.style.setProperty( '--scroll-direction', this.scrollOrientation.toString() ); } this.lastProgress = progress as number; } }); isScrollStopped.listen((value) => { if (value) { this.stop(); } else { this.start(); } }); } static destroy() { this.locomotiveScroll?.destroy(); } // ============================================================================= // Methods // ============================================================================= static start() { this.locomotiveScroll?.start(); } static stop() { this.locomotiveScroll?.stop(); } static addScrollElements(container: HTMLElement) { this.locomotiveScroll?.addScrollElements(container); } static removeScrollElements(container: HTMLElement) { this.locomotiveScroll?.removeScrollElements(container); } static scrollTo(target: lenisTargetScrollTo, options?: ILenisScrollToOptions) { this.locomotiveScroll?.scrollTo(target, options); } } ================================================ FILE: packages/demo/src/scripts/utils/maths.ts ================================================ const mapRange = (min: number, max: number, nmin: number, nmax: number, value: number) => { return ((value - min) / (max - min)) * (nmax - nmin) + nmin; }; const clamp = (min: number, max: number, value: number) => { return Math.max(min, Math.min(value, max)); }; const normalize = (min: number, max: number, value: number) => { return clamp(0, 1, (value - min) / (max - min)); }; const roundToDecimals = (value: number, decimals: number): number => { const factor = Math.pow(10, decimals); return Math.round((value + Number.EPSILON) * factor) / factor; }; export { mapRange, clamp, normalize, roundToDecimals }; ================================================ FILE: packages/demo/src/scripts/utils/setViewportSize.ts ================================================ const SUPPORTS_VH: boolean = 'CSS' in window && 'supports' in window.CSS && window.CSS.supports('height: 100svh') && window.CSS.supports('height: 100dvh') && window.CSS.supports('height: 100lvh'); export const setViewportSize = () => { // Document styles const documentStyles = document.documentElement.style; // Viewport width const vw: number = document.body.clientWidth * 0.01; documentStyles.setProperty('--vw', `${vw}px`); // Return if browser supports vh, svh, dvh, & lvh // if (SUPPORTS_VH) { // return; // } // Viewport height const svh: number = document.documentElement.clientHeight * 0.01; documentStyles.setProperty('--svh', `${svh}px`); const dvh: number = window.innerHeight * 0.01; documentStyles.setProperty('--dvh', `${dvh}px`); if (document.body) { const fixed = document.createElement('div'); fixed.style.width = '1px'; fixed.style.height = '100vh'; fixed.style.position = 'fixed'; fixed.style.left = '0'; fixed.style.top = '0'; fixed.style.bottom = '0'; fixed.style.visibility = 'hidden'; document.body.appendChild(fixed); const fixedHeight: number = fixed.clientHeight; fixed.remove(); const lvh: number = fixedHeight * 0.01; documentStyles.setProperty('--lvh', `${lvh}px`); } }; ================================================ FILE: packages/demo/src/scripts/utils/string.ts ================================================ const toDash = (str: string) => str .split(/(?=[A-Z])/) .join('-') .toLowerCase(); export { toDash }; ================================================ FILE: packages/demo/src/stores/screen.ts ================================================ import { map } from 'nanostores'; import { debounce } from 'ts-debounce'; export interface IScreenValues { width: number; height: number; } export interface IScreenDebounceValues { width: number; height: number; } export const $screen = map({ width: window.innerWidth, height: window.innerHeight }); export const $screenDebounce = map({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', () => { $screen.setKey('width', window.innerWidth); $screen.setKey('height', window.innerHeight); }); const debouncedFunction: any = () => { $screenDebounce.setKey('width', window.innerWidth); $screenDebounce.setKey('height', window.innerHeight); }; window.addEventListener('resize', debounce(debouncedFunction, 200)); ================================================ FILE: packages/demo/src/stores/scroll.ts ================================================ import { atom } from "nanostores"; export const isScrollStopped = atom(false); ================================================ FILE: packages/demo/src/styles/main.scss ================================================ // ========================================================================== // Tailwind CSS // ========================================================================== /** * This injects Tailwind's base styles and any base styles registered by * plugins. */ @tailwind base; /** * This injects Tailwind's component classes and any component classes * registered by plugins. */ @tailwind components; /** * This injects Tailwind's utility classes and any utility classes registered * by plugins. */ @tailwind utilities; // ========================================================================== // Fonts imports // ========================================================================== @layer base { // @font-face { // font-family: 'IBM Plex Mono'; // src: url('/fonts/IBMPlexMono-Regular.woff2') format('woff2'); // font-weight: normal; // font-style: normal; // font-display: swap; // } } // ========================================================================== // Vendors // ========================================================================== @import '../../../lib/bundled/locomotive-scroll.css'; // ========================================================================== // Local files imports // ========================================================================== @import 'tools/maths'; @import 'tools/functions'; // ========================================================================== // Global styles // ========================================================================== :root { --color-primary: color('black'); --color-secondary: color('white'); } html { font-family: theme('fontFamily.sans'); } body { background: var(--color-secondary); color: var(--color-primary); } ::selection { background-color: var(--color-primary); color: var(--color-secondary); text-shadow: none; } * { box-sizing: border-box; margin: 0; padding: 0; } /* Define a transition duration during page visits */ html.is-changing .transition-fade { transition: opacity 0.25s; opacity: 1; } /* Define the styles for the unloaded pages */ html.is-animating .transition-fade { opacity: 0; } html[data-scroll-orientation='horizontal'] { body { width: fit-content; } main { display: flex; } } :root { --color-1: #f4f4ed; --color-2: #6decaf; --color-3: #357ded; --color-4: #5e239d; --color-5: #f61067; } .c-scroll { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: auto; } .c-scroll_offset { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; &:before, &:after { content: ''; position: absolute; z-index: 1; html[data-scroll-orientation='vertical'] & { width: 100%; height: 1px; } html[data-scroll-orientation='horizontal'] & { width: 1px; height: 100%; } } &:before { background-color: rgb(217, 66, 66); html[data-scroll-orientation='vertical'] & { bottom: var(--offset-start); } html[data-scroll-orientation='horizontal'] & { right: var(--offset-start); } } &:after { background-color: rgb(74, 166, 215); html[data-scroll-orientation='vertical'] & { top: var(--offset-end); } html[data-scroll-orientation='horizontal'] & { left: var(--offset-end); } } } .c-scroll_section { width: 100%; background-color: var(--color-1); &.-full { height: calc(100 * var(--svh)); html[data-scroll-orientation='horizontal'] & { width: 100vw; flex-shrink: 0; } } &.-centered { display: flex; flex-direction: column; align-items: center; justify-content: center; } &.-row { flex-direction: row; html[data-scroll-orientation='horizontal'] & { flex-direction: column; } } } .c-scroll_box { position: relative; display: flex; justify-content: center; align-items: center; flex-direction: column; width: 20vw; height: 20vw; max-width: 300px; max-height: 300px; overflow: hidden; html[data-scroll-orientation='horizontal'] & { width: 24vh; height: 24vh; } img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } } .c-scroll-opacity { opacity: 1 !important; transition: opacity 0.75s ease(); } .c-scroll-rotate { transform: rotate(2turn) !important; transition: transform 0.75s ease(); } .c-scroll-direction { html[data-scroll-orientation='vertical'] & { transform: scaleY(var(--scroll-direction)); } html[data-scroll-orientation='horizontal'] & { transform: scaleX(var(--scroll-direction)) rotate(-90deg); } } .c-scroll-toggler { position: fixed; top: 0; left: 0; display: inline-block; background-color: grey; padding: 0.5em; } ================================================ FILE: packages/demo/src/styles/tools/functions.scss ================================================ // ========================================================================== // Tools / Functions // ========================================================================== // Returns calculation of a percentage of the viewport small height. // // ```scss // .c-box { // height: svh(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in svh @function svh($number) { @return calc(#{$number} * var(--svh, 1svh)); } // Returns calculation of a percentage of the viewport large height. // // ```scss // .c-box { // height: lvh(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in lvh @function lvh($number) { @return calc(#{$number} * var(--lvh, 1lvh)); } // Returns calculation of a percentage of the viewport dynamic height. // // ```scss // .c-box { // height: dvh(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in dvh @function dvh($number) { @return calc(#{$number} * var(--dvh, 1dvh)); } // Returns calculation of a percentage of the viewport width. // // ```scss // .c-box { // width: vw(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in vw @function vw($number) { @return calc(#{$number} * var(--vw, 1vw)); } ================================================ FILE: packages/demo/src/styles/tools/maths.scss ================================================ // ========================================================================== // Tools / Maths // ========================================================================== // Remove the unit of a length // // @param {Number} $number Number to remove unit from // @return {function} @function strip-unit($value) { @if type-of($value) != 'number' { @error "Invalid `#{type-of($value)}` type. Choose a number type instead."; } @else if type-of($value) == 'number' and not is-unitless($value) { @return math.div($value, $value * 0 + 1); } @return $value; } // Returns the square root of the given number. // // @param {number} $number The number to calculate. // @return {number} @function sqrt($number) { $x: 1; $value: $x; @for $i from 1 through 10 { $value: $x - math.div(($x * $x - abs($number)), (2 * $x)); $x: $value; } @return $value; } // Returns a number raised to the power of an exponent. // // @param {number} $number The base number. // @param {number} $exp The exponent. // @return {number} @function pow($number, $exp) { $value: 1; @if $exp >0 { @for $i from 1 through $exp { $value: $value * $number; } } @else if $exp < 0 { @for $i from 1 through -$exp { $value: math.div($value, $number); } } @return $value; } // Returns the factorial of the given number. // // @param {number} $number The number to calculate. // @return {number} @function fact($number) { $value: 1; @if $number >0 { @for $i from 1 through $number { $value: $value * $i; } } @return $value; } // Returns an approximation of pi, with 11 decimals. // // @return {number} @function pi() { @return 3.14159265359; } // Converts the number in degrees to the radian equivalent . // // @param {number} $angle The angular value to calculate. // @return {number} If $angle has the `deg` unit, // the radian equivalent is returned. // Otherwise, the unitless value of $angle is returned. @function rad($angle) { $unit: unit($angle); $angle: strip-units($angle); // If the angle has `deg` as unit, convert to radians. @if ($unit ==deg) { @return math.div($angle, 180) * pi(); } @return $angle; } // Returns the sine of the given number. // // @param {number} $angle The angle to calculate. // @return {number} @function sin($angle) { $sin: 0; $angle: rad($angle); @for $i from 0 through 10 { $sin: $sin + pow(-1, $i) * math.div(pow($angle, (2 * $i + 1)), fact(2 * $i + 1)); } @return $sin; } // Returns the cosine of the given number. // // @param {string} $angle The angle to calculate. // @return {number} @function cos($angle) { $cos: 0; $angle: rad($angle); @for $i from 0 through 10 { $cos: $cos + pow(-1, $i) * math.div(pow($angle, 2 * $i), fact(2 * $i)); } @return $cos; } // Returns the tangent of the given number. // // @param {string} $angle The angle to calculate. // @return {number} @function tan($angle) { @return math.div(sin($angle), cos($angle)); } ================================================ FILE: packages/demo/tailwind.config.ts ================================================ import defaultTheme from 'tailwindcss/defaultTheme'; import type { Config } from 'tailwindcss'; export default { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], prefix: 'u-', corePlugins: { container: false, }, theme: { extend: { fontFamily: { serif: [ 'Times New Roman', ...defaultTheme.fontFamily.serif ], sans: [ 'Arial', ...defaultTheme.fontFamily.sans ], }, colors: { black: '#000000', white: '#ffffff', primary: '#312dfb', }, screens: { 'to-2xs': { 'max': '339px' }, '2xs': '340px', 'to-xs': { 'max': '499px' }, 'xs': '500px', 'to-sm': { 'max': '699px' }, 'sm': '700px', 'to-md': { 'max': '999px' }, 'md': '1000px', 'to-lg': { 'max': '1199px' }, 'lg': '1200px', 'to-xl': { 'max': '1399px' }, 'xl': '1400px', 'to-2xl': { 'max': '1599px' }, '2xl': '1600px', 'to-3xl': { 'max': '1799px' }, '3xl': '1800px', 'to-4xl': { 'max': '1999px' }, '4xl': '2000px', 'to-5xl': { 'max': '2399px' }, '5xl': '2400px', }, gap: { gutter: '20px', gutterMobile: '10px' }, transitionDuration: { fast: '0.2s', default: '0.4s', slow: '0.6s', slower: '0.8s', slowest: '1s', }, transitionTimingFunction: { // Smooth default: 'cubic-bezier(0.380, 0.005, 0.215, 1)', // // Common easings // power1: { // in: 'cubic-bezier(0.550, 0.085, 0.680, 0.530)', // out: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)', // inOut: 'cubic-bezier(0.455, 0.030, 0.515, 0.955)', // }, // power2: { // in: 'cubic-bezier(0.550, 0.055, 0.675, 0.190)', // out: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)', // inOut: 'cubic-bezier(0.645, 0.045, 0.355, 1.000)', // }, // power3: { // in: 'cubic-bezier(0.895, 0.030, 0.685, 0.220)', // out: 'cubic-bezier(0.165, 0.840, 0.440, 1.000)', // inOut: 'cubic-bezier(0.770, 0.000, 0.175, 1.000)', // }, // power4: { // in: 'cubic-bezier(0.755, 0.050, 0.855, 0.060)', // out: 'cubic-bezier(0.230, 1.000, 0.320, 1.000)', // inOut: 'cubic-bezier(0.860, 0.000, 0.070, 1.000)', // }, // expo: { // in: 'cubic-bezier(0.950, 0.050, 0.795, 0.035)', // out: 'cubic-bezier(0.190, 1.000, 0.220, 1.000)', // inOut: 'cubic-bezier(1.000, 0.000, 0.000, 1.000)', // }, // back: { // in: 'cubic-bezier(0.600, -0.280, 0.735, 0.045)', // out: 'cubic-bezier(0.175, 00.885, 0.320, 1.275)', // inOut: 'cubic-bezier(0.680, -0.550, 0.265, 1.550)', // }, // sine: { // in: 'cubic-bezier(0.470, 0.000, 0.745, 0.715)', // out: 'cubic-bezier(0.390, 0.575, 0.565, 1.000)', // inOut: 'cubic-bezier(0.445, 0.050, 0.550, 0.950)', // }, // circ: { // in: 'cubic-bezier(0.600, 0.040, 0.980, 0.335)', // out: 'cubic-bezier(0.075, 0.820, 0.165, 1.000)', // inOut: 'cubic-bezier(0.785, 0.135, 0.150, 0.860)', // }, // slow: { // out: 'cubic-bezier(.04,1.15,0.4,.99)', // }, // bounce: 'cubic-bezier(0.17, 0.67, 0.3, 1.33)', // smooth: 'cubic-bezier(0.380, 0.005, 0.215, 1)', }, zIndex: { modal: '200', header: '100', above: '1', default: '0', below: '-1', }, }, }, plugins: [], } satisfies Config; ================================================ FILE: packages/demo/tsconfig.json ================================================ { "extends": "astro/tsconfigs/strict", "compilerOptions": { "baseUrl": ".", "moduleResolution": "Bundler", "paths": { "@root/*": [ "./*" ], "@src/*": [ "./src/*" ], "@components/*": [ "./src/components/*" ], "@layouts/*": [ "./src/layouts/*" ], "@scripts/*": [ "./src/scripts/*" ], "@styles/*": [ "./src/styles/*" ], "@types/*": [ "types/*" ], "@data/*": [ "./src/data/*" ] } } } ================================================ FILE: packages/demo/types/global.d.ts ================================================ type Seo = { title?: string; description?: string; social?: { facebook?: { title?: string; image?: { url?: string; }; description?: string; }; twitter?: { creator?: string; title?: string; image?: { url?: string; }; description?: string; }; }, advanced?: { robots?: string[]; canonical?: string; }; }; ================================================ FILE: packages/demo/types/swup.d.ts ================================================ type VisitType = { fragmentVisit: any; to: { html: string; }; }; ================================================ FILE: packages/docs/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: packages/docs/README.md ================================================ # Website This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. ### Installation ``` $ yarn ``` ### Local Development ``` $ yarn start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ### Build ``` $ yarn build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment Using SSH: ``` $ USE_SSH=true yarn deploy ``` Not using SSH: ``` $ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: packages/docs/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: packages/docs/docs/documentation/attributes.md ================================================ --- sidebar_position: 3 --- # Attributes ## data-scroll Enable viewport detection on an element. ## data-scroll-position - **Type:** `string` - **Default:** `start,end` ![Data Scroll Position](/assets/data-scroll-position.jpg) This attribute specifies the trigger position of the element within the scroll container when using Locomotive Scroll. It accepts two values: one for the position when the element enters the viewport, and a second for the position when the element leaves the viewport. The position is calculated relative to the **Lenis scroll container** (which defaults to `window`, but can be customized via `lenisOptions.wrapper`). Accepted values are: `'start'`, `'middle'`, `'end'`. ### Examples ![Data Scroll Position](/assets/data-scroll-position-example-1.jpg) ![Data Scroll Position](/assets/data-scroll-position-example-2.jpg) Here's an example of using Locomotive Scroll's data-scroll-position attribute on an HTML element: ```html
``` ## data-scroll-offset - **Type:** `string` - **Default:** `0,0` ![Data Scroll Offset](/assets/scroll-offset-1.jpg) Specifies the trigger offset of the element within the viewport when using the Locomotive Scroll library. It takes two values: one for the offset when the element enters the viewport, and a second for the offset when the element leaves the viewport. The offset can be defined in two ways: - If specified in percentages, it is relative to the viewport height. - If specified in pixels, it is an absolute value. For example: - `'100,50%'` represents an offset of `100` pixels for the enter position and `50%` of the viewport height for the leave position. - `'25%, 15%'` represents an offset of `25%` of the viewport height for the enter position and `15%` of the viewport height for the leave position. ### Example ![Data Scroll Offset](/assets/data-scroll-offset-example.jpg) Here's an example of using Locomotive Scroll's data-scroll-offset attribute on an HTML element: ```html
``` ## data-scroll-class - **Type:** `string` - **Default:** `is-inview` Specifies a custom class to be applied to the element when its offset intersects with the viewport. The default class used is `is-inview`. You can provide your own class name as a string value to customize the styling or behavior of the element when it becomes visible within the viewport. ## data-scroll-repeat Specifies whether the element's in-view detection should repeat if it is declared. By default, the in-view detection of elements is not repeated. **Simply declaring this attribute will enable the repeat behavior for in-view detection of the element.** ## data-scroll-speed - **Type:** `number` Specifies the parallax speed for the element. The speed is relative to the scroll container size (not pixels), making it predictable across different viewport sizes. ### How it works The parallax displacement is calculated using: ``` displacement = progress × containerSize × speed × -1 ``` Where: - `containerSize` = Height (or width for horizontal) of the Lenis scroll container - `progress` = Element's progress through the viewport (`-1` to `1` for normal elements, `0` to `1` for in-fold elements) - `speed` = Your specified value ### Examples **With `data-scroll-speed="1"`:** - Normal element: Moves from `+containerHeight` to `-containerHeight` (total = 2× container height) - In-fold element: Moves from `0` to `-containerHeight` (total = 1× container height) **With `data-scroll-speed="0.5"`:** - Normal element: Total displacement = 1× container height - In-fold element: Total displacement = 0.5× container height **With `data-scroll-speed="-0.3"` (negative = reversed):** - Element moves in opposite direction - Total displacement = 0.6× container height ### Touch devices Parallax is **automatically disabled on touch devices** by default for better native scrolling performance. To enable it on mobile/tablets, add the [`data-scroll-enable-touch-speed`](#data-scroll-enable-touch-speed) attribute. :::tip Start with small values like `0.1` to `0.5` for subtle effects. The value is now relative to container size, so effects scale naturally with viewport changes. ::: ## data-scroll-call - **Type:** `string` The `data-scroll-call` attribute enables you to trigger a custom event when an element becomes visible within the viewport. This attribute requires a string value that specifies the name of the custom event that you want to trigger. By utilizing the `data-scroll-call` attribute, you can define and trigger your own events to perform specific actions or handle certain behaviors when elements scroll into view. These events can be listened to and handled in your JavaScript code using event listeners or any event handling mechanism provided by your framework or library. Here's an example of how to use the `data-scroll-call` attribute: ```html
Trigger
``` ```js window.addEventListener('scrollEvent', (e) => { const { target, way, from } = e.detail; console.log(`target: ${target}`, `way: ${way}`, `from: ${from}`); }); ``` ## data-scroll-css-progress If you declare this attribute, it will add a CSS variable `--progress` to the element. This variable represents the current progress of the element and ranges between `0` and `1`. By adding `--progress` as a CSS variable, you can utilize it in your CSS styles to create dynamic effects or animations based on the scrolling progress of the element. ## data-scroll-event-progress - **Type:** `string` When you declare this attribute, it will trigger the custom event that you specify. This event allows you to retrieve the current progress of the element, which ranges between `0` and `1`. By utilizing the custom event, you can implement event handlers in your JavaScript code to perform actions or retrieve information based on the scrolling progress of the element. ```html
Progress custom event
``` ```js import LocomotiveScroll from 'locomotive-scroll'; const locomotiveScroll = new LocomotiveScroll(); window.addEventListener('progressEvent', (e) => { const { target, progress } = e.detail; console.log(`target: ${target}`, `progress: ${progress}`); }); ``` ## data-scroll-to Prevents the click event and initiates scrolling to a target. If the element is a link (``), it uses the `href` attribute as the target. Otherwise, if the element has the `data-scroll-to-href` attribute, it uses that as the target. ## data-scroll-to-href - **Type:** `string` Specifies the target for the `data-scroll-to` action. If the element is a link, the `href` attribute is used as the target. Alternatively, you can specify a custom target using the `data-scroll-to-href` attribute. ## data-scroll-to-offset - **Type:** `number` Specifies the scroll offset for the `data-scroll-to` action. The value is a number that represents the scroll padding top. It determines the vertical offset from the top of the target element when scrolling to it. ## data-scroll-to-duration - **Type:** `number` Specifies the duration of the scroll animation for the `data-scroll-to` action. The value is a number representing the scroll duration in seconds. It determines how long the scrolling animation takes to reach the target element. ## data-scroll-ignore-fold If an element is within the fold (visible portion of the viewport), its offset is automatically adjusted to start its progress from its starting position. However, if you want to ignore this default behavior and prevent the offset adjustment, you can use the `data-scroll-ignore-fold` attribute. Please note that by using the `data-scroll-ignore-fold` attribute, the element's progress will not be influenced by its position within the fold, and its offset will remain unchanged throughout the scrolling process. ## data-scroll-enable-touch-speed By default, the parallax effect (`data-scroll-speed`) is **automatically disabled on touch devices** to ensure smooth scrolling performance. Touch devices are detected using `'ontouchstart' in window || navigator.maxTouchPoints > 0`. If you want to enable the parallax effect on touch devices, add the `data-scroll-enable-touch-speed` attribute to the element. **Example:** ```html
Parallax only on desktop
Parallax on desktop AND mobile
``` **Note:** Enabling parallax on touch devices may impact scrolling smoothness, especially on lower-end mobile devices. Use this attribute judiciously and test thoroughly on target devices. ================================================ FILE: packages/docs/docs/documentation/methods.md ================================================ --- sidebar_position: 2 --- # Methods ## destroy() The `destroy()` method allows you to destroy the Locomotive Scroll instance along with its associated events. It is useful when you want to remove Locomotive Scroll functionality from a specific element or completely clean up the instance. ```js const locomotiveScroll = new LocomotiveScroll(); locomotiveScroll.destroy(); ``` ## start() The `start()` method allows you to manually start the scroll. By default, the scroll automatically starts when you create the Locomotive Scroll instance. However, there may be situations where you need to programmatically control when the scroll starts. In such cases, you can utilize the [autoStart](/documentation/options#autostart) option to have more flexibility and decide whether to initiate the scroll automatically or not. ```js const locomotiveScroll = new LocomotiveScroll({ autoStart: false }); // Starting the locomotive scroll on the next frame requestAnimationFrame(() => { locomotiveScroll.start(); }); ``` > :bulb: **Tip**: If you prefer not to start Locomotive Scroll automatically, you can utilize the [autoStart](/documentation/options#autostart) option. ## stop() The `stop()` method allows you to manually stop the scroll. When you call this method, the scroll motion will come to a halt. ```js const locomotiveScroll = new LocomotiveScroll(); // Stopping locomotive-scroll on the next frame requestAnimationFrame(() => { locomotiveScroll.stop(); }); ``` > :bulb: **Tip**: If you prefer not to start Locomotive Scroll automatically, you can utilize the [autoStart](/documentation/options#autostart) option. ## resize() The `resize()` method allows you to manually trigger the resize callback of the Locomotive Scroll instance. This is useful when you need to handle resizing events programmatically or when you want to manually update the scroll calculations after a layout change. > :bulb: **Note**: Locomotive Scroll automatically handles resize events by synchronizing with Lenis's internal ResizeObservers (`onContentResize` and `onWrapperResize`). Manual resizing is rarely needed unless you're making dynamic layout changes that Lenis doesn't detect. ```js const locomotiveScroll = new LocomotiveScroll(); locomotiveScroll.resize(); ``` ## removeScrollElements($oldContainer) The `removeScrollElements($oldContainer)` method allows you to dynamically unobserve scroll elements (`[data-scroll]`) by providing their container. This is particularly useful when you're updating the DOM dynamically, such as through Ajax fetching or other operations that add or remove elements from the page. - **Parameters:** - `$oldContainer` (_HTMLElement_): The `NodeElement` that has been removed from the DOM. This container should be the parent element that contains the scroll elements you want to unobserve. Here's an example of how to use the `removeScrollElements($oldContainer)` method: ```js const locomotiveScroll = new LocomotiveScroll(); const $oldContainer = document.getElementById('containerToRemove'); // Call the method to remove scroll elements from the old container locomotiveScroll.removeScrollElements($oldContainer); ``` ## addScrollElements($newContainer) The `addScrollElements($newContainer)` method allows you to dynamically observe scroll elements (`[data-scroll]`) by providing their container. This is particularly useful when you're updating the DOM dynamically, such as through Ajax fetching or other operations that add or remove elements from the page. - **Parameters:** - `$newContainer` (_HTMLElement_): The `NodeElement` that has been added from the DOM. This container should be the parent element that contains the scroll elements you want to observe. Here's an example of how to use the `addScrollElements($newContainer)` method: ```js const locomotiveScroll = new LocomotiveScroll(); const $newContainer = document.getElementById('containerToAdd'); // Call the method to add scroll elements from the new container locomotiveScroll.addScrollElements($newContainer); ``` ## scrollTo(target, options) The `scrollTo(target, options)` method allows you to scroll to a specific target on the page. - **Parameters:** - `target` (_optional, number / HTMLElement / string_): The target to scroll to. It can be a `number` (scroll position), `HTMLElement` (DOM element), or `string` (CSS selector or keyword: `top`, `left`, `start`, `bottom`, `right`, `end`). - `options` (_optional, ILenisScrollToOptions_): An options object that configures the scroll behavior. The available options are based on [Lenis's scroll-to options](https://github.com/darkroomengineering/lenis#instance-methods): - `offset` (_number_): A number equivalent to `scroll-padding-top`. Specifies the offset from the top of the target element. - `lerp` (_number_): Animation lerp intensity. - `duration` (_number_): The duration of the scroll animation in seconds. - `immediate` (_boolean_): If set to `true`, it ignores the duration and easing and performs an immediate scroll. - `lock` (_boolean_): Whether or not to prevent the user from scrolling until the target is reached. - `force` (_boolean_): Reach the target even if the instance is stopped. - `onComplete` (_function_): A callback function called when the target is reached. - `easing` (_function_): A smooth scroll easing function. By calling `scrollTo(target, options)`, Locomotive Scroll will smoothly scroll to the specified target on the page, taking into account the provided options. ```js import LocomotiveScroll from 'locomotive-scroll'; const locomotiveScroll = new LocomotiveScroll(); const $target = document.getElementById('jsTarget'); function scrollTo(params) { const { target, options } = params; locomotiveScroll.scrollTo(target, options); } scrollTo({ target: $target, options: {} }); ``` ================================================ FILE: packages/docs/docs/documentation/options.md ================================================ --- sidebar_position: 1 --- # Options ## lenisOptions - **Type:** `object` _(Optional)_ The `lenisOptions` parameter is an optional object that allows you to configure specific settings based on some of [Lenis's instance settings](https://github.com/darkroomengineering/lenis#instance-settings): - `wrapper` (**HTMLElement|Window**): Specifies the element that will be used as the scroll container. Defaults to `window` for full-page scrolling. Can be set to a custom element for contained scrolling (e.g., `document.querySelector('.scroll-container')`). - `content` (**HTMLElement**): Specifies the element that contains the content that will be scrolled, usually `wrapper`'s direct child. Defaults to `document.documentElement`. Can be set to a custom element when using a custom wrapper. - `lerp` (**number**): Specifies the intensity of linear interpolation (lerp) between frames, ranging from 0 to 1. - `duration` (**number**): Specifies the duration of the animation. - `orientation` (**string**): Specifies whether the scrolling is `vertical` or `horizontal`. It adds a `data-scroll-orientation` attribute on the `` tag. - `gestureOrientation` (**boolean**): Specifies the orientation of the gestures. It can be set to `vertical`, `horizontal`, or `both`. - `smoothWheel` (**boolean**): Specifies whether to enable smooth scrolling for mouse wheel events. - `smoothTouch` (**boolean**): Specifies whether to enable smooth scrolling for touch events. Note that it is disabled by default because it is impossible to mimic the native smoothness of touch devices. - `wheelMultiplier` (**number**): Specifies the multiplier to use for mouse wheel events. - `touchMultiplier` (**number**): Specifies the multiplier to use for touch events. - `normalizeWheel` (**boolean**): Specifies whether to normalize wheel inputs across different browsers. - `easing` (**function**): Specifies the rate of change of a specific value. Our default easing is custom, but you can choose one from [Easings.net](https://easings.net/). Here's an example of using `lenisOptions` with its default values: ```js const locomotiveScroll = new LocomotiveScroll({ lenisOptions: { wrapper: window, content: document.documentElement, lerp: 0.1, duration: 1.2, orientation: 'vertical', gestureOrientation: 'vertical', smoothWheel: true, smoothTouch: false, wheelMultiplier: 1, touchMultiplier: 2, normalizeWheel: true, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // https://www.desmos.com/calculator/brs54l4xou }, }); ``` ### Custom Scroll Container You can use a custom scroll container instead of the default full-page scroll: ```js const locomotiveScroll = new LocomotiveScroll({ lenisOptions: { wrapper: document.querySelector('.scroll-container'), content: document.querySelector('.scroll-content'), }, }); ``` ```html
Parallax element
``` **Requirements:** - The `wrapper` must have a fixed height and `overflow: hidden` (or `auto`/`scroll`) - The `content` must be a direct child of the wrapper - Intersection Observers will automatically use the wrapper as their root - Resize detection is automatically synchronized with Lenis's ResizeObservers ## triggerRootMargin - **Type:** `string` - **Default:** `'-1px -1px -1px -1px'` _(Optional)_ Specifies the root margin for scroll elements that need to be triggered by the [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin). ```js // Default Value const locomotiveScroll = new LocomotiveScroll({ triggerRootMargin: '-1px -1px -1px -1px', }); ``` ## rafRootMargin - **Type:** `string` - **Default:** `'100% 100% 100% 100%'` _(Optional)_ Specifies the root margin for scroll elements that need to be triggered by the [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) based on a **RequestAnimationFrame**. This option is relevant for elements with any of the following attributes: `data-scroll-offset`, `data-scroll-position`, `data-scroll-css-progress`, `data-scroll-event-progress`, `data-scroll-speed`. ```js // Default Value const locomotiveScroll = new LocomotiveScroll({ rafRootMargin: '100% 100% 100% 100%', }); ``` ## autoStart - Type: `boolean` - Default: `true` _(Optional)_ Enable or disable the RAF (Request Animation Frame) starting. By default, the RAF starts automatically when Locomotive Scroll is initialized. If you want to manually control when the RAF starts, you can set `autoStart` to `false`. ```js // Default Value const locomotiveScroll = new LocomotiveScroll({ autoStart: false, }); // Manually start the RAF setTimeout(() => { locomotiveScroll.start(); }, 2000) ``` ## scrollCallback - **Type:** `function` _(Optional)_ Specifies a callback function that can return an object with the following properties: `{ scroll, limit, velocity, direction, progress }`. This functionality is made possible by Lenis's scroll callback feature. ```js import LocomotiveScroll from 'locomotive-scroll'; function onScroll({ scroll, limit, velocity, direction, progress }) { console.log(scroll, limit, velocity, direction, progress); } const locomotiveScroll = new LocomotiveScroll({ scrollCallback: onScroll, }); ``` ## initCustomTicker - **Type:** `function` _(Optional)_ Specifies a callback function to initialize an external ticker instead of Locomotive Scroll's default request animation frame. The `destroyCustomTicker` function also needs to be declared. You can use an external ticker by following this example, which overrides the default request animation frame used by Locomotive Scroll: ```js import LocomotiveScroll from 'locomotive-scroll'; import { gsap } from 'gsap/all'; const locomotiveScroll = new LocomotiveScroll({ initCustomTicker: (render) => { gsap.ticker.add(render); }, destroyCustomTicker: (render) => { gsap.ticker.remove(render); }, }); ``` ## destroyCustomTicker - **Type:** `function` _(Optional)_ Specifies a callback function to destroy an external ticker instead of Locomotive Scroll's default request animation frame. The `initCustomTicker` function also needs to be declared. You can use an external ticker by following this example, which overrides the default request animation frame used by Locomotive Scroll: ```js import LocomotiveScroll from 'locomotive-scroll'; import { gsap } from 'gsap/all'; const locomotiveScroll = new LocomotiveScroll({ initCustomTicker: (render) => { gsap.ticker.add(render); }, destroyCustomTicker: (render) => { gsap.ticker.remove(render); }, }); ``` ================================================ FILE: packages/docs/docs/examples.md ================================================ --- sidebar_position: 4 --- # Examples Below are several CodeSandbox examples showcasing different usage scenarios: 1. [Basic Usage](https://codesandbox.io/s/basic-76t7lh): Demonstrates the basic setup and usage of Locomotive Scroll with parallax effect on elements. 2. [Scroll Call](https://codesandbox.io/s/scroll-call-k6eqen): Demonstrates how to trigger a custom event based on scroll position using Locomotive Scroll. 3. [Progress](https://codesandbox.io/s/progress-srtqx9): Demonstrates how to extract the scroll progress of an element. 4. [GSAP Timeline](https://codesandbox.io/s/gsap-timeline-4kk8dc): Demonstrates how to synchronize a GSAP animation with the scroll progress of an element. 5. [Dynamic Content](https://codesandbox.io/p/sandbox/dynamic-content-5m30r6): Shows how to handle dynamic content and layout changes with Locomotive Scroll. 6. [Next.js Integration](https://codesandbox.io/p/devbox/cocky-star-5vcklc?file=%2Fapp%2Fpage.tsx): Shows how to integrate Locomotive Scroll with Next.js. 7. [Third Party Injected Popups](https://codesandbox.io/p/sandbox/third-party-injected-popups-xch5tq): Shows how to handle third party injected popups with Locomotive Scroll. 8. [GSAP ScrollTrigger](https://codesandbox.io/p/sandbox/gsap-scrolltrigger-tt7sjd): Demonstrates how to synchronize a GSAP ScrollTrigger animation with the scroll progress of an element. Feel free to explore these examples to gain a better understanding of different use cases and how Locomotive Scroll can be utilized in your projects. ================================================ FILE: packages/docs/docs/extras/limitations.md ================================================ --- sidebar_position: 1 --- # Limitations > We encourage the open-source community to develop and share their own solutions to overcome limitations in Locomotive Scroll. ## Lenis Lenis already have its own [considerations](https://github.com/darkroomengineering/lenis#considerations). We recommend reviewing them before using Locomotive Scroll to ensure a smooth integration and avoid any potential conflicts. ## SSR Due to Locomotive Scroll's reliance on the window reference and Intersection Observer, it may not be fully compatible with server-side rendering setups. SSR typically involves rendering the web page on the server before sending it to the client, and certain client-side dependencies may not function properly in this context. Therefore, when using Locomotive Scroll with SSR, it's important to carefully consider and test its compatibility to ensure optimal functionality. ## Third Party Injected Popups Some third-party JavaScript used on websites may inject popups or modals that require scrolling. In certain cases, conflicts can arise when the default Lenis `wrapper` (typically the `window`) captures the scroll event. Normally, we can [resolve this](https://github.com/darkroomengineering/lenis#use-the-data-lenis-prevent-attribute-on-nested-scroll-elements-in-addition-we-advise-you-to-add-overscroll-behavior-contain-on-this-element) by adding the `data-lenis-prevent` data attribute to the DOM element that requires inner scrolling, such as a popup. However, this solution cannot be used when the DOM is injected dynamically. Since Lenis v1.1.0, you can use the `prevent` option to manually prevent scroll smoothing based on elements traversed by events. If the function returns `true`, it will prevent the scroll from being smoothed. HTML: ```html

Third Party Injected Popups

Scroll inside the modal

``` JavaScript: ```js import LocomotiveScroll from 'locomotive-scroll'; const locomotiveScroll = new LocomotiveScroll({ lenisOptions: { prevent: (node) => node.getAttribute('id') === 'modalSelector', }, }); ``` You can check out the [example](https://codesandbox.io/p/sandbox/third-party-injected-popups-xch5tq) for more details. ================================================ FILE: packages/docs/docs/extras/migration-guide.md ================================================ --- sidebar_position: 1 --- # Migration Guide: v4 to v5 This guide will help you migrate from Locomotive Scroll v4 to v5. Version 5 is a complete rewrite built on [Lenis](https://github.com/darkroomengineering/lenis), offering better performance, smaller bundle size, and improved TypeScript support. :::tip Locomotive Scroll v5 is simpler and more performant than v4, but it requires some changes to your code. Take your time to understand the new patterns. ::: ## Overview of Changes ### What's New - **Built on Lenis** — Modern smooth scroll engine instead of Virtual Scroll - **Smaller Bundle** — 9.4kB (v5) vs ~12.1kB (v4) gzipped - **TypeScript First** — Fully typed with better autocomplete - **Simplified API** — Fewer options, clearer patterns - **Better Performance** — Dual Intersection Observer strategy - **Custom Scroll Containers** — Support for contained scrolling - **Native Events** — CustomEvents instead of ModularJS ### What's Removed - ❌ **ModularJS support** — Use native CustomEvents instead - ❌ **`data-scroll-container`** — No longer required - ❌ **`data-scroll-section`** — No longer needed for performance - ❌ **Custom scrollbar** — Use Lenis scrollbar or native scrollbar - ❌ **`data-scroll-sticky`** — Use CSS `position: sticky` instead - ❌ **`data-scroll-delay`** — No longer available - ❌ **`data-scroll-direction`** — Parallax follows scroll orientation - ❌ **`data-scroll-target`** — No longer available --- ## Installation ### v4 ```bash npm install locomotive-scroll@4.1.4 ``` ### v5 ```bash npm install locomotive-scroll ``` --- ## Basic Setup ### v4: Required Container ```html

Hey

``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll({ el: document.querySelector('[data-scroll-container]'), smooth: true, }); ``` ### v5: Simpler Setup ```html

Hey

``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll(); ``` :::info In v5, smooth scrolling is enabled by default through Lenis. No need for `data-scroll-container` or `data-scroll-section`. ::: --- ## Instance Options ### v4 → v5 Mapping | v4 Option | v5 Equivalent | Notes | | -------------------- | ------------------------------ | ----------------------------------------------- | | `el` | `lenisOptions.wrapper` | Now inside `lenisOptions` | | `smooth` | ✅ Always enabled | Smooth scrolling is default behavior | | `direction` | `lenisOptions.orientation` | Values: `'vertical'` or `'horizontal'` | | `lerp` | `lenisOptions.lerp` | Same concept, inside `lenisOptions` | | `multiplier` | `lenisOptions.wheelMultiplier` | Renamed for clarity | | `touchMultiplier` | `lenisOptions.touchMultiplier` | Same, inside `lenisOptions` | | `class` | ❌ Removed | Use `data-scroll-class` attribute instead | | `offset` | `triggerRootMargin` | Now uses IntersectionObserver rootMargin format | | `repeat` | `data-scroll-repeat` | Now per-element attribute | | `initClass` | ❌ Removed | No longer needed | | `scrollingClass` | ❌ Removed | Use scroll event to detect scrolling | | `getDirection` | ✅ Available | Access via `lenisInstance.direction` | | `getSpeed` | ✅ Available | Access via `lenisInstance.velocity` | | `scrollbarContainer` | ❌ Removed | Use Lenis scrollbar or native | | `scrollbarClass` | ❌ Removed | Use Lenis scrollbar or native | ### v4 Example ```js const scroll = new LocomotiveScroll({ el: document.querySelector('[data-scroll-container]'), smooth: true, direction: 'vertical', lerp: 0.1, multiplier: 1, class: 'is-inview', scrollbarClass: 'c-scrollbar', }); ``` ### v5 Equivalent ```js const scroll = new LocomotiveScroll({ lenisOptions: { orientation: 'vertical', lerp: 0.1, wheelMultiplier: 1, }, }); // Access Lenis directly for more control console.log(scroll.lenisInstance.direction); // Get direction console.log(scroll.lenisInstance.velocity); // Get speed ``` --- ## Data Attributes ### Removed Attributes | v4 Attribute | v5 Alternative | | ----------------------- | ----------------------------- | | `data-scroll-container` | ❌ Remove it (not needed) | | `data-scroll-section` | ❌ Remove it (not needed) | | `data-scroll-sticky` | Use CSS `position: sticky` | | `data-scroll-delay` | ❌ Not available | | `data-scroll-direction` | ❌ Follows scroll orientation | | `data-scroll-target` | ❌ Not available | ### Modified Attributes | Attribute | v4 Behavior | v5 Behavior | | ---------------------- | ------------------------------------- | ---------------------------------------------------------------------- | | `data-scroll-speed` | Parallax effect with arbitrary values | ✅ **Recalculated based on scroll container size** (see details below) | | `data-scroll-call` | Triggers ModularJS method | Now triggers **native CustomEvent** | | `data-scroll-offset` | `"bottom,top"` format | ✅ Same format | | `data-scroll-position` | Based on window position | Now based on **Lenis scroll container** position | ### New Attributes | Attribute | Description | | -------------------------------- | ------------------------------------ | | `data-scroll-css-progress` | Adds `--progress` CSS variable (0-1) | | `data-scroll-event-progress` | Emits progress via CustomEvent | | `data-scroll-enable-touch-speed` | Enable parallax on touch devices | --- ## Parallax ### v4 ```html
Fast parallax
``` ### v5 ```html
Fast parallax (desktop only)
Fast parallax (all devices)
``` :::warning Key Changes 1. **Parallax is automatically disabled on touch devices in v5** to use native scrolling. Use `data-scroll-enable-touch-speed` to override this behavior. 2. **Speed calculation completely redesigned** - Now based on scroll container size, not arbitrary values. ::: ### How `data-scroll-speed` Works in v5 The parallax calculation has been **completely redesigned** in v5 to be more predictable and based on the scroll container size. **Formula:** ``` translateValue = progress × containerSize × speed × -1 ``` Where: - `containerSize` = Height (vertical) or width (horizontal) of Lenis scroll container - `progress` ranges from `-1` to `1` for normal elements - `progress` ranges from `0` to `1` for elements visible on page load (in fold) **Examples:** With `data-scroll-speed="1"` and vertical scrolling: - **Normal element**: Moves from `+containerHeight` to `-containerHeight` (total displacement = 2× container height) - **In-fold element**: Moves from `0` to `-containerHeight` (total displacement = 1× container height) With `data-scroll-speed="0.5"`: - **Normal element**: Moves from `+0.5×containerHeight` to `-0.5×containerHeight` (total = 1× container height) - **In-fold element**: Moves from `0` to `-0.5×containerHeight` (total = 0.5× container height) With `data-scroll-speed="-1"` (reversed): - **Normal element**: Moves from `-containerHeight` to `+containerHeight` (opposite direction) :::tip Migration Tip **v4 to v5 speed conversion:** - v4 speeds were arbitrary values - v5 speeds are relative to container size - Start with smaller values (0.1 to 0.5) and adjust based on container size - Test with different viewport sizes since it's now proportional ::: --- ## Events & Callbacks ### v4: ModularJS Integration ```html
Trigger
``` ```js scroll.on('call', (func) => { this.call(...func); // ModularJS }); ``` ### v5: Native CustomEvents ```html
Trigger
``` ```js window.addEventListener('videoTrigger', (e) => { const { target, way, from } = e.detail; // way: 'enter' | 'leave' // from: 'start' | 'end' if (way === 'enter') { target.querySelector('video').play(); } }); ``` :::tip Migration Tip Replace all ModularJS `data-scroll-call` patterns with native CustomEvent listeners. It's simpler and has no dependencies! ::: --- ## Scroll Events ### v4 ```js scroll.on('scroll', (args) => { console.log(args.scroll.y); console.log(args.direction); console.log(args.speed); }); ``` ### v5 ```js const scroll = new LocomotiveScroll({ scrollCallback: ({ scroll, velocity, direction, progress }) => { console.log(scroll); // Current scroll position console.log(velocity); // Scroll speed console.log(direction); // 1 (down/right), -1 (up/left), 0 (stopped) console.log(progress); // 0 to 1 }, }); ``` --- ## Progress Tracking ### v4: Using `data-scroll-id` ```html

Hero

``` ```js scroll.on('scroll', (args) => { if (args.currentElements['hero']) { let progress = args.currentElements['hero'].progress; console.log(progress); // 0 to 1 } }); ``` ### v5: Using Progress Events ```html

Hero

``` ```js window.addEventListener('heroProgress', (e) => { console.log(e.detail.progress); // 0 to 1 }); ``` ### v5: Using CSS Variables ```html

Hero

``` ```css [data-scroll-css-progress] { opacity: calc(var(--progress) * 1); transform: translateY(calc((1 - var(--progress)) * 100px)); } ``` --- ## Methods ### Updated Methods | Method | v4 | v5 | Notes | | ------------------ | --- | ------------------------ | --------------------- | | `destroy()` | ✅ | ✅ | Same API | | `start()` | ✅ | ✅ | Same API | | `stop()` | ✅ | ✅ | Same API | | `scrollTo(target)` | ✅ | ✅ | Delegated to Lenis | | `update()` | ✅ | ❌ Renamed to `resize()` | Manual resize trigger | | `init()` | ✅ | ❌ Removed | No longer needed | ### v4 ```js scroll.update(); scroll.destroy(); scroll.init(); // Reinitialize ``` ### v5 ```js scroll.resize(); // Rarely needed (auto-synced with Lenis) scroll.destroy(); // No init() - create new instance instead ``` --- ## Sticky Elements ### v4: Using `data-scroll-sticky` ```html
Sticky element
Target
``` ### v5: Use CSS `position: sticky` ```html
Sticky element
``` ```css .sticky-element { position: sticky; top: 0; } ``` :::info Why? CSS `position: sticky` is performant, native, and doesn't require JavaScript. v5 removed custom sticky implementation in favor of the native solution. ::: --- ## Custom Scroll Containers ### v4: Not Supported In v4, you could only do full-page scrolling. ### v5: Fully Supported ```js const scroll = new LocomotiveScroll({ lenisOptions: { wrapper: document.querySelector('.scroll-container'), content: document.querySelector('.scroll-content'), }, }); ``` ```html
Parallax works here too!
``` --- ## Performance Improvements ### v4 Performance Patterns ```html
``` ### v5 Performance (Automatic) v5 uses a **dual Intersection Observer strategy** that automatically optimizes performance: - **Trigger IO** — For simple in-view detection (classes, callbacks) - **RAF IO** — For continuous animations (parallax, progress) Elements only subscribe to RAF when: 1. They're **visible** (detected by IO) 2. They need **continuous updates** (parallax, progress tracking) **No manual optimization needed!** --- ## Complete Migration Checklist ### HTML Changes - [ ] Remove all `data-scroll-container` attributes - [ ] Remove all `data-scroll-section` attributes - [ ] Remove `data-scroll-sticky` → use CSS `position: sticky` - [ ] Remove `data-scroll-delay` → no replacement - [ ] Remove `data-scroll-direction` → follows scroll orientation - [ ] Remove `data-scroll-target` → no replacement - [ ] Update ModularJS `data-scroll-call` → use simple event names - [ ] Consider adding `data-scroll-enable-touch-speed` for mobile parallax ### JavaScript Changes - [ ] Remove `el` option → use `lenisOptions.wrapper` if needed - [ ] Remove `smooth` option → always enabled - [ ] Rename `direction` → `lenisOptions.orientation` - [ ] Move scroll options into `lenisOptions` object - [ ] Replace `scroll.on('call', ...)` → `window.addEventListener(eventName, ...)` - [ ] Replace `scroll.on('scroll', ...)` → `scrollCallback` option - [ ] Replace `scroll.update()` → `scroll.resize()` (rarely needed) - [ ] Remove `scroll.init()` → recreate instance instead - [ ] Remove custom scrollbar code → use native or Lenis scrollbar ### CSS Changes - [ ] Remove locomotive-scroll v4 CSS - [ ] Add locomotive-scroll v5 CSS (minimal) - [ ] Replace sticky elements → use `position: sticky` - [ ] Update any custom scrollbar styles → use Lenis scrollbar --- ## Example: Complete v4 to v5 Migration ### Before (v4) ```html

Hero

Parallax
Video
Sticky
End
``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll({ el: document.querySelector('[data-scroll-container]'), smooth: true, direction: 'vertical', lerp: 0.1, class: 'is-inview', }); scroll.on('scroll', (args) => { if (args.currentElements['hero']) { console.log(args.currentElements['hero'].progress); } }); scroll.on('call', (func) => { this.call(...func); // ModularJS }); ``` ### After (v5) ```html

Hero

Parallax
Video
Sticky
End
``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll({ lenisOptions: { orientation: 'vertical', lerp: 0.1, }, scrollCallback: ({ scroll, velocity, direction, progress }) => { // Global scroll callback if needed }, }); // Progress tracking window.addEventListener('heroProgress', (e) => { console.log(e.detail.progress); }); // Video callback window.addEventListener('videoPlay', (e) => { const { target, way } = e.detail; if (way === 'enter') { target.querySelector('video').play(); } }); ``` ```css .sticky { position: sticky; top: 0; } ``` --- ## Need Help? - Check the [documentation](/documentation/options) for full API reference - Review [examples](/examples) for common patterns - Open an issue on [GitHub](https://github.com/locomotivemtl/locomotive-scroll/issues) for bugs **Happy scrolling with v5! 🚀** ================================================ FILE: packages/docs/docs/extras/showcase.md ================================================ --- sidebar_position: 2 --- # Showcase Here is a curated list of websites that utilize Locomotive Scroll: - [Locomotive](https://locomotive.ca/) - [Destigmatize](https://2024.destigmatize.ca/) - [Scout Motors](https://www.scoutmotors.com/) - [Lightship](https://lightshiprv.com/) - [Vooban](https://vooban.com/) - [Construction Desourdy](https://www.constructiondesourdy.com/) - [GKC](https://gkc.ca/) - [Troa](https://www.troa.fr/) - [Vazzi](https://vazzi.fun/) - [21TSI](https://21tsi.com/) - [Eduard Bodak](https://www.eduardbodak.com/) - [Mindmarket](https://mindmarket.com/) Please note that these websites are provided as examples to showcase the potential of Locomotive Scroll. Each website may have its own unique design, features, and implementation of Locomotive Scroll. Feel free to explore these websites to gain inspiration and insights on how Locomotive Scroll can be applied in various contexts. ================================================ FILE: packages/docs/docs/getting-started/installation.md ================================================ --- sidebar_position: 1 --- # Installation To install Locomotive Scroll, you have two options: ## NPM (Recommended) You can install it via NPM by running the following command: ```bash npm install locomotive-scroll ``` This will install the latest version. ## CDN Alternatively, you can use a CDN to include Locomotive Scroll directly in your HTML: ```html ``` ## Package Information For more details and package information, you can visit the [NPM package page](https://www.npmjs.com/package/locomotive-scroll). ================================================ FILE: packages/docs/docs/getting-started/usage.md ================================================ --- sidebar_position: 2 --- # Usage ## Javascript ### With a bundler ```js import LocomotiveScroll from 'locomotive-scroll'; const locomotiveScroll = new LocomotiveScroll(); ``` ### Without a bundler ```html ``` ## CSS ### Essential styles Add the base styles to your CSS file: ```css @import 'locomotive-scroll/dist/locomotive-scroll.css'; ``` Or link directly to the bundled CSS: ```html ``` Learn more about styling considerations on [Lenis documentation](https://github.com/darkroomengineering/lenis#considerations). ### Sass Import ```scss // Vendors @import 'node_modules/locomotive-scroll/dist/locomotive-scroll'; ``` ### Horizontal styles If you are utilizing the horizontal feature, we recommend applying the following CSS rules: ```css /* Only necessary with horizontal scrolling */ html[data-scroll-orientation='horizontal'] { body { width: fit-content; } [data-scroll-container] { display: flex; } } ``` ## Let's try ```html

Hello 👋

What's up?

😬

``` ```js import LocomotiveScroll from 'locomotive-scroll'; const locomotiveScroll = new LocomotiveScroll(); ``` ================================================ FILE: packages/docs/docs/intro.md ================================================ --- slug: / sidebar_position: 1 title: Introduction --- # Locomotive Scroll v5 > Detection of elements in viewport & smooth scrolling with parallax effects. Locomotive Scroll is a lightweight JavaScript library (9.4kB gzipped) that provides smooth scrolling animations and advanced scroll interactions for web applications. Built on top of [Lenis](https://github.com/darkroomengineering/lenis), it offers features such as smooth scrolling, parallax effects, and scroll-based animations with a focus on performance and accessibility. ## What's New in V5 Version 5 is a complete rewrite with major improvements: - **Lighter & Faster** — Built on Lenis 1.3.17, reduced to 9.4kB gzipped - **TypeScript First** — Fully typed for better developer experience - **Dual Intersection Observer Strategy** — Optimized detection for simple triggers vs. continuous animations - **Smart Touch Detection** — Parallax automatically disabled on mobile for native scrolling. - **Custom Scroll Containers** — Support for full-page or contained scrolling - **No Layout Shifts** — No more greedy CSS transforms breaking your layouts - **Improved Resize Handling** — Synchronized with Lenis's internal ResizeObservers for perfect timing ## Key Features | Feature | Description | | ---------------------- | -------------------------------------------------------------------------------- | | **Viewport Detection** | Using browser's native Intersection Observer API with dual optimization strategy | | **Parallax Effects** | Simple, performant parallax with `data-scroll-speed` attribute | | **Progress Tracking** | Real-time scroll progress (0-1) as CSS variables or JavaScript events | | **Custom Events** | Trigger callbacks when elements enter/leave viewport | | **Accessibility** | Native scrollbar, keyboard navigation, proper ARIA support | | **Performance** | Elements only subscribe to RAF when visible **and** animating | ## Quick Start ```bash npm install locomotive-scroll ``` Ready to dive in? Start with our [Installation Guide](./getting-started/installation.md) to learn how to set up Locomotive Scroll in your project. ## Dependencies | Name | Description | | ----------------------------------------------------- | -------------------- | | [Lenis](https://github.com/darkroomengineering/lenis) | Smooth scroll engine | ## Related - [Lenis](https://github.com/darkroomengineering/lenis) ================================================ FILE: packages/docs/docusaurus.config.js ================================================ // @ts-check // `@type` JSDoc annotations allow editor autocompletion and type checking // (when paired with `@ts-check`). // There are various equivalent ways to declare your Docusaurus config. // See: https://docusaurus.io/docs/api/docusaurus-config import { themes as prismThemes } from 'prism-react-renderer'; /** @type {import('@docusaurus/types').Config} */ const config = { title: 'Locomotive Scroll Documentation', tagline: 'Detection of elements in viewport & smooth scrolling with parallax effects.', favicon: 'img/favicon.ico', // Set the production url of your site here url: 'https://scroll.locomotive.ca', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' baseUrl: '/docs/', // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. organizationName: 'locomotivemtl', // Usually your GitHub org/user name. projectName: 'docusaurus', // Usually your repo name. onBrokenLinks: 'throw', // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". i18n: { defaultLocale: 'en', locales: ['en'], }, presets: [ [ 'classic', /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { routeBasePath: '/', sidebarPath: './sidebars.js', // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, blog: false, theme: { customCss: './src/css/custom.css', }, }), ], ], themes: [ [ require.resolve('@easyops-cn/docusaurus-search-local'), { hashed: true, indexDocs: true, indexBlog: false, indexPages: false, docsRouteBasePath: '/', }, ], ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ image: 'img/og-image.png', metadata: [ { name: 'og:title', content: 'Locomotive Scroll Documentation', }, { name: 'og:description', content: 'Detection of elements in viewport & smooth scrolling with parallax effects.', }, { name: 'og:image', content: 'https://scroll.locomotive.ca/docs/img/og-image.png', }, { name: 'og:type', content: 'website' }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:title', content: 'Locomotive Scroll Documentation', }, { name: 'twitter:description', content: 'Detection of elements in viewport & smooth scrolling with parallax effects.', }, { name: 'twitter:image', content: 'https://scroll.locomotive.ca/docs/img/og-image.png', }, ], colorMode: { // defaultMode: 'light', // disableSwitch: true, respectPrefersColorScheme: true, }, navbar: { title: 'Locomotive Scroll', items: [ { href: 'https://scroll.locomotive.ca/', label: 'Landing', position: 'right', }, { href: 'https://github.com/locomotivemtl/locomotive-scroll', label: 'GitHub', position: 'right', }, ], }, footer: { links: [ { title: 'Docs', items: [ { label: 'Getting Started', to: '/', }, { label: 'Documentation', to: '/documentation/options', }, { label: 'Examples', to: '/examples', }, { label: 'Extras', to: '/extras/limitations', }, ], }, { title: 'About us', items: [ { href: 'https://locomotive.ca', label: 'Website', }, { label: 'Instagram', href: 'https://www.instagram.com/locomotivemtl/', }, { label: 'Linkedin', href: 'https://www.linkedin.com/company/locomotive-mtl/', }, ], }, { title: 'More', items: [ { href: 'https://scroll.locomotive.ca', label: 'Landing', }, { href: 'https://github.com/locomotivemtl/locomotive-scroll', label: 'GitHub', }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} -
Locomotive`, }, prism: { theme: prismThemes.github, darkTheme: prismThemes.vsDark, }, }), plugins: [ [ 'vercel-analytics', { debug: false, mode: 'auto', }, ], ], }; export default config; ================================================ FILE: packages/docs/package.json ================================================ { "name": "docs-v-2", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build --out-dir ../../www/docs", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@docusaurus/core": "^3.9.2", "@docusaurus/plugin-vercel-analytics": "^3.9.2", "@docusaurus/preset-classic": "^3.9.2", "@easyops-cn/docusaurus-search-local": "^0.44.5", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.9.2", "@docusaurus/types": "^3.9.2", "cssnano": "^7.0.2", "postcss": "^8.4.38" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 3 chrome version", "last 3 firefox version", "last 5 safari version" ] }, "engines": { "node": ">=18.0" } } ================================================ FILE: packages/docs/sidebars.js ================================================ /** * Creating a sidebar enables you to: - create an ordered group of docs - render a sidebar for each doc of that group - provide next/previous navigation The sidebars can be generated from the filesystem, or explicitly defined here. Create as many sidebars as you want. */ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure //tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], // But you can create a sidebar manually /* tutorialSidebar: [ 'intro', 'hello', { type: 'category', label: 'Tutorial', items: ['tutorial-basics/create-a-document'], }, ], */ }; //export default sidebars; export default { docs: [ 'intro', { type: 'category', label: 'Getting Started', collapsible: true, items: ['getting-started/installation', 'getting-started/usage'], }, { type: 'category', label: 'Documentation', collapsible: true, items: ['documentation/options', 'documentation/methods', 'documentation/attributes'], }, 'examples', { type: 'category', label: 'Extras', collapsible: true, items: ['extras/migration-guide', 'extras/limitations', 'extras/showcase'], }, ], }; ================================================ FILE: packages/docs/src/css/custom.css ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ @font-face { font-display: swap; font-family: "HelveticaNowDisplay"; src: url("/fonts/HelveticaNowDisplay-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal } @font-face { font-display: swap; font-family: "LocomotiveNew"; src: url("/fonts/PPLocomotiveNew-Light.woff2") format("woff2"); font-weight: 400; font-style: normal } /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #312dfb; --ifm-color-primary-dark: #1510fa; --ifm-color-primary-darker: #0905f7; --ifm-color-primary-darkest: #0804cb; --ifm-color-primary-light: #4d4afc; --ifm-color-primary-lighter: #5c59fc; --ifm-color-primary-lightest: #8684fd; --ifm-font-family-base: "HelveticaNowDisplay", sans-serif; --ifm-font-weight-base: var(--ifm-font-weight-semibold); --ifm-navbar-background-color: #202ded; --ifm-navbar-link-color: white; --ifm-navbar-search-input-background-color: #ffffff; --ifm-navbar-search-input-placeholder-color: #29292dff; --ifm-navbar-search-input-color: #29292dff; } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { --ifm-color-primary: #7299ff; --ifm-color-primary-dark: #5b7bf9; --ifm-color-primary-darker: #3d54ee; --ifm-color-primary-darkest: #2f40d3; --ifm-color-primary-light: #ffdede; --ifm-color-primary-lighter: #fff4f4; --ifm-color-primary-lightest: #ffffff; } /* Add logo */ .navbar__title:before { display: inline-block; content: '🟠🔁'; font-family: "LocomotiveNew", sans-serif; padding-right: .5rem; } .navbar__inner { --ifm-color-emphasis-200: #ffffff; } .navbar__inner .clean-btn svg { color: #ffffff; } .navbar__inner .clean-btn:hover svg { color: #202ded; } ================================================ FILE: packages/docs/static/.nojekyll ================================================ ================================================ FILE: packages/landing/.browserslistrc ================================================ defaults ================================================ FILE: packages/landing/.editorconfig ================================================ # editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.{md,markdown}] trim_trailing_whitespace = false [*.{ms,mustache}] insert_final_newline = false ================================================ FILE: packages/landing/.gitignore ================================================ node_modules .DS_Store Thumbs.db loconfig.*.json !loconfig.example.json .prettierrc www/**/*.html ================================================ FILE: packages/landing/.npmrc ================================================ //npm.greensock.com/:_authToken=8d111942-fc15-4353-9a4e-ee25d3f5d4da @gsap:registry=https://npm.greensock.com ================================================ FILE: packages/landing/.nvmrc ================================================ v20.10 ================================================ FILE: packages/landing/LICENSE ================================================ The MIT License (MIT) Copyright (c) Locomotive, Inc. Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: packages/landing/README.md ================================================

Locomotive Boilerplate

Front-end boilerplate for projects by Locomotive.

## Features * Uses a custom [task runner](docs/development.md) for handling assets. * Uses [BrowserSync] for fast development and testing in browsers. * Uses [Sass] for a feature rich superset of CSS. * Uses [ESBuild] for extremely fast processing of JS/ES modules. * Uses [SVG Mixer] for processing SVG files and generating spritesheets. * Uses [ITCSS] for a sane and scalable CSS architecture. * Uses [Locomotive Scroll] for smooth scrolling with parallax effects. * Uses a custom [grid system](docs/grid.md) for layout creation. Learn more about [languages and technologies](docs/technologies.md). ## Getting started Make sure you have the following installed: * [Node] — at least 17.9, the latest LTS is recommended. * [NPM] — at least 8.0, the latest LTS is recommended. > 💡 You can use [NVM] to install and use different versions of Node via the command-line. ```sh # Clone the repository. git clone https://github.com/locomotivemtl/locomotive-boilerplate.git my-new-project # Enter the newly-cloned directory. cd my-new-project ``` Then replace the original remote repository with your project's repository. Then update the following files to suit your project: * [`README.md`](README.md): The file you are currently reading. * [`package.json`](package.json): * Package name: `@locomotivemtl/boilerplate` * Package title: `Locomotive Boilerplate` * [`package-lock.json`](package-lock.json): * Package name: `@locomotivemtl/boilerplate` * [`loconfig.json`](loconfig.json): * BrowserSync proxy URL: `locomotive-boilerplate.test` Remove `paths.url` to use BrowserSync's built-in server which uses `paths.dest`. * View path: `./views/boilerplate/template` * [`environment.js`](assets/scripts/utils/environment.js): * Application name: `Boilerplate` * [`site.webmanifest`](www/site.webmanifest): * Manifest name: `Locomotive Boilerplate` * Manifest short name: `Boilerplate` * HTML files: * Page title: `Locomotive Boilerplate` ## Installation ```sh # Switch to recommended Node version from .nvmrc nvm use # Install dependencies from package.json npm install ``` ## Development ```sh # Start development server, watch for changes, and compile assets npm start # Compile and minify assets npm run build ``` Learn more about [development and building](docs/development.md). ## Documentation * [Development and building](docs/development.md) * [Languages and technologies](docs/technologies.md) * [Grid system](docs/grid.md) [BrowserSync]: https://npmjs.com/package/browser-sync [ESBuild]: https://npmjs.com/package/esbuild [ITCSS]: https://itcss.io/ [Locomotive Scroll]: https://npmjs.com/package/locomotive-scroll [modularJS]: https://npmjs.com/package/modujs [modularLoad]: https://npmjs.com/package/modularload [Sass]: https://sass-lang.com/ [SVG Mixer]: https://npmjs.com/package/svg-mixer [Node]: https://nodejs.org/ [NPM]: https://npmjs.com/ [NVM]: https://github.com/nvm-sh/nvm ================================================ FILE: packages/landing/assets/images/.gitkeep ================================================ ================================================ FILE: packages/landing/assets/images/sprite/.gitkeep ================================================ ================================================ FILE: packages/landing/assets/scripts/app.js ================================================ import modular from 'modujs'; import * as modules from './modules'; import globals from './globals'; import { debounce } from './utils/tickers'; import { $html } from './utils/dom'; import { ENV, FONT, CUSTOM_EVENT, CSS_CLASS } from './config'; import { isFontLoadingAPIAvailable, loadFonts } from './utils/fonts'; import { inject } from '@vercel/analytics'; // Modules const app = new modular({ modules: modules, }); function init() { // Bind global events bindEvents(); // First resize onResize(); // Set initial viewport height document.documentElement.style.setProperty( '--vh-initial', `${window.innerHeight * 0.01}px` ); globals(); app.init(app); setTimeout(() => { $html.classList.add(CSS_CLASS.FIRST_LOADED); $html.classList.add(CSS_CLASS.LOADED); $html.classList.remove(CSS_CLASS.LOADING); setTimeout(() => { $html.classList.add(CSS_CLASS.READY); }, 100); }, 100); inject(); /** * Eagerly load the following fonts. */ if (isFontLoadingAPIAvailable) { loadFonts(FONT.EAGER, ENV.IS_DEV).then((eagerFonts) => { $html.classList.add(CSS_CLASS.FONTS_LOADED); if (ENV.IS_DEV) { console.group( 'Eager fonts loaded!', eagerFonts.length, '/', document.fonts.size ); console.group('State of eager fonts:'); eagerFonts.forEach((font) => console.log( font.family, font.style, font.weight, font.status /*, font*/ ) ); console.groupEnd(); console.group('State of all fonts:'); document.fonts.forEach((font) => console.log( font.family, font.style, font.weight, font.status /*, font*/ ) ); console.groupEnd(); } }); } } //////////////// // Global events //////////////// function bindEvents() { // Resize event const resizeEndEvent = new CustomEvent(CUSTOM_EVENT.RESIZE_END); window.addEventListener( 'resize', debounce( () => { window.dispatchEvent(resizeEndEvent); }, 200, false ) ); window.addEventListener('resize', onResize); // Orientation change event window.addEventListener( 'orientationchange', debounce( () => { onOnrientationChange(); }, 200, false ) ); } //////////////// // Callbacks //////////////// function onOnrientationChange() { document.documentElement.style.setProperty( '--vh-initial', `${window.innerHeight * 0.01}px` ); } function onResize() { let vw = $html.offsetWidth * 0.01; let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vw', `${vw}px`); document.documentElement.style.setProperty('--vh', `${vh}px`); } //////////////// // Execute //////////////// window.addEventListener('load', (event) => { init(); }); ================================================ FILE: packages/landing/assets/scripts/config.js ================================================ /** * > When using the esBuild API, all `process.env.NODE_ENV` expressions * > are automatically defined to `"production"` if all minification * > options are enabled and `"development"` otherwise. This only happens * > if `process`, `process.env`, and `process.env.NODE_ENV` are not already * > defined. This substitution is necessary to avoid code crashing instantly * > (since `process` is a Node API, not a web API). * > — https://esbuild.github.io/api/#platform */ const NODE_ENV = process.env.NODE_ENV const IS_MOBILE = window.matchMedia('(any-pointer:coarse)').matches // Main environment variables const ENV = Object.freeze({ // Node environment NAME: NODE_ENV, IS_PROD: NODE_ENV === 'production', IS_DEV: NODE_ENV === 'development', // Device IS_MOBILE, IS_DESKTOP: !IS_MOBILE, }) // Main CSS classes used within the project const CSS_CLASS = Object.freeze({ FIRST_LOADED: 'is-first-loaded', LOADING: 'is-loading', LOADED: 'is-loaded', READY: 'is-ready', FONTS_LOADED: 'fonts-loaded', LAZY_CONTAINER: 'c-lazy', LAZY_LOADED: '-lazy-loaded', // ... }) // Custom js events const CUSTOM_EVENT = Object.freeze({ RESIZE_END: 'loco.resizeEnd', // ... }) // Fonts parameters const FONT = Object.freeze({ EAGER: [ { family: 'PP Locomotive New', style: 'normal', weight: '300' }, { family: 'Helvetica Now Display', style: 'normal', weight: '400' }, { family: 'Helvetica Now Display', style: 'normal', weight: '500' } ], }) export { ENV, CSS_CLASS, CUSTOM_EVENT, FONT, } ================================================ FILE: packages/landing/assets/scripts/globals.js ================================================ import svg4everybody from 'svg4everybody'; import { ENV } from './config'; // Dynamic imports for development mode only let gridHelper; (async () => { if (ENV.IS_DEV) { const gridHelperModule = await import('./utils/grid-helper'); gridHelper = gridHelperModule?.gridHelper; } })(); export default function () { /** * Use external SVG spritemaps */ svg4everybody(); /** * Add grid helper */ gridHelper?.(); } ================================================ FILE: packages/landing/assets/scripts/modules/Example.js ================================================ import { module } from 'modujs'; import { FONT } from '../config'; import { whenReady } from '../utils/fonts'; export default class extends module { constructor(m) { super(m); } init() { whenReady(FONT.EAGER).then((fonts) => this.onFontsLoaded(fonts)); } onFontsLoaded(fonts) { console.log('Example: Eager Fonts Loaded!', fonts) } } ================================================ FILE: packages/landing/assets/scripts/modules/FadeInText.js ================================================ import { module } from 'modujs'; import gsap from "gsap"; import { SplitText } from "gsap/all"; gsap.registerPlugin(SplitText); export default class extends module { constructor(m) { super(m); // Binding this.onFadeinTextProgressBind = this.onFadeinTextProgress.bind(this); // UI this.$texts = this.el; // Data this.progress = 0; this.metrics = []; this.baseColor = getComputedStyle(this.el).getPropertyValue('--color-cta-fadein'); this.targetColor = getComputedStyle(this.el).getPropertyValue('--color'); } /////////////// // Lifecycle /////////////// init() { // Bind this.bindEvents(); // Split with autoSplit for automatic resize handling this.splitText(); } destroy() { // Unbind this.unbindEvents(); // Clean up split if (this.split) { this.split.revert(); } } /////////////// // Events /////////////// bindEvents() { window.addEventListener("fadeinTextProgress", this.onFadeinTextProgressBind); } unbindEvents() { window.removeEventListener("fadeinTextProgress", this.onFadeinTextProgressBind); } /////////////// // Callback /////////////// onFadeinTextProgress(e) { const {target, progress} = e.detail; if (!this.el.contains(target) || !this.split) return; this.progress = progress; this.computeProgress(); } /////////////// // Methods /////////////// splitText() { // Use SplitText.create with autoSplit for automatic resize handling this.split = SplitText.create(this.$texts, { type: "lines", linesClass: "c-fadein-text_line", autoSplit: true, onSplit: (self) => { // This callback is called every time the text is split, including on resize this.computeMetrics(self); requestAnimationFrame(() => { this.computeProgress(); }); } }); // Initial metrics computation this.computeMetrics(this.split); } computeMetrics(split) { // Reset metrics this.metrics = []; const widths = split.lines.map(line => line.getBoundingClientRect().width); const totalWidth = widths.reduce((total, width) => total + width, 0); let widthIncrementor = 0; for (let index = 0; index < split.lines.length; index++) { // Compute metrics const from = widthIncrementor / totalWidth; const ratio = widths[index] / totalWidth; widthIncrementor += widths[index]; this.metrics.push({ from: from, to: from + ratio }); } } computeProgress() { for (let index = 0; index < this.split.lines.length; index++) { const $line = this.split.lines[index]; const { from, to } = this.metrics[index]; const clampedProgress = gsap.utils.clamp(0, 1, gsap.utils.mapRange(from, to, 0, 1, this.progress)) $line.style.setProperty('--progress', `${clampedProgress}`) this.updateGradient($line, clampedProgress) } } updateGradient($item, progress) { const offset = 100; const x = progress; const g1 = 0 const g5 = 100 const g3 = gsap.utils.mapRange(0, 1, -offset, 100 + offset, x) const g2 = Math.max(g3 - offset, 0) const g4 = Math.min(g3 + offset, 100) const background = `linear-gradient(to right, ${this.targetColor} ${g1}%, ${this.targetColor} ${g2}%, ${this.targetColor} ${g3}%, ${this.baseColor} ${g4}%, ${this.baseColor} ${g5}%)` $item.style.setProperty('--background', `${background}`) } } ================================================ FILE: packages/landing/assets/scripts/modules/HoverShuffle.js ================================================ import { module } from 'modujs'; import { gsap } from 'gsap'; const DURATION = 0.25; const SHUFFLE_PATTERN = /\p{Extended_Pictographic}|\r|\n|./gu export default class extends module { constructor(m) { super(m); } init() { this.onItemEnterBind = this.onItemEnter.bind(this) this.onItemLeaveBind = this.onItemLeave.bind(this) this.update(); } update() { this.reset(); this.el.addEventListener('mouseenter', this.onItemEnterBind) this.el.addEventListener('mouseleave', this.onItemLeaveBind) } reset() { this.el.removeEventListener('mouseenter', this.onItemEnterBind) this.el.removeEventListener('mouseleave', this.onItemLeaveBind) } shuffle(input) { var a = typeof input == 'string' ? input.split("") : input, n = a.length; for(var i = n - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = a[i]; a[i] = a[j]; a[j] = tmp; } return a.join(""); } onItemEnter(e) { const $el = e.currentTarget; let targets = []; if($el.dataset.hoverShuffle == 'children') { $el.querySelectorAll('[data-hover-shuffle-child]').forEach(child => { targets.push(child) child.setAttribute('aria-label', child.innerText) }) } else { targets = [$el] $el.setAttribute('aria-label', $el.innerText) } this.tw = gsap.timeline({ onComplete: () => { targets.forEach($target => { $target.innerText = $target.getAttribute('aria-label') $target.removeAttribute('aria-label') }) } }) const shufflePerSecond = 4; const shuffleDelay = DURATION/shufflePerSecond for(let i = 0; i < shufflePerSecond; i++) { for(let $target of targets) { this.tw.add(() => { this.shuffleElementTexts($target, this.shuffle) }, shuffleDelay*i) } } } onItemLeave(e) { this.tw && this.tw.kill() const $el = e.currentTarget; let targets = []; if($el.dataset.hoverShuffle == 'children') { $el.querySelectorAll('[data-hover-shuffle-child]').forEach(child => { targets.push(child) }) } else { targets = [$el] } targets.forEach($target => { if($target.getAttribute('aria-label')) $target.innerText = $target.getAttribute('aria-label') $target.removeAttribute('aria-label') }) } shuffleElementTexts(item, shuffleFn) { if(!item.children.length && item.innerText) { // Wrap line breaks with spaces const words = item.innerText.replace('\n', ' \n ').split(' ') // Actually shuffle each "word" for(let i=0;i item[0]) words[i] = shuffleFn(chars) } // Output result item.innerText = words.join(' ') } } } ================================================ FILE: packages/landing/assets/scripts/modules/Load.js ================================================ import { module } from 'modujs'; import modularLoad from 'modularload'; export default class extends module { constructor(m) { super(m); } init() { const load = new modularLoad({ enterDelay: 0, transitions: { customTransition: {} } }); load.on('loaded', (transition, oldContainer, newContainer) => { this.call('destroy', oldContainer, 'app'); this.call('update', newContainer, 'app'); }); } } ================================================ FILE: packages/landing/assets/scripts/modules/MaskLines.js ================================================ import { module } from 'modujs'; import { whenReady } from "../utils/fonts"; import gsap from 'gsap'; import { SplitText } from 'gsap/SplitText'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { FONT } from '../config.js'; gsap.registerPlugin(SplitText); gsap.registerPlugin(ScrollTrigger); export default class extends module { constructor(m) { super(m); // Binding this.onProgressBind = this.onProgress.bind(this); // Options this.delay = parseFloat(this.getData('delay')) || 0.0; this.stagger = parseFloat(this.getData('stagger')) || 0.1; // Data this.metrics = []; } /////////////// // Lifecycle /////////////// init() { this.bindEvents(); whenReady(FONT.EAGER).then((fonts) => { this.onFontsLoaded(fonts); }); } destroy() { super.destroy(); this.unbindEvents(); // Clean up split if (this.splitObject) { this.splitObject.revert(); } } /////////////// // Events /////////////// unbindEvents() { window.removeEventListener('progressEvent', this.onProgressBind); } bindEvents() { window.addEventListener('progressEvent', this.onProgressBind); } /////////////// // Callbacks /////////////// onFontsLoaded(fonts) { this.split(); } onProgress(e) { if (!this.splitObject || !this.splitObject.lines) return; const { progress } = e.detail; this.splitObject.lines.forEach((line, i) => { if (this.metrics[i]) { const { from, to } = this.metrics[i]; const x = gsap.utils.clamp(-25, 0, gsap.utils.mapRange(from, to, -25, 0, progress)); const opacity = gsap.utils.clamp(0, 1, gsap.utils.mapRange(from, to, 0, 1, progress)); gsap.set(line, { x, opacity }); } }); } /////////////// // Methods /////////////// split() { // Use SplitText.create with autoSplit for automatic resize handling this.splitObject = SplitText.create(this.el, { type: "lines", tag: "span", autoSplit: true, onSplit: (self) => { // This callback is called every time the text is split, including on resize this.computeMetrics(self); } }); // Initial metrics computation this.computeMetrics(this.splitObject); } computeMetrics(split) { this.metrics = split.lines.map((line, i, array) => { const from = i / array.length; const to = from + (1 / array.length); return { from, to }; }); } } ================================================ FILE: packages/landing/assets/scripts/modules/Rail.js ================================================ import { module as Module } from 'modujs'; import { whenReady } from '../utils/fonts'; import gsap from 'gsap'; import { CUSTOM_EVENT, FONT, ENV } from '../config'; export default class extends Module { constructor(m) { super(m); // Binding this.onUpdateBind = this.onUpdate.bind(this); this.onResizeBind = this.onResize.bind(this); this.onToggleBind = this.onToggle.bind(this); // UI this.$container = this.$('container')[0]; this.$pattern = this.$('pattern')[0]; this.prevCount = null; // Data this.currentTranslate = 0; this.maxTranslate = this.$container.offsetWidth; this.idleVelocity = 1; this.scrollVelocity = 0.1; this.scrollLerp = 0.8; this.scrollDirection = 1; this.railDirection = this.getData('direction'); this.showFrom = parseInt(this.getData('from')) || false; this.showTo = parseInt(this.getData('to')) || false; this.data = []; this.$items = []; this.glyphs = ['🛑', '🔝', '🍸', '⛺', '😖']; } /////////////// // Lifecyle /////////////// init() { this.bindEvents(); whenReady(FONT.EAGER).then((fonts) => { this.onFontsLoaded(fonts); }); } destroy() { super.destroy(); this.unbindEvents(); this.stop(); } /////////////// // Events /////////////// bindEvents() { window.addEventListener(CUSTOM_EVENT.RESIZE_END, this.onResizeBind); window.addEventListener('toggleRail', this.onToggleBind); } unbindEvents() { window.removeEventListener(CUSTOM_EVENT.RESIZE_END, this.onResizeBind); window.removeEventListener('toggleRail', this.onToggleBind); } /////////////// // Callbacks /////////////// onResize() { this.repeatPattern(); } onFontsLoaded() { this.repeatPattern(); } onUpdate() { this.currentTranslate = (this.currentTranslate + this.idleVelocity * this.scrollDirection * this.railDirection + this.scrollVelocity * this.scrollDirection * this.railDirection) % this.maxTranslate; for (const [index, $items] of this.$items.entries()) { let translate; const right = this.data[index].left + this.data[index].width; if (this.currentTranslate < right * -1) { translate = this.maxTranslate; } else if (this.currentTranslate > this.maxTranslate - right) { translate = -this.maxTranslate; } else { translate = 0; } this.data[index].translate = translate; $items.style.transform = `translate3d(${translate}px,0,0)`; } this.$container.style.transform = `translate3d(${this.currentTranslate}px,0,0)`; } onScroll(scrollData) { const { velocity, direction } = scrollData; if (ENV.IS_MOBILE) { return; } this.scrollDirection = direction != 0 ? direction : this.scrollDirection; this.scrollDirection = this.scrollDirection * -1; this.scrollVelocity = Math.round(Math.abs(velocity)) * this.scrollLerp; } onToggle(e) { const { way } = e.detail; way === 'enter' ? this.start() : this.stop(); } /////////////// // Methods /////////////// start() { if (this.isPlaying) return; this.isPlaying = true; gsap.ticker.add(this.onUpdateBind); } stop() { if (!this.isPlaying) return; this.isPlaying = false; gsap.ticker.remove(this.onUpdateBind); } computeMetrics(reset = false) { if (reset) { this.$items = this.el.querySelectorAll('[data-rail-item]'); this.data = []; this.currentTranslate = 0; for (const [index, $items] of this.$items.entries()) { const { left, width } = $items.getBoundingClientRect(); this.data[index] = { left, width, translate: 0, }; } } else { for (const [index, $items] of this.$items.entries()) { const { left, width } = $items.getBoundingClientRect(); this.data[index].left = left - this.currentTranslate - this.data[index].translate; this.data[index].width = width; } } } repeatPattern() { if ( (this.showFrom && window.innerWidth < this.showFrom) || (this.showTo && window.innerWidth > this.showTo) ) { return; } const patternWidth = this.$pattern.offsetWidth; const repeatCount = Math.ceil(window.innerWidth / patternWidth) + 1; // Add one more for security this.maxTranslate = repeatCount * patternWidth; if (repeatCount === this.prevCount) return this.computeMetrics(); this.prevCount = repeatCount; const $currentClones = this.$container.querySelectorAll('[data-clone]'); for (const $currentClone of $currentClones) { $currentClone.remove(); } for (let index = 0; index < repeatCount - 1; index++) { const $clone = this.$pattern.cloneNode(true); // $clone.querySelector("p").appendChild(this.createGlyph(0)) $clone.querySelector('.c-rail_glyph').innerHTML = this.glyphs[index]; $clone.setAttribute('data-clone', ''); $clone.setAttribute('aria-hidden', 'true'); this.$container.appendChild($clone); } requestAnimationFrame(() => { this.computeMetrics(true); }); } } ================================================ FILE: packages/landing/assets/scripts/modules/Randomize.js ================================================ import { module } from 'modujs'; import gsap from "gsap"; import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin'; gsap.registerPlugin(ScrambleTextPlugin) export default class Ramdomize extends module { constructor(m) { super(m); // Binding this.inViewBind = this.inView.bind(this) } init() { this.bindEvents(); } destroy() { this.unbindEvents(); } bindEvents() { window.addEventListener("randomize", this.inViewBind); } unbindEvents() { window.removeEventListener("randomize", this.inViewBind); } inView(args) { const { target } = args.detail const words = [...target.querySelectorAll("p")] words.forEach((word, i) => { gsap.to(word, {opacity: 1, duration: 0, delay: 0.1 * i}) gsap.to(word, {scrambleText: word.innerHTML, duration: 1.5, delay: 0.1 * i}) }) } } ================================================ FILE: packages/landing/assets/scripts/modules/Scroll.js ================================================ import { module } from 'modujs' import { $html } from '../utils/dom' import LocomotiveScroll from '../../../../lib/index'; export default class Scroll extends module { constructor(m) { super(m); // Binding this.onScrollBind = this.onScroll.bind(this) this.changeHeaderThemeBind = this.changeHeaderTheme.bind(this) } init() { this.scroll = new LocomotiveScroll({ modularInstance: this, scrollCallback: this.onScrollBind, }) // Force scroll to top if (history.scrollRestoration) { history.scrollRestoration = 'manual' window.scrollTo(0, 0) } // Bind events this.bindEvents(); } destroy() { this.unbindEvents(); this.scroll.destroy(); } /////////////// // Events /////////////// bindEvents() { window.addEventListener("changeHeaderTheme", this.changeHeaderThemeBind); } unbindEvents() { window.removeEventListener("changeHeaderTheme", this.changeHeaderThemeBind); } /////////////// // Callbacks /////////////// onScroll({ scroll, limit, velocity, direction, progress }) { //get direction if (progress > this.lastProgress) { if (this.scrollDirection != 1) { this.scrollDirection = 1; } } else { if (this.scrollDirection != -1) { this.scrollDirection = -1; } } if (this.scrollDirection < 0) { $html.classList.add("is-scrolling-up"); } else { $html.classList.remove("is-scrolling-up"); } this.lastProgress = progress; // used by Rail.js this.call('onScroll', { velocity, direction }, 'Rail'); } /////////////// // Methods /////////////// scrollTo(params) { let { target, ...options } = params options = Object.assign({ // Defaults duration: 1, }, options) this.scroll?.scrollTo(target, options) } changeHeaderTheme(args) { const { target, way } = args.detail if (way == 'enter') { const theme = target?.parentNode?.getAttribute('data-theme') theme && $html.setAttribute('data-header-theme', theme) } } /** * Observe new scroll elements * * @param $newContainer (HTMLElement) */ addScrollElements($newContainer) { this.scroll?.addScrollElements($newContainer); } /** * Unobserve scroll elements * * @param $oldContainer (HTMLElement) */ removeScrollElements($oldContainer) { this.scroll?.removeScrollElements($oldContainer); } } ================================================ FILE: packages/landing/assets/scripts/modules.js ================================================ export { default as Load } from './modules/Load'; export { default as Rail } from './modules/Rail'; export { default as Scroll } from './modules/Scroll'; export { default as Randomize } from './modules/Randomize'; export { default as MaskLines } from './modules/MaskLines'; export { default as FadeInText } from './modules/FadeInText'; export { default as HoverShuffle } from './modules/HoverShuffle'; ================================================ FILE: packages/landing/assets/scripts/utils/dom.js ================================================ const $html = document.documentElement const $body = document.body export { $html, $body, } ================================================ FILE: packages/landing/assets/scripts/utils/fonts.js ================================================ /** * Font Faces * * Provides utilities to facilitate interactions with the CSS Font Loading API. * * Features functions to: * * - Retrieve one or more `FontFace` instances based on a font search query. * - Check if a `FontFace` instance matches a font search query. * - Eagerly load fonts that match a font search query. * - Wait until fonts that match a font search query are loaded. * * References: * * - {@link https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API} */ /** * @typedef {Object} FontFaceReference * * @property {string} family - The name used to identify the font in our CSS. * @property {string} [style] - The style used by the font in our CSS. * @property {string} [weight] - The weight used by the font in our CSS. */ const isFontLoadingAPIAvailable = ('fonts' in document); /** * Determines if the given font matches the given `FontFaceReference`. * * @param {FontFace} font - The font to inspect. * @param {FontFaceReference} criterion - The object of property values to match. * * @returns {boolean} */ function conformsToReference(font, criterion) { for (const [ key, value ] of Object.entries(criterion)) { switch (key) { case 'family': { if (trim(font[key]) !== value) { return false; } break; } case 'weight': { /** * Note concerning font weights: * Loose equality (`==`) is used to compare numeric weights, * a number (`400`) and a numeric string (`"400"`). * Comparison between numeric and keyword values is neglected. * * @link https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping */ if (font[key] != value) { return false; } break; } default: { if (font[key] !== value) { return false; } break; } } } return true; } /** * Determines if the given font matches the given font shorthand. * * @param {FontFace} font - The font to inspect. * @param {string} criterion - The font shorthand to match. * * @returns {boolean} */ function conformsToShorthand(font, criterion) { const family = trim(font.family); if (trim(family) === criterion) { return true; } if ( criterion.endsWith(trim(family)) && ( criterion.match(font.weight) || criterion.match(font.style) ) ) { return true; } return true; } /** * Determines if the given font matches any of the given criteria. * * @param {FontFace} font - The font to inspect. * @param {FontFaceReference[]} criteria - A list of objects with property values to match. * * @returns {boolean} */ function conformsToAnyReference(font, criteria) { for (const criterion of criteria) { if (conformsToReference(font, criterion)) { return true; } } return false; } /** * Returns an iterator of all `FontFace` from `document.fonts` that satisfy * the provided `FontFaceReference`. * * @param {FontFaceReference} font * * @returns {FontFace[]} */ function findManyByReference(search) { const found = []; for (const font of document.fonts) { if (conformsToReference(font, search)) { found.push(font); } } return found; } /** * Returns an iterator of all `FontFace` from `document.fonts` that satisfy * the provided font shorthand. * * @param {string} font * * @returns {FontFace[]} */ function findManyByShorthand(search) { const found = []; for (const font of document.fonts) { if (conformsToShorthand(font, search)) { found.push(font); } } return found; } /** * Returns the first `FontFace` from `document.fonts` that satisfies * the provided `FontFaceReference`. * * @param {FontFaceReference} font * * @returns {?FontFace} */ function findOneByReference(search) { for (const font of document.fonts) { if (conformsToReference(font, criterion)) { return font; } } return null; } /** * Returns the first `FontFace` from `document.fonts` that satisfies * the provided font shorthand. * * Examples: * * - "Roboto" * - "italic bold 16px Roboto" * * @param {string} font * * @returns {?FontFace} */ function findOneByShorthand(search) { for (const font of document.fonts) { if (conformsToShorthand(font, search)) { return font; } } return null; } /** * Returns a `FontFace` from `document.fonts` that satisfies * the provided query. * * @param {FontFaceReference|string} font - Either: * - a `FontFaceReference` object * - a font family name * - a font specification, for example "italic bold 16px Roboto" * * @returns {?FontFace} * * @throws {TypeError} */ function getAny(search) { if (search) { switch (typeof search) { case 'string': return findOneByShorthand(search); case 'object': return findOneByReference(search); } } throw new TypeError( 'Expected font query to be font shorthand or font reference' ); } /** * Returns an iterator of all `FontFace` from `document.fonts` that satisfy * the provided queries. * * @param {FontFaceReference|string|(FontFaceReference|string)[]} queries * * @returns {FontFace[]} * * @throws {TypeError} */ function getMany(queries) { if (!Array.isArray(queries)) { queries = [ queries ]; } const found = new Set(); queries.forEach((search) => { if (search) { switch (typeof search) { case 'string': found.add(...findManyByShorthand(search)); return; case 'object': found.add(...findManyByReference(search)); return; } } throw new TypeError( 'Expected font query to be font shorthand or font reference' ); }) return [ ...found ]; } /** * Determines if a font face is registered. * * @param {FontFace|FontFaceReference|string} search - Either: * - a `FontFace` instance * - a `FontFaceReference` object * - a font family name * - a font specification, for example "italic bold 16px Roboto" * * @returns {boolean} */ function hasAny(search) { if (search instanceof FontFace) { return document.fonts.has(search); } return getAny(search) != null; } /** * Eagerly load fonts. * * Most user agents only fetch and load fonts when they are first needed * ("lazy loaded"), which can result in a perceptible delay. * * This function will "eager load" the fonts. * * @param {(FontFace|FontFaceReference)[]} fontsToLoad - List of fonts to load. * @param {boolean} [debug] - If TRUE, log details to the console. * * @returns {Promise} */ async function loadFonts(fontsToLoad, debug = false) { if ((fontsToLoad.size ?? fontsToLoad.length) === 0) { throw new TypeError( 'Expected at least one font' ); } return await loadFontsWithAPI([ ...fontsToLoad ], debug); } /** * Eagerly load a font using `FontFaceSet` API. * * @param {FontFace} font * * @returns {Promise} */ async function loadFontFaceWithAPI(font) { return await (font.status === 'unloaded' ? font.load() : font.loaded ).then((font) => font, (err) => font) } /** * Eagerly load fonts using `FontFaceSet` API. * * @param {FontFaceReference[]} fontsToLoad * @param {boolean} [debug] * * @returns {Promise} */ async function loadFontsWithAPI(fontsToLoad, debug = false) { debug && console.group('[loadFonts:API]', fontsToLoad.length, '/', document.fonts.size); const fontsToBeLoaded = []; for (const fontToLoad of fontsToLoad) { if (fontToLoad instanceof FontFace) { if (!document.fonts.has(fontToLoad)) { document.fonts.add(fontToLoad); } fontsToBeLoaded.push( loadFontFaceWithAPI(fontToLoad) ); } else { fontsToBeLoaded.push( ...getMany(fontToLoad).map((font) => loadFontFaceWithAPI(font)) ); } } debug && console.groupEnd(); return await Promise.all(fontsToBeLoaded); } /** * Removes quotes from the the string. * * When a `@font-face` is declared, the font family is sometimes * defined in quotes which end up included in the `FontFace` instance. * * @param {string} value * * @returns {string} */ function trim(value) { return value.replace(/['"]+/g, ''); } /** * Returns a Promise that resolves with the specified fonts * when they are done loading or failed. * * @param {FontFaceReference|string|(FontFaceReference|string)[]} queries * * @returns {Promise} */ async function whenReady(queries) { const fonts = getMany(queries); // Handle each font's loaded promise, catching errors so that // Promise.all doesn't reject if one font fails return await Promise.all( fonts.map((font) => font.loaded.then( (loadedFont) => loadedFont, (error) => { // Return the font even if it failed to load // This allows the promise to resolve instead of reject console.warn(`Font failed to load: ${font.family} ${font.style} ${font.weight}`, error); return font; } ) ) ); } export { getAny, getMany, hasAny, isFontLoadingAPIAvailable, loadFonts, whenReady, } ================================================ FILE: packages/landing/assets/scripts/utils/grid-helper.js ================================================ /** * Grid Helper * * Provides a grid based on the design guidelines and is helpful for web integration. * * - `Control + g` to toggle the grid * */ /** * @typedef {Object} GridHelperReference * * @property {string} [gutterCssVar=GRID_HELPER_GUTTER_CSS_VAR] - CSS variable used to define grid gutters. * @property {string} [marginCssVar=GRID_HELPER_MARGIN_CSS_VAR] - CSS variable used to define grid margins. * @property {string} [rgbaColor=GRID_HELPER_RGBA_COLOR] - RGBA color for the grid appearence. */ const GRID_HELPER_GUTTER_CSS_VAR = '--grid-gutter'; const GRID_HELPER_MARGIN_CSS_VAR = '--grid-margin'; const GRID_HELPER_RGBA_COLOR = 'rgba(255, 0, 0, .1)'; /** * Create a grid helper * * @param {GridHelperReference} * */ function gridHelper({ gutterCssVar = GRID_HELPER_GUTTER_CSS_VAR, marginCssVar = GRID_HELPER_MARGIN_CSS_VAR, rgbaColor = GRID_HELPER_RGBA_COLOR, } = {}) { // Set grid container const $gridContainer = document.createElement('div'); document.body.append($gridContainer); // Set grid appearence setGridHelperColumns($gridContainer, rgbaColor); setGridHelperStyles($gridContainer, gutterCssVar, marginCssVar); // Set grid interactivity setGridEvents($gridContainer, rgbaColor); } /** * Set grid container styles * * @param {HTMLElement} $container - DOM Element that contains a list of generated columns * @param {string} gutterCssVar - CSS variable used to define grid gutters. * @param {string} marginCssVar - CSS variable used to define grid margins. * */ function setGridHelperStyles($container, gutterCssVar, marginCssVar) { const elStyles = $container.style; elStyles.zIndex = '10000'; elStyles.position = 'fixed'; elStyles.top = '0'; elStyles.left = '0'; elStyles.display = 'flex'; elStyles.width = '100%'; elStyles.height = '100%'; elStyles.columnGap = `var(${gutterCssVar}, 0)`; elStyles.paddingLeft = `var(${marginCssVar}, 0)`; elStyles.paddingRight = `var(${marginCssVar}, 0)`; elStyles.pointerEvents = 'none'; elStyles.visibility = 'hidden'; } /** * Set grid columns * * @param {HTMLElement} $container - DOM Element that will contain a list of generated columns * @param {string} rgbaColor - RGBA color to stylize the generated columns * */ function setGridHelperColumns($container, rgbaColor) { // Clear columns $container.innerHTML = ''; // Loop through columns const columns = Number( window.getComputedStyle($container).getPropertyValue('--grid-columns') ); let $col; for (var i = 0; i < columns; i++) { $col = document.createElement('div'); $col.style.flex = '1 1 0'; $col.style.backgroundColor = rgbaColor; $container.appendChild($col); } } /** * Set grid events * * Resize to rebuild columns * Keydown/Keyup to toggle the grid display * * @param {HTMLElement} $container - DOM Element that contains a list of generated columns * @param {string} rgbaColor - RGBA color to stylize the generated columns * */ function setGridEvents($container, rgbaColor) { // Handle resize window.addEventListener( 'resize', setGridHelperColumns($container, rgbaColor) ); // Toggle grid let ctrlDown = false; let isActive = false; document.addEventListener('keydown', (e) => { if (e.key == 'Control') { ctrlDown = true; } else { if (ctrlDown && e.key == 'g') { if (isActive) { $container.style.visibility = 'hidden'; } else { $container.style.visibility = 'visible'; } isActive = !isActive; } } }); document.addEventListener('keyup', (e) => { if (e.key == 'Control') { ctrlDown = false; } }); } export { gridHelper }; ================================================ FILE: packages/landing/assets/scripts/utils/html.js ================================================ /** * Escape HTML string * @param {string} str - string to escape * @return {string} escaped string */ const escapeHtml = str => str.replace(/[&<>'"]/g, tag => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[tag])) /** * Unescape HTML string * @param {string} str - string to unescape * @return {string} unescaped string */ const unescapeHtml = str => str.replace('&', '&') .replace('<', '<') .replace('>', '>') .replace(''', "'") .replace('"', '"') /** * Get element data attributes * @param {HTMLElement} node - node element * @return {array} node data */ const getNodeData = node => { // All attributes const attributes = node.attributes // Regex Pattern const pattern = /^data\-(.+)$/ // Output const data = {} for (let i in attributes) { if (!attributes[i]) { continue } // Attributes name (ex: data-module) let name = attributes[i].name // This happens. if (!name) { continue } let match = name.match(pattern) if (!match) { continue } // If this throws an error, you have some // serious problems in your HTML. data[match[1]] = getData(node.getAttribute(name)) } return data; } /** * Parse value to data type. * * @link https://github.com/jquery/jquery/blob/3.1.1/src/data.js * @param {string} data - value to convert * @return {mixed} value in its natural data type */ const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/ const getData = data => { if (data === 'true') { return true } if (data === 'false') { return false } if (data === 'null') { return null } // Only convert to a number if it doesn't change the string if (data === +data+'') { return +data } if (rbrace.test(data)) { return JSON.parse(data) } return data } /** * Returns an array containing all the parent nodes of the given node * @param {HTMLElement} $el - DOM Element * @return {array} parent nodes */ const getParents = $el => { // Set up a parent array let parents = [] // Push each parent element to the array for (; $el && $el !== document; $el = $el.parentNode) { parents.push($el) } // Return our parent array return parents } export { escapeHtml, unescapeHtml, getNodeData, getData, getParents, } ================================================ FILE: packages/landing/assets/scripts/utils/image.js ================================================ import { CSS_CLASS } from '../config' /** * Get an image meta data * * @param {HTMLImageElement} $img - The image element. * @return {object} The given image meta data */ const getImageMetadata = $img => ({ url: $img.src, width: $img.naturalWidth, height: $img.naturalHeight, ratio: $img.naturalWidth / $img.naturalHeight, }) /** * Load the given image. * * @param {string} url - The URI to lazy load into $el. * @param {object} options - An object of options * @return {void} */ const loadImage = (url, options = {}) => { return new Promise((resolve, reject) => { const $img = new Image() if (options.crossOrigin) { $img.crossOrigin = options.crossOrigin } const loadCallback = () => { resolve({ element: $img, ...getImageMetadata($img), }) } if($img.decode) { $img.src = url $img.decode().then(loadCallback).catch(e => { reject(e) }) } else { $img.onload = loadCallback $img.onerror = (e) => { reject(e) } $img.src = url } }) } /** * Lazy load the given image. * * @param {HTMLImageElement} $el - The image element. * @param {?string} url - The URI to lazy load into $el. * If falsey, the value of the `data-src` attribute on $el will be used as the URI. * @param {?function} callback - A function to call when the image is loaded. * @return {void} */ const LAZY_LOADED_IMAGES = [] const lazyLoadImage = async ($el, url, callback) => { let src = url ? url : $el.dataset.src let loadedImage = LAZY_LOADED_IMAGES.find(image => image.url === src) if (!loadedImage) { loadedImage = await loadImage(src) if (!loadedImage.url) { return } LAZY_LOADED_IMAGES.push(loadedImage) } if($el.src === src) { return } if ($el.tagName === 'IMG') { $el.src = loadedImage.url } else { $el.style.backgroundImage = `url(${loadedImage.url})` } requestAnimationFrame(() => { let lazyParent = $el.closest(`.${CSS_CLASS.LAZY_CONTAINER}`) if(lazyParent) { lazyParent.classList.add(CSS_CLASS.LAZY_LOADED) lazyParent.style.backgroundImage = '' } $el.classList.add(CSS_CLASS.LAZY_LOADED) callback?.() }) } export { getImageMetadata, loadImage, lazyLoadImage } ================================================ FILE: packages/landing/assets/scripts/utils/is.js ================================================ /** * Determines if the argument is object-like. * * A value is object-like if it's not `null` and has a `typeof` result of "object". * * @param {*} x - The value to be checked. * @return {boolean} */ const isObject = x => (x && typeof x === 'object') /** * Determines if the argument is a function. * * @param {*} x - The value to be checked. * @return {boolean} */ const isFunction = x => typeof x === 'function' export { isObject, isFunction } ================================================ FILE: packages/landing/assets/scripts/utils/maths.js ================================================ /** * Clamp value * @param {number} min - start value * @param {number} max - end value * @param {number} a - alpha value * @return {number} clamped value */ const clamp = (min = 0, max = 1, a) => Math.min(max, Math.max(min, a)) /** * Calculate lerp * @param {number} x - start value * @param {number} y - end value * @param {number} a - alpha value * @return {number} lerp value */ const lerp = (x, y, a) => x * (1 - a) + y * a /** * Calculate inverted lerp * @param {number} x - start value * @param {number} y - end value * @param {number} a - alpha value * @return {number} inverted lerp value */ const invlerp = (x, y, a) => clamp((a - x)/(y - x)) /** * Round number to the specified precision. * * This function is necessary because `Number.prototype.toPrecision()` * and `Number.prototype.toFixed()` * * @param {number} number - The floating point number to round. * @param {number} [precision] - The number of digits to appear after the decimal point. * @return {number} The rounded number. */ const roundNumber = (number, precision = 2) => { return Number.parseFloat(number.toPrecision(precision)); } export { clamp, lerp, invlerp, roundNumber } ================================================ FILE: packages/landing/assets/scripts/utils/tickers.js ================================================ /** * Creates a debounced function. * * A debounced function delays invoking `callback` until after * `delay` milliseconds have elapsed since the last time the * debounced function was invoked. * * Useful for behaviour that should only happen _before_ or * _after_ an event has stopped occurring. * * @template {function} T * * @param {T} callback - The function to debounce. * @param {number} delay - The number of milliseconds to wait. * @param {boolean} [immediate] - * If `true`, `callback` is invoked before `delay`. * If `false`, `callback` is invoked after `delay`. * @return {function} The new debounced function. */ const debounce = (callback, delay, immediate = false) => { let timeout = null return (...args) => { clearTimeout(timeout) const later = () => { timeout = null if (!immediate) { callback(...args) } } if (immediate && !timeout) { callback(...args) } timeout = setTimeout(later, delay) } } /** * Creates a throttled function. * * A throttled function invokes `callback` at most once per every * `delay` milliseconds. * * Useful for rate-limiting an event that occurs in quick succession. * * @template {function} T * * @param {T} callback - The function to throttle. * @param {number} delay - The number of milliseconds to wait. * @return {function} The new throttled function. */ const throttle = (callback, delay) => { let timeout = false return (...args) => { if (!timeout) { timeout = true callback(...args) setTimeout(() => { timeout = false }, delay) } } } export { debounce, throttle } ================================================ FILE: packages/landing/assets/scripts/utils/transform.js ================================================ /** * Get translate function * @param {HTMLElement} $el - DOM Element * @return {number|object} translate value */ const getTranslate = $el => { if(!window.getComputedStyle) { return } let translate const style = getComputedStyle($el) const transform = style.msTransform || style.webkitTransform || style.MozTransform || style.OTransform || style.transform const matrix3D = transform.match(/^matrix3d\((.+)\)$/) if(matrix3D) { translate = parseFloat(matrix3D[1].split(', ')[13]) } else { const matrix = transform.match(/^matrix\((.+)\)$/) translate = { x: matrix ? parseFloat(matrix[1].split(', ')[4]) : 0 y: matrix ? parseFloat(matrix[1].split(', ')[5]) : 0 } } return translate } export { transform, getTranslate } ================================================ FILE: packages/landing/assets/scripts/utils/visibility.js ================================================ /** * The `PageVisibility` interface provides support for dispatching * a custom event derived from the value of {@see document.visibilityState} * when the "visibilitychange" event is fired. * * The custom events are: * * - "visibilityhidden" representing the "hidden" visibility state. * - "visibilityvisible" representing the "visibile" visibility state. * * Example: * * ```js * import pageVisibility from './utils/visibility.js'; * * pageVisibility.enableCustomEvents(); * * document.addEventListener('visibilityhidden', () => videoElement.pause()); * ``` * * The dispatched event object is the same from "visibilitychange" * and renamed according to the visibility state. * * The `PageVisibility` interface does not manage the attachment/detachment * of event listeners on the custom event types. * * Further reading: * * - {@link https://www.w3.org/TR/page-visibility/ W3 Specification} * - {@link https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API MDN Web Docs} */ export default new class PageVisibility { /** * Checks if the "visibilitychange" event listener has been registered. * * @return {boolean} Returns `false` if the event listener is not registered, * otherwise returns `true`. */ get isEnabled() { return isVisibilityChangeObserved; } /** * Removes the "visibilitychange" event listener. * * @return {boolean} Returns `false` if the event listener was already unregistered, * otherwise returns `true`. */ disableCustomEvents() { if (isVisibilityChangeObserved) { isVisibilityChangeObserved = false; document.removeEventListener('visibilitychange', handleCustomVisibilityChange); return true; } return false; } /** * Registers the "visibilitychange" event listener. * * @return {boolean} Returns `false` if the event listener was already registered, * otherwise returns `true`. */ enableCustomEvents() { if (!isVisibilityChangeObserved) { isVisibilityChangeObserved = true; document.addEventListener('visibilitychange', handleCustomVisibilityChange); return true; } return false; } } /** * Tracks whether custom visibility event types * are available (`true`) or not (`false`). * * @type {boolean} */ let isVisibilityChangeObserved = false; /** * Dispatches a custom visibility event at the document derived * from the value of {@see document.visibilityState}. * * @listens document#visibilitychange * * @fires PageVisibility#visibilityhidden * @fires PageVisibility#visibilityvisible * * @param {Event} event * @return {void} */ function handleCustomVisibilityChange(event) { document.dispatchEvent(new CustomEvent(`visibility${document.visibilityState}`, { detail: { cause: event } })); } /** * The "visibilityhidden" eveent is fired at the document when the contents * of its tab have become hidden. * * @event PageVisibility#visibilityhidden * @type {Event} */ /** * The "visibilityvisible" eveent is fired at the document when the contents * of its tab have become visible. * * @event PageVisibility#visibilityvisible * @type {Event} */ ================================================ FILE: packages/landing/assets/scripts/vendors/.gitkeep ================================================ ================================================ FILE: packages/landing/assets/styles/_core.scss ================================================ // ========================================================================== // Core // ========================================================================== @forward "tools/maths"; @forward "tools/functions" hide $font-size, $context; @forward "tools/mixins"; @forward "tools/family"; @forward "tools/layout"; @forward "tools/widths"; @forward "settings/config.colors"; @forward "settings/config"; @forward "settings/config.breakpoints"; @forward "settings/config.eases"; @forward "settings/config.fonts"; @forward "settings/config.spacers"; @forward "settings/config.timings"; @forward "settings/config.zindexes"; @forward "settings/config.variables"; ================================================ FILE: packages/landing/assets/styles/components/_button.scss ================================================ // ========================================================================== // Components / Buttons // ========================================================================== @use "../core" as *; @use "text" as *; .c-button { --button-height: #{rem(56px)}; --button-padding: #{rem(16px)}; --button-gap: #{rem(16px)}; --button-color-text: var(--color-background); --button-color-background: var(--color-text); --button-color-stroke: var(--color-text); --button-hover-color-text: var(--color-text); --button-hover-color-background: var(--color-background); position: relative; overflow: hidden; border-radius: 4px; color: var(--button-color-text); border: 1px solid var(--button-color-stroke); background-color: var(--button-color-background); &.-stroke { --button-color-text: currentColor; --button-color-background: transparent; --button-hover-color-text: var(--color-background); --button-hover-color-background: var(--color-text); } } .c-button_wrap { &:nth-child(2) { position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px); color: var(--button-hover-color-text); background-color: var(--button-hover-color-background); clip-path: inset(10% -100% 10% 100% round 4px 4px 4px 4px); transition: clip-path t(normal) ease('power3.inOut'); } .c-button:hover & { &:nth-child(2) { clip-path: inset(0 0 0 0 round 4px 4px 4px 4px); } } } .c-button_inner { display: inline-flex; align-items: center; column-gap: var(--button-gap); min-height: var(--button-height); padding: 0 var(--button-padding); transition: transform t(normal) ease('power3.inOut'); .c-button_wrap:nth-child(1) & { .c-button:hover & { transform: translate3d(calc(-1 * var(--icon-width) - var(--button-gap)), 0, 0) } } .c-button_wrap:nth-child(2) & { position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px); flex-direction: row-reverse; transform: translate3d(calc(var(--icon-width) + var(--button-gap)), 0, 0); .c-button:hover & { transform: translate3d(0, 0, 0); } } } .c-button_label { @include text; font-size: var(--font-size-label); } ================================================ FILE: packages/landing/assets/styles/components/_cascade.scss ================================================ // ========================================================================== // Components / Cascade // ========================================================================== @use "../core" as *; .c-cascade { position: relative; } .c-cascade_text { display: flex; flex-direction: column; max-width: rem(220px); @media (max-width: $to-large) { margin-left: 50%; margin-bottom: spacingClamp('xl'); } @media (min-width: $from-large) { position: absolute; top: 0; left: calc((100% + var(--grid-gutter)) / 4 * 3); } } .c-cascade_container { display: flex; justify-content: center; margin-left: calc(-1 * var(--grid-margin)); margin-right: calc(-1 * var(--grid-margin)); overflow: hidden; } .c-cascade_inner { width: auto; } .c-cascade_item { .c-cascade_item + & { margin-left: calc(var(--item-index) * 2.35em); margin-top: calc(-1em); } &:nth-child(odd) { opacity: 0.35; } } .c-cascade_line { display: block; perspective: 100vw; transform-style: preserve-3d; } .c-cascade_word { display: inline-block; white-space: nowrap; transform: rotate3d(1, 0, 0, mapRangeClamp(var(--progress), calc(var(--item-index) / 8), calc(calc(var(--item-index) + 1) / 8), 90deg, 0deg, 0deg, 90deg)); opacity: mapRangeClamp(var(--progress), calc(var(--item-index) / 8), calc(calc(var(--item-index) + 1) / 8), 0, 1, 0, 1); transform-origin: top; clip-path: inset(0); } .c-cascade_glyph { position: absolute; bottom: 0; left: 0; font-size: var(--font-size-serif-medium); } ================================================ FILE: packages/landing/assets/styles/components/_fadeInText.scss ================================================ @use "../core" as *; .c-fadein-text_line { background: var(--background); -webkit-background-clip: text; color: transparent; } .fadeInText{ &.-blue { --color-cta-fadein: #898fe9; --color: white; } &.-white { --color-cta-fadein: #cacdff; --color: #{color(blue)}; } &.-black { --color-cta-fadein: #424242; --color: white; } } ================================================ FILE: packages/landing/assets/styles/components/_features-grid.scss ================================================ // ========================================================================== // Components / Features grid // ========================================================================== @use '../core' as *; $color-grid: 282828; .c-features-grid { @media (max-width: $to-small) { --rows: 6; --columns: 2; --cell-ratio: 180/220; --grid-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 361.5 1980'%3E%3Cpath fill='%23#{$color-grid}' fill-rule='evenodd' d='M173 1760H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v211h1v-211c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V889c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V669c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V449c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V229c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V.5c0-.3-.2-.5-.5-.5s-.5.2-.5.5V212c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } @media (min-width: $from-small) { --rows: 6; --columns: 4; --cell-ratio: 1; --grid-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 1400 4198'%3E%3Cpath fill='%23#{$color-grid}' fill-rule='evenodd' d='M342 349a8 8 0 0 0 8-8V.5a.5.5 0 0 1 1 0V341a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V.5a.5.5 0 0 1 1 0V341a8 8 0 0 0 8 8h333a8 8 0 0 0 8-8V.5c0-.276.22-.5.5-.5s.5.224.5.5V341a8 8 0 0 0 8 8h340c.28 0 .5.224.5.5s-.22.5-.5.5h-340a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h340c.28 0 .5.224.5.5s-.22.5-.5.5h-340a8 8 0 0 0-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v339.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V3858c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v339.5c0 .28-.224.5-.5.5s-.5-.22-.5-.5V3858a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v339.5c0 .28-.224.5-.5.5s-.5-.22-.5-.5V3858a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8V708a8 8 0 0 0-8-8H.5a.5.5 0 0 1 0-1H342a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8H.5a.5.5 0 0 1 0-1H342Zm17 1a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8H359Zm350.5 0a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8h-333Zm341 358a8 8 0 0 0-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8V708Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm-358 341a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333.5ZM351 3491a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V708a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } position: relative; &:before { content: ''; display: block; width: 100%; padding-top: calc( (var(--rows) + 2) / var(--columns) * (1 / (var(--cell-ratio))) * 100% ); pointer-events: none; } &:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; // opacity: 0.15; background-size: cover; background-image: var(--grid-image); pointer-events: none; } } .c-features-grid_container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; &::before, &::after { content: ''; position: absolute; left: 0; width: 100%; padding-top: calc( 1 / var(--columns) * (1 / (var(--cell-ratio))) * 100% ); background-color: var(--color-background); z-index: 1; } &::before { top: 0; -webkit-mask-image: -webkit-linear-gradient( bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) ); } &::after { bottom: 0; -webkit-mask-image: -webkit-linear-gradient( top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) ); } } .c-features-grid_inner { position: relative; display: grid; grid-template-columns: repeat(var(--columns), 1fr); grid-template-rows: repeat(var(--rows), 1fr); width: 100%; height: 100%; padding-top: calc(1 / var(--columns) * (1 / (var(--cell-ratio))) * 100%); padding-bottom: calc(1 / var(--columns) * (1 / (var(--cell-ratio))) * 100%); pointer-events: none; @media (max-width: $to-small) { grid-template-areas: 'locomotive lenis' '. scrollbar' 'normalized sticky' 'io sticky' 'direction scroll-to' '. custom-easing'; } @media (min-width: $from-small) { grid-template-areas: 'scrollbar . lenis locomotive' '. normalized . .' '. . sticky .' 'io . sticky .' '. scroll-to sticky direction' '. custom-easing sticky direction'; } } .c-features-grid_cell { position: relative; text-align: left; &.-lenis { grid-area: lenis; } &.-locomotive { grid-area: locomotive; } &.-scrollbar { grid-area: scrollbar; } &.-normalized { grid-area: normalized; } &.-sticky { grid-area: sticky; } &.-io { grid-area: io; } &.-scroll-to { grid-area: scroll-to; } &.-direction { grid-area: direction; } &.-custom-easing { grid-area: custom-easing; } @media (min-width: $from-small) { &.-io { transition: transform t(slow) ease('power3.out'); &.is-inview { transform: translate3d(100%, 0, 0); } } } } .c-features-grid_cell_ratio { display: flex; aspect-ratio: var(--cell-ratio); padding: rem(16px); pointer-events: auto; .c-features-grid_cell.-sticky & { @media (min-width: $from-small) { position: sticky; top: calc(var(--header-height) + 1px); z-index: 1; &::before { content: ''; position: absolute; background-color: var(--color-background); z-index: -1; top: -1px; left: 0px; width: calc(100% + 1px); height: calc(100% + 1px); border: 1px solid #{'#' + $color-grid}; border-radius: calc(var(--vw, 1vw) * 0.6); } } } .c-features-grid_cell.-direction & { position: relative; } @media (min-width: $from-small) { .c-features-grid_cell.-io & { z-index: 1; &::before { content: ''; position: absolute; top: -1px; left: 0px; width: calc(100% + 1px); height: calc(100% + 1px); background-color: var(--color-background); border-top: 1px solid #{'#' + $color-grid}; border-right: 1px solid #{'#' + $color-grid}; border-bottom: 1px solid #{'#' + $color-grid}; border-top-right-radius: calc(var(--vw, 1vw) * 0.6); border-bottom-right-radius: calc(var(--vw, 1vw) * 0.6); z-index: -1; } } .c-features-grid_cell.-io.is-inview & { &::before { border-left: 1px solid #{'#' + $color-grid}; border-radius: calc(var(--vw, 1vw) * 0.6); } } } } .c-features-grid_cell_inner { position: relative; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; width: 100%; padding-right: rem(30px); &.-logo { padding-right: 0; } } // Link // ======================================== .c-features-grid_link { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; @media (hover: hover) { &::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.07; background-color: currentColor; clip-path: polygon( 80% 20%, 80% 20%, 80% 20%, 20% 80%, 20% 80%, 20% 80%, 20% 80%, 80% 20% ); transition: clip-path t(fast) ease('power3.out'); } &:hover { &::before { animation-play-state: running; clip-path: polygon( 15% 5%, 85% 5%, 95% 15%, 95% 85%, 85% 95%, 15% 95%, 5% 85%, 5% 15% ); } } } } // Content // ======================================== .c-features-grid_title { display: inline-block; text-wrap: balance; .c-features-grid_cell.-direction & { transition: transform t(normal) ease('power3.out'); // html.is-scrolling-up & { // transform: scaleY(-100%); // animation: blink 100ms step-start 3; // } } .c-features-grid_cell.-link & { position: absolute; bottom: 0; left: 0; clip-path: inset(0 #{rem(-16px)}); } } .c-features_direction_title { display: flex; gap: rem(10px); } .c-features_direction_arrows { position: relative; clip-path: inset(0); span { --translate: 0%; display: block; transition: transform t('medium') ease('power3.out'); &:nth-of-type(1) { transform: translate3d(0, var(--translate), 0); } &:nth-of-type(2) { position: absolute; transform: translate3d(0, calc(var(--translate) + 100%), 0); top: 0; left: 0; } html.is-scrolling-up & { --translate: -100%; } } } .c-features-grid_title_label { display: inline-block; @media (hover: none) { &:nth-child(2) { display: none; } } @media (hover: hover) { transition: transform t(normal) ease('power4.out'); &:nth-child(2) { position: absolute; top: 0; left: 0; transform: translate3d(#{rem(-16px)}, -100%, 0); } .c-features-grid_cell:hover & { &:nth-child(1) { transform: translate3d(#{rem(16px)}, 100%, 0); } &:nth-child(2) { transform: translate3d(0, 0, 0); } } } } .c-features-grid_desc { max-width: rem(220px); @media (max-width: $to-small) { opacity: 0.6; } } .c-features-grid_index { position: absolute; top: 0; right: 0; } .c-features-grid_logo { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; @media (hover: hover) { svg { transition: transform t(normal) ease('power4.out'); .c-features-grid_cell:hover & { transform: scale3d(0.95, 0.95, 1); animation: blink 100ms step-start 3; } } } } // Icon // ======================================== .c-features-grid_icon-container { position: absolute; top: rem(10px); right: rem(10px); display: flex; overflow: hidden; } .c-features-grid_icon { padding: rem(6px); &:nth-child(2) { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transform: translate3d(-100%, 100%, 0); } html.is-first-loaded & { transition: transform t(normal) ease('power4.out'); } html.is-first-loaded .c-features-grid_cell:hover & { &:nth-child(1) { transform: translate3d(100%, -100%, 0); } &:nth-child(2) { transform: translate3d(0, 0, 0); } } } // Keyframes // ======================================== @keyframes blink { 50% { opacity: 0; } } ================================================ FILE: packages/landing/assets/styles/components/_footer.scss ================================================ @use "../core" as *; .c-footer_attributes { align-items: flex-start; div { @media (max-width: $to-medium) { white-space: nowrap; } p{ line-height: 1.25; opacity: 0; } &:nth-of-type(1){ @media (max-width: $to-medium) { grid-column: 3 / 1; } } &:nth-of-type(2){ @media (max-width: $to-medium) { grid-column: 5 / 3; margin-top: 10rem; } @media (min-width: $from-medium) { margin-top: 15rem; } } &:nth-of-type(3){ @media (max-width: $to-medium) { grid-column: 5 / 2; margin-top: 5rem; } @media (min-width: $from-medium) { margin-top: 10rem; } } &:nth-of-type(4){ @media (max-width: $to-medium) { grid-column: 3 / 1; margin-top: 5rem; } @media (min-width: $from-medium) { margin-top: 25rem; } } } } .c-footer_thanks { grid-column: 5 / 2; margin-top: calc(spacingClamp('3xl') * 1.5); // @for $i from 0 through 10 { // span { // &:nth-of-type(#{$i}) { // transform: translateX(calc(-100px + calc(100px * var(--progress)))); // } // } // } // span { // transform: translateX(calc(-100px * var(--progress))); // } @media (min-width: $from-medium) { grid-column: 5 / 3; margin-top: spacingClamp('3xl'); } } .c-footer_website { opacity: 0; transform: translate3d(0,100%,0); transition: transform t('slow') ease('power3.out'), opacity t('slow') ease('power3.out'); &.is-inview { opacity: 1; transform: translate3d(0,0,0); } } ================================================ FILE: packages/landing/assets/styles/components/_form.scss ================================================ // ========================================================================== // Components / Form // ========================================================================== @use "../core" as *; @use "sass:math"; .c-form { } .c-form_item { position: relative; margin-bottom: rem(30px); } // Label // ========================================================================== .c-form_label { display: block; margin-bottom: rem(10px); } // Input // ========================================================================== $input-icon-color: 424242; // No # .c-form_input { padding: rem(10px); border: 1px solid lightgray; background-color: color(white); &:hover { border-color: darkgray; } &:focus { border-color: dimgray; } &::placeholder { color: gray; } } // Checkbox // ========================================================================== $checkbox: rem(18px); $checkbox-icon-color: $input-icon-color; .c-form_checkboxLabel { @extend .c-form_label; position: relative; display: inline-block; margin-right: rem(10px); margin-bottom: 0; padding-left: ($checkbox + rem(10px)); cursor: pointer; &::before, &::after { position: absolute; top: 50%; left: 0; display: inline-block; margin-top: math.div(-$checkbox, 2); padding: 0; width: $checkbox; height: $checkbox; content: ""; } &::before { background-color: color(white); border: 1px solid lightgray; } &::after { border-color: transparent; background-color: transparent; background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2210.5%22%20viewBox%3D%220%200%2013%2010.5%22%20enable-background%3D%22new%200%200%2013%2010.5%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23#{$checkbox-icon-color}%22%20d%3D%22M4.8%205.8L2.4%203.3%200%205.7l4.8%204.8L13%202.4c0%200-2.4-2.4-2.4-2.4L4.8%205.8z%22%2F%3E%3C%2Fsvg%3E"); background-position: center; background-size: rem(12px); background-repeat: no-repeat; opacity: 0; } &:hover { &::before { border-color: darkgray; } } .c-form_checkbox:focus + & { &::before { border-color: dimgray; } } .c-form_checkbox:checked + & { &::after { opacity: 1; } } } .c-form_checkbox { position: absolute; width: 0; opacity: 0; } // Radio // ========================================================================== $radio-icon-color: $input-icon-color; .c-form_radioLabel { @extend .c-form_checkboxLabel; &::before, &::after { border-radius: 50%; } &::after { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2213%22%20viewBox%3D%220%200%2013%2013%22%20enable-background%3D%22new%200%200%2013%2013%22%20xml%3Aspace%3D%22preserve%22%3E%3Ccircle%20fill%3D%22%23#{$radio-icon-color}%22%20cx%3D%226.5%22%20cy%3D%226.5%22%20r%3D%226.5%22%2F%3E%3C%2Fsvg%3E"); background-size: rem(6px); } } .c-form_radio { @extend .c-form_checkbox; } // Select // ============================================================================= $select-icon: rem(40px); $select-icon-color: $input-icon-color; .c-form_select { position: relative; cursor: pointer; &::after { position: absolute; top: 0; right: 0; bottom: 0; z-index: 2; width: $select-icon; background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2211.3%22%20viewBox%3D%220%200%2013%2011.3%22%20enable-background%3D%22new%200%200%2013%2011.3%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23#{$select-icon-color}%22%20points%3D%226.5%2011.3%203.3%205.6%200%200%206.5%200%2013%200%209.8%205.6%20%22%2F%3E%3C%2Fsvg%3E"); background-position: center; background-size: rem(8px); background-repeat: no-repeat; content: ""; pointer-events: none; } } .c-form_select_input { @extend .c-form_input; position: relative; z-index: 1; padding-right: $select-icon; cursor: pointer; } // Textarea // ============================================================================= .c-form_textarea { @extend .c-form_input; min-height: rem(200px); } ================================================ FILE: packages/landing/assets/styles/components/_header.scss ================================================ // ========================================================================== // Components / Header // ========================================================================== @use "../core" as *; .c-header { [data-header-theme="blue"] & { --color-header-text: #{color(white)}; --color-header-background: #{color(blue)}; } [data-header-theme="black"] & { --color-header-text: #{color(white)}; --color-header-background: #{color(black)}; } [data-header-theme="white"] & { --color-header-text: #{color(blue)}; --color-header-background: #{color(white)}; } position: fixed; top: 0; width: 100%; display: grid; align-items: center; grid-template-columns: repeat(3, 1fr); column-gap: var(--grid-gutter); color: var(--color-header-text); background-color: var(--color-header-background); padding: var(--grid-margin); z-index: z('header'); @media (max-width: $to-small) { align-items: flex-start; } @media (min-width: $from-small) { height: var(--header-height); } html.is-first-loaded & { transition: color t(faster), background-color t(faster); } } .c-header_col { display: flex; justify-content: space-between; align-items: center; &.-center { justify-content: center; align-items: center; } @media (max-width: $to-small) { flex-direction: column; align-items: flex-start; gap: var(--grid-margin); &:last-of-type { align-items: flex-end; } } } .c-header-theme-toggler { position: absolute; top: calc(-1 * var(--header-height)); bottom: var(--header-height); left: 0; width: 100%; pointer-events: none; } ================================================ FILE: packages/landing/assets/styles/components/_heading.scss ================================================ // ========================================================================== // Components / Headings // ========================================================================== // Font sizes // ========================================================================== @use "../core" as *; :root { --font-size-serif-large: #{responsive-value(100px, 300px, $from-figma)}; --font-size-serif-medium: #{responsive-value(40px, 120px, $from-figma)}; --font-size-h1: #{responsive-value(54px, 120px, $from-figma)}; --font-size-h2: #{responsive-value(32px, 90px, $from-figma)}; --font-size-h3: #{responsive-value(14px, 28px, $from-figma)}; } // Mixins // ========================================================================== @mixin heading { font-family: ff('display'); font-weight: $font-weight-regular; line-height: 1; letter-spacing: -0.03em; } @mixin heading-serif { font-family: ff('serif'); font-weight: $font-weight-light; line-height: 0.85; letter-spacing: -0.03em; } // Styles // ========================================================================== .c-heading { @include heading; &.-h1 { font-size: var(--font-size-h1); } &.-h2 { font-size: var(--font-size-h2); } &.-h3 { font-size: var(--font-size-h3); } } .c-heading-serif { @include heading-serif; &.-large { font-size: var(--font-size-serif-large); } &.-medium { font-size: var(--font-size-serif-medium); } } ================================================ FILE: packages/landing/assets/styles/components/_hero.scss ================================================ @use "../core" as *; // Components / Hero // ========================================================================== @use "sass:math"; @use "heading" as *; .c-hero_sticky { position: sticky; bottom: 0; } .c-hero_main { display: grid; gap: var(--grid-gutter); align-items: flex-end; min-height: 100svh; padding-bottom: var(--grid-margin); position: relative; z-index: 10; &.-footer{ min-height: 100svh; } @media (max-width: $to-small) { grid-template-rows: 1fr auto 1fr; } @media (min-width: $from-small) { grid-template-columns: 1fr 1fr; } } .c-hero_heading { display: flex; flex-direction: column; row-gap: spacingClamp('md'); height: 100%; @media (max-width: $to-small) { grid-row: 2/3; } @media (min-width: $from-small) { grid-column: 2/3; grid-row: 1/2; } } .c-hero_nav { @media (max-width: $to-small) { grid-row: 3/4; } @media (min-width: $from-small) { grid-column: 1/2; grid-row: 1/2; } } .c-hero_title { margin-block: auto; .c-footer & { margin-top: auto; margin-bottom: 0; } } .c-hero_links_item { .c-hero_links_item + & { margin-top: rem(8px); } } .c-hero_description_container { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--grid-gutter); align-items: flex-end; } .c-hero_version { display: flex; justify-content: flex-end; column-gap: .5em; @media (max-width: $to-small) { position: absolute; bottom: var(--grid-margin); right: 0; } span { transition: transform t('slow') ease('power3.out'); transform: translate3d(0,100%,0); &:nth-child(1) { position: relative; top: .1em; } } &.is-inview { span { transform: translate3d(0,0,0); } } } .c-hero_line { display: block; clip-path: inset(-5% 0); } .c-hero_word { display: inline-block; transform: translate3d(0,-110%,0); html.is-first-loaded & { transition: transform t(medium) ease('power3.out'); transition-delay: calc(var(--index) * 0.1s); } &.-hero{ html.is-ready & { transform: translate3d(0,0,0); } } &.-footer{ .is-inview & { transform: translate3d(0,0,0); } } } ================================================ FILE: packages/landing/assets/styles/components/_list.scss ================================================ // ========================================================================== // Components / Perks list // ========================================================================== @use "../core" as *; .c-list { position: relative; &::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 1px; background-color: currentColor; transform-origin: top right; transform: scale3d(0,1,1); } html.is-first-loaded & { &::before { transition: transform t() ease('power3.inOut'); } &.is-inview::before { transform: scale3d(1,1,1); // transition-delay: var(--master-delay); } } @media (min-width: $from-medium) { display: grid; grid-template-columns: repeat(4, 1fr); column-gap: var(--grid-gutter); } *:focus-visible:not(input):not(textarea):not(select) { [data-theme="dark"] & { @include u-focus-visible($color: #FFFFFF, $offset: -4px); } [data-theme="white"] & { @include u-focus-visible($color: #202ded, $offset: -4px); } [data-theme="blue"] & { @include u-focus-visible($color: #FFFFFF, $offset: -4px); } @include u-focus-visible(); padding-inline: rem(8px); } } .c-list_inner { font-size: 0; @media (min-width: $from-medium) { grid-column: 2/-1; } } .c-list_item { } .c-list_item_inner { position: relative; clip-path: inset(0); padding-block: rem(12px); &::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 1px; background-color: currentColor; transform-origin: top right; transform: scale3d(0,1,1); html.is-first-loaded & { transition: transform t() ease('power3.inOut'); } html.is-first-loaded .c-list.is-inview & { transform: scale3d(1,1,1); transition-delay: calc((var(--index) + 1) * 0.08s); } } @media (max-width: $to-small) { display: flex; flex-direction: column; .-showcase & { display: grid; grid-template-columns: 1fr; gap: rem(15px); padding-right: rem(30px); } .-perks & { padding-block: rem(20px); } } @media (min-width: $from-small) { display: grid; align-items: center; } @media (min-width: $from-small) and (max-width: $to-medium) { grid-template-columns: 1fr 1fr; .c-list.-showcase & { grid-template-columns: 4fr 2fr 1fr; } } @media (min-width: $from-medium) { grid-template-columns: 1fr 2fr; .c-list.-showcase & { grid-template-columns: 4fr 2fr 1fr; } } .c-list-anchor { text-align: end; @media (max-width: $to-small) { position: absolute; right: 0; bottom: 0; .-showcase & { top: rem(10px); } } } } .c-list_title { @media (max-width: $to-small) { &.-body-regular{ --font-size-body-regular: var(--font-size-body-medium); } } @media (min-width: $from-medium) { grid-column: 1/2; } } .c-list_description { @media (max-width: $to-small) { margin-top: rem(8px); .-showcase & { display: none; } } @media (min-width: $from-medium) { grid-column: 2/-1; .c-list.-showcase & { grid-column: auto; } } } .c-list_title, .c-list_description, .c-list-anchor { opacity: 0; transform: translate3d(0, 100%, 0); html.is-first-loaded & { transition: transform t(normal) ease('power3.out'), opacity t(normal) ease('power3.out'); } html.is-first-loaded .c-list.is-inview & { opacity: 1; transform: translate3d(0,0,0); transition-delay: calc((var(--index) + 1) * 0.08s + .25s); } } ================================================ FILE: packages/landing/assets/styles/components/_perks-list.scss ================================================ // ========================================================================== // Components / Perks list // ========================================================================== .c-perks-list { position: relative; &::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 1px; background-color: currentColor; transform-origin: top right; transform: scale3d(0,1,1); } html.is-first-loaded & { &::before { transition: transform t() ease('power3.inOut'); } &.is-inview::before { transform: scale3d(1,1,1); // transition-delay: var(--master-delay); } } @media (min-width: $from-medium) { display: grid; grid-template-columns: repeat(4, 1fr); column-gap: var(--grid-gutter); } } .c-perks-list_inner { @media (min-width: $from-medium) { grid-column: 2/-1; } } .c-perks-list_item { position: relative; padding: rem(8px) 0 rem(20px); clip-path: inset(0); &::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 1px; background-color: currentColor; transform-origin: top right; transform: scale3d(0,1,1); html.is-first-loaded & { transition: transform t() ease('power3.inOut'); } html.is-first-loaded .c-perks-list.is-inview & { transform: scale3d(1,1,1); transition-delay: calc((var(--index) + 1) * 0.08s); } } @media (max-width: $to-small) { display: flex; flex-direction: column; } @media (min-width: $from-small) { display: grid; align-items: flex-start; } @media (min-width: $from-small) and (max-width: $to-medium) { grid-template-columns: 1fr 1fr; } @media (min-width: $from-medium) { grid-template-columns: 1fr 2fr; } } .c-perks-list_title { @media (min-width: $from-medium) { grid-column: 1/2; } } .c-perks-list_description { @media (max-width: $to-small) { margin-top: rem($unit-small); } @media (min-width: $from-medium) { grid-column: 2/-1; } } .c-perks-list_title, .c-perks-list_description { opacity: 0; transform: translate3d(0, 100%, 0); html.is-first-loaded & { transition: transform t(normal) ease('power3.out'), opacity t(normal) ease('power3.out'); } html.is-first-loaded .c-perks-list.is-inview & { opacity: 1; transform: translate3d(0,0,0); transition-delay: calc((var(--index) + 1) * 0.08s + .5s); } } ================================================ FILE: packages/landing/assets/styles/components/_preloader.scss ================================================ @use "../core" as *; .c-preloader { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; cursor: wait; background-color: #202ded; pointer-events: none; html.is-ready & { transition: opacity 0.6s cubic-bezier(0.215, 0.610, 0.355, 1.000), visibility 0s 0.6s cubic-bezier(0.215, 0.610, 0.355, 1.000); opacity: 0; visibility: hidden; } } ================================================ FILE: packages/landing/assets/styles/components/_rail.scss ================================================ // ========================================================================== // Components / Rail // ========================================================================== @use "../core" as *; @use "heading" as *; .c-rail { position: relative; overflow: hidden; padding: spacingClamp('md') 0; } .c-rail_inner { display: inline-flex; align-items: center; white-space: nowrap; } .c-rail_item { @include heading-serif; font-size: var(--font-size-serif-medium); padding-right: 0.5em; .c-icon { transform: translateY(-0.075em); } } .c-rail_glyph { display: inline-block; padding-left: 0.5em; width: 100px; text-align: center; @media (min-width: $from-medium) { width: 140px; } @media (min-width: $from-large) { width: 170px; } } ================================================ FILE: packages/landing/assets/styles/components/_scrollbar.scss ================================================ // ========================================================================== // Components / Scrollbar // ========================================================================== .c-scrollbar { position: absolute; right: 0; top: 0; width: 11px; height: 100vh; transform-origin: center right; transition: transform t(normal), opacity t(normal); opacity: 0; &:hover { transform: scaleX(1.45); } &:hover, .has-scroll-scrolling &, .has-scroll-dragging & { opacity: 1; } } .c-scrollbar_thumb { position: absolute; top: 0; right: 0; background-color: color(black); opacity: 0.5; width: 7px; border-radius: 10px; margin: 2px; cursor: grab; .has-scroll-dragging & { cursor: grabbing; } } ================================================ FILE: packages/landing/assets/styles/components/_section-heading.scss ================================================ // ========================================================================== // Components / Section heading // ========================================================================== @use "../core" as *; .c-section-heading { display: flex; flex-direction: column; } .c-section-heading_line { .c-section-heading_line + & { margin-top: 0.05em; } @media (max-width: $to-medium) { display: flex; flex-wrap: wrap; } &:last-child { @media (min-width: $from-medium) { display: grid; grid-template-columns: 1fr auto 1fr; column-gap: var(--grid-gutter); align-items: flex-end; } } } .c-section-heading_word { display: inline-block; width: 100%; text-align: center; @media (max-width: $to-medium) { .c-section-heading_line:last-child & { margin-bottom: rem(50px); } } @media (min-width: $from-medium) { .c-section-heading_line:last-child & { grid-column: 2/3; margin-bottom: -0.02em; } } } .c-section-heading_label { display: flex; align-items: center; @media (max-width: $to-medium) { flex: 0 0 50%; &.-left { flex-direction: column; } &.-right { flex-direction: column-reverse; } } @media (min-width: $from-medium) { justify-content: center; column-gap: .5em; grid-row: 1/2; &.-left { grid-column: 1/2; } &.-right { grid-column: 3/4; } } } ================================================ FILE: packages/landing/assets/styles/components/_sticky-heading.scss ================================================ @use "../core" as *; @use "heading" as *; // Components / Sticky heading // ========================================================================== @use "sass:math"; .c-sticky-heading { height: 300vh; @media (max-aspect-ratio: 1.25 / 1) { height: auto; padding-top: spacingClamp('2xl'); } } .c-sticky-heading_inner { position: sticky; top: 0; display: flex; align-items: flex-end; width: 100%; height: calc(var(--vh, 1vh) * 100); overflow: hidden; @media (max-aspect-ratio: 1.25 / 1) { align-items: start; height: auto; position: relative; padding-top: spacingClamp('xs'); } } .c-sticky-heading_title { @include heading-serif; font-size: calc(#{math.div(870, 1740)} * 100 * var(--vw, 1vw)); width: auto; padding-right: calc(var(--grid-margin) * 2); transform: translate3d(calc(-1 * (var(--progress) * (100% - (var(--vw, 1vw) * 100)))), 0, 0); // @media (max-width: $to-small) { // font-size: calc(#{math.div(870, 1440)} * 125 * var(--vw, 1vw)); // } @media (max-aspect-ratio: 1.25 / 1) { font-size: calc(#{math.div(870, 1440)} * 49 * var(--vw, 1vw)); transform: none; } } .c-sticky-heading_description { margin-left: calc(100vw - 13.75rem - var(--grid-margin) * 2); max-width: 13.75rem; margin-bottom: spacingClamp('sm'); @media (min-width: $from-medium) { max-width: 17.5rem; margin-left: 50vw; font-size: calc(#{math.div(870, 1740)} * 100 * var(--vw, 1vw)); margin-bottom: -.075em; } } .c-sticky_scroll { display: block; position: absolute; top: 0; height: 100%; width: 100%; } ================================================ FILE: packages/landing/assets/styles/components/_text.scss ================================================ // ========================================================================== // Components / Headings // ========================================================================== // Font sizes // ========================================================================== @use "../core" as *; :root { --font-size-body-regular: #{rem(16px)}; --font-size-body-medium: #{rem(21px)}; --font-size-label: #{rem(16px)}; --font-size-label-small: #{rem(12px)}; } // Mixins // ========================================================================== @mixin text { font-family: ff('display'); font-weight: $font-weight-medium; line-height: 1.2; letter-spacing: -0.01em; } // Styles // ========================================================================== .c-text { @include text; &.-body-regular { font-size: var(--font-size-body-regular); } &.-body-medium { font-size: var(--font-size-body-medium); } &.-label { font-size: var(--font-size-label); } &.-label-small { font-size: var(--font-size-label-small); } } ================================================ FILE: packages/landing/assets/styles/components/_tool.scss ================================================ // ========================================================================== // Components / Tool // ========================================================================== @use '../core' as *; .c-tool { position: relative; background-color: color(blue); overflow: hidden; &.-parallax { padding-bottom: spacingClamp('sm'); &:before { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: spacingClamp('xl'); background-image: linear-gradient( to bottom, hsla(236, 85%, 53%, 0) 0%, hsla(236, 85%, 53%, 0.013) 8.1%, hsla(236, 85%, 53%, 0.049) 15.5%, hsla(236, 85%, 53%, 0.104) 22.5%, hsla(236, 85%, 53%, 0.175) 29%, hsla(236, 85%, 53%, 0.259) 35.3%, hsla(236, 85%, 53%, 0.352) 41.2%, hsla(236, 85%, 53%, 0.45) 47.1%, hsla(236, 85%, 53%, 0.55) 52.9%, hsla(236, 85%, 53%, 0.648) 58.8%, hsla(236, 85%, 53%, 0.741) 64.7%, hsla(236, 85%, 53%, 0.825) 71%, hsla(236, 85%, 53%, 0.896) 77.5%, hsla(236, 85%, 53%, 0.951) 84.5%, hsla(236, 85%, 53%, 0.987) 91.9%, hsla(236, 85%, 53%, 1) 100% ); z-index: 1; } } } .c-tool_head { position: relative; display: grid; column-gap: var(--grid-gutter); z-index: 1; padding-top: 0.5rem; @media (max-width: $to-medium) { grid-template-columns: repeat(4, 1fr); } @media (min-width: $from-medium) { grid-template-columns: repeat(3, 1fr); } &::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 1px; background-color: currentColor; transform-origin: top right; transform: scale3d(0, 1, 1); html.is-first-loaded & { transition: transform t(slow) ease('power3.inOut'); } html.is-first-loaded .c-tool.is-inview & { transform: scale3d(1, 1, 1); } } @media (min-width: $from-medium) { margin-left: calc((100% + var(--grid-gutter)) / 4); } } .c-tool_index { @media (max-width: $to-medium) { grid-column: span 2; } @media (min-width: $from-medium) { grid-column: span 1; } } .c-tool_label { @media (max-width: $to-small) { grid-column: span 2; } @media (min-width: $from-small) { grid-column: span 1; } } .c-tool_description { @media (max-width: $to-small) { grid-column: 1/-1; grid-row: 3/4; max-width: rem(280px); margin-top: rem(10px); } @media (min-width: $from-small) { grid-column: span 1; grid-column-end: -1; } } .c-tool_title { grid-column: 1/-1; max-width: rem(500px); @media (max-width: $to-small) { grid-row: 2/3; margin-top: 0.8em; margin-bottom: 0.4em; } } .c-tool_playground { /* position: absolute; top: 0; left: calc(-1 * var(--grid-margin)); width: calc(100% + 2 * var(--grid-margin)); height: 100%; overflow: hidden; @media (max-width: $to-medium) { margin-top: 15%; } @media (max-width: $to-small) { margin-top: 25%; } */ .c-tool.-parallax & { @media (max-width: $to-small) { margin-top: spacingClamp('md'); } } position: relative; aspect-ratio: var(--container-ratio); width: 100%; } .c-tool_shape { position: absolute; // Inview // ======================================== .c-tool.-inview & { left: calc(var(--left, 0) / 1440 * 100%); top: calc(var(--top, 0) / (1 / (var(--container-ratio)) * 1440) * 100%); transform: translateX(-50%) translateY(-50%); &.-first { --top: 502; --left: 90; } &.-second { --top: 700; --left: 355; } &.-third { --top: 475; --left: 655; } &.-fourth { --top: 250; --left: 987; } &.-fifth { --top: 345; --left: 1310; } &.-sixth { --top: 750; --left: 1050; } &.-first { svg { transform: rotate(calc(var(--progress) * -90deg)); } } &.-fifth { svg { transform: rotate(calc(var(--progress) * 150deg)); } } } // Progress // ======================================== .c-tool.-progress & { top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%); &.-fourth { z-index: 1; } } // Parallax // ======================================== .c-tool.-parallax & { left: calc(var(--left, 0)); top: calc(var(--top, 0)); transform: translateX(-50%) translateY(-50%); &.-first { --top: 40%; --left: 16%; } &.-second { --top: 55%; --left: 34%; } &.-third { --top: 30%; --left: 53%; } &.-fourth { --top: 65%; --left: 68%; } &.-fifth { --top: 40%; --left: 86%; } } } .c-tool_shape_svg { // Inview // ======================================== .c-tool.-inview & { transform: scale3d(0, 0, 1); html.is-first-loaded & { transition: transform t(normal) ease('power3.out'); } } .c-tool.-inview .c-tool_shape.is-inview & { transform: scale3d(1, 1, 1); } // Progress // ======================================== .c-tool.-progress & { display: block; transform: translate3d( calc((0.5 + 0.5 * var(--progress)) * var(--index) * 100%), 0, 0 ); } } // Keyframes @keyframes rotation { from { transform: rotate(0deg); } to { transform: rotate(180deg); } } ================================================ FILE: packages/landing/assets/styles/critical.scss ================================================ // ========================================================================== // Critical CSS // ========================================================================== $assets-path: "assets/"; ================================================ FILE: packages/landing/assets/styles/elements/_document.scss ================================================ // ========================================================================== // Elements / Document // ========================================================================== @use "../core" as *; // // Simple page-level setup. // // 1. Includes fonts // 2. Ensure the page always fills at least the entire height of the viewport. // 3. Set the default `font-size` and `line-height` for the entire project, // sourced from our default variables. @include font-faces($font-faces, $font-dir); // [1] html { min-height: 100%; // [2] line-height: $line-height; // [3] font-family: ff('display'); color: $font-color; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @media (max-width: $to-small) { font-size: $font-size - 2px; } @media (min-width: $from-small) and (max-width: $to-medium) { font-size: $font-size - 2px; } @media (min-width: $from-medium) and (max-width: $to-large) { font-size: $font-size - 1px; } @media (min-width: $from-large) and (max-width: $to-huge) { font-size: $font-size; // [1] } @media (min-width: $from-huge) and (max-width: $to-gigantic) { font-size: $font-size + 1px; } @media (min-width: $from-gigantic) and (max-width: $to-colossal) { font-size: $font-size + 2px; } @media (min-width: $from-colossal) { font-size: $font-size + 4px; } &.is-loading { cursor: wait; } } body { } ::selection { background-color: $color-selection-background; color: $color-selection-text; text-shadow: none; } a { color: inherit; } *:focus-visible:not(input):not(textarea):not(select) { [data-theme="dark"] & { @include u-focus-visible($color: #FFFFFF, $offset: 2px); } [data-theme="white"] & { @include u-focus-visible($color: #202ded, $offset: 2px); } [data-theme="blue"] & { @include u-focus-visible($color: #FFFFFF, $offset: 2px); } @include u-focus-visible(); } input, select, textarea { &:focus-visible { @include u-focus-visible($color: var(--color-theme-dark), $offset: 0px); } } ================================================ FILE: packages/landing/assets/styles/generic/_button.scss ================================================ // ========================================================================== // Generic / Buttons // ========================================================================== @use "../core" as *; // 1. Allow us to style box model properties. // 2. Fixes odd inner spacing in IE7. // 3. Reset/normalize some styles. // 4. Line different sized buttons up a little nicer. // 5. Make buttons inherit font styles (often necessary when styling `input`s as buttons). // 6. Force all button-styled elements to appear clickable. button, .c-button { @include u-hocus { text-decoration: none; } display: inline-block; // [1] overflow: visible; // [2] margin: 0; // [3] padding: 0; outline: 0; border: 0; background: none transparent; color: inherit; vertical-align: middle; // [4] text-align: center; // [3] text-decoration: none; text-transform: none; font: inherit; // [5] line-height: normal; cursor: pointer; // [6] user-select: none; } ================================================ FILE: packages/landing/assets/styles/generic/_form.scss ================================================ // ========================================================================== // Generic / Forms // ========================================================================== input, select, textarea { display: block; margin: 0; padding: 0; width: 100%; outline: 0; border: 0; border-radius: 0; background: none transparent; color: inherit; font: inherit; line-height: normal; appearance: none; } select { text-transform: none; &::-ms-expand { display: none; } &::-ms-value { background: none; color: inherit; } // // Remove Firefox :focus dotted outline, breaks color inherit // // &:-moz-focusring { // // color: transparent; // // text-shadow: 0 0 0 #000000; // Text :focus color // // } } textarea { overflow: auto; resize: vertical; } ================================================ FILE: packages/landing/assets/styles/generic/_generic.scss ================================================ // ========================================================================== // Generic // ========================================================================== @use "../core" as *; html { box-sizing: border-box; } // Add the correct display in IE 10-. // 1. Add the correct display in IE. template, // [1] [hidden] { display: none; } *, :before, :after { box-sizing: inherit; } address { font-style: inherit; } dfn, cite, em, i { font-style: italic; } b, strong { font-weight: $font-weight-medium; } a { text-decoration: none; svg { pointer-events: none; } } ul, ol { margin: 0; padding: 0; list-style: none; } p, figure { margin: 0; padding: 0; } h1, h2, h3, h4, h5, h6 { margin: 0; } // 1. Single taps should be dispatched immediately on clickable elements a, area, button, input, label, select, textarea, [tabindex] { -ms-touch-action: manipulation; // [1] touch-action: manipulation; } [hreflang] > abbr[title] { text-decoration: none; } table { border-spacing: 0; border-collapse: collapse; } hr { display: block; margin: 1em 0; padding: 0; height: 1px; border: 0; border-top: 1px solid #CCCCCC; } ================================================ FILE: packages/landing/assets/styles/generic/_media.scss ================================================ // ========================================================================== // Generic / Media // ========================================================================== @use "../core" as *; // 1. Setting `vertical-align` removes the whitespace that appears under `img` // elements when they are dropped into a page as-is. Safer alternative to // using `display: block;`. audio, canvas, iframe, img, svg, video { vertical-align: middle; // [1] } // Add the correct display in iOS 4-7. audio:not([controls]) { display: none; height: 0; } // 2. Fluid media for responsive purposes. img, svg { // max-width: 100%; // [2] height: auto; // 4. If a `width` and/or `height` attribute have been explicitly defined, // let’s not make the image fluid. &[width], // [4] &[height] { // [4] max-width: none; } } // 4. Offset `alt` text from surrounding copy. img { font-style: italic; // [4] } // 5. SVG elements should fallback to their surrounding text color. svg { fill: currentColor; // [5] } ================================================ FILE: packages/landing/assets/styles/main.scss ================================================ // ========================================================================== // Main // ========================================================================== // @use "sass:math"; @use "../../../../node_modules/normalize.css/normalize"; @use "../../../../node_modules/lenis/dist/lenis.css"; // Generic // ========================================================================== @use "generic/generic"; @use "generic/media"; @use "generic/button" as generic-button; // Vendors // ========================================================================== // Elements // ========================================================================== @use "elements/document"; // Objects // ========================================================================== @use "objects/container"; @use "objects/icons"; @use "objects/grid"; // Components // ========================================================================== @use "components/heading"; @use "components/text"; @use "components/button"; @use "components/form"; @use "components/header"; @use "components/hero"; @use "components/rail"; @use "components/cascade"; @use "components/section-heading"; @use "components/tool"; @use "components/features-grid"; @use "components/sticky-heading"; @use "components/list"; @use "components/footer"; @use "components/fadeInText"; @use "components/preloader"; // Utilities // ========================================================================== @use "utilities/grid-column"; @use "utilities/theme"; @use "utilities/helpers"; @use "utilities/spacing"; ================================================ FILE: packages/landing/assets/styles/objects/_container.scss ================================================ // ========================================================================== // Objects / Container // ========================================================================== @use "../core" as *; // Page-level constraining and wrapping elements. // // > In programming languages the word *container* is generally used for structures // that can contain more than one element. // > A *wrapper* instead is something that wraps around a single object to provide // more functionalities and interfaces to it. // @link http://stackoverflow.com/a/13202141/140357 .o-container { margin-right: auto; margin-left: auto; padding-left: var(--grid-margin); padding-right: var(--grid-margin); } ================================================ FILE: packages/landing/assets/styles/objects/_grid.scss ================================================ // ========================================================================== // Grid helper // ========================================================================== @use "../core" as *; // Help: https://css-tricks.com/snippets/css/complete-guide-grid/ // /** * Usage: * * ```html *
*
*

Hello

*
*
*

Hello

*
*
* ``` */ .o-grid { display: grid; width: 100%; &:is(ul, ol) { margin: 0; padding: 0; list-style: none; } // ========================================================================== // Cols // ========================================================================== // Responsive grid columns based on `--grid-columns` &.-cols { grid-template-columns: repeat(var(--grid-columns), 1fr); } &.-col-#{$base-column-nb} { grid-template-columns: repeat(#{$base-column-nb}, 1fr); } &.-col-2 { grid-template-columns: repeat(2, 1fr); } &.-col-#{$base-column-nb}\@from-medium { @media (min-width: $from-medium) { grid-template-columns: repeat(#{$base-column-nb}, 1fr); } } // ========================================================================== // Gutters // ========================================================================== // Gutters rows and columns &.-gutters { gap: var(--grid-gutter); column-gap: var(--grid-gutter); } // ========================================================================== // Modifiers // ========================================================================== &.-full-height { height: 100%; } // ========================================================================== // Aligns // ========================================================================== // ========================================================================== // Items inside cells // &.-top-items { align-items: start; } &.-right-items { justify-items: end; } &.-bottom-items { align-items: end; } &.-left-items { justify-items: start; } &.-center-items { align-items: center; justify-items: center; } &.-center-items-x { justify-items: center; } &.-center-items-y { align-items: center; } &.-stretch-items { align-items: stretch; justify-items: stretch; } // ========================================================================== // Cells // &.-top-cells { align-content: start; } &.-right-cells { justify-content: end; } &.-bottom-cells { align-content: end; } &.-left-cells { justify-content: start; } &.-center-cells { align-content: center; justify-content: center; } &.-center-cells-x { justify-content: center; } &.-center-cells-y { align-content: center; } &.-stretch-cells { align-content: stretch; justify-content: stretch; } &.-space-around-cells { align-content: space-around; justify-content: space-around; } &.-space-around-cells-x { justify-content: space-around; } &.-space-around-cells-y { align-content: space-around; } &.-space-between-cells { justify-content: space-between; align-content: space-between; } &.-space-between-cells-x { justify-content: space-between; } &.-space-between-cells-y { align-content: space-between; } &.-space-evenly-cells { justify-content: space-evenly; align-content: space-evenly; } &.-space-evenly-cells-x { justify-content: space-evenly; } &.-space-evenly-cells-y { align-content: space-evenly; } } // ========================================================================== // Grid item // ========================================================================== // By default, a grid item takes full width of its parent. // .o-grid_item { grid-column-start: var(--gc-start, 1); grid-column-end: var(--gc-end, -1); &.-align-end { align-self: end; } } ================================================ FILE: packages/landing/assets/styles/objects/_icons.scss ================================================ // ========================================================================== // Objects / SVG Icons // ========================================================================== // Markup // // 1. If icon is accessible and has a title // 2. If icon is decorative // // // // [1] // Locomotive // // // // // Global styles for icones // ========================================================================== @use "../core" as *; // Default metrics :root { --icon-width: #{rem(24px)}; --icon-ratio: 1; } .o-icon { display: inline-block; vertical-align: middle; svg { display: block; width: var(--icon-width); height: calc(var(--icon-width) * (1 / (var(--icon-ratio)))); } } // SVG sizes // ========================================================================== .svg-logo-lenis { --icon-width: #{rem(67px)}; --icon-ratio: 67/72; } .svg-logo-locomotive { --icon-width: #{rem(79px)}; --icon-ratio: 79/35; } .svg-arrow-external { --icon-width: #{rem(12px)}; } .svg-inview-01, .svg-inview-02, .svg-inview-03, .svg-inview-04, .svg-inview-05, .svg-inview-06 { --icon-width: #{vw(24)}; } .svg-progress-01, .svg-progress-02, .svg-progress-03, .svg-progress-04, .svg-progress-05 { --icon-width: #{vw(24)}; --icon-ratio: 224/350; } .svg-parallax-01, .svg-parallax-02, .svg-parallax-03, .svg-parallax-04, .svg-parallax-05 { --icon-width: #{vw(24)}; } ================================================ FILE: packages/landing/assets/styles/objects/_layout.scss ================================================ // ========================================================================== // Objects / Layout // ========================================================================== //// /// Grid-like layout system. /// /// The layout object provides us with a column-style layout system. This file /// contains the basic structural elements, but classes should be complemented /// with width utilities, for example: /// /// @example ///
///
///
///
///
///
///
///
/// /// We can also manipulate entire layout systems by adding a series of modifiers /// to the `.o-layout` block. For example: /// /// @example ///
/// /// This will reverse the displayed order of the system so that it runs in the /// opposite order to our source, effectively flipping the system over. /// /// @example ///
/// /// This will cause the system to fill up from either the centre or the right /// hand side. Default behaviour is to fill up the layout system from the left. /// /// @requires tools/layout /// @link https://github.com/inuitcss/inuitcss/blob/0420ba8/objects/_objects.layout.scss //// .o-layout { @include o-layout; // Gutter modifiers &.-gutter { margin-left: rem(-$unit); } &.-gutter-small { margin-left: rem(-$unit-small); } // Horizontal aligment modifiers &.-center { text-align: center; } &.-right { text-align: right; } &.-reverse { direction: rtl; &.-flex { flex-direction: row-reverse; } } &.-flex { display: flex; &.-top { align-items: flex-start; } &.-middle { align-items: center; } &.-bottom { align-items: flex-end; } } &.-stretch { align-items: stretch; } } .o-layout_item { @include o-layout_item; // Gutter modifiers .o-layout.-gutter > & { padding-left: rem($unit); } .o-layout.-gutter-small > & { padding-left: rem($unit-small); } // Vertical alignment modifiers .o-layout.-middle > & { vertical-align: middle; } .o-layout.-bottom > & { vertical-align: bottom; } // Horizontal aligment modifiers .o-layout.-center > &, .o-layout.-right > &, .o-layout.-reverse > & { text-align: left; } .o-layout.-reverse > & { direction: ltr; } } ================================================ FILE: packages/landing/assets/styles/objects/_ratio.scss ================================================ // ========================================================================== // Objects / Ratio // ========================================================================== // Create ratio-bound content blocks, to keep media (e.g. images, videos) in // their correct aspect ratios. // // http://alistapart.com/article/creating-intrinsic-ratios-for-video // // 1. Default cropping is a 1:1 ratio (i.e. a perfect square). .o-ratio { position: relative; display: block; overflow: hidden; &:before { display: block; padding-bottom: 100%; // [1] width: 100%; content: ""; } } .o-ratio_content, .o-ratio > img, .o-ratio > iframe, .o-ratio > embed, .o-ratio > object { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; // height: 100%; } ================================================ FILE: packages/landing/assets/styles/objects/_table.scss ================================================ // ========================================================================== // Objects / Tables // ========================================================================== .o-table { width: 100%; // Force all cells within a table to occupy the same width as each other. // // @link https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout#Values &.-fixed { table-layout: fixed; } } ================================================ FILE: packages/landing/assets/styles/settings/_config.breakpoints.scss ================================================ // ========================================================================== // Settings / Config / Breakpoints // ========================================================================== @use "sass:map"; // Breakpoints // ========================================================================== $breakpoints: ( "tiny": 500px, "small": 700px, "medium": 1000px, "large": 1200px, "big": 1400px, "figma": 1440px, "huge": 1600px, "enormous": 1800px, "gigantic": 2000px, "colossal": 2400px ); // Functions // ========================================================================== // Creates a min-width or max-width media query expression. // // @param {string} $breakpoint The breakpoint. // @param {string} $type Either "min" or "max". // @return {string} @function mq($breakpoint, $type: "min") { @if not map.has-key($breakpoints, $breakpoint) { @warn "Unknown media query breakpoint: `#{$breakpoint}`"; } $value: map.get($breakpoints, $breakpoint); @if ($type == "min") { @return "(min-width: #{$value})"; } @if ($type == "max") { @return "(max-width: #{$value - 1px})"; } @error "Unknown media query type: #{$type}"; } // Creates a min-width media query expression. // // @param {string} $breakpoint The breakpoint. // @return {string} @function mq-min($breakpoint) { @return mq($breakpoint, "min"); } // Creates a max-width media query expression. // // @param {string} $breakpoint The breakpoint. // @return {string} @function mq-max($breakpoint) { @return mq($breakpoint, "max"); } // Creates a min-width and max-width media query expression. // // @param {string} $from The min-width breakpoint. // @param {string} $until The max-width breakpoint. // @return {string} @function mq-between($breakpointMin, $breakpointMax) { @return "#{mq-min($breakpointMin)} and #{mq-max($breakpointMax)}"; } // Legacy // ========================================================================== $from-tiny: map.get($breakpoints, "tiny") !default; $to-tiny: map.get($breakpoints, "tiny") - 1 !default; $from-small: map.get($breakpoints, "small") !default; $to-small: map.get($breakpoints, "small") - 1 !default; $from-medium: map.get($breakpoints, "medium") !default; $to-medium: map.get($breakpoints, "medium") - 1 !default; $from-large: map.get($breakpoints, "large") !default; $to-large: map.get($breakpoints, "large") - 1 !default; $from-big: map.get($breakpoints, "big") !default; $to-big: map.get($breakpoints, "big") - 1 !default; $from-figma: map.get($breakpoints, "figma") !default; $to-figma: map.get($breakpoints, "figma") - 1 !default; $from-huge: map.get($breakpoints, "huge") !default; $to-huge: map.get($breakpoints, "huge") - 1 !default; $from-enormous: map.get($breakpoints, "enormous") !default; $to-enormous: map.get($breakpoints, "enormous") - 1 !default; $from-gigantic: map.get($breakpoints, "gigantic") !default; $to-gigantic: map.get($breakpoints, "gigantic") - 1 !default; $from-colossal: map.get($breakpoints, "colossal") !default; $to-colossal: map.get($breakpoints, "colossal") - 1 !default; ================================================ FILE: packages/landing/assets/styles/settings/_config.colors.scss ================================================ // ========================================================================== // Settings / Config / Colors // ========================================================================== @use "sass:map"; @use "sass:color"; // Palette // ========================================================================== $colors: ( 'blue': #202ded, 'white': #FFFFFF, 'black': #000000, 'red': #F4574D, ); // Function // ========================================================================== // Returns color code. // // ```scss // .c-box { // color: color(blue); // } // ``` // // @param {string} $key - The color key in $colors. // @param {number} $alpha - The alpha for the color value. // @return {color} @function color($key, $alpha: 1) { $key: #{$key}; // Force string conversion @if not map.has-key($colors, $key) { @error "Unknown '#{$key}' in $colors."; } @if($alpha < 0 or $alpha > 1) { @error "Alpha '#{$alpha}' must be in range [0, 1]."; } $color: map.get($colors, $key); @return rgba($color, $alpha); } // Specifics // ========================================================================== // Link $color-link: color(blue); $color-link-focus: color(blue); $color-link-hover: color.adjust(color(blue), $lightness: -10%); // Selection $color-selection-text: color(black); $color-selection-background: color(white); // Socials $color-facebook: #3B5998; $color-instagram: #E1306C; $color-youtube: #CD201F; $color-twitter: #1DA1F2; ================================================ FILE: packages/landing/assets/styles/settings/_config.eases.scss ================================================ // ========================================================================== // Settings / Config / Eases // ========================================================================== @use "sass:map"; // Eases // ========================================================================== $eases: ( // Power 1 "power1.in": cubic-bezier(0.550, 0.085, 0.680, 0.530), "power1.out": cubic-bezier(0.250, 0.460, 0.450, 0.940), "power1.inOut": cubic-bezier(0.455, 0.030, 0.515, 0.955), // Power 2 "power2.in": cubic-bezier(0.550, 0.055, 0.675, 0.190), "power2.out": cubic-bezier(0.215, 0.610, 0.355, 1.000), "power2.inOut": cubic-bezier(0.645, 0.045, 0.355, 1.000), // Power 3 "power3.in": cubic-bezier(0.895, 0.030, 0.685, 0.220), "power3.out": cubic-bezier(0.165, 0.840, 0.440, 1.000), "power3.inOut": cubic-bezier(0.770, 0.000, 0.175, 1.000), // Power 4 "power4.in": cubic-bezier(0.755, 0.050, 0.855, 0.060), "power4.out": cubic-bezier(0.230, 1.000, 0.320, 1.000), "power4.inOut": cubic-bezier(0.860, 0.000, 0.070, 1.000), // Expo "expo.in": cubic-bezier(0.950, 0.050, 0.795, 0.035), "expo.out": cubic-bezier(0.190, 1.000, 0.220, 1.000), "expo.inOut": cubic-bezier(1.000, 0.000, 0.000, 1.000), // Back "back.in": cubic-bezier(0.600, -0.280, 0.735, 0.045), "back.out": cubic-bezier(0.175, 00.885, 0.320, 1.275), "back.inOut": cubic-bezier(0.680, -0.550, 0.265, 1.550), // Sine "sine.in": cubic-bezier(0.470, 0.000, 0.745, 0.715), "sine.out": cubic-bezier(0.390, 0.575, 0.565, 1.000), "sine.inOut": cubic-bezier(0.445, 0.050, 0.550, 0.950), // Circ "circ.in": cubic-bezier(0.600, 0.040, 0.980, 0.335), "circ.out": cubic-bezier(0.075, 0.820, 0.165, 1.000), "circ.inOut": cubic-bezier(0.785, 0.135, 0.150, 0.860), // Misc "bounce": cubic-bezier(0.17, 0.67, 0.3, 1.33), "slow.out": cubic-bezier(.04,1.15,0.4,.99), "smooth": cubic-bezier(0.380, 0.005, 0.215, 1), ); // Default value for ease() $ease-default: "power2.out" !default; // Function // ========================================================================== // Returns ease curve. // // ```scss // .c-box { // transition-timing-function: ease("power2.out"); // } // ``` // // @param {string} $key - The ease key in $eases. // @return {easing-function} @function ease($key: $ease-default) { @if not map.has-key($eases, $key) { @error "Unknown '#{$key}' in $eases."; } @return map.get($eases, $key); } ================================================ FILE: packages/landing/assets/styles/settings/_config.fonts.scss ================================================ // ========================================================================== // Settings / Config / Breakpoints // ========================================================================== @use "sass:list"; @use "sass:map"; @use "sass:meta"; // Font fallbacks (retrieved from systemfontstack.com on 2022-05-31) // ========================================================================== $font-fallback-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif; $font-fallback-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; $font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; // Typefaces // ========================================================================== // List of custom font faces as tuples. // // ``` // // ``` $font-faces: ( ("Helvetica Now Display", "HelveticaNowDisplay-Medium", 500, normal), ("Helvetica Now Display", "HelveticaNowDisplay-Regular", 400, normal), ("PP Locomotive New", "PPLocomotiveNew-Light", 300, normal) ); // Map of font families. // // ``` // : (, ) // ``` $font-families: ( display: list.join("Helvetica Now Display", $font-fallback-sans, $separator: comma), serif: list.join("PP Locomotive New", $font-fallback-sans, $separator: comma), ); // Font directory $font-dir: "../fonts/"; // Functions // ========================================================================== // Imports the custom font. // // The mixin expects font files to be woff and woff2. // // @param {List} $webfont - A custom font to import, as a tuple: // ` `. // @param {String} $dir - The webfont directory path. // @output The `@font-face` at-rule specifying the custom font. @mixin font-face($webfont, $dir) { @font-face { font-display: swap; font-family: list.nth($webfont, 1); src: url("#{$dir}#{list.nth($webfont, 2)}.woff2") format("woff2"), url("#{$dir}#{list.nth($webfont, 2)}.woff") format("woff"); font-weight: #{list.nth($webfont, 3)}; font-style: #{list.nth($webfont, 4)}; } } // Imports the list of custom fonts. // // @require {mixin} font-face // // @param {List} $webfonts - List of custom fonts to import. // See `font-face` mixin for details. // @param {String} $dir - The webfont directory path. // @output The `@font-face` at-rules specifying the custom fonts. @mixin font-faces($webfonts, $dir) { @if (list.length($webfonts) > 0) { @if (meta.type-of(list.nth($webfonts, 1)) == "list") { @each $webfont in $webfonts { @include font-face($webfont, $dir); } } @else { @include font-face($webfonts, $dir); } } } // Retrieves the font family stack for the given font ID. // // @require {variable} $font-families - See settings directory. // // @param {String} $font-family - The custom font ID. // @throws Error if the $font-family does not exist. // @return {List} The font stack. @function ff($font-family) { @if not map.has-key($font-families, $font-family) { @error "No font-family found in $font-families map for `#{$font-family}`."; } $value: map.get($font-families, $font-family); @return $value; } ================================================ FILE: packages/landing/assets/styles/settings/_config.scss ================================================ // ========================================================================== // Settings / Config // ========================================================================== @use "sass:math"; @use "config.colors" as *; @use "config.timings" as *; @use "config.eases" as *; // Context // ============================================================================= // The current stylesheet context. Available values: frontend, editor. $context: frontend !default; // Path is relative to the stylesheets directory. $assets-path: "../" !default; // Typography // ============================================================================= // Base $font-size: 16px; $line-height: math.div(24px, $font-size); $font-color: color(black); // Weights $font-weight-light: 300; $font-weight-regular: 400; $font-weight-medium: 500; // Transition defaults // ============================================================================= $speed: t(normal); $easing: ease("power3.out"); // Spacing Units // ============================================================================= $unit: 60px; $unit-small: 20px; // Container // ========================================================================== $padding: $unit; // Grid // ========================================================================== $base-column-nb: 4; ================================================ FILE: packages/landing/assets/styles/settings/_config.spacers.scss ================================================ // ========================================================================== // Settings / Config / Spacers // ========================================================================== @use "sass:map"; @use "../tools/functions" as *; :root { --spacing-2xs-mobile: 6; --spacing-2xs-desktop: 10; --spacing-xs-mobile: 14; --spacing-xs-desktop: 16; --spacing-sm-mobile: 28; --spacing-sm-desktop: 32; --spacing-md-mobile: 42; --spacing-md-desktop: 56; --spacing-lg-mobile: 72; --spacing-lg-desktop: 96; --spacing-xl-mobile: 90; --spacing-xl-desktop: 120; --spacing-2xl-mobile: 96; --spacing-2xl-desktop: 160; --spacing-3xl-mobile: 112; --spacing-3xl-desktop: 224; } // Spacers // ========================================================================== $spacers: ( 'gutter': var(--grid-gutter), '2xs': #{spacingClamp('2xs')}, 'xs': #{spacingClamp('xs')}, 'sm': #{spacingClamp('sm')}, 'md': #{spacingClamp('md')}, 'lg': #{spacingClamp('lg')}, 'xl': #{spacingClamp('xl')}, '2xl': #{spacingClamp('2xl')}, '3xl': #{spacingClamp('3xl')}, ); // Function // ========================================================================== // Returns spacer. // // ```scss // .c-box { // margin-top: spacer(gutter); // } // ``` // // @param {string} $key - The spacer key in $spacers. // @param {number} $multiplier - The multiplier of the spacer value. // @return {size} @function spacer($spacer: $spacer-default, $multiplier: 1) { @if not map.has-key($spacers, $spacer) { @error "Unknown master spacer: #{$spacer}"; } $index: map.get($spacers, $spacer); @return calc(#{$index} * #{$multiplier}); } ================================================ FILE: packages/landing/assets/styles/settings/_config.timings.scss ================================================ // ========================================================================== // Settings / Config / Timings // ========================================================================== @use "sass:map"; // Timings // ========================================================================== $timings: ( fastest: 0.1s, faster: 0.15s, fast: 0.25s, normal: 0.5s, medium: 0.6s, slow: 0.75s, slower: 1s, slowest: 2s, ); // Default timing for t() $timing-default: "normal" !default; // Function // ========================================================================== // Returns timing. // // ```scss // .c-box { // transition-duration: t(slow); // } // ``` // // @param {string} $key - The timing key in $timings. // @return {duration} @function t($key: $timing-default) { @if not map.has-key($timings, $key) { @error "Unknown '#{$key}' in $timings."; } @return map.get($timings, $key); } ================================================ FILE: packages/landing/assets/styles/settings/_config.variables.scss ================================================ // ========================================================================== // Settings / Config / CSS VARS // ========================================================================== @use '../tools/functions' as *; @use 'config.breakpoints' as *; :root { // Grid --grid-columns: 4; --grid-gutter: #{rem(10px)}; --grid-margin: #{rem(10px)}; // Container --container-width: calc(100% - 2 * var(--grid-margin)); @media (max-width: $to-small) { --header-height: #{rem(34px)}; } @media (min-width: $from-small) { --grid-gutter: #{rem(16px)}; --grid-margin: #{rem(20px)}; --header-height: #{rem(60px)}; } } ================================================ FILE: packages/landing/assets/styles/settings/_config.zindexes.scss ================================================ // ========================================================================== // Settings / Config / Z-indexes // ========================================================================== @use "sass:map"; // Timings // ========================================================================== $z-indexes: ( "header": 200, "above": 1, "default": 0, "below": -1 ); // Default z-index for z() $z-index-default: "above" !default; // Function // ========================================================================== // Retrieves the z-index from the {@see $layers master list}. // // @link on http://css-tricks.com/handling-z-index/ // // @param {string} $layer The name of the z-index. // @param {number} $modifier A positive or negative modifier to apply // to the returned z-index value. // @throw Error if the $layer does not exist. // @throw Warning if the $modifier might overlap another master z-index. // @return {number} The computed z-index of $layer and $modifier. @function z($layer: $z-index-default, $modifier: 0) { @if not map.has-key($z-indexes, $layer) { @error "Unknown master z-index layer: #{$layer}"; } @if ($modifier >= 50 or $modifier <= -50) { @warn "Modifier may overlap the another master z-index layer: #{$modifier}"; } $index: map.get($z-indexes, $layer); @return $index + $modifier; } ================================================ FILE: packages/landing/assets/styles/tools/_family.scss ================================================ // ========================================================================== // Tools / Family // ========================================================================== @use "sass:math"; // DOCS : https://lukyvj.github.io/family.scss/ // // Select all children from the first to `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin first($num) { @if $num == 1 { &:first-child { @content; } } @else { &:nth-child(-n + #{$num}) { @content; } } } // Select all children from the last to `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin last($num) { &:nth-last-child(-n + #{$num}) { @content; } } // Select all children after the first to `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin after-first($num) { &:nth-child(n + #{$num + 1}) { @content; } } // Select all children before `$num` from the last. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin from-end($num) { &:nth-last-child(#{$num}) { @content; } } // Select all children between `$first` and `$last`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin between($first, $last) { &:nth-child(n + #{$first}):nth-child(-n + #{$last}) { @content; } } // Select all even children between `$first` and `$last`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin even-between($first, $last) { &:nth-child(even):nth-child(n + #{$first}):nth-child(-n + #{$last}) { @content; } } // Select all odd children between `$first` and `$last`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin odd-between($first, $last) { &:nth-child(odd):nth-child(n + #{$first}):nth-child(-n + #{$last}) { @content; } } // Select all `$num` children between `$first` and `$last`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin n-between($num, $first, $last) { &:nth-child(#{$num}n):nth-child(n + #{$first}):nth-child(-n + #{$last}) { @content; } } // Select all children but `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin all-but($num) { &:not(:nth-child(#{$num})) { @content; } } // Select children each `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child // @alias every @mixin each($num) { &:nth-child(#{$num}n) { @content; } } // Select children each `$num`. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin every($num) { &:nth-child(#{$num}n) { @content; } } // Select the `$num` child from the start and the `$num` child from the last. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin from-first-last($num) { &:nth-child(#{$num}), &:nth-last-child(#{$num}) { @content; } } // Select the item in the middle of `$num` child. Only works with odd number // chain. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin middle($num) { &:nth-child(#{round(math.div($num, 2))}) { @content; } } // Select all children between the `$num` first and the `$num` last. // @group with-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - id of the child @mixin all-but-first-last($num) { &:nth-child(n + #{$num}):nth-last-child(n + #{$num}) { @content; } } // This quantity-query mixin will only select the first of `$limit` items. It will not // work if there is not as much as item as you set in `$limit`. // @group Quantity queries // @param {number} $limit // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin first-of($limit) { &:nth-last-child(#{$limit}):first-child { @content; } } // This quantity-query mixin will only select the last of `$limit` items. It will not // if there is not as much as item as you set in `$limit`. // @group Quantity queries // @param {number} $limit // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin last-of($limit) { &:nth-of-type(#{$limit}):nth-last-of-type(1) { @content; } } // This quantity-query mixin will select every items if there is at least `$num` items. It will not // if there is not as much as item as you set in `$num`. // @group Quantity queries // @param {number} $limit // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin at-least($num) { $selector: &; $child: nth(nth($selector, -1), -1); &:nth-last-child(n + #{$num}), &:nth-last-child(n + #{$num}) ~ #{$child} { @content; } } // This quantity-query mixin will select every items if there is at most `$num` items. It will not // if there is not as much as item as you set in `$num`. // @group Quantity queries // @param {number} $limit // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin at-most($num) { $selector: &; $child: nth(nth($selector, -1), -1); &:nth-last-child(-n + #{$num}):first-child, &:nth-last-child(-n + #{$num}):first-child ~ #{$child} { @content; } } // This quantity-query mixin will select every items only if there is between `$min` and `$max` items. // @group Quantity queries // @param {number} $limit // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin in-between($min, $max) { $selector: &; $child: nth(nth($selector, -1), -1); &:nth-last-child(n + #{$min}):nth-last-child(-n + #{$max}):first-child, &:nth-last-child(n + #{$min}):nth-last-child(-n + #{$max}):first-child ~ #{$child} { @content; } } // Select the first exact child // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin first-child() { &:first-of-type { @content } } // Select the last exact child // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin last-child() { &:last-of-type { @content } } // Select all even children. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin even() { &:nth-child(even) { @content; } } // Select all odd children. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin odd() { &:nth-child(odd) { @content; } } // Select only the first and last child. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin first-last() { &:first-child, &:last-child { @content; } } // Will only select the child if it’s unique. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @alias only @mixin unique() { &:only-child { @content; } } // Will only select the child if it’s unique. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin only() { &:only-child { @content; } } // Will only select children if they are not unique. Meaning if there is at // least 2 children, the style is applied. // @group no-arguments // @content [Write the style you want to apply to the children, and it will be added within the @content directive] @mixin not-unique() { &:not(:only-child) { @content; } } // This mixin is used to automatically sort z-index in numerical order. But it // can also sort them in anti-numerical order, depending the parameters you use. // @group using functions // @content [Write the style you want to apply to the children, and it will be added within the @content directive] // @param {number} $num - Number of children // @param {string} $direction [forward] - Direction of the sort // @param {number} $index [0] - Index of the sorting @mixin child-index($num, $direction: 'forward', $index: 0) { @for $i from 1 through $num { @if ($direction == 'forward') { &:nth-child(#{$i}) { z-index: order-index($i, $index); @content; } } @else if ($direction == 'backward') { &:nth-last-child(#{$i}) { z-index: order-index($i, $index); @content; } } } } // Used by the child-index mixin. It will returned the proper sorted numbers // depending on the `$index` value. // @access private // @param {number} $num - Number of children // @param {number} $index - Index of the sorting @function order-index($i, $index) { @return ($index + $i); } ================================================ FILE: packages/landing/assets/styles/tools/_functions.scss ================================================ // ========================================================================== // Tools / Functions // ========================================================================== @use "sass:math"; @use "sass:meta"; @use "sass:list"; @use "maths" as *; // Default font size for em() and rem() functions $font-size: 16px !default; // Check if the given value is a number in pixel // // @param {Number} $number - The value to check // @return {Boolean} @function is-pixel-number($number) { @return meta.type-of($number) == number and math.unit($number) == "px"; } // Converts the given pixel value to its EM quivalent. // // @param {Number} $size - The pixel value to convert. // @param {Number} $base [$font-size] - The assumed base font size. // @return {Number} Scalable pixel value in EMs. @function em($size, $base: $font-size) { @if not is-pixel-number($size) { @error "`#{$size}` needs to be a number in pixel."; } @if not is-pixel-number($base) { @error "`#{$base}` needs to be a number in pixel."; } @return math.div($size, $base) * 1em; } // Converts the given pixel value to its REM quivalent. // // @param {Number} $size - The pixel value to convert. // @param {Number} $base [$font-size] - The assumed base font size. // @return {Number} Scalable pixel value in REMs. @function rem($size, $base: $font-size) { @if not is-pixel-number($size) { @error "`#{$size}` needs to be a number in pixel."; } @if not is-pixel-number($base) { @error "`#{$base}` needs to be a number in pixel."; } @return math.div($size, $base) * 1rem; } // Converts a number to a percentage. // // @alias percentage() // @link http://sassdoc.com/annotations/#alias // @param {Number} $number - The value to convert. // @return {Number} A percentage. @function span($number) { @return percentage($number); } // Checks if a list contains a value(s). // // @link https://github.com/thoughtbot/bourbon/blob/master/core/bourbon/validators/_contains.scss // @param {List} $list - The list to check against. // @param {List} $values - A single value or list of values to check for. // @return {Boolean} // @access private @function list-contains( $list, $values... ) { @each $value in $values { @if meta.type-of(list.index($list, $value)) != "number" { @return false; } } @return true; } // Resolve whether a rule is important or not. // // @param {Boolean} $flag - Whether a rule is important (TRUE) or not (FALSE). // @return {String|Null} Returns `!important` or NULL. @function important($flag: false) { @if ($flag == true) { @return !important; } @else if ($flag == false) { @return null; } @else { @error "`#{$flag}` needs to be `true` or `false`."; } } // Determine if the current context is for a WYSIWYG editor. // // @requires {String} $context - The global context of the stylesheet. // @return {Boolean} If the $context is set to "editor". @function is-editor() { @return ('editor' == $context); } // Determine if the current context is for the front-end. // // @requires {String} $context - The global context of the stylesheet. // @return {Boolean} If the $context is set to "frontend". @function is-frontend() { @return ('frontend' == $context); } $context: 'frontend' !default; // Returns calculation of a percentage of the grid cell width // with optional inset of grid gutter. // // ```scss // .c-box { // width: grid-space(6/12); // margin-left: grid-space(1/12, 1); // } // ``` // // @param {number} $number - The percentage spacer // @param {number} $inset - The grid gutter inset // @return {function} @function grid-space($percentage, $inset: 0) { @return calc(#{$percentage} * (100vw - 2 * var(--grid-margin, 0px)) - (1 - #{$percentage}) * var(--grid-gutter, 0px) + #{$inset} * var(--grid-gutter, 0px)); } // Returns calculation of a percentage of the viewport height. // // ```scss // .c-box { // height: vh(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in vh @function vh($number) { @return calc(#{$number} * var(--vh, 1vh)); } // Returns calculation of a percentage of the viewport width. // // ```scss // .c-box { // width: vw(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in vw @function vw($number) { @return calc(#{$number} * var(--vw, 1vw)); } // Returns calculation of a percentage of the viewport width. // // ```scss // .c-box { // width: vw(100); // } // ``` // // @param {number} $number - The percentage number // @return {function} in vw $vw-viewport: 1440; @function vw($number) { @return calc(#{$number} * var(--vw, 1vw)); } @function clampWithMax($min, $size, $max) { $vw-context: $vw-viewport * 0.01; @return clamp(#{$min}, calc(#{$size} / #{$vw-context} * 1vw), #{$max}); } @function spacingClamp($size) { @return clampWithMax( calc(#{rem(1px)} * var(--spacing-#{$size}-mobile)), var(--spacing-#{$size}-desktop), calc(#{rem(1px)} * var(--spacing-#{$size}-desktop)) ); } // Returns clamp of calculated preferred responsive font size // within a font size and breakpoint range. // // ```scss // .c-heading.-h1 { // font-size: responsive-value(30px, 60px, 1800); // } // // .c-heading.-h2 { // font-size: responsive-value(20px, 40px, $from-big); // } // ``` // // @param {number} $min-size - Minimum font size in pixels. // @param {number} $max-size - Maximum font size in pixels. // @param {number} $breakpoint - Maximum breakpoint. // @return {function, number>} @function responsive-value($min-size, $max-size, $breakpoint) { $delta: math.div($max-size, $breakpoint); @return clamp($min-size, calc(#{strip-unit($delta)} * #{vw(100)}), $max-size); } ================================================ FILE: packages/landing/assets/styles/tools/_layout.scss ================================================ // ========================================================================== // Tools / Layout // ========================================================================== @use "sass:meta"; // Grid-like layout system. // // The layout tools provide a column-style layout system. This file contains // the mixins to generate basic structural elements. // // @link https://github.com/inuitcss/inuitcss/blob/0420ba8/objects/_objects.layout.scss // // // Generate the layout container. // // 1. Use the negative margin trick for multi-row grids: // http://csswizardry.com/2011/08/building-better-grid-systems/ // // @requires {function} u-list-reset // @output `font-size`, `margin`, `padding`, `list-style` @mixin o-layout($gutter: 0, $fix-whitespace: true) { margin: 0; padding: 0; list-style: none; @if ($fix-whitespace) { font-size: 0; } @if (meta.type-of($gutter) == number) { margin-left: -$gutter; // [1] } } // Generate the layout item. // // 1. Required in order to combine fluid widths with fixed gutters. // 2. Allows us to manipulate grids vertically, with text-level properties, // etc. // 3. Default item alignment is with the tops of each other, like most // traditional grid/layout systems. // 4. By default, all layout items are full-width (mobile first). // 5. Gutters provided by left padding: // http://csswizardry.com/2011/08/building-better-grid-systems/ @mixin o-layout_item($gutter: 0, $fix-whitespace: true) { display: inline-block; // [2] width: 100%; // [4] vertical-align: top; // [3] @if ($fix-whitespace) { font-size: 1rem; } @if (meta.type-of($gutter) == number) { padding-left: $gutter; // [5] } } ================================================ FILE: packages/landing/assets/styles/tools/_maths.scss ================================================ // ========================================================================== // Tools / Maths // ========================================================================== @use "sass:meta"; @use "sass:math"; // Remove the unit of a length // // @param {Number} $number Number to remove unit from // @return {function} @function strip-unit($value) { @if meta.type-of($value) != "number" { @error "Invalid `#{meta.type-of($value)}` type. Choose a number type instead."; } @else if meta.type-of($value) == "number" and not math.is-unitless($value) { @return math.div($value, $value * 0 + 1); } @return $value; } // Returns the square root of the given number. // // @param {number} $number The number to calculate. // @return {number} @function sqrt($number) { $x: 1; $value: $x; @for $i from 1 through 10 { $value: $x - math.div(($x * $x - abs($number)), (2 * $x)); $x: $value; } @return $value; } // Returns a number raised to the power of an exponent. // // @param {number} $number The base number. // @param {number} $exp The exponent. // @return {number} @function pow($number, $exp) { $value: 1; @if $exp > 0 { @for $i from 1 through $exp { $value: $value * $number; } } @else if $exp < 0 { @for $i from 1 through -$exp { $value: math.div($value, $number); } } @return $value; } // Returns the factorial of the given number. // // @param {number} $number The number to calculate. // @return {number} @function fact($number) { $value: 1; @if $number > 0 { @for $i from 1 through $number { $value: $value * $i; } } @return $value; } // Returns an approximation of pi, with 11 decimals. // // @return {number} @function pi() { @return 3.14159265359; } // Converts the number in degrees to the radian equivalent . // // @param {number} $angle The angular value to calculate. // @return {number} If $angle has the `deg` unit, // the radian equivalent is returned. // Otherwise, the unitless value of $angle is returned. @function rad($angle) { $unit: math.unit($angle); $angle: strip-units($angle); // If the angle has `deg` as unit, convert to radians. @if ($unit == deg) { @return math.div($angle, 180) * pi(); } @return $angle; } // Returns the sine of the given number. // // @param {number} $angle The angle to calculate. // @return {number} @function sin($angle) { $sin: 0; $angle: rad($angle); @for $i from 0 through 10 { $sin: $sin + pow(-1, $i) * math.div(pow($angle, (2 * $i + 1)), fact(2 * $i + 1)); } @return $sin; } // Returns the cosine of the given number. // // @param {string} $angle The angle to calculate. // @return {number} @function cos($angle) { $cos: 0; $angle: rad($angle); @for $i from 0 through 10 { $cos: $cos + pow(-1, $i) * math.div(pow($angle, 2 * $i), fact(2 * $i)); } @return $cos; } // Returns the tangent of the given number. // // @param {string} $angle The angle to calculate. // @return {number} @function tan($angle) { @return math.div(sin($angle), cos($angle)); } @function mapRangePx($css-var, $min0, $max0, $min1, $max1) { @return calc((#{$min1} + (( #{$css-var} - #{$min0}) / (#{$max0} - #{$min0})) * (#{$max1} - #{$min1})) * 1px); } @function mapRange($css-var, $min0, $max0, $min1, $max1) { @return calc(#{$min1} + (( #{$css-var} - #{$min0}) / (#{$max0} - #{$min0})) * (#{$max1} - #{$min1})); } @function mapRangeClampPx($css-var, $min0, $max0, $min1, $max1, $clampMin, $clampMax) { @return calc(clamp( (#{$clampMin}) * 1px, (#{$min1} + (( #{$css-var} - #{$min0}) / (#{$max0} - #{$min0})) * (#{$max1} - #{$min1})) * 1px, (#{$clampMax}) * 1px)); } @function mapRangeClamp($css-var, $min0, $max0, $min1, $max1, $clampMin, $clampMax) { @return calc(clamp( #{$clampMin}, #{$min1} + (( #{$css-var} - #{$min0}) / (#{$max0} - #{$min0})) * (#{$max1} - #{$min1}), #{$clampMax})); } ================================================ FILE: packages/landing/assets/styles/tools/_mixins.scss ================================================ // ========================================================================== // Tools / Mixins // ========================================================================== @use "sass:meta"; @use "sass:math"; @use "functions" as *; // Set the color of the highlight that appears over a link while it's being tapped. // // By default, the highlight is suppressed. // // @param {Color} $value [rgba(0, 0, 0, 0)] - The value of the highlight. // @output `-webkit-tap-highlight-color` @mixin tap-highlight-color($value: rgba(0, 0, 0, 0)) { -webkit-tap-highlight-color: $value; } // Set whether or not touch devices use momentum-based scrolling for the given element. // // By default, applies momentum-based scrolling for the current element. // // @param {String} $value [rgba(0, 0, 0, 0)] - The type of scrolling. // @output `-webkit-overflow-scrolling` @mixin overflow-scrolling($value: touch) { -webkit-overflow-scrolling: $value; } // Micro clearfix rules for containing floats. // // @link http://www.cssmojo.com/the-very-latest-clearfix-reloaded/ // @param {List} $supports The type of clearfix to generate. // @output Injects `:::after` pseudo-element. @mixin u-clearfix($supports...) { &::after { display: meta.if(list-contains($supports, table), table, block); clear: both; content: meta.if(list-contains($supports, opera), " ", ""); } } // Generate a font-size and baseline-compatible line-height. // // @link https://github.com/inuitcss/inuitcss/c14029c/tools/_tools.font-size.scss // @param {Number} $font-size - The size of the font. // @param {Number} $line-height [auto] - The line box height. // @param {Boolean} $important [false] - Whether the font-size is important. // @output `font-size`, `line-height` @mixin font-size($font-size, $line-height: auto, $important: false) { $important: important($important); font-size: rem($font-size) $important; @if ($line-height == "auto") { line-height: ceil(math.div($font-size, $line-height)) * math.div($line-height, $font-size) $important; } @else { @if (meta.type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") { line-height: $line-height $important; } @else if ($line-height != "none" and $line-height != false) { @error "D’oh! `#{$line-height}` is not a valid value for `$line-height`."; } } } // Vertically-center the direct descendants of the current element. // // Centering is achieved by displaying children as inline-blocks. Any whitespace // between elements is nullified by redefining the font size of the container // and its children. // // @output `font-size`, `display`, `vertical-align` @mixin o-vertical-center { font-size: 0; &::before { display: inline-block; height: 100%; content: ""; vertical-align: middle; } > * { display: inline-block; vertical-align: middle; font-size: 1rem; } } // Generate `:hover` and `:focus` styles in one go. // // @link https://github.com/inuitcss/inuitcss/blob/master/tools/_tools.mixins.scss // @content Wrapped in `:focus` and `:hover` pseudo-classes. // @output Wraps the given content in `:focus` and `:hover` pseudo-classes. @mixin u-hocus { &:focus, &:hover { @content; } } // Generate `:active` and `:focus` styles in one go. // // @see {Mixin} u-hocus // @content Wrapped in `:focus` and `:active` pseudo-classes. // @output Wraps the given content in `:focus` and `:hover` pseudo-classes. @mixin u-actus { &:focus, &:active { @content; } } // Prevent text from wrapping onto multiple lines for the current element. // // An ellipsis is appended to the end of the line. // // 1. Ensure that the node has a maximum width after which truncation can occur. // 2. Fix for IE 8/9 if `word-wrap: break-word` is in effect on ancestor nodes. // // @param {Number} $width [100%] - The maximum width of element. // @output `max-width`, `word-wrap`, `white-space`, `overflow`, `text-overflow` @mixin u-truncate($width: 100%) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; word-wrap: normal; // [2] @if $width { max-width: $width; // [1] } } // Applies accessible hiding to the current element. // // @param {Boolean} $important [true] - Whether the visibility is important. // @output Properties for removing the element from the document flow. @mixin u-accessibly-hidden($important: true) { $important: important($important); position: absolute $important; overflow: hidden; clip: rect(0 0 0 0); margin: 0; padding: 0; width: 1px; height: 1px; border: 0; } // Allows an accessibly hidden element to be focusable via keyboard navigation. // // @content For styling the now visible element. // @output Injects `:focus`, `:active` pseudo-classes. @mixin u-accessibly-focusable { @include u-actus { clip: auto; width: auto; height: auto; @content; } } // Hide the current element from all. // // The element will be hidden from screen readers and removed from the document flow. // // @link http://juicystudio.com/article/screen-readers-display-none.php // @param {Boolean} $important [true] - Whether the visibility is important. // @output `display`, `visibility` @mixin u-hidden($important: true) { $important: important($important); display: none $important; visibility: hidden $important; } // Show the current element for all. // // The element will be accessible from screen readers and visible in the document flow. // // @param {String} $display [block] - The rendering box used for the element. // @param {Boolean} $important [true] - Whether the visibility is important. // @output `display`, `visibility` @mixin u-shown($display: block, $important: true) { $important: important($important); display: $display $important; visibility: visible $important; } // Aspect-ratio polyfill // // @param {Number} $ratio [19/6] - The ratio of the element. // @param {Number} $width [100%] - The fallback width of element. // @param {Boolean} $children [false] - Whether the element contains children for the fallback properties. // @output Properties for maintaining aspect-ratio @mixin aspect-ratio($ratio: math.div(16, 9), $width: 100%, $children: false) { @supports (aspect-ratio: 1) { aspect-ratio: $ratio; } @supports not (aspect-ratio: 1) { height: 0; padding-top: calc(#{$width} * #{math.div(1, $ratio)}); @if ($children == true) { position: relative; > * { position: absolute; top: 0; left: 0; } } } } // Add focus state to focused element @mixin u-focus-visible ( $color: currentColor, $style: solid, $width: 2px, $offset: 2px, ) { outline-color: $color; outline-style: $style; outline-width: $width; outline-offset: $offset; } ================================================ FILE: packages/landing/assets/styles/tools/_widths.scss ================================================ // ========================================================================== // Tools / Widths // ========================================================================== @use "sass:math"; @use "functions" as *; // Optionally, the boilerplate can generate classes to offset items by a // certain width. Would you like to generate these types of class as well? E.g.: // // @example css // .u-push-1/3 // .u-pull-2/4 // .u-pull-1/5 // .u-push-2/3 $widths-offsets: false !default; // By default, the boilerplate uses fractions-like classes like `
`. // You can change the `/` to whatever you fancy with this variable. $fractions-delimiter: \/ !default; // When using Sass-MQ, this defines the separator for the breakpoints suffix // in the class name. By default, we are generating the responsive suffixes // for the classes with a `@` symbol so you get classes like: //
$breakpoint-delimiter: \@ !default; // Generate a series of width helper classes // // @example scss // @include widths(12); // // @example html //
// // @example scss // @include widths(3 4, -mobile); // // @example html //
// // @link https://github.com/inuitcss/inuitcss/commit/6eb574f/utilities/_utilities.widths.scss // @requires {Function} important // @requires {Function} $widths-offsets // @requires {Function} $fractions-delimiter // @requires {Function} $breakpoint-delimiter // @param {List} $colums - The columns we want the widths to have. // @param {String} $breakpoint - Optional suffix for responsive widths. // @output `width`, `position`, `right`, `left` @mixin widths($columns, $breakpoint: null, $important: true) { $important: important($important); // Loop through the number of columns for each denominator of our fractions. @each $denominator in $columns { // Begin creating a numerator for our fraction up until we hit the // denominator. @for $numerator from 1 through $denominator { // Build a class in the format `.u-3/4[@]`. .u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} { width: math.div($numerator, $denominator) * 100% $important; } @if ($widths-offsets == true) { // Build a class in the format `.u-push-1/2[@]`. .u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} { position: relative $important; right: auto $important; left: math.div($numerator, $denominator) * 100% $important; } // Build a class in the format `.u-pull-5/6[@]`. .u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} { position: relative $important; right: math.div($numerator, $denominator) * 100% $important; left: auto $important; } } } } } ================================================ FILE: packages/landing/assets/styles/utilities/_align.scss ================================================ // ========================================================================== // Utilities / Alignment // ========================================================================== // Floats // ========================================================================== .u-float-left { float: left !important; } .u-float-right { float: right !important; } // Horizontal Text // ========================================================================== .u-text-center { text-align: center !important; } .u-text-left { text-align: left !important; } .u-text-right { text-align: right !important; } // Vertical Text // ========================================================================== .u-align-baseline { vertical-align: baseline !important; } .u-align-bottom { vertical-align: bottom !important; } .u-align-middle { vertical-align: middle !important; } .u-align-top { vertical-align: top !important; } .u-vertical-center { @include o-vertical-center; } ================================================ FILE: packages/landing/assets/styles/utilities/_grid-column.scss ================================================ // ========================================================================== // Tools / Grid Columns // ========================================================================== @use "../core" as *; // // Grid layout system. // // This tool generates columns for all needed media queries. // Unused classes will be purge by the css post-processor. // $colsMax: $base-column-nb + 1; @each $breakpoint, $mediaquery in $breakpoints { @for $fromIndex from 1 through $colsMax { @for $toIndex from 1 through $colsMax { // Columns without media query @if $breakpoint == "tiny" { .u-gc-#{$fromIndex}\/#{$toIndex} { --gc-start: #{$fromIndex}; --gc-end: #{$toIndex}; } } // Columns min-width breakpoints `@from-*` .u-gc-#{$fromIndex}\/#{$toIndex}\@from-#{$breakpoint} { @media #{mq-min($breakpoint)} { --gc-start: #{$fromIndex}; --gc-end: #{$toIndex}; } } // Columns max-width breakpoints @to-*` .u-gc-#{$fromIndex}\/#{$toIndex}\@to-#{$breakpoint} { @media #{mq-max($breakpoint)} { --gc-start: #{$fromIndex}; --gc-end: #{$toIndex}; } } } } } ================================================ FILE: packages/landing/assets/styles/utilities/_helpers.scss ================================================ // ========================================================================== // Utilities / Helpers // ========================================================================== // Layout // ========================================================================== @use '../core' as *; .u-relative { position: relative; } .u-clipped { clip-path: polygon(0% 0, 100% 00%, 100% 100%, 0 100%); } .u-max { &-w300 { max-width: rem(300px); } &-w440 { max-width: rem(440px); } } .u-glyph { font-family: ff('serif'); font-feature-settings: 'dlig' on, 'ss01' on, 'salt' on; font-weight: $font-weight-regular; } .u-hover-underline { position: relative; @media (hover: hover) { &::before { content: ''; position: absolute; bottom: -0.1em; left: 0; width: 100%; height: 1px; background-color: currentColor; transform: scale3d(0, 1, 1); transition: transform t(fast) ease('power3.out'); transform-origin: top right; } &:hover, .u-hover:hover & { &::before { transform: scale3d(1, 1, 1); transform-origin: top left; } } } } // Completely remove from the flow but leave available to screen readers. .u-screen-reader-text { @include u-accessibly-hidden; } @media not print { .u-screen-reader-text\@screen { @include u-accessibly-hidden; } } // Extends the `.screen-reader-text` class to allow the element // to be focusable when navigated to via the keyboard. // // @link https://www.drupal.org/node/897638 // @todo Define styles when focused. .u-screen-reader-text.-focusable { @include u-accessibly-focusable; } .u-external-icon { font-size: 0.85em; } .u-text-balance { text-wrap: balance; } .u-hidden-md { @media (max-width: $to-medium) { display: none; } } ================================================ FILE: packages/landing/assets/styles/utilities/_print.scss ================================================ // ========================================================================== // Utilities / Print Mode // ========================================================================== //// /// Very crude, reset-like styles taken from the HTML5 Boilerplate: /// - https://github.com/h5bp/html5-boilerplate/blob/5.3.0/dist/doc/css.md#print-styles /// - https://github.com/h5bp/html5-boilerplate/blob/master/dist/css/main.css#L205-L282 /// /// @link https://github.com/inuitcss/inuitcss/blob/c27993f/utilities/_utilities.print.scss //// @media print { // 1. Black prints faster: http://www.sanbeiji.com/archives/953 *, *:before, *:after, *:first-letter, *:first-line { background: transparent !important; box-shadow: none !important; color: #000000 !important; // [1] text-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } // Don't show links that are fragment identifiers, or use the `javascript:` // pseudo protocol. a[href^="#"]:after, a[href^="javascript:"]:after { content: ""; } pre, blockquote { border: 1px solid #999999; page-break-inside: avoid; } // Printing Tables: http://css-discuss.incutio.com/wiki/Printing_Tables thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } } ================================================ FILE: packages/landing/assets/styles/utilities/_ratio.scss ================================================ // ========================================================================== // Utilities / Ratio // ========================================================================== @use "sass:meta"; // @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.ratio.scss // A list of aspect ratios that get generated as modifier classes. $aspect-ratios: ( (2:1), (4:3), (16:9), ) !default; /* stylelint-disable */ // Generate a series of ratio classes to be used like so: // // @example //
@each $ratio in $aspect-ratios { @each $antecedent, $consequent in $ratio { @if (meta.type-of($antecedent) != number) { @error "`#{$antecedent}` needs to be a number." } @if (meta.type-of($consequent) != number) { @error "`#{$consequent}` needs to be a number." } .u-#{$antecedent}\:#{$consequent}::before { padding-bottom: math.div($consequent, $antecedent) * 100%; } } } /* stylelint-enable */ ================================================ FILE: packages/landing/assets/styles/utilities/_spacing.scss ================================================ // ========================================================================== // Utilities / Spacing // ========================================================================== @use "../core" as *; @use "sass:list"; //// /// Utility classes to put specific spacing values onto elements. The below loop /// will generate us a suite of classes like: /// /// @example /// .u-margin-top {} /// .u-padding-left-large {} /// .u-margin-right-small {} /// .u-padding {} /// .u-padding-right-none {} /// .u-padding-horizontal {} /// .u-padding-vertical-small {} /// /// @link https://github.com/inuitcss/inuitcss/blob/512977a/utilities/_utilities.spacing.scss //// /* stylelint-disable string-quotes */ $spacing-directions: ( null: null, '-top': '-top', '-right': '-right', '-bottom': '-bottom', '-left': '-left', '-x': '-left' '-right', '-y': '-top' '-bottom', ) !default; $spacing-properties: ( 'padding': 'padding', 'margin': 'margin', ) !default; $spacing-sizes: list.join($spacers, ( null: var(--grid-gutter), 'none': 0 )); @each $breakpoint, $mediaquery in $breakpoints { @each $property-namespace, $property in $spacing-properties { @each $direction-namespace, $directions in $spacing-directions { @each $size-namespace, $size in $spacing-sizes { // Prepend "-" to spacing sizes if not null @if ($size-namespace != null) { $size-namespace: "-" + $size-namespace; } // Base class $base-class: ".u-" + #{$property-namespace}#{$direction-namespace}#{$size-namespace}; // Spacer without media query @if $breakpoint == "tiny" { #{$base-class} { @each $direction in $directions { #{$property}#{$direction}: $size !important; } } } // Spacer min-width breakpoints `@from-*` #{$base-class}\@from-#{$breakpoint} { @media #{mq-min($breakpoint)} { @each $direction in $directions { #{$property}#{$direction}: $size !important; } } } // Spacer max-width breakpoints @to-*` #{$base-class}\@to-#{$breakpoint} { @media #{mq-max($breakpoint)} { @each $direction in $directions { #{$property}#{$direction}: $size !important; } } } } } } } /* stylelint-enable string-quotes */ ================================================ FILE: packages/landing/assets/styles/utilities/_states.scss ================================================ // ========================================================================== // Utilities / States // ========================================================================== // ARIA roles display visual cursor hints [aria-busy="true"] { cursor: progress; } [aria-controls] { cursor: pointer; } [aria-disabled] { cursor: default; } // Control visibility without affecting flow. .is-visible { visibility: visible !important; opacity: 1 !important; } .is-invisible { visibility: hidden !important; opacity: 0 !important; } // Completely remove from the flow and screen readers. .is-hidden { @include u-hidden; } @media not print { .is-hidden\@screen { @include u-hidden; } } @media print { .is-hidden\@print { @include u-hidden; } } // .is-hidden\@to-large { // @media (max-width: $to-large) { // display: none; // } // } // // .is-hidden\@from-large { // @media (min-width: $from-large) { // display: none; // } // } // // Display a hidden-by-default element. // // .is-shown { // @include u-shown; // } // // table.is-shown { // display: table !important; // } // // tr.is-shown { // display: table-row !important; // } // // td.is-shown, // th.is-shown { // display: table-cell !important; // } ================================================ FILE: packages/landing/assets/styles/utilities/_theme.scss ================================================ // ========================================================================== // Utilities / Theme // ========================================================================== @use "../core" as *; :root { --color-text: #{color(blue)}; --color-background: #{color(white)}; } [data-theme="white"] { color: var(--color-text); background-color: var(--color-background); } [data-theme="blue"] { --color-text: #{color(white)}; --color-background: #{color(blue)}; color: var(--color-text); background-color: var(--color-background); } [data-theme="black"] { --color-text: #{color(white)}; --color-background: #{color(black)}; color: var(--color-text); background-color: var(--color-background); } ================================================ FILE: packages/landing/assets/styles/utilities/_widths.scss ================================================ // ========================================================================== // Utilities / Widths // ========================================================================== //// /// @link https://github.com/inuitcss/inuitcss/blob/6eb574f/utilities/_utilities.widths.scss /// /// /// Which fractions would you like in your grid system(s)? /// By default, the boilerplate provides fractions of one whole, halves, thirds, /// quarters, and fifths, e.g.: /// /// @example css /// .u-1/2 /// .u-2/5 /// .u-3/4 /// .u-2/3 //// $widths-fractions: 1 2 3 4 5 !default; @include widths($widths-fractions); .u-1\/2\@from-small { @media (min-width: $from-small) { width: span(1/2); } } ================================================ FILE: packages/landing/assets/styles/vendors/.gitkeep ================================================ ================================================ FILE: packages/landing/assets.json ================================================ { "version": 1767911710101 } ================================================ FILE: packages/landing/build/build.js ================================================ import buildEleventy from './tasks/eleventy.js'; import concatFiles from './tasks/concats.js'; import compileScripts from './tasks/scripts.js'; import compileStyles from './tasks/styles.js'; import compileSVGs from './tasks/svgs.js'; import bumpVersions from './tasks/versions.js'; buildEleventy(); concatFiles(); compileScripts(); compileStyles(); compileSVGs(); bumpVersions(); ================================================ FILE: packages/landing/build/helpers/config.js ================================================ /** * @file Provides simple user configuration options. */ import loconfig from '../../loconfig.json' with { type: 'json' }; import { merge } from '../utils/index.js'; let usrconfig; try { usrconfig = await import('../../loconfig.local.json', { with: { type: 'json' }, }); usrconfig = usrconfig.default; merge(loconfig, usrconfig); } catch (err) { // do nothing } export default loconfig; export { loconfig, }; ================================================ FILE: packages/landing/build/helpers/glob.js ================================================ /** * @file Retrieve the first available glob library. * * Note that options vary between libraries. * * Candidates: * * - {@link https://npmjs.com/package/tiny-glob tiny-glob} [1][5][6] * - {@link https://npmjs.com/package/globby globby} [2][5] * - {@link https://npmjs.com/package/fast-glob fast-glob} [3] * - {@link https://npmjs.com/package/glob glob} [1][4][5] * * Notes: * * - [1] The library's function accepts only a single pattern. * - [2] The library's function accepts only an array of patterns. * - [3] The library's function accepts either a single pattern * or an array of patterns. * - [4] The library's function does not return a Promise but will be * wrapped in a function that does return a Promise. * - [5] The library's function will be wrapped in a function that * supports a single pattern and an array of patterns. * - [6] The library's function returns files and directories but will be * preconfigured to return only files. */ import { promisify } from 'node:util'; /** * @callback GlobFn * * @param {string|string[]} patterns - A string pattern * or an array of string patterns. * @param {object} options * * @returns {Promise} */ /** * @typedef {object} GlobOptions */ /** * @type {GlobFn|undefined} The discovered glob function. */ let glob; /** * @type {string[]} A list of packages to attempt import. */ const candidates = [ 'tiny-glob', 'globby', 'fast-glob', 'glob', ]; try { glob = await importGlob(); } catch (err) { // do nothing } /** * @type {boolean} Whether a glob function was discovered (TRUE) or not (FALSE). */ const supportsGlob = (typeof glob === 'function'); /** * Imports the first available glob function. * * @throws {TypeError} If no glob library was found. * * @returns {GlobFn} */ async function importGlob() { for (let name of candidates) { try { let globModule = await import(name); if (typeof globModule.default !== 'function') { throw new TypeError(`Expected ${name} to be a function`); } /** * Wrap the function to ensure * a common pattern. */ switch (name) { case 'tiny-glob': /** [1][5] */ return createArrayableGlob( /** [6] */ createPresetGlob(globModule.default, { filesOnly: true }) ); case 'globby': /** [2][5] - If `patterns` is a string, wraps into an array. */ return (patterns, options) => globModule.default([].concat(patterns), options); case 'glob': /** [1][5] */ return createArrayableGlob( /** [4] */ promisify(globModule.default) ); default: return globModule.default; } } catch (err) { // swallow this error; skip to the next candidate. } } throw new TypeError( `No glob library was found, expected one of: ${candidates.join(', ')}` ); } /** * Creates a wrapper function for the glob function * to provide support for arrays of patterns. * * @param {function} globFn - The glob function. * * @returns {GlobFn} */ function createArrayableGlob(globFn) { return (patterns, options) => { /** [2] If `patterns` is a string, wraps into an array. */ patterns = [].concat(patterns); const globs = patterns.map((pattern) => globFn(pattern, options)); return Promise.all(globs).then((files) => { return [].concat.apply([], files); }); }; } /** * Creates a wrapper function for the glob function * to define new default options. * * @param {function} globFn - The glob function. * @param {GlobOptions} presets - The glob function options to preset. * * @returns {GlobFn} */ function createPresetGlob(globFn, presets) { return (patterns, options) => globFn(patterns, Object.assign({}, presets, options)); } export default glob; export { glob, supportsGlob, }; ================================================ FILE: packages/landing/build/helpers/message.js ================================================ /** * @file Provides a decorator for console messages. */ import kleur from 'kleur'; /** * Outputs a message to the console. * * @param {string} text - The message to output. * @param {string} [type] - The type of message. * @param {string} [timerID] - The console time label to output. */ function message(text, type, timerID) { switch (type) { case 'success': console.log('✅ ', kleur.bgGreen().black(text)); break; case 'chore': console.log('🧹 ', kleur.bgGreen().black(text)); break; case 'notice': console.log('ℹ️ ', kleur.bgBlue().black(text)); break; case 'error': console.log('❌ ', kleur.bgRed().black(text)); break; case 'warning': console.log('⚠️ ', kleur.bgYellow().black(text)); break; case 'waiting': console.log('⏱ ', kleur.blue().italic(text)); if (timerID != null) { console.timeLog(timerID); timerID = null; } break; default: console.log(text); break; } if (timerID != null) { console.timeEnd(timerID); } console.log(''); } export default message; export { message, }; ================================================ FILE: packages/landing/build/helpers/notification.js ================================================ /** * @file Provides a decorator for cross-platform notification. */ import notifier from 'node-notifier'; /** * Sends a cross-platform native notification. * * Wraps around node-notifier to assign default values. * * @param {string|object} options - The notification options or a message. * @param {string} options.title - The notification title. * @param {string} options.message - The notification message. * @param {string} options.icon - The notification icon. * @param {function} callback - The notification callback. * @return {void} */ function notification(options, callback) { if (typeof options === 'string') { options = { message: options }; } else if (!options.title && !options.message) { throw new TypeError( 'Notification expects at least a \'message\' parameter' ); } if (typeof options.icon === 'undefined') { options.icon = 'https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png'; } // If notification does not use a callback, // shorten the wait before timing out. if (typeof callback === 'undefined') { if (typeof options.wait === 'undefined') { if (typeof options.timeout === 'undefined') { options.timeout = 5; } } } notifier.notify(options, callback); } export default notification; export { notification, }; ================================================ FILE: packages/landing/build/helpers/postcss.js ================================================ /** * @file If available, returns the PostCSS Processor creator and * any the Autoprefixer PostCSS plugin. */ /** * @typedef {import('autoprefixer').autoprefixer.Options} AutoprefixerOptions */ /** * @typedef {import('postcss').AcceptedPlugin} AcceptedPlugin */ /** * @typedef {import('postcss').Postcss} Postcss */ /** * @typedef {import('postcss').ProcessOptions} ProcessOptions */ /** * @typedef {import('postcss').Processor} Processor */ /** * @typedef {AcceptedPlugin[]} PluginList */ /** * @typedef {object} PluginMap */ /** * @typedef {PluginList|PluginMap} PluginCollection */ /** * @typedef {object} PostCSSOptions * * @property {ProcessOptions} processor - The `Processor#process()` options. * @property {AutoprefixerOptions} autoprefixer - The `autoprefixer()` options. */ /** * @type {Postcss|undefined} postcss - The discovered PostCSS function. * @type {AcceptedPlugin|undefined} autoprefixer - The discovered Autoprefixer function. */ let postcss, autoprefixer; try { postcss = await import('postcss'); postcss = postcss.default; autoprefixer = await import('autoprefixer'); autoprefixer = autoprefixer.default; } catch (err) { // do nothing } /** * @type {boolean} Whether PostCSS was discovered (TRUE) or not (FALSE). */ const supportsPostCSS = (typeof postcss === 'function'); /** * @type {PluginList} A list of supported plugins. */ const pluginsList = [ autoprefixer, ]; /** * @type {PluginMap} A map of supported plugins. */ const pluginsMap = { 'autoprefixer': autoprefixer, }; /** * Attempts to create a PostCSS Processor with the given plugins and options. * * @param {PluginCollection} pluginsListOrMap - A list or map of plugins. * If a map of plugins, the plugin name looks up `options`. * @param {PostCSSOptions} options - The PostCSS wrapper options. * * @returns {Processor|null} */ function createProcessor(pluginsListOrMap, options) { if (!postcss) { return null; } const plugins = parsePlugins(pluginsListOrMap, options); return postcss(plugins); } /** * Parses the PostCSS plugins and options. * * @param {PluginCollection} pluginsListOrMap - A list or map of plugins. * If a map of plugins, the plugin name looks up `options`. * @param {PostCSSOptions} options - The PostCSS wrapper options. * * @returns {PluginList} */ function parsePlugins(pluginsListOrMap, options) { if (Array.isArray(pluginsListOrMap)) { return pluginsListOrMap; } /** @type {PluginList} */ const plugins = []; for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) { if (name in options) { plugin = plugin[name](options[name]); } plugins.push(plugin); } return plugins; } export default postcss; export { autoprefixer, createProcessor, parsePlugins, pluginsList, pluginsMap, postcss, supportsPostCSS, }; ================================================ FILE: packages/landing/build/helpers/template.js ================================================ /** * @file Provides simple template tags. */ import loconfig from './config.js'; import { escapeRegExp, flatten } from '../utils/index.js'; const templateData = flatten({ paths: loconfig.paths }); /** * Replaces all template tags from a map of keys and values. * * If replacement pairs contain a mix of substrings, regular expressions, * and functions, regular expressions are executed last. * * @param {*} input - The value being searched and replaced on. * If input is, or contains, a string, tags will be resolved. * If input is, or contains, an object, it is mutated directly. * If input is, or contains, an array, a shallow copy is returned. * Otherwise, the value is left intact. * @param {object} [data] - An object in the form `{ 'from': 'to', … }`. * @return {*} Returns the transformed value. */ function resolve(input, data = templateData) { switch (typeof input) { case 'string': { return resolveValue(input, data); } case 'object': { if (input == null) { break; } if (Array.isArray(input)) { return input.map((value) => resolve(value, data)); } else { for (const key in input) { input[key] = resolve(input[key], data); } } } } return input; } /** * Replaces all template tags in a string from a map of keys and values. * * If replacement pairs contain a mix of substrings, regular expressions, * and functions, regular expressions are executed last. * * @param {string} input - The string being searched and replaced on. * @param {object} [data] - An object in the form `{ 'from': 'to', … }`. * @return {string} Returns the translated string. */ function resolveValue(input, data = templateData) { const tags = []; if (data !== templateData) { data = flatten(data); } for (let tag in data) { tags.push(escapeRegExp(tag)); } if (tags.length === 0) { return input; } const search = new RegExp('\\{%\\s*(' + tags.join('|') + ')\\s*%\\}', 'g'); return input.replace(search, (match, key) => { let value = data[key]; switch (typeof value) { case 'function': /** * Retrieve the offset of the matched substring `args[0]` * and the whole string being examined `args[1]`. */ let args = Array.prototype.slice.call(arguments, -2); return value.call(data, match, args[0], args[1]); case 'string': case 'number': return value; } return ''; }); } export default resolve; export { resolve, resolveValue, }; ================================================ FILE: packages/landing/build/migrate_imports.js ================================================ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const files = [ 'generic/_generic.scss', 'generic/_media.scss', 'generic/_button.scss', 'elements/_document.scss', 'objects/_container.scss', 'objects/_icons.scss', 'objects/_grid.scss', 'components/_heading.scss', 'components/_text.scss', 'components/_button.scss', 'components/_form.scss', 'components/_header.scss', 'components/_hero.scss', 'components/_rail.scss', 'components/_cascade.scss', 'components/_section-heading.scss', 'components/_tool.scss', 'components/_features-grid.scss', 'components/_sticky-heading.scss', 'components/_list.scss', 'components/_footer.scss', 'components/_fadeInText.scss', 'components/_preloader.scss', 'utilities/_grid-column.scss', 'utilities/_theme.scss', 'utilities/_helpers.scss', 'utilities/_spacing.scss' ]; const basePath = '/Users/arnvvd/Projects/locomotive-scroll/packages/landing/assets/styles'; files.forEach(file => { const filePath = path.join(basePath, file); if (fs.existsSync(filePath)) { let content = fs.readFileSync(filePath, 'utf8'); // Check if already has @use if (content.includes('@use "../core"')) { console.log(`Skipping ${file}, already has @use`); return; } // Try to find the header block const headerRegex = /^(\/\/ =+[\r\n]+(?:\/\/.*[\r\n]+)*\/\/ =+[\r\n]+)/; const match = content.match(headerRegex); if (match) { // Insert after header const header = match[1]; const newContent = content.replace(header, header + '\n@use "../core" as *;\n'); fs.writeFileSync(filePath, newContent, 'utf8'); console.log(`Updated ${file}`); } else { // Prepend to top const newContent = '@use "../core" as *;\n\n' + content; fs.writeFileSync(filePath, newContent, 'utf8'); console.log(`Updated ${file} (prepended)`); } } else { console.error(`File not found: ${filePath}`); } }); ================================================ FILE: packages/landing/build/tasks/concats.js ================================================ import loconfig from '../helpers/config.js'; import glob, { supportsGlob } from '../helpers/glob.js'; import message from '../helpers/message.js'; import notification from '../helpers/notification.js'; import resolve from '../helpers/template.js'; import { merge } from '../utils/index.js'; import concat from 'concat'; import { basename, normalize, } from 'node:path'; /** * @const {object} defaultGlobOptions - The default shared glob options. * @const {object} developmentGlobOptions - The predefined glob options for development. * @const {object} productionGlobOptions - The predefined glob options for production. */ export const defaultGlobOptions = { }; export const developmentGlobOptions = Object.assign({}, defaultGlobOptions); export const productionGlobOptions = Object.assign({}, defaultGlobOptions); /** * @typedef {object} ConcatOptions * @property {boolean} removeDuplicates - Removes duplicate paths from * the array of matching files and folders. * Only the first occurrence of each path is kept. */ /** * @const {ConcatOptions} defaultConcatOptions - The default shared concatenation options. * @const {ConcatOptions} developmentConcatOptions - The predefined concatenation options for development. * @const {ConcatOptions} productionConcatOptions - The predefined concatenation options for production. */ export const defaultConcatOptions = { removeDuplicates: true, }; export const developmentConcatOptions = Object.assign({}, defaultConcatOptions); export const productionConcatOptions = Object.assign({}, defaultConcatOptions); /** * @const {object} developmentConcatFilesArgs - The predefined `concatFiles()` options for development. * @const {object} productionConcatFilesArgs - The predefined `concatFiles()` options for production. */ export const developmentConcatFilesArgs = [ developmentGlobOptions, developmentConcatOptions, ]; export const productionConcatFilesArgs = [ productionGlobOptions, productionConcatOptions, ]; /** * Concatenates groups of files. * * @todo Add support for minification. * * @async * @param {object|boolean} [globOptions=null] - Customize the glob options. * If `null`, default production options are used. * If `false`, the glob function will be ignored. * @param {object} [concatOptions=null] - Customize the concatenation options. * If `null`, default production options are used. * @return {Promise} */ export default async function concatFiles(globOptions = null, concatOptions = null) { if (supportsGlob) { if (globOptions == null) { globOptions = productionGlobOptions; } else if ( globOptions !== false && globOptions !== developmentGlobOptions && globOptions !== productionGlobOptions ) { globOptions = merge({}, defaultGlobOptions, globOptions); } } if (concatOptions == null) { concatOptions = productionConcatOptions; } else if ( concatOptions !== developmentConcatOptions && concatOptions !== productionConcatOptions ) { concatOptions = merge({}, defaultConcatOptions, concatOptions); } /** * @async * @param {object} entry - The entrypoint to process. * @param {string[]} entry.includes - One or more paths to process. * @param {string} entry.outfile - The file to write to. * @param {?string} [entry.label] - The task label. * Defaults to the outfile name. * @return {Promise} */ loconfig.tasks.concats?.forEach(async ({ includes, outfile, label = null }) => { if (!label) { label = basename(outfile || 'undefined'); } const timeLabel = `${label} concatenated in`; console.time(timeLabel); try { if (!Array.isArray(includes)) { includes = [ includes ]; } includes = resolve(includes); outfile = resolve(outfile); if (supportsGlob && globOptions) { includes = await glob(includes, globOptions); } if (concatOptions.removeDuplicates) { includes = includes.map((path) => normalize(path)); includes = [ ...new Set(includes) ]; } await concat(includes, outfile); if (includes.length) { message(`${label} concatenated`, 'success', timeLabel); } else { message(`${label} is empty`, 'notice', timeLabel); } } catch (err) { message(`Error concatenating ${label}`, 'error'); message(err); notification({ title: `${label} concatenation failed 🚨`, message: `${err.name}: ${err.message}` }); } }); }; ================================================ FILE: packages/landing/build/tasks/eleventy.js ================================================ import message from '../helpers/message.js'; import { merge } from '../utils/index.js'; import Eleventy from "@11ty/eleventy"; export const defaultEleventyOptions = { production: true } export const developmentEleventyOptions = { production: false } export const productionEleventyOptions = { production: true } let elev; export default async function buildEleventy(eleventyOptions = null) { if (eleventyOptions == null) { eleventyOptions = productionEleventyOptions; } else if ( eleventyOptions !== developmentEleventyOptions && eleventyOptions !== productionEleventyOptions ) { eleventyOptions = merge({}, defaultEleventyOptions, eleventyOptions); } const { production } = eleventyOptions; const timeLabel = `11ty compiled in`; console.time(timeLabel); try { if(!elev) { elev = new Eleventy(); if(!production) { await elev.watch(); } } // Disable caching to ensure a fresh build each time await elev.write(); message(`11ty compiled`, 'success', timeLabel); } catch(err) { console.error(err) message(err, 'error', timeLabel); } } ================================================ FILE: packages/landing/build/tasks/scripts.js ================================================ import loconfig from '../helpers/config.js'; import message from '../helpers/message.js'; import notification from '../helpers/notification.js'; import resolve from '../helpers/template.js'; import { merge } from '../utils/index.js'; import esbuild from 'esbuild'; import { basename } from 'node:path'; /** * @const {object} defaultESBuildOptions - The default shared ESBuild options. * @const {object} developmentESBuildOptions - The predefined ESBuild options for development. * @const {object} productionESBuildOptions - The predefined ESBuild options for production. */ export const defaultESBuildOptions = { bundle: true, color: true, sourcemap: true, target: [ 'es2015', ], }; export const developmentESBuildOptions = Object.assign({}, defaultESBuildOptions); export const productionESBuildOptions = Object.assign({}, defaultESBuildOptions, { logLevel: 'warning', minify: true, }); /** * @const {object} developmentScriptsArgs - The predefined `compileScripts()` options for development. * @const {object} productionScriptsArgs - The predefined `compileScripts()` options for production. */ export const developmentScriptsArgs = [ developmentESBuildOptions, ]; export const productionScriptsArgs = [ productionESBuildOptions, ]; /** * Bundles and minifies main JavaScript files. * * @async * @param {object} [esBuildOptions=null] - Customize the ESBuild build API options. * If `null`, default production options are used. * @return {Promise} */ export default async function compileScripts(esBuildOptions = null) { if (esBuildOptions == null) { esBuildOptions = productionESBuildOptions; } else if ( esBuildOptions !== developmentESBuildOptions && esBuildOptions !== productionESBuildOptions ) { esBuildOptions = merge({}, defaultESBuildOptions, esBuildOptions); } /** * @async * @param {object} entry - The entrypoint to process. * @param {string[]} entry.includes - One or more paths to process. * @param {string} [entry.outdir] - The directory to write to. * @param {string} [entry.outfile] - The file to write to. * @param {?string} [entry.label] - The task label. * Defaults to the outdir or outfile name. * @throws {TypeError} If outdir and outfile are missing. * @return {Promise} */ loconfig.tasks.scripts?.forEach(async ({ includes, outdir = '', outfile = '', label = null }) => { if (!label) { label = basename(outdir || outfile || 'undefined'); } const timeLabel = `${label} compiled in`; console.time(timeLabel); try { if (!Array.isArray(includes)) { includes = [ includes ]; } includes = resolve(includes); if (outdir) { outdir = resolve(outdir); } else if (outfile) { outfile = resolve(outfile); } else { throw new TypeError( 'Expected \'outdir\' or \'outfile\'' ); } await esbuild.build(Object.assign({}, esBuildOptions, { entryPoints: includes, outdir, outfile, })); message(`${label} compiled`, 'success', timeLabel); } catch (err) { // errors managments (already done in esbuild) notification({ title: `${label} compilation failed 🚨`, message: `${err.errors[0].text} in ${err.errors[0].location.file} line ${err.errors[0].location.line}` }); } }); }; ================================================ FILE: packages/landing/build/tasks/styles.js ================================================ import loconfig from '../helpers/config.js'; import message from '../helpers/message.js'; import notification from '../helpers/notification.js'; import { createProcessor, pluginsMap as postcssPluginsMap, supportsPostCSS } from '../helpers/postcss.js'; import resolve from '../helpers/template.js'; import { merge } from '../utils/index.js'; import { writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; import { promisify } from 'node:util'; import * as sass from 'sass'; import { PurgeCSS } from 'purgecss'; const sassRender = promisify(sass.render); let postcssProcessor; /** * @const {object} defaultSassOptions - The default shared Sass options. * @const {object} developmentSassOptions - The predefined Sass options for development. * @const {object} productionSassOptions - The predefined Sass options for production. */ export const defaultSassOptions = { omitSourceMapUrl: true, sourceMap: true, sourceMapContents: true, }; export const developmentSassOptions = Object.assign({}, defaultSassOptions, { outputStyle: 'expanded', }); export const productionSassOptions = Object.assign({}, defaultSassOptions, { outputStyle: 'compressed', }); /** * @const {object} defaultPostCSSOptions - The default shared PostCSS options. * @const {object} developmentPostCSSOptions - The predefined PostCSS options for development. * @const {object} productionPostCSSOptions - The predefined PostCSS options for production. */ export const defaultPostCSSOptions = { processor: { map: { annotation: false, inline: false, sourcesContent: true, }, }, }; export const developmentPostCSSOptions = Object.assign({}, defaultPostCSSOptions); export const productionPostCSSOptions = Object.assign({}, defaultPostCSSOptions); /** * @const {object|boolean} developmentStylesArgs - The predefined `compileStyles()` options for development. * @const {object|boolean} productionStylesArgs - The predefined `compileStyles()` options for production. */ export const developmentStylesArgs = [ developmentSassOptions, developmentPostCSSOptions, false ]; export const productionStylesArgs = [ productionSassOptions, productionPostCSSOptions, true ]; /** * Compiles and minifies main Sass files to CSS. * * @todo Add deep merge of `postcssOptions` to better support customization * of default processor options. * * @async * @param {object} [sassOptions=null] - Customize the Sass render API options. * If `null`, default production options are used. * @param {object|boolean} [postcssOptions=null] - Customize the PostCSS processor API options. * If `null`, default production options are used. * If `false`, PostCSS processing will be ignored. * @return {Promise} */ export default async function compileStyles(sassOptions = null, postcssOptions = null, purge = true) { if (sassOptions == null) { sassOptions = productionSassOptions; } else if ( sassOptions !== developmentSassOptions && sassOptions !== productionSassOptions ) { sassOptions = merge({}, defaultSassOptions, sassOptions); } if (supportsPostCSS) { if (postcssOptions == null) { postcssOptions = productionPostCSSOptions; } else if ( postcssOptions !== false && postcssOptions !== developmentPostCSSOptions && postcssOptions !== productionPostCSSOptions ) { postcssOptions = merge({}, defaultPostCSSOptions, postcssOptions); } } /** * @async * @param {object} entry - The entrypoint to process. * @param {string[]} entry.infile - The file to process. * @param {string} entry.outfile - The file to write to. * @param {?string} [entry.label] - The task label. * Defaults to the outfile name. * @return {Promise} */ loconfig.tasks.styles?.forEach(async ({ infile, outfile, label = null }) => { const filestem = basename((outfile || 'undefined'), '.css'); const timeLabel = `${label || `${filestem}.css`} compiled in`; console.time(timeLabel); try { infile = resolve(infile); outfile = resolve(outfile); let result = await sassRender(Object.assign({}, sassOptions, { file: infile, outFile: outfile, })); if (supportsPostCSS && postcssOptions) { if (typeof postcssProcessor === 'undefined') { postcssProcessor = createProcessor( postcssPluginsMap, postcssOptions ); } result = await postcssProcessor.process( result.css, Object.assign({}, postcssOptions.processor, { from: outfile, to: outfile, }) ); if (result.warnings) { const warnings = result.warnings(); if (warnings.length) { message(`Error processing ${label || `${filestem}.css`}`, 'warning'); warnings.forEach((warn) => { message(warn.toString()); }); } } } try { await writeFile(outfile, result.css).then(() => { // Purge CSS once file exists. if (outfile && purge) { purgeUnusedCSS(outfile, `${label || `${filestem}.css`}`); } }); if (result.css) { message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel); } else { message(`${label || `${filestem}.css`} is empty`, 'notice', timeLabel); } } catch (err) { message(`Error compiling ${label || `${filestem}.css`}`, 'error'); message(err); notification({ title: `${label || `${filestem}.css`} save failed 🚨`, message: `Could not save stylesheet to ${label || `${filestem}.css`}` }); } if (result.map) { try { await writeFile(outfile + '.map', result.map.toString()); } catch (err) { message(`Error compiling ${label || `${filestem}.css.map`}`, 'error'); message(err); notification({ title: `${label || `${filestem}.css.map`} save failed 🚨`, message: `Could not save sourcemap to ${label || `${filestem}.css.map`}` }); } } } catch (err) { message(`Error compiling ${label || `${filestem}.scss`}`, 'error'); message(err.formatted || err); notification({ title: `${label || `${filestem}.scss`} compilation failed 🚨`, message: (err.formatted || `${err.name}: ${err.message}`) }); } }); }; /** * Purge unused styles from CSS files. * * @async * * @param {string} outfile - The path of a css file * If missing the function stops. * @param {string} label - The CSS file label or name. * @return {Promise} */ async function purgeUnusedCSS(outfile, label) { const contentFiles = loconfig.tasks.purgeCSS?.content; if (!Array.isArray(contentFiles) || !contentFiles.length) { return; } label = label ?? basename(outfile); const timeLabel = `${label} purged in`; console.time(timeLabel); const purgeCSSResults = await (new PurgeCSS()).purge({ content: contentFiles, css: [ outfile ], defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [], fontFaces: true, keyframes: true, safelist: { // Keep all except .u-gc-* | .u-margin-* | .u-padding-* standard: [ /^(?!.*\b(u-gc-|u-margin|u-padding)).*$/ ], // Preserve CSS variables and their content in these selectors deep: [ /fadeInText/ ] }, // Disable variable purging to preserve all CSS custom properties variables: false, }) for (let result of purgeCSSResults) { await writeFile(outfile, result.css) message(`${label} purged`, 'chore', timeLabel); } } ================================================ FILE: packages/landing/build/tasks/svgs.js ================================================ import loconfig from '../helpers/config.js'; import message from '../helpers/message.js'; import notification from '../helpers/notification.js'; import resolve from '../helpers/template.js'; import { merge } from '../utils/index.js'; import { basename } from 'node:path'; import mixer from 'svg-mixer'; /** * @const {object} defaultMixerOptions - The default shared Mixer options. * @const {object} developmentMixerOptions - The predefined Mixer options for development. * @const {object} productionMixerOptions - The predefined Mixer options for production. */ export const defaultMixerOptions = { spriteConfig: { usages: false, }, }; export const developmentMixerOptions = Object.assign({}, defaultMixerOptions); export const productionMixerOptions = Object.assign({}, defaultMixerOptions); /** * @const {object} developmentSVGsArgs - The predefined `compileSVGs()` options for development. * @const {object} productionSVGsArgs - The predefined `compileSVGs()` options for production. */ export const developmentSVGsArgs = [ developmentMixerOptions, ]; export const productionSVGsArgs = [ productionMixerOptions, ]; /** * Generates and transforms SVG spritesheets. * * @async * @param {object} [mixerOptions=null] - Customize the Mixer API options. * If `null`, default production options are used. * @return {Promise} */ export default async function compileSVGs(mixerOptions = null) { if (mixerOptions == null) { mixerOptions = productionMixerOptions; } else if ( mixerOptions !== developmentMixerOptions && mixerOptions !== productionMixerOptions ) { mixerOptions = merge({}, defaultMixerOptions, mixerOptions); } /** * @async * @param {object} entry - The entrypoint to process. * @param {string[]} entry.includes - One or more paths to process. * @param {string} entry.outfile - The file to write to. * @param {?string} [entry.label] - The task label. * Defaults to the outfile name. * @return {Promise} */ loconfig.tasks.svgs?.forEach(async ({ includes, outfile, label = null }) => { if (!label) { label = basename(outfile || 'undefined'); } const timeLabel = `${label} compiled in`; console.time(timeLabel); try { if (!Array.isArray(includes)) { includes = [ includes ]; } includes = resolve(includes); outfile = resolve(outfile); const result = await mixer(includes, mixerOptions); await result.write(outfile); message(`${label} compiled`, 'success', timeLabel); } catch (err) { message(`Error compiling ${label}`, 'error'); message(err); notification({ title: `${label} compilation failed 🚨`, message: `${err.name}: ${err.message}` }); } }); }; ================================================ FILE: packages/landing/build/tasks/versions.js ================================================ import loconfig from '../helpers/config.js'; import message from '../helpers/message.js'; import resolve from '../helpers/template.js'; import { merge } from '../utils/index.js'; import { randomBytes } from 'node:crypto'; import events from 'node:events'; import { createReadStream, createWriteStream, } from 'node:fs'; import { mkdir, rename, rm, readFile, writeFile, } from 'node:fs/promises'; import { basename, dirname, } from 'node:path'; import readline from 'node:readline'; export const REGEXP_SEMVER = /^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; /** * @typedef {object} VersionOptions * @property {string|number|null} prettyPrint - A string or number to insert * white space into the output JSON string for readability purposes. * @property {string} versionFormat - The version number format. * @property {string|RegExp} versionKey - Either: * - A string representing the JSON field name assign the version number to. * * Explicit: * * ```json * "key": "json:version" * ``` * * Implicit: * * ```json * "key": "version" * ``` * * - A `RegExp` object or regular expression string prefixed with `regexp:`. * * ```json * "key": "regexp:(?<=^const ASSETS_VERSION = ')(?\\d+)(?=';$)" * ``` * * ```js * key: new RegExp('(?<=^const ASSETS_VERSION = ')(?\\d+)(?=';$)') * ``` * * ```js * key: /(?<=^const ASSETS_VERSION = ')(?\d+)(?=';$)/ * ``` */ /** * @const {VersionOptions} defaultVersionOptions - The default shared version options. * @const {VersionOptions} developmentVersionOptions - The predefined version options for development. * @const {VersionOptions} productionVersionOptions - The predefined version options for production. */ export const defaultVersionOptions = { prettyPrint: 4, versionFormat: 'timestamp', versionKey: 'version', }; export const developmentVersionOptions = Object.assign({}, defaultVersionOptions); export const productionVersionOptions = Object.assign({}, defaultVersionOptions); /** * @const {object} developmentVersionFilesArgs - The predefined `bumpVersion()` options for development. * @const {object} productionVersionFilesArgs - The predefined `bumpVersion()` options for production. */ export const developmentVersionFilesArgs = [ developmentVersionOptions, ]; export const productionVersionFilesArgs = [ productionVersionOptions, ]; /** * Bumps version numbers in file. * * @async * @param {object} [versionOptions=null] - Customize the version options. * If `null`, default production options are used. * @return {Promise} */ export default async function bumpVersions(versionOptions = null) { if (versionOptions == null) { versionOptions = productionVersionOptions; } else if ( versionOptions !== developmentVersionOptions && versionOptions !== productionVersionOptions ) { versionOptions = merge({}, defaultVersionOptions, versionOptions); } const queue = new Map(); /** * @async * @param {object} entry - The entrypoint to process. * @param {string} entry.outfile - The file to write to. * @param {?string} [entry.label] - The task label. * Defaults to the outfile name. * @param {?string} [entry.format] - The version number format. * @param {?string} [entry.key] - The JSON field name assign the version number to. * @param {?string|number} [entry.pretty] - The white space to use to format the JSON file. * @return {Promise} */ loconfig.tasks.versions?.forEach(({ outfile, label = null, ...options }) => { if (!label) { label = basename(outfile || 'undefined'); } options.pretty = (options.pretty ?? versionOptions.prettyPrint); options.format = (options.format ?? versionOptions.versionFormat); options.key = (options.key ?? versionOptions.versionKey); if (queue.has(outfile)) { queue.get(outfile).then(() => handleBumpVersion(outfile, label, options)); } else { queue.set(outfile, handleBumpVersion(outfile, label, options)); } }); }; /** * Creates a formatted version number or string. * * @param {string} format - The version format. * @param {?string} [oldValue] - The old version value. * @return {string|number} * @throws TypeError If the format or value are invalid. */ function createVersionNumber(format, oldValue = null) { let [ type, modifier ] = format.split(':', 2); switch (type) { case 'hex': case 'hexadecimal': try { modifier = Number.parseInt(modifier); if (Number.isNaN(modifier)) { modifier = 6; } return randomBytes(modifier).toString('hex'); } catch (err) { throw new TypeError( `${err.message} for \'format\' type "hexadecimal"`, { cause: err } ); } case 'inc': case 'increment': try { if (modifier === 'semver') { return incrementSemVer(oldValue, [ 'buildmetadata', 'patch' ]); } return incrementNumber(oldValue, modifier); } catch (err) { throw new TypeError( `${err.message} for \'format\' type "increment"`, { cause: err } ); } case 'regex': case 'regexp': try { return new RegExp(modifier); } catch (err) { throw new TypeError( `${err.message} for \'format\' type "regexp"`, { cause: err } ); } case 'timestamp': return Date.now().valueOf(); } throw new TypeError( 'Expected \'format\' to be either "timestamp", "increment", or "hexadecimal"' ); } /** * @async * @param {string} outfile * @param {string} label * @param {object} options * @return {Promise} */ async function handleBumpVersion(outfile, label, options) { const timeLabel = `${label} bumped in`; console.time(timeLabel); try { options.key = parseVersionKey(options.key); if (options.key instanceof RegExp) { await handleBumpVersionWithRegExp(outfile, label, options); } else { await handleBumpVersionInJson(outfile, label, options); } message(`${label} bumped`, 'success', timeLabel); } catch (err) { message(`Error bumping ${label}`, 'error'); message(err); notification({ title: `${label} bumping failed 🚨`, message: `${err.name}: ${err.message}` }); } } /** * Changes the version number for the provided JSON key in file. * * @async * @param {string} outfile * @param {string} label * @param {object} options * @param {string} options.key * @return {Promise} */ async function handleBumpVersionInJson(outfile, label, options) { outfile = resolve(outfile); let json; try { json = JSON.parse(await readFile(outfile, { encoding: 'utf8' })); } catch (err) { json = {}; message(`${label} is a new file`, 'notice'); await mkdir(dirname(outfile), { recursive: true }); } json[options.key] = createVersionNumber(options.format, json?.[options.key]); return await writeFile( outfile, JSON.stringify(json, null, options.pretty), { encoding: 'utf8' } ); } /** * Changes the version number for the provided RegExp in file. * * @async * @param {string} outfile * @param {string} label * @param {object} options * @param {RegExp} options.key * @return {Promise} */ async function handleBumpVersionWithRegExp(outfile, label, options) { outfile = resolve(outfile); const bckfile = `${outfile}~`; await rename(outfile, bckfile); try { const rl = readline.createInterface({ input: createReadStream(bckfile), }); let newVersion = null; const writeStream = createWriteStream(outfile, { encoding: 'utf8' }); rl.on('line', (line) => { const found = line.match(options.key); if (found) { const groups = (found.groups ?? {}); const oldVersion = (groups.build ?? groups.version ?? found[1] ?? found[0]); const newVersion = createVersionNumber(options.format, oldVersion); const replacement = found[0].replace(oldVersion, newVersion); line = line.replace(found[0], replacement); } writeStream.write(line + "\n"); }); await events.once(rl, 'close'); await rm(bckfile); } catch (err) { await rm(outfile, { force: true }); await rename(bckfile, outfile); throw err; } } /** * Increments the given integer. * * @param {string|int} value - The number to increment. * @param {string|int} [increment=1] - The amount to increment by. * @return {int} * @throws TypeError If the version number is invalid. */ function incrementNumber(value, increment = 1) { const version = Number.parseInt(value); if (Number.isNaN(version)) { throw new TypeError( `Expected an integer version number, received [${value}]` ); } increment = Number.parseInt(increment); if (Number.isNaN(increment)) { throw new TypeError( 'Expected an integer increment number' ); } return (version + increment); } /** * Increments the given SemVer version number. * * @param {string} value - The version to mutate. * @param {string|string[]} [target] - The segment to increment, one of: * 'major', 'minor', 'patch', ~~'prerelease'~~, 'buildmetadata'. * @param {string|int} [increment=1] - The amount to increment by. * @return {string} * @throws TypeError If the version or target are invalid. */ function incrementSemVer(value, target = 'patch', increment = 1) { const found = value.match(REGEXP_SEMVER); if (!found) { throw new TypeError( `Expected a SemVer version number, received [${value}]` ); } if (Array.isArray(target)) { for (const group of target) { if (found.groups[group] != null) { target = group; break; } } } if (!target || !found.groups[target]) { throw new TypeError( `Expected a supported SemVer segment, received [${target}]` ); } const segments = { 'major': '', 'minor': '.', 'patch': '.', 'prerelease': '-', 'buildmetadata': '+', }; let replacement = ''; for (const [ segment, delimiter ] of Object.entries(segments)) { if (found.groups?.[segment] != null) { const newVersion = (segment === target) ? incrementNumber(found.groups[segment], increment) : found.groups[segment]; replacement += `${delimiter}${newVersion}`; } } return value.replace(found[0], replacement); } /** * Parses the version key. * * @param {*} key - The version key. * @return {string|RegExp} */ function parseVersionKey(key) { if (key instanceof RegExp) { return key; } if (typeof key !== 'string') { throw new TypeError( 'Expected \'key\' to be either a string or a RegExp' ); } const delimiter = key.indexOf(':'); if (delimiter === -1) { // Assumes its a JSON key return key; } const type = key.slice(0, delimiter); const value = key.slice(delimiter + 1); switch (type) { case 'json': return value; case 'regex': case 'regexp': return new RegExp(value); } throw new TypeError( 'Expected \'key\' type to be either "json" or "regexp"' ); } ================================================ FILE: packages/landing/build/utils/index.js ================================================ /** * @file Provides generic functions and constants. */ /** * @type {RegExp} - Match all special characters. */ const regexUnescaped = /[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g; /** * Quotes regular expression characters. * * @param {string} str - The input string. * @return {string} Returns the quoted (escaped) string. */ function escapeRegExp(str) { return str.replace(regexUnescaped, '\\$&'); } /** * Creates a new object with all nested object properties * concatenated into it recursively. * * Nested keys are flattened into a property path: * * ```js * { * a: { * b: { * c: 1 * } * }, * d: 1 * } * ``` * * ```js * { * "a.b.c": 1, * "d": 1 * } * ``` * * @param {object} input - The object to flatten. * @param {string} prefix - The parent key prefix. * @param {object} target - The object that will receive the flattened properties. * @return {object} Returns the `target` object. */ function flatten(input, prefix, target = {}) { for (const key in input) { const field = (prefix ? prefix + '.' + key : key); if (isObjectLike(input[key])) { flatten(input[key], field, target); } else { target[field] = input[key]; } } return target; } /** * Determines whether the passed value is an `Object`. * * @param {*} value - The value to be checked. * @return {boolean} Returns `true` if the value is an `Object`, * otherwise `false`. */ function isObjectLike(value) { return (value != null && typeof value === 'object'); } /** * Creates a new object with all nested object properties * merged into it recursively. * * @param {object} target - The target object. * @param {object[]} ...sources - The source object(s). * @throws {TypeError} If the target and source are the same. * @return {object} Returns the `target` object. */ function merge(target, ...sources) { for (const source of sources) { if (target === source) { throw new TypeError( 'Cannot merge, target and source are the same' ); } for (const key in source) { if (source[key] != null) { if (isObjectLike(source[key]) && isObjectLike(target[key])) { merge(target[key], source[key]); continue; } else if (Array.isArray(source[key]) && Array.isArray(target[key])) { target[key] = target[key].concat(source[key]); continue; } } target[key] = source[key]; } } return target; } export { escapeRegExp, flatten, isObjectLike, merge, regexUnescaped, }; ================================================ FILE: packages/landing/build/watch.js ================================================ import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js'; import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js'; import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ; import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js'; import buildEleventy, { developmentEleventyOptions } from './tasks/eleventy.js'; import loconfig from './helpers/config.js'; import message from './helpers/message.js'; import notification from './helpers/notification.js'; import resolve from './helpers/template.js'; import { merge } from './utils/index.js'; import browserSync from 'browser-sync'; import { join } from 'node:path'; // Match a URL protocol. const regexUrlStartsWithProtocol = /^[a-z0-9\-]:\/\//i; // Build scripts, compile styles, concat files, // and generate spritesheets on first hit concatFiles(...developmentConcatFilesArgs); compileScripts(...developmentScriptsArgs); compileStyles(...developmentStylesArgs); compileSVGs(...developmentSVGsArgs); await buildEleventy(developmentEleventyOptions); // Create a new BrowserSync instance const server = browserSync.create(); // Start the BrowserSync server server.init(createServerOptions(loconfig), (err) => { if (err) { message('Error starting development server', 'error'); message(err); notification({ title: 'Development server failed', message: `${err.name}: ${err.message}` }); } }); configureServer(server, loconfig); /** * Configures the BrowserSync options. * * @param {BrowserSync} server - The BrowserSync API. * @param {object} loconfig - The project configset. * @param {object} loconfig.paths - The paths options. * @param {object} loconfig.tasks - The tasks options. * @return {void} */ function configureServer(server, { paths, tasks }) { const views = createViewsArray(paths.views); // Reload on any changes to views or processed files server.watch( [ join(paths.dest, '**/*') ] ).on('change', server.reload); // Watch source scripts server.watch( [ join(paths.scripts.src, '**/*.js'), ] ).on('change', () => { compileScripts(...developmentScriptsArgs); }); // Watch source concats if (tasks.concats?.length) { server.watch( resolve( tasks.concats.reduce( (patterns, { includes }) => patterns.concat(includes), [] ) ) ).on('change', () => { concatFiles(...developmentConcatFilesArgs); }); } // Watch source styles server.watch( [ join(paths.styles.src, '**/*.scss'), ] ).on('change', () => { compileStyles(...developmentStylesArgs); }); // Watch source SVGs server.watch( [ join(paths.svgs.src, '*.svg'), ] ).on('change', () => { compileSVGs(...developmentSVGsArgs); }); } /** * Creates a new object with all the BrowserSync options. * * @param {object} loconfig - The project configset. * @param {object} loconfig.paths - The paths options. * @param {object} loconfig.server - The server options. * @return {object} Returns the server options. */ function createServerOptions({ paths, server: options }) { const config = { open: false, notify: false, ghostMode: false }; // Resolve the URL for the BrowserSync server if (isNonEmptyString(paths.url)) { // Use proxy config.proxy = paths.url; } else if (isNonEmptyString(paths.dest)) { // Use base directory config.server = { baseDir: paths.dest }; } merge(config, resolve(options)); // If HTTPS is enabled, prepend `https://` to proxy URL if (options?.https) { if (isNonEmptyString(config.proxy?.target)) { config.proxy.target = prependSchemeToUrl(config.proxy.target, 'https'); } else if (isNonEmptyString(config.proxy)) { config.proxy = prependSchemeToUrl(config.proxy, 'https'); } } return config; } /** * Creates a new array (shallow-copied) from the views configset. * * @param {*} views - The views configset. * @throws {TypeError} If views is invalid. * @return {array} Returns the views array. */ function createViewsArray(views) { if (Array.isArray(views)) { return Array.from(views); } switch (typeof views) { case 'string': return [ views ]; case 'object': if (views != null) { return Object.values(views); } } throw new TypeError( 'Expected \'views\' to be a string, array, or object' ); } /** * Prepends the scheme to the URL. * * @param {string} url - The URL to mutate. * @param {string} [scheme] - The URL scheme to prepend. * @return {string} Returns the mutated URL. */ function prependSchemeToUrl(url, scheme = 'http') { if (regexUrlStartsWithProtocol.test(url)) { return url.replace(regexUrlStartsWithProtocol, `${scheme}://`); } return `${scheme}://${url}`; } /** * Determines whether the passed value is a string with at least one character. * * @param {*} value - The value to be checked. * @return {boolean} Returns `true` if the value is a non-empty string, * otherwise `false`. */ function isNonEmptyString(value) { return (typeof value === 'string' && value.length > 0); } ================================================ FILE: packages/landing/data/features.json ================================================ { "title": "Features", "description": "Locomotive Scroll is a thin, opinionated wrapper around Lenis. You get all of Lenis's power plus our detection and animation layer.", "scrollbar": { "title": "Native scrollbar", "description": "Real browser scrollbar. No fake alternatives. Accessible, performant, and familiar to users." }, "normalized": { "title": "Smooth easing", "description": "Configurable lerp, duration, and custom easing functions thanks to Lenis options." }, "sticky": { "title": "CSS sticky", "description": "Works perfectly with position: sticky. No conflicts, no workarounds." }, "intersectionObserver": { "title": "Intersection Observer API", "description": "Browser-native detection. No polling, no performance hits, just efficient viewport tracking." }, "scrollTo": { "title": "Scroll to", "description": "Programmatic scrolling to any element, selector, or pixel value via Lenis. Supports offset, duration, and custom easing." }, "direction": { "title": "Scroll direction", "description": "Track direction changes in real-time thanks to Lenis. Perfect for hiding/showing headers or reversing animations." }, "layoutShifts": { "title": "No layout shifts", "description": "No more greedy CSS transforms breaking your layouts. Plays nice with position: sticky, fixed headers, and existing CSS." } } ================================================ FILE: packages/landing/data/general.json ================================================ { "title": "Locomotive Scroll", "description": "A lightweight scroll library for modern web experiences. Detection, animation, and smooth scrolling — all in 9.4kB. Built on top of Lenis.", "github": "https://github.com/locomotivemtl/locomotive-scroll", "documentation": "https://scroll.locomotive.ca/docs", "website": "https://locomotive.ca", "version": "5.0" } ================================================ FILE: packages/landing/data/metadata.json ================================================ { "title": "Locomotive Scroll — Detection of elements in viewport & smooth scrolling with parallax effects", "description": "Locomotive Scroll is a lightweight JavaScript library that provides smooth scrolling animations and advanced scroll interactions for web applications.", "ogImage": "https://scroll.locomotive.ca/assets/images/og-image.png", "ogImageWidth": "1200", "ogImageHeight": "630", "url": "https://scroll.locomotive.ca" } ================================================ FILE: packages/landing/data/perks.json ================================================ { "bigText": "Version 5 is a complete rewrite. Designed for modern workflows, built on top of Lenis, and optimized for production.", "smallText": "This library has evolved considerably over the years. From jQuery to vanilla ES6, from custom engines to Lenis foundation.", "items": [ { "title": "Built on top of Lenis", "description": "Latest stable release with improved touch handling and performance fixes. No more greedy CSS transforms!" }, { "title": "TypeScript First", "description": "Fully typed. Better autocomplete, fewer bugs, happier developers." }, { "title": "Dual Intersection Observers", "description": "Separate observers for simple triggers vs. continuous animations. No wasted RAF cycles." }, { "title": "Smart Touch Detection", "description": "Parallax auto-disabled on mobile. Opt-in with one attribute." }, { "title": "Production Ready", "description": "Accessible by default. Native scrollbar, keyboard nav, and proper cleanup for SPAs." }, { "title": "Lightweight", "description": "Only 9.4kB gzipped" } ] } ================================================ FILE: packages/landing/data/showcase.json ================================================ { "title": "Showcase", "description": "Real projects built with Locomotive Scroll by leading studios and developers.", "items": [ { "title": "Locomotive", "description": "https://locomotive.ca", "url": "https://locomotive.ca" }, { "title": "Destigmatize", "description": "https://2024.destigmatize.ca/", "url": "https://2024.destigmatize.ca/" }, { "title": "Scout Motors", "description": "https://scoutmotors.com/", "url": "https://scoutmotors.com/" }, { "title": "Lightship", "description": "https://lightshiprv.com/", "url": "https://lightshiprv.com/" }, { "title": "Vooban", "description": "https://vooban.com/", "url": "https://vooban.com/" }, { "title": "Construction Desourdy", "description": "https://constructiondesourdy.com/", "url": "https://constructiondesourdy.com/" }, { "title": "GKC", "description": "https://gkc.ca/", "url": "https://gkc.ca/" }, { "title": "Troa", "description": "https://www.troa.fr/", "url": "https://www.troa.fr/" }, { "title": "Vazzi", "description": "https://vazzi.fun/", "url": "https://vazzi.fun/" }, { "title": "21TSI", "description": "https://21tsi.com/", "url": "https://21tsi.com/" }, { "title": "Eduard Bodak", "description": "https://www.eduardbodak.com/", "url": "https://www.eduardbodak.com/" }, { "title": "Mindmarket", "description": "https://mindmarket.com/", "url": "https://mindmarket.com/" } ] } ================================================ FILE: packages/landing/data/tools.json ================================================ { "inview": { "title": "In-view detection", "description": "Add classes, trigger callbacks, or fire custom events when elements enter the viewport. Built on Intersection Observer for native performance." }, "progress": { "title": "Progress tracking", "description": "Get real-time scroll progress (0-1) as CSS variables or JavaScript events. Perfect for progress bars, scroll-driven animations, or custom interactions." }, "parallax": { "title": "Parallax effects", "description": "Create smooth parallax effects instantly with a single data-scroll-speed attribute. No complex setup, no math, just add a number and it works." } } ================================================ FILE: packages/landing/docs/development.md ================================================ # Development * [Installation](#installation) * [Usage](#usage) * [Configuration](#configuration) * [Environment Configuration](#environment-configuration) * [Development Configuration](#development-configuration) * [`paths` option](#paths-option) * [`paths.url` option](#pathsurl-option) * [`paths.dest` option](#pathsdest-option) * [`tasks` option](#tasks-option) * [`server` option](#server-option) * [Tasks](#tasks) * [`concats`](#concats) * [`scripts`](#scripts) * [`styles`](#styles) * [`svgs`](#svgs) * [`versions`](#versions) --- The boilerplate provides a custom, easily configured, and very simple, task runner for [Node] to process assets and test quickly in browsers. Learn more about the boilerplate's [tasks](#tasks) below. ## Installation Make sure you have the following installed: * [Node] — at least 14.17, the latest LTS is recommended. * [NPM] — at least 8.0, the latest LTS is recommended. > 💡 You can use [NVM] to install and use different versions of Node via the command-line. ```sh # Switch to recommended Node version from .nvmrc nvm use # Install dependencies from package.json npm install ``` ## Usage ```sh # Start development server, watch for changes, and compile assets npm start # Compile and minify assets npm run build ``` See [`build.js`](../build/build.js) and [`watch.js`](../build/watch.js) for details. ## Configuration For development, most configuration values for processing front-end assets are defined in the [`loconfig.json`](../loconfig.json) file that exists at the root directory of your project. ### Environment Configuration If any configuration options vary depending on whether your project is running on your computer, a collaborator's computer, or on a web server, these values should be stored in a `loconfig.local.json` file. In fresh copy of the boilerplate, the root directory of your project will contain a [`loconfig.example.json`](../loconfig.example.json) file. > 💡 The boilerplate's default example customizes the development server > to use a custom SSL certificate. That file can be copied to `loconfig.local.json` and customized to suit your local environment. Your `loconfig.local.json` _should not_ be committed to your project's source control. > 💡 If you are developing with a team, you may wish to continue > including a `loconfig.example.json` file with your project. ### Development Configuration The boilerplate provides a few configuration settings to control the behaviour for processing front-end assets. #### `paths` option The `paths` option defines URIs and file paths. It is primarily used for template tags to reference any configuration properties to reduce repetition. Template tags are specified using `{% %}` delimiters. They will be automatically expanded when tasks process paths. ```jsonc { "paths": { "styles": { "src": "./assets/styles", "dest": "./www/assets/styles" } }, "tasks": { "styles": [ { "infile": "{% paths.styles.src %}/main.scss", // → ./assets/styles/main.scss "outfile": "{% paths.styles.dest %}/main.css" // → ./www/assets/styles/main.css } ] } } ``` #### `paths.url` option The `paths.url` option defines the base URI of the project. By default, it is used by the development server as a proxy for an existing virtual host. ```json { "paths": { "url": "locomotive-boilerplate.test" } } ``` #### `paths.dest` option The `paths.dest` option defines the public web directory of the project. By default, it is used by the development server as the base directory to serve the website from if a proxy URI is not provided. ```json { "paths": { "dest": "./www" } } ``` #### `tasks` option Which assets and how they should be processed can be configured via the `tasks` option: ```json { "tasks": { "scripts": [ { "includes": [ "./assets/scripts/app.js" ], "outfile": "./www/assets/scripts/app.js" } ], "styles": [ { "infile": "./assets/styles/main.scss", "outfile": "./www/assets/styles/main.css" } ] } } ``` See [tasks](#tasks) section, below, for details. #### `server` option The development server (BrowserSync) can be configured via the `server` option: ```json { "server": { "open": true, "https": { "key": "~/.config/valet/Certificates/{% paths.url %}.key", "cert": "~/.config/valet/Certificates/{% paths.url %}.crt" } } } ``` Visit [BrowserSync's documentation](https://browsersync.io/docs/options) for all options. ## Tasks The boilerplate provides a handful of tasks for handling the most commonly processed assets. ### `concats` A wrapper around [concat] (with optional support for globbing) for concatenating multiple files. By default, [tiny-glob] is installed with the boilerplate. Example: ```json { "concats": [ { "label": "Application Vendors", "includes": [ "{% paths.scripts.src %}/vendors/*.js", "node_modules/focus-visible/dist/focus-visible.min.js", "node_modules/vue/dist/vue.min.js", "node_modules/vuelidate/dist/vuelidate.min.js", "node_modules/vuelidate/dist/validators.min.js" ], "outfile": "{% paths.scripts.dest %}/app/vendors.js" }, { "label": "Public Site Vendors", "includes": [ "{% paths.scripts.src %}/vendors/*.js", "node_modules/focus-visible/dist/focus-visible.min.js" ], "outfile": "{% paths.scripts.dest %}/site/vendors.js" } ] } ``` See [`concats.js`](../build/tasks/concats.js) for details. ### `scripts` A wrapper around [esbuild] for bundling and minifying modern JS/ES modules. Example: ```json { "scripts": [ { "label": "Application Dashboard JS", "includes": [ "{% paths.scripts.src %}/app/dashboard.js" ], "outfile": "{% paths.scripts.dest %}/app/dashboard.js" }, { "label": "Public Site JS", "includes": [ "{% paths.scripts.src %}/site/main.js" ], "outfile": "{% paths.scripts.dest %}/site/main.js" } ] } ``` See [`scripts.js`](../build/tasks/scripts.js) for details. ### `styles` A wrapper around [sass] (with optional support for [Autoprefixer] via [PostCSS]) for compiling and minifying Sass into CSS. By default, [PostCSS] and [Autoprefixer] are installed with the boilerplate. Example: ```json { "styles": [ { "label": "Text Editor CSS", "infile": "{% paths.styles.src %}/app/editor.scss", "outfile": "{% paths.styles.dest %}/app/editor.css" }, { "label": "Application Dashboard CSS", "infile": "{% paths.styles.src %}/app/dashboard.scss", "outfile": "{% paths.styles.dest %}/app/dashboard.css" }, { "label": "Public Site Critical CSS", "infile": "{% paths.styles.src %}/site/critical.scss", "outfile": "{% paths.styles.dest %}/site/critical.css" }, { "label": "Public Site CSS", "infile": "{% paths.styles.src %}/site/main.scss", "outfile": "{% paths.styles.dest %}/site/main.css" } ] } ``` See [`styles.js`](../build/tasks/styles.js) for details. The task also supports [PurgeCSS] to remove unused CSS. See the [documentation on our Grid System](grid.md#build-tasks) for details. ### `svgs` A wrapper around [SVG Mixer] for transforming and minifying SVG files and generating spritesheets. Example: ```json { "svgs": [ { "label": "Application Spritesheet", "includes": [ "{% paths.images.src %}/app/*.svg" ], "outfile": "{% paths.svgs.dest %}/app/sprite.svg" }, { "label": "Public Site Spritesheet", "includes": [ "{% paths.images.src %}/site/*.svg" ], "outfile": "{% paths.svgs.dest %}/site/sprite.svg" } ] } ``` See [`svgs.js`](../build/tasks/svgs.js) for details. ### `versions` A task to create and update values for use in versioning assets. Can generate a hexadecimal value (using random bytes), use the current timestamp, or increment a number. Example: ```json { "versions": [ { "format": "timestamp", "key": "now", "outfile": "./assets.json" }, { "format": "hex:8", "key": "hex", "outfile": "./assets.json" }, { "format": "inc:semver", "key": "inc", "outfile": "./assets.json" } ] } ``` ```json { "now": 1665071717350, "hex": "6ef54181c4ba", "hex": "1.0.2" } ``` The task supports replacing the value of a data key in a JSON file or replacing a string in a file using a [regular expression](RegExp). * Explicit JSON field name: ```json { "key": "json:version" } ``` * Implicit JSON field name: ```json { "key": "version" } ``` The regular expression can be a `RegExp` object or a pattern prefixed with `regexp:`. * ```json { "key": "regexp:(?<=^const ASSETS_VERSION = ')(?\\d+)(?=';$)" } ``` * ```js { key: new RegExp('(?<=^const ASSETS_VERSION = ')(?\\d+)(?=';$)') } ``` * ```js { key: /^ \* Version: +(?:.+?)\+(.+?)$/ } ``` The regular expression pattern will match the first occurrence and replace the first match in the following order: `build` (named capture), `version` (named capture), `1` (first capture), or `0` (whole match). See [`versions.js`](../build/tasks/versions.js) for details. [Autoprefixer]: https://npmjs.com/package/autoprefixer [BrowserSync]: https://npmjs.com/package/browser-sync [concat]: https://npmjs.com/package/concat [esbuild]: https://npmjs.com/package/esbuild [fast-glob]: https://npmjs.com/package/fast-glob [glob]: https://npmjs.com/package/glob [globby]: https://npmjs.com/package/globby [Node]: https://nodejs.org/ [sass]: https://npmjs.com/package/sass [NPM]: https://npmjs.com/ [NVM]: https://github.com/nvm-sh/nvm [PostCSS]: https://npmjs.com/package/postcss [PurgeCSS]: https://purgecss.com/ [RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp [SVG Mixer]: https://npmjs.com/package/svg-mixer [tiny-glob]: https://npmjs.com/package/tiny-glob ================================================ FILE: packages/landing/docs/grid.md ================================================ # Grid system * [Architectures](#architecture) * [Build tasks](#build-tasks) * [Configuration](#configuration) * [Usage](#usage) * [Example](#example) ## Architecture The boilerplate's grid system is meant to be simple and easy to use. The goal is to create a light, flexible, and reusable way to build layouts. The following styles are needed to work properly: * [`o-grid`](../assets/styles/objects/_grid.scss) — Object file where the default grid styles are set such as column numbers, modifiers, and options. * [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) — Utility file that generates the styles for every possible column based on an array of media queries and column numbers. ### Build tasks The columns generated by [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) adds a lot of styles to the compiled CSS file. To mitigate that, [PurgeCSS] is integrated into the `styles` build task to purge unused CSS. #### Configuration Depending on your project, you will need to specify all the files that include CSS classes from the grid system. These files will be scanned by [PurgeCSS] to your compiled CSS files. Example of a Charcoal project: ```jsonc "purgeCSS": { "content": [ "./views/app/template/**/*.mustache", "./src/App/Template/*.php", "./assets/scripts/**/*" // use case: `el.classList.add('u-gc-1/2')` ] } ``` ## Usage The first step is to set intial SCSS values in the following files : - [`settings/_config.scss`](../assets/styles/settings/_config.scss) ```scss // Grid // ========================================================================== $base-column-nb: 12; $base-column-gap: $unit-small; ``` You can create multiple column layouts depending on media queries. - [`objects/_grid.scss`](../assets/styles/objects/_grid.scss) ```scss .o-grid { display: grid; width: 100%; margin: 0; padding: 0; list-style: none; // ========================================================================== // Cols // ========================================================================== &.-col-#{$base-column-nb} { grid-template-columns: repeat(#{$base-column-nb}, 1fr); } &.-col-4 { grid-template-columns: repeat(4, 1fr); } &.-col-#{$base-column-nb}\@from-medium { @media (min-width: $from-medium) { grid-template-columns: repeat(#{$base-column-nb}, 1fr); } } // … ``` ### Example The following layout has 4 columns at `>=999px` and 12 columns at `<1000px`. ```html

Hello

This grid has 4 columns and 12 columns from `medium` MQ

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?

``` [PurgeCSS]: https://purgecss.com/ ================================================ FILE: packages/landing/docs/technologies.md ================================================ # Technologies * [Styles](#styles) * [CSS Architecture](#css-architecture) * [CSS Naming Convention](#css-naming-convention) * [CSS Namespacing](#css-namespacing) * [Example](#example-1) * [Scripts](#scripts) * [Example](#example-2) * [Page transitions](#page-transitions) * [Example](#example-3) * [Scroll detection](#scroll-detection) * [Example](#example-4) ## Styles [SCSS][Sass] is a superset of CSS that adds many helpful features to improve and modularize our styles. We use [node-sass] (LibSass) for processing and minifying SCSS into CSS. We also use [PostCSS] and [Autoprefixer] to parse our CSS and add vendor prefixes for experimental features. ### CSS Architecture The boilerplate's CSS architecture is based on [Inuit CSS][inuitcss] and [ITCSS]. * `settings`: Global variables, site-wide settings, config switches, etc. * `tools`: Site-wide mixins and functions. * `generic`: Low-specificity, far-reaching rulesets (e.g. resets). * `elements`: Unclassed HTML elements (e.g. `a {}`, `blockquote {}`, `address {}`). * `objects`: Objects, abstractions, and design patterns (e.g. `.o-layout {}`). * `components`: Discrete, complete chunks of UI (e.g. `.c-carousel {}`). * `utilities`: High-specificity, very explicit selectors. Overrides and helper classes (e.g. `.u-hidden {}`) Learn more about [Inuit CSS](https://github.com/inuitcss/inuitcss#css-directory-structure). ### CSS Naming Convention We use a simplified [BEM] (Block, Element, Modifier) syntax: * `.block` * `.block_element` * `.-modifier` ### CSS Namespacing We namespace our classes for more UI transparency: * `o-`: Object that it may be used in any number of unrelated contexts to the one you can currently see it in. Making modifications to these types of class could potentially have knock-on effects in a lot of other unrelated places. * `c-`: Component is a concrete, implementation-specific piece of UI. All of the changes you make to its styles should be detectable in the context you’re currently looking at. Modifying these styles should be safe and have no side effects. * `u-`: Utility has a very specific role (often providing only one declaration) and should not be bound onto or changed. It can be reused and is not tied to any specific piece of UI. * `s-`: Scope creates a new styling context. Similar to a Theme, but not necessarily cosmetic, these should be used sparingly—they can be open to abuse and lead to poor CSS if not used wisely. * `is-`, `has-`: Is currently styled a certain way because of a state or condition. It tells us that the DOM currently has a temporary, optional, or short-lived style applied to it due to a certain state being invoked. Learn about [namespacing](https://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/). ### Example \#1 ```html
Heading
Button
``` ```scss .c-block { &.-large { padding: rem(60px); } } .c-block_heading { @media (max-width: $to-medium) { .c-block.-large & { margin-bottom: rem(40px); } } } ``` ## Scripts We use [esbuild] for bundling and minifying JavaScript/ES modules. [modularJS] is a small framework we use on top of ES modules. * Automatically init visible modules. * Easily call other modules methods. * Quickly set scoped events with delegation. * Simply select DOM elements scoped in their module. [_source_](https://npmjs.com/package/modujs#why) ### Example \#2 ```html

Example

``` ```js import { module } from 'modujs'; export default class extends module { constructor(m) { super(m); this.events = { click: { load: 'loadMore' } }; } loadMore() { this.$('main')[0].classList.add('is-loading'); } } ``` Learn more about [modularJS]. ## Page transitions [modularLoad] is used for page transitions and lazy loading. ### Example \#3 ```html
Home Page
``` ```js import modularLoad from 'modularload'; this.load = new modularLoad({ enterDelay: 300, transitions: { transitionName: { enterDelay: 450 } } }); ``` Learn more about [modularLoad]. ## Scroll detection [Locomotive Scroll][locomotive-scroll] is used for elements in viewport detection and smooth scrolling with parallax. ### Example \#4 ```html
Trigger
Parallax
``` ```js import LocomotiveScroll from 'locomotive-scroll'; this.scroll = new LocomotiveScroll({}) ```` Learn more about [Locomotive Scroll][locomotive-scroll]. [Autoprefixer]: https://npmjs.com/package/autoprefixer [BEM]: https://bem.info/ [BrowserSync]: https://npmjs.com/package/browser-sync [esbuild]: https://npmjs.com/package/esbuild [inuitcss]: https://github.com/inuitcss/inuitcss [ITCSS]: https://itcss.io/ [locomotive-scroll]: https://npmjs.com/package/locomotive-scroll [modularJS]: https://npmjs.com/package/modujs [modularLoad]: https://npmjs.com/package/modularload [node-sass]: https://npmjs.com/package/node-sass [PostCSS]: https://npmjs.com/package/postcss [Sass]: https://sass-lang.com/ [svg-mixer]: https://npmjs.com/package/svg-mixer [Node]: https://nodejs.org/ [NPM]: https://npmjs.com/ [NVM]: https://github.com/nvm-sh/nvm ================================================ FILE: packages/landing/eleventy.config.cjs ================================================ // Import required modules const twig = require("twig"); const fs = require('fs'); const path = require('path'); const eleventyPluginTwig = require("@factorial/eleventy-plugin-twig"); // Export Eleventy configuration module.exports = function(eleventyConfig) { // Load assets.json version const assetsJsonPath = path.resolve(__dirname, '../../www/landing/assets.json'); let assetsVersion = null; function getAssetsVersion() { if (assetsVersion === null) { try { const assetsData = JSON.parse(fs.readFileSync(assetsJsonPath, 'utf-8')); assetsVersion = assetsData.version; } catch(error) { console.warn('Could not load assets.json, using timestamp instead'); assetsVersion = Date.now(); } } return assetsVersion; } eleventyConfig.setServerOptions({ enabled: false, }); eleventyConfig.addWatchTarget("views/**/*"); // Use twig eleventyConfig.addPlugin(eleventyPluginTwig, { twig: { namespaces: { layouts: "views/layouts", partials: "views/partials", snippets: "views/snippets" } }, dir: { input: 'views/templates', output: '../../www/landing' } }); twig.extendFilter("log", function(value) { return '
'+JSON.stringify(value)+'
'; }); twig.extendFilter("slug", function(str) { return str .toLowerCase() // Convert the string to lowercase .replace(/\s+/g, '-') // Replace spaces with dashes .replace(/[^\w-]/g, '') // Remove non-word characters .replace(/--+/g, '-') // Replace consecutive dashes with a single dash .trim(); // Trim any leading or trailing dashes }); // Add a filter to append the version to asset URLs from assets.json twig.extendFilter('asset', function(url) { const version = getAssetsVersion(); return `${url}?v=${version}`; }); // Add global data for assets version eleventyConfig.addGlobalData('assetsVersion', function() { return getAssetsVersion(); }); twig.extendFunction("critical_css", function() { try { return fs.readFileSync('../../www/landing/assets/styles/critical.css', 'utf-8') } catch(error) { return ''; } }); // Configure and return Eleventy settings return { templateFormats: ['twig'], dir: { input: 'views/templates', output: '../../www/landing', data: '../../data' } }; }; ================================================ FILE: packages/landing/loconfig.example.json ================================================ { "server": { "https": { "key": "~/.config/valet/Certificates/{% paths.url %}.key", "cert": "~/.config/valet/Certificates/{% paths.url %}.crt" } } } ================================================ FILE: packages/landing/loconfig.json ================================================ { "paths": { "url": "locomotive-scroll-website.test", "src": "./assets", "dest": "../../www/landing", "images": { "src": "./assets/images" }, "styles": { "src": "./assets/styles", "dest": "../../www/landing/assets/styles" }, "scripts": { "src": "./assets/scripts", "dest": "../../www/landing/assets/scripts" }, "svgs": { "src": "./assets/images/sprite", "dest": "../../www/landing/assets/images" }, "views": { "src": "./views/" }, "data": { "src": "./data/" } }, "tasks": { "concats": [ { "includes": [ "{% paths.scripts.src %}/vendors/*.js" ], "outfile": "{% paths.scripts.dest %}/vendors.js" } ], "scripts": [ { "includes": [ "{% paths.scripts.src %}/app.js" ], "outfile": "{% paths.scripts.dest %}/app.js" } ], "styles": [ { "infile": "{% paths.styles.src %}/critical.scss", "outfile": "{% paths.styles.dest %}/critical.css" }, { "infile": "{% paths.styles.src %}/main.scss", "outfile": "{% paths.styles.dest %}/main.css" } ], "svgs": [ { "includes": [ "{% paths.svgs.src %}/*.svg" ], "outfile": "{% paths.svgs.dest %}/sprite.svg" } ], "purgeCSS": { "content": [ "../../www/landing/**/*.html", "./views/**/*.twig", "../../www/landing/assets/scripts/**/*" ] }, "versions": [ { "outfile": "../../www/landing/assets.json" } ] } } ================================================ FILE: packages/landing/package.json ================================================ { "private": true, "name": "@locomotivemtl/boilerplate", "title": "Locomotive Boilerplate", "version": "1.0.0", "author": "Locomotive ", "type": "module", "engines": { "node": ">=20.1", "npm": ">=8.0" }, "scripts": { "start": "node --experimental-json-modules --no-warnings build/watch.js", "build": "node --experimental-json-modules --no-warnings build/build.js" }, "dependencies": { "@11ty/eleventy-img": "^4.0.2", "@vercel/analytics": "^1.6.1", "gsap": "^3.14.2", "modujs": "^1.4.2", "modularload": "^1.2.6", "normalize.css": "^8.0.1", "svg4everybody": "^2.1.9" }, "devDependencies": { "@11ty/eleventy": "^2.0.1", "@factorial/eleventy-plugin-twig": "^0.1.2", "autoprefixer": "^10.4.13", "browser-sync": "^3.0.2", "concat": "^1.0.3", "esbuild": "^0.17.6", "kleur": "^4.1.5", "node-notifier": "^10.0.1", "postcss": "^8.4.21", "purgecss": "^5.0.0", "sass": "^1.69.5", "svg-mixer": "~2.3.14", "tiny-glob": "^0.2.9", "twig": "^1.16.0" }, "overrides": { "browser-sync": { "ua-parser-js": "~1.0.33" }, "svg-mixer": { "postcss": "^8.4.20" } } } ================================================ FILE: packages/landing/views/layouts/base.twig ================================================ {% if title %}{{ title }} | {% endif %}{{ metadata.title }} {# Open Graph meta tags #} {# Twitter Card meta tags #}
{% block beforeMain %}
{% include "@partials/header.twig" %} {% endblock %}
{% block content %}{% endblock %} {% block afterContent %} {% include "@partials/footer.twig" %} {% endblock %}
{% block afterMain %}
{% endblock %}
================================================ FILE: packages/landing/views/layouts/features.twig ================================================
{% include "@partials/section-heading.twig" with { "title": [ 'Features' ], "attr": { 'data-scroll': true, 'data-scroll-speed': -0.1 } } %}
⛵
{{ features.description }}
Visit Lenis website {% include "@snippets/icon.twig" with { icon: 'arrow-external', classes: 'c-features-grid_icon' } %} {% include "@snippets/icon.twig" with { icon: 'arrow-external', classes: 'c-features-grid_icon' } %}
Lenis Lenis {% include "@snippets/icon.twig" with { icon: 'logo-lenis', classes: 'c-features-grid_logo' } %}
Visit Locomotive website {% include "@snippets/icon.twig" with { icon: 'arrow-external', classes: 'c-features-grid_icon' } %} {% include "@snippets/icon.twig" with { icon: 'arrow-external', classes: 'c-features-grid_icon' } %}
Locomotive Locomotive {% include "@snippets/icon.twig" with { icon: 'logo-locomotive', classes: 'c-features-grid_logo' } %}
01

{{ features.scrollbar.title }}

{{ features.scrollbar.description }}

02

{{ features.normalized.title }}

{{ features.normalized.description }}

03

{{ features.sticky.title }}

{{ features.sticky.description }}

04

{{ features.intersectionObserver.title }}

{{ features.intersectionObserver.description }}

05 {{ features.scrollTo.title }} {{ features.scrollTo.description }}
06

{{ features.direction.title }}

↓ ↑

{{ features.direction.description }}

07

{{ features.layoutShifts.title }}

{{ features.layoutShifts.description }}

================================================ FILE: packages/landing/views/layouts/hero.twig ================================================

🟦🔁 Locomotive® Scroll

{{ general.description }}

🔻 Version {{ general.version }}

  • {% include "@snippets/button.twig" with { label: "View on Github", icon: "github", href: general.github, attr: 'data-load="false"' } %}
  • {% include "@snippets/button.twig" with { label: "Read documentation", icon: "doc", href: general.documentation, modifiers: '-stroke', attr: 'data-load="false"' } %}
================================================ FILE: packages/landing/views/layouts/perks.twig ================================================
{% include "@partials/rail.twig" %}
{# List #} {% include "@partials/list.twig" with { items: perks.items, modifiers: "-perks" } %} {# Big text #}

{{ perks.bigText }}

{# Cascade #}
⛵
{{ perks.smallText }}
{% for index in 1..8 %}
Locomotive® Scroll
{% endfor %}

👀

================================================ FILE: packages/landing/views/layouts/showcase.twig ================================================
⛵
{{showcase.description}}

{{showcase.title}}

{% include "@partials/list.twig" with { items: showcase.items, modifiers: "-showcase", link: true } %}
================================================ FILE: packages/landing/views/layouts/tools.twig ================================================
{% include "@partials/section-heading.twig" with { title: [ 'Built-in', 'Tools' ], labelLeft: '🔴Work faster', labelRight: 'Work smarter🔴', } %}
01 {{ tools.inview.title }} {{ tools.inview.description }}

{{ tools.inview.title }}

{% include "@snippets/icon.twig" with { icon: 'inview-01', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'inview-02', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'inview-03', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'inview-04', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'inview-05', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'inview-06', modifiers: 'c-tool_shape_svg' } %}
02 {{ tools.progress.title }} {{ tools.progress.description }}

{{ tools.progress.title }}

{% include "@snippets/icon.twig" with { icon: 'progress-01', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'progress-02', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'progress-03', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'progress-04', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'progress-05', modifiers: 'c-tool_shape_svg' } %}
02 {{ tools.parallax.title }} {{ tools.parallax.description }}

{{ tools.parallax.title }}

{% include "@snippets/icon.twig" with { icon: 'parallax-01', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'parallax-02', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'parallax-03', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'parallax-04', modifiers: 'c-tool_shape_svg' } %}
{% include "@snippets/icon.twig" with { icon: 'parallax-05', modifiers: 'c-tool_shape_svg' } %}
================================================ FILE: packages/landing/views/partials/footer.twig ================================================

npm

npm install

npm install locomotive-scroll

npm install

npm

locomotive

locomotive-scroll

locomotive-scroll v5.x

locomotive-scroll v5.x

locomotive-scroll

locomotive

data

data-scroll

data-scroll-speed

data-scroll-class

data-scroll-repeat

data

data-scroll

data-scroll-position

data-scroll-call

data-scroll-event-progress

We hope that you enjoy our library.

🟦🔁 Locomotive® Scroll

Made by Locomotive

  • {% include "@snippets/button.twig" with { label: "View on Github", icon: "github", href: general.github, attr: 'data-load="false"' } %}
  • {% include "@snippets/button.twig" with { label: "Read documentation", icon: "doc", href: general.documentation, modifiers: '-stroke', attr: 'data-load="false"' } %}
================================================ FILE: packages/landing/views/partials/header.twig ================================================

{{ general.title }}

Version {{ general.version }}

Github↗
Documentation↗

©2026

================================================ FILE: packages/landing/views/partials/list.twig ================================================ {# List #} {% set _items = items %} {% set _classes = classes | default(null) %} {% set _modifiers = modifiers | default(null) %} {% set _link = link | default(null) %}
    {% for item in _items %}
  • {% if _link %} {{ item.title }} {{ item.description }} ↗ {% else %}

    {{ item.title }}

    {{ item.description }}

    {% endif %}
  • {% endfor %}
================================================ FILE: packages/landing/views/partials/rail.twig ================================================

Version ({{ general.version }}) 🏢

================================================ FILE: packages/landing/views/partials/section-heading.twig ================================================

{{ title[0] }} {{ title[1] }} {% if labelLeft %} {{ labelLeft|raw }} {% endif %} {% if labelRight %} {{ labelRight|raw }} {% endif %}

================================================ FILE: packages/landing/views/snippets/button.twig ================================================ {# --- Parameters ------------------------- #} {% set _tag = tag | default('button') %} {% set _href = href | default(null) %} {% set _external = external ?? false %} {% set _classes = classes | default(null) %} {% set _modifiers = modifiers | default(null) %} {% set _label = label | default(null) %} {% set _icon = icon | default(null) %} {% set _attr = attr | default('') %} {# --- Computed --------------------------- #} {% if _href != null %} {% set _tag = 'a' %} {% elseif _tag == 'a' %} {% set _tag = 'span' %} {% endif %} {% set _button_classes = 'c-button' %} {% if _modifiers %} {% set _button_classes = _button_classes ~ ' ' ~ _modifiers %} {% endif %} {% if _classes %} {% set _button_classes = _button_classes ~ ' ' ~ _classes %} {% endif %} {# ---------------------------------------- #} <{{ _tag }} class="{{ _button_classes }}" {% if _href %}href="{{ _href }}"{% endif %} {% if _external %}target="_blank" rel="noopener noreferrer"{% endif %} {% if _attr %}{{ _attr|raw }}{% endif %} > {% for index in 1..2 %} {% if _icon %} {% include "@snippets/icon.twig" with { icon: _icon, classes: 'c-button_icon', } only %} {% endif %} {% if _label %} {{ _label }} {% endif %} {% endfor %} ================================================ FILE: packages/landing/views/snippets/icon.twig ================================================ {# --- Parameters ------------------------- #} {% set _icon = icon %} {% set _classes = classes | default(null) %} {% set _modifiers = modifiers | default(null) %} {# ---------------------------------------- #} {% if _icon %} {% endif %} ================================================ FILE: packages/landing/views/templates/index.twig ================================================ {% extends "@layouts/base.twig" %} {% block content %} {% include "@layouts/hero.twig" %} {% include "@layouts/perks.twig" %} {% include "@layouts/tools.twig" %} {% include "@layouts/features.twig" %} {% include "@layouts/showcase.twig" %} {% endblock %} ================================================ FILE: packages/lib/README.md ================================================ # Locomotive Scroll [![npm version](https://img.shields.io/npm/v/locomotive-scroll.svg)](https://www.npmjs.com/package/locomotive-scroll) [![npm downloads](https://img.shields.io/npm/dm/locomotive-scroll.svg)](https://www.npmjs.com/package/locomotive-scroll) [![bundle size](https://img.shields.io/bundlephobia/minzip/locomotive-scroll)](https://bundlephobia.com/package/locomotive-scroll) A **lightweight** & **modern** scroll library for detection, animation, and smooth scrolling. Built on top of [Lenis](https://github.com/darkroomengineering/lenis). ## Documentation Full documentation available at [scroll.locomotive.ca/docs](https://scroll.locomotive.ca/docs). ## Quick Start ```bash npm install locomotive-scroll ``` ```js import LocomotiveScroll from 'locomotive-scroll'; const scroll = new LocomotiveScroll(); ``` ```css @import 'locomotive-scroll/dist/locomotive-scroll.css'; ``` ```html
I move at half speed
``` ## Features - **Lightweight** — Only 9.4kB gzipped - **TypeScript First** — Fully typed - **Built on Lenis** — Latest stable release with improved performance - **Dual Intersection Observers** — Optimized detection for triggers vs. animations - **Smart Touch Detection** — Parallax auto-disabled on mobile - **Accessible** — Native scrollbar, keyboard navigation, proper ARIA support ## Demo Check out the [examples and playground](https://scroll.locomotive.ca/docs/examples) ## Support [GitHub Issues](https://github.com/locomotivemtl/locomotive-scroll/issues) ================================================ FILE: packages/lib/bundled/locomotive-scroll.css ================================================ html.lenis,html.lenis body{height:auto}.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}.lenis [data-lenis-prevent-touch],.lenis [data-lenis-prevent-wheel],.lenis [data-lenis-prevent]{overscroll-behavior:contain}.lenis.lenis-smooth iframe{pointer-events:none}.lenis.lenis-autoToggle{transition-behavior:allow-discrete;transition-duration:1ms;transition-property:overflow} ================================================ FILE: packages/lib/bundled/locomotive-scroll.js ================================================ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.LocomotiveScroll = factory()); })(this, (function () { function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } var version$1 = "5.0.0"; // package.json var version = "1.3.17"; // packages/core/src/maths.ts function clamp$1(min, input, max) { return Math.max(min, Math.min(input, max)); } function lerp(x, y, t) { return (1 - t) * x + t * y; } function damp(x, y, lambda, deltaTime) { return lerp(x, y, 1 - Math.exp(-lambda * deltaTime)); } function modulo(n, d) { return (n % d + d) % d; } // packages/core/src/animate.ts var Animate = class { isRunning = false; value = 0; from = 0; to = 0; currentTime = 0; // These are instanciated in the fromTo method lerp; duration; easing; onUpdate; /** * Advance the animation by the given delta time * * @param deltaTime - The time in seconds to advance the animation */ advance(deltaTime) { if (!this.isRunning) return; let completed = false; if (this.duration && this.easing) { this.currentTime += deltaTime; const linearProgress = clamp$1(0, this.currentTime / this.duration, 1); completed = linearProgress >= 1; const easedProgress = completed ? 1 : this.easing(linearProgress); this.value = this.from + (this.to - this.from) * easedProgress; } else if (this.lerp) { this.value = damp(this.value, this.to, this.lerp * 60, deltaTime); if (Math.round(this.value) === this.to) { this.value = this.to; completed = true; } } else { this.value = this.to; completed = true; } if (completed) { this.stop(); } this.onUpdate?.(this.value, completed); } /** Stop the animation */ stop() { this.isRunning = false; } /** * Set up the animation from a starting value to an ending value * with optional parameters for lerping, duration, easing, and onUpdate callback * * @param from - The starting value * @param to - The ending value * @param options - Options for the animation */ fromTo(from, to, { lerp: lerp2, duration, easing, onStart, onUpdate }) { this.from = this.value = from; this.to = to; this.lerp = lerp2; this.duration = duration; this.easing = easing; this.currentTime = 0; this.isRunning = true; onStart?.(); this.onUpdate = onUpdate; } }; // packages/core/src/debounce.ts function debounce(callback, delay) { let timer; return function(...args) { let context = this; clearTimeout(timer); timer = setTimeout(() => { timer = void 0; callback.apply(context, args); }, delay); }; } // packages/core/src/dimensions.ts var Dimensions = class { constructor(wrapper, content, { autoResize = true, debounce: debounceValue = 250 } = {}) { this.wrapper = wrapper; this.content = content; if (autoResize) { this.debouncedResize = debounce(this.resize, debounceValue); if (this.wrapper instanceof Window) { window.addEventListener("resize", this.debouncedResize, false); } else { this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize); this.wrapperResizeObserver.observe(this.wrapper); } this.contentResizeObserver = new ResizeObserver(this.debouncedResize); this.contentResizeObserver.observe(this.content); } this.resize(); } width = 0; height = 0; scrollHeight = 0; scrollWidth = 0; // These are instanciated in the constructor as they need information from the options debouncedResize; wrapperResizeObserver; contentResizeObserver; destroy() { this.wrapperResizeObserver?.disconnect(); this.contentResizeObserver?.disconnect(); if (this.wrapper === window && this.debouncedResize) { window.removeEventListener("resize", this.debouncedResize, false); } } resize = () => { this.onWrapperResize(); this.onContentResize(); }; onWrapperResize = () => { if (this.wrapper instanceof Window) { this.width = window.innerWidth; this.height = window.innerHeight; } else { this.width = this.wrapper.clientWidth; this.height = this.wrapper.clientHeight; } }; onContentResize = () => { if (this.wrapper instanceof Window) { this.scrollHeight = this.content.scrollHeight; this.scrollWidth = this.content.scrollWidth; } else { this.scrollHeight = this.wrapper.scrollHeight; this.scrollWidth = this.wrapper.scrollWidth; } }; get limit() { return { x: this.scrollWidth - this.width, y: this.scrollHeight - this.height }; } }; // packages/core/src/emitter.ts var Emitter = class { events = {}; /** * Emit an event with the given data * @param event Event name * @param args Data to pass to the event handlers */ emit(event, ...args) { let callbacks = this.events[event] || []; for (let i = 0, length = callbacks.length; i < length; i++) { callbacks[i]?.(...args); } } /** * Add a callback to the event * @param event Event name * @param cb Callback function * @returns Unsubscribe function */ on(event, cb) { this.events[event]?.push(cb) || (this.events[event] = [cb]); return () => { this.events[event] = this.events[event]?.filter((i) => cb !== i); }; } /** * Remove a callback from the event * @param event Event name * @param callback Callback function */ off(event, callback) { this.events[event] = this.events[event]?.filter((i) => callback !== i); } /** * Remove all event listeners and clean up */ destroy() { this.events = {}; } }; // packages/core/src/virtual-scroll.ts var LINE_HEIGHT = 100 / 6; var listenerOptions = { passive: false }; var VirtualScroll = class { constructor(element, options = { wheelMultiplier: 1, touchMultiplier: 1 }) { this.element = element; this.options = options; window.addEventListener("resize", this.onWindowResize, false); this.onWindowResize(); this.element.addEventListener("wheel", this.onWheel, listenerOptions); this.element.addEventListener( "touchstart", this.onTouchStart, listenerOptions ); this.element.addEventListener( "touchmove", this.onTouchMove, listenerOptions ); this.element.addEventListener("touchend", this.onTouchEnd, listenerOptions); } touchStart = { x: 0, y: 0 }; lastDelta = { x: 0, y: 0 }; window = { width: 0, height: 0 }; emitter = new Emitter(); /** * Add an event listener for the given event and callback * * @param event Event name * @param callback Callback function */ on(event, callback) { return this.emitter.on(event, callback); } /** Remove all event listeners and clean up */ destroy() { this.emitter.destroy(); window.removeEventListener("resize", this.onWindowResize, false); this.element.removeEventListener("wheel", this.onWheel, listenerOptions); this.element.removeEventListener( "touchstart", this.onTouchStart, listenerOptions ); this.element.removeEventListener( "touchmove", this.onTouchMove, listenerOptions ); this.element.removeEventListener( "touchend", this.onTouchEnd, listenerOptions ); } /** * Event handler for 'touchstart' event * * @param event Touch event */ onTouchStart = (event) => { const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event; this.touchStart.x = clientX; this.touchStart.y = clientY; this.lastDelta = { x: 0, y: 0 }; this.emitter.emit("scroll", { deltaX: 0, deltaY: 0, event }); }; /** Event handler for 'touchmove' event */ onTouchMove = (event) => { const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event; const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier; const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier; this.touchStart.x = clientX; this.touchStart.y = clientY; this.lastDelta = { x: deltaX, y: deltaY }; this.emitter.emit("scroll", { deltaX, deltaY, event }); }; onTouchEnd = (event) => { this.emitter.emit("scroll", { deltaX: this.lastDelta.x, deltaY: this.lastDelta.y, event }); }; /** Event handler for 'wheel' event */ onWheel = (event) => { let { deltaX, deltaY, deltaMode } = event; const multiplierX = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.width : 1; const multiplierY = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.height : 1; deltaX *= multiplierX; deltaY *= multiplierY; deltaX *= this.options.wheelMultiplier; deltaY *= this.options.wheelMultiplier; this.emitter.emit("scroll", { deltaX, deltaY, event }); }; onWindowResize = () => { this.window = { width: window.innerWidth, height: window.innerHeight }; }; }; // packages/core/src/lenis.ts var defaultEasing = (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)); var Lenis = class { _isScrolling = false; // true when scroll is animating _isStopped = false; // true if user should not be able to scroll - enable/disable programmatically _isLocked = false; // same as isStopped but enabled/disabled when scroll reaches target _preventNextNativeScrollEvent = false; _resetVelocityTimeout = null; _rafId = null; /** * Whether or not the user is touching the screen */ isTouching; /** * The time in ms since the lenis instance was created */ time = 0; /** * User data that will be forwarded through the scroll event * * @example * lenis.scrollTo(100, { * userData: { * foo: 'bar' * } * }) */ userData = {}; /** * The last velocity of the scroll */ lastVelocity = 0; /** * The current velocity of the scroll */ velocity = 0; /** * The direction of the scroll */ direction = 0; /** * The options passed to the lenis instance */ options; /** * The target scroll value */ targetScroll; /** * The animated scroll value */ animatedScroll; // These are instanciated here as they don't need information from the options animate = new Animate(); emitter = new Emitter(); // These are instanciated in the constructor as they need information from the options dimensions; // This is not private because it's used in the Snap class virtualScroll; constructor({ wrapper = window, content = document.documentElement, eventsTarget = wrapper, smoothWheel = true, syncTouch = false, syncTouchLerp = 0.075, touchInertiaExponent = 1.7, duration, // in seconds easing, lerp: lerp2 = 0.1, infinite = false, orientation = "vertical", // vertical, horizontal gestureOrientation = orientation === "horizontal" ? "both" : "vertical", // vertical, horizontal, both touchMultiplier = 1, wheelMultiplier = 1, autoResize = true, prevent, virtualScroll, overscroll = true, autoRaf = false, anchors = false, autoToggle = false, // https://caniuse.com/?search=transition-behavior allowNestedScroll = false, // @ts-ignore: this will be deprecated in the future __experimental__naiveDimensions = false, naiveDimensions = __experimental__naiveDimensions, stopInertiaOnNavigate = false } = {}) { window.lenisVersion = version; if (!wrapper || wrapper === document.documentElement) { wrapper = window; } if (typeof duration === "number" && typeof easing !== "function") { easing = defaultEasing; } else if (typeof easing === "function" && typeof duration !== "number") { duration = 1; } this.options = { wrapper, content, eventsTarget, smoothWheel, syncTouch, syncTouchLerp, touchInertiaExponent, duration, easing, lerp: lerp2, infinite, gestureOrientation, orientation, touchMultiplier, wheelMultiplier, autoResize, prevent, virtualScroll, overscroll, autoRaf, anchors, autoToggle, allowNestedScroll, naiveDimensions, stopInertiaOnNavigate }; this.dimensions = new Dimensions(wrapper, content, { autoResize }); this.updateClassName(); this.targetScroll = this.animatedScroll = this.actualScroll; this.options.wrapper.addEventListener("scroll", this.onNativeScroll, false); this.options.wrapper.addEventListener("scrollend", this.onScrollEnd, { capture: true }); if (this.options.anchors || this.options.stopInertiaOnNavigate) { this.options.wrapper.addEventListener( "click", this.onClick, false ); } this.options.wrapper.addEventListener( "pointerdown", this.onPointerDown, false ); this.virtualScroll = new VirtualScroll(eventsTarget, { touchMultiplier, wheelMultiplier }); this.virtualScroll.on("scroll", this.onVirtualScroll); if (this.options.autoToggle) { this.checkOverflow(); this.rootElement.addEventListener("transitionend", this.onTransitionEnd, { passive: true }); } if (this.options.autoRaf) { this._rafId = requestAnimationFrame(this.raf); } } /** * Destroy the lenis instance, remove all event listeners and clean up the class name */ destroy() { this.emitter.destroy(); this.options.wrapper.removeEventListener( "scroll", this.onNativeScroll, false ); this.options.wrapper.removeEventListener("scrollend", this.onScrollEnd, { capture: true }); this.options.wrapper.removeEventListener( "pointerdown", this.onPointerDown, false ); if (this.options.anchors || this.options.stopInertiaOnNavigate) { this.options.wrapper.removeEventListener( "click", this.onClick, false ); } this.virtualScroll.destroy(); this.dimensions.destroy(); this.cleanUpClassName(); if (this._rafId) { cancelAnimationFrame(this._rafId); } } on(event, callback) { return this.emitter.on(event, callback); } off(event, callback) { return this.emitter.off(event, callback); } onScrollEnd = (e) => { if (!(e instanceof CustomEvent)) { if (this.isScrolling === "smooth" || this.isScrolling === false) { e.stopPropagation(); } } }; dispatchScrollendEvent = () => { this.options.wrapper.dispatchEvent( new CustomEvent("scrollend", { bubbles: this.options.wrapper === window, // cancelable: false, detail: { lenisScrollEnd: true } }) ); }; get overflow() { const property = this.isHorizontal ? "overflow-x" : "overflow-y"; return getComputedStyle(this.rootElement)[property]; } checkOverflow() { if (["hidden", "clip"].includes(this.overflow)) { this.internalStop(); } else { this.internalStart(); } } onTransitionEnd = (event) => { if (event.propertyName.includes("overflow")) { this.checkOverflow(); } }; setScroll(scroll) { if (this.isHorizontal) { this.options.wrapper.scrollTo({ left: scroll, behavior: "instant" }); } else { this.options.wrapper.scrollTo({ top: scroll, behavior: "instant" }); } } onClick = (event) => { const path = event.composedPath(); const anchorElements = path.filter( (node) => node instanceof HTMLAnchorElement && node.getAttribute("href") ); if (this.options.anchors) { const anchor = anchorElements.find( (node) => node.getAttribute("href")?.includes("#") ); if (anchor) { const href = anchor.getAttribute("href"); if (href) { const options = typeof this.options.anchors === "object" && this.options.anchors ? this.options.anchors : void 0; const target = `#${href.split("#")[1]}`; this.scrollTo(target, options); } } } if (this.options.stopInertiaOnNavigate) { const internalLink = anchorElements.find( (node) => node.host === window.location.host ); if (internalLink) { this.reset(); } } }; onPointerDown = (event) => { if (event.button === 1) { this.reset(); } }; onVirtualScroll = (data) => { if (typeof this.options.virtualScroll === "function" && this.options.virtualScroll(data) === false) return; const { deltaX, deltaY, event } = data; this.emitter.emit("virtual-scroll", { deltaX, deltaY, event }); if (event.ctrlKey) return; if (event.lenisStopPropagation) return; const isTouch = event.type.includes("touch"); const isWheel = event.type.includes("wheel"); this.isTouching = event.type === "touchstart" || event.type === "touchmove"; const isClickOrTap = deltaX === 0 && deltaY === 0; const isTapToStop = this.options.syncTouch && isTouch && event.type === "touchstart" && isClickOrTap && !this.isStopped && !this.isLocked; if (isTapToStop) { this.reset(); return; } const isUnknownGesture = this.options.gestureOrientation === "vertical" && deltaY === 0 || this.options.gestureOrientation === "horizontal" && deltaX === 0; if (isClickOrTap || isUnknownGesture) { return; } let composedPath = event.composedPath(); composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement)); const prevent = this.options.prevent; if (!!composedPath.find( (node) => node instanceof HTMLElement && (typeof prevent === "function" && prevent?.(node) || node.hasAttribute?.("data-lenis-prevent") || isTouch && node.hasAttribute?.("data-lenis-prevent-touch") || isWheel && node.hasAttribute?.("data-lenis-prevent-wheel") || this.options.allowNestedScroll && this.checkNestedScroll(node, { deltaX, deltaY })) )) return; if (this.isStopped || this.isLocked) { if (event.cancelable) { event.preventDefault(); } return; } const isSmooth = this.options.syncTouch && isTouch || this.options.smoothWheel && isWheel; if (!isSmooth) { this.isScrolling = "native"; this.animate.stop(); event.lenisStopPropagation = true; return; } let delta = deltaY; if (this.options.gestureOrientation === "both") { delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX; } else if (this.options.gestureOrientation === "horizontal") { delta = deltaX; } if (!this.options.overscroll || this.options.infinite || this.options.wrapper !== window && this.limit > 0 && (this.animatedScroll > 0 && this.animatedScroll < this.limit || this.animatedScroll === 0 && deltaY > 0 || this.animatedScroll === this.limit && deltaY < 0)) { event.lenisStopPropagation = true; } if (event.cancelable) { event.preventDefault(); } const isSyncTouch = isTouch && this.options.syncTouch; const isTouchEnd = isTouch && event.type === "touchend"; const hasTouchInertia = isTouchEnd; if (hasTouchInertia) { delta = Math.sign(this.velocity) * Math.pow(Math.abs(this.velocity), this.options.touchInertiaExponent); } this.scrollTo(this.targetScroll + delta, { programmatic: false, ...isSyncTouch ? { lerp: hasTouchInertia ? this.options.syncTouchLerp : 1 } : { lerp: this.options.lerp, duration: this.options.duration, easing: this.options.easing } }); }; /** * Force lenis to recalculate the dimensions */ resize() { this.dimensions.resize(); this.animatedScroll = this.targetScroll = this.actualScroll; this.emit(); } emit() { this.emitter.emit("scroll", this); } onNativeScroll = () => { if (this._resetVelocityTimeout !== null) { clearTimeout(this._resetVelocityTimeout); this._resetVelocityTimeout = null; } if (this._preventNextNativeScrollEvent) { this._preventNextNativeScrollEvent = false; return; } if (this.isScrolling === false || this.isScrolling === "native") { const lastScroll = this.animatedScroll; this.animatedScroll = this.targetScroll = this.actualScroll; this.lastVelocity = this.velocity; this.velocity = this.animatedScroll - lastScroll; this.direction = Math.sign( this.animatedScroll - lastScroll ); if (!this.isStopped) { this.isScrolling = "native"; } this.emit(); if (this.velocity !== 0) { this._resetVelocityTimeout = setTimeout(() => { this.lastVelocity = this.velocity; this.velocity = 0; this.isScrolling = false; this.emit(); }, 400); } } }; reset() { this.isLocked = false; this.isScrolling = false; this.animatedScroll = this.targetScroll = this.actualScroll; this.lastVelocity = this.velocity = 0; this.animate.stop(); } /** * Start lenis scroll after it has been stopped */ start() { if (!this.isStopped) return; if (this.options.autoToggle) { this.rootElement.style.removeProperty("overflow"); return; } this.internalStart(); } internalStart() { if (!this.isStopped) return; this.reset(); this.isStopped = false; this.emit(); } /** * Stop lenis scroll */ stop() { if (this.isStopped) return; if (this.options.autoToggle) { this.rootElement.style.setProperty("overflow", "clip"); return; } this.internalStop(); } internalStop() { if (this.isStopped) return; this.reset(); this.isStopped = true; this.emit(); } /** * RequestAnimationFrame for lenis * * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus */ raf = (time) => { const deltaTime = time - (this.time || time); this.time = time; this.animate.advance(deltaTime * 1e-3); if (this.options.autoRaf) { this._rafId = requestAnimationFrame(this.raf); } }; /** * Scroll to a target value * * @param target The target value to scroll to * @param options The options for the scroll * * @example * lenis.scrollTo(100, { * offset: 100, * duration: 1, * easing: (t) => 1 - Math.cos((t * Math.PI) / 2), * lerp: 0.1, * onStart: () => { * console.log('onStart') * }, * onComplete: () => { * console.log('onComplete') * }, * }) */ scrollTo(target, { offset = 0, immediate = false, lock = false, programmatic = true, // called from outside of the class lerp: lerp2 = programmatic ? this.options.lerp : void 0, duration = programmatic ? this.options.duration : void 0, easing = programmatic ? this.options.easing : void 0, onStart, onComplete, force = false, // scroll even if stopped userData } = {}) { if ((this.isStopped || this.isLocked) && !force) return; if (typeof target === "string" && ["top", "left", "start", "#"].includes(target)) { target = 0; } else if (typeof target === "string" && ["bottom", "right", "end"].includes(target)) { target = this.limit; } else { let node; if (typeof target === "string") { node = document.querySelector(target); if (!node) { if (target === "#top") { target = 0; } else { console.warn("Lenis: Target not found", target); } } } else if (target instanceof HTMLElement && target?.nodeType) { node = target; } if (node) { if (this.options.wrapper !== window) { const wrapperRect = this.rootElement.getBoundingClientRect(); offset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top; } const rect = node.getBoundingClientRect(); target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll; } } if (typeof target !== "number") return; target += offset; target = Math.round(target); if (this.options.infinite) { if (programmatic) { this.targetScroll = this.animatedScroll = this.scroll; const distance = target - this.animatedScroll; if (distance > this.limit / 2) { target = target - this.limit; } else if (distance < -this.limit / 2) { target = target + this.limit; } } } else { target = clamp$1(0, target, this.limit); } if (target === this.targetScroll) { onStart?.(this); onComplete?.(this); return; } this.userData = userData ?? {}; if (immediate) { this.animatedScroll = this.targetScroll = target; this.setScroll(this.scroll); this.reset(); this.preventNextNativeScrollEvent(); this.emit(); onComplete?.(this); this.userData = {}; requestAnimationFrame(() => { this.dispatchScrollendEvent(); }); return; } if (!programmatic) { this.targetScroll = target; } if (typeof duration === "number" && typeof easing !== "function") { easing = defaultEasing; } else if (typeof easing === "function" && typeof duration !== "number") { duration = 1; } this.animate.fromTo(this.animatedScroll, target, { duration, easing, lerp: lerp2, onStart: () => { if (lock) this.isLocked = true; this.isScrolling = "smooth"; onStart?.(this); }, onUpdate: (value, completed) => { this.isScrolling = "smooth"; this.lastVelocity = this.velocity; this.velocity = value - this.animatedScroll; this.direction = Math.sign(this.velocity); this.animatedScroll = value; this.setScroll(this.scroll); if (programmatic) { this.targetScroll = value; } if (!completed) this.emit(); if (completed) { this.reset(); this.emit(); onComplete?.(this); this.userData = {}; requestAnimationFrame(() => { this.dispatchScrollendEvent(); }); this.preventNextNativeScrollEvent(); } } }); } preventNextNativeScrollEvent() { this._preventNextNativeScrollEvent = true; requestAnimationFrame(() => { this._preventNextNativeScrollEvent = false; }); } checkNestedScroll(node, { deltaX, deltaY }) { const time = Date.now(); const cache = node._lenis ??= {}; let hasOverflowX, hasOverflowY, isScrollableX, isScrollableY, scrollWidth, scrollHeight, clientWidth, clientHeight; const gestureOrientation = this.options.gestureOrientation; if (time - (cache.time ?? 0) > 2e3) { cache.time = Date.now(); const computedStyle = window.getComputedStyle(node); cache.computedStyle = computedStyle; const overflowXString = computedStyle.overflowX; const overflowYString = computedStyle.overflowY; hasOverflowX = ["auto", "overlay", "scroll"].includes(overflowXString); hasOverflowY = ["auto", "overlay", "scroll"].includes(overflowYString); cache.hasOverflowX = hasOverflowX; cache.hasOverflowY = hasOverflowY; if (!hasOverflowX && !hasOverflowY) return false; if (gestureOrientation === "vertical" && !hasOverflowY) return false; if (gestureOrientation === "horizontal" && !hasOverflowX) return false; scrollWidth = node.scrollWidth; scrollHeight = node.scrollHeight; clientWidth = node.clientWidth; clientHeight = node.clientHeight; isScrollableX = scrollWidth > clientWidth; isScrollableY = scrollHeight > clientHeight; cache.isScrollableX = isScrollableX; cache.isScrollableY = isScrollableY; cache.scrollWidth = scrollWidth; cache.scrollHeight = scrollHeight; cache.clientWidth = clientWidth; cache.clientHeight = clientHeight; } else { isScrollableX = cache.isScrollableX; isScrollableY = cache.isScrollableY; hasOverflowX = cache.hasOverflowX; hasOverflowY = cache.hasOverflowY; scrollWidth = cache.scrollWidth; scrollHeight = cache.scrollHeight; clientWidth = cache.clientWidth; clientHeight = cache.clientHeight; } if (!hasOverflowX && !hasOverflowY || !isScrollableX && !isScrollableY) { return false; } if (gestureOrientation === "vertical" && (!hasOverflowY || !isScrollableY)) return false; if (gestureOrientation === "horizontal" && (!hasOverflowX || !isScrollableX)) return false; let orientation; if (gestureOrientation === "horizontal") { orientation = "x"; } else if (gestureOrientation === "vertical") { orientation = "y"; } else { const isScrollingX = deltaX !== 0; const isScrollingY = deltaY !== 0; if (isScrollingX && hasOverflowX && isScrollableX) { orientation = "x"; } if (isScrollingY && hasOverflowY && isScrollableY) { orientation = "y"; } } if (!orientation) return false; let scroll, maxScroll, delta, hasOverflow, isScrollable; if (orientation === "x") { scroll = node.scrollLeft; maxScroll = scrollWidth - clientWidth; delta = deltaX; hasOverflow = hasOverflowX; isScrollable = isScrollableX; } else if (orientation === "y") { scroll = node.scrollTop; maxScroll = scrollHeight - clientHeight; delta = deltaY; hasOverflow = hasOverflowY; isScrollable = isScrollableY; } else { return false; } const willScroll = delta > 0 ? scroll < maxScroll : scroll > 0; return willScroll && hasOverflow && isScrollable; } /** * The root element on which lenis is instanced */ get rootElement() { return this.options.wrapper === window ? document.documentElement : this.options.wrapper; } /** * The limit which is the maximum scroll value */ get limit() { if (this.options.naiveDimensions) { if (this.isHorizontal) { return this.rootElement.scrollWidth - this.rootElement.clientWidth; } else { return this.rootElement.scrollHeight - this.rootElement.clientHeight; } } else { return this.dimensions.limit[this.isHorizontal ? "x" : "y"]; } } /** * Whether or not the scroll is horizontal */ get isHorizontal() { return this.options.orientation === "horizontal"; } /** * The actual scroll value */ get actualScroll() { const wrapper = this.options.wrapper; return this.isHorizontal ? wrapper.scrollX ?? wrapper.scrollLeft : wrapper.scrollY ?? wrapper.scrollTop; } /** * The current scroll value */ get scroll() { return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll; } /** * The progress of the scroll relative to the limit */ get progress() { return this.limit === 0 ? 1 : this.scroll / this.limit; } /** * Current scroll state */ get isScrolling() { return this._isScrolling; } set isScrolling(value) { if (this._isScrolling !== value) { this._isScrolling = value; this.updateClassName(); } } /** * Check if lenis is stopped */ get isStopped() { return this._isStopped; } set isStopped(value) { if (this._isStopped !== value) { this._isStopped = value; this.updateClassName(); } } /** * Check if lenis is locked */ get isLocked() { return this._isLocked; } set isLocked(value) { if (this._isLocked !== value) { this._isLocked = value; this.updateClassName(); } } /** * Check if lenis is smooth scrolling */ get isSmooth() { return this.isScrolling === "smooth"; } /** * The class name applied to the wrapper element */ get className() { let className = "lenis"; if (this.options.autoToggle) className += " lenis-autoToggle"; if (this.isStopped) className += " lenis-stopped"; if (this.isLocked) className += " lenis-locked"; if (this.isScrolling) className += " lenis-scrolling"; if (this.isScrolling === "smooth") className += " lenis-smooth"; return className; } updateClassName() { this.cleanUpClassName(); this.rootElement.className = `${this.rootElement.className} ${this.className}`.trim(); } cleanUpClassName() { this.rootElement.className = this.rootElement.className.replace(/lenis(-\w+)?/g, "").trim(); } }; /** * Intersection Observer * * Detecting visibility of an element in the viewport. * * Features functions to: * * - Trigger inview/outOfView callbacks * - If the element has a requestAnimationFrame dependency, set interactivy status for the ScrollElement Class * * References: * * - {@link https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API} */ var IO = /*#__PURE__*/function () { function IO(_ref) { var scrollElements = _ref.scrollElements, _ref$rootMargin = _ref.rootMargin, rootMargin = _ref$rootMargin === void 0 ? '-1px -1px -1px -1px' : _ref$rootMargin, _ref$root = _ref.root, root = _ref$root === void 0 ? null : _ref$root, IORaf = _ref.IORaf; this.scrollElements = void 0; this.rootMargin = void 0; this.root = void 0; this.IORaf = void 0; this.observer = void 0; // Parameters this.scrollElements = scrollElements; this.rootMargin = rootMargin; this.root = root; this.IORaf = IORaf; // Init this._init(); } /** * Lifecyle - Initialize Intersection Observer. * * @private */ var _proto = IO.prototype; _proto._init = function _init() { var _this = this; // Options var observerOptions = { root: this.root, rootMargin: this.rootMargin }; // Callback var onIntersect = function onIntersect(entries) { entries.forEach(function (entry) { var $targetItem = _this.scrollElements.find(function (item) { return item.$el === entry.target; }); if (entry.isIntersecting) { $targetItem && ($targetItem.isAlreadyIntersected = true); _this._setInview(entry); } else if ($targetItem && $targetItem.isAlreadyIntersected) { _this._setOutOfView(entry); } }); }; // Instance this.observer = new IntersectionObserver(onIntersect, observerOptions); // Observe each default elements for (var _iterator = _createForOfIteratorHelperLoose(this.scrollElements), _step; !(_step = _iterator()).done;) { var scrollElement = _step.value; var $scrollElement = scrollElement.$el; this.observe($scrollElement); } } /** * Lifecyle - Destroy Intersection Observer. */; _proto.destroy = function destroy() { this.observer.disconnect(); } /** * Subscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to observe. */; _proto.observe = function observe($scrollElement) { if (!$scrollElement) { return; } this.observer.observe($scrollElement); } /** * Unsubscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to unobserve. */; _proto.unobserve = function unobserve($scrollElement) { if (!$scrollElement) { return; } this.observer.unobserve($scrollElement); } /** * Find ScrollElementReference instance and trigger inview callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */; _proto._setInview = function _setInview(entry) { var scrollElement = this.scrollElements.find(function (scrollElement) { return scrollElement.$el === entry.target; }); this.IORaf && (scrollElement == null ? void 0 : scrollElement.setInteractivityOn()); !this.IORaf && (scrollElement == null ? void 0 : scrollElement.setInview()); } /** * Find ScrollElementReference instance and trigger out of view callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */; _proto._setOutOfView = function _setOutOfView(entry) { var scrollElement = this.scrollElements.find(function (scrollElement) { return scrollElement.$el === entry.target; }); this.IORaf && (scrollElement == null ? void 0 : scrollElement.setInteractivityOff()); !this.IORaf && (scrollElement == null ? void 0 : scrollElement.setOutOfView()); // Unobserve if element doesn't have repeat attribute if (!(scrollElement != null && scrollElement.attributes.scrollRepeat) && !this.IORaf) { this.unobserve(entry.target); } }; return IO; }(); // https://greensock.com/docs/v3/GSAP/gsap.utils /** * Clamp a value to fit within a specific range (ex: clamp(0, 100, -12) --> 0). * * @param {number} min - Minimum value expected. * @param {number} max - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - Clamped value. */ function clamp(min, max, value) { return value < min ? min : value > max ? max : value; } /** * Map one range to another (ex: mapRange(-10, 10, 0, 100, 5) --> 75). * * @param {number} inMin - Current minimum value. * @param {number} inMax - Current maximum value. * @param {number} outMin - Maximum value expected. * @param {number} outMax - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - New value that should be between minimum value expected and maximum value. */ function mapRange(inMin, inMax, outMin, outMax, value) { var inRange = inMax - inMin; var outRange = outMax - outMin; return outMin + ((value - inMin) / inRange * outRange || 0); } /** * Map a number within a range to a progress between 0 to 1 (ex: normalize(100, 200, 150) --> 0.5). * * @param {number} min - Current minimum value. * @param {number} max - Current maximum value. * @param {number} value - Current value. * * @returns {number} - New value that should be between 0 and 1. */ function normalize(min, max, value) { return mapRange(min, max, 0, 1, value); } /** * Get closest number from an array. * * @param {number[]} array - Numbers array. * @param {number} target - Reference value. * * @returns {number} - Closest number. */ function closestNumber(array, target) { return array.reduce(function (prev, curr) { return Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev; }); } /** * Scroll Element * * Give tools to compute element progress in the viewport and triggers callbacks to animate it. * * Features functions to: * * - scrollClass - Add a custom class when the element is intersected by the offset * - scrollOffset - Determine offsets to intersect the element * - scrollPosition - Determine the element positions to consider an element as intersected. * - scrollCssProgress - Add a specific css variable (PROGRESS_CSS_VAR) that store the scroll progress * - scrollEventProgress - Send scroll progress to custom event listeners. * - scrollSpeed - Add a scroll multiplicator to create a parallax effect * - scrollRepeat - Repeat the option to trigger animation each time the element is intersected * - scrollCall - Call a custom event when the element is intersected */ /** Constants */ var INVIEW_CLASS = 'is-inview'; var PROGRESS_CSS_VAR = '--progress'; var ScrollElement = /*#__PURE__*/function () { function ScrollElement(_ref) { var _this$$el$dataset$scr, _this$$el$dataset$scr2, _this$$el$dataset$scr3, _this$$el$dataset$scr4, _this$$el$dataset$scr5, _this = this; var $el = _ref.$el, id = _ref.id, subscribeElementUpdateFn = _ref.subscribeElementUpdateFn, unsubscribeElementUpdateFn = _ref.unsubscribeElementUpdateFn, needRaf = _ref.needRaf, scrollOrientation = _ref.scrollOrientation, lenisInstance = _ref.lenisInstance; this.$el = void 0; this.id = void 0; this.needRaf = void 0; this.attributes = void 0; this.scrollOrientation = void 0; this.isAlreadyIntersected = void 0; this.intersection = void 0; this.metrics = void 0; this.currentScroll = void 0; this.translateValue = void 0; this.progress = void 0; this.lastProgress = void 0; this.isInview = void 0; this.isInteractive = void 0; this.isInFold = void 0; this.isFirstResize = void 0; this.subscribeElementUpdateFn = void 0; this.unsubscribeElementUpdateFn = void 0; this.lenisInstance = void 0; // Cached functions to avoid orientation checks every frame this.getWindowSize = void 0; this.getMetricsStart = void 0; this.getMetricsSize = void 0; // Position handlers for intersection.start (includes wSize) this.startPositionHandlers = { 'start': function start(offsetStart, wSize, viewport) { return offsetStart - wSize + viewport; }, 'middle': function middle(offsetStart, wSize, viewport, size) { return offsetStart - wSize + viewport + size * 0.5; }, 'end': function end(offsetStart, wSize, viewport, size) { return offsetStart - wSize + viewport + size; }, 'fold': function fold() { return 0; } }; // Position handlers for intersection.end (DOES NOT include wSize - critical difference) this.endPositionHandlers = { 'start': function start(offsetStart, viewport) { return offsetStart - viewport; }, 'middle': function middle(offsetStart, viewport, size) { return offsetStart - viewport + size * 0.5; }, 'end': function end(offsetStart, viewport, size) { return offsetStart - viewport + size; } }; // Scroll DOM element this.$el = $el; // Unique ID this.id = id; // RAF option this.needRaf = needRaf; // Scroll Direction this.scrollOrientation = scrollOrientation; // Lenis instance this.lenisInstance = lenisInstance; // Parent's callbacks this.subscribeElementUpdateFn = subscribeElementUpdateFn; this.unsubscribeElementUpdateFn = unsubscribeElementUpdateFn; // Attributes this.attributes = { scrollClass: (_this$$el$dataset$scr = this.$el.dataset['scrollClass']) != null ? _this$$el$dataset$scr : INVIEW_CLASS, scrollOffset: (_this$$el$dataset$scr2 = this.$el.dataset['scrollOffset']) != null ? _this$$el$dataset$scr2 : '0,0', scrollPosition: (_this$$el$dataset$scr3 = this.$el.dataset['scrollPosition']) != null ? _this$$el$dataset$scr3 : 'start,end', scrollCssProgress: this.$el.dataset['scrollCssProgress'] !== undefined, scrollEventProgress: (_this$$el$dataset$scr4 = this.$el.dataset['scrollEventProgress']) != null ? _this$$el$dataset$scr4 : null, scrollSpeed: this.$el.dataset['scrollSpeed'] !== undefined ? parseFloat(this.$el.dataset['scrollSpeed']) : null, scrollRepeat: this.$el.dataset['scrollRepeat'] !== undefined, scrollCall: (_this$$el$dataset$scr5 = this.$el.dataset['scrollCall']) != null ? _this$$el$dataset$scr5 : null, scrollIgnoreFold: this.$el.dataset['scrollIgnoreFold'] !== undefined, scrollEnableTouchSpeed: this.$el.dataset['scrollEnableTouchSpeed'] !== undefined }; // Limits this.intersection = { start: 0, end: 0 }; // Metrics this.metrics = { offsetStart: 0, offsetEnd: 0, bcr: {} }; // Scroll Values this.currentScroll = this.lenisInstance.scroll; // Parallax this.translateValue = 0; // Progress this.progress = 0; this.lastProgress = null; // Inview this.isInview = false; this.isInteractive = false; this.isAlreadyIntersected = false; this.isInFold = false; this.isFirstResize = true; // Cache orientation-dependent functions to avoid repeated conditionals this.getWindowSize = this.scrollOrientation === 'vertical' ? function () { return _this.lenisInstance.dimensions.height; } : function () { return _this.lenisInstance.dimensions.width; }; this.getMetricsStart = this.scrollOrientation === 'vertical' ? function (bcr) { return bcr.top; } : function (bcr) { return bcr.left; }; this.getMetricsSize = this.scrollOrientation === 'vertical' ? function (bcr) { return bcr.height; } : function (bcr) { return bcr.width; }; // Init this._init(); } /** * Lifecyle - Initialize progress tracking. * * @private */ var _proto = ScrollElement.prototype; _proto._init = function _init() { if (!this.needRaf) { return; } // First resize to compute all values this._resize(); } /** * Callback - Resize callback */; _proto.onResize = function onResize(_ref2) { var currentScroll = _ref2.currentScroll; this.currentScroll = currentScroll; this._resize(); } /** * Callback - RAF callback */; _proto.onRender = function onRender(_ref3) { var currentScroll = _ref3.currentScroll, smooth = _ref3.smooth; var wSize = this.getWindowSize(); this.currentScroll = currentScroll; this._computeProgress(); // Parallax if (this.attributes.scrollSpeed && !isNaN(this.attributes.scrollSpeed)) { // if touch detected or smooth disabled if (!this.attributes.scrollEnableTouchSpeed && !smooth) { if (this.translateValue) { this.$el.style.transform = "translate3d(0, 0, 0)"; } this.translateValue = 0; // if mousewheel or smooth enabled } else { // Check fold condition if (this.isInFold) { var progress = Math.max(0, this.progress); this.translateValue = progress * wSize * this.attributes.scrollSpeed * -1; } else { var _progress = mapRange(0, 1, -1, 1, this.progress); this.translateValue = _progress * wSize * this.attributes.scrollSpeed * -1; } this.$el.style.transform = this.scrollOrientation === 'vertical' ? "translate3d(0, " + this.translateValue + "px, 0)" : "translate3d(" + this.translateValue + "px, 0, 0)"; } } } /** * Inview callback */; _proto.setInview = function setInview() { if (this.isInview) { return; } this.isInview = true; this.$el.classList.add(this.attributes.scrollClass); var way = 'enter'; var from = this._getScrollCallFrom(); this.attributes.scrollCall && this._dispatchCall(way, from); } /** * Out of view callback */; _proto.setOutOfView = function setOutOfView() { if (!(this.isInview && this.attributes.scrollRepeat)) { return; } this.isInview = false; this.$el.classList.remove(this.attributes.scrollClass); var way = 'leave'; var from = this._getScrollCallFrom(); this.attributes.scrollCall && this._dispatchCall(way, from); } /** * Switch interactivity on to subscribe the instance to the RAF * and start calculations. */; _proto.setInteractivityOn = function setInteractivityOn() { if (this.isInteractive) { return; } this.isInteractive = true; this.subscribeElementUpdateFn(this); } /** * Switch interactivity off to unsubscribe the instance to the RAF * and stop calculations. */; _proto.setInteractivityOff = function setInteractivityOff() { if (!this.isInteractive) { return; } this.isInteractive = false; this.unsubscribeElementUpdateFn(this); // Force progress to progress limit when the element is out this.lastProgress !== null && this._computeProgress(closestNumber([0, 1], this.lastProgress)); } /** * Resize method that compute the element's values. * * @private */; _proto._resize = function _resize() { this.metrics.bcr = this.$el.getBoundingClientRect(); this._computeMetrics(); this._computeIntersection(); // First resize logic if (this.isFirstResize) { this.isFirstResize = false; // Dispatch default call if the element is in fold. if (this.isInFold) { this.setInview(); } } } /** * Compute element's offsets and determine if the element is in fold. * * @private */; _proto._computeMetrics = function _computeMetrics() { var wSize = this.getWindowSize(); var metricsStart = this.getMetricsStart(this.metrics.bcr); var metricsSize = this.getMetricsSize(this.metrics.bcr); this.metrics.offsetStart = this.currentScroll + metricsStart - this.translateValue; this.metrics.offsetEnd = this.metrics.offsetStart + metricsSize; if (this.metrics.offsetStart < wSize && !this.attributes.scrollIgnoreFold) { this.isInFold = true; } else { this.isInFold = false; } } /** * Compute intersection values depending on the context. * Uses handler-based approach for cleaner, more maintainable code. * * @private */; _proto._computeIntersection = function _computeIntersection() { var _offset$0$trim, _offset$, _offset$1$trim, _offset$2, _scrollPosition$0$tri, _scrollPosition$, _scrollPosition$1$tri, _scrollPosition$2; var wSize = this.getWindowSize(); var metricsSize = this.getMetricsSize(this.metrics.bcr); // Parse offset var offset = this.attributes.scrollOffset.split(','); var offsetStart = (_offset$0$trim = (_offset$ = offset[0]) == null ? void 0 : _offset$.trim()) != null ? _offset$0$trim : '0'; var offsetEnd = (_offset$1$trim = (_offset$2 = offset[1]) == null ? void 0 : _offset$2.trim()) != null ? _offset$1$trim : '0'; // Parse positions var scrollPosition = this.attributes.scrollPosition.split(','); var scrollPositionStart = (_scrollPosition$0$tri = (_scrollPosition$ = scrollPosition[0]) == null ? void 0 : _scrollPosition$.trim()) != null ? _scrollPosition$0$tri : 'start'; var scrollPositionEnd = (_scrollPosition$1$tri = (_scrollPosition$2 = scrollPosition[1]) == null ? void 0 : _scrollPosition$2.trim()) != null ? _scrollPosition$1$tri : 'end'; // Calculate viewport offsets var viewportStart = offsetStart.includes('%') ? wSize * parseInt(offsetStart.replace('%', '').trim()) * 0.01 : parseInt(offsetStart); var viewportEnd = offsetEnd.includes('%') ? wSize * parseInt(offsetEnd.replace('%', '').trim()) * 0.01 : parseInt(offsetEnd); // Fold exception if (this.isInFold) { scrollPositionStart = 'fold'; } // Calculate intersection.start using handlers var startHandler = this.startPositionHandlers[scrollPositionStart]; this.intersection.start = startHandler ? startHandler(this.metrics.offsetStart, wSize, viewportStart, metricsSize) : this.metrics.offsetStart - wSize + viewportStart; // default fallback // Calculate intersection.end using handlers var endHandler = this.endPositionHandlers[scrollPositionEnd]; this.intersection.end = endHandler ? endHandler(this.metrics.offsetStart, viewportEnd, metricsSize) : this.metrics.offsetStart - viewportEnd + metricsSize; // default fallback // Ensure end > start if (this.intersection.end <= this.intersection.start) { switch (scrollPositionEnd) { case 'start': this.intersection.end = this.intersection.start + 1; break; case 'middle': this.intersection.end = this.intersection.start + metricsSize * 0.5; break; case 'end': this.intersection.end = this.intersection.start + metricsSize; break; default: this.intersection.end = this.intersection.start + 1; break; } } } /** * Compute the scroll progress of the element depending * on its intersection values. * * @private * * @param {number} [forcedProgress] - Value to force progress. */; _proto._computeProgress = function _computeProgress(forcedProgress) { // Progress var progress = forcedProgress != null ? forcedProgress : clamp(0, 1, normalize(this.intersection.start, this.intersection.end, this.currentScroll)); this.progress = progress; if (progress !== this.lastProgress) { this.lastProgress = progress; // Set the element's progress to the css variable this.attributes.scrollCssProgress && this._setCssProgress(progress); // Set the element's progress to the custom event listeners this.attributes.scrollEventProgress && this._setCustomEventProgress(progress); // Logic to trigger the inview/out of view callbacks progress > 0 && progress < 1 && this.setInview(); progress === 0 && this.setOutOfView(); progress === 1 && this.setOutOfView(); } } /** * Set the element's progress to a specific css variable. * * @private * * @param {number} [currentProgress] - Progress value. */; _proto._setCssProgress = function _setCssProgress(currentProgress) { if (currentProgress === void 0) { currentProgress = 0; } this.$el.style.setProperty(PROGRESS_CSS_VAR, currentProgress.toString()); } /** * Set the element's progress to the custom event listeners. * * @private * * @param {number} [currentProgress] - Progress value. */; _proto._setCustomEventProgress = function _setCustomEventProgress(currentProgress) { if (currentProgress === void 0) { currentProgress = 0; } var customEventName = this.attributes.scrollEventProgress; if (!customEventName) return; var customEvent = new CustomEvent(customEventName, { detail: { target: this.$el, progress: currentProgress } }); window.dispatchEvent(customEvent); } /** * Function to get scroll call from. * * @private */; _proto._getScrollCallFrom = function _getScrollCallFrom() { var closestIntersectionValue = closestNumber([this.intersection.start, this.intersection.end], this.currentScroll); return this.intersection.start === closestIntersectionValue ? 'start' : 'end'; } /** * Lifecyle - Destroy and cleanup the scroll element. * * Removes all CSS modifications and clears references to prevent memory leaks. */; _proto.destroy = function destroy() { // Remove CSS variables if (this.attributes.scrollCssProgress) { this.$el.style.removeProperty(PROGRESS_CSS_VAR); } // Remove transform if parallax was applied if (this.attributes.scrollSpeed) { this.$el.style.removeProperty('transform'); } // Remove class if added if (this.isInview && this.attributes.scrollClass) { this.$el.classList.remove(this.attributes.scrollClass); } } /** * Function to dispatch a custom event. * * @private * * @param {string} way - Enter or leave. * @param {string} from - Start or end. */; _proto._dispatchCall = function _dispatchCall(way, from) { var customEventName = this.attributes.scrollCall; if (!customEventName) return; // Using CustomEvent API (https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) var customEvent = new CustomEvent(customEventName, { detail: { target: this.$el, way: way, from: from } }); window.dispatchEvent(customEvent); }; return ScrollElement; }(); /** Defined attributes that need a requestAnimationFrame */ var ATTRIBUTES_THAT_NEED_RAF = ['scrollOffset', 'scrollPosition', 'scrollCssProgress', 'scrollEventProgress', 'scrollSpeed']; /** Default root margins */ var TRIGGER_ROOT_MARGIN = '-1px -1px -1px -1px'; var RAF_ROOT_MARGIN = '100% 100% 100% 100%'; // Add 100vh top/bottom && 100vw left/right to use a biggest value with data-scroll-speed /** Default scroll attribute values */ var DEFAULT_SCROLL_OFFSET = '0,0'; var DEFAULT_SCROLL_POSITION = 'top,bottom'; var Core = /*#__PURE__*/function () { function Core(_ref) { var $el = _ref.$el, triggerRootMargin = _ref.triggerRootMargin, rafRootMargin = _ref.rafRootMargin, scrollOrientation = _ref.scrollOrientation, lenisInstance = _ref.lenisInstance; this.$scrollContainer = void 0; this.triggerRootMargin = void 0; this.rafRootMargin = void 0; this.scrollElements = void 0; this.triggeredScrollElements = void 0; this.RAFScrollElements = void 0; this.scrollElementsToUpdate = void 0; this.IOTriggerInstance = void 0; this.IORafInstance = void 0; this.scrollOrientation = void 0; this.lenisInstance = void 0; if (!$el) { console.error('Please provide a DOM Element as scrollContainer'); return; } // Scroll container this.$scrollContainer = $el; // Lenis instance this.lenisInstance = lenisInstance; // Scroll Direction this.scrollOrientation = scrollOrientation; // IO Margins this.triggerRootMargin = triggerRootMargin != null ? triggerRootMargin : TRIGGER_ROOT_MARGIN; this.rafRootMargin = rafRootMargin != null ? rafRootMargin : RAF_ROOT_MARGIN; // ScrollElements arrays this.scrollElements = []; this.triggeredScrollElements = []; this.RAFScrollElements = []; this.scrollElementsToUpdate = []; // Init this._init(); } /** * Lifecyle - Initialize the core. * * @private */ var _proto = Core.prototype; _proto._init = function _init() { var $scrollElements = this.$scrollContainer.querySelectorAll('[data-scroll]'); var $scrollElementsArr = this.toElementArray($scrollElements); this._subscribeScrollElements($scrollElementsArr); // Determine IO root (null for window, wrapper element for custom container) var ioRoot = this.lenisInstance.options.wrapper === window ? null : this.lenisInstance.options.wrapper; // Trigger IO this.IOTriggerInstance = new IO({ scrollElements: [].concat(this.triggeredScrollElements), root: ioRoot, rootMargin: this.triggerRootMargin, IORaf: false }); // Raf IO this.IORafInstance = new IO({ scrollElements: [].concat(this.RAFScrollElements), root: ioRoot, rootMargin: this.rafRootMargin, IORaf: true }); } /** * Lifecyle - Destroy core. */; _proto.destroy = function destroy() { this.IOTriggerInstance.destroy(); this.IORafInstance.destroy(); this._unsubscribeAllScrollElements(); } /** * Callback - Resize callback. */; _proto.onResize = function onResize(_ref2) { var currentScroll = _ref2.currentScroll; for (var _iterator = _createForOfIteratorHelperLoose(this.RAFScrollElements), _step; !(_step = _iterator()).done;) { var scrollElement = _step.value; scrollElement.onResize({ currentScroll: currentScroll }); } } /** * Callback - RAF callback. */; _proto.onRender = function onRender(_ref3) { var currentScroll = _ref3.currentScroll, smooth = _ref3.smooth; for (var _iterator2 = _createForOfIteratorHelperLoose(this.scrollElementsToUpdate), _step2; !(_step2 = _iterator2()).done;) { var scrollElement = _step2.value; scrollElement.onRender({ currentScroll: currentScroll, smooth: smooth }); } } /** * Remove items from lists of scroll elements and compute all new values. * * @param {HTMLElement} $oldContainer - HTMLElement that contains data-scroll elements to unsubscribe */; _proto.removeScrollElements = function removeScrollElements($oldContainer) { var _this = this; var $scrollElementsToRemove = $oldContainer.querySelectorAll('[data-scroll]'); if (!$scrollElementsToRemove.length) return; var $scrollElementsToRemoveSet = new Set(Array.from($scrollElementsToRemove)); // 1. Remove from IO for (var index = 0; index < this.triggeredScrollElements.length; index++) { var scrollElement = this.triggeredScrollElements[index]; if ($scrollElementsToRemoveSet.has(scrollElement.$el)) { this.IOTriggerInstance.unobserve(scrollElement.$el); this.triggeredScrollElements.splice(index, 1); } } for (var _index = 0; _index < this.RAFScrollElements.length; _index++) { var _scrollElement = this.RAFScrollElements[_index]; if ($scrollElementsToRemoveSet.has(_scrollElement.$el)) { this.IORafInstance.unobserve(_scrollElement.$el); this.RAFScrollElements.splice(_index, 1); } } // 2. Remove from scrollElementsToUpdate[] and scrollElements[] $scrollElementsToRemove.forEach(function ($scrollElement) { var targetScrollElementToUpdate = _this.scrollElementsToUpdate.find(function (scrollElement) { return scrollElement.$el === $scrollElement; }); var targetScrollElement = _this.scrollElements.find(function (scrollElement) { return scrollElement.$el === $scrollElement; }); if (targetScrollElementToUpdate) { _this._unsubscribeElementUpdate(targetScrollElementToUpdate); } if (targetScrollElement) { _this.scrollElements = _this.scrollElements.filter(function (scrollElementItem) { return scrollElementItem.id != targetScrollElement.id; }); } }); } /** * Add items to lists of scroll elements and compute all new values. * * @param {HTMLElement} $newContainer - HTMLElement that contains data-scroll elements to subscribe */; _proto.addScrollElements = function addScrollElements($newContainer) { // 3. Rebuild ScrollElements var $scrollElements = $newContainer.querySelectorAll('[data-scroll]'); // 4. Get max scrollElement.id var ids = []; this.scrollElements.forEach(function (scrollElement) { ids.push(scrollElement.id); }); var maxID = Math.max.apply(Math, ids.concat([0])); var fromIndex = maxID + 1; var $scrollElementsArr = this.toElementArray($scrollElements); this._subscribeScrollElements($scrollElementsArr, fromIndex, true); } /** * Create a ScrollElement instance for each elements with * `data-scroll` attribute. * * @private * * @param {HTMLElement[]} $scrollElements - List of elements that need * to be regarded. */; _proto._subscribeScrollElements = function _subscribeScrollElements($scrollElements, fromIndex, toObserve) { if (fromIndex === void 0) { fromIndex = 0; } if (toObserve === void 0) { toObserve = false; } // For each scroll element create a ScrollElement instance for (var index = 0; index < $scrollElements.length; index++) { var $scrollElement = $scrollElements[index]; var needRaf = this._checkRafNeeded($scrollElement); var scrollElementInstance = new ScrollElement({ $el: $scrollElement, id: fromIndex + index, scrollOrientation: this.scrollOrientation, lenisInstance: this.lenisInstance, subscribeElementUpdateFn: this._subscribeElementUpdate.bind(this), unsubscribeElementUpdateFn: this._unsubscribeElementUpdate.bind(this), needRaf: needRaf }); // Push to common array this.scrollElements.push(scrollElementInstance); // Push to specific array if (needRaf) { this.RAFScrollElements.push(scrollElementInstance); // Dynamic observe item if (toObserve) { this.IORafInstance.scrollElements.push(scrollElementInstance); this.IORafInstance.observe(scrollElementInstance.$el); } } else { this.triggeredScrollElements.push(scrollElementInstance); // Dynamic observe item if (toObserve) { this.IOTriggerInstance.scrollElements.push(scrollElementInstance); this.IOTriggerInstance.observe(scrollElementInstance.$el); } } } } /** * Clear all ScrollElement arrays. * * @private */; _proto._unsubscribeAllScrollElements = function _unsubscribeAllScrollElements() { // Destroy all scroll elements to clean up CSS and references for (var _iterator3 = _createForOfIteratorHelperLoose(this.scrollElements), _step3; !(_step3 = _iterator3()).done;) { var scrollElement = _step3.value; scrollElement.destroy(); } this.scrollElements = []; this.RAFScrollElements = []; this.triggeredScrollElements = []; this.scrollElementsToUpdate = []; } /** * Subscribe ScrollElement instance that needs to be updated. * * @private * * @param {ScrollElement} scrollElement - ScrollElement instance inview * that needs to be updated. */; _proto._subscribeElementUpdate = function _subscribeElementUpdate(scrollElement) { this.scrollElementsToUpdate.push(scrollElement); } /** * Unscribe ScrollElement instance that doesn't need to be updated. * * @private * * @param {ScrollElement} scrollElement - The updated ScrollElement instance * out of view now. */; _proto._unsubscribeElementUpdate = function _unsubscribeElementUpdate(scrollElement) { this.scrollElementsToUpdate = this.scrollElementsToUpdate.filter(function (scrollElementToUpdate) { return scrollElementToUpdate.id != scrollElement.id; }); } /** * Convert NodeListOf to HTMLElement array. * * @private * * @param {NodeListOf} elements - The NodeList to convert. * * @returns {HTMLElement[]} */; _proto.toElementArray = function toElementArray(elements) { return Array.from(elements); } /** * Check if a DOM Element need a requestAnimationFrame to be used. * * @private * * @param {HTMLElement} $scrollElement - The element that needs to be checked. * * @returns {boolean} */; _proto._checkRafNeeded = function _checkRafNeeded($scrollElement) { var attributesThatNeedRaf = [].concat(ATTRIBUTES_THAT_NEED_RAF); // Remove utils var removeAttribute = function removeAttribute(attributeToRemove) { attributesThatNeedRaf = attributesThatNeedRaf.filter(function (attribute) { return attribute !== attributeToRemove; }); }; // 1. Check scroll offset values if ($scrollElement.dataset.scrollOffset) { var value = $scrollElement.dataset.scrollOffset.split(',').map(function (test) { return test.replace('%', '').trim(); }).join(','); if (value !== DEFAULT_SCROLL_OFFSET) { return true; } else { removeAttribute('scrollOffset'); } } else { removeAttribute('scrollOffset'); } // 2. Check scroll position values if ($scrollElement.dataset.scrollPosition) { var _value = $scrollElement.dataset.scrollPosition.trim(); if (_value !== DEFAULT_SCROLL_POSITION) { return true; } else { removeAttribute('scrollPosition'); } } else { removeAttribute('scrollPosition'); } // 3. Check scroll speed values if ($scrollElement.dataset.scrollSpeed && !isNaN(parseFloat($scrollElement.dataset.scrollSpeed))) { return true; } else { removeAttribute('scrollSpeed'); } // 4. Check others attributes for (var _iterator4 = _createForOfIteratorHelperLoose(attributesThatNeedRaf), _step4; !(_step4 = _iterator4()).done;) { var attribute = _step4.value; if (attribute in $scrollElement.dataset) { return true; } } return false; }; return Core; }(); /** * Locomotive Scroll * * Detection of elements in viewport & smooth scrolling with parallax. * * Inspired by * {@link https://github.com/locomotivemtl/locomotive-scroll locomotive-scroll.js} * and built around * {@link https://github.com/darkroomengineering/lenis lenis.js}. */ var LocomotiveScroll = /*#__PURE__*/function () { function LocomotiveScroll(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$lenisOptions = _ref.lenisOptions, lenisOptions = _ref$lenisOptions === void 0 ? {} : _ref$lenisOptions, triggerRootMargin = _ref.triggerRootMargin, rafRootMargin = _ref.rafRootMargin, _ref$autoStart = _ref.autoStart, autoStart = _ref$autoStart === void 0 ? true : _ref$autoStart, _ref$scrollCallback = _ref.scrollCallback, scrollCallback = _ref$scrollCallback === void 0 ? function () {} : _ref$scrollCallback, initCustomTicker = _ref.initCustomTicker, destroyCustomTicker = _ref.destroyCustomTicker; this.rafPlaying = void 0; this.lenisInstance = null; this.coreInstance = null; this.lenisOptions = void 0; this.triggerRootMargin = void 0; this.rafRootMargin = void 0; this.rafInstance = void 0; this.autoStart = void 0; this.isTouchDevice = void 0; this.initCustomTicker = void 0; this.destroyCustomTicker = void 0; this._onRenderBind = void 0; this._onResizeBind = void 0; this._onScrollToBind = void 0; this._originalOnContentResize = void 0; this._originalOnWrapperResize = void 0; // Set version window.locomotiveScrollVersion = version$1; // Get arguments Object.assign(this, { lenisOptions: lenisOptions, triggerRootMargin: triggerRootMargin, rafRootMargin: rafRootMargin, autoStart: autoStart, scrollCallback: scrollCallback, initCustomTicker: initCustomTicker, destroyCustomTicker: destroyCustomTicker }); // Binding this._onRenderBind = this._onRender.bind(this); this._onScrollToBind = this._onScrollTo.bind(this); this._onResizeBind = this._onResize.bind(this); // Data this.rafPlaying = false; // Detect if device has touch capability this.isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; // Init this._init(); } /** * Lifecyle - Initialize instance. * * @private */ var _proto = LocomotiveScroll.prototype; _proto._init = function _init() { var _this = this; // Create Lenis instance this.lenisInstance = new Lenis(_extends({}, this.lenisOptions)); // Subscribe to scroll callback if provided if (this.scrollCallback) { this.lenisInstance.on('scroll', this.scrollCallback); } // Add scroll direction attribute on body document.documentElement.setAttribute('data-scroll-orientation', this.lenisInstance.options.orientation); requestAnimationFrame(function () { // Create Core Instance // lenisInstance is guaranteed to exist at this point (created above) _this.coreInstance = new Core({ $el: _this.lenisInstance.rootElement, triggerRootMargin: _this.triggerRootMargin, rafRootMargin: _this.rafRootMargin, scrollOrientation: _this.lenisInstance.options.orientation, lenisInstance: _this.lenisInstance }); // Bind Events _this._bindEvents(); // RAF warning if (_this.initCustomTicker && !_this.destroyCustomTicker) { console.warn('initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble.'); } else if (!_this.initCustomTicker && _this.destroyCustomTicker) { console.warn('destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble.'); } // Start RAF _this.autoStart && _this.start(); }); } /** * Lifecyle - Destroy instance. */; _proto.destroy = function destroy() { var _this$lenisInstance, _this2 = this; // Stop raf this.stop(); // Unbind Events this._unbindEvents(); // Destroy Lenis (_this$lenisInstance = this.lenisInstance) == null || _this$lenisInstance.destroy(); // Destroy Core after RAF to ensure any pending Intersection Observer callbacks complete // This prevents race conditions when destroy() is called while IO callbacks are queued requestAnimationFrame(function () { var _this2$coreInstance; (_this2$coreInstance = _this2.coreInstance) == null || _this2$coreInstance.destroy(); }); } /** * Events - Subscribe events to listen. */; _proto._bindEvents = function _bindEvents() { var _this3 = this; this._bindScrollToEvents(); // Hook into Lenis dimensions resize callbacks // onContentResize: called when content size changes (images load, dynamic content) // onWrapperResize: called when wrapper size changes (window resize, layout changes) if (this.lenisInstance) { this._originalOnContentResize = this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions); this._originalOnWrapperResize = this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions); this.lenisInstance.dimensions.onContentResize = function () { _this3._originalOnContentResize == null || _this3._originalOnContentResize(); _this3._onResizeBind(); }; this.lenisInstance.dimensions.onWrapperResize = function () { _this3._originalOnWrapperResize == null || _this3._originalOnWrapperResize(); _this3._onResizeBind(); }; } } /** * Events - Unsubscribe listened events. */; _proto._unbindEvents = function _unbindEvents() { this._unbindScrollToEvents(); // Restore original Lenis dimensions resize callbacks if (this.lenisInstance) { if (this._originalOnContentResize) { this.lenisInstance.dimensions.onContentResize = this._originalOnContentResize; } if (this._originalOnWrapperResize) { this.lenisInstance.dimensions.onWrapperResize = this._originalOnWrapperResize; } } } /** * Events - Subscribe scrollTo events to listen. */; _proto._bindScrollToEvents = function _bindScrollToEvents($container) { var _this$lenisInstance2, _this4 = this; var $rootContainer = $container ? $container : (_this$lenisInstance2 = this.lenisInstance) == null ? void 0 : _this$lenisInstance2.rootElement; var $scrollToElements = $rootContainer == null ? void 0 : $rootContainer.querySelectorAll('[data-scroll-to]'); ($scrollToElements == null ? void 0 : $scrollToElements.length) && $scrollToElements.forEach(function ($el) { $el.addEventListener('click', _this4._onScrollToBind, false); }); } /** * Events - Unsubscribe scrollTo listened events. */; _proto._unbindScrollToEvents = function _unbindScrollToEvents($container) { var _this$lenisInstance3, _this5 = this; var $rootContainer = $container ? $container : (_this$lenisInstance3 = this.lenisInstance) == null ? void 0 : _this$lenisInstance3.rootElement; var $scrollToElements = $rootContainer == null ? void 0 : $rootContainer.querySelectorAll('[data-scroll-to]'); ($scrollToElements == null ? void 0 : $scrollToElements.length) && $scrollToElements.forEach(function ($el) { $el.removeEventListener('click', _this5._onScrollToBind, false); }); } /** * Callback - Resize callback. * * Called synchronously after Lenis updates its dimensions via onContentResize/onWrapperResize. * All dimension values are already up-to-date when this executes. */; _proto._onResize = function _onResize() { var _this$coreInstance, _this$lenisInstance$s, _this$lenisInstance4; (_this$coreInstance = this.coreInstance) == null || _this$coreInstance.onResize({ currentScroll: (_this$lenisInstance$s = (_this$lenisInstance4 = this.lenisInstance) == null ? void 0 : _this$lenisInstance4.scroll) != null ? _this$lenisInstance$s : 0, smooth: !this.isTouchDevice }); } /** * Callback - Render callback. */; _proto._onRender = function _onRender() { var _this$lenisInstance5, _this$coreInstance2, _this$lenisInstance$s2, _this$lenisInstance6; (_this$lenisInstance5 = this.lenisInstance) == null || _this$lenisInstance5.raf(Date.now()); (_this$coreInstance2 = this.coreInstance) == null || _this$coreInstance2.onRender({ currentScroll: (_this$lenisInstance$s2 = (_this$lenisInstance6 = this.lenisInstance) == null ? void 0 : _this$lenisInstance6.scroll) != null ? _this$lenisInstance$s2 : 0, smooth: !this.isTouchDevice }); } /** * Callback - Scroll To callback. */; _proto._onScrollTo = function _onScrollTo(event) { var _event$currentTarget, _this$lenisInstance7; event.preventDefault(); var $target = (_event$currentTarget = event.currentTarget) != null ? _event$currentTarget : null; if (!$target) return; var target = $target.getAttribute('data-scroll-to-href') || $target.getAttribute('href'); var offset = $target.getAttribute('data-scroll-to-offset') || 0; var duration = $target.getAttribute('data-scroll-to-duration') || ((_this$lenisInstance7 = this.lenisInstance) == null ? void 0 : _this$lenisInstance7.options.duration); target && this.scrollTo(target, { offset: typeof offset === 'string' ? parseInt(offset) : offset, duration: typeof duration === 'string' ? parseInt(duration) : duration }); } /** * Start RequestAnimationFrame that active Lenis smooth and scroll progress. */; _proto.start = function start() { var _this$lenisInstance8; if (this.rafPlaying) { return; } // Call lenis start method (_this$lenisInstance8 = this.lenisInstance) == null || _this$lenisInstance8.start(); this.rafPlaying = true; this.initCustomTicker ? this.initCustomTicker(this._onRenderBind) : this._raf(); } /** * Stop RequestAnimationFrame that active Lenis smooth and scroll progress. */; _proto.stop = function stop() { var _this$lenisInstance9; if (!this.rafPlaying) { return; } // Call lenis stop method (_this$lenisInstance9 = this.lenisInstance) == null || _this$lenisInstance9.stop(); this.rafPlaying = false; this.destroyCustomTicker ? this.destroyCustomTicker(this._onRenderBind) : this.rafInstance && cancelAnimationFrame(this.rafInstance); } /** * Remove old scroll elements items and rebuild ScrollElements instances. */; _proto.removeScrollElements = function removeScrollElements($oldContainer) { var _this$coreInstance3; if (!$oldContainer) { console.error('Please provide a DOM Element as $oldContainer'); return; } this._unbindScrollToEvents($oldContainer); (_this$coreInstance3 = this.coreInstance) == null || _this$coreInstance3.removeScrollElements($oldContainer); } /** * Add new scroll elements items and rebuild ScrollElements instances. */; _proto.addScrollElements = function addScrollElements($newContainer) { var _this$coreInstance4, _this6 = this; if (!$newContainer) { console.error('Please provide a DOM Element as $newContainer'); return; } (_this$coreInstance4 = this.coreInstance) == null || _this$coreInstance4.addScrollElements($newContainer); requestAnimationFrame(function () { _this6._bindScrollToEvents($newContainer); }); } /** * Trigger resize callback. */; _proto.resize = function resize() { this._onResizeBind(); } /** * Trigger scroll to callback. */; _proto.scrollTo = function scrollTo(target, options) { var _this$lenisInstance0; (_this$lenisInstance0 = this.lenisInstance) == null || _this$lenisInstance0.scrollTo(target, { offset: options == null ? void 0 : options.offset, lerp: options == null ? void 0 : options.lerp, duration: options == null ? void 0 : options.duration, immediate: options == null ? void 0 : options.immediate, lock: options == null ? void 0 : options.lock, force: options == null ? void 0 : options.force, easing: options == null ? void 0 : options.easing, onComplete: options == null ? void 0 : options.onComplete }); } /** * RequestAnimationFrame that active Lenis smooth and scroll progress. * * @private * */; _proto._raf = function _raf() { var _this7 = this; this._onRenderBind(); this.rafInstance = requestAnimationFrame(function () { return _this7._raf(); }); }; return LocomotiveScroll; }(); return LocomotiveScroll; })); ================================================ FILE: packages/lib/core/Core.ts ================================================ /** * Integrates Lenis with Locomotive's built-in animation system */ import type { CoreOptions, IScrollElementCallbacksValues, scrollOrientation, } from '../types'; import IO from './IO'; import ScrollElement from './ScrollElement'; /** Defined attributes that need a requestAnimationFrame */ const ATTRIBUTES_THAT_NEED_RAF = [ 'scrollOffset', 'scrollPosition', 'scrollCssProgress', 'scrollEventProgress', 'scrollSpeed', ]; /** Default root margins */ const TRIGGER_ROOT_MARGIN = '-1px -1px -1px -1px'; const RAF_ROOT_MARGIN = '100% 100% 100% 100%'; // Add 100vh top/bottom && 100vw left/right to use a biggest value with data-scroll-speed /** Default scroll attribute values */ const DEFAULT_SCROLL_OFFSET = '0,0'; const DEFAULT_SCROLL_POSITION = 'top,bottom'; export default class Core { private $scrollContainer!: HTMLElement; private triggerRootMargin!: string; private rafRootMargin!: string; private scrollElements!: ScrollElement[]; private triggeredScrollElements!: ScrollElement[]; private RAFScrollElements!: ScrollElement[]; private scrollElementsToUpdate!: ScrollElement[]; private IOTriggerInstance!: IO; private IORafInstance!: IO; private scrollOrientation!: scrollOrientation; private lenisInstance: any; constructor({ $el, triggerRootMargin, rafRootMargin, scrollOrientation, lenisInstance, }: CoreOptions) { if (!$el) { console.error('Please provide a DOM Element as scrollContainer'); return; } // Scroll container this.$scrollContainer = $el; // Lenis instance this.lenisInstance = lenisInstance; // Scroll Direction this.scrollOrientation = scrollOrientation; // IO Margins this.triggerRootMargin = triggerRootMargin ?? TRIGGER_ROOT_MARGIN; this.rafRootMargin = rafRootMargin ?? RAF_ROOT_MARGIN; // ScrollElements arrays this.scrollElements = []; this.triggeredScrollElements = []; this.RAFScrollElements = []; this.scrollElementsToUpdate = []; // Init this._init(); } /** * Lifecyle - Initialize the core. * * @private */ private _init() { const $scrollElements = this.$scrollContainer.querySelectorAll('[data-scroll]'); const $scrollElementsArr = this.toElementArray($scrollElements); this._subscribeScrollElements($scrollElementsArr); // Determine IO root (null for window, wrapper element for custom container) const ioRoot = this.lenisInstance.options.wrapper === window ? null : this.lenisInstance.options.wrapper as HTMLElement; // Trigger IO this.IOTriggerInstance = new IO({ scrollElements: [...this.triggeredScrollElements], root: ioRoot, rootMargin: this.triggerRootMargin, IORaf: false, }); // Raf IO this.IORafInstance = new IO({ scrollElements: [...this.RAFScrollElements], root: ioRoot, rootMargin: this.rafRootMargin, IORaf: true, }); } /** * Lifecyle - Destroy core. */ public destroy() { this.IOTriggerInstance.destroy(); this.IORafInstance.destroy(); this._unsubscribeAllScrollElements(); } /** * Callback - Resize callback. */ onResize({ currentScroll }: IScrollElementCallbacksValues) { for (const scrollElement of this.RAFScrollElements) { scrollElement.onResize({ currentScroll, } as IScrollElementCallbacksValues); } } /** * Callback - RAF callback. */ onRender({ currentScroll, smooth }: IScrollElementCallbacksValues) { for (const scrollElement of this.scrollElementsToUpdate) { scrollElement.onRender({ currentScroll, smooth, } as IScrollElementCallbacksValues); } } /** * Remove items from lists of scroll elements and compute all new values. * * @param {HTMLElement} $oldContainer - HTMLElement that contains data-scroll elements to unsubscribe */ removeScrollElements($oldContainer: HTMLElement) { const $scrollElementsToRemove = $oldContainer.querySelectorAll('[data-scroll]'); if (!$scrollElementsToRemove.length) return; const $scrollElementsToRemoveSet = new Set(Array.from($scrollElementsToRemove)); // 1. Remove from IO for (let index = 0; index < this.triggeredScrollElements.length; index++) { const scrollElement = this.triggeredScrollElements[index]; if ($scrollElementsToRemoveSet.has(scrollElement.$el)) { this.IOTriggerInstance.unobserve(scrollElement.$el); this.triggeredScrollElements.splice(index, 1); } } for (let index = 0; index < this.RAFScrollElements.length; index++) { const scrollElement = this.RAFScrollElements[index]; if ($scrollElementsToRemoveSet.has(scrollElement.$el)) { this.IORafInstance.unobserve(scrollElement.$el); this.RAFScrollElements.splice(index, 1); } } // 2. Remove from scrollElementsToUpdate[] and scrollElements[] $scrollElementsToRemove.forEach(($scrollElement) => { const targetScrollElementToUpdate = this.scrollElementsToUpdate.find( (scrollElement) => scrollElement.$el === $scrollElement ); const targetScrollElement = this.scrollElements.find( (scrollElement) => scrollElement.$el === $scrollElement ); if (targetScrollElementToUpdate) { this._unsubscribeElementUpdate(targetScrollElementToUpdate); } if (targetScrollElement) { this.scrollElements = this.scrollElements.filter( (scrollElementItem) => scrollElementItem.id != targetScrollElement.id ); } }); } /** * Add items to lists of scroll elements and compute all new values. * * @param {HTMLElement} $newContainer - HTMLElement that contains data-scroll elements to subscribe */ addScrollElements($newContainer: HTMLElement) { // 3. Rebuild ScrollElements const $scrollElements = $newContainer.querySelectorAll('[data-scroll]'); // 4. Get max scrollElement.id const ids: number[] = []; this.scrollElements.forEach((scrollElement) => { ids.push(scrollElement.id); }); const maxID = Math.max(...ids, 0); const fromIndex = maxID + 1; const $scrollElementsArr = this.toElementArray($scrollElements); this._subscribeScrollElements( $scrollElementsArr, fromIndex, true ); } /** * Create a ScrollElement instance for each elements with * `data-scroll` attribute. * * @private * * @param {HTMLElement[]} $scrollElements - List of elements that need * to be regarded. */ _subscribeScrollElements( $scrollElements: HTMLElement[], fromIndex = 0, toObserve = false ) { // For each scroll element create a ScrollElement instance for (let index = 0; index < $scrollElements.length; index++) { const $scrollElement = $scrollElements[index]; const needRaf = this._checkRafNeeded($scrollElement); const scrollElementInstance = new ScrollElement({ $el: $scrollElement, id: fromIndex + index, scrollOrientation: this.scrollOrientation, lenisInstance: this.lenisInstance, subscribeElementUpdateFn: this._subscribeElementUpdate.bind(this), unsubscribeElementUpdateFn: this._unsubscribeElementUpdate.bind(this), needRaf, }); // Push to common array this.scrollElements.push(scrollElementInstance); // Push to specific array if (needRaf) { this.RAFScrollElements.push(scrollElementInstance); // Dynamic observe item if (toObserve) { this.IORafInstance.scrollElements.push( scrollElementInstance ); this.IORafInstance.observe(scrollElementInstance.$el); } } else { this.triggeredScrollElements.push(scrollElementInstance); // Dynamic observe item if (toObserve) { this.IOTriggerInstance.scrollElements.push( scrollElementInstance ); this.IOTriggerInstance.observe(scrollElementInstance.$el); } } } } /** * Clear all ScrollElement arrays. * * @private */ _unsubscribeAllScrollElements() { // Destroy all scroll elements to clean up CSS and references for (const scrollElement of this.scrollElements) { scrollElement.destroy(); } this.scrollElements = []; this.RAFScrollElements = []; this.triggeredScrollElements = []; this.scrollElementsToUpdate = []; } /** * Subscribe ScrollElement instance that needs to be updated. * * @private * * @param {ScrollElement} scrollElement - ScrollElement instance inview * that needs to be updated. */ _subscribeElementUpdate(scrollElement: ScrollElement) { this.scrollElementsToUpdate.push(scrollElement); } /** * Unscribe ScrollElement instance that doesn't need to be updated. * * @private * * @param {ScrollElement} scrollElement - The updated ScrollElement instance * out of view now. */ _unsubscribeElementUpdate(scrollElement: ScrollElement) { this.scrollElementsToUpdate = this.scrollElementsToUpdate.filter( (scrollElementToUpdate) => scrollElementToUpdate.id != scrollElement.id ); } /** * Convert NodeListOf to HTMLElement array. * * @private * * @param {NodeListOf} elements - The NodeList to convert. * * @returns {HTMLElement[]} */ private toElementArray(elements: NodeListOf): HTMLElement[] { return Array.from(elements) as HTMLElement[]; } /** * Check if a DOM Element need a requestAnimationFrame to be used. * * @private * * @param {HTMLElement} $scrollElement - The element that needs to be checked. * * @returns {boolean} */ _checkRafNeeded($scrollElement: HTMLElement) { let attributesThatNeedRaf = [...ATTRIBUTES_THAT_NEED_RAF]; // Remove utils const removeAttribute = (attributeToRemove: string) => { attributesThatNeedRaf = attributesThatNeedRaf.filter( (attribute) => attribute !== attributeToRemove ); }; // 1. Check scroll offset values if ($scrollElement.dataset.scrollOffset) { const value = $scrollElement.dataset.scrollOffset .split(',') .map((test) => test.replace('%', '').trim()) .join(','); if (value !== DEFAULT_SCROLL_OFFSET) { return true; } else { removeAttribute('scrollOffset'); } } else { removeAttribute('scrollOffset'); } // 2. Check scroll position values if ($scrollElement.dataset.scrollPosition) { const value = $scrollElement.dataset.scrollPosition.trim(); if (value !== DEFAULT_SCROLL_POSITION) { return true; } else { removeAttribute('scrollPosition'); } } else { removeAttribute('scrollPosition'); } // 3. Check scroll speed values if ( $scrollElement.dataset.scrollSpeed && !isNaN(parseFloat($scrollElement.dataset.scrollSpeed)) ) { return true; } else { removeAttribute('scrollSpeed'); } // 4. Check others attributes for (const attribute of attributesThatNeedRaf) { if (attribute in $scrollElement.dataset) { return true; } } return false; } } ================================================ FILE: packages/lib/core/IO.ts ================================================ /** * Intersection Observer * * Detecting visibility of an element in the viewport. * * Features functions to: * * - Trigger inview/outOfView callbacks * - If the element has a requestAnimationFrame dependency, set interactivy status for the ScrollElement Class * * References: * * - {@link https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API} */ import type { IIOOptions } from '../types'; import ScrollElement from './ScrollElement'; export default class IO { public scrollElements: ScrollElement[]; private rootMargin: string; private root: HTMLElement | null; private IORaf: boolean; private observer!: IntersectionObserver; constructor({ scrollElements, rootMargin = '-1px -1px -1px -1px', root = null, IORaf, }: IIOOptions) { // Parameters this.scrollElements = scrollElements; this.rootMargin = rootMargin; this.root = root; this.IORaf = IORaf; // Init this._init(); } /** * Lifecyle - Initialize Intersection Observer. * * @private */ private _init() { // Options const observerOptions = { root: this.root, rootMargin: this.rootMargin, }; // Callback const onIntersect = (entries: IntersectionObserverEntry[]) => { entries.forEach((entry) => { const $targetItem: ScrollElement | undefined = this.scrollElements.find( (item) => item.$el === entry.target ); if (entry.isIntersecting) { $targetItem && ($targetItem.isAlreadyIntersected = true); this._setInview(entry); } else if ($targetItem && $targetItem.isAlreadyIntersected) { this._setOutOfView(entry); } }); }; // Instance this.observer = new IntersectionObserver(onIntersect, observerOptions); // Observe each default elements for (const scrollElement of this.scrollElements) { const $scrollElement = scrollElement.$el; this.observe($scrollElement); } } /** * Lifecyle - Destroy Intersection Observer. */ public destroy() { this.observer.disconnect(); } /** * Subscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to observe. */ public observe($scrollElement: HTMLElement) { if (!$scrollElement) { return; } this.observer.observe($scrollElement); } /** * Unsubscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to unobserve. */ public unobserve($scrollElement: HTMLElement) { if (!$scrollElement) { return; } this.observer.unobserve($scrollElement); } /** * Find ScrollElementReference instance and trigger inview callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */ private _setInview(entry: IntersectionObserverEntry) { const scrollElement = this.scrollElements.find( (scrollElement) => scrollElement.$el === entry.target ); this.IORaf && scrollElement?.setInteractivityOn(); !this.IORaf && scrollElement?.setInview(); } /** * Find ScrollElementReference instance and trigger out of view callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */ private _setOutOfView(entry: IntersectionObserverEntry) { const scrollElement = this.scrollElements.find( (scrollElement) => scrollElement.$el === entry.target ); this.IORaf && scrollElement?.setInteractivityOff(); !this.IORaf && scrollElement?.setOutOfView(); // Unobserve if element doesn't have repeat attribute if (!scrollElement?.attributes.scrollRepeat && !this.IORaf) { this.unobserve(entry.target as HTMLElement); } } } ================================================ FILE: packages/lib/core/ScrollElement.ts ================================================ /** * Scroll Element * * Give tools to compute element progress in the viewport and triggers callbacks to animate it. * * Features functions to: * * - scrollClass - Add a custom class when the element is intersected by the offset * - scrollOffset - Determine offsets to intersect the element * - scrollPosition - Determine the element positions to consider an element as intersected. * - scrollCssProgress - Add a specific css variable (PROGRESS_CSS_VAR) that store the scroll progress * - scrollEventProgress - Send scroll progress to custom event listeners. * - scrollSpeed - Add a scroll multiplicator to create a parallax effect * - scrollRepeat - Repeat the option to trigger animation each time the element is intersected * - scrollCall - Call a custom event when the element is intersected */ import type { IScrollElementOptions, IScrollElementAttributes, IScrollElementIntersection, IScrollElementMetrics, IScrollElementCallbacksValues, scrollOrientation, } from '../types'; import { clamp, closestNumber, normalize, mapRange } from '../utils/maths'; /** Constants */ const INVIEW_CLASS = 'is-inview'; const PROGRESS_CSS_VAR = '--progress'; export default class ScrollElement { public $el: HTMLElement; public id: number; public needRaf: boolean; public attributes: IScrollElementAttributes; public scrollOrientation: scrollOrientation; public isAlreadyIntersected: boolean; private intersection: IScrollElementIntersection; private metrics: IScrollElementMetrics; private currentScroll: number; private translateValue: number; private progress: number; private lastProgress: number | null; private isInview: boolean; private isInteractive: boolean; private isInFold: boolean; private isFirstResize: boolean; private subscribeElementUpdateFn: (scrollElement: ScrollElement) => void; private unsubscribeElementUpdateFn: (scrollElement: ScrollElement) => void; private lenisInstance: any; // Cached functions to avoid orientation checks every frame private getWindowSize: () => number; private getMetricsStart: (bcr: DOMRect) => number; private getMetricsSize: (bcr: DOMRect) => number; // Position handlers for intersection.start (includes wSize) private readonly startPositionHandlers: Record number> = { 'start': (offsetStart, wSize, viewport) => offsetStart - wSize + viewport, 'middle': (offsetStart, wSize, viewport, size) => offsetStart - wSize + viewport + size * 0.5, 'end': (offsetStart, wSize, viewport, size) => offsetStart - wSize + viewport + size, 'fold': () => 0, }; // Position handlers for intersection.end (DOES NOT include wSize - critical difference) private readonly endPositionHandlers: Record number> = { 'start': (offsetStart, viewport) => offsetStart - viewport, 'middle': (offsetStart, viewport, size) => offsetStart - viewport + size * 0.5, 'end': (offsetStart, viewport, size) => offsetStart - viewport + size, }; constructor({ $el, id, subscribeElementUpdateFn, unsubscribeElementUpdateFn, needRaf, scrollOrientation, lenisInstance, }: IScrollElementOptions) { // Scroll DOM element this.$el = $el; // Unique ID this.id = id; // RAF option this.needRaf = needRaf; // Scroll Direction this.scrollOrientation = scrollOrientation; // Lenis instance this.lenisInstance = lenisInstance; // Parent's callbacks this.subscribeElementUpdateFn = subscribeElementUpdateFn; this.unsubscribeElementUpdateFn = unsubscribeElementUpdateFn; // Attributes this.attributes = { scrollClass: this.$el.dataset['scrollClass'] ?? INVIEW_CLASS, scrollOffset: this.$el.dataset['scrollOffset'] ?? '0,0', scrollPosition: this.$el.dataset['scrollPosition'] ?? 'start,end', scrollCssProgress: this.$el.dataset['scrollCssProgress'] !== undefined, scrollEventProgress: this.$el.dataset['scrollEventProgress'] ?? null, scrollSpeed: this.$el.dataset['scrollSpeed'] !== undefined ? parseFloat(this.$el.dataset['scrollSpeed']) : null, scrollRepeat: this.$el.dataset['scrollRepeat'] !== undefined, scrollCall: this.$el.dataset['scrollCall'] ?? null, scrollIgnoreFold: this.$el.dataset['scrollIgnoreFold'] !== undefined, scrollEnableTouchSpeed: this.$el.dataset['scrollEnableTouchSpeed'] !== undefined, }; // Limits this.intersection = { start: 0, end: 0, }; // Metrics this.metrics = { offsetStart: 0, offsetEnd: 0, bcr: {} as DOMRect, }; // Scroll Values this.currentScroll = this.lenisInstance.scroll; // Parallax this.translateValue = 0; // Progress this.progress = 0; this.lastProgress = null; // Inview this.isInview = false; this.isInteractive = false; this.isAlreadyIntersected = false; this.isInFold = false; this.isFirstResize = true; // Cache orientation-dependent functions to avoid repeated conditionals this.getWindowSize = this.scrollOrientation === 'vertical' ? () => this.lenisInstance.dimensions.height : () => this.lenisInstance.dimensions.width; this.getMetricsStart = this.scrollOrientation === 'vertical' ? (bcr: DOMRect) => bcr.top : (bcr: DOMRect) => bcr.left; this.getMetricsSize = this.scrollOrientation === 'vertical' ? (bcr: DOMRect) => bcr.height : (bcr: DOMRect) => bcr.width; // Init this._init(); } /** * Lifecyle - Initialize progress tracking. * * @private */ private _init() { if (!this.needRaf) { return; } // First resize to compute all values this._resize(); } /** * Callback - Resize callback */ public onResize({ currentScroll }: IScrollElementCallbacksValues) { this.currentScroll = currentScroll; this._resize(); } /** * Callback - RAF callback */ public onRender({ currentScroll, smooth }: IScrollElementCallbacksValues) { const wSize = this.getWindowSize(); this.currentScroll = currentScroll; this._computeProgress(); // Parallax if ( this.attributes.scrollSpeed && !isNaN(this.attributes.scrollSpeed) ) { // if touch detected or smooth disabled if (!this.attributes.scrollEnableTouchSpeed && !smooth) { if (this.translateValue) { this.$el.style.transform = `translate3d(0, 0, 0)`; } this.translateValue = 0; // if mousewheel or smooth enabled } else { // Check fold condition if (this.isInFold) { const progress = Math.max(0, this.progress); this.translateValue = progress * wSize * this.attributes.scrollSpeed * -1; } else { const progress = mapRange(0, 1, -1, 1, this.progress); this.translateValue = progress * wSize * this.attributes.scrollSpeed * -1; } this.$el.style.transform = this.scrollOrientation === 'vertical' ? `translate3d(0, ${this.translateValue}px, 0)` : `translate3d(${this.translateValue}px, 0, 0)`; } } } /** * Inview callback */ public setInview() { if (this.isInview) { return; } this.isInview = true; this.$el.classList.add(this.attributes.scrollClass); const way = 'enter'; const from = this._getScrollCallFrom(); this.attributes.scrollCall && this._dispatchCall(way, from); } /** * Out of view callback */ public setOutOfView() { if (!(this.isInview && this.attributes.scrollRepeat)) { return; } this.isInview = false; this.$el.classList.remove(this.attributes.scrollClass); const way = 'leave'; const from = this._getScrollCallFrom(); this.attributes.scrollCall && this._dispatchCall(way, from); } /** * Switch interactivity on to subscribe the instance to the RAF * and start calculations. */ public setInteractivityOn() { if (this.isInteractive) { return; } this.isInteractive = true; this.subscribeElementUpdateFn(this); } /** * Switch interactivity off to unsubscribe the instance to the RAF * and stop calculations. */ public setInteractivityOff() { if (!this.isInteractive) { return; } this.isInteractive = false; this.unsubscribeElementUpdateFn(this); // Force progress to progress limit when the element is out this.lastProgress !== null && this._computeProgress(closestNumber([0, 1], this.lastProgress)); } /** * Resize method that compute the element's values. * * @private */ private _resize() { this.metrics.bcr = this.$el.getBoundingClientRect(); this._computeMetrics(); this._computeIntersection(); // First resize logic if (this.isFirstResize) { this.isFirstResize = false; // Dispatch default call if the element is in fold. if (this.isInFold) { this.setInview(); } } } /** * Compute element's offsets and determine if the element is in fold. * * @private */ private _computeMetrics() { const wSize = this.getWindowSize(); const metricsStart = this.getMetricsStart(this.metrics.bcr); const metricsSize = this.getMetricsSize(this.metrics.bcr); this.metrics.offsetStart = this.currentScroll + metricsStart - this.translateValue; this.metrics.offsetEnd = this.metrics.offsetStart + metricsSize; if ( this.metrics.offsetStart < wSize && !this.attributes.scrollIgnoreFold ) { this.isInFold = true; } else { this.isInFold = false; } } /** * Compute intersection values depending on the context. * Uses handler-based approach for cleaner, more maintainable code. * * @private */ private _computeIntersection() { const wSize = this.getWindowSize(); const metricsSize = this.getMetricsSize(this.metrics.bcr); // Parse offset const offset = this.attributes.scrollOffset.split(','); const offsetStart = offset[0]?.trim() ?? '0'; const offsetEnd = offset[1]?.trim() ?? '0'; // Parse positions const scrollPosition = this.attributes.scrollPosition.split(','); let scrollPositionStart = scrollPosition[0]?.trim() ?? 'start'; const scrollPositionEnd = scrollPosition[1]?.trim() ?? 'end'; // Calculate viewport offsets const viewportStart = offsetStart.includes('%') ? wSize * parseInt(offsetStart.replace('%', '').trim()) * 0.01 : parseInt(offsetStart); const viewportEnd = offsetEnd.includes('%') ? wSize * parseInt(offsetEnd.replace('%', '').trim()) * 0.01 : parseInt(offsetEnd); // Fold exception if (this.isInFold) { scrollPositionStart = 'fold'; } // Calculate intersection.start using handlers const startHandler = this.startPositionHandlers[scrollPositionStart]; this.intersection.start = startHandler ? startHandler(this.metrics.offsetStart, wSize, viewportStart, metricsSize) : this.metrics.offsetStart - wSize + viewportStart; // default fallback // Calculate intersection.end using handlers const endHandler = this.endPositionHandlers[scrollPositionEnd]; this.intersection.end = endHandler ? endHandler(this.metrics.offsetStart, viewportEnd, metricsSize) : this.metrics.offsetStart - viewportEnd + metricsSize; // default fallback // Ensure end > start if (this.intersection.end <= this.intersection.start) { switch (scrollPositionEnd) { case 'start': this.intersection.end = this.intersection.start + 1; break; case 'middle': this.intersection.end = this.intersection.start + metricsSize * 0.5; break; case 'end': this.intersection.end = this.intersection.start + metricsSize; break; default: this.intersection.end = this.intersection.start + 1; break; } } } /** * Compute the scroll progress of the element depending * on its intersection values. * * @private * * @param {number} [forcedProgress] - Value to force progress. */ private _computeProgress(forcedProgress?: number) { // Progress const progress = forcedProgress ?? clamp( 0, 1, normalize( this.intersection.start, this.intersection.end, this.currentScroll ) ); this.progress = progress; if (progress !== this.lastProgress) { this.lastProgress = progress; // Set the element's progress to the css variable this.attributes.scrollCssProgress && this._setCssProgress(progress); // Set the element's progress to the custom event listeners this.attributes.scrollEventProgress && this._setCustomEventProgress(progress); // Logic to trigger the inview/out of view callbacks progress > 0 && progress < 1 && this.setInview(); progress === 0 && this.setOutOfView(); progress === 1 && this.setOutOfView(); } } /** * Set the element's progress to a specific css variable. * * @private * * @param {number} [currentProgress] - Progress value. */ _setCssProgress(currentProgress = 0) { this.$el.style.setProperty( PROGRESS_CSS_VAR, currentProgress.toString() ); } /** * Set the element's progress to the custom event listeners. * * @private * * @param {number} [currentProgress] - Progress value. */ _setCustomEventProgress(currentProgress = 0) { const customEventName = this.attributes.scrollEventProgress; if (!customEventName) return; const customEvent = new CustomEvent(customEventName, { detail: { target: this.$el, progress: currentProgress, }, }); window.dispatchEvent(customEvent); } /** * Function to get scroll call from. * * @private */ _getScrollCallFrom() { const closestIntersectionValue = closestNumber( [this.intersection.start, this.intersection.end], this.currentScroll ); return this.intersection.start === closestIntersectionValue ? 'start' : 'end'; } /** * Lifecyle - Destroy and cleanup the scroll element. * * Removes all CSS modifications and clears references to prevent memory leaks. */ public destroy(): void { // Remove CSS variables if (this.attributes.scrollCssProgress) { this.$el.style.removeProperty(PROGRESS_CSS_VAR); } // Remove transform if parallax was applied if (this.attributes.scrollSpeed) { this.$el.style.removeProperty('transform'); } // Remove class if added if (this.isInview && this.attributes.scrollClass) { this.$el.classList.remove(this.attributes.scrollClass); } } /** * Function to dispatch a custom event. * * @private * * @param {string} way - Enter or leave. * @param {string} from - Start or end. */ _dispatchCall(way: string, from: string) { const customEventName = this.attributes.scrollCall; if (!customEventName) return; // Using CustomEvent API (https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) const customEvent = new CustomEvent(customEventName, { detail: { target: this.$el, way, from, }, }); window.dispatchEvent(customEvent); } } ================================================ FILE: packages/lib/dist/locomotive-scroll.cjs ================================================ function t(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var e=/*#__PURE__*/t(require("lenis"));function s(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(e);s=t.length?{done:!0}:{done:!1,value:t[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function n(){return n=Object.assign?Object.assign.bind():function(t){for(var e=1;e1?1:e;this.progress=s,s!==this.lastProgress&&(this.lastProgress=s,this.attributes.scrollCssProgress&&this._setCssProgress(s),this.attributes.scrollEventProgress&&this._setCustomEventProgress(s),s>0&&s<1&&this.setInview(),0===s&&this.setOutOfView(),1===s&&this.setOutOfView())},e._setCssProgress=function(t){void 0===t&&(t=0),this.$el.style.setProperty(a,t.toString())},e._setCustomEventProgress=function(t){void 0===t&&(t=0);var e=this.attributes.scrollEventProgress;if(e){var s=new CustomEvent(e,{detail:{target:this.$el,progress:t}});window.dispatchEvent(s)}},e._getScrollCallFrom=function(){var t=l([this.intersection.start,this.intersection.end],this.currentScroll);return this.intersection.start===t?"start":"end"},e.destroy=function(){this.attributes.scrollCssProgress&&this.$el.style.removeProperty(a),this.attributes.scrollSpeed&&this.$el.style.removeProperty("transform"),this.isInview&&this.attributes.scrollClass&&this.$el.classList.remove(this.attributes.scrollClass)},e._dispatchCall=function(t,e){var s=this.attributes.scrollCall;if(s){var i=new CustomEvent(s,{detail:{target:this.$el,way:t,from:e}});window.dispatchEvent(i)}},t}(),h=["scrollOffset","scrollPosition","scrollCssProgress","scrollEventProgress","scrollSpeed"],u=/*#__PURE__*/function(){function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.scrollOrientation,r=t.lenisInstance;this.$scrollContainer=void 0,this.triggerRootMargin=void 0,this.rafRootMargin=void 0,this.scrollElements=void 0,this.triggeredScrollElements=void 0,this.RAFScrollElements=void 0,this.scrollElementsToUpdate=void 0,this.IOTriggerInstance=void 0,this.IORafInstance=void 0,this.scrollOrientation=void 0,this.lenisInstance=void 0,e?(this.$scrollContainer=e,this.lenisInstance=r,this.scrollOrientation=n,this.triggerRootMargin=null!=s?s:"-1px -1px -1px -1px",this.rafRootMargin=null!=i?i:"100% 100% 100% 100%",this.scrollElements=[],this.triggeredScrollElements=[],this.RAFScrollElements=[],this.scrollElementsToUpdate=[],this._init()):console.error("Please provide a DOM Element as scrollContainer")}var e=t.prototype;return e._init=function(){var t=this.$scrollContainer.querySelectorAll("[data-scroll]"),e=this.toElementArray(t);this._subscribeScrollElements(e);var s=this.lenisInstance.options.wrapper===window?null:this.lenisInstance.options.wrapper;this.IOTriggerInstance=new r({scrollElements:[].concat(this.triggeredScrollElements),root:s,rootMargin:this.triggerRootMargin,IORaf:!1}),this.IORafInstance=new r({scrollElements:[].concat(this.RAFScrollElements),root:s,rootMargin:this.rafRootMargin,IORaf:!0})},e.destroy=function(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),this._unsubscribeAllScrollElements()},e.onResize=function(t){for(var e,s=t.currentScroll,n=i(this.RAFScrollElements);!(e=n()).done;)e.value.onResize({currentScroll:s})},e.onRender=function(t){for(var e,s=t.currentScroll,n=t.smooth,r=i(this.scrollElementsToUpdate);!(e=r()).done;)e.value.onRender({currentScroll:s,smooth:n})},e.removeScrollElements=function(t){var e=this,s=t.querySelectorAll("[data-scroll]");if(s.length){for(var i=new Set(Array.from(s)),n=0;n0,this._init()}var s=t.prototype;return s._init=function(){var t=this;this.lenisInstance=new e.default(n({},this.lenisOptions)),this.scrollCallback&&this.lenisInstance.on("scroll",this.scrollCallback),document.documentElement.setAttribute("data-scroll-orientation",this.lenisInstance.options.orientation),requestAnimationFrame(function(){t.coreInstance=new u({$el:t.lenisInstance.rootElement,triggerRootMargin:t.triggerRootMargin,rafRootMargin:t.rafRootMargin,scrollOrientation:t.lenisInstance.options.orientation,lenisInstance:t.lenisInstance}),t._bindEvents(),t.initCustomTicker&&!t.destroyCustomTicker?console.warn("initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble."):!t.initCustomTicker&&t.destroyCustomTicker&&console.warn("destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble."),t.autoStart&&t.start()})},s.destroy=function(){var t,e=this;this.stop(),this._unbindEvents(),null==(t=this.lenisInstance)||t.destroy(),requestAnimationFrame(function(){var t;null==(t=e.coreInstance)||t.destroy()})},s._bindEvents=function(){var t=this;this._bindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize=this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions),this._originalOnWrapperResize=this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions),this.lenisInstance.dimensions.onContentResize=function(){null==t._originalOnContentResize||t._originalOnContentResize(),t._onResizeBind()},this.lenisInstance.dimensions.onWrapperResize=function(){null==t._originalOnWrapperResize||t._originalOnWrapperResize(),t._onResizeBind()})},s._unbindEvents=function(){this._unbindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize&&(this.lenisInstance.dimensions.onContentResize=this._originalOnContentResize),this._originalOnWrapperResize&&(this.lenisInstance.dimensions.onWrapperResize=this._originalOnWrapperResize))},s._bindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.addEventListener("click",s._onScrollToBind,!1)})},s._unbindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.removeEventListener("click",s._onScrollToBind,!1)})},s._onResize=function(){var t,e,s;null==(t=this.coreInstance)||t.onResize({currentScroll:null!=(e=null==(s=this.lenisInstance)?void 0:s.scroll)?e:0,smooth:!this.isTouchDevice})},s._onRender=function(){var t,e,s,i;null==(t=this.lenisInstance)||t.raf(Date.now()),null==(e=this.coreInstance)||e.onRender({currentScroll:null!=(s=null==(i=this.lenisInstance)?void 0:i.scroll)?s:0,smooth:!this.isTouchDevice})},s._onScrollTo=function(t){var e,s;t.preventDefault();var i=null!=(e=t.currentTarget)?e:null;if(i){var n=i.getAttribute("data-scroll-to-href")||i.getAttribute("href"),r=i.getAttribute("data-scroll-to-offset")||0,o=i.getAttribute("data-scroll-to-duration")||(null==(s=this.lenisInstance)?void 0:s.options.duration);n&&this.scrollTo(n,{offset:"string"==typeof r?parseInt(r):r,duration:"string"==typeof o?parseInt(o):o})}},s.start=function(){var t;this.rafPlaying||(null==(t=this.lenisInstance)||t.start(),this.rafPlaying=!0,this.initCustomTicker?this.initCustomTicker(this._onRenderBind):this._raf())},s.stop=function(){var t;this.rafPlaying&&(null==(t=this.lenisInstance)||t.stop(),this.rafPlaying=!1,this.destroyCustomTicker?this.destroyCustomTicker(this._onRenderBind):this.rafInstance&&cancelAnimationFrame(this.rafInstance))},s.removeScrollElements=function(t){var e;t?(this._unbindScrollToEvents(t),null==(e=this.coreInstance)||e.removeScrollElements(t)):console.error("Please provide a DOM Element as $oldContainer")},s.addScrollElements=function(t){var e,s=this;t?(null==(e=this.coreInstance)||e.addScrollElements(t),requestAnimationFrame(function(){s._bindScrollToEvents(t)})):console.error("Please provide a DOM Element as $newContainer")},s.resize=function(){this._onResizeBind()},s.scrollTo=function(t,e){var s;null==(s=this.lenisInstance)||s.scrollTo(t,{offset:null==e?void 0:e.offset,lerp:null==e?void 0:e.lerp,duration:null==e?void 0:e.duration,immediate:null==e?void 0:e.immediate,lock:null==e?void 0:e.lock,force:null==e?void 0:e.force,easing:null==e?void 0:e.easing,onComplete:null==e?void 0:e.onComplete})},s._raf=function(){var t=this;this._onRenderBind(),this.rafInstance=requestAnimationFrame(function(){return t._raf()})},t}(); //# sourceMappingURL=locomotive-scroll.cjs.map ================================================ FILE: packages/lib/dist/locomotive-scroll.css ================================================ html.lenis,html.lenis body{height:auto}.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}.lenis [data-lenis-prevent-touch],.lenis [data-lenis-prevent-wheel],.lenis [data-lenis-prevent]{overscroll-behavior:contain}.lenis.lenis-smooth iframe{pointer-events:none}.lenis.lenis-autoToggle{transition-behavior:allow-discrete;transition-duration:1ms;transition-property:overflow} ================================================ FILE: packages/lib/dist/locomotive-scroll.mjs ================================================ import t from"lenis";function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(e);s=t.length?{done:!0}:{done:!1,value:t[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function i(){return i=Object.assign?Object.assign.bind():function(t){for(var e=1;e1?1:e;this.progress=s,s!==this.lastProgress&&(this.lastProgress=s,this.attributes.scrollCssProgress&&this._setCssProgress(s),this.attributes.scrollEventProgress&&this._setCustomEventProgress(s),s>0&&s<1&&this.setInview(),0===s&&this.setOutOfView(),1===s&&this.setOutOfView())},e._setCssProgress=function(t){void 0===t&&(t=0),this.$el.style.setProperty(l,t.toString())},e._setCustomEventProgress=function(t){void 0===t&&(t=0);var e=this.attributes.scrollEventProgress;if(e){var s=new CustomEvent(e,{detail:{target:this.$el,progress:t}});window.dispatchEvent(s)}},e._getScrollCallFrom=function(){var t=o([this.intersection.start,this.intersection.end],this.currentScroll);return this.intersection.start===t?"start":"end"},e.destroy=function(){this.attributes.scrollCssProgress&&this.$el.style.removeProperty(l),this.attributes.scrollSpeed&&this.$el.style.removeProperty("transform"),this.isInview&&this.attributes.scrollClass&&this.$el.classList.remove(this.attributes.scrollClass)},e._dispatchCall=function(t,e){var s=this.attributes.scrollCall;if(s){var i=new CustomEvent(s,{detail:{target:this.$el,way:t,from:e}});window.dispatchEvent(i)}},t}(),c=["scrollOffset","scrollPosition","scrollCssProgress","scrollEventProgress","scrollSpeed"],h=/*#__PURE__*/function(){function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.scrollOrientation,r=t.lenisInstance;this.$scrollContainer=void 0,this.triggerRootMargin=void 0,this.rafRootMargin=void 0,this.scrollElements=void 0,this.triggeredScrollElements=void 0,this.RAFScrollElements=void 0,this.scrollElementsToUpdate=void 0,this.IOTriggerInstance=void 0,this.IORafInstance=void 0,this.scrollOrientation=void 0,this.lenisInstance=void 0,e?(this.$scrollContainer=e,this.lenisInstance=r,this.scrollOrientation=n,this.triggerRootMargin=null!=s?s:"-1px -1px -1px -1px",this.rafRootMargin=null!=i?i:"100% 100% 100% 100%",this.scrollElements=[],this.triggeredScrollElements=[],this.RAFScrollElements=[],this.scrollElementsToUpdate=[],this._init()):console.error("Please provide a DOM Element as scrollContainer")}var e=t.prototype;return e._init=function(){var t=this.$scrollContainer.querySelectorAll("[data-scroll]"),e=this.toElementArray(t);this._subscribeScrollElements(e);var s=this.lenisInstance.options.wrapper===window?null:this.lenisInstance.options.wrapper;this.IOTriggerInstance=new n({scrollElements:[].concat(this.triggeredScrollElements),root:s,rootMargin:this.triggerRootMargin,IORaf:!1}),this.IORafInstance=new n({scrollElements:[].concat(this.RAFScrollElements),root:s,rootMargin:this.rafRootMargin,IORaf:!0})},e.destroy=function(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),this._unsubscribeAllScrollElements()},e.onResize=function(t){for(var e,i=t.currentScroll,n=s(this.RAFScrollElements);!(e=n()).done;)e.value.onResize({currentScroll:i})},e.onRender=function(t){for(var e,i=t.currentScroll,n=t.smooth,r=s(this.scrollElementsToUpdate);!(e=r()).done;)e.value.onRender({currentScroll:i,smooth:n})},e.removeScrollElements=function(t){var e=this,s=t.querySelectorAll("[data-scroll]");if(s.length){for(var i=new Set(Array.from(s)),n=0;n0,this._init()}var s=e.prototype;return s._init=function(){var e=this;this.lenisInstance=new t(i({},this.lenisOptions)),this.scrollCallback&&this.lenisInstance.on("scroll",this.scrollCallback),document.documentElement.setAttribute("data-scroll-orientation",this.lenisInstance.options.orientation),requestAnimationFrame(function(){e.coreInstance=new h({$el:e.lenisInstance.rootElement,triggerRootMargin:e.triggerRootMargin,rafRootMargin:e.rafRootMargin,scrollOrientation:e.lenisInstance.options.orientation,lenisInstance:e.lenisInstance}),e._bindEvents(),e.initCustomTicker&&!e.destroyCustomTicker?console.warn("initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble."):!e.initCustomTicker&&e.destroyCustomTicker&&console.warn("destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble."),e.autoStart&&e.start()})},s.destroy=function(){var t,e=this;this.stop(),this._unbindEvents(),null==(t=this.lenisInstance)||t.destroy(),requestAnimationFrame(function(){var t;null==(t=e.coreInstance)||t.destroy()})},s._bindEvents=function(){var t=this;this._bindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize=this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions),this._originalOnWrapperResize=this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions),this.lenisInstance.dimensions.onContentResize=function(){null==t._originalOnContentResize||t._originalOnContentResize(),t._onResizeBind()},this.lenisInstance.dimensions.onWrapperResize=function(){null==t._originalOnWrapperResize||t._originalOnWrapperResize(),t._onResizeBind()})},s._unbindEvents=function(){this._unbindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize&&(this.lenisInstance.dimensions.onContentResize=this._originalOnContentResize),this._originalOnWrapperResize&&(this.lenisInstance.dimensions.onWrapperResize=this._originalOnWrapperResize))},s._bindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.addEventListener("click",s._onScrollToBind,!1)})},s._unbindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.removeEventListener("click",s._onScrollToBind,!1)})},s._onResize=function(){var t,e,s;null==(t=this.coreInstance)||t.onResize({currentScroll:null!=(e=null==(s=this.lenisInstance)?void 0:s.scroll)?e:0,smooth:!this.isTouchDevice})},s._onRender=function(){var t,e,s,i;null==(t=this.lenisInstance)||t.raf(Date.now()),null==(e=this.coreInstance)||e.onRender({currentScroll:null!=(s=null==(i=this.lenisInstance)?void 0:i.scroll)?s:0,smooth:!this.isTouchDevice})},s._onScrollTo=function(t){var e,s;t.preventDefault();var i=null!=(e=t.currentTarget)?e:null;if(i){var n=i.getAttribute("data-scroll-to-href")||i.getAttribute("href"),r=i.getAttribute("data-scroll-to-offset")||0,o=i.getAttribute("data-scroll-to-duration")||(null==(s=this.lenisInstance)?void 0:s.options.duration);n&&this.scrollTo(n,{offset:"string"==typeof r?parseInt(r):r,duration:"string"==typeof o?parseInt(o):o})}},s.start=function(){var t;this.rafPlaying||(null==(t=this.lenisInstance)||t.start(),this.rafPlaying=!0,this.initCustomTicker?this.initCustomTicker(this._onRenderBind):this._raf())},s.stop=function(){var t;this.rafPlaying&&(null==(t=this.lenisInstance)||t.stop(),this.rafPlaying=!1,this.destroyCustomTicker?this.destroyCustomTicker(this._onRenderBind):this.rafInstance&&cancelAnimationFrame(this.rafInstance))},s.removeScrollElements=function(t){var e;t?(this._unbindScrollToEvents(t),null==(e=this.coreInstance)||e.removeScrollElements(t)):console.error("Please provide a DOM Element as $oldContainer")},s.addScrollElements=function(t){var e,s=this;t?(null==(e=this.coreInstance)||e.addScrollElements(t),requestAnimationFrame(function(){s._bindScrollToEvents(t)})):console.error("Please provide a DOM Element as $newContainer")},s.resize=function(){this._onResizeBind()},s.scrollTo=function(t,e){var s;null==(s=this.lenisInstance)||s.scrollTo(t,{offset:null==e?void 0:e.offset,lerp:null==e?void 0:e.lerp,duration:null==e?void 0:e.duration,immediate:null==e?void 0:e.immediate,lock:null==e?void 0:e.lock,force:null==e?void 0:e.force,easing:null==e?void 0:e.easing,onComplete:null==e?void 0:e.onComplete})},s._raf=function(){var t=this;this._onRenderBind(),this.rafInstance=requestAnimationFrame(function(){return t._raf()})},e}();export{u as default}; //# sourceMappingURL=locomotive-scroll.mjs.map ================================================ FILE: packages/lib/dist/locomotive-scroll.modern.mjs ================================================ import t from"lenis";function s(){return s=Object.assign?Object.assign.bind():function(t){for(var s=1;s{t.forEach(t=>{const s=this.scrollElements.find(s=>s.$el===t.target);t.isIntersecting?(s&&(s.isAlreadyIntersected=!0),this._setInview(t)):s&&s.isAlreadyIntersected&&this._setOutOfView(t)})},{root:this.root,rootMargin:this.rootMargin});for(const t of this.scrollElements)this.observe(t.$el)}destroy(){this.observer.disconnect()}observe(t){t&&this.observer.observe(t)}unobserve(t){t&&this.observer.unobserve(t)}_setInview(t){const s=this.scrollElements.find(s=>s.$el===t.target);this.IORaf&&(null==s||s.setInteractivityOn()),!this.IORaf&&(null==s||s.setInview())}_setOutOfView(t){const s=this.scrollElements.find(s=>s.$el===t.target);this.IORaf&&(null==s||s.setInteractivityOff()),!this.IORaf&&(null==s||s.setOutOfView()),null!=s&&s.attributes.scrollRepeat||this.IORaf||this.unobserve(t.target)}}function i(t,s,e,i,n){return e+((n-t)/(s-t)*(i-e)||0)}function n(t,s){return t.reduce((t,e)=>Math.abs(e-s)t-s+e,middle:(t,s,e,i)=>t-s+e+.5*i,end:(t,s,e,i)=>t-s+e+i,fold:()=>0},this.endPositionHandlers={start:(t,s)=>t-s,middle:(t,s,e)=>t-s+.5*e,end:(t,s,e)=>t-s+e},this.$el=t,this.id=s,this.needRaf=n,this.scrollOrientation=r,this.lenisInstance=l,this.subscribeElementUpdateFn=e,this.unsubscribeElementUpdateFn=i,this.attributes={scrollClass:null!=(o=this.$el.dataset.scrollClass)?o:"is-inview",scrollOffset:null!=(a=this.$el.dataset.scrollOffset)?a:"0,0",scrollPosition:null!=(c=this.$el.dataset.scrollPosition)?c:"start,end",scrollCssProgress:void 0!==this.$el.dataset.scrollCssProgress,scrollEventProgress:null!=(h=this.$el.dataset.scrollEventProgress)?h:null,scrollSpeed:void 0!==this.$el.dataset.scrollSpeed?parseFloat(this.$el.dataset.scrollSpeed):null,scrollRepeat:void 0!==this.$el.dataset.scrollRepeat,scrollCall:null!=(d=this.$el.dataset.scrollCall)?d:null,scrollIgnoreFold:void 0!==this.$el.dataset.scrollIgnoreFold,scrollEnableTouchSpeed:void 0!==this.$el.dataset.scrollEnableTouchSpeed},this.intersection={start:0,end:0},this.metrics={offsetStart:0,offsetEnd:0,bcr:{}},this.currentScroll=this.lenisInstance.scroll,this.translateValue=0,this.progress=0,this.lastProgress=null,this.isInview=!1,this.isInteractive=!1,this.isAlreadyIntersected=!1,this.isInFold=!1,this.isFirstResize=!0,this.getWindowSize="vertical"===this.scrollOrientation?()=>this.lenisInstance.dimensions.height:()=>this.lenisInstance.dimensions.width,this.getMetricsStart="vertical"===this.scrollOrientation?t=>t.top:t=>t.left,this.getMetricsSize="vertical"===this.scrollOrientation?t=>t.height:t=>t.width,this._init()}_init(){this.needRaf&&this._resize()}onResize({currentScroll:t}){this.currentScroll=t,this._resize()}onRender({currentScroll:t,smooth:s}){const e=this.getWindowSize();if(this.currentScroll=t,this._computeProgress(),this.attributes.scrollSpeed&&!isNaN(this.attributes.scrollSpeed))if(this.attributes.scrollEnableTouchSpeed||s){if(this.isInFold){const t=Math.max(0,this.progress);this.translateValue=t*e*this.attributes.scrollSpeed*-1}else{const t=i(0,1,-1,1,this.progress);this.translateValue=t*e*this.attributes.scrollSpeed*-1}this.$el.style.transform="vertical"===this.scrollOrientation?`translate3d(0, ${this.translateValue}px, 0)`:`translate3d(${this.translateValue}px, 0, 0)`}else this.translateValue&&(this.$el.style.transform="translate3d(0, 0, 0)"),this.translateValue=0}setInview(){if(this.isInview)return;this.isInview=!0,this.$el.classList.add(this.attributes.scrollClass);const t=this._getScrollCallFrom();this.attributes.scrollCall&&this._dispatchCall("enter",t)}setOutOfView(){if(!this.isInview||!this.attributes.scrollRepeat)return;this.isInview=!1,this.$el.classList.remove(this.attributes.scrollClass);const t=this._getScrollCallFrom();this.attributes.scrollCall&&this._dispatchCall("leave",t)}setInteractivityOn(){this.isInteractive||(this.isInteractive=!0,this.subscribeElementUpdateFn(this))}setInteractivityOff(){this.isInteractive&&(this.isInteractive=!1,this.unsubscribeElementUpdateFn(this),null!==this.lastProgress&&this._computeProgress(n([0,1],this.lastProgress)))}_resize(){this.metrics.bcr=this.$el.getBoundingClientRect(),this._computeMetrics(),this._computeIntersection(),this.isFirstResize&&(this.isFirstResize=!1,this.isInFold&&this.setInview())}_computeMetrics(){const t=this.getWindowSize(),s=this.getMetricsStart(this.metrics.bcr),e=this.getMetricsSize(this.metrics.bcr);this.metrics.offsetStart=this.currentScroll+s-this.translateValue,this.metrics.offsetEnd=this.metrics.offsetStart+e,this.isInFold=this.metrics.offsetStart1?1:e;var e;this.progress=s,s!==this.lastProgress&&(this.lastProgress=s,this.attributes.scrollCssProgress&&this._setCssProgress(s),this.attributes.scrollEventProgress&&this._setCustomEventProgress(s),s>0&&s<1&&this.setInview(),0===s&&this.setOutOfView(),1===s&&this.setOutOfView())}_setCssProgress(t=0){this.$el.style.setProperty(r,t.toString())}_setCustomEventProgress(t=0){const s=this.attributes.scrollEventProgress;if(!s)return;const e=new CustomEvent(s,{detail:{target:this.$el,progress:t}});window.dispatchEvent(e)}_getScrollCallFrom(){const t=n([this.intersection.start,this.intersection.end],this.currentScroll);return this.intersection.start===t?"start":"end"}destroy(){this.attributes.scrollCssProgress&&this.$el.style.removeProperty(r),this.attributes.scrollSpeed&&this.$el.style.removeProperty("transform"),this.isInview&&this.attributes.scrollClass&&this.$el.classList.remove(this.attributes.scrollClass)}_dispatchCall(t,s){const e=this.attributes.scrollCall;if(!e)return;const i=new CustomEvent(e,{detail:{target:this.$el,way:t,from:s}});window.dispatchEvent(i)}}const o=["scrollOffset","scrollPosition","scrollCssProgress","scrollEventProgress","scrollSpeed"];class a{constructor({$el:t,triggerRootMargin:s,rafRootMargin:e,scrollOrientation:i,lenisInstance:n}){this.$scrollContainer=void 0,this.triggerRootMargin=void 0,this.rafRootMargin=void 0,this.scrollElements=void 0,this.triggeredScrollElements=void 0,this.RAFScrollElements=void 0,this.scrollElementsToUpdate=void 0,this.IOTriggerInstance=void 0,this.IORafInstance=void 0,this.scrollOrientation=void 0,this.lenisInstance=void 0,t?(this.$scrollContainer=t,this.lenisInstance=n,this.scrollOrientation=i,this.triggerRootMargin=null!=s?s:"-1px -1px -1px -1px",this.rafRootMargin=null!=e?e:"100% 100% 100% 100%",this.scrollElements=[],this.triggeredScrollElements=[],this.RAFScrollElements=[],this.scrollElementsToUpdate=[],this._init()):console.error("Please provide a DOM Element as scrollContainer")}_init(){const t=this.$scrollContainer.querySelectorAll("[data-scroll]"),s=this.toElementArray(t);this._subscribeScrollElements(s);const i=this.lenisInstance.options.wrapper===window?null:this.lenisInstance.options.wrapper;this.IOTriggerInstance=new e({scrollElements:[...this.triggeredScrollElements],root:i,rootMargin:this.triggerRootMargin,IORaf:!1}),this.IORafInstance=new e({scrollElements:[...this.RAFScrollElements],root:i,rootMargin:this.rafRootMargin,IORaf:!0})}destroy(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),this._unsubscribeAllScrollElements()}onResize({currentScroll:t}){for(const s of this.RAFScrollElements)s.onResize({currentScroll:t})}onRender({currentScroll:t,smooth:s}){for(const e of this.scrollElementsToUpdate)e.onRender({currentScroll:t,smooth:s})}removeScrollElements(t){const s=t.querySelectorAll("[data-scroll]");if(!s.length)return;const e=new Set(Array.from(s));for(let t=0;t{const s=this.scrollElementsToUpdate.find(s=>s.$el===t),e=this.scrollElements.find(s=>s.$el===t);s&&this._unsubscribeElementUpdate(s),e&&(this.scrollElements=this.scrollElements.filter(t=>t.id!=e.id))})}addScrollElements(t){const s=t.querySelectorAll("[data-scroll]"),e=[];this.scrollElements.forEach(t=>{e.push(t.id)});const i=Math.max(...e,0)+1,n=this.toElementArray(s);this._subscribeScrollElements(n,i,!0)}_subscribeScrollElements(t,s=0,e=!1){for(let i=0;is.id!=t.id)}toElementArray(t){return Array.from(t)}_checkRafNeeded(t){let s=[...o];const e=t=>{s=s.filter(s=>s!==t)};if(t.dataset.scrollOffset){if("0,0"!==t.dataset.scrollOffset.split(",").map(t=>t.replace("%","").trim()).join(","))return!0;e("scrollOffset")}else e("scrollOffset");if(t.dataset.scrollPosition){if("top,bottom"!==t.dataset.scrollPosition.trim())return!0;e("scrollPosition")}else e("scrollPosition");if(t.dataset.scrollSpeed&&!isNaN(parseFloat(t.dataset.scrollSpeed)))return!0;e("scrollSpeed");for(const e of s)if(e in t.dataset)return!0;return!1}}class c{constructor({lenisOptions:t={},triggerRootMargin:s,rafRootMargin:e,autoStart:i=!0,scrollCallback:n=()=>{},initCustomTicker:r,destroyCustomTicker:l}={}){this.rafPlaying=void 0,this.lenisInstance=null,this.coreInstance=null,this.lenisOptions=void 0,this.triggerRootMargin=void 0,this.rafRootMargin=void 0,this.rafInstance=void 0,this.autoStart=void 0,this.isTouchDevice=void 0,this.initCustomTicker=void 0,this.destroyCustomTicker=void 0,this._onRenderBind=void 0,this._onResizeBind=void 0,this._onScrollToBind=void 0,this._originalOnContentResize=void 0,this._originalOnWrapperResize=void 0,window.locomotiveScrollVersion="5.0.0",Object.assign(this,{lenisOptions:t,triggerRootMargin:s,rafRootMargin:e,autoStart:i,scrollCallback:n,initCustomTicker:r,destroyCustomTicker:l}),this._onRenderBind=this._onRender.bind(this),this._onScrollToBind=this._onScrollTo.bind(this),this._onResizeBind=this._onResize.bind(this),this.rafPlaying=!1,this.isTouchDevice="ontouchstart"in window||navigator.maxTouchPoints>0,this._init()}_init(){this.lenisInstance=new t(s({},this.lenisOptions)),this.scrollCallback&&this.lenisInstance.on("scroll",this.scrollCallback),document.documentElement.setAttribute("data-scroll-orientation",this.lenisInstance.options.orientation),requestAnimationFrame(()=>{this.coreInstance=new a({$el:this.lenisInstance.rootElement,triggerRootMargin:this.triggerRootMargin,rafRootMargin:this.rafRootMargin,scrollOrientation:this.lenisInstance.options.orientation,lenisInstance:this.lenisInstance}),this._bindEvents(),this.initCustomTicker&&!this.destroyCustomTicker?console.warn("initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble."):!this.initCustomTicker&&this.destroyCustomTicker&&console.warn("destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble."),this.autoStart&&this.start()})}destroy(){var t;this.stop(),this._unbindEvents(),null==(t=this.lenisInstance)||t.destroy(),requestAnimationFrame(()=>{var t;null==(t=this.coreInstance)||t.destroy()})}_bindEvents(){this._bindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize=this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions),this._originalOnWrapperResize=this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions),this.lenisInstance.dimensions.onContentResize=()=>{var t;null==(t=this._originalOnContentResize)||t.call(this),this._onResizeBind()},this.lenisInstance.dimensions.onWrapperResize=()=>{var t;null==(t=this._originalOnWrapperResize)||t.call(this),this._onResizeBind()})}_unbindEvents(){this._unbindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize&&(this.lenisInstance.dimensions.onContentResize=this._originalOnContentResize),this._originalOnWrapperResize&&(this.lenisInstance.dimensions.onWrapperResize=this._originalOnWrapperResize))}_bindScrollToEvents(t){var s;const e=t||(null==(s=this.lenisInstance)?void 0:s.rootElement),i=null==e?void 0:e.querySelectorAll("[data-scroll-to]");(null==i?void 0:i.length)&&i.forEach(t=>{t.addEventListener("click",this._onScrollToBind,!1)})}_unbindScrollToEvents(t){var s;const e=t||(null==(s=this.lenisInstance)?void 0:s.rootElement),i=null==e?void 0:e.querySelectorAll("[data-scroll-to]");(null==i?void 0:i.length)&&i.forEach(t=>{t.removeEventListener("click",this._onScrollToBind,!1)})}_onResize(){var t,s,e;null==(t=this.coreInstance)||t.onResize({currentScroll:null!=(s=null==(e=this.lenisInstance)?void 0:e.scroll)?s:0,smooth:!this.isTouchDevice})}_onRender(){var t,s,e,i;null==(t=this.lenisInstance)||t.raf(Date.now()),null==(s=this.coreInstance)||s.onRender({currentScroll:null!=(e=null==(i=this.lenisInstance)?void 0:i.scroll)?e:0,smooth:!this.isTouchDevice})}_onScrollTo(t){var s,e;t.preventDefault();const i=null!=(s=t.currentTarget)?s:null;if(!i)return;const n=i.getAttribute("data-scroll-to-href")||i.getAttribute("href"),r=i.getAttribute("data-scroll-to-offset")||0,l=i.getAttribute("data-scroll-to-duration")||(null==(e=this.lenisInstance)?void 0:e.options.duration);n&&this.scrollTo(n,{offset:"string"==typeof r?parseInt(r):r,duration:"string"==typeof l?parseInt(l):l})}start(){var t;this.rafPlaying||(null==(t=this.lenisInstance)||t.start(),this.rafPlaying=!0,this.initCustomTicker?this.initCustomTicker(this._onRenderBind):this._raf())}stop(){var t;this.rafPlaying&&(null==(t=this.lenisInstance)||t.stop(),this.rafPlaying=!1,this.destroyCustomTicker?this.destroyCustomTicker(this._onRenderBind):this.rafInstance&&cancelAnimationFrame(this.rafInstance))}removeScrollElements(t){var s;t?(this._unbindScrollToEvents(t),null==(s=this.coreInstance)||s.removeScrollElements(t)):console.error("Please provide a DOM Element as $oldContainer")}addScrollElements(t){var s;t?(null==(s=this.coreInstance)||s.addScrollElements(t),requestAnimationFrame(()=>{this._bindScrollToEvents(t)})):console.error("Please provide a DOM Element as $newContainer")}resize(){this._onResizeBind()}scrollTo(t,s){var e;null==(e=this.lenisInstance)||e.scrollTo(t,{offset:null==s?void 0:s.offset,lerp:null==s?void 0:s.lerp,duration:null==s?void 0:s.duration,immediate:null==s?void 0:s.immediate,lock:null==s?void 0:s.lock,force:null==s?void 0:s.force,easing:null==s?void 0:s.easing,onComplete:null==s?void 0:s.onComplete})}_raf(){this._onRenderBind(),this.rafInstance=requestAnimationFrame(()=>this._raf())}}export{c as default}; //# sourceMappingURL=locomotive-scroll.modern.mjs.map ================================================ FILE: packages/lib/dist/locomotive-scroll.umd.js ================================================ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("lenis")):"function"==typeof define&&define.amd?define(["lenis"],e):(t||self).locomotiveScroll=e(t.lenis)}(this,function(t){function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var s=/*#__PURE__*/e(t);function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(e);s=t.length?{done:!0}:{done:!1,value:t[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function r(){return r=Object.assign?Object.assign.bind():function(t){for(var e=1;e1?1:e;this.progress=s,s!==this.lastProgress&&(this.lastProgress=s,this.attributes.scrollCssProgress&&this._setCssProgress(s),this.attributes.scrollEventProgress&&this._setCustomEventProgress(s),s>0&&s<1&&this.setInview(),0===s&&this.setOutOfView(),1===s&&this.setOutOfView())},e._setCssProgress=function(t){void 0===t&&(t=0),this.$el.style.setProperty(c,t.toString())},e._setCustomEventProgress=function(t){void 0===t&&(t=0);var e=this.attributes.scrollEventProgress;if(e){var s=new CustomEvent(e,{detail:{target:this.$el,progress:t}});window.dispatchEvent(s)}},e._getScrollCallFrom=function(){var t=a([this.intersection.start,this.intersection.end],this.currentScroll);return this.intersection.start===t?"start":"end"},e.destroy=function(){this.attributes.scrollCssProgress&&this.$el.style.removeProperty(c),this.attributes.scrollSpeed&&this.$el.style.removeProperty("transform"),this.isInview&&this.attributes.scrollClass&&this.$el.classList.remove(this.attributes.scrollClass)},e._dispatchCall=function(t,e){var s=this.attributes.scrollCall;if(s){var i=new CustomEvent(s,{detail:{target:this.$el,way:t,from:e}});window.dispatchEvent(i)}},t}(),u=["scrollOffset","scrollPosition","scrollCssProgress","scrollEventProgress","scrollSpeed"],d=/*#__PURE__*/function(){function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.scrollOrientation,r=t.lenisInstance;this.$scrollContainer=void 0,this.triggerRootMargin=void 0,this.rafRootMargin=void 0,this.scrollElements=void 0,this.triggeredScrollElements=void 0,this.RAFScrollElements=void 0,this.scrollElementsToUpdate=void 0,this.IOTriggerInstance=void 0,this.IORafInstance=void 0,this.scrollOrientation=void 0,this.lenisInstance=void 0,e?(this.$scrollContainer=e,this.lenisInstance=r,this.scrollOrientation=n,this.triggerRootMargin=null!=s?s:"-1px -1px -1px -1px",this.rafRootMargin=null!=i?i:"100% 100% 100% 100%",this.scrollElements=[],this.triggeredScrollElements=[],this.RAFScrollElements=[],this.scrollElementsToUpdate=[],this._init()):console.error("Please provide a DOM Element as scrollContainer")}var e=t.prototype;return e._init=function(){var t=this.$scrollContainer.querySelectorAll("[data-scroll]"),e=this.toElementArray(t);this._subscribeScrollElements(e);var s=this.lenisInstance.options.wrapper===window?null:this.lenisInstance.options.wrapper;this.IOTriggerInstance=new o({scrollElements:[].concat(this.triggeredScrollElements),root:s,rootMargin:this.triggerRootMargin,IORaf:!1}),this.IORafInstance=new o({scrollElements:[].concat(this.RAFScrollElements),root:s,rootMargin:this.rafRootMargin,IORaf:!0})},e.destroy=function(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),this._unsubscribeAllScrollElements()},e.onResize=function(t){for(var e,s=t.currentScroll,i=n(this.RAFScrollElements);!(e=i()).done;)e.value.onResize({currentScroll:s})},e.onRender=function(t){for(var e,s=t.currentScroll,i=t.smooth,r=n(this.scrollElementsToUpdate);!(e=r()).done;)e.value.onRender({currentScroll:s,smooth:i})},e.removeScrollElements=function(t){var e=this,s=t.querySelectorAll("[data-scroll]");if(s.length){for(var i=new Set(Array.from(s)),n=0;n0,this._init()}var e=t.prototype;return e._init=function(){var t=this;this.lenisInstance=new s.default(r({},this.lenisOptions)),this.scrollCallback&&this.lenisInstance.on("scroll",this.scrollCallback),document.documentElement.setAttribute("data-scroll-orientation",this.lenisInstance.options.orientation),requestAnimationFrame(function(){t.coreInstance=new d({$el:t.lenisInstance.rootElement,triggerRootMargin:t.triggerRootMargin,rafRootMargin:t.rafRootMargin,scrollOrientation:t.lenisInstance.options.orientation,lenisInstance:t.lenisInstance}),t._bindEvents(),t.initCustomTicker&&!t.destroyCustomTicker?console.warn("initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble."):!t.initCustomTicker&&t.destroyCustomTicker&&console.warn("destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble."),t.autoStart&&t.start()})},e.destroy=function(){var t,e=this;this.stop(),this._unbindEvents(),null==(t=this.lenisInstance)||t.destroy(),requestAnimationFrame(function(){var t;null==(t=e.coreInstance)||t.destroy()})},e._bindEvents=function(){var t=this;this._bindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize=this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions),this._originalOnWrapperResize=this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions),this.lenisInstance.dimensions.onContentResize=function(){null==t._originalOnContentResize||t._originalOnContentResize(),t._onResizeBind()},this.lenisInstance.dimensions.onWrapperResize=function(){null==t._originalOnWrapperResize||t._originalOnWrapperResize(),t._onResizeBind()})},e._unbindEvents=function(){this._unbindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize&&(this.lenisInstance.dimensions.onContentResize=this._originalOnContentResize),this._originalOnWrapperResize&&(this.lenisInstance.dimensions.onWrapperResize=this._originalOnWrapperResize))},e._bindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.addEventListener("click",s._onScrollToBind,!1)})},e._unbindScrollToEvents=function(t){var e,s=this,i=t||(null==(e=this.lenisInstance)?void 0:e.rootElement),n=null==i?void 0:i.querySelectorAll("[data-scroll-to]");(null==n?void 0:n.length)&&n.forEach(function(t){t.removeEventListener("click",s._onScrollToBind,!1)})},e._onResize=function(){var t,e,s;null==(t=this.coreInstance)||t.onResize({currentScroll:null!=(e=null==(s=this.lenisInstance)?void 0:s.scroll)?e:0,smooth:!this.isTouchDevice})},e._onRender=function(){var t,e,s,i;null==(t=this.lenisInstance)||t.raf(Date.now()),null==(e=this.coreInstance)||e.onRender({currentScroll:null!=(s=null==(i=this.lenisInstance)?void 0:i.scroll)?s:0,smooth:!this.isTouchDevice})},e._onScrollTo=function(t){var e,s;t.preventDefault();var i=null!=(e=t.currentTarget)?e:null;if(i){var n=i.getAttribute("data-scroll-to-href")||i.getAttribute("href"),r=i.getAttribute("data-scroll-to-offset")||0,o=i.getAttribute("data-scroll-to-duration")||(null==(s=this.lenisInstance)?void 0:s.options.duration);n&&this.scrollTo(n,{offset:"string"==typeof r?parseInt(r):r,duration:"string"==typeof o?parseInt(o):o})}},e.start=function(){var t;this.rafPlaying||(null==(t=this.lenisInstance)||t.start(),this.rafPlaying=!0,this.initCustomTicker?this.initCustomTicker(this._onRenderBind):this._raf())},e.stop=function(){var t;this.rafPlaying&&(null==(t=this.lenisInstance)||t.stop(),this.rafPlaying=!1,this.destroyCustomTicker?this.destroyCustomTicker(this._onRenderBind):this.rafInstance&&cancelAnimationFrame(this.rafInstance))},e.removeScrollElements=function(t){var e;t?(this._unbindScrollToEvents(t),null==(e=this.coreInstance)||e.removeScrollElements(t)):console.error("Please provide a DOM Element as $oldContainer")},e.addScrollElements=function(t){var e,s=this;t?(null==(e=this.coreInstance)||e.addScrollElements(t),requestAnimationFrame(function(){s._bindScrollToEvents(t)})):console.error("Please provide a DOM Element as $newContainer")},e.resize=function(){this._onResizeBind()},e.scrollTo=function(t,e){var s;null==(s=this.lenisInstance)||s.scrollTo(t,{offset:null==e?void 0:e.offset,lerp:null==e?void 0:e.lerp,duration:null==e?void 0:e.duration,immediate:null==e?void 0:e.immediate,lock:null==e?void 0:e.lock,force:null==e?void 0:e.force,easing:null==e?void 0:e.easing,onComplete:null==e?void 0:e.onComplete})},e._raf=function(){var t=this;this._onRenderBind(),this.rafInstance=requestAnimationFrame(function(){return t._raf()})},t}()}); //# sourceMappingURL=locomotive-scroll.umd.js.map ================================================ FILE: packages/lib/dist/types/core/Core.d.ts ================================================ /** * Integrates Lenis with Locomotive's built-in animation system */ import type { CoreOptions, IScrollElementCallbacksValues } from '../types'; import ScrollElement from './ScrollElement'; export default class Core { private $scrollContainer; private triggerRootMargin; private rafRootMargin; private scrollElements; private triggeredScrollElements; private RAFScrollElements; private scrollElementsToUpdate; private IOTriggerInstance; private IORafInstance; private scrollOrientation; private lenisInstance; constructor({ $el, triggerRootMargin, rafRootMargin, scrollOrientation, lenisInstance, }: CoreOptions); /** * Lifecyle - Initialize the core. * * @private */ private _init; /** * Lifecyle - Destroy core. */ destroy(): void; /** * Callback - Resize callback. */ onResize({ currentScroll }: IScrollElementCallbacksValues): void; /** * Callback - RAF callback. */ onRender({ currentScroll, smooth }: IScrollElementCallbacksValues): void; /** * Remove items from lists of scroll elements and compute all new values. * * @param {HTMLElement} $oldContainer - HTMLElement that contains data-scroll elements to unsubscribe */ removeScrollElements($oldContainer: HTMLElement): void; /** * Add items to lists of scroll elements and compute all new values. * * @param {HTMLElement} $newContainer - HTMLElement that contains data-scroll elements to subscribe */ addScrollElements($newContainer: HTMLElement): void; /** * Create a ScrollElement instance for each elements with * `data-scroll` attribute. * * @private * * @param {HTMLElement[]} $scrollElements - List of elements that need * to be regarded. */ _subscribeScrollElements($scrollElements: HTMLElement[], fromIndex?: number, toObserve?: boolean): void; /** * Clear all ScrollElement arrays. * * @private */ _unsubscribeAllScrollElements(): void; /** * Subscribe ScrollElement instance that needs to be updated. * * @private * * @param {ScrollElement} scrollElement - ScrollElement instance inview * that needs to be updated. */ _subscribeElementUpdate(scrollElement: ScrollElement): void; /** * Unscribe ScrollElement instance that doesn't need to be updated. * * @private * * @param {ScrollElement} scrollElement - The updated ScrollElement instance * out of view now. */ _unsubscribeElementUpdate(scrollElement: ScrollElement): void; /** * Convert NodeListOf to HTMLElement array. * * @private * * @param {NodeListOf} elements - The NodeList to convert. * * @returns {HTMLElement[]} */ private toElementArray; /** * Check if a DOM Element need a requestAnimationFrame to be used. * * @private * * @param {HTMLElement} $scrollElement - The element that needs to be checked. * * @returns {boolean} */ _checkRafNeeded($scrollElement: HTMLElement): boolean; } //# sourceMappingURL=Core.d.ts.map ================================================ FILE: packages/lib/dist/types/core/IO.d.ts ================================================ /** * Intersection Observer * * Detecting visibility of an element in the viewport. * * Features functions to: * * - Trigger inview/outOfView callbacks * - If the element has a requestAnimationFrame dependency, set interactivy status for the ScrollElement Class * * References: * * - {@link https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API} */ import type { IIOOptions } from '../types'; import ScrollElement from './ScrollElement'; export default class IO { scrollElements: ScrollElement[]; private rootMargin; private root; private IORaf; private observer; constructor({ scrollElements, rootMargin, root, IORaf, }: IIOOptions); /** * Lifecyle - Initialize Intersection Observer. * * @private */ private _init; /** * Lifecyle - Destroy Intersection Observer. */ destroy(): void; /** * Subscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to observe. */ observe($scrollElement: HTMLElement): void; /** * Unsubscribe element to the Intersection Observer. * * @param {HTMLElement} $scrollElement - DOM Element to unobserve. */ unobserve($scrollElement: HTMLElement): void; /** * Find ScrollElementReference instance and trigger inview callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */ private _setInview; /** * Find ScrollElementReference instance and trigger out of view callbacks. * * @private * * @param {IntersectionObserverEntry} entry - DOM Element to observe. */ private _setOutOfView; } //# sourceMappingURL=IO.d.ts.map ================================================ FILE: packages/lib/dist/types/core/ScrollElement.d.ts ================================================ /** * Scroll Element * * Give tools to compute element progress in the viewport and triggers callbacks to animate it. * * Features functions to: * * - scrollClass - Add a custom class when the element is intersected by the offset * - scrollOffset - Determine offsets to intersect the element * - scrollPosition - Determine the element positions to consider an element as intersected. * - scrollCssProgress - Add a specific css variable (PROGRESS_CSS_VAR) that store the scroll progress * - scrollEventProgress - Send scroll progress to custom event listeners. * - scrollSpeed - Add a scroll multiplicator to create a parallax effect * - scrollRepeat - Repeat the option to trigger animation each time the element is intersected * - scrollCall - Call a custom event when the element is intersected */ import type { IScrollElementOptions, IScrollElementAttributes, IScrollElementCallbacksValues, scrollOrientation } from '../types'; export default class ScrollElement { $el: HTMLElement; id: number; needRaf: boolean; attributes: IScrollElementAttributes; scrollOrientation: scrollOrientation; isAlreadyIntersected: boolean; private intersection; private metrics; private currentScroll; private translateValue; private progress; private lastProgress; private isInview; private isInteractive; private isInFold; private isFirstResize; private subscribeElementUpdateFn; private unsubscribeElementUpdateFn; private lenisInstance; private getWindowSize; private getMetricsStart; private getMetricsSize; private readonly startPositionHandlers; private readonly endPositionHandlers; constructor({ $el, id, subscribeElementUpdateFn, unsubscribeElementUpdateFn, needRaf, scrollOrientation, lenisInstance, }: IScrollElementOptions); /** * Lifecyle - Initialize progress tracking. * * @private */ private _init; /** * Callback - Resize callback */ onResize({ currentScroll }: IScrollElementCallbacksValues): void; /** * Callback - RAF callback */ onRender({ currentScroll, smooth }: IScrollElementCallbacksValues): void; /** * Inview callback */ setInview(): void; /** * Out of view callback */ setOutOfView(): void; /** * Switch interactivity on to subscribe the instance to the RAF * and start calculations. */ setInteractivityOn(): void; /** * Switch interactivity off to unsubscribe the instance to the RAF * and stop calculations. */ setInteractivityOff(): void; /** * Resize method that compute the element's values. * * @private */ private _resize; /** * Compute element's offsets and determine if the element is in fold. * * @private */ private _computeMetrics; /** * Compute intersection values depending on the context. * Uses handler-based approach for cleaner, more maintainable code. * * @private */ private _computeIntersection; /** * Compute the scroll progress of the element depending * on its intersection values. * * @private * * @param {number} [forcedProgress] - Value to force progress. */ private _computeProgress; /** * Set the element's progress to a specific css variable. * * @private * * @param {number} [currentProgress] - Progress value. */ _setCssProgress(currentProgress?: number): void; /** * Set the element's progress to the custom event listeners. * * @private * * @param {number} [currentProgress] - Progress value. */ _setCustomEventProgress(currentProgress?: number): void; /** * Function to get scroll call from. * * @private */ _getScrollCallFrom(): "start" | "end"; /** * Lifecyle - Destroy and cleanup the scroll element. * * Removes all CSS modifications and clears references to prevent memory leaks. */ destroy(): void; /** * Function to dispatch a custom event. * * @private * * @param {string} way - Enter or leave. * @param {string} from - Start or end. */ _dispatchCall(way: string, from: string): void; } //# sourceMappingURL=ScrollElement.d.ts.map ================================================ FILE: packages/lib/dist/types/index.d.ts ================================================ import Lenis from 'lenis'; import type { ILenisScrollToOptions, ILocomotiveScrollOptions, lenisTargetScrollTo } from './types'; /** * Locomotive Scroll * * Detection of elements in viewport & smooth scrolling with parallax. * * Inspired by * {@link https://github.com/locomotivemtl/locomotive-scroll locomotive-scroll.js} * and built around * {@link https://github.com/darkroomengineering/lenis lenis.js}. */ export default class LocomotiveScroll { rafPlaying: boolean; lenisInstance: Lenis | null; private coreInstance; private lenisOptions?; private triggerRootMargin?; private rafRootMargin?; private rafInstance?; private autoStart?; private isTouchDevice; private scrollCallback; private initCustomTicker?; private destroyCustomTicker?; private _onRenderBind; private _onResizeBind; private _onScrollToBind; private _originalOnContentResize?; private _originalOnWrapperResize?; constructor({ lenisOptions, triggerRootMargin, rafRootMargin, autoStart, scrollCallback, initCustomTicker, destroyCustomTicker, }?: ILocomotiveScrollOptions); /** * Lifecyle - Initialize instance. * * @private */ private _init; /** * Lifecyle - Destroy instance. */ destroy(): void; /** * Events - Subscribe events to listen. */ private _bindEvents; /** * Events - Unsubscribe listened events. */ private _unbindEvents; /** * Events - Subscribe scrollTo events to listen. */ private _bindScrollToEvents; /** * Events - Unsubscribe scrollTo listened events. */ private _unbindScrollToEvents; /** * Callback - Resize callback. * * Called synchronously after Lenis updates its dimensions via onContentResize/onWrapperResize. * All dimension values are already up-to-date when this executes. */ private _onResize; /** * Callback - Render callback. */ private _onRender; /** * Callback - Scroll To callback. */ private _onScrollTo; /** * Start RequestAnimationFrame that active Lenis smooth and scroll progress. */ start(): void; /** * Stop RequestAnimationFrame that active Lenis smooth and scroll progress. */ stop(): void; /** * Remove old scroll elements items and rebuild ScrollElements instances. */ removeScrollElements($oldContainer: HTMLElement): void; /** * Add new scroll elements items and rebuild ScrollElements instances. */ addScrollElements($newContainer: HTMLElement): void; /** * Trigger resize callback. */ resize(): void; /** * Trigger scroll to callback. */ scrollTo(target: lenisTargetScrollTo, options?: ILenisScrollToOptions): void; /** * RequestAnimationFrame that active Lenis smooth and scroll progress. * * @private * */ private _raf; } export * from './types'; //# sourceMappingURL=index.d.ts.map ================================================ FILE: packages/lib/dist/types/types.d.ts ================================================ import ScrollElement from './core/ScrollElement'; import type { LenisOptions } from 'lenis'; import type Lenis from 'lenis'; /** * @typedef {Object} ILenisScrollValues * * @property {number} scroll * @property {number} limit * @property {number} velocity * @property {number} direction * @property {number} progress */ export interface ILenisScrollValues { scroll: number; limit: number; velocity: number; direction: number; progress: number; } /** * @typedef {(number|HTMLElement|string)} LenisTargetScrollTo */ export type lenisTargetScrollTo = number | HTMLElement | string; /** * * @typedef {Object} ILenisScrollTo * * @property {LenisTargetScrollTo} target - Target that can be number, NodeElement or string (CSS selector). * @property {ILenisScrollToOptions} [options] - Scroll to options. */ export interface ILenisScrollTo { target: number | HTMLElement | string; options?: ILenisScrollToOptions; } /** * @typedef {Object} ILenisScrollToOptions * * @property {number} [offset] - Number equivalent to scroll-padding-top. * @property {number} [lerp] - Animation lerp intensity. * @property {number} [duration] - Number scroll duration in seconds. * @property {boolean} [immediate] - Ignore duration and easing. * @property {boolean} [lock] - Whether or not to prevent the user from scrolling until the target is reached. * @property {boolean} [force] - Reach target even if instance is stopped. * @property {function} [easing] - Smooth scroll easing function. * @property {function} [onComplete] - Called when the target is reached. */ export interface ILenisScrollToOptions { offset?: number; lerp?: number; duration?: number; immediate?: boolean; lock?: boolean; force?: boolean; easing?(t: number): number; onComplete?(): void; } /** * @typedef {Object} ILocomotiveScrollOptions * * @property {LenisOptions} [lenisOptions] - Object to override default Lenis options. * @property {string} [triggerRootMargin] - Root margin for trigger Intersection Observer. * @property {string} [rafRootMargin] - Root margin for RAF Intersection Observer. * @property {boolean} [autoStart] - Enable/Disable RAF auto start. * @property {function} [scrollCallback] - Scroll callback binded on the Lenis scroll event. */ export interface ILocomotiveScrollOptions { lenisOptions?: LenisOptions; triggerRootMargin?: string; rafRootMargin?: string; autoStart?: boolean; scrollCallback?(scrollValues: ILenisScrollValues): void; initCustomTicker?(render: () => void): void; destroyCustomTicker?(render: () => void): void; } /** * @typedef {Object} IScrollElementOptions * * @property {HTMLElement} $el - DOM Element with data-scroll attribute. * @property {number} id - Instance UID. * @property {boolean} needRaf - RequestAnimationFrame status. * @property {Lenis} lenisInstance - Lenis instance for scroll data. * @property {function} subscribeElementUpdateFn - Callback to subscribe instance to the main RAF. * @property {function} unsubscribeElementUpdateFn - Callback to unsubscribe instance to the main RAF. * @property {function} [scrollCallback] - Scroll callback binded on the Lenis scroll event. */ export interface IScrollElementOptions { $el: HTMLElement; id: number; needRaf: boolean; scrollOrientation: scrollOrientation; lenisInstance: Lenis; subscribeElementUpdateFn(scrollElement: ScrollElement): void; unsubscribeElementUpdateFn(scrollElement: ScrollElement): void; scrollCallback?(scrollValues: ILenisScrollValues): void; } /** * @typedef {Object} IScrollElementAttributes * * @property {string} scrollClass - Add a custom class when the element is intersected by the offset. * @property {string} scrollOffset - Determine offsets to intersect the element. * @property {string} scrollPosition - Determine the element positions to consider an element as intersected.. * @property {boolean} scrollCssProgress - Add a specific css variable (PROGRESS_CSS_VAR) that store the scroll progress. * @property {?string} scrollEventProgress - Send scroll progress to custom event listeners. * @property {?number} scrollSpeed - Add a scroll multiplicator to create a parallax effect. * @property {boolean} scrollRepeat - Repeat the option to trigger animation each time the element is intersected. * @property {?string} scrollCall - Call a custom event when the element is intersected. * @property {boolean} scrollIgnoreFold - Ignore fold progress remapping. * @property {boolean} scrollEnableTouchSpeed - Enable parallax on touch devices. */ export interface IScrollElementAttributes { scrollClass: string; scrollOffset: string; scrollPosition: string; scrollCssProgress: boolean; scrollEventProgress: string | null; scrollSpeed: number | null; scrollRepeat: boolean; scrollCall: string | null; scrollIgnoreFold: boolean; scrollEnableTouchSpeed: boolean; } /** * @typedef {Object} IScrollElementIntersection * * @property {number} start * @property {number} end */ export interface IScrollElementIntersection { start: number; end: number; } /** * @typedef {Object} IScrollElementMetrics * * @property {number} offsetTop * @property {number} offsetBottom * @property {DOMRect} bcr */ export interface IScrollElementMetrics { offsetStart: number; offsetEnd: number; bcr: DOMRect; } /** * @typedef {Object} IScrollElementCallbacksValues * * @property {number} currentScroll - Scroll value returned by Lenis. * @property {boolean} smooth - Whether parallax should be enabled (false on touch devices). */ export interface IScrollElementCallbacksValues { currentScroll: number; smooth: boolean; } /** * @typedef {Object} IIOOptions * * @property {ScrollElement[]} scrollElements - A list of objects with property values to match. * @property {boolean} IORaf - Defined if it concern elements which need a RAF. * @property {string} [rootMargin] - Margin around the root. * @property {HTMLElement} [root] - The Element that is used as the viewport for checking visibility (null for window). */ export interface IIOOptions { scrollElements: ScrollElement[]; IORaf: boolean; rootMargin?: string; root?: HTMLElement | null; } /** * @typedef {Object} CoreOptions * * @property {HTMLElement} $el - DOM Element used as scroll container. * @property {string} [triggerRootMargin=TRIGGER_ROOT_MARGIN] - Root margin for trigger Intersection Observer. * @property {string} [rafRootMargin=RAF_ROOT_MARGIN] - Root margin for raf Intersection Observer. * @property {Lenis} lenisInstance - Lenis instance for scroll data. */ export interface CoreOptions { $el: HTMLElement; triggerRootMargin?: string; rafRootMargin?: string; scrollOrientation: scrollOrientation; lenisInstance: Lenis; } export type scrollOrientation = 'vertical' | 'horizontal'; export type gestureOrientation = 'vertical' | 'horizontal' | 'both'; declare global { interface Window { locomotiveScrollVersion: string; } } //# sourceMappingURL=types.d.ts.map ================================================ FILE: packages/lib/dist/types/utils/maths.d.ts ================================================ /** * Clamp a value to fit within a specific range (ex: clamp(0, 100, -12) --> 0). * * @param {number} min - Minimum value expected. * @param {number} max - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - Clamped value. */ export declare function clamp(min: number, max: number, value: number): number; /** * Map one range to another (ex: mapRange(-10, 10, 0, 100, 5) --> 75). * * @param {number} inMin - Current minimum value. * @param {number} inMax - Current maximum value. * @param {number} outMin - Maximum value expected. * @param {number} outMax - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - New value that should be between minimum value expected and maximum value. */ export declare function mapRange(inMin: number, inMax: number, outMin: number, outMax: number, value: number): number; /** * Map a number within a range to a progress between 0 to 1 (ex: normalize(100, 200, 150) --> 0.5). * * @param {number} min - Current minimum value. * @param {number} max - Current maximum value. * @param {number} value - Current value. * * @returns {number} - New value that should be between 0 and 1. */ export declare function normalize(min: number, max: number, value: number): number; /** * Get closest number from an array. * * @param {number[]} array - Numbers array. * @param {number} target - Reference value. * * @returns {number} - Closest number. */ export declare function closestNumber(array: number[], target: number): number; //# sourceMappingURL=maths.d.ts.map ================================================ FILE: packages/lib/index.ts ================================================ import { version } from './package.json' import Lenis from 'lenis'; import Core from './core/Core'; import type { ILenisScrollToOptions, ILenisScrollValues, ILocomotiveScrollOptions, lenisTargetScrollTo, } from './types'; import type { LenisOptions } from 'lenis'; /** * Locomotive Scroll * * Detection of elements in viewport & smooth scrolling with parallax. * * Inspired by * {@link https://github.com/locomotivemtl/locomotive-scroll locomotive-scroll.js} * and built around * {@link https://github.com/darkroomengineering/lenis lenis.js}. */ export default class LocomotiveScroll { public rafPlaying: boolean; public lenisInstance: Lenis | null = null; private coreInstance: Core | null = null; private lenisOptions?: LenisOptions; private triggerRootMargin?: string; private rafRootMargin?: string; private rafInstance?: number; private autoStart?: boolean; private isTouchDevice: boolean; private scrollCallback?(scrollValues: ILenisScrollValues): void; private initCustomTicker?: (render: () => void) => void; private destroyCustomTicker?: (render: () => void) => void; private _onRenderBind: () => void; private _onResizeBind: () => void; private _onScrollToBind: (event: MouseEvent) => void; private _originalOnContentResize?: () => void; private _originalOnWrapperResize?: () => void; constructor({ lenisOptions = {}, triggerRootMargin, rafRootMargin, autoStart = true, scrollCallback = () => {}, initCustomTicker, destroyCustomTicker, }: ILocomotiveScrollOptions = {}) { // Set version window.locomotiveScrollVersion = version; // Get arguments Object.assign(this, { lenisOptions, triggerRootMargin, rafRootMargin, autoStart, scrollCallback, initCustomTicker, destroyCustomTicker, }); // Binding this._onRenderBind = this._onRender.bind(this); this._onScrollToBind = this._onScrollTo.bind(this); this._onResizeBind = this._onResize.bind(this); // Data this.rafPlaying = false; // Detect if device has touch capability this.isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; // Init this._init(); } /** * Lifecyle - Initialize instance. * * @private */ private _init(): void { // Create Lenis instance this.lenisInstance = new Lenis({ ...this.lenisOptions }); // Subscribe to scroll callback if provided if (this.scrollCallback) { this.lenisInstance.on('scroll', this.scrollCallback); } // Add scroll direction attribute on body document.documentElement.setAttribute( 'data-scroll-orientation', this.lenisInstance.options.orientation ); requestAnimationFrame(() => { // Create Core Instance // lenisInstance is guaranteed to exist at this point (created above) this.coreInstance = new Core({ $el: this.lenisInstance!.rootElement, triggerRootMargin: this.triggerRootMargin, rafRootMargin: this.rafRootMargin, scrollOrientation: this.lenisInstance!.options.orientation, lenisInstance: this.lenisInstance!, }); // Bind Events this._bindEvents(); // RAF warning if (this.initCustomTicker && !this.destroyCustomTicker) { console.warn( 'initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble.' ); } else if (!this.initCustomTicker && this.destroyCustomTicker) { console.warn( 'destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble.' ); } // Start RAF this.autoStart && this.start(); }); } /** * Lifecyle - Destroy instance. */ public destroy(): void { // Stop raf this.stop(); // Unbind Events this._unbindEvents(); // Destroy Lenis this.lenisInstance?.destroy(); // Destroy Core after RAF to ensure any pending Intersection Observer callbacks complete // This prevents race conditions when destroy() is called while IO callbacks are queued requestAnimationFrame(() => { this.coreInstance?.destroy(); }); } /** * Events - Subscribe events to listen. */ private _bindEvents() { this._bindScrollToEvents(); // Hook into Lenis dimensions resize callbacks // onContentResize: called when content size changes (images load, dynamic content) // onWrapperResize: called when wrapper size changes (window resize, layout changes) if (this.lenisInstance) { this._originalOnContentResize = this.lenisInstance.dimensions.onContentResize.bind( this.lenisInstance.dimensions ); this._originalOnWrapperResize = this.lenisInstance.dimensions.onWrapperResize.bind( this.lenisInstance.dimensions ); this.lenisInstance.dimensions.onContentResize = () => { this._originalOnContentResize?.(); this._onResizeBind(); }; this.lenisInstance.dimensions.onWrapperResize = () => { this._originalOnWrapperResize?.(); this._onResizeBind(); }; } } /** * Events - Unsubscribe listened events. */ private _unbindEvents() { this._unbindScrollToEvents(); // Restore original Lenis dimensions resize callbacks if (this.lenisInstance) { if (this._originalOnContentResize) { this.lenisInstance.dimensions.onContentResize = this._originalOnContentResize; } if (this._originalOnWrapperResize) { this.lenisInstance.dimensions.onWrapperResize = this._originalOnWrapperResize; } } } /** * Events - Subscribe scrollTo events to listen. */ private _bindScrollToEvents($container?: HTMLElement) { const $rootContainer = $container ? $container : this.lenisInstance?.rootElement; const $scrollToElements = $rootContainer?.querySelectorAll('[data-scroll-to]'); $scrollToElements?.length && $scrollToElements.forEach(($el): void => { ($el as HTMLElement).addEventListener('click', this._onScrollToBind, false); }); } /** * Events - Unsubscribe scrollTo listened events. */ private _unbindScrollToEvents($container?: HTMLElement) { const $rootContainer = $container ? $container : this.lenisInstance?.rootElement; const $scrollToElements = $rootContainer?.querySelectorAll('[data-scroll-to]'); $scrollToElements?.length && $scrollToElements.forEach(($el) => { ($el as HTMLElement).removeEventListener('click', this._onScrollToBind, false); }); } /** * Callback - Resize callback. * * Called synchronously after Lenis updates its dimensions via onContentResize/onWrapperResize. * All dimension values are already up-to-date when this executes. */ private _onResize() { this.coreInstance?.onResize({ currentScroll: this.lenisInstance?.scroll ?? 0, smooth: !this.isTouchDevice, }); } /** * Callback - Render callback. */ private _onRender() { this.lenisInstance?.raf(Date.now()); this.coreInstance?.onRender({ currentScroll: this.lenisInstance?.scroll ?? 0, smooth: !this.isTouchDevice, }); } /** * Callback - Scroll To callback. */ private _onScrollTo(event: MouseEvent) { event.preventDefault(); const $target = (event.currentTarget as HTMLElement) ?? null; if (!$target) return; const target = $target.getAttribute('data-scroll-to-href') || $target.getAttribute('href'); const offset = $target.getAttribute('data-scroll-to-offset') || 0; const duration = $target.getAttribute('data-scroll-to-duration') || this.lenisInstance?.options.duration target && this.scrollTo(target, { offset: typeof offset === 'string' ? parseInt(offset) : offset, duration: typeof duration === 'string' ? parseInt(duration) : duration, }); } /** * Start RequestAnimationFrame that active Lenis smooth and scroll progress. */ public start(): void { if (this.rafPlaying) { return; } // Call lenis start method this.lenisInstance?.start(); this.rafPlaying = true; this.initCustomTicker ? this.initCustomTicker(this._onRenderBind) : this._raf(); } /** * Stop RequestAnimationFrame that active Lenis smooth and scroll progress. */ public stop(): void { if (!this.rafPlaying) { return; } // Call lenis stop method this.lenisInstance?.stop(); this.rafPlaying = false; this.destroyCustomTicker ? this.destroyCustomTicker(this._onRenderBind) : this.rafInstance && cancelAnimationFrame(this.rafInstance); } /** * Remove old scroll elements items and rebuild ScrollElements instances. */ public removeScrollElements($oldContainer: HTMLElement): void { if (!$oldContainer) { console.error('Please provide a DOM Element as $oldContainer'); return; } this._unbindScrollToEvents($oldContainer); this.coreInstance?.removeScrollElements($oldContainer); } /** * Add new scroll elements items and rebuild ScrollElements instances. */ public addScrollElements($newContainer: HTMLElement): void { if (!$newContainer) { console.error('Please provide a DOM Element as $newContainer'); return; } this.coreInstance?.addScrollElements($newContainer); requestAnimationFrame(() => { this._bindScrollToEvents($newContainer); }); } /** * Trigger resize callback. */ public resize(): void { this._onResizeBind(); } /** * Trigger scroll to callback. */ public scrollTo( target: lenisTargetScrollTo, options?: ILenisScrollToOptions ): void { this.lenisInstance?.scrollTo(target, { offset: options?.offset, lerp: options?.lerp, duration: options?.duration, immediate: options?.immediate, lock: options?.lock, force: options?.force, easing: options?.easing, onComplete: options?.onComplete, }); } /** * RequestAnimationFrame that active Lenis smooth and scroll progress. * * @private * */ private _raf() { this._onRenderBind(); this.rafInstance = requestAnimationFrame(() => this._raf()); } } export * from './types'; ================================================ FILE: packages/lib/package.json ================================================ { "name": "locomotive-scroll", "version": "5.0.1", "description": "Detection of elements in viewport & smooth scrolling with parallax effects.", "license": "MIT", "homepage": "https://github.com/locomotivemtl/locomotive-scroll", "repository": { "type": "git", "url": "https://github.com/locomotivemtl/locomotive-scroll.git" }, "author": { "name": "Locomotive", "email": "info@locomotive.ca", "homepage": "https://locomotive.ca" }, "keywords": [ "front-end", "javascript", "smooth-scrolling", "parallax", "scroll" ], "engines": { "node": ">=20" }, "type": "module", "source": "src/index.ts", "main": "dist/locomotive-scroll.js", "umd:main": "dist/locomotive-scroll.umd.js", "module": "dist/locomotive-scroll.mjs", "types": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "require": "./dist/locomotive-scroll.js", "default": "./dist/locomotive-scroll.modern.mjs" }, "./locomotive-scroll.css": "./dist/locomotive-scroll.css", "./dist/*": "./dist/*" }, "scripts": { "clean": "rimraf dist bundled", "build": "npm run clean && npm-run-all --parallel build:dist build:css build:bundle build:types", "build:types": "tsc --allowJs -d --emitDeclarationOnly --declarationDir ./dist/types --removeComments --skipLibCheck --resolveJsonModule ./index.ts", "build:dist": "microbundle build -i index.ts --o ./dist", "build:bundle": "npm-run-all build:bundle-full build:bundle-min build:bundle-css", "build:bundle-full": "microbundle build -i index.ts --o ./bundled/locomotive-scroll.js --no-sourcemap --no-pkg-main --external none --name LocomotiveScroll --format umd --no-compress", "build:bundle-min": "microbundle build -i index.ts --o ./bundled/locomotive-scroll.min.js --no-sourcemap --no-pkg-main --external none --name LocomotiveScroll --format umd", "build:css": "postcss styles/main.css --o ./dist/locomotive-scroll.css --no-map", "build:bundle-css": "postcss styles/main.css --o ./bundled/locomotive-scroll.css --no-map", "watch": "microbundle watch -i index.ts --o ./dist --no-compress", "publish": "npm publish", "publish:next": "npm publish --tag next" }, "files": [ "dist", "bundled" ], "dependencies": { "lenis": "1.3.17" }, "devDependencies": { "cssnano": "^6.0.1", "microbundle": "^0.15.1", "npm-run-all": "^4.1.5", "postcss": "^8.4.23", "postcss-cli": "^10.1.0", "postcss-import": "^15.1.0", "rimraf": "^5.0.0", "typescript": "^5.5.4" } } ================================================ FILE: packages/lib/styles/locomotive-scroll.css ================================================ ================================================ FILE: packages/lib/styles/main.css ================================================ @import "locomotive-scroll.css"; @import "../../../node_modules/lenis/dist/lenis.css"; ================================================ FILE: packages/lib/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "noEmit": true, "noUnusedLocals": false, "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "allowSyntheticDefaultImports": true }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist", "bundled"] } ================================================ FILE: packages/lib/types.ts ================================================ import ScrollElement from './core/ScrollElement'; import type { LenisOptions } from 'lenis'; import type Lenis from 'lenis'; /** * @typedef {Object} ILenisScrollValues * * @property {number} scroll * @property {number} limit * @property {number} velocity * @property {number} direction * @property {number} progress */ export interface ILenisScrollValues { scroll: number; limit: number; velocity: number; direction: number; progress: number; } /** * @typedef {(number|HTMLElement|string)} LenisTargetScrollTo */ export type lenisTargetScrollTo = number | HTMLElement | string; /** * * @typedef {Object} ILenisScrollTo * * @property {LenisTargetScrollTo} target - Target that can be number, NodeElement or string (CSS selector). * @property {ILenisScrollToOptions} [options] - Scroll to options. */ export interface ILenisScrollTo { target: number | HTMLElement | string; options?: ILenisScrollToOptions; } /** * @typedef {Object} ILenisScrollToOptions * * @property {number} [offset] - Number equivalent to scroll-padding-top. * @property {number} [lerp] - Animation lerp intensity. * @property {number} [duration] - Number scroll duration in seconds. * @property {boolean} [immediate] - Ignore duration and easing. * @property {boolean} [lock] - Whether or not to prevent the user from scrolling until the target is reached. * @property {boolean} [force] - Reach target even if instance is stopped. * @property {function} [easing] - Smooth scroll easing function. * @property {function} [onComplete] - Called when the target is reached. */ export interface ILenisScrollToOptions { offset?: number; lerp?: number; duration?: number; immediate?: boolean; lock?: boolean; force?: boolean; easing?(t: number): number; onComplete?(): void; } /** * @typedef {Object} ILocomotiveScrollOptions * * @property {LenisOptions} [lenisOptions] - Object to override default Lenis options. * @property {string} [triggerRootMargin] - Root margin for trigger Intersection Observer. * @property {string} [rafRootMargin] - Root margin for RAF Intersection Observer. * @property {boolean} [autoStart] - Enable/Disable RAF auto start. * @property {function} [scrollCallback] - Scroll callback binded on the Lenis scroll event. */ export interface ILocomotiveScrollOptions { lenisOptions?: LenisOptions; triggerRootMargin?: string; rafRootMargin?: string; autoStart?: boolean; scrollCallback?(scrollValues: ILenisScrollValues): void; initCustomTicker?(render: () => void): void; destroyCustomTicker?(render: () => void): void; } /** * @typedef {Object} IScrollElementOptions * * @property {HTMLElement} $el - DOM Element with data-scroll attribute. * @property {number} id - Instance UID. * @property {boolean} needRaf - RequestAnimationFrame status. * @property {Lenis} lenisInstance - Lenis instance for scroll data. * @property {function} subscribeElementUpdateFn - Callback to subscribe instance to the main RAF. * @property {function} unsubscribeElementUpdateFn - Callback to unsubscribe instance to the main RAF. * @property {function} [scrollCallback] - Scroll callback binded on the Lenis scroll event. */ export interface IScrollElementOptions { $el: HTMLElement; id: number; needRaf: boolean; scrollOrientation: scrollOrientation; lenisInstance: Lenis; subscribeElementUpdateFn(scrollElement: ScrollElement): void; unsubscribeElementUpdateFn(scrollElement: ScrollElement): void; scrollCallback?(scrollValues: ILenisScrollValues): void; } /** * @typedef {Object} IScrollElementAttributes * * @property {string} scrollClass - Add a custom class when the element is intersected by the offset. * @property {string} scrollOffset - Determine offsets to intersect the element. * @property {string} scrollPosition - Determine the element positions to consider an element as intersected.. * @property {boolean} scrollCssProgress - Add a specific css variable (PROGRESS_CSS_VAR) that store the scroll progress. * @property {?string} scrollEventProgress - Send scroll progress to custom event listeners. * @property {?number} scrollSpeed - Add a scroll multiplicator to create a parallax effect. * @property {boolean} scrollRepeat - Repeat the option to trigger animation each time the element is intersected. * @property {?string} scrollCall - Call a custom event when the element is intersected. * @property {boolean} scrollIgnoreFold - Ignore fold progress remapping. * @property {boolean} scrollEnableTouchSpeed - Enable parallax on touch devices. */ export interface IScrollElementAttributes { scrollClass: string; scrollOffset: string; scrollPosition: string; scrollCssProgress: boolean; scrollEventProgress: string | null; scrollSpeed: number | null; scrollRepeat: boolean; scrollCall: string | null; scrollIgnoreFold: boolean; scrollEnableTouchSpeed: boolean; } /** * @typedef {Object} IScrollElementIntersection * * @property {number} start * @property {number} end */ export interface IScrollElementIntersection { start: number; end: number; } /** * @typedef {Object} IScrollElementMetrics * * @property {number} offsetTop * @property {number} offsetBottom * @property {DOMRect} bcr */ export interface IScrollElementMetrics { offsetStart: number; offsetEnd: number; bcr: DOMRect; } /** * @typedef {Object} IScrollElementCallbacksValues * * @property {number} currentScroll - Scroll value returned by Lenis. * @property {boolean} smooth - Whether parallax should be enabled (false on touch devices). */ export interface IScrollElementCallbacksValues { currentScroll: number; smooth: boolean; } /** * @typedef {Object} IIOOptions * * @property {ScrollElement[]} scrollElements - A list of objects with property values to match. * @property {boolean} IORaf - Defined if it concern elements which need a RAF. * @property {string} [rootMargin] - Margin around the root. * @property {HTMLElement} [root] - The Element that is used as the viewport for checking visibility (null for window). */ export interface IIOOptions { scrollElements: ScrollElement[]; IORaf: boolean; rootMargin?: string; root?: HTMLElement | null; } /** * @typedef {Object} CoreOptions * * @property {HTMLElement} $el - DOM Element used as scroll container. * @property {string} [triggerRootMargin=TRIGGER_ROOT_MARGIN] - Root margin for trigger Intersection Observer. * @property {string} [rafRootMargin=RAF_ROOT_MARGIN] - Root margin for raf Intersection Observer. * @property {Lenis} lenisInstance - Lenis instance for scroll data. */ export interface CoreOptions { $el: HTMLElement; triggerRootMargin?: string; rafRootMargin?: string; scrollOrientation: scrollOrientation; lenisInstance: Lenis; } export type scrollOrientation = 'vertical' | 'horizontal'; export type gestureOrientation = 'vertical' | 'horizontal' | 'both'; declare global { interface Window { locomotiveScrollVersion: string; } } ================================================ FILE: packages/lib/utils/maths.ts ================================================ // https://greensock.com/docs/v3/GSAP/gsap.utils /** * Clamp a value to fit within a specific range (ex: clamp(0, 100, -12) --> 0). * * @param {number} min - Minimum value expected. * @param {number} max - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - Clamped value. */ export function clamp(min: number, max: number, value: number): number { return value < min ? min : value > max ? max : value; } /** * Map one range to another (ex: mapRange(-10, 10, 0, 100, 5) --> 75). * * @param {number} inMin - Current minimum value. * @param {number} inMax - Current maximum value. * @param {number} outMin - Maximum value expected. * @param {number} outMax - Maximum value expected. * @param {number} value - Current value. * * @returns {number} - New value that should be between minimum value expected and maximum value. */ export function mapRange( inMin: number, inMax: number, outMin: number, outMax: number, value: number ): number { const inRange = inMax - inMin; const outRange = outMax - outMin; return outMin + (((value - inMin) / inRange) * outRange || 0); } /** * Map a number within a range to a progress between 0 to 1 (ex: normalize(100, 200, 150) --> 0.5). * * @param {number} min - Current minimum value. * @param {number} max - Current maximum value. * @param {number} value - Current value. * * @returns {number} - New value that should be between 0 and 1. */ export function normalize(min: number, max: number, value: number): number { return mapRange(min, max, 0, 1, value); } /** * Get closest number from an array. * * @param {number[]} array - Numbers array. * @param {number} target - Reference value. * * @returns {number} - Closest number. */ export function closestNumber(array: number[], target: number): number { return array.reduce((prev, curr) => { return Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev; }); } ================================================ FILE: postcss.config.cjs ================================================ module.exports = { plugins: [ require('cssnano')({ preset: 'default', }), require('postcss-import'), ], }; ================================================ FILE: scripts/ignore-build-step.js ================================================ #!/usr/bin/env node /** * Vercel Ignore Build Step Script * * This script determines whether Vercel should skip the build process. * Returns exit code 0 to skip build, exit code 1 to proceed with build. * * Build is skipped ONLY when: * - Only non-code files changed (README, .github, IDE config) * - Changes are only in landing package (deployed separately via committed builds) * * Build proceeds for ANY changes in: * - packages/lib, packages/demo, packages/docs (including all files) * - Config files (vercel.json, turbo.json, package.json, etc.) * * Note: Turbo handles caching - we only skip truly irrelevant changes. * Turbo will cache unchanged packages automatically. */ const { execSync } = require('child_process'); const SKIP_BUILD_EXIT_CODE = 0; const PROCEED_BUILD_EXIT_CODE = 1; function log(message) { console.log(`[Ignore Build] ${message}`); } function getChangedFiles() { try { // Get the commit SHA to compare against const beforeCommit = process.env.VERCEL_GIT_PREVIOUS_SHA || 'HEAD~1'; const command = `git diff --name-only ${beforeCommit} HEAD`; log(`Running: ${command}`); const output = execSync(command, { encoding: 'utf-8' }); const files = output.split('\n').filter(Boolean); log(`Changed files (${files.length}):`); files.forEach(file => log(` - ${file}`)); return files; } catch (error) { log(`Error getting changed files: ${error.message}`); log('Proceeding with build to be safe'); return null; } } function shouldSkipBuild(changedFiles) { // If we can't determine changed files, proceed with build if (!changedFiles || changedFiles.length === 0) { log('No changed files detected or error occurred'); return false; } // Patterns that are safe to skip (truly non-critical files) // Everything else will trigger a build const skipPatterns = [ /^README\.md$/i, // Root README only /^LICENSE$/i, // License file /^\.github\//, // GitHub workflows/actions /^\.vscode\//, // VSCode settings /^\.idea\//, // JetBrains IDE settings /^\.editorconfig$/, // Editor config /^\.gitignore$/, // Git ignore /^\.gitattributes$/, // Git attributes ]; // Check if ALL changed files match skip patterns const allFilesCanBeSkipped = changedFiles.every(file => skipPatterns.some(pattern => pattern.test(file)) ); if (allFilesCanBeSkipped) { log('Build can be skipped: Only non-critical files changed'); log('Changed files are: ' + changedFiles.join(', ')); return true; } // If any file doesn't match skip patterns, we need to build const criticalFiles = changedFiles.filter(file => !skipPatterns.some(pattern => pattern.test(file)) ); log('Build required: Critical files changed'); log('Critical files: ' + criticalFiles.join(', ')); return false; } function main() { log('Starting Vercel ignore build check...'); // Check environment variables const branch = process.env.VERCEL_GIT_COMMIT_REF; const isProduction = process.env.VERCEL_ENV === 'production'; log(`Branch: ${branch}`); log(`Environment: ${process.env.VERCEL_ENV}`); log(`Previous SHA: ${process.env.VERCEL_GIT_PREVIOUS_SHA || 'not available'}`); // Always build on production/master branch to be safe if (isProduction || branch === 'master' || branch === 'main') { log('Production branch detected - proceeding with build'); process.exit(PROCEED_BUILD_EXIT_CODE); } // Get changed files and determine if we should skip const changedFiles = getChangedFiles(); const shouldSkip = shouldSkipBuild(changedFiles); if (shouldSkip) { log('✓ Skipping build - no relevant changes detected'); process.exit(SKIP_BUILD_EXIT_CODE); } else { log('✓ Proceeding with build - relevant changes detected'); process.exit(PROCEED_BUILD_EXIT_CODE); } } main(); ================================================ FILE: turbo.json ================================================ { "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": [ "dist/**", "bundled/**", ".next/**", "!.next/cache/**", ".docusaurus/**", "build/**", "../../www/demo/**", "../../www/docs/**" ], "cache": true }, "dev": { "cache": false, "persistent": true }, "clean": { "cache": false } }, "globalDependencies": [ "tsconfig.json" ] } ================================================ FILE: vercel.json ================================================ { "buildCommand": "npm run build:vercel", "ignoreCommand": "node scripts/ignore-build-step.js", "outputDirectory": "www", "rewrites": [ { "source": "/", "destination": "/landing" }, { "source": "/assets/:path*", "destination": "/landing/assets/:path*" } ], "cleanUrls": true } ================================================ FILE: www/landing/assets/images/favicons/browserconfig.xml ================================================ #ffffff ================================================ FILE: www/landing/assets/scripts/app.js ================================================ (()=>{var fc=Object.create;var Rn=Object.defineProperty;var dc=Object.getOwnPropertyDescriptor;var pc=Object.getOwnPropertyNames,Pn=Object.getOwnPropertySymbols,_c=Object.getPrototypeOf,zs=Object.prototype.hasOwnProperty,ma=Object.prototype.propertyIsEnumerable;var Bs=(n,t,e)=>t in n?Rn(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,Mn=(n,t)=>{for(var e in t||(t={}))zs.call(t,e)&&Bs(n,e,t[e]);if(Pn)for(var e of Pn(t))ma.call(t,e)&&Bs(n,e,t[e]);return n};var Ns=(n,t)=>{var e={};for(var i in n)zs.call(n,i)&&t.indexOf(i)<0&&(e[i]=n[i]);if(n!=null&&Pn)for(var i of Pn(n))t.indexOf(i)<0&&ma.call(n,i)&&(e[i]=n[i]);return e};var mc=(n,t)=>()=>(n&&(t=n(n=0)),t);var gc=(n,t)=>()=>(t||n((t={exports:{}}).exports,t),t.exports),ga=(n,t)=>{for(var e in t)Rn(n,e,{get:t[e],enumerable:!0})},Dc=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of pc(t))!zs.call(n,r)&&r!==e&&Rn(n,r,{get:()=>t[r],enumerable:!(i=dc(t,r))||i.enumerable});return n};var vc=(n,t,e)=>(e=n!=null?fc(_c(n)):{},Dc(t||!n||!n.__esModule?Rn(e,"default",{value:n,enumerable:!0}):e,n));var I=(n,t,e)=>(Bs(n,typeof t!="symbol"?t+"":t,e),e);var Hi=(n,t,e)=>new Promise((i,r)=>{var s=l=>{try{a(e.next(l))}catch(u){r(u)}},o=l=>{try{a(e.throw(l))}catch(u){r(u)}},a=l=>l.done?i(l.value):Promise.resolve(l.value).then(s,o);a((e=e.apply(n,t)).next())});var ec=gc((tc,Ms)=>{(function(n,t){typeof define=="function"&&define.amd?define([],function(){return n.svg4everybody=t()}):typeof Ms=="object"&&Ms.exports?Ms.exports=t():n.svg4everybody=t()})(tc,function(){function n(r,s,o){if(o){var a=document.createDocumentFragment(),l=!s.hasAttribute("viewBox")&&o.getAttribute("viewBox");l&&s.setAttribute("viewBox",l);for(var u=o.cloneNode(!0);u.childNodes.length;)a.appendChild(u.firstChild);r.appendChild(a)}}function t(r){r.onreadystatechange=function(){if(r.readyState===4){var s=r._cachedDocument;s||(s=r._cachedDocument=document.implementation.createHTMLDocument(""),s.body.innerHTML=r.responseText,r._cachedTarget={}),r._embeds.splice(0).map(function(o){var a=r._cachedTarget[o.id];a||(a=r._cachedTarget[o.id]=s.getElementById(o.id)),n(o.parent,o.svg,a)})}},r.onreadystatechange()}function e(r){function s(){for(var E=0;E0)&&_(s,67)}var o,a=Object(r),l=/\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/,u=/\bAppleWebKit\/(\d+)\b/,c=/\bEdge\/12\.(\d+)\b/,f=/\bEdge\/.(\d+)\b/,d=window.top!==window.self;o="polyfill"in a?a.polyfill:l.test(navigator.userAgent)||(navigator.userAgent.match(c)||[])[1]<10547||(navigator.userAgent.match(u)||[])[1]<537||f.test(navigator.userAgent)&&d;var h={},_=window.requestAnimationFrame||setTimeout,p=document.getElementsByTagName("use"),g=0;o&&s()}function i(r){for(var s=r;s.nodeName.toLowerCase()!=="svg"&&(s=s.parentNode););return s}return e})});var rc={};ga(rc,{gridHelper:()=>vd});function vd({gutterCssVar:n=md,marginCssVar:t=gd,rgbaColor:e=Dd}={}){let i=document.createElement("div");document.body.append(i),ic(i,e),yd(i,n,t),bd(i,e)}function yd(n,t,e){let i=n.style;i.zIndex="10000",i.position="fixed",i.top="0",i.left="0",i.display="flex",i.width="100%",i.height="100%",i.columnGap=`var(${t}, 0)`,i.paddingLeft=`var(${e}, 0)`,i.paddingRight=`var(${e}, 0)`,i.pointerEvents="none",i.visibility="hidden"}function ic(n,t){n.innerHTML="";let e=Number(window.getComputedStyle(n).getPropertyValue("--grid-columns")),i;for(var r=0;r{r.key=="Control"?e=!0:e&&r.key=="g"&&(i?n.style.visibility="hidden":n.style.visibility="visible",i=!i)}),document.addEventListener("keyup",r=>{r.key=="Control"&&(e=!1)})}var md,gd,Dd,nc=mc(()=>{md="--grid-gutter",gd="--grid-margin",Dd="rgba(255, 0, 0, .1)"});function Ln(n){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ln=function(t){return typeof t}:Ln=function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Ln(n)}function ya(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a class as a function")}function Da(n,t){for(var e=0;en.length)&&(t=n.length);for(var e=0,i=new Array(t);ePs,HoverShuffle:()=>Rs,Load:()=>Bn,MaskLines:()=>ks,Rail:()=>rs,Randomize:()=>an,Scroll:()=>sn});function Fc(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a class as a function")}function Ca(n,t){for(var e=0;en.length)&&(t=n.length);for(var e=0,i=new Array(t);e-1||(this.reset(),this.getStateOptions())}},{key:"reset",value:function(){this.isLoading&&(this.controller.abort(),this.isLoading=!1,this.controller=new AbortController),window.clearTimeout(this.enterTimeout),this.isInserted&&this.removeContainer(),this.classContainer=this.html,Object.assign(this,this.defaults,this.options)}},{key:"getClickOptions",value:function(e){this.transition=e.getAttribute("data-"+this.name),this.isUrl=e.getAttribute("data-"+this.name+"-url");var i=e.getAttribute("href"),r=e.getAttribute("target");if(r=="_blank"){window.open(i,"_blank");return}if(this.transition=="false"){window.location=i;return}this.setOptions(i,!0)}},{key:"getStateOptions",value:function(){this.transitionsHistory?this.transition=history.state:this.transition=!1;var e=window.location.href;this.setOptions(e)}},{key:"goTo",value:function(e,i,r){this.reset(),this.transition=i,this.isUrl=r,this.setOptions(e,!0)}},{key:"setOptions",value:function(e,i){var r="["+this.container+"]",s;this.transition&&this.transition!="true"&&(this.transitionContainer="["+this.container+'="'+this.transition+'"]',this.loadingClass=this.transitions[this.transition].loadingClass||this.loadingClass,this.loadedClass=this.transitions[this.transition].loadedClass||this.loadedClass,this.readyClass=this.transitions[this.transition].readyClass||this.readyClass,this.transitionsPrefix=this.transitions[this.transition].transitionsPrefix||this.transitionsPrefix,this.enterDelay=this.transitions[this.transition].enterDelay||this.enterDelay,this.exitDelay=this.transitions[this.transition].exitDelay||this.exitDelay,this.loadedDelay=this.transitions[this.transition].loadedDelay||this.loadedDelay,s=document.querySelector(this.transitionContainer)),s?(r=this.transitionContainer,this.oldContainer=s,this.classContainer=this.oldContainer.parentNode,this.subContainer||history.replaceState(this.transition,null,this.href),this.subContainer=!0):(this.oldContainer=document.querySelector(r),this.subContainer&&history.replaceState(this.prevTransition,null,this.href),this.subContainer=!1),this.href=e,this.parentContainer=this.oldContainer.parentNode,this.isUrl===""||this.isUrl!=null&&this.isUrl!="false"&&this.isUrl!=!1?history.pushState(this.transition,null,e):(this.oldContainer.classList.add("is-old"),this.setLoading(),this.startEnterDelay(),this.loadHref(e,r,i))}},{key:"setLoading",value:function(){this.classContainer.classList.remove(this.loadedClass,this.readyClass),this.classContainer.classList.add(this.loadingClass),this.classContainer.classList.remove(this.transitionsPrefix+this.prevTransition),this.transition&&this.classContainer.classList.add(this.transitionsPrefix+this.transition),this.subContainer||(this.prevTransition=this.transition);var e=new Event(this.namespace+"loading");window.dispatchEvent(e)}},{key:"startEnterDelay",value:function(){var e=this;this.enterTimeout=window.setTimeout(function(){e.isEntered=!0,e.isLoaded&&e.transitionContainers()},this.enterDelay)}},{key:"loadHref",value:function(e,i,r){var s=this;this.isLoading=!0;var o=this.controller.signal;fetch(e,{signal:o}).then(function(a){return a.text()}).then(function(a){r&&history.pushState(s.transition,null,e);var l=new DOMParser;s.data=l.parseFromString(a,"text/html"),s.newContainer=s.data.querySelector(i),s.newContainer.classList.add("is-new"),s.parentNewContainer=s.newContainer.parentNode,s.hideContainer(),s.parentContainer.insertBefore(s.newContainer,s.oldContainer),s.isInserted=!0,s.setSvgs(),s.isLoaded=!0,s.isEntered&&s.transitionContainers(),s.loadEls(s.newContainer),s.isLoading=!1}).catch(function(a){window.location=e})}},{key:"transitionContainers",value:function(){var e=this;this.setAttributes(),this.showContainer(),this.setLoaded(),setTimeout(function(){e.removeContainer(),e.setReady()},this.exitDelay)}},{key:"setSvgs",value:function(){if(this.isChrome){var e=this.newContainer.querySelectorAll("use");e.length&&e.forEach(function(i){var r=i.getAttribute("xlink:href");if(r)i.parentNode.innerHTML='';else{var s=i.getAttribute("href");s&&(i.parentNode.innerHTML='')}})}}},{key:"setAttributes",value:function(){var e=this,i=this.data.getElementsByTagName("title")[0],r=this.data.head.querySelector('meta[name="description"]'),s=document.head.querySelector('meta[name="description"]'),o,a;this.subContainer?(a=this.parentNewContainer,o=document.querySelector(this.transitionContainer).parentNode):(a=this.data.querySelector("html"),o=document.querySelector("html"));var l=Object.assign({},a.dataset);i&&(document.title=i.innerText),s&&r&&s.setAttribute("content",r.getAttribute("content")),l&&Object.entries(l).forEach(function(u){var c=Oc(u,2),f=c[0],d=c[1];o.setAttribute("data-"+e.toDash(f),d)})}},{key:"toDash",value:function(e){return e.split(/(?=[A-Z])/).join("-").toLowerCase()}},{key:"hideContainer",value:function(){this.newContainer.style.visibility="hidden",this.newContainer.style.height=0,this.newContainer.style.overflow="hidden"}},{key:"showContainer",value:function(){this.newContainer.style.visibility="",this.newContainer.style.height="",this.newContainer.style.overflow=""}},{key:"loadEls",value:function(e){var i=this,r=[];this.loadAttributes.forEach(function(s){var o="data-"+i.name+"-"+s,a=e.querySelectorAll("["+o+"]");a.length&&a.forEach(function(l){var u=l.getAttribute(o);if(l.setAttribute(s,u),s=="src"||s=="srcset"){var c=new Promise(function(f){l.onload=function(){return f(l)}});r.push(c)}})}),Promise.all(r).then(function(s){var o=new Event(i.namespace+"images");window.dispatchEvent(o)})}},{key:"setLoaded",value:function(){var e=this;this.classContainer.classList.remove(this.loadingClass),setTimeout(function(){e.classContainer.classList.add(e.loadedClass)},this.loadedDelay);var i=new Event(this.namespace+"loaded");window.dispatchEvent(i)}},{key:"removeContainer",value:function(){this.parentContainer.removeChild(this.oldContainer),this.newContainer.classList.remove("is-new"),this.isInserted=!1}},{key:"setReady",value:function(){this.classContainer.classList.add(this.readyClass);var e=new Event(this.namespace+"ready");window.dispatchEvent(e)}},{key:"on",value:function(e,i){var r=this;window.addEventListener(this.namespace+e,function(){switch(e){case"loading":return i(r.transition,r.oldContainer);case"loaded":return i(r.transition,r.oldContainer,r.newContainer);case"ready":return i(r.transition,r.newContainer);default:return i()}},!1)}}]),n}(),Ta=Ic;var Bn=class extends de{constructor(n){super(n)}init(){new Ta({enterDelay:0,transitions:{customTransition:{}}}).on("loaded",(t,e,i)=>{this.call("destroy",e,"app"),this.call("update",i,"app")})}};var Fa="fonts"in document;function Lc(n,t){for(let[e,i]of Object.entries(t))switch(e){case"family":{if(zn(n[e])!==i)return!1;break}case"weight":{if(n[e]!=i)return!1;break}default:{if(n[e]!==i)return!1;break}}return!0}function Bc(n,t){let e=zn(n.family);return zn(e)===t||t.endsWith(zn(e))&&(t.match(n.weight)||t.match(n.style)),!0}function zc(n){let t=[];for(let e of document.fonts)Lc(e,n)&&t.push(e);return t}function Nc(n){let t=[];for(let e of document.fonts)Bc(e,n)&&t.push(e);return t}function Aa(n){Array.isArray(n)||(n=[n]);let t=new Set;return n.forEach(e=>{if(e)switch(typeof e){case"string":t.add(...Nc(e));return;case"object":t.add(...zc(e));return}throw new TypeError("Expected font query to be font shorthand or font reference")}),[...t]}function Oa(n,t=!1){return Hi(this,null,function*(){var e;if(((e=n.size)!=null?e:n.length)===0)throw new TypeError("Expected at least one font");return yield Hc([...n],t)})}function xa(n){return Hi(this,null,function*(){return yield(n.status==="unloaded"?n.load():n.loaded).then(t=>t,t=>n)})}function Hc(n,t=!1){return Hi(this,null,function*(){t&&console.group("[loadFonts:API]",n.length,"/",document.fonts.size);let e=[];for(let i of n)i instanceof FontFace?(document.fonts.has(i)||document.fonts.add(i),e.push(xa(i))):e.push(...Aa(i).map(r=>xa(r)));return t&&console.groupEnd(),yield Promise.all(e)})}function zn(n){return n.replace(/['"]+/g,"")}function Nn(n){return Hi(this,null,function*(){let t=Aa(n);return yield Promise.all(t.map(e=>e.loaded.then(i=>i,i=>(console.warn(`Font failed to load: ${e.family} ${e.style} ${e.weight}`,i),e))))})}function oi(n){if(n===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return n}function Na(n,t){n.prototype=Object.create(t.prototype),n.prototype.constructor=n,n.__proto__=t}var ge={autoSleep:120,force3D:"auto",nullTargetWarn:1,units:{lineHeight:""}},dr={duration:.5,overwrite:!1,delay:0},no,Wt,ct,Pe=1e8,at=1/Pe,qs=Math.PI*2,Wc=qs/4,Vc=0,Ha=Math.sqrt,Uc=Math.cos,Yc=Math.sin,kt=function(t){return typeof t=="string"},Dt=function(t){return typeof t=="function"},li=function(t){return typeof t=="number"},Gn=function(t){return typeof t=="undefined"},Qe=function(t){return typeof t=="object"},me=function(t){return t!==!1},so=function(){return typeof window!="undefined"},Hn=function(t){return Dt(t)||kt(t)},Wa=typeof ArrayBuffer=="function"&&ArrayBuffer.isView||function(){},Gt=Array.isArray,$c=/random\([^)]+\)/g,jc=/,\s*/g,ka=/(?:-?\.?\d|\.)+/gi,oo=/[-+=.]*\d+[.e\-+]*\d*[e\-+]*\d*/g,Yi=/[-+=.]*\d+[.e-]*\d*[a-z%]*/g,Ws=/[-+=.]*\d+\.?\d*(?:e-|e\+)?\d*/gi,ao=/[+-]=-?[.\d]+/,Xc=/[^,'"\[\]\s]+/gi,qc=/^[+\-=e\s\d]*\d+[.\d]*([a-z]*|%)\s*$/i,dt,Ge,Gs,lo,we={},Yn={},Va,Ua=function(t){return(Yn=pr(t,we))&&Kt},Kn=function(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")},jr=function(t,e){return!e&&console.warn(t)},Ya=function(t,e){return t&&(we[t]=e)&&Yn&&(Yn[t]=e)||we},Xr=function(){return 0},Gc={suppressEvents:!0,isStart:!0,kill:!1},Wn={suppressEvents:!0,kill:!1},Kc={suppressEvents:!0},uo={},wi=[],Ks={},$a,pe={},Vs={},Pa=30,Vn=[],co="",ho=function(t){var e=t[0],i,r;if(Qe(e)||Dt(e)||(t=[t]),!(i=(e._gsap||{}).harness)){for(r=Vn.length;r--&&!Vn[r].targetTest(e););i=Vn[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new mo(t[r],i)))||t.splice(r,1);return t},Ci=function(t){return t._gsap||ho(Re(t))[0]._gsap},fo=function(t,e,i){return(i=t[e])&&Dt(i)?t[e]():Gn(i)&&t.getAttribute&&t.getAttribute(e)||i},ne=function(t,e){return(t=t.split(",")).forEach(e)||t},vt=function(t){return Math.round(t*1e5)/1e5||0},ft=function(t){return Math.round(t*1e7)/1e7||0},$i=function(t,e){var i=e.charAt(0),r=parseFloat(e.substr(2));return t=parseFloat(t),i==="+"?t+r:i==="-"?t-r:i==="*"?t*r:t/r},Qc=function(t,e){for(var i=e.length,r=0;t.indexOf(e[r])<0&&++ra;)o=o._prev;return o?(e._next=o._next,o._next=e):(e._next=t[i],t[i]=e),e._next?e._next._prev=e:t[r]=e,e._prev=o,e.parent=e._dp=t,e},Qn=function(t,e,i,r){i===void 0&&(i="_first"),r===void 0&&(r="_last");var s=e._prev,o=e._next;s?s._next=o:t[i]===e&&(t[i]=o),o?o._prev=s:t[r]===e&&(t[r]=s),e._next=e._prev=e.parent=null},Si=function(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0},Wi=function(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var i=t;i;)i._dirty=1,i=i.parent;return t},th=function(t){for(var e=t.parent;e&&e.parent;)e._dirty=1,e.totalDuration(),e=e.parent;return t},Qs=function(t,e,i,r){return t._startAt&&(Wt?t._startAt.revert(Wn):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,r))},eh=function n(t){return!t||t._ts&&n(t.parent)},Ma=function(t){return t._repeat?_r(t._tTime,t=t.duration()+t._rDelay)*t:0},_r=function(t,e){var i=Math.floor(t=ft(t/e));return t&&i===t?i-1:i},Xn=function(t,e){return(t-e._start)*e._ts+(e._ts>=0?0:e._dirty?e.totalDuration():e._tDur)},Zn=function(t){return t._end=ft(t._start+(t._tDur/Math.abs(t._ts||t._rts||at)||0))},Jn=function(t,e){var i=t._dp;return i&&i.smoothChildTiming&&t._ts&&(t._start=ft(i._time-(t._ts>0?e/t._ts:((t._dirty?t.totalDuration():t._tDur)-e)/-t._ts)),Zn(t),i._dirty||Wi(i,t)),t},Ka=function(t,e){var i;if((e._time||!e._dur&&e._initted||e._startat)&&e.render(i,!0)),Wi(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur=0&&i.totalTime(i._tTime),i=i._dp;t._zTime=-at}},Ke=function(t,e,i,r){return e.parent&&Si(e),e._start=ft((li(i)?i:i||t!==dt?ke(t,i,e):t._time)+e._delay),e._end=ft(e._start+(e.totalDuration()/Math.abs(e.timeScale())||0)),Ga(t,e,"_first","_last",t._sort?"_start":0),Zs(e)||(t._recent=e),r||Ka(t,e),t._ts<0&&Jn(t,t._tTime),t},Qa=function(t,e){return(we.ScrollTrigger||Kn("scrollTrigger",e))&&we.ScrollTrigger.create(e,t)},Za=function(t,e,i,r,s){if(vo(t,e,s),!t._initted)return 1;if(!i&&t._pt&&!Wt&&(t._dur&&t.vars.lazy!==!1||!t._dur&&t.vars.lazy)&&$a!==_e.frame)return wi.push(t),t._lazy=[s,r],1},ih=function n(t){var e=t.parent;return e&&e._ts&&e._initted&&!e._lock&&(e.rawTime()<0||n(e))},Zs=function(t){var e=t.data;return e==="isFromStart"||e==="isStart"},rh=function(t,e,i,r){var s=t.ratio,o=e<0||!e&&(!t._start&&ih(t)&&!(!t._initted&&Zs(t))||(t._ts<0||t._dp._ts<0)&&!Zs(t))?0:1,a=t._rDelay,l=0,u,c,f;if(a&&t._repeat&&(l=Kr(0,t._tDur,e),c=_r(l,a),t._yoyo&&c&1&&(o=1-o),c!==_r(t._tTime,a)&&(s=1-o,t.vars.repeatRefresh&&t._initted&&t.invalidate())),o!==s||Wt||r||t._zTime===at||!e&&t._zTime){if(!t._initted&&Za(t,e,r,i,l))return;for(f=t._zTime,t._zTime=e||(i?at:0),i||(i=e&&!f),t.ratio=o,t._from&&(o=1-o),t._time=0,t._tTime=l,u=t._pt;u;)u.r(o,u.d),u=u._next;e<0&&Qs(t,e,i,!0),t._onUpdate&&!i&&Ee(t,"onUpdate"),l&&t._repeat&&!i&&t.parent&&Ee(t,"onRepeat"),(e>=t._tDur||e<0)&&t.ratio===o&&(o&&Si(t,1),!i&&!Wt&&(Ee(t,o?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)},nh=function(t,e,i){var r;if(i>e)for(r=t._first;r&&r._start<=i;){if(r.data==="isPause"&&r._start>e)return r;r=r._next}else for(r=t._last;r&&r._start>=i;){if(r.data==="isPause"&&r._start0&&!r&&Jn(t,t._tTime=t._tDur*a),t.parent&&Zn(t),i||Wi(t.parent,t),t},Ia=function(t){return t instanceof Ht?Wi(t):mr(t,t._dur)},sh={_start:0,endTime:Xr,totalDuration:Xr},ke=function n(t,e,i){var r=t.labels,s=t._recent||sh,o=t.duration()>=Pe?s.endTime(!1):t._dur,a,l,u;return kt(e)&&(isNaN(e)||e in r)?(l=e.charAt(0),u=e.substr(-1)==="%",a=e.indexOf("="),l==="<"||l===">"?(a>=0&&(e=e.replace(/=/,"")),(l==="<"?s._start:s.endTime(s._repeat>=0))+(parseFloat(e.substr(1))||0)*(u?(a<0?s:i).totalDuration()/100:1)):a<0?(e in r||(r[e]=o),r[e]):(l=parseFloat(e.charAt(a-1)+e.substr(a+1)),u&&i&&(l=l/100*(Gt(i)?i[0]:i).totalDuration()),a>1?n(t,e.substr(0,a-1),i)+l:o+l)):e==null?o:+e},Yr=function(t,e,i){var r=li(e[1]),s=(r?2:1)+(t<2?0:1),o=e[s],a,l;if(r&&(o.duration=e[1]),o.parent=i,t){for(a=o,l=i;l&&!("immediateRender"in a);)a=l.vars.defaults||{},l=me(l.vars.inherit)&&l.parent;o.immediateRender=me(a.immediateRender),t<2?o.runBackwards=1:o.startAt=e[s-1]}return new Ct(e[0],o,e[s+1])},Ti=function(t,e){return t||t===0?e(t):e},Kr=function(t,e,i){return ie?e:i},Vt=function(t,e){return!kt(t)||!(e=qc.exec(t))?"":e[1]},oh=function(t,e,i){return Ti(i,function(r){return Kr(t,e,r)})},Js=[].slice,Ja=function(t,e){return t&&Qe(t)&&"length"in t&&(!e&&!t.length||t.length-1 in t&&Qe(t[0]))&&!t.nodeType&&t!==Ge},ah=function(t,e,i){return i===void 0&&(i=[]),t.forEach(function(r){var s;return kt(r)&&!e||Ja(r,1)?(s=i).push.apply(s,Re(r)):i.push(r)})||i},Re=function(t,e,i){return ct&&!e&&ct.selector?ct.selector(t):kt(t)&&!i&&(Gs||!gr())?Js.call((e||lo).querySelectorAll(t),0):Gt(t)?ah(t,i):Ja(t)?Js.call(t,0):t?[t]:[]},to=function(t){return t=Re(t)[0]||jr("Invalid scope")||{},function(e){var i=t.current||t.nativeElement||t;return Re(e,i.querySelectorAll?i:i===t?jr("Invalid scope")||lo.createElement("div"):t)}},tl=function(t){return t.sort(function(){return .5-Math.random()})},el=function(t){if(Dt(t))return t;var e=Qe(t)?t:{each:t},i=Vi(e.ease),r=e.from||0,s=parseFloat(e.base)||0,o={},a=r>0&&r<1,l=isNaN(r)||a,u=e.axis,c=r,f=r;return kt(r)?c=f={center:.5,edges:.5,end:1}[r]||0:!a&&l&&(c=r[0],f=r[1]),function(d,h,_){var p=(_||e).length,g=o[p],E,y,S,D,v,T,b,x,w;if(!g){if(w=e.grid==="auto"?0:(e.grid||[1,Pe])[1],!w){for(b=-Pe;b<(b=_[w++].getBoundingClientRect().left)&&wb&&(b=v),vp?p-1:u?u==="y"?p/w:w:Math.max(w,p/w))||0)*(r==="edges"?-1:1),g.b=p<0?s-p:s,g.u=Vt(e.amount||e.each)||0,i=i&&p<0?cl(i):i}return p=(g[d]-g.min)/g.max||0,ft(g.b+(i?i(p):p)*g.v)+g.u}},eo=function(t){var e=Math.pow(10,((t+"").split(".")[1]||"").length);return function(i){var r=ft(Math.round(parseFloat(i)/t)*t*e);return(r-r%1)/e+(li(i)?0:Vt(i))}},il=function(t,e){var i=Gt(t),r,s;return!i&&Qe(t)&&(r=i=t.radius||Pe,t.values?(t=Re(t.values),(s=!li(t[0]))&&(r*=r)):t=eo(t.increment)),Ti(e,i?Dt(t)?function(o){return s=t(o),Math.abs(s-o)<=r?s:o}:function(o){for(var a=parseFloat(s?o.x:o),l=parseFloat(s?o.y:0),u=Pe,c=0,f=t.length,d,h;f--;)s?(d=t[f].x-a,h=t[f].y-l,d=d*d+h*h):d=Math.abs(t[f]-a),dr?s-o:o)})},Dr=function(t){return t.replace($c,function(e){var i=e.indexOf("[")+1,r=e.substring(i||7,i?e.indexOf("]"):e.length-1).split(jc);return rl(i?r:+r[0],i?0:+r[1],+r[2]||1e-5)})},sl=function(t,e,i,r,s){var o=e-t,a=r-i;return Ti(s,function(l){return i+((l-t)/o*a||0)})},dh=function n(t,e,i,r){var s=isNaN(t+e)?0:function(h){return(1-h)*t+h*e};if(!s){var o=kt(t),a={},l,u,c,f,d;if(i===!0&&(r=1)&&(i=null),o)t={p:t},e={p:e};else if(Gt(t)&&!Gt(e)){for(c=[],f=t.length,d=f-2,u=1;u(a=Math.abs(a))&&(l=o,s=a);return l},Ee=function(t,e,i){var r=t.vars,s=r[e],o=ct,a=t._ctx,l,u,c;if(s)return l=r[e+"Params"],u=r.callbackScope||t,i&&wi.length&&$n(),a&&(ct=a),c=l?s.apply(u,l):s.call(u),ct=o,c},Wr=function(t){return Si(t),t.scrollTrigger&&t.scrollTrigger.kill(!!Wt),t.progress()<1&&Ee(t,"onInterrupt"),t},fr,ol=[],al=function(t){if(t)if(t=!t.name&&t.default||t,so()||t.headless){var e=t.name,i=Dt(t),r=e&&!i&&t.init?function(){this._props=[]}:t,s={init:Xr,render:Eo,add:go,kill:Ah,modifier:Fh,rawVars:0},o={targetTest:0,get:0,getSetter:ts,aliases:{},register:0};if(gr(),t!==r){if(pe[e])return;Ce(r,Ce(jn(t,s),o)),pr(r.prototype,pr(s,jn(t,o))),pe[r.prop=e]=r,t.targetTest&&(Vn.push(r),uo[e]=1),e=(e==="css"?"CSS":e.charAt(0).toUpperCase()+e.substr(1))+"Plugin"}Ya(e,r),t.register&&t.register(Kt,r,se)}else ol.push(t)},ot=255,Vr={aqua:[0,ot,ot],lime:[0,ot,0],silver:[192,192,192],black:[0,0,0],maroon:[128,0,0],teal:[0,128,128],blue:[0,0,ot],navy:[0,0,128],white:[ot,ot,ot],olive:[128,128,0],yellow:[ot,ot,0],orange:[ot,165,0],gray:[128,128,128],purple:[128,0,128],green:[0,128,0],red:[ot,0,0],pink:[ot,192,203],cyan:[0,ot,ot],transparent:[ot,ot,ot,0]},Us=function(t,e,i){return t+=t<0?1:t>1?-1:0,(t*6<1?e+(i-e)*t*6:t<.5?i:t*3<2?e+(i-e)*(2/3-t)*6:e)*ot+.5|0},ll=function(t,e,i){var r=t?li(t)?[t>>16,t>>8&ot,t&ot]:0:Vr.black,s,o,a,l,u,c,f,d,h,_;if(!r){if(t.substr(-1)===","&&(t=t.substr(0,t.length-1)),Vr[t])r=Vr[t];else if(t.charAt(0)==="#"){if(t.length<6&&(s=t.charAt(1),o=t.charAt(2),a=t.charAt(3),t="#"+s+s+o+o+a+a+(t.length===5?t.charAt(4)+t.charAt(4):"")),t.length===9)return r=parseInt(t.substr(1,6),16),[r>>16,r>>8&ot,r&ot,parseInt(t.substr(7),16)/255];t=parseInt(t.substr(1),16),r=[t>>16,t>>8&ot,t&ot]}else if(t.substr(0,3)==="hsl"){if(r=_=t.match(ka),!e)l=+r[0]%360/360,u=+r[1]/100,c=+r[2]/100,o=c<=.5?c*(u+1):c+u-c*u,s=c*2-o,r.length>3&&(r[3]*=1),r[0]=Us(l+1/3,s,o),r[1]=Us(l,s,o),r[2]=Us(l-1/3,s,o);else if(~t.indexOf("="))return r=t.match(oo),i&&r.length<4&&(r[3]=1),r}else r=t.match(ka)||Vr.transparent;r=r.map(Number)}return e&&!_&&(s=r[0]/ot,o=r[1]/ot,a=r[2]/ot,f=Math.max(s,o,a),d=Math.min(s,o,a),c=(f+d)/2,f===d?l=u=0:(h=f-d,u=c>.5?h/(2-f-d):h/(f+d),l=f===s?(o-a)/h+(ot||E<0)&&(i+=E-e),r+=E,v=r-i,S=v-o,(S>0||y)&&(T=++f.frame,d=v-f.time*1e3,f.time=v=v/1e3,o+=S+(S>=s?4:s-S),D=1),y||(l=u(p)),D)for(h=0;h=E&&h--},_listeners:a},f}(),gr=function(){return!qr&&_e.wake()},tt={},_h=/^[\d.\-M][\d.\-,\s]/,mh=/["']/g,gh=function(t){for(var e={},i=t.substr(1,t.length-3).split(":"),r=i[0],s=1,o=i.length,a,l,u;s1&&i.config?i.config.apply(null,~t.indexOf("{")?[gh(e[1])]:Dh(t).split(",").map(Xa)):tt._CE&&_h.test(t)?tt._CE("",t):i},cl=function(t){return function(e){return 1-t(1-e)}},hl=function n(t,e){for(var i=t._first,r;i;)i instanceof Ht?n(i,e):i.vars.yoyoEase&&(!i._yoyo||!i._repeat)&&i._yoyo!==e&&(i.timeline?n(i.timeline,e):(r=i._ease,i._ease=i._yEase,i._yEase=r,i._yoyo=e)),i=i._next},Vi=function(t,e){return t&&(Dt(t)?t:tt[t]||vh(t))||e},ji=function(t,e,i,r){i===void 0&&(i=function(l){return 1-e(1-l)}),r===void 0&&(r=function(l){return l<.5?e(l*2)/2:1-e((1-l)*2)/2});var s={easeIn:e,easeOut:i,easeInOut:r},o;return ne(t,function(a){tt[a]=we[a]=s,tt[o=a.toLowerCase()]=i;for(var l in s)tt[o+(l==="easeIn"?".in":l==="easeOut"?".out":".inOut")]=tt[a+"."+l]=s[l]}),s},fl=function(t){return function(e){return e<.5?(1-t(1-e*2))/2:.5+t((e-.5)*2)/2}},Ys=function n(t,e,i){var r=e>=1?e:1,s=(i||(t?.3:.45))/(e<1?e:1),o=s/qs*(Math.asin(1/r)||0),a=function(c){return c===1?1:r*Math.pow(2,-10*c)*Yc((c-o)*s)+1},l=t==="out"?a:t==="in"?function(u){return 1-a(1-u)}:fl(a);return s=qs/s,l.config=function(u,c){return n(t,u,c)},l},$s=function n(t,e){e===void 0&&(e=1.70158);var i=function(o){return o?--o*o*((e+1)*o+e)+1:0},r=t==="out"?i:t==="in"?function(s){return 1-i(1-s)}:fl(i);return r.config=function(s){return n(t,s)},r};ne("Linear,Quad,Cubic,Quart,Quint,Strong",function(n,t){var e=t<5?t+1:t;ji(n+",Power"+(e-1),t?function(i){return Math.pow(i,e)}:function(i){return i},function(i){return 1-Math.pow(1-i,e)},function(i){return i<.5?Math.pow(i*2,e)/2:1-Math.pow((1-i)*2,e)/2})});tt.Linear.easeNone=tt.none=tt.Linear.easeIn;ji("Elastic",Ys("in"),Ys("out"),Ys());(function(n,t){var e=1/t,i=2*e,r=2.5*e,s=function(a){return a0?i+(i+this._rDelay)*this._repeat:i):this.totalDuration()&&this._dur},t.totalDuration=function(i){return arguments.length?(this._dirty=0,mr(this,this._repeat<0?i:(i-this._repeat*this._rDelay)/(this._repeat+1))):this._tDur},t.totalTime=function(i,r){if(gr(),!arguments.length)return this._tTime;var s=this._dp;if(s&&s.smoothChildTiming&&this._ts){for(Jn(this,i),!s._dp||s.parent||Ka(s,this);s&&s.parent;)s.parent._time!==s._start+(s._ts>=0?s._tTime/s._ts:(s.totalDuration()-s._tTime)/-s._ts)&&s.totalTime(s._tTime,!0),s=s.parent;!this.parent&&this._dp.autoRemoveChildren&&(this._ts>0&&i0||!this._tDur&&!i)&&Ke(this._dp,this,this._start-this._delay)}return(this._tTime!==i||!this._dur&&!r||this._initted&&Math.abs(this._zTime)===at||!this._initted&&this._dur&&i||!i&&!this._initted&&(this.add||this._ptLookup))&&(this._ts||(this._pTime=i),ja(this,i,r)),this},t.time=function(i,r){return arguments.length?this.totalTime(Math.min(this.totalDuration(),i+Ma(this))%(this._dur+this._rDelay)||(i?this._dur:0),r):this._time},t.totalProgress=function(i,r){return arguments.length?this.totalTime(this.totalDuration()*i,r):this.totalDuration()?Math.min(1,this._tTime/this._tDur):this.rawTime()>=0&&this._initted?1:0},t.progress=function(i,r){return arguments.length?this.totalTime(this.duration()*(this._yoyo&&!(this.iteration()&1)?1-i:i)+Ma(this),r):this.duration()?Math.min(1,this._time/this._dur):this.rawTime()>0?1:0},t.iteration=function(i,r){var s=this.duration()+this._rDelay;return arguments.length?this.totalTime(this._time+(i-1)*s,r):this._repeat?_r(this._tTime,s)+1:1},t.timeScale=function(i,r){if(!arguments.length)return this._rts===-at?0:this._rts;if(this._rts===i)return this;var s=this.parent&&this._ts?Xn(this.parent._time,this):this._tTime;return this._rts=+i||0,this._ts=this._ps||i===-at?0:this._rts,this.totalTime(Kr(-Math.abs(this._delay),this.totalDuration(),s),r!==!1),Zn(this),th(this)},t.paused=function(i){return arguments.length?(this._ps!==i&&(this._ps=i,i?(this._pTime=this._tTime||Math.max(-this._delay,this.rawTime()),this._ts=this._act=0):(gr(),this._ts=this._rts,this.totalTime(this.parent&&!this.parent.smoothChildTiming?this.rawTime():this._tTime||this._pTime,this.progress()===1&&Math.abs(this._zTime)!==at&&(this._tTime-=at)))),this):this._ps},t.startTime=function(i){if(arguments.length){this._start=ft(i);var r=this.parent||this._dp;return r&&(r._sort||!this.parent)&&Ke(r,this,this._start-this._delay),this}return this._start},t.endTime=function(i){return this._start+(me(i)?this.totalDuration():this.duration())/Math.abs(this._ts||1)},t.rawTime=function(i){var r=this.parent||this._dp;return r?i&&(!this._ts||this._repeat&&this._time&&this.totalProgress()<1)?this._tTime%(this._dur+this._rDelay):this._ts?Xn(r.rawTime(i),this):this._tTime:this._tTime},t.revert=function(i){i===void 0&&(i=Kc);var r=Wt;return Wt=i,po(this)&&(this.timeline&&this.timeline.revert(i),this.totalTime(-.01,i.suppressEvents)),this.data!=="nested"&&i.kill!==!1&&this.kill(),Wt=r,this},t.globalTime=function(i){for(var r=this,s=arguments.length?i:r.rawTime();r;)s=r._start+s/(Math.abs(r._ts)||1),r=r._dp;return!this.parent&&this._sat?this._sat.globalTime(i):s},t.repeat=function(i){return arguments.length?(this._repeat=i===1/0?-2:i,Ia(this)):this._repeat===-2?1/0:this._repeat},t.repeatDelay=function(i){if(arguments.length){var r=this._time;return this._rDelay=i,Ia(this),r?this.time(r):this}return this._rDelay},t.yoyo=function(i){return arguments.length?(this._yoyo=i,this):this._yoyo},t.seek=function(i,r){return this.totalTime(ke(this,i),me(r))},t.restart=function(i,r){return this.play().totalTime(i?-this._delay:0,me(r)),this._dur||(this._zTime=-at),this},t.play=function(i,r){return i!=null&&this.seek(i,r),this.reversed(!1).paused(!1)},t.reverse=function(i,r){return i!=null&&this.seek(i||this.totalDuration(),r),this.reversed(!0).paused(!1)},t.pause=function(i,r){return i!=null&&this.seek(i,r),this.paused(!0)},t.resume=function(){return this.paused(!1)},t.reversed=function(i){return arguments.length?(!!i!==this.reversed()&&this.timeScale(-this._rts||(i?-at:0)),this):this._rts<0},t.invalidate=function(){return this._initted=this._act=0,this._zTime=-at,this},t.isActive=function(){var i=this.parent||this._dp,r=this._start,s;return!!(!i||this._ts&&this._initted&&i.isActive()&&(s=i.rawTime(!0))>=r&&s1?(r?(o[i]=r,s&&(o[i+"Params"]=s),i==="onUpdate"&&(this._onUpdate=r)):delete o[i],this):o[i]},t.then=function(i){var r=this,s=r._prom;return new Promise(function(o){var a=Dt(i)?i:qa,l=function(){var c=r.then;r.then=null,s&&s(),Dt(a)&&(a=a(r))&&(a.then||a===r)&&(r.then=c),o(a),r.then=c};r._initted&&r.totalProgress()===1&&r._ts>=0||!r._tTime&&r._ts<0?l():r._prom=l})},t.kill=function(){Wr(this)},n}();Ce(Gr.prototype,{_time:0,_start:0,_end:0,_tTime:0,_tDur:0,_dirty:0,_repeat:0,_yoyo:!1,parent:null,_initted:!1,_rDelay:0,_ts:1,_dp:0,ratio:0,_zTime:-at,_prom:0,_ps:!1,_rts:1});var Ht=function(n){Na(t,n);function t(i,r){var s;return i===void 0&&(i={}),s=n.call(this,i)||this,s.labels={},s.smoothChildTiming=!!i.smoothChildTiming,s.autoRemoveChildren=!!i.autoRemoveChildren,s._sort=me(i.sortChildren),dt&&Ke(i.parent||dt,oi(s),r),i.reversed&&s.reverse(),i.paused&&s.paused(!0),i.scrollTrigger&&Qa(oi(s),i.scrollTrigger),s}var e=t.prototype;return e.to=function(r,s,o){return Yr(0,arguments,this),this},e.from=function(r,s,o){return Yr(1,arguments,this),this},e.fromTo=function(r,s,o,a){return Yr(2,arguments,this),this},e.set=function(r,s,o){return s.duration=0,s.parent=this,Ur(s).repeatDelay||(s.repeat=0),s.immediateRender=!!s.immediateRender,new Ct(r,s,ke(this,o),1),this},e.call=function(r,s,o){return Ke(this,Ct.delayedCall(0,r,s),o)},e.staggerTo=function(r,s,o,a,l,u,c){return o.duration=s,o.stagger=o.stagger||a,o.onComplete=u,o.onCompleteParams=c,o.parent=this,new Ct(r,o,ke(this,l)),this},e.staggerFrom=function(r,s,o,a,l,u,c){return o.runBackwards=1,Ur(o).immediateRender=me(o.immediateRender),this.staggerTo(r,s,o,a,l,u,c)},e.staggerFromTo=function(r,s,o,a,l,u,c,f){return a.startAt=o,Ur(a).immediateRender=me(a.immediateRender),this.staggerTo(r,s,a,l,u,c,f)},e.render=function(r,s,o){var a=this._time,l=this._dirty?this.totalDuration():this._tDur,u=this._dur,c=r<=0?0:ft(r),f=this._zTime<0!=r<0&&(this._initted||!u),d,h,_,p,g,E,y,S,D,v,T,b;if(this!==dt&&c>l&&r>=0&&(c=l),c!==this._tTime||o||f){if(a!==this._time&&u&&(c+=this._time-a,r+=this._time-a),d=c,D=this._start,S=this._ts,E=!S,f&&(u||(a=this._zTime),(r||!s)&&(this._zTime=r)),this._repeat){if(T=this._yoyo,g=u+this._rDelay,this._repeat<-1&&r<0)return this.totalTime(g*100+r,s,o);if(d=ft(c%g),c===l?(p=this._repeat,d=u):(v=ft(c/g),p=~~v,p&&p===v&&(d=u,p--),d>u&&(d=u)),v=_r(this._tTime,g),!a&&this._tTime&&v!==p&&this._tTime-v*g-this._dur<=0&&(v=p),T&&p&1&&(d=u-d,b=1),p!==v&&!this._lock){var x=T&&v&1,w=x===(T&&p&1);if(p=a&&r>=0)for(h=this._first;h;){if(_=h._next,(h._act||d>=h._start)&&h._ts&&y!==h){if(h.parent!==this)return this.render(r,s,o);if(h.render(h._ts>0?(d-h._start)*h._ts:(h._dirty?h.totalDuration():h._tDur)+(d-h._start)*h._ts,s,o),d!==this._time||!this._ts&&!E){y=0,_&&(c+=this._zTime=-at);break}}h=_}else{h=this._last;for(var F=r<0?r:d;h;){if(_=h._prev,(h._act||F<=h._end)&&h._ts&&y!==h){if(h.parent!==this)return this.render(r,s,o);if(h.render(h._ts>0?(F-h._start)*h._ts:(h._dirty?h.totalDuration():h._tDur)+(F-h._start)*h._ts,s,o||Wt&&po(h)),d!==this._time||!this._ts&&!E){y=0,_&&(c+=this._zTime=F?-at:at);break}}h=_}}if(y&&!s&&(this.pause(),y.render(d>=a?0:-at)._zTime=d>=a?1:-1,this._ts))return this._start=D,Zn(this),this.render(r,s,o);this._onUpdate&&!s&&Ee(this,"onUpdate",!0),(c===l&&this._tTime>=this.totalDuration()||!c&&a)&&(D===this._start||Math.abs(S)!==Math.abs(this._ts))&&(this._lock||((r||!u)&&(c===l&&this._ts>0||!c&&this._ts<0)&&Si(this,1),!s&&!(r<0&&!a)&&(c||a||!l)&&(Ee(this,c===l&&r>=0?"onComplete":"onReverseComplete",!0),this._prom&&!(c0)&&this._prom())))}return this},e.add=function(r,s){var o=this;if(li(s)||(s=ke(this,s,r)),!(r instanceof Gr)){if(Gt(r))return r.forEach(function(a){return o.add(a,s)}),this;if(kt(r))return this.addLabel(r,s);if(Dt(r))r=Ct.delayedCall(0,r);else return this}return this!==r?Ke(this,r,s):this},e.getChildren=function(r,s,o,a){r===void 0&&(r=!0),s===void 0&&(s=!0),o===void 0&&(o=!0),a===void 0&&(a=-Pe);for(var l=[],u=this._first;u;)u._start>=a&&(u instanceof Ct?s&&l.push(u):(o&&l.push(u),r&&l.push.apply(l,u.getChildren(!0,s,o)))),u=u._next;return l},e.getById=function(r){for(var s=this.getChildren(1,1,1),o=s.length;o--;)if(s[o].vars.id===r)return s[o]},e.remove=function(r){return kt(r)?this.removeLabel(r):Dt(r)?this.killTweensOf(r):(r.parent===this&&Qn(this,r),r===this._recent&&(this._recent=this._last),Wi(this))},e.totalTime=function(r,s){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=ft(_e.time-(this._ts>0?r/this._ts:(this.totalDuration()-r)/-this._ts))),n.prototype.totalTime.call(this,r,s),this._forcing=0,this):this._tTime},e.addLabel=function(r,s){return this.labels[r]=ke(this,s),this},e.removeLabel=function(r){return delete this.labels[r],this},e.addPause=function(r,s,o){var a=Ct.delayedCall(0,s||Xr,o);return a.data="isPause",this._hasPause=1,Ke(this,a,ke(this,r))},e.removePause=function(r){var s=this._first;for(r=ke(this,r);s;)s._start===r&&s.data==="isPause"&&Si(s),s=s._next},e.killTweensOf=function(r,s,o){for(var a=this.getTweensOf(r,o),l=a.length;l--;)Ei!==a[l]&&a[l].kill(r,s);return this},e.getTweensOf=function(r,s){for(var o=[],a=Re(r),l=this._first,u=li(s),c;l;)l instanceof Ct?Qc(l._targets,a)&&(u?(!Ei||l._initted&&l._ts)&&l.globalTime(0)<=s&&l.globalTime(l.totalDuration())>s:!s||l.isActive())&&o.push(l):(c=l.getTweensOf(a,s)).length&&o.push.apply(o,c),l=l._next;return o},e.tweenTo=function(r,s){s=s||{};var o=this,a=ke(o,r),l=s,u=l.startAt,c=l.onStart,f=l.onStartParams,d=l.immediateRender,h,_=Ct.to(o,Ce({ease:s.ease||"none",lazy:!1,immediateRender:!1,time:a,overwrite:"auto",duration:s.duration||Math.abs((a-(u&&"time"in u?u.time:o._time))/o.timeScale())||at,onStart:function(){if(o.pause(),!h){var g=s.duration||Math.abs((a-(u&&"time"in u?u.time:o._time))/o.timeScale());_._dur!==g&&mr(_,g,0,1).render(_._time,!0,!0),h=1}c&&c.apply(_,f||[])}},s));return d?_.render(0):_},e.tweenFromTo=function(r,s,o){return this.tweenTo(s,Ce({startAt:{time:ke(this,r)}},o))},e.recent=function(){return this._recent},e.nextLabel=function(r){return r===void 0&&(r=this._time),La(this,ke(this,r))},e.previousLabel=function(r){return r===void 0&&(r=this._time),La(this,ke(this,r),1)},e.currentLabel=function(r){return arguments.length?this.seek(r,!0):this.previousLabel(this._time+at)},e.shiftChildren=function(r,s,o){o===void 0&&(o=0);var a=this._first,l=this.labels,u;for(r=ft(r);a;)a._start>=o&&(a._start+=r,a._end+=r),a=a._next;if(s)for(u in l)l[u]>=o&&(l[u]+=r);return Wi(this)},e.invalidate=function(r){var s=this._first;for(this._lock=0;s;)s.invalidate(r),s=s._next;return n.prototype.invalidate.call(this,r)},e.clear=function(r){r===void 0&&(r=!0);for(var s=this._first,o;s;)o=s._next,this.remove(s),s=o;return this._dp&&(this._time=this._tTime=this._pTime=0),r&&(this.labels={}),Wi(this)},e.totalDuration=function(r){var s=0,o=this,a=o._last,l=Pe,u,c,f;if(arguments.length)return o.timeScale((o._repeat<0?o.duration():o.totalDuration())/(o.reversed()?-r:r));if(o._dirty){for(f=o.parent;a;)u=a._prev,a._dirty&&a.totalDuration(),c=a._start,c>l&&o._sort&&a._ts&&!o._lock?(o._lock=1,Ke(o,a,c-a._delay,1)._lock=0):l=c,c<0&&a._ts&&(s-=c,(!f&&!o._dp||f&&f.smoothChildTiming)&&(o._start+=ft(c/o._ts),o._time-=c,o._tTime-=c),o.shiftChildren(-c,!1,-1/0),l=0),a._end>s&&a._ts&&(s=a._end),a=u;mr(o,o===dt&&o._time>s?o._time:s,1,1),o._dirty=0}return o._tDur},t.updateRoot=function(r){if(dt._ts&&(ja(dt,Xn(r,dt)),$a=_e.frame),_e.frame>=Pa){Pa+=ge.autoSleep||120;var s=dt._first;if((!s||!s._ts)&&ge.autoSleep&&_e._listeners.length<2){for(;s&&!s._ts;)s=s._next;s||_e.sleep()}}},t}(Gr);Ce(Ht.prototype,{_lock:0,_hasPause:0,_forcing:0});var yh=function(t,e,i,r,s,o,a){var l=new se(this._pt,t,e,0,1,bo,null,s),u=0,c=0,f,d,h,_,p,g,E,y;for(l.b=i,l.e=r,i+="",r+="",(E=~r.indexOf("random("))&&(r=Dr(r)),o&&(y=[i,r],o(y,t,e),i=y[0],r=y[1]),d=i.match(Ws)||[];f=Ws.exec(r);)_=f[0],p=r.substring(u,f.index),h?h=(h+1)%5:p.substr(-5)==="rgba("&&(h=1),_!==d[c++]&&(g=parseFloat(d[c-1])||0,l._pt={_next:l._pt,p:p||c===1?p:",",s:g,c:_.charAt(1)==="="?$i(g,_)-g:parseFloat(_)-g,m:h&&h<4?Math.round:0},u=Ws.lastIndex);return l.c=u")}),D.duration();else{T={};for(x in _)x==="ease"||x==="easeEach"||Ch(x,_[x],T,_.easeEach);for(x in T)for(P=T[x].sort(function(H,R){return H.t-R.t}),A=0,v=0;vl-at&&!c?l:ru&&(d=u)),E=this._yoyo&&_&1,E&&(D=this._yEase,d=u-d),g=_r(this._tTime,p),d===a&&!o&&this._initted&&_===g)return this._tTime=f,this;_!==g&&(S&&this._yEase&&hl(S,E),this.vars.repeatRefresh&&!E&&!this._lock&&d!==p&&this._initted&&(this._lock=o=1,this.render(ft(p*_),!0).invalidate()._lock=0))}if(!this._initted){if(Za(this,c?r:d,o,s,f))return this._tTime=0,this;if(a!==this._time&&!(o&&this.vars.repeatRefresh&&_!==g))return this;if(u!==this._dur)return this.render(r,s,o)}if(this._tTime=f,this._time=d,!this._act&&this._ts&&(this._act=1,this._lazy=0),this.ratio=y=(D||this._ease)(d/u),this._from&&(this.ratio=y=1-y),!a&&f&&!s&&!g&&(Ee(this,"onStart"),this._tTime!==f))return this;for(h=this._pt;h;)h.r(y,h.d),h=h._next;S&&S.render(r<0?r:S._dur*S._ease(d/this._dur),s,o)||this._startAt&&(this._zTime=r),this._onUpdate&&!s&&(c&&Qs(this,r,s,o),Ee(this,"onUpdate")),this._repeat&&_!==g&&this.vars.onRepeat&&!s&&this.parent&&Ee(this,"onRepeat"),(f===this._tDur||!f)&&this._tTime===f&&(c&&!this._onUpdate&&Qs(this,r,!0,!0),(r||!u)&&(f===this._tDur&&this._ts>0||!f&&this._ts<0)&&Si(this,1),!s&&!(c&&!a)&&(f||a||E)&&(Ee(this,f===l?"onComplete":"onReverseComplete",!0),this._prom&&!(f0)&&this._prom()))}return this},e.targets=function(){return this._targets},e.invalidate=function(r){return(!r||!this.vars.runBackwards)&&(this._startAt=0),this._pt=this._op=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(r),n.prototype.invalidate.call(this,r)},e.resetTo=function(r,s,o,a,l){qr||_e.wake(),this._ts||this.play();var u=Math.min(this._dur,(this._dp._time-this._start)*this._ts),c;return this._initted||vo(this,u),c=this._ease(u/this._dur),Eh(this,r,s,o,a,c,u,l)?this.resetTo(r,s,o,a,1):(Jn(this,0),this.parent||Ga(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function(r,s){if(s===void 0&&(s="all"),!r&&(!s||s==="all"))return this._lazy=this._pt=0,this.parent?Wr(this):this.scrollTrigger&&this.scrollTrigger.kill(!!Wt),this;if(this.timeline){var o=this.timeline.totalDuration();return this.timeline.killTweensOf(r,s,Ei&&Ei.vars.overwrite!==!0)._first||Wr(this),this.parent&&o!==this.timeline.totalDuration()&&mr(this,this._dur*this.timeline._tDur/o,0,1),this}var a=this._targets,l=r?Re(r):a,u=this._ptLookup,c=this._pt,f,d,h,_,p,g,E;if((!s||s==="all")&&Jc(a,l))return s==="all"&&(this._pt=0),Wr(this);for(f=this._op=this._op||[],s!=="all"&&(kt(s)&&(p={},ne(s,function(y){return p[y]=1}),s=p),s=wh(a,s)),E=a.length;E--;)if(~l.indexOf(a[E])){d=u[E],s==="all"?(f[E]=s,_=d,h={}):(h=f[E]=f[E]||{},_=s);for(p in _)g=d&&d[p],g&&((!("kill"in g.d)||g.d.kill(p)===!0)&&Qn(this,g,"_pt"),delete d[p]),h!=="all"&&(h[p]=1)}return this._initted&&!this._pt&&c&&Wr(this),this},t.to=function(r,s){return new t(r,s,arguments[2])},t.from=function(r,s){return Yr(1,arguments)},t.delayedCall=function(r,s,o,a){return new t(s,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:r,onComplete:s,onReverseComplete:s,onCompleteParams:o,onReverseCompleteParams:o,callbackScope:a})},t.fromTo=function(r,s,o){return Yr(2,arguments)},t.set=function(r,s){return s.duration=0,s.repeatDelay||(s.repeat=0),new t(r,s)},t.killTweensOf=function(r,s,o){return dt.killTweensOf(r,s,o)},t}(Gr);Ce(Ct.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0});ne("staggerTo,staggerFrom,staggerFromTo",function(n){Ct[n]=function(){var t=new Ht,e=Js.call(arguments,0);return e.splice(n==="staggerFromTo"?5:4,0,0),t[n].apply(t,e)}});var yo=function(t,e,i){return t[e]=i},_l=function(t,e,i){return t[e](i)},Sh=function(t,e,i,r){return t[e](r.fp,i)},Th=function(t,e,i){return t.setAttribute(e,i)},ts=function(t,e){return Dt(t[e])?_l:Gn(t[e])&&t.setAttribute?Th:yo},ml=function(t,e){return e.set(e.t,e.p,Math.round((e.s+e.c*t)*1e6)/1e6,e)},xh=function(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},bo=function(t,e){var i=e._pt,r="";if(!t&&e.b)r=e.b;else if(t===1&&e.e)r=e.e;else{for(;i;)r=i.p+(i.m?i.m(i.s+i.c*t):Math.round((i.s+i.c*t)*1e4)/1e4)+r,i=i._next;r+=e.c}e.set(e.t,e.p,r,e)},Eo=function(t,e){for(var i=e._pt;i;)i.r(t,i.d),i=i._next},Fh=function(t,e,i,r){for(var s=this._pt,o;s;)o=s._next,s.p===r&&s.modifier(t,e,i),s=o},Ah=function(t){for(var e=this._pt,i,r;e;)r=e._next,e.p===t&&!e.op||e.op===t?Qn(this,e,"_pt"):e.dep||(i=1),e=r;return!i},Oh=function(t,e,i,r){r.mSet(t,e,r.m.call(r.tween,i,r.mt),r)},wo=function(t){for(var e=t._pt,i,r,s,o;e;){for(i=e._next,r=s;r&&r.pr>e.pr;)r=r._next;(e._prev=r?r._prev:o)?e._prev._next=e:s=e,(e._next=r)?r._prev=e:o=e,e=i}t._pt=s},se=function(){function n(e,i,r,s,o,a,l,u,c){this.t=i,this.s=s,this.c=o,this.p=r,this.r=a||ml,this.d=l||this,this.set=u||yo,this.pr=c||0,this._next=e,e&&(e._prev=this)}var t=n.prototype;return t.modifier=function(i,r,s){this.mSet=this.mSet||this.set,this.set=Oh,this.m=i,this.mt=s,this.tween=r},n}();ne(co+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(n){return uo[n]=1});we.TweenMax=we.TweenLite=Ct;we.TimelineLite=we.TimelineMax=Ht;dt=new Ht({sortChildren:!1,defaults:dr,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0});ge.stringFilter=_o;var Ui=[],Un={},kh=[],za=0,Ph=0,js=function(t){return(Un[t]||kh).map(function(e){return e()})},ro=function(){var t=Date.now(),e=[];t-za>2&&(js("matchMediaInit"),Ui.forEach(function(i){var r=i.queries,s=i.conditions,o,a,l,u;for(a in r)o=Ge.matchMedia(r[a]).matches,o&&(l=1),o!==s[a]&&(s[a]=o,u=1);u&&(i.revert(),l&&e.push(i))}),js("matchMediaRevert"),e.forEach(function(i){return i.onMatch(i,function(r){return i.add(null,r)})}),za=t,js("matchMedia"))},gl=function(){function n(e,i){this.selector=i&&to(i),this.data=[],this._r=[],this.isReverted=!1,this.id=Ph++,e&&this.add(e)}var t=n.prototype;return t.add=function(i,r,s){Dt(i)&&(s=r,r=i,i=Dt);var o=this,a=function(){var u=ct,c=o.selector,f;return u&&u!==o&&u.data.push(o),s&&(o.selector=to(s)),ct=o,f=r.apply(o,arguments),Dt(f)&&o._r.push(f),ct=u,o.selector=c,o.isReverted=!1,f};return o.last=a,i===Dt?a(o,function(l){return o.add(null,l)}):i?o[i]=a:a},t.ignore=function(i){var r=ct;ct=null,i(this),ct=r},t.getTweens=function(){var i=[];return this.data.forEach(function(r){return r instanceof n?i.push.apply(i,r.getTweens()):r instanceof Ct&&!(r.parent&&r.parent.data==="nested")&&i.push(r)}),i},t.clear=function(){this._r.length=this.data.length=0},t.kill=function(i,r){var s=this;if(i?function(){for(var a=s.getTweens(),l=s.data.length,u;l--;)u=s.data[l],u.data==="isFlip"&&(u.revert(),u.getChildren(!0,!0,!1).forEach(function(c){return a.splice(a.indexOf(c),1)}));for(a.map(function(c){return{g:c._dur||c._delay||c._sat&&!c._sat.vars.immediateRender?c.globalTime(0):-1/0,t:c}}).sort(function(c,f){return f.g-c.g||-1/0}).forEach(function(c){return c.t.revert(i)}),l=s.data.length;l--;)u=s.data[l],u instanceof Ht?u.data!=="nested"&&(u.scrollTrigger&&u.scrollTrigger.revert(),u.kill()):!(u instanceof Ct)&&u.revert&&u.revert(i);s._r.forEach(function(c){return c(i,s)}),s.isReverted=!0}():this.data.forEach(function(a){return a.kill&&a.kill()}),this.clear(),r)for(var o=Ui.length;o--;)Ui[o].id===this.id&&Ui.splice(o,1)},t.revert=function(i){this.kill(i||{})},n}(),Rh=function(){function n(e){this.contexts=[],this.scope=e,ct&&ct.data.push(this)}var t=n.prototype;return t.add=function(i,r,s){Qe(i)||(i={matches:i});var o=new gl(0,s||this.scope),a=o.conditions={},l,u,c;ct&&!o.selector&&(o.selector=ct.selector),this.contexts.push(o),r=o.add("onMatch",r),o.queries=i;for(u in i)u==="all"?c=1:(l=Ge.matchMedia(i[u]),l&&(Ui.indexOf(o)<0&&Ui.push(o),(a[u]=l.matches)&&(c=1),l.addListener?l.addListener(ro):l.addEventListener("change",ro)));return c&&r(o,function(f){return o.add(null,f)}),this},t.revert=function(i){this.kill(i||{})},t.kill=function(i){this.contexts.forEach(function(r){return r.kill(i,!0)})},n}(),qn={registerPlugin:function(){for(var t=arguments.length,e=new Array(t),i=0;i1){var r=t.map(function(c){return Kt.quickSetter(c,e,i)}),s=r.length;return function(c){for(var f=s;f--;)r[f](c)}}t=t[0]||{};var o=pe[e],a=Ci(t),l=a.harness&&(a.harness.aliases||{})[e]||e,u=o?function(c){var f=new o;fr._pt=0,f.init(t,i?c+i:c,fr,0,[t]),f.render(1,f),fr._pt&&Eo(1,fr)}:a.set(t,l);return o?u:function(c){return u(t,l,i?c+i:c,a,1)}},quickTo:function(t,e,i){var r,s=Kt.to(t,Ce((r={},r[e]="+=0.1",r.paused=!0,r.stagger=0,r),i||{})),o=function(l,u,c){return s.resetTo(e,l,u,c)};return o.tween=s,o},isTweening:function(t){return dt.getTweensOf(t,!0).length>0},defaults:function(t){return t&&t.ease&&(t.ease=Vi(t.ease,dr.ease)),Ra(dr,t||{})},config:function(t){return Ra(ge,t||{})},registerEffect:function(t){var e=t.name,i=t.effect,r=t.plugins,s=t.defaults,o=t.extendTimeline;(r||"").split(",").forEach(function(a){return a&&!pe[a]&&!we[a]&&jr(e+" effect requires "+a+" plugin.")}),Vs[e]=function(a,l,u){return i(Re(a),Ce(l||{},s),u)},o&&(Ht.prototype[e]=function(a,l,u){return this.add(Vs[e](a,Qe(l)?l:(u=l)&&{},this),u)})},registerEase:function(t,e){tt[t]=Vi(e)},parseEase:function(t,e){return arguments.length?Vi(t,e):tt},getById:function(t){return dt.getById(t)},exportRoot:function(t,e){t===void 0&&(t={});var i=new Ht(t),r,s;for(i.smoothChildTiming=me(t.smoothChildTiming),dt.remove(i),i._dp=0,i._time=i._tTime=dt._time,r=dt._first;r;)s=r._next,(e||!(!r._dur&&r instanceof Ct&&r.vars.onComplete===r._targets[0]))&&Ke(i,r,r._start-r._delay),r=s;return Ke(dt,i,0),i},context:function(t,e){return t?new gl(t,e):ct},matchMedia:function(t){return new Rh(t)},matchMediaRefresh:function(){return Ui.forEach(function(t){var e=t.conditions,i,r;for(r in e)e[r]&&(e[r]=!1,i=1);i&&t.revert()})||ro()},addEventListener:function(t,e){var i=Un[t]||(Un[t]=[]);~i.indexOf(e)||i.push(e)},removeEventListener:function(t,e){var i=Un[t],r=i&&i.indexOf(e);r>=0&&i.splice(r,1)},utils:{wrap:hh,wrapYoyo:fh,distribute:el,random:rl,snap:il,normalize:ch,getUnit:Vt,clamp:oh,splitColor:ll,toArray:Re,selector:to,mapRange:sl,pipe:lh,unitize:uh,interpolate:dh,shuffle:tl},install:Ua,effects:Vs,ticker:_e,updateRoot:Ht.updateRoot,plugins:pe,globalTimeline:dt,core:{PropTween:se,globals:Ya,Tween:Ct,Timeline:Ht,Animation:Gr,getCache:Ci,_removeLinkedListItem:Qn,reverting:function(){return Wt},context:function(t){return t&&ct&&(ct.data.push(t),t._ctx=ct),ct},suppressOverwrites:function(t){return no=t}}};ne("to,from,fromTo,delayedCall,set,killTweensOf",function(n){return qn[n]=Ct[n]});_e.add(Ht.updateRoot);fr=qn.to({},{duration:0});var Mh=function(t,e){for(var i=t._pt;i&&i.p!==e&&i.op!==e&&i.fp!==e;)i=i._next;return i},Ih=function(t,e){var i=t._targets,r,s,o;for(r in e)for(s=i.length;s--;)o=t._ptLookup[s][r],o&&(o=o.d)&&(o._pt&&(o=Mh(o,r)),o&&o.modifier&&o.modifier(e[r],t,i[s],r))},Xs=function(t,e){return{name:t,headless:1,rawVars:1,init:function(r,s,o){o._onInit=function(a){var l,u;if(kt(s)&&(l={},ne(s,function(c){return l[c]=1}),s=l),e){l={};for(u in s)l[u]=e(s[u]);s=l}Ih(a,s)}}}},Kt=qn.registerPlugin({name:"attr",init:function(t,e,i,r,s){var o,a,l;this.tween=i;for(o in e)l=t.getAttribute(o)||"",a=this.add(t,"setAttribute",(l||0)+"",e[o],r,s,0,0,o),a.op=o,a.b=l,this._props.push(o)},render:function(t,e){for(var i=e._pt;i;)Wt?i.set(i.t,i.p,i.b,i):i.r(t,i.d),i=i._next}},{name:"endArray",headless:1,init:function(t,e){for(var i=e.length;i--;)this.add(t,i,t[i]||0,e[i],0,0,0,0,0,1)}},Xs("roundProps",eo),Xs("modifiers"),Xs("snap",il))||qn;Ct.version=Ht.version=Kt.version="3.14.2";Va=1;so()&&gr();var Lh=tt.Power0,Bh=tt.Power1,zh=tt.Power2,Nh=tt.Power3,Hh=tt.Power4,Wh=tt.Linear,Vh=tt.Quad,Uh=tt.Cubic,Yh=tt.Quart,$h=tt.Quint,jh=tt.Strong,Xh=tt.Elastic,qh=tt.Back,Gh=tt.SteppedEase,Kh=tt.Bounce,Qh=tt.Sine,Zh=tt.Expo,Jh=tt.Circ;var Dl,xi,yr,Ao,Ki,tf,vl,Oo,ef=function(){return typeof window!="undefined"},ci={},Gi=180/Math.PI,br=Math.PI/180,vr=Math.atan2,yl=1e8,ko=/([A-Z])/g,rf=/(left|right|width|margin|padding|x)/i,nf=/[\s,\(]\S/,Ze={autoAlpha:"opacity,visibility",scale:"scaleX,scaleY",alpha:"opacity"},So=function(t,e){return e.set(e.t,e.p,Math.round((e.s+e.c*t)*1e4)/1e4+e.u,e)},sf=function(t,e){return e.set(e.t,e.p,t===1?e.e:Math.round((e.s+e.c*t)*1e4)/1e4+e.u,e)},of=function(t,e){return e.set(e.t,e.p,t?Math.round((e.s+e.c*t)*1e4)/1e4+e.u:e.b,e)},af=function(t,e){return e.set(e.t,e.p,t===1?e.e:t?Math.round((e.s+e.c*t)*1e4)/1e4+e.u:e.b,e)},lf=function(t,e){var i=e.s+e.c*t;e.set(e.t,e.p,~~(i+(i<0?-.5:.5))+e.u,e)},Fl=function(t,e){return e.set(e.t,e.p,t?e.e:e.b,e)},Al=function(t,e){return e.set(e.t,e.p,t!==1?e.b:e.e,e)},uf=function(t,e,i){return t.style[e]=i},cf=function(t,e,i){return t.style.setProperty(e,i)},hf=function(t,e,i){return t._gsap[e]=i},ff=function(t,e,i){return t._gsap.scaleX=t._gsap.scaleY=i},df=function(t,e,i,r,s){var o=t._gsap;o.scaleX=o.scaleY=i,o.renderTransform(s,o)},pf=function(t,e,i,r,s){var o=t._gsap;o[e]=i,o.renderTransform(s,o)},pt="transform",De=pt+"Origin",_f=function n(t,e){var i=this,r=this.target,s=r.style,o=r._gsap;if(t in ci&&s){if(this.tfm=this.tfm||{},t!=="transform")t=Ze[t]||t,~t.indexOf(",")?t.split(",").forEach(function(a){return i.tfm[a]=ui(r,a)}):this.tfm[t]=o.x?o[t]:ui(r,t),t===De&&(this.tfm.zOrigin=o.zOrigin);else return Ze.transform.split(",").forEach(function(a){return n.call(i,a,e)});if(this.props.indexOf(pt)>=0)return;o.svg&&(this.svgo=r.getAttribute("data-svg-origin"),this.props.push(De,e,"")),t=pt}(s||e)&&this.props.push(t,e,s[t])},Ol=function(t){t.translate&&(t.removeProperty("translate"),t.removeProperty("scale"),t.removeProperty("rotate"))},mf=function(){var t=this.props,e=this.target,i=e.style,r=e._gsap,s,o;for(s=0;s=0?bl[o]:"")+t},xo=function(){ef()&&window.document&&(Dl=window,xi=Dl.document,yr=xi.documentElement,Ki=To("div")||{style:{}},tf=To("div"),pt=Er(pt),De=pt+"Origin",Ki.style.cssText="border-width:0;line-height:0;position:absolute;padding:0",Pl=!!Er("perspective"),Oo=Kt.core.reverting,Ao=1)},El=function(t){var e=t.ownerSVGElement,i=To("svg",e&&e.getAttribute("xmlns")||"http://www.w3.org/2000/svg"),r=t.cloneNode(!0),s;r.style.display="block",i.appendChild(r),yr.appendChild(i);try{s=r.getBBox()}catch(o){}return i.removeChild(r),yr.removeChild(i),s},wl=function(t,e){for(var i=e.length;i--;)if(t.hasAttribute(e[i]))return t.getAttribute(e[i])},Rl=function(t){var e,i;try{e=t.getBBox()}catch(r){e=El(t),i=1}return e&&(e.width||e.height)||i||(e=El(t)),e&&!e.width&&!e.x&&!e.y?{x:+wl(t,["x","cx","x1"])||0,y:+wl(t,["y","cy","y1"])||0,width:0,height:0}:e},Ml=function(t){return!!(t.getCTM&&(!t.parentNode||t.ownerSVGElement)&&Rl(t))},Ai=function(t,e){if(e){var i=t.style,r;e in ci&&e!==De&&(e=pt),i.removeProperty?(r=e.substr(0,2),(r==="ms"||e.substr(0,6)==="webkit")&&(e="-"+e),i.removeProperty(r==="--"?e:e.replace(ko,"-$1").toLowerCase())):i.removeAttribute(e)}},Fi=function(t,e,i,r,s,o){var a=new se(t._pt,e,i,0,1,o?Al:Fl);return t._pt=a,a.b=r,a.e=s,t._props.push(i),a},Cl={deg:1,rad:1,turn:1},gf={grid:1,flex:1},Oi=function n(t,e,i,r){var s=parseFloat(i)||0,o=(i+"").trim().substr((s+"").length)||"px",a=Ki.style,l=rf.test(e),u=t.tagName.toLowerCase()==="svg",c=(u?"client":"offset")+(l?"Width":"Height"),f=100,d=r==="px",h=r==="%",_,p,g,E;if(r===o||!s||Cl[r]||Cl[o])return s;if(o!=="px"&&!d&&(s=n(t,e,i,"px")),E=t.getCTM&&Ml(t),(h||o==="%")&&(ci[e]||~e.indexOf("adius")))return _=E?t.getBBox()[l?"width":"height"]:t[c],vt(h?s/_*f:s/100*_);if(a[l?"width":"height"]=f+(d?o:r),p=r!=="rem"&&~e.indexOf("adius")||r==="em"&&t.appendChild&&!u?t:t.parentNode,E&&(p=(t.ownerSVGElement||{}).parentNode),(!p||p===xi||!p.appendChild)&&(p=xi.body),g=p._gsap,g&&h&&g.width&&l&&g.time===_e.time&&!g.uncache)return vt(s/g.width*f);if(h&&(e==="height"||e==="width")){var y=t.style[e];t.style[e]=f+r,_=t[c],y?t.style[e]=y:Ai(t,e)}else(h||o==="%")&&!gf[Se(p,"display")]&&(a.position=Se(t,"position")),p===t&&(a.position="static"),p.appendChild(Ki),_=Ki[c],p.removeChild(Ki),a.position="absolute";return l&&h&&(g=Ci(p),g.time=_e.time,g.width=p[c]),vt(d?_*s/f:_&&s?f/_*s:0)},ui=function(t,e,i,r){var s;return Ao||xo(),e in Ze&&e!=="transform"&&(e=Ze[e],~e.indexOf(",")&&(e=e.split(",")[0])),ci[e]&&e!=="transform"?(s=Jr(t,r),s=e!=="transformOrigin"?s[e]:s.svg?s.origin:is(Se(t,De))+" "+s.zOrigin+"px"):(s=t.style[e],(!s||s==="auto"||r||~(s+"").indexOf("calc("))&&(s=es[e]&&es[e](t,e,i)||Se(t,e)||fo(t,e)||(e==="opacity"?1:0))),i&&!~(s+"").trim().indexOf(" ")?Oi(t,e,s,i)+i:s},Df=function(t,e,i,r){if(!i||i==="none"){var s=Er(e,t,1),o=s&&Se(t,s,1);o&&o!==i?(e=s,i=o):e==="borderColor"&&(i=Se(t,"borderTopColor"))}var a=new se(this._pt,t.style,e,0,1,bo),l=0,u=0,c,f,d,h,_,p,g,E,y,S,D,v;if(a.b=i,a.e=r,i+="",r+="",r.substring(0,6)==="var(--"&&(r=Se(t,r.substring(4,r.indexOf(")")))),r==="auto"&&(p=t.style[e],t.style[e]=r,r=Se(t,e)||r,p?t.style[e]=p:Ai(t,e)),c=[i,r],_o(c),i=c[0],r=c[1],d=i.match(Yi)||[],v=r.match(Yi)||[],v.length){for(;f=Yi.exec(r);)g=f[0],y=r.substring(l,f.index),_?_=(_+1)%5:(y.substr(-5)==="rgba("||y.substr(-5)==="hsla(")&&(_=1),g!==(p=d[u++]||"")&&(h=parseFloat(p)||0,D=p.substr((h+"").length),g.charAt(1)==="="&&(g=$i(h,g)+D),E=parseFloat(g),S=g.substr((E+"").length),l=Yi.lastIndex-S.length,S||(S=S||ge.units[e]||D,l===r.length&&(r+=S,a.e+=S)),D!==S&&(h=Oi(t,e,p,S)||0),a._pt={_next:a._pt,p:y||u===1?y:",",s:h,c:E-h,m:_&&_<4||e==="zIndex"?Math.round:0});a.c=l-1;)a=s[u],ci[a]&&(l=1,a=a==="transformOrigin"?De:pt),Ai(i,a);l&&(Ai(i,pt),o&&(o.svg&&i.removeAttribute("transform"),r.scale=r.rotate=r.translate="none",Jr(i,1),o.uncache=1,Ol(r)))}},es={clearProps:function(t,e,i,r,s){if(s.data!=="isFromStart"){var o=t._pt=new se(t._pt,e,i,0,0,yf);return o.u=r,o.pr=-10,o.tween=s,t._props.push(i),1}}},Zr=[1,0,0,1,0,0],Il={},Ll=function(t){return t==="matrix(1, 0, 0, 1, 0, 0)"||t==="none"||!t},Tl=function(t){var e=Se(t,pt);return Ll(e)?Zr:e.substr(7).match(oo).map(vt)},Po=function(t,e){var i=t._gsap||Ci(t),r=t.style,s=Tl(t),o,a,l,u;return i.svg&&t.getAttribute("transform")?(l=t.transform.baseVal.consolidate().matrix,s=[l.a,l.b,l.c,l.d,l.e,l.f],s.join(",")==="1,0,0,1,0,0"?Zr:s):(s===Zr&&!t.offsetParent&&t!==yr&&!i.svg&&(l=r.display,r.display="block",o=t.parentNode,(!o||!t.offsetParent&&!t.getBoundingClientRect().width)&&(u=1,a=t.nextElementSibling,yr.appendChild(t)),s=Tl(t),l?r.display=l:Ai(t,"display"),u&&(a?o.insertBefore(t,a):o?o.appendChild(t):yr.removeChild(t))),e&&s.length>6?[s[0],s[1],s[4],s[5],s[12],s[13]]:s)},Fo=function(t,e,i,r,s,o){var a=t._gsap,l=s||Po(t,!0),u=a.xOrigin||0,c=a.yOrigin||0,f=a.xOffset||0,d=a.yOffset||0,h=l[0],_=l[1],p=l[2],g=l[3],E=l[4],y=l[5],S=e.split(" "),D=parseFloat(S[0])||0,v=parseFloat(S[1])||0,T,b,x,w;i?l!==Zr&&(b=h*g-_*p)&&(x=D*(g/b)+v*(-p/b)+(p*y-g*E)/b,w=D*(-_/b)+v*(h/b)-(h*y-_*E)/b,D=x,v=w):(T=Rl(t),D=T.x+(~S[0].indexOf("%")?D/100*T.width:D),v=T.y+(~(S[1]||S[0]).indexOf("%")?v/100*T.height:v)),r||r!==!1&&a.smooth?(E=D-u,y=v-c,a.xOffset=f+(E*h+y*p)-E,a.yOffset=d+(E*_+y*g)-y):a.xOffset=a.yOffset=0,a.xOrigin=D,a.yOrigin=v,a.smooth=!!r,a.origin=e,a.originIsAbsolute=!!i,t.style[De]="0px 0px",o&&(Fi(o,a,"xOrigin",u,D),Fi(o,a,"yOrigin",c,v),Fi(o,a,"xOffset",f,a.xOffset),Fi(o,a,"yOffset",d,a.yOffset)),t.setAttribute("data-svg-origin",D+" "+v)},Jr=function(t,e){var i=t._gsap||new mo(t);if("x"in i&&!e&&!i.uncache)return i;var r=t.style,s=i.scaleX<0,o="px",a="deg",l=getComputedStyle(t),u=Se(t,De)||"0",c,f,d,h,_,p,g,E,y,S,D,v,T,b,x,w,F,B,A,P,L,M,H,R,z,V,m,j,Z,Rt,nt,St;return c=f=d=p=g=E=y=S=D=0,h=_=1,i.svg=!!(t.getCTM&&Ml(t)),l.translate&&((l.translate!=="none"||l.scale!=="none"||l.rotate!=="none")&&(r[pt]=(l.translate!=="none"?"translate3d("+(l.translate+" 0 0").split(" ").slice(0,3).join(", ")+") ":"")+(l.rotate!=="none"?"rotate("+l.rotate+") ":"")+(l.scale!=="none"?"scale("+l.scale.split(" ").join(",")+") ":"")+(l[pt]!=="none"?l[pt]:"")),r.scale=r.rotate=r.translate="none"),b=Po(t,i.svg),i.svg&&(i.uncache?(z=t.getBBox(),u=i.xOrigin-z.x+"px "+(i.yOrigin-z.y)+"px",R=""):R=!e&&t.getAttribute("data-svg-origin"),Fo(t,R||u,!!R||i.originIsAbsolute,i.smooth!==!1,b)),v=i.xOrigin||0,T=i.yOrigin||0,b!==Zr&&(B=b[0],A=b[1],P=b[2],L=b[3],c=M=b[4],f=H=b[5],b.length===6?(h=Math.sqrt(B*B+A*A),_=Math.sqrt(L*L+P*P),p=B||A?vr(A,B)*Gi:0,y=P||L?vr(P,L)*Gi+p:0,y&&(_*=Math.abs(Math.cos(y*br))),i.svg&&(c-=v-(v*B+T*P),f-=T-(v*A+T*L))):(St=b[6],Rt=b[7],m=b[8],j=b[9],Z=b[10],nt=b[11],c=b[12],f=b[13],d=b[14],x=vr(St,Z),g=x*Gi,x&&(w=Math.cos(-x),F=Math.sin(-x),R=M*w+m*F,z=H*w+j*F,V=St*w+Z*F,m=M*-F+m*w,j=H*-F+j*w,Z=St*-F+Z*w,nt=Rt*-F+nt*w,M=R,H=z,St=V),x=vr(-P,Z),E=x*Gi,x&&(w=Math.cos(-x),F=Math.sin(-x),R=B*w-m*F,z=A*w-j*F,V=P*w-Z*F,nt=L*F+nt*w,B=R,A=z,P=V),x=vr(A,B),p=x*Gi,x&&(w=Math.cos(x),F=Math.sin(x),R=B*w+A*F,z=M*w+H*F,A=A*w-B*F,H=H*w-M*F,B=R,M=z),g&&Math.abs(g)+Math.abs(p)>359.9&&(g=p=0,E=180-E),h=vt(Math.sqrt(B*B+A*A+P*P)),_=vt(Math.sqrt(H*H+St*St)),x=vr(M,H),y=Math.abs(x)>2e-4?x*Gi:0,D=nt?1/(nt<0?-nt:nt):0),i.svg&&(R=t.getAttribute("transform"),i.forceCSS=t.setAttribute("transform","")||!Ll(Se(t,pt)),R&&t.setAttribute("transform",R))),Math.abs(y)>90&&Math.abs(y)<270&&(s?(h*=-1,y+=p<=0?180:-180,p+=p<=0?180:-180):(_*=-1,y+=y<=0?180:-180)),e=e||i.uncache,i.x=c-((i.xPercent=c&&(!e&&i.xPercent||(Math.round(t.offsetWidth/2)===Math.round(-c)?-50:0)))?t.offsetWidth*i.xPercent/100:0)+o,i.y=f-((i.yPercent=f&&(!e&&i.yPercent||(Math.round(t.offsetHeight/2)===Math.round(-f)?-50:0)))?t.offsetHeight*i.yPercent/100:0)+o,i.z=d+o,i.scaleX=vt(h),i.scaleY=vt(_),i.rotation=vt(p)+a,i.rotationX=vt(g)+a,i.rotationY=vt(E)+a,i.skewX=y+a,i.skewY=S+a,i.transformPerspective=D+o,(i.zOrigin=parseFloat(u.split(" ")[2])||!e&&i.zOrigin||0)&&(r[De]=is(u)),i.xOffset=i.yOffset=0,i.force3D=ge.force3D,i.renderTransform=i.svg?Ef:Pl?Bl:bf,i.uncache=0,i},is=function(t){return(t=t.split(" "))[0]+" "+t[1]},Co=function(t,e,i){var r=Vt(e);return vt(parseFloat(e)+parseFloat(Oi(t,"x",i+"px",r)))+r},bf=function(t,e){e.z="0px",e.rotationY=e.rotationX="0deg",e.force3D=0,Bl(t,e)},Xi="0deg",Qr="0px",qi=") ",Bl=function(t,e){var i=e||this,r=i.xPercent,s=i.yPercent,o=i.x,a=i.y,l=i.z,u=i.rotation,c=i.rotationY,f=i.rotationX,d=i.skewX,h=i.skewY,_=i.scaleX,p=i.scaleY,g=i.transformPerspective,E=i.force3D,y=i.target,S=i.zOrigin,D="",v=E==="auto"&&t&&t!==1||E===!0;if(S&&(f!==Xi||c!==Xi)){var T=parseFloat(c)*br,b=Math.sin(T),x=Math.cos(T),w;T=parseFloat(f)*br,w=Math.cos(T),o=Co(y,o,b*w*-S),a=Co(y,a,-Math.sin(T)*-S),l=Co(y,l,x*w*-S+S)}g!==Qr&&(D+="perspective("+g+qi),(r||s)&&(D+="translate("+r+"%, "+s+"%) "),(v||o!==Qr||a!==Qr||l!==Qr)&&(D+=l!==Qr||v?"translate3d("+o+", "+a+", "+l+") ":"translate("+o+", "+a+qi),u!==Xi&&(D+="rotate("+u+qi),c!==Xi&&(D+="rotateY("+c+qi),f!==Xi&&(D+="rotateX("+f+qi),(d!==Xi||h!==Xi)&&(D+="skew("+d+", "+h+qi),(_!==1||p!==1)&&(D+="scale("+_+", "+p+qi),y.style[pt]=D||"translate(0, 0)"},Ef=function(t,e){var i=e||this,r=i.xPercent,s=i.yPercent,o=i.x,a=i.y,l=i.rotation,u=i.skewX,c=i.skewY,f=i.scaleX,d=i.scaleY,h=i.target,_=i.xOrigin,p=i.yOrigin,g=i.xOffset,E=i.yOffset,y=i.forceCSS,S=parseFloat(o),D=parseFloat(a),v,T,b,x,w;l=parseFloat(l),u=parseFloat(u),c=parseFloat(c),c&&(c=parseFloat(c),u+=c,l+=c),l||u?(l*=br,u*=br,v=Math.cos(l)*f,T=Math.sin(l)*f,b=Math.sin(l-u)*-d,x=Math.cos(l-u)*d,u&&(c*=br,w=Math.tan(u-c),w=Math.sqrt(1+w*w),b*=w,x*=w,c&&(w=Math.tan(c),w=Math.sqrt(1+w*w),v*=w,T*=w)),v=vt(v),T=vt(T),b=vt(b),x=vt(x)):(v=f,x=d,T=b=0),(S&&!~(o+"").indexOf("px")||D&&!~(a+"").indexOf("px"))&&(S=Oi(h,"x",o,"px"),D=Oi(h,"y",a,"px")),(_||p||g||E)&&(S=vt(S+_-(_*v+p*b)+g),D=vt(D+p-(_*T+p*x)+E)),(r||s)&&(w=h.getBBox(),S=vt(S+r/100*w.width),D=vt(D+s/100*w.height)),w="matrix("+v+","+T+","+b+","+x+","+S+","+D+")",h.setAttribute("transform",w),y&&(h.style[pt]=w)},wf=function(t,e,i,r,s){var o=360,a=kt(s),l=parseFloat(s)*(a&&~s.indexOf("rad")?Gi:1),u=l-r,c=r+u+"deg",f,d;return a&&(f=s.split("_")[1],f==="short"&&(u%=o,u!==u%(o/2)&&(u+=u<0?o:-o)),f==="cw"&&u<0?u=(u+o*yl)%o-~~(u/o)*o:f==="ccw"&&u>0&&(u=(u-o*yl)%o-~~(u/o)*o)),t._pt=d=new se(t._pt,e,i,r,u,sf),d.e=c,d.u="deg",t._props.push(i),d},xl=function(t,e){for(var i in e)t[i]=e[i];return t},Cf=function(t,e,i){var r=xl({},i._gsap),s="perspective,force3D,transformOrigin,svgOrigin",o=i.style,a,l,u,c,f,d,h,_;r.svg?(u=i.getAttribute("transform"),i.setAttribute("transform",""),o[pt]=e,a=Jr(i,1),Ai(i,pt),i.setAttribute("transform",u)):(u=getComputedStyle(i)[pt],o[pt]=e,a=Jr(i,1),o[pt]=u);for(l in ci)u=r[l],c=a[l],u!==c&&s.indexOf(l)<0&&(h=Vt(u),_=Vt(c),f=h!==_?Oi(i,l,u,_):parseFloat(u),d=parseFloat(c),t._pt=new se(t._pt,a,l,f,d-f,So),t._pt.u=_||0,t._props.push(l));xl(a,r)};ne("padding,margin,Width,Radius",function(n,t){var e="Top",i="Right",r="Bottom",s="Left",o=(t<3?[e,i,r,s]:[e+s,e+i,r+i,r+s]).map(function(a){return t<2?n+a:"border"+a+n});es[t>1?"border"+n:n]=function(a,l,u,c,f){var d,h;if(arguments.length<4)return d=o.map(function(_){return ui(a,_,u)}),h=d.join(" "),h.split(d[0]).length===5?d[0]:h;d=(c+"").split(" "),h={},o.forEach(function(_,p){return h[_]=d[p]=d[p]||d[(p-1)/2|0]}),a.init(l,h,f)}});var Ro={name:"css",register:xo,targetTest:function(t){return t.style&&t.nodeType},init:function(t,e,i,r,s){var o=this._props,a=t.style,l=i.vars.startAt,u,c,f,d,h,_,p,g,E,y,S,D,v,T,b,x,w;Ao||xo(),this.styles=this.styles||kl(t),x=this.styles.props,this.tween=i;for(p in e)if(p!=="autoRound"&&(c=e[p],!(pe[p]&&Do(p,e,i,r,t,s)))){if(h=typeof c,_=es[p],h==="function"&&(c=c.call(i,r,t,s),h=typeof c),h==="string"&&~c.indexOf("random(")&&(c=Dr(c)),_)_(this,t,p,c,i)&&(b=1);else if(p.substr(0,2)==="--")u=(getComputedStyle(t).getPropertyValue(p)+"").trim(),c+="",ai.lastIndex=0,ai.test(u)||(g=Vt(u),E=Vt(c),E?g!==E&&(u=Oi(t,p,u,E)+E):g&&(c+=g)),this.add(a,"setProperty",u,c,r,s,0,0,p),o.push(p),x.push(p,0,a[p]);else if(h!=="undefined"){if(l&&p in l?(u=typeof l[p]=="function"?l[p].call(i,r,t,s):l[p],kt(u)&&~u.indexOf("random(")&&(u=Dr(u)),Vt(u+"")||u==="auto"||(u+=ge.units[p]||Vt(ui(t,p))||""),(u+"").charAt(1)==="="&&(u=ui(t,p))):u=ui(t,p),d=parseFloat(u),y=h==="string"&&c.charAt(1)==="="&&c.substr(0,2),y&&(c=c.substr(2)),f=parseFloat(c),p in Ze&&(p==="autoAlpha"&&(d===1&&ui(t,"visibility")==="hidden"&&f&&(d=0),x.push("visibility",0,a.visibility),Fi(this,a,"visibility",d?"inherit":"hidden",f?"inherit":"hidden",!f)),p!=="scale"&&p!=="transform"&&(p=Ze[p],~p.indexOf(",")&&(p=p.split(",")[0]))),S=p in ci,S){if(this.styles.save(p),w=c,h==="string"&&c.substring(0,6)==="var(--"){if(c=Se(t,c.substring(4,c.indexOf(")"))),c.substring(0,5)==="calc("){var F=t.style.perspective;t.style.perspective=c,c=Se(t,"perspective"),F?t.style.perspective=F:Ai(t,"perspective")}f=parseFloat(c)}if(D||(v=t._gsap,v.renderTransform&&!e.parseTransform||Jr(t,e.parseTransform),T=e.smoothOrigin!==!1&&v.smooth,D=this._pt=new se(this._pt,a,pt,0,1,v.renderTransform,v,0,-1),D.dep=1),p==="scale")this._pt=new se(this._pt,v,"scaleY",v.scaleY,(y?$i(v.scaleY,y+f):f)-v.scaleY||0,So),this._pt.u=0,o.push("scaleY",p),p+="X";else if(p==="transformOrigin"){x.push(De,0,a[De]),c=vf(c),v.svg?Fo(t,c,0,T,0,this):(E=parseFloat(c.split(" ")[2])||0,E!==v.zOrigin&&Fi(this,v,"zOrigin",v.zOrigin,E),Fi(this,a,p,is(u),is(c)));continue}else if(p==="svgOrigin"){Fo(t,c,1,T,0,this);continue}else if(p in Il){wf(this,v,p,d,y?$i(d,y+c):c);continue}else if(p==="smoothOrigin"){Fi(this,v,"smooth",v.smooth,c);continue}else if(p==="force3D"){v[p]=c;continue}else if(p==="transform"){Cf(this,c,t);continue}}else p in a||(p=Er(p)||p);if(S||(f||f===0)&&(d||d===0)&&!nf.test(c)&&p in a)g=(u+"").substr((d+"").length),f||(f=0),E=Vt(c)||(p in ge.units?ge.units[p]:g),g!==E&&(d=Oi(t,p,u,E)),this._pt=new se(this._pt,S?v:a,p,d,(y?$i(d,y+f):f)-d,!S&&(E==="px"||p==="zIndex")&&e.autoRound!==!1?lf:So),this._pt.u=E||0,S&&w!==c?(this._pt.b=u,this._pt.e=w,this._pt.r=af):g!==E&&E!=="%"&&(this._pt.b=u,this._pt.r=of);else if(p in a)Df.call(this,t,p,u,y?y+c:c);else if(p in t)this.add(t,p,u||t[p],y?y+c:c,r,s);else if(p!=="parseTransform"){Kn(p,c);continue}S||(p in a?x.push(p,0,a[p]):typeof t[p]=="function"?x.push(p,2,t[p]()):x.push(p,1,u||t[p])),o.push(p)}}b&&wo(this)},render:function(t,e){if(e.tween._time||!Oo())for(var i=e._pt;i;)i.r(t,i.d),i=i._next;else e.styles.revert()},get:ui,aliases:Ze,getSetter:function(t,e,i){var r=Ze[e];return r&&r.indexOf(",")<0&&(e=r),e in ci&&e!==De&&(t._gsap.x||ui(t,"x"))?i&&vl===i?e==="scale"?ff:hf:(vl=i||{})&&(e==="scale"?df:pf):t.style&&!Gn(t.style[e])?uf:~e.indexOf("-")?cf:ts(t,e)},core:{_removeProperty:Ai,_getMatrix:Po}};Kt.utils.checkPrefix=Er;Kt.core.getStyleSaver=kl;(function(n,t,e,i){var r=ne(n+","+t+","+e,function(s){ci[s]=1});ne(t,function(s){ge.units[s]="deg",Il[s]=1}),Ze[r[13]]=n+","+t,ne(i,function(s){var o=s.split(":");Ze[o[1]]=r[o[0]]})})("x,y,z,scale,scaleX,scaleY,xPercent,yPercent","rotation,rotationX,rotationY,skewX,skewY","transform,transformOrigin,svgOrigin,force3D,smoothOrigin,transformPerspective","0:translateX,1:translateY,2:translateZ,8:rotate,8:rotationZ,8:rotateZ,9:rotateX,10:rotateY");ne("x,y,z,top,right,bottom,left,width,height,fontSize,padding,margin,perspective",function(n){ge.units[n]="px"});Kt.registerPlugin(Ro);var _t=Kt.registerPlugin(Ro)||Kt,Xd=_t.core.Tween;var Mo="production",zl=window.matchMedia("(any-pointer:coarse)").matches,Qi=Object.freeze({NAME:Mo,IS_PROD:Mo==="production",IS_DEV:Mo==="development",IS_MOBILE:zl,IS_DESKTOP:!zl}),wr=Object.freeze({FIRST_LOADED:"is-first-loaded",LOADING:"is-loading",LOADED:"is-loaded",READY:"is-ready",FONTS_LOADED:"fonts-loaded",LAZY_CONTAINER:"c-lazy",LAZY_LOADED:"-lazy-loaded"}),tn=Object.freeze({RESIZE_END:"loco.resizeEnd"}),Cr=Object.freeze({EAGER:[{family:"PP Locomotive New",style:"normal",weight:"300"},{family:"Helvetica Now Display",style:"normal",weight:"400"},{family:"Helvetica Now Display",style:"normal",weight:"500"}]});var rs=class extends de{constructor(n){super(n),this.onUpdateBind=this.onUpdate.bind(this),this.onResizeBind=this.onResize.bind(this),this.onToggleBind=this.onToggle.bind(this),this.$container=this.$("container")[0],this.$pattern=this.$("pattern")[0],this.prevCount=null,this.currentTranslate=0,this.maxTranslate=this.$container.offsetWidth,this.idleVelocity=1,this.scrollVelocity=.1,this.scrollLerp=.8,this.scrollDirection=1,this.railDirection=this.getData("direction"),this.showFrom=parseInt(this.getData("from"))||!1,this.showTo=parseInt(this.getData("to"))||!1,this.data=[],this.$items=[],this.glyphs=["\u{1F6D1}","\u{1F51D}","\u{1F378}","\u26FA","\u{1F616}"]}init(){this.bindEvents(),Nn(Cr.EAGER).then(n=>{this.onFontsLoaded(n)})}destroy(){super.destroy(),this.unbindEvents(),this.stop()}bindEvents(){window.addEventListener(tn.RESIZE_END,this.onResizeBind),window.addEventListener("toggleRail",this.onToggleBind)}unbindEvents(){window.removeEventListener(tn.RESIZE_END,this.onResizeBind),window.removeEventListener("toggleRail",this.onToggleBind)}onResize(){this.repeatPattern()}onFontsLoaded(){this.repeatPattern()}onUpdate(){this.currentTranslate=(this.currentTranslate+this.idleVelocity*this.scrollDirection*this.railDirection+this.scrollVelocity*this.scrollDirection*this.railDirection)%this.maxTranslate;for(let[n,t]of this.$items.entries()){let e,i=this.data[n].left+this.data[n].width;this.currentTranslatethis.maxTranslate-i?e=-this.maxTranslate:e=0,this.data[n].translate=e,t.style.transform=`translate3d(${e}px,0,0)`}this.$container.style.transform=`translate3d(${this.currentTranslate}px,0,0)`}onScroll(n){let{velocity:t,direction:e}=n;Qi.IS_MOBILE||(this.scrollDirection=e!=0?e:this.scrollDirection,this.scrollDirection=this.scrollDirection*-1,this.scrollVelocity=Math.round(Math.abs(t))*this.scrollLerp)}onToggle(n){let{way:t}=n.detail;t==="enter"?this.start():this.stop()}start(){this.isPlaying||(this.isPlaying=!0,_t.ticker.add(this.onUpdateBind))}stop(){this.isPlaying&&(this.isPlaying=!1,_t.ticker.remove(this.onUpdateBind))}computeMetrics(n=!1){if(n){this.$items=this.el.querySelectorAll("[data-rail-item]"),this.data=[],this.currentTranslate=0;for(let[t,e]of this.$items.entries()){let{left:i,width:r}=e.getBoundingClientRect();this.data[t]={left:i,width:r,translate:0}}}else for(let[t,e]of this.$items.entries()){let{left:i,width:r}=e.getBoundingClientRect();this.data[t].left=i-this.currentTranslate-this.data[t].translate,this.data[t].width=r}}repeatPattern(){if(this.showFrom&&window.innerWidththis.showTo)return;let n=this.$pattern.offsetWidth,t=Math.ceil(window.innerWidth/n)+1;if(this.maxTranslate=t*n,t===this.prevCount)return this.computeMetrics();this.prevCount=t;let e=this.$container.querySelectorAll("[data-clone]");for(let i of e)i.remove();for(let i=0;i{this.computeMetrics(!0)})}};var Ve=document.documentElement,ip=document.body;var Nl="5.0.1";var Tf="1.3.17";function Vl(n,t,e){return Math.max(n,Math.min(t,e))}function xf(n,t,e){return(1-e)*n+e*t}function Ff(n,t,e,i){return xf(n,t,1-Math.exp(-e*i))}function Af(n,t){return(n%t+t)%t}var Of=class{constructor(){I(this,"isRunning",!1);I(this,"value",0);I(this,"from",0);I(this,"to",0);I(this,"currentTime",0);I(this,"lerp");I(this,"duration");I(this,"easing");I(this,"onUpdate")}advance(n){var e;if(!this.isRunning)return;let t=!1;if(this.duration&&this.easing){this.currentTime+=n;let i=Vl(0,this.currentTime/this.duration,1);t=i>=1;let r=t?1:this.easing(i);this.value=this.from+(this.to-this.from)*r}else this.lerp?(this.value=Ff(this.value,this.to,this.lerp*60,n),Math.round(this.value)===this.to&&(this.value=this.to,t=!0)):(this.value=this.to,t=!0);t&&this.stop(),(e=this.onUpdate)==null||e.call(this,this.value,t)}stop(){this.isRunning=!1}fromTo(n,t,{lerp:e,duration:i,easing:r,onStart:s,onUpdate:o}){this.from=this.value=n,this.to=t,this.lerp=e,this.duration=i,this.easing=r,this.currentTime=0,this.isRunning=!0,s==null||s(),this.onUpdate=o}};function kf(n,t){let e;return function(...i){let r=this;clearTimeout(e),e=setTimeout(()=>{e=void 0,n.apply(r,i)},t)}}var Pf=class{constructor(n,t,{autoResize:e=!0,debounce:i=250}={}){I(this,"width",0);I(this,"height",0);I(this,"scrollHeight",0);I(this,"scrollWidth",0);I(this,"debouncedResize");I(this,"wrapperResizeObserver");I(this,"contentResizeObserver");I(this,"resize",()=>{this.onWrapperResize(),this.onContentResize()});I(this,"onWrapperResize",()=>{this.wrapper instanceof Window?(this.width=window.innerWidth,this.height=window.innerHeight):(this.width=this.wrapper.clientWidth,this.height=this.wrapper.clientHeight)});I(this,"onContentResize",()=>{this.wrapper instanceof Window?(this.scrollHeight=this.content.scrollHeight,this.scrollWidth=this.content.scrollWidth):(this.scrollHeight=this.wrapper.scrollHeight,this.scrollWidth=this.wrapper.scrollWidth)});this.wrapper=n,this.content=t,e&&(this.debouncedResize=kf(this.resize,i),this.wrapper instanceof Window?window.addEventListener("resize",this.debouncedResize,!1):(this.wrapperResizeObserver=new ResizeObserver(this.debouncedResize),this.wrapperResizeObserver.observe(this.wrapper)),this.contentResizeObserver=new ResizeObserver(this.debouncedResize),this.contentResizeObserver.observe(this.content)),this.resize()}destroy(){var n,t;(n=this.wrapperResizeObserver)==null||n.disconnect(),(t=this.contentResizeObserver)==null||t.disconnect(),this.wrapper===window&&this.debouncedResize&&window.removeEventListener("resize",this.debouncedResize,!1)}get limit(){return{x:this.scrollWidth-this.width,y:this.scrollHeight-this.height}}},Ul=class{constructor(){I(this,"events",{})}emit(n,...t){var i;let e=this.events[n]||[];for(let r=0,s=e.length;r{var i;this.events[n]=(i=this.events[n])==null?void 0:i.filter(r=>t!==r)}}off(n,t){var e;this.events[n]=(e=this.events[n])==null?void 0:e.filter(i=>t!==i)}destroy(){this.events={}}},Hl=100/6,ki={passive:!1},Rf=class{constructor(n,t={wheelMultiplier:1,touchMultiplier:1}){I(this,"touchStart",{x:0,y:0});I(this,"lastDelta",{x:0,y:0});I(this,"window",{width:0,height:0});I(this,"emitter",new Ul);I(this,"onTouchStart",n=>{let{clientX:t,clientY:e}=n.targetTouches?n.targetTouches[0]:n;this.touchStart.x=t,this.touchStart.y=e,this.lastDelta={x:0,y:0},this.emitter.emit("scroll",{deltaX:0,deltaY:0,event:n})});I(this,"onTouchMove",n=>{let{clientX:t,clientY:e}=n.targetTouches?n.targetTouches[0]:n,i=-(t-this.touchStart.x)*this.options.touchMultiplier,r=-(e-this.touchStart.y)*this.options.touchMultiplier;this.touchStart.x=t,this.touchStart.y=e,this.lastDelta={x:i,y:r},this.emitter.emit("scroll",{deltaX:i,deltaY:r,event:n})});I(this,"onTouchEnd",n=>{this.emitter.emit("scroll",{deltaX:this.lastDelta.x,deltaY:this.lastDelta.y,event:n})});I(this,"onWheel",n=>{let{deltaX:t,deltaY:e,deltaMode:i}=n,r=i===1?Hl:i===2?this.window.width:1,s=i===1?Hl:i===2?this.window.height:1;t*=r,e*=s,t*=this.options.wheelMultiplier,e*=this.options.wheelMultiplier,this.emitter.emit("scroll",{deltaX:t,deltaY:e,event:n})});I(this,"onWindowResize",()=>{this.window={width:window.innerWidth,height:window.innerHeight}});this.element=n,this.options=t,window.addEventListener("resize",this.onWindowResize,!1),this.onWindowResize(),this.element.addEventListener("wheel",this.onWheel,ki),this.element.addEventListener("touchstart",this.onTouchStart,ki),this.element.addEventListener("touchmove",this.onTouchMove,ki),this.element.addEventListener("touchend",this.onTouchEnd,ki)}on(n,t){return this.emitter.on(n,t)}destroy(){this.emitter.destroy(),window.removeEventListener("resize",this.onWindowResize,!1),this.element.removeEventListener("wheel",this.onWheel,ki),this.element.removeEventListener("touchstart",this.onTouchStart,ki),this.element.removeEventListener("touchmove",this.onTouchMove,ki),this.element.removeEventListener("touchend",this.onTouchEnd,ki)}},Wl=n=>Math.min(1,1.001-Math.pow(2,-10*n)),Yl=class{constructor({wrapper:n=window,content:t=document.documentElement,eventsTarget:e=n,smoothWheel:i=!0,syncTouch:r=!1,syncTouchLerp:s=.075,touchInertiaExponent:o=1.7,duration:a,easing:l,lerp:u=.1,infinite:c=!1,orientation:f="vertical",gestureOrientation:d=f==="horizontal"?"both":"vertical",touchMultiplier:h=1,wheelMultiplier:_=1,autoResize:p=!0,prevent:g,virtualScroll:E,overscroll:y=!0,autoRaf:S=!1,anchors:D=!1,autoToggle:v=!1,allowNestedScroll:T=!1,__experimental__naiveDimensions:b=!1,naiveDimensions:x=b,stopInertiaOnNavigate:w=!1}={}){I(this,"_isScrolling",!1);I(this,"_isStopped",!1);I(this,"_isLocked",!1);I(this,"_preventNextNativeScrollEvent",!1);I(this,"_resetVelocityTimeout",null);I(this,"_rafId",null);I(this,"isTouching");I(this,"time",0);I(this,"userData",{});I(this,"lastVelocity",0);I(this,"velocity",0);I(this,"direction",0);I(this,"options");I(this,"targetScroll");I(this,"animatedScroll");I(this,"animate",new Of);I(this,"emitter",new Ul);I(this,"dimensions");I(this,"virtualScroll");I(this,"onScrollEnd",n=>{n instanceof CustomEvent||(this.isScrolling==="smooth"||this.isScrolling===!1)&&n.stopPropagation()});I(this,"dispatchScrollendEvent",()=>{this.options.wrapper.dispatchEvent(new CustomEvent("scrollend",{bubbles:this.options.wrapper===window,detail:{lenisScrollEnd:!0}}))});I(this,"onTransitionEnd",n=>{n.propertyName.includes("overflow")&&this.checkOverflow()});I(this,"onClick",n=>{let e=n.composedPath().filter(i=>i instanceof HTMLAnchorElement&&i.getAttribute("href"));if(this.options.anchors){let i=e.find(r=>{var s;return(s=r.getAttribute("href"))==null?void 0:s.includes("#")});if(i){let r=i.getAttribute("href");if(r){let s=typeof this.options.anchors=="object"&&this.options.anchors?this.options.anchors:void 0,o=`#${r.split("#")[1]}`;this.scrollTo(o,s)}}}this.options.stopInertiaOnNavigate&&e.find(r=>r.host===window.location.host)&&this.reset()});I(this,"onPointerDown",n=>{n.button===1&&this.reset()});I(this,"onVirtualScroll",n=>{if(typeof this.options.virtualScroll=="function"&&this.options.virtualScroll(n)===!1)return;let{deltaX:t,deltaY:e,event:i}=n;if(this.emitter.emit("virtual-scroll",{deltaX:t,deltaY:e,event:i}),i.ctrlKey||i.lenisStopPropagation)return;let r=i.type.includes("touch"),s=i.type.includes("wheel");this.isTouching=i.type==="touchstart"||i.type==="touchmove";let o=t===0&&e===0;if(this.options.syncTouch&&r&&i.type==="touchstart"&&o&&!this.isStopped&&!this.isLocked){this.reset();return}let l=this.options.gestureOrientation==="vertical"&&e===0||this.options.gestureOrientation==="horizontal"&&t===0;if(o||l)return;let u=i.composedPath();u=u.slice(0,u.indexOf(this.rootElement));let c=this.options.prevent;if(u.find(g=>{var E,y,S;return g instanceof HTMLElement&&(typeof c=="function"&&(c==null?void 0:c(g))||((E=g.hasAttribute)==null?void 0:E.call(g,"data-lenis-prevent"))||r&&((y=g.hasAttribute)==null?void 0:y.call(g,"data-lenis-prevent-touch"))||s&&((S=g.hasAttribute)==null?void 0:S.call(g,"data-lenis-prevent-wheel"))||this.options.allowNestedScroll&&this.checkNestedScroll(g,{deltaX:t,deltaY:e}))}))return;if(this.isStopped||this.isLocked){i.cancelable&&i.preventDefault();return}if(!(this.options.syncTouch&&r||this.options.smoothWheel&&s)){this.isScrolling="native",this.animate.stop(),i.lenisStopPropagation=!0;return}let d=e;this.options.gestureOrientation==="both"?d=Math.abs(e)>Math.abs(t)?e:t:this.options.gestureOrientation==="horizontal"&&(d=t),(!this.options.overscroll||this.options.infinite||this.options.wrapper!==window&&this.limit>0&&(this.animatedScroll>0&&this.animatedScroll0||this.animatedScroll===this.limit&&e<0))&&(i.lenisStopPropagation=!0),i.cancelable&&i.preventDefault();let h=r&&this.options.syncTouch,p=r&&i.type==="touchend";p&&(d=Math.sign(this.velocity)*Math.pow(Math.abs(this.velocity),this.options.touchInertiaExponent)),this.scrollTo(this.targetScroll+d,Mn({programmatic:!1},h?{lerp:p?this.options.syncTouchLerp:1}:{lerp:this.options.lerp,duration:this.options.duration,easing:this.options.easing}))});I(this,"onNativeScroll",()=>{if(this._resetVelocityTimeout!==null&&(clearTimeout(this._resetVelocityTimeout),this._resetVelocityTimeout=null),this._preventNextNativeScrollEvent){this._preventNextNativeScrollEvent=!1;return}if(this.isScrolling===!1||this.isScrolling==="native"){let n=this.animatedScroll;this.animatedScroll=this.targetScroll=this.actualScroll,this.lastVelocity=this.velocity,this.velocity=this.animatedScroll-n,this.direction=Math.sign(this.animatedScroll-n),this.isStopped||(this.isScrolling="native"),this.emit(),this.velocity!==0&&(this._resetVelocityTimeout=setTimeout(()=>{this.lastVelocity=this.velocity,this.velocity=0,this.isScrolling=!1,this.emit()},400))}});I(this,"raf",n=>{let t=n-(this.time||n);this.time=n,this.animate.advance(t*.001),this.options.autoRaf&&(this._rafId=requestAnimationFrame(this.raf))});window.lenisVersion=Tf,(!n||n===document.documentElement)&&(n=window),typeof a=="number"&&typeof l!="function"?l=Wl:typeof l=="function"&&typeof a!="number"&&(a=1),this.options={wrapper:n,content:t,eventsTarget:e,smoothWheel:i,syncTouch:r,syncTouchLerp:s,touchInertiaExponent:o,duration:a,easing:l,lerp:u,infinite:c,gestureOrientation:d,orientation:f,touchMultiplier:h,wheelMultiplier:_,autoResize:p,prevent:g,virtualScroll:E,overscroll:y,autoRaf:S,anchors:D,autoToggle:v,allowNestedScroll:T,naiveDimensions:x,stopInertiaOnNavigate:w},this.dimensions=new Pf(n,t,{autoResize:p}),this.updateClassName(),this.targetScroll=this.animatedScroll=this.actualScroll,this.options.wrapper.addEventListener("scroll",this.onNativeScroll,!1),this.options.wrapper.addEventListener("scrollend",this.onScrollEnd,{capture:!0}),(this.options.anchors||this.options.stopInertiaOnNavigate)&&this.options.wrapper.addEventListener("click",this.onClick,!1),this.options.wrapper.addEventListener("pointerdown",this.onPointerDown,!1),this.virtualScroll=new Rf(e,{touchMultiplier:h,wheelMultiplier:_}),this.virtualScroll.on("scroll",this.onVirtualScroll),this.options.autoToggle&&(this.checkOverflow(),this.rootElement.addEventListener("transitionend",this.onTransitionEnd,{passive:!0})),this.options.autoRaf&&(this._rafId=requestAnimationFrame(this.raf))}destroy(){this.emitter.destroy(),this.options.wrapper.removeEventListener("scroll",this.onNativeScroll,!1),this.options.wrapper.removeEventListener("scrollend",this.onScrollEnd,{capture:!0}),this.options.wrapper.removeEventListener("pointerdown",this.onPointerDown,!1),(this.options.anchors||this.options.stopInertiaOnNavigate)&&this.options.wrapper.removeEventListener("click",this.onClick,!1),this.virtualScroll.destroy(),this.dimensions.destroy(),this.cleanUpClassName(),this._rafId&&cancelAnimationFrame(this._rafId)}on(n,t){return this.emitter.on(n,t)}off(n,t){return this.emitter.off(n,t)}get overflow(){let n=this.isHorizontal?"overflow-x":"overflow-y";return getComputedStyle(this.rootElement)[n]}checkOverflow(){["hidden","clip"].includes(this.overflow)?this.internalStop():this.internalStart()}setScroll(n){this.isHorizontal?this.options.wrapper.scrollTo({left:n,behavior:"instant"}):this.options.wrapper.scrollTo({top:n,behavior:"instant"})}resize(){this.dimensions.resize(),this.animatedScroll=this.targetScroll=this.actualScroll,this.emit()}emit(){this.emitter.emit("scroll",this)}reset(){this.isLocked=!1,this.isScrolling=!1,this.animatedScroll=this.targetScroll=this.actualScroll,this.lastVelocity=this.velocity=0,this.animate.stop()}start(){if(this.isStopped){if(this.options.autoToggle){this.rootElement.style.removeProperty("overflow");return}this.internalStart()}}internalStart(){this.isStopped&&(this.reset(),this.isStopped=!1,this.emit())}stop(){if(!this.isStopped){if(this.options.autoToggle){this.rootElement.style.setProperty("overflow","clip");return}this.internalStop()}}internalStop(){this.isStopped||(this.reset(),this.isStopped=!0,this.emit())}scrollTo(n,{offset:t=0,immediate:e=!1,lock:i=!1,programmatic:r=!0,lerp:s=r?this.options.lerp:void 0,duration:o=r?this.options.duration:void 0,easing:a=r?this.options.easing:void 0,onStart:l,onComplete:u,force:c=!1,userData:f}={}){if(!((this.isStopped||this.isLocked)&&!c)){if(typeof n=="string"&&["top","left","start","#"].includes(n))n=0;else if(typeof n=="string"&&["bottom","right","end"].includes(n))n=this.limit;else{let d;if(typeof n=="string"?(d=document.querySelector(n),d||(n==="#top"?n=0:console.warn("Lenis: Target not found",n))):n instanceof HTMLElement&&(n!=null&&n.nodeType)&&(d=n),d){if(this.options.wrapper!==window){let _=this.rootElement.getBoundingClientRect();t-=this.isHorizontal?_.left:_.top}let h=d.getBoundingClientRect();n=(this.isHorizontal?h.left:h.top)+this.animatedScroll}}if(typeof n=="number"){if(n+=t,n=Math.round(n),this.options.infinite){if(r){this.targetScroll=this.animatedScroll=this.scroll;let d=n-this.animatedScroll;d>this.limit/2?n=n-this.limit:d<-this.limit/2&&(n=n+this.limit)}}else n=Vl(0,n,this.limit);if(n===this.targetScroll){l==null||l(this),u==null||u(this);return}if(this.userData=f!=null?f:{},e){this.animatedScroll=this.targetScroll=n,this.setScroll(this.scroll),this.reset(),this.preventNextNativeScrollEvent(),this.emit(),u==null||u(this),this.userData={},requestAnimationFrame(()=>{this.dispatchScrollendEvent()});return}r||(this.targetScroll=n),typeof o=="number"&&typeof a!="function"?a=Wl:typeof a=="function"&&typeof o!="number"&&(o=1),this.animate.fromTo(this.animatedScroll,n,{duration:o,easing:a,lerp:s,onStart:()=>{i&&(this.isLocked=!0),this.isScrolling="smooth",l==null||l(this)},onUpdate:(d,h)=>{this.isScrolling="smooth",this.lastVelocity=this.velocity,this.velocity=d-this.animatedScroll,this.direction=Math.sign(this.velocity),this.animatedScroll=d,this.setScroll(this.scroll),r&&(this.targetScroll=d),h||this.emit(),h&&(this.reset(),this.emit(),u==null||u(this),this.userData={},requestAnimationFrame(()=>{this.dispatchScrollendEvent()}),this.preventNextNativeScrollEvent())}})}}}preventNextNativeScrollEvent(){this._preventNextNativeScrollEvent=!0,requestAnimationFrame(()=>{this._preventNextNativeScrollEvent=!1})}checkNestedScroll(n,{deltaX:t,deltaY:e}){var v,T;let i=Date.now(),r=(v=n._lenis)!=null?v:n._lenis={},s,o,a,l,u,c,f,d,h=this.options.gestureOrientation;if(i-((T=r.time)!=null?T:0)>2e3){r.time=Date.now();let b=window.getComputedStyle(n);r.computedStyle=b;let x=b.overflowX,w=b.overflowY;if(s=["auto","overlay","scroll"].includes(x),o=["auto","overlay","scroll"].includes(w),r.hasOverflowX=s,r.hasOverflowY=o,!s&&!o||h==="vertical"&&!o||h==="horizontal"&&!s)return!1;u=n.scrollWidth,c=n.scrollHeight,f=n.clientWidth,d=n.clientHeight,a=u>f,l=c>d,r.isScrollableX=a,r.isScrollableY=l,r.scrollWidth=u,r.scrollHeight=c,r.clientWidth=f,r.clientHeight=d}else a=r.isScrollableX,l=r.isScrollableY,s=r.hasOverflowX,o=r.hasOverflowY,u=r.scrollWidth,c=r.scrollHeight,f=r.clientWidth,d=r.clientHeight;if(!s&&!o||!a&&!l||h==="vertical"&&(!o||!l)||h==="horizontal"&&(!s||!a))return!1;let _;if(h==="horizontal")_="x";else if(h==="vertical")_="y";else{let b=t!==0,x=e!==0;b&&s&&a&&(_="x"),x&&o&&l&&(_="y")}if(!_)return!1;let p,g,E,y,S;if(_==="x")p=n.scrollLeft,g=u-f,E=t,y=s,S=a;else if(_==="y")p=n.scrollTop,g=c-d,E=e,y=o,S=l;else return!1;return(E>0?p0)&&y&&S}get rootElement(){return this.options.wrapper===window?document.documentElement:this.options.wrapper}get limit(){return this.options.naiveDimensions?this.isHorizontal?this.rootElement.scrollWidth-this.rootElement.clientWidth:this.rootElement.scrollHeight-this.rootElement.clientHeight:this.dimensions.limit[this.isHorizontal?"x":"y"]}get isHorizontal(){return this.options.orientation==="horizontal"}get actualScroll(){var t,e;let n=this.options.wrapper;return this.isHorizontal?(t=n.scrollX)!=null?t:n.scrollLeft:(e=n.scrollY)!=null?e:n.scrollTop}get scroll(){return this.options.infinite?Af(this.animatedScroll,this.limit):this.animatedScroll}get progress(){return this.limit===0?1:this.scroll/this.limit}get isScrolling(){return this._isScrolling}set isScrolling(n){this._isScrolling!==n&&(this._isScrolling=n,this.updateClassName())}get isStopped(){return this._isStopped}set isStopped(n){this._isStopped!==n&&(this._isStopped=n,this.updateClassName())}get isLocked(){return this._isLocked}set isLocked(n){this._isLocked!==n&&(this._isLocked=n,this.updateClassName())}get isSmooth(){return this.isScrolling==="smooth"}get className(){let n="lenis";return this.options.autoToggle&&(n+=" lenis-autoToggle"),this.isStopped&&(n+=" lenis-stopped"),this.isLocked&&(n+=" lenis-locked"),this.isScrolling&&(n+=" lenis-scrolling"),this.isScrolling==="smooth"&&(n+=" lenis-smooth"),n}updateClassName(){this.cleanUpClassName(),this.rootElement.className=`${this.rootElement.className} ${this.className}`.trim()}cleanUpClassName(){this.rootElement.className=this.rootElement.className.replace(/lenis(-\w+)?/g,"").trim()}};var Sr=class{constructor({scrollElements:t,rootMargin:e="-1px -1px -1px -1px",root:i=null,IORaf:r}){this.scrollElements=t,this.rootMargin=e,this.root=i,this.IORaf=r,this._init()}_init(){let t={root:this.root,rootMargin:this.rootMargin},e=i=>{i.forEach(r=>{let s=this.scrollElements.find(o=>o.$el===r.target);r.isIntersecting?(s&&(s.isAlreadyIntersected=!0),this._setInview(r)):s&&s.isAlreadyIntersected&&this._setOutOfView(r)})};this.observer=new IntersectionObserver(e,t);for(let i of this.scrollElements){let r=i.$el;this.observe(r)}}destroy(){this.observer.disconnect()}observe(t){t&&this.observer.observe(t)}unobserve(t){t&&this.observer.unobserve(t)}_setInview(t){let e=this.scrollElements.find(i=>i.$el===t.target);this.IORaf&&(e==null||e.setInteractivityOn()),!this.IORaf&&(e==null||e.setInview())}_setOutOfView(t){let e=this.scrollElements.find(i=>i.$el===t.target);this.IORaf&&(e==null||e.setInteractivityOff()),!this.IORaf&&(e==null||e.setOutOfView()),!(e!=null&&e.attributes.scrollRepeat)&&!this.IORaf&&this.unobserve(t.target)}};function $l(n,t,e){return et?t:e}function Io(n,t,e,i,r){let s=t-n,o=i-e;return e+((r-n)/s*o||0)}function jl(n,t,e){return Io(n,t,0,1,e)}function Lo(n,t){return n.reduce((e,i)=>Math.abs(i-t)t-e+i,middle:(t,e,i,r)=>t-e+i+r*.5,end:(t,e,i,r)=>t-e+i+r,fold:()=>0};this.endPositionHandlers={start:(t,e)=>t-e,middle:(t,e,i)=>t-e+i*.5,end:(t,e,i)=>t-e+i};var l,u,c,f,d;this.$el=t,this.id=e,this.needRaf=s,this.scrollOrientation=o,this.lenisInstance=a,this.subscribeElementUpdateFn=i,this.unsubscribeElementUpdateFn=r,this.attributes={scrollClass:(l=this.$el.dataset.scrollClass)!=null?l:Mf,scrollOffset:(u=this.$el.dataset.scrollOffset)!=null?u:"0,0",scrollPosition:(c=this.$el.dataset.scrollPosition)!=null?c:"start,end",scrollCssProgress:this.$el.dataset.scrollCssProgress!==void 0,scrollEventProgress:(f=this.$el.dataset.scrollEventProgress)!=null?f:null,scrollSpeed:this.$el.dataset.scrollSpeed!==void 0?parseFloat(this.$el.dataset.scrollSpeed):null,scrollRepeat:this.$el.dataset.scrollRepeat!==void 0,scrollCall:(d=this.$el.dataset.scrollCall)!=null?d:null,scrollIgnoreFold:this.$el.dataset.scrollIgnoreFold!==void 0,scrollEnableTouchSpeed:this.$el.dataset.scrollEnableTouchSpeed!==void 0},this.intersection={start:0,end:0},this.metrics={offsetStart:0,offsetEnd:0,bcr:{}},this.currentScroll=this.lenisInstance.scroll,this.translateValue=0,this.progress=0,this.lastProgress=null,this.isInview=!1,this.isInteractive=!1,this.isAlreadyIntersected=!1,this.isInFold=!1,this.isFirstResize=!0,this.getWindowSize=this.scrollOrientation==="vertical"?()=>this.lenisInstance.dimensions.height:()=>this.lenisInstance.dimensions.width,this.getMetricsStart=this.scrollOrientation==="vertical"?h=>h.top:h=>h.left,this.getMetricsSize=this.scrollOrientation==="vertical"?h=>h.height:h=>h.width,this._init()}_init(){this.needRaf&&this._resize()}onResize({currentScroll:t}){this.currentScroll=t,this._resize()}onRender({currentScroll:t,smooth:e}){let i=this.getWindowSize();if(this.currentScroll=t,this._computeProgress(),this.attributes.scrollSpeed&&!isNaN(this.attributes.scrollSpeed))if(!this.attributes.scrollEnableTouchSpeed&&!e)this.translateValue&&(this.$el.style.transform="translate3d(0, 0, 0)"),this.translateValue=0;else{if(this.isInFold){let r=Math.max(0,this.progress);this.translateValue=r*i*this.attributes.scrollSpeed*-1}else{let r=Io(0,1,-1,1,this.progress);this.translateValue=r*i*this.attributes.scrollSpeed*-1}this.$el.style.transform=this.scrollOrientation==="vertical"?`translate3d(0, ${this.translateValue}px, 0)`:`translate3d(${this.translateValue}px, 0, 0)`}}setInview(){if(this.isInview)return;this.isInview=!0,this.$el.classList.add(this.attributes.scrollClass);let t="enter",e=this._getScrollCallFrom();this.attributes.scrollCall&&this._dispatchCall(t,e)}setOutOfView(){if(!(this.isInview&&this.attributes.scrollRepeat))return;this.isInview=!1,this.$el.classList.remove(this.attributes.scrollClass);let t="leave",e=this._getScrollCallFrom();this.attributes.scrollCall&&this._dispatchCall(t,e)}setInteractivityOn(){this.isInteractive||(this.isInteractive=!0,this.subscribeElementUpdateFn(this))}setInteractivityOff(){this.isInteractive&&(this.isInteractive=!1,this.unsubscribeElementUpdateFn(this),this.lastProgress!==null&&this._computeProgress(Lo([0,1],this.lastProgress)))}_resize(){this.metrics.bcr=this.$el.getBoundingClientRect(),this._computeMetrics(),this._computeIntersection(),this.isFirstResize&&(this.isFirstResize=!1,this.isInFold&&this.setInview())}_computeMetrics(){let t=this.getWindowSize(),e=this.getMetricsStart(this.metrics.bcr),i=this.getMetricsSize(this.metrics.bcr);this.metrics.offsetStart=this.currentScroll+e-this.translateValue,this.metrics.offsetEnd=this.metrics.offsetStart+i,this.metrics.offsetStart0&&e<1&&this.setInview(),e===0&&this.setOutOfView(),e===1&&this.setOutOfView())}_setCssProgress(t=0){this.$el.style.setProperty(Xl,t.toString())}_setCustomEventProgress(t=0){let e=this.attributes.scrollEventProgress;if(!e)return;let i=new CustomEvent(e,{detail:{target:this.$el,progress:t}});window.dispatchEvent(i)}_getScrollCallFrom(){let t=Lo([this.intersection.start,this.intersection.end],this.currentScroll);return this.intersection.start===t?"start":"end"}destroy(){this.attributes.scrollCssProgress&&this.$el.style.removeProperty(Xl),this.attributes.scrollSpeed&&this.$el.style.removeProperty("transform"),this.isInview&&this.attributes.scrollClass&&this.$el.classList.remove(this.attributes.scrollClass)}_dispatchCall(t,e){let i=this.attributes.scrollCall;if(!i)return;let r=new CustomEvent(i,{detail:{target:this.$el,way:t,from:e}});window.dispatchEvent(r)}};var If=["scrollOffset","scrollPosition","scrollCssProgress","scrollEventProgress","scrollSpeed"],Lf="-1px -1px -1px -1px",Bf="100% 100% 100% 100%",zf="0,0",Nf="top,bottom",rn=class{constructor({$el:t,triggerRootMargin:e,rafRootMargin:i,scrollOrientation:r,lenisInstance:s}){if(!t){console.error("Please provide a DOM Element as scrollContainer");return}this.$scrollContainer=t,this.lenisInstance=s,this.scrollOrientation=r,this.triggerRootMargin=e!=null?e:Lf,this.rafRootMargin=i!=null?i:Bf,this.scrollElements=[],this.triggeredScrollElements=[],this.RAFScrollElements=[],this.scrollElementsToUpdate=[],this._init()}_init(){let t=this.$scrollContainer.querySelectorAll("[data-scroll]"),e=this.toElementArray(t);this._subscribeScrollElements(e);let i=this.lenisInstance.options.wrapper===window?null:this.lenisInstance.options.wrapper;this.IOTriggerInstance=new Sr({scrollElements:[...this.triggeredScrollElements],root:i,rootMargin:this.triggerRootMargin,IORaf:!1}),this.IORafInstance=new Sr({scrollElements:[...this.RAFScrollElements],root:i,rootMargin:this.rafRootMargin,IORaf:!0})}destroy(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),this._unsubscribeAllScrollElements()}onResize({currentScroll:t}){for(let e of this.RAFScrollElements)e.onResize({currentScroll:t})}onRender({currentScroll:t,smooth:e}){for(let i of this.scrollElementsToUpdate)i.onRender({currentScroll:t,smooth:e})}removeScrollElements(t){let e=t.querySelectorAll("[data-scroll]");if(!e.length)return;let i=new Set(Array.from(e));for(let r=0;r{let s=this.scrollElementsToUpdate.find(a=>a.$el===r),o=this.scrollElements.find(a=>a.$el===r);s&&this._unsubscribeElementUpdate(s),o&&(this.scrollElements=this.scrollElements.filter(a=>a.id!=o.id))})}addScrollElements(t){let e=t.querySelectorAll("[data-scroll]"),i=[];this.scrollElements.forEach(a=>{i.push(a.id)});let s=Math.max(...i,0)+1,o=this.toElementArray(e);this._subscribeScrollElements(o,s,!0)}_subscribeScrollElements(t,e=0,i=!1){for(let r=0;re.id!=t.id)}toElementArray(t){return Array.from(t)}_checkRafNeeded(t){let e=[...If],i=r=>{e=e.filter(s=>s!==r)};if(t.dataset.scrollOffset){if(t.dataset.scrollOffset.split(",").map(s=>s.replace("%","").trim()).join(",")!==zf)return!0;i("scrollOffset")}else i("scrollOffset");if(t.dataset.scrollPosition){if(t.dataset.scrollPosition.trim()!==Nf)return!0;i("scrollPosition")}else i("scrollPosition");if(t.dataset.scrollSpeed&&!isNaN(parseFloat(t.dataset.scrollSpeed)))return!0;i("scrollSpeed");for(let r of e)if(r in t.dataset)return!0;return!1}};var nn=class{constructor({lenisOptions:t={},triggerRootMargin:e,rafRootMargin:i,autoStart:r=!0,scrollCallback:s=()=>{},initCustomTicker:o,destroyCustomTicker:a}={}){this.lenisInstance=null;this.coreInstance=null;window.locomotiveScrollVersion=Nl,Object.assign(this,{lenisOptions:t,triggerRootMargin:e,rafRootMargin:i,autoStart:r,scrollCallback:s,initCustomTicker:o,destroyCustomTicker:a}),this._onRenderBind=this._onRender.bind(this),this._onScrollToBind=this._onScrollTo.bind(this),this._onResizeBind=this._onResize.bind(this),this.rafPlaying=!1,this.isTouchDevice="ontouchstart"in window||navigator.maxTouchPoints>0,this._init()}_init(){this.lenisInstance=new Yl(Mn({},this.lenisOptions)),this.scrollCallback&&this.lenisInstance.on("scroll",this.scrollCallback),document.documentElement.setAttribute("data-scroll-orientation",this.lenisInstance.options.orientation),requestAnimationFrame(()=>{this.coreInstance=new rn({$el:this.lenisInstance.rootElement,triggerRootMargin:this.triggerRootMargin,rafRootMargin:this.rafRootMargin,scrollOrientation:this.lenisInstance.options.orientation,lenisInstance:this.lenisInstance}),this._bindEvents(),this.initCustomTicker&&!this.destroyCustomTicker?console.warn("initCustomTicker callback is declared, but destroyCustomTicker is not. Please pay attention. It could cause trouble."):!this.initCustomTicker&&this.destroyCustomTicker&&console.warn("destroyCustomTicker callback is declared, but initCustomTicker is not. Please pay attention. It could cause trouble."),this.autoStart&&this.start()})}destroy(){var t;this.stop(),this._unbindEvents(),(t=this.lenisInstance)==null||t.destroy(),requestAnimationFrame(()=>{var e;(e=this.coreInstance)==null||e.destroy()})}_bindEvents(){this._bindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize=this.lenisInstance.dimensions.onContentResize.bind(this.lenisInstance.dimensions),this._originalOnWrapperResize=this.lenisInstance.dimensions.onWrapperResize.bind(this.lenisInstance.dimensions),this.lenisInstance.dimensions.onContentResize=()=>{var t;(t=this._originalOnContentResize)==null||t.call(this),this._onResizeBind()},this.lenisInstance.dimensions.onWrapperResize=()=>{var t;(t=this._originalOnWrapperResize)==null||t.call(this),this._onResizeBind()})}_unbindEvents(){this._unbindScrollToEvents(),this.lenisInstance&&(this._originalOnContentResize&&(this.lenisInstance.dimensions.onContentResize=this._originalOnContentResize),this._originalOnWrapperResize&&(this.lenisInstance.dimensions.onWrapperResize=this._originalOnWrapperResize))}_bindScrollToEvents(t){var r;let e=t||((r=this.lenisInstance)==null?void 0:r.rootElement),i=e==null?void 0:e.querySelectorAll("[data-scroll-to]");i!=null&&i.length&&i.forEach(s=>{s.addEventListener("click",this._onScrollToBind,!1)})}_unbindScrollToEvents(t){var r;let e=t||((r=this.lenisInstance)==null?void 0:r.rootElement),i=e==null?void 0:e.querySelectorAll("[data-scroll-to]");i!=null&&i.length&&i.forEach(s=>{s.removeEventListener("click",this._onScrollToBind,!1)})}_onResize(){var t,e,i;(i=this.coreInstance)==null||i.onResize({currentScroll:(e=(t=this.lenisInstance)==null?void 0:t.scroll)!=null?e:0,smooth:!this.isTouchDevice})}_onRender(){var t,e,i,r;(t=this.lenisInstance)==null||t.raf(Date.now()),(r=this.coreInstance)==null||r.onRender({currentScroll:(i=(e=this.lenisInstance)==null?void 0:e.scroll)!=null?i:0,smooth:!this.isTouchDevice})}_onScrollTo(t){var o,a;t.preventDefault();let e=(o=t.currentTarget)!=null?o:null;if(!e)return;let i=e.getAttribute("data-scroll-to-href")||e.getAttribute("href"),r=e.getAttribute("data-scroll-to-offset")||0,s=e.getAttribute("data-scroll-to-duration")||((a=this.lenisInstance)==null?void 0:a.options.duration);i&&this.scrollTo(i,{offset:typeof r=="string"?parseInt(r):r,duration:typeof s=="string"?parseInt(s):s})}start(){var t;this.rafPlaying||((t=this.lenisInstance)==null||t.start(),this.rafPlaying=!0,this.initCustomTicker?this.initCustomTicker(this._onRenderBind):this._raf())}stop(){var t;this.rafPlaying&&((t=this.lenisInstance)==null||t.stop(),this.rafPlaying=!1,this.destroyCustomTicker?this.destroyCustomTicker(this._onRenderBind):this.rafInstance&&cancelAnimationFrame(this.rafInstance))}removeScrollElements(t){var e;if(!t){console.error("Please provide a DOM Element as $oldContainer");return}this._unbindScrollToEvents(t),(e=this.coreInstance)==null||e.removeScrollElements(t)}addScrollElements(t){var e;if(!t){console.error("Please provide a DOM Element as $newContainer");return}(e=this.coreInstance)==null||e.addScrollElements(t),requestAnimationFrame(()=>{this._bindScrollToEvents(t)})}resize(){this._onResizeBind()}scrollTo(t,e){var i;(i=this.lenisInstance)==null||i.scrollTo(t,{offset:e==null?void 0:e.offset,lerp:e==null?void 0:e.lerp,duration:e==null?void 0:e.duration,immediate:e==null?void 0:e.immediate,lock:e==null?void 0:e.lock,force:e==null?void 0:e.force,easing:e==null?void 0:e.easing,onComplete:e==null?void 0:e.onComplete})}_raf(){this._onRenderBind(),this.rafInstance=requestAnimationFrame(()=>this._raf())}};var sn=class extends de{constructor(t){super(t),this.onScrollBind=this.onScroll.bind(this),this.changeHeaderThemeBind=this.changeHeaderTheme.bind(this)}init(){this.scroll=new nn({modularInstance:this,scrollCallback:this.onScrollBind}),history.scrollRestoration&&(history.scrollRestoration="manual",window.scrollTo(0,0)),this.bindEvents()}destroy(){this.unbindEvents(),this.scroll.destroy()}bindEvents(){window.addEventListener("changeHeaderTheme",this.changeHeaderThemeBind)}unbindEvents(){window.removeEventListener("changeHeaderTheme",this.changeHeaderThemeBind)}onScroll({scroll:t,limit:e,velocity:i,direction:r,progress:s}){s>this.lastProgress?this.scrollDirection!=1&&(this.scrollDirection=1):this.scrollDirection!=-1&&(this.scrollDirection=-1),this.scrollDirection<0?Ve.classList.add("is-scrolling-up"):Ve.classList.remove("is-scrolling-up"),this.lastProgress=s,this.call("onScroll",{velocity:i,direction:r},"Rail")}scrollTo(t){var s;let r=t,{target:e}=r,i=Ns(r,["target"]);i=Object.assign({duration:1},i),(s=this.scroll)==null||s.scrollTo(e,i)}changeHeaderTheme(t){var r;let{target:e,way:i}=t.detail;if(i=="enter"){let s=(r=e==null?void 0:e.parentNode)==null?void 0:r.getAttribute("data-theme");s&&Ve.setAttribute("data-header-theme",s)}}addScrollElements(t){var e;(e=this.scroll)==null||e.addScrollElements(t)}removeScrollElements(t){var e;(e=this.scroll)==null||e.removeScrollElements(t)}};var Hf=/(?:^\s+|\s+$)/g,Wf=/([\uD800-\uDBFF][\uDC00-\uDFFF](?:[\u200D\uFE0F][\uD800-\uDBFF][\uDC00-\uDFFF]){2,}|\uD83D\uDC69(?:\u200D(?:(?:\uD83D\uDC69\u200D)?\uD83D\uDC67|(?:\uD83D\uDC69\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2642\u2640]\uFE0F|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDD27\uDCBC\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC6F\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3C-\uDD3E\uDDD6-\uDDDF])\u200D[\u2640\u2642]\uFE0F|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|(?:\u26F9|\uD83C[\uDFCC\uDFCB]|\uD83D\uDD75)(?:\uFE0F\u200D[\u2640\u2642]|(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642])\uFE0F|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\uD83D\uDC69\u200D[\u2695\u2696\u2708]|\uD83D\uDC68(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708]))\uFE0F|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83D\uDC69\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC68(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]))|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDD1-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\u200D(?:(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F)/;function ns(n){var t=n.nodeType,e="";if(t===1||t===9||t===11){if(typeof n.textContent=="string")return n.textContent;for(n=n.firstChild;n;n=n.nextSibling)e+=ns(n)}else if(t===3||t===4)return n.nodeValue;return e}function Ue(n,t,e,i,r){if(n+="",e&&(n=n.trim?n.trim():n.replace(Hf,"")),t&&t!=="")return n.replace(/>/g,">").replace(/=55296&&u.charCodeAt(0)<=56319||n.charCodeAt(a+1)>=65024&&n.charCodeAt(a+1)<=65039)&&(l=((n.substr(a,12).split(Wf)||[])[1]||"").length||2,u=n.substr(a,l),s.emoji=1,a+=l-1),s.push(r?u:u===">"?">":u==="<"?"<":i&&u===" "&&(n.charAt(a-1)===" "||n.charAt(a+1)===" ")?" ":u);return s}var ss=function(){function n(e){this.chars=Ue(e),this.sets=[],this.length=50;for(var i=0;i<20;i++)this.sets[i]=Gl(80,this.chars)}var t=n.prototype;return t.grow=function(i){for(var r=0;r<20;r++)this.sets[r]+=Gl(i-this.length,this.chars);this.length=i},n}(),Zi,Zl,Jl=function(){return Zi||typeof window!="undefined"&&(Zi=window.gsap)&&Zi.registerPlugin&&Zi},Vf=1,ql=/\s+/g,Gl=function(t,e){for(var i=e.length,r="";--t>-1;)r+=e[~~(Math.random()*i)];return r},Bo="ABCDEFGHIJKLMNOPQRSTUVWXYZ",Kl=Bo.toLowerCase(),Uf={upperCase:new ss(Bo),lowerCase:new ss(Kl),upperAndLowerCase:new ss(Bo+Kl)},Ql=function(){Zl=Zi=Jl()},on={version:"3.14.2",name:"scrambleText",register:function(t,e,i){Zi=t,Ql()},init:function(t,e,i,r,s){if(Zl||Ql(),this.prop="innerHTML"in t?"innerHTML":"textContent"in t?"textContent":0,!!this.prop){this.target=t,typeof e!="object"&&(e={text:e});var o=e.text||e.value||"",a=e.trim!==!1,l=this,u,c,f,d;return l.delimiter=u=e.delimiter||"",l.original=Ue(ns(t).replace(ql," ").split(" ").join(""),u,a),(o==="{original}"||o===!0||o==null)&&(o=l.original.join(u)),l.text=Ue((o||"").replace(ql," "),u,a),l.hasClass=!!(e.newClass||e.oldClass),l.newClass=e.newClass,l.oldClass=e.oldClass,d=u==="",l.textHasEmoji=d&&!!l.text.emoji,l.charsHaveEmoji=!!e.chars&&!!Ue(e.chars).emoji,l.length=d?l.original.length:l.original.join(u).length,l.lengthDif=(d?l.text.length:l.text.join(u).length)-l.length,l.fillChar=e.fillChar||e.chars&&~e.chars.indexOf(" ")?" ":"",l.charSet=f=Uf[e.chars||"upperCase"]||new ss(e.chars),l.speed=.05/(e.speed||1),l.prevScrambleTime=0,l.setIndex=Math.random()*20|0,c=l.length+Math.max(l.lengthDif,0),c>f.length&&f.grow(c),l.chars=f.sets[l.setIndex],l.revealDelay=e.revealDelay||0,l.tweenLength=e.tweenLength!==!1,l.tween=i,l.rightToLeft=!!e.rightToLeft,l._props.push("scrambleText","text"),Vf}},render:function(t,e){var i=e.target,r=e.prop,s=e.text,o=e.delimiter,a=e.tween,l=e.prevScrambleTime,u=e.revealDelay,c=e.setIndex,f=e.chars,d=e.charSet,h=e.length,_=e.textHasEmoji,p=e.charsHaveEmoji,g=e.lengthDif,E=e.tweenLength,y=e.oldClass,S=e.newClass,D=e.rightToLeft,v=e.fillChar,T=e.speed,b=e.original,x=e.hasClass,w=s.length,F=a._time,B=F-l,A,P,L,M,H,R,z,V,m,j,Z;u&&(a._from&&(F=a._dur-F),t=F===0?0:F1&&(t=1),D&&(t=1-t),A=~~(t*w+.5),t?((B>T||B<-T)&&(e.setIndex=c=(c+(Math.random()*19|0))%20,e.chars=d.sets[c],e.prevScrambleTime+=B),M=f):M=b.join(o),Z=a._from?t:1-t,j=h+(E?a._from?Z*Z*Z:1-Z*Z*Z:1)*g,D?t===1&&(a._from||a.data==="isFromStart")?(L="",M=b.join(o)):(z=s.slice(A).join(o),p?L=Ue(M).slice(0,j-(_?Ue(z):z).length+.5|0).join(""):L=M.substr(0,j-(_?Ue(z):z).length+.5|0),M=z):(L=s.slice(0,A).join(o),P=(_?Ue(L):L).length,p?M=Ue(M).slice(P,j+.5|0).join(""):M=M.substr(P,j-P+.5|0)),x?(V=D?y:S,m=D?S:y,H=V&&A!==0,R=m&&A!==w,z=(H?"":"")+L+(H?"":"")+(R?"":"")+o+M+(R?"":"")):z=L+o+M,i[r]=v===" "&&~z.indexOf(" ")?z.split(" ").join("  "):z}};on.emojiSafeSplit=Ue;on.getText=ns;Jl()&&Zi.registerPlugin(on);_t.registerPlugin(on);var an=class extends de{constructor(t){super(t),this.inViewBind=this.inView.bind(this)}init(){this.bindEvents()}destroy(){this.unbindEvents()}bindEvents(){window.addEventListener("randomize",this.inViewBind)}unbindEvents(){window.removeEventListener("randomize",this.inViewBind)}inView(t){let{target:e}=t.detail;[...e.querySelectorAll("p")].forEach((r,s)=>{_t.to(r,{opacity:1,duration:0,delay:.1*s}),_t.to(r,{scrambleText:r.innerHTML,duration:1.5,delay:.1*s})})}};var ln,Tr,tu=typeof Symbol=="function"?Symbol():"_split",No,Yf=()=>No||Ji.register(window.gsap),eu=typeof Intl!="undefined"&&"Segmenter"in Intl?new Intl.Segmenter:0,un=n=>typeof n=="string"?un(document.querySelectorAll(n)):"length"in n?Array.from(n).reduce((t,e)=>(typeof e=="string"?t.push(...un(e)):t.push(e),t),[]):[n],iu=n=>un(n).filter(t=>t instanceof HTMLElement),Ho=[],zo=function(){},$f={add:n=>n()},jf=/\s+/g,ru=new RegExp("\\p{RI}\\p{RI}|\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?(\\u{200D}\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?)*|.","gu"),os={left:0,top:0,width:0,height:0},Xf=(n,t)=>{for(;++t{n.innerHTML=t,e?n.setAttribute("aria-label",e):n.removeAttribute("aria-label"),i?n.setAttribute("aria-hidden",i):n.removeAttribute("aria-hidden")},su=(n,t)=>{if(t){let e=new Set(n.join("").match(t)||Ho),i=n.length,r,s,o,a;if(e.size)for(;--i>-1;){s=n[i];for(o of e)if(o.startsWith(s)&&o.length>s.length){for(r=0,a=s;o.startsWith(a+=n[i+ ++r])&&a.lengthwindow.getComputedStyle(n).display==="inline"&&(n.style.display="inline-block"),xr=(n,t,e)=>t.insertBefore(typeof n=="string"?document.createTextNode(n):n,e),Wo=(n,t,e)=>{let i=t[n+"sClass"]||"",{tag:r="div",aria:s="auto",propIndex:o=!1}=t,a=n==="line"?"block":"inline-block",l=i.indexOf("++")>-1,u=c=>{let f=document.createElement(r),d=e.length+1;return i&&(f.className=i+(l?" "+i+d:"")),o&&f.style.setProperty("--"+n,d+""),s!=="none"&&f.setAttribute("aria-hidden","true"),r!=="span"&&(f.style.position="relative",f.style.display=a),f.textContent=c,e.push(f),f};return l&&(i=i.replace("++","")),u.collection=e,u},qf=(n,t,e,i)=>{let r=Wo("line",e,i),s=window.getComputedStyle(n).textAlign||"left";return(o,a)=>{let l=r("");for(l.style.textAlign=s,n.insertBefore(l,t[o]);o{var c;let f=Array.from(n.childNodes),d=0,{wordDelimiter:h,reduceWhiteSpace:_=!0,prepareText:p}=t,g=n.getBoundingClientRect(),E=g,y=!_&&window.getComputedStyle(n).whiteSpace.substring(0,3)==="pre",S=0,D=e.collection,v,T,b,x,w,F,B,A,P,L,M,H,R,z,V,m,j,Z;for(typeof h=="object"?(b=h.delimiter||h,T=h.replaceWith||""):T=h===""?"":h||" ",v=T!==" ";d-1?(F=D[D.length-1],F.appendChild(document.createTextNode(i?"":m))):(F=e(i?"":m),xr(F,n,x),S&&P===1&&!B&&F.insertBefore(S,F.firstChild)),i)for(M=eu?su([...eu.segment(m)].map(Rt=>Rt.segment),l):m.match(a)||Ho,Z=0;ZE.top&&L.left<=E.left){for(H=n.cloneNode(),R=n.childNodes[0];R&&R!==F;)z=R,R=R.nextSibling,H.appendChild(z);n.parentNode.insertBefore(H,n),r&&ou(H)}E=L}(P=w.length?" ":v&&m.slice(-1)===" "?" "+T:T,n,x)}n.removeChild(x),S=0}else x.nodeType===1&&(o&&o.indexOf(x)>-1?(D.indexOf(x.previousSibling)>-1&&D[D.length-1].appendChild(x),S=x):(au(x,t,e,i,r,s,o,a,l,!0),S=0),r&&ou(x))},lu=class uu{constructor(t,e){this.isSplit=!1,Yf(),this.elements=iu(t),this.chars=[],this.words=[],this.lines=[],this.masks=[],this.vars=e,this.elements.forEach(o=>{var a;e.overwrite!==!1&&((a=o[tu])==null||a._data.orig.filter(({element:l})=>l===o).forEach(nu)),o[tu]=this}),this._split=()=>this.isSplit&&this.split(this.vars);let i=[],r,s=()=>{let o=i.length,a;for(;o--;){a=i[o];let l=a.element.offsetWidth;if(l!==a.width){a.width=l,this._split();return}}};this._data={orig:i,obs:typeof ResizeObserver!="undefined"&&new ResizeObserver(()=>{clearTimeout(r),r=setTimeout(s,200)})},zo(this),this.split(e)}split(t){return(this._ctx||$f).add(()=>{this.isSplit&&this.revert(),this.vars=t=t||this.vars||{};let{type:e="chars,words,lines",aria:i="auto",deepSlice:r=!0,smartWrap:s,onSplit:o,autoSplit:a=!1,specialChars:l,mask:u}=this.vars,c=e.indexOf("lines")>-1,f=e.indexOf("chars")>-1,d=e.indexOf("words")>-1,h=f&&!d&&!c,_=l&&("push"in l?new RegExp("(?:"+l.join("|")+")","gu"):l),p=_?new RegExp(_.source+"|"+ru.source,"gu"):ru,g=!!t.ignore&&iu(t.ignore),{orig:E,animTime:y,obs:S}=this._data,D;(f||d||c)&&(this.elements.forEach((v,T)=>{E[T]={element:v,html:v.innerHTML,ariaL:v.getAttribute("aria-label"),ariaH:v.getAttribute("aria-hidden")},i==="auto"?v.setAttribute("aria-label",(v.textContent||"").trim()):i==="hidden"&&v.setAttribute("aria-hidden","true");let b=[],x=[],w=[],F=f?Wo("char",t,b):null,B=Wo("word",t,x),A,P,L,M;if(au(v,t,B,F,h,r&&(c||h),g,p,_,!1),c){let H=un(v.childNodes),R=qf(v,H,t,w),z,V=[],m=0,j=H.map(nt=>nt.nodeType===1?nt.getBoundingClientRect():os),Z=os,Rt;for(A=0;AZ.top&&Rt.left{var St;return(St=nt.parentNode)==null?void 0:St.removeChild(nt)})}if(!d){for(A=0;A{let T=v.cloneNode();return v.replaceWith(T),T.appendChild(v),v.className&&(T.className=v.className.trim()+"-mask"),T.style.overflow="clip",T}))),this.isSplit=!0,Tr&&c&&(a?Tr.addEventListener("loadingdone",this._split):Tr.status==="loading"&&console.warn("SplitText called before fonts loaded")),(D=o&&o(this))&&D.totalTime&&(this._data.anim=y?D.totalTime(y):D),c&&a&&this.elements.forEach((v,T)=>{E[T].width=v.offsetWidth,S&&S.observe(v)})}),this}kill(){let{obs:t}=this._data;t&&t.disconnect(),Tr==null||Tr.removeEventListener("loadingdone",this._split)}revert(){var t,e;if(this.isSplit){let{orig:i,anim:r}=this._data;this.kill(),i.forEach(nu),this.chars.length=this.words.length=this.lines.length=i.length=this.masks.length=0,this.isSplit=!1,r&&(this._data.animTime=r.totalTime(),r.revert()),(e=(t=this.vars).onRevert)==null||e.call(t,this)}return this}static create(t,e){return new uu(t,e)}static register(t){ln=ln||t||window.gsap,ln&&(un=ln.utils.toArray,zo=ln.core.context||zo),!No&&window.innerWidth>0&&(Tr=document.fonts,No=!0)}};lu.version="3.14.2";var Ji=lu;function cu(n,t){for(var e=0;el?(s=r,r=_,a=o,o=g):i?r+=_:r=s+(_-s)/(g-a)*(o-a)},f=function(){s=r=i?0:r,a=o=0},d=function(_){var p=a,g=s,E=fn();return(_||_===0)&&_!==r&&c(_),o===a||E-a>u?0:(r+(i?g:-g))/((i?E:o)-p)*1e3};return{update:c,reset:f,getVelocity:d}},cn=function(t,e){return e&&!t._gsapAllow&&t.preventDefault(),t.changedTouches?t.changedTouches[0]:t},hu=function(t){var e=Math.max.apply(Math,t),i=Math.min.apply(Math,t);return Math.abs(e)>=Math.abs(i)?e:i},gu=function(){hn=Ut.core.globals().ScrollTrigger,hn&&hn.core&&Qf()},Du=function(t){return Ut=t||_u(),!us&&Ut&&typeof document!="undefined"&&document.body&&(Te=window,Pi=document,Ri=Pi.documentElement,Ar=Pi.body,du=[Te,Pi,Ri,Ar],Kf=Ut.utils.clamp,pu=Ut.core.context||function(){},tr="onpointerenter"in Ar?"pointer":"mouse",fu=yt.isTouch=Te.matchMedia&&Te.matchMedia("(hover: none), (pointer: coarse)").matches?1:"ontouchstart"in Te||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0?2:0,Ye=yt.eventTypes=("ontouchstart"in Ri?"touchstart,touchmove,touchcancel,touchend":"onpointerdown"in Ri?"pointerdown,pointermove,pointercancel,pointerup":"mousedown,mousemove,mouseup,mouseup").split(","),setTimeout(function(){return mu=0},500),gu(),us=1),us};Qt.op=Ft;X.cache=0;var yt=function(){function n(e){this.init(e)}var t=n.prototype;return t.init=function(i){us||Du(Ut)||console.warn("Please gsap.registerPlugin(Observer)"),hn||gu();var r=i.tolerance,s=i.dragMinimum,o=i.type,a=i.target,l=i.lineHeight,u=i.debounce,c=i.preventDefault,f=i.onStop,d=i.onStopDelay,h=i.ignore,_=i.wheelSpeed,p=i.event,g=i.onDragStart,E=i.onDragEnd,y=i.onDrag,S=i.onPress,D=i.onRelease,v=i.onRight,T=i.onLeft,b=i.onUp,x=i.onDown,w=i.onChangeX,F=i.onChangeY,B=i.onChange,A=i.onToggleX,P=i.onToggleY,L=i.onHover,M=i.onHoverEnd,H=i.onMove,R=i.ignoreCheck,z=i.isNormalizer,V=i.onGestureStart,m=i.onGestureEnd,j=i.onWheel,Z=i.onEnable,Rt=i.onDisable,nt=i.onClick,St=i.scrollSpeed,Yt=i.capture,Tt=i.allowClicks,ee=i.lockAxis,$t=i.onLockAxis;this.target=a=le(a)||Ri,this.vars=i,h&&(h=Ut.utils.toArray(h)),r=r||1e-9,s=s||0,_=_||1,St=St||1,o=o||"wheel,touch,pointer",u=u!==!1,l||(l=parseFloat(Te.getComputedStyle(Ar).lineHeight)||22);var mi,ie,re,et,bt,fe,ve,C=this,ye=0,ei=0,gi=i.passive||!c&&i.passive!==!1,mt=fi(a,Qt),ii=fi(a,Ft),Di=mt(),Ii=ii(),Mt=~o.indexOf("touch")&&!~o.indexOf("pointer")&&Ye[0]==="pointerdown",vi=dn(a),Et=a.ownerDocument||Pi,Be=[0,0,0],Oe=[0,0,0],ri=0,Br=function(){return ri=fn()},xt=function(W,it){return(C.event=W)&&h&&Zf(W.target,h)||it&&Mt&&W.pointerType!=="touch"||R&&R(W,it)},An=function(){C._vx.reset(),C._vy.reset(),ie.pause(),f&&f(C)},ni=function(){var W=C.deltaX=hu(Be),it=C.deltaY=hu(Oe),O=Math.abs(W)>=r,U=Math.abs(it)>=r;B&&(O||U)&&B(C,W,it,Be,Oe),O&&(v&&C.deltaX>0&&v(C),T&&C.deltaX<0&&T(C),w&&w(C),A&&C.deltaX<0!=ye<0&&A(C),ye=C.deltaX,Be[0]=Be[1]=Be[2]=0),U&&(x&&C.deltaY>0&&x(C),b&&C.deltaY<0&&b(C),F&&F(C),P&&C.deltaY<0!=ei<0&&P(C),ei=C.deltaY,Oe[0]=Oe[1]=Oe[2]=0),(et||re)&&(H&&H(C),re&&(g&&re===1&&g(C),y&&y(C),re=0),et=!1),fe&&!(fe=!1)&&$t&&$t(C),bt&&(j(C),bt=!1),mi=0},ur=function(W,it,O){Be[O]+=W,Oe[O]+=it,C._vx.update(W),C._vy.update(it),u?mi||(mi=requestAnimationFrame(ni)):ni()},cr=function(W,it){ee&&!ve&&(C.axis=ve=Math.abs(W)>Math.abs(it)?"x":"y",fe=!0),ve!=="y"&&(Be[2]+=W,C._vx.update(W,!0)),ve!=="x"&&(Oe[2]+=it,C._vy.update(it,!0)),u?mi||(mi=requestAnimationFrame(ni)):ni()},yi=function(W){if(!xt(W,1)){W=cn(W,c);var it=W.clientX,O=W.clientY,U=it-C.x,N=O-C.y,Y=C.isDragging;C.x=it,C.y=O,(Y||(U||N)&&(Math.abs(C.startX-it)>=s||Math.abs(C.startY-O)>=s))&&(re||(re=Y?2:1),Y||(C.isDragging=!0),cr(U,N))}},Li=C.onPress=function($){xt($,1)||$&&$.button||(C.axis=ve=null,ie.pause(),C.isPressed=!0,$=cn($),ye=ei=0,C.startX=C.x=$.clientX,C.startY=C.y=$.clientY,C._vx.reset(),C._vy.reset(),ae(z?a:Et,Ye[1],yi,gi,!0),C.deltaX=C.deltaY=0,S&&S(C))},Q=C.onRelease=function($){if(!xt($,1)){oe(z?a:Et,Ye[1],yi,!0);var W=!isNaN(C.y-C.startY),it=C.isDragging,O=it&&(Math.abs(C.x-C.startX)>3||Math.abs(C.y-C.startY)>3),U=cn($);!O&&W&&(C._vx.reset(),C._vy.reset(),c&&Tt&&Ut.delayedCall(.08,function(){if(fn()-ri>300&&!$.defaultPrevented){if($.target.click)$.target.click();else if(Et.createEvent){var N=Et.createEvent("MouseEvents");N.initMouseEvent("click",!0,!0,Te,1,U.screenX,U.screenY,U.clientX,U.clientY,!1,!1,!1,!1,0,null),$.target.dispatchEvent(N)}}})),C.isDragging=C.isGesturing=C.isPressed=!1,f&&it&&!z&&ie.restart(!0),re&&ni(),E&&it&&E(C),D&&D(C,O)}},Bi=function(W){return W.touches&&W.touches.length>1&&(C.isGesturing=!0)&&V(W,C.isDragging)},ze=function(){return(C.isGesturing=!1)||m(C)},Ne=function(W){if(!xt(W)){var it=mt(),O=ii();ur((it-Di)*St,(O-Ii)*St,1),Di=it,Ii=O,f&&ie.restart(!0)}},He=function(W){if(!xt(W)){W=cn(W,c),j&&(bt=!0);var it=(W.deltaMode===1?l:W.deltaMode===2?Te.innerHeight:1)*_;ur(W.deltaX*it,W.deltaY*it,0),f&&!z&&ie.restart(!0)}},zi=function(W){if(!xt(W)){var it=W.clientX,O=W.clientY,U=it-C.x,N=O-C.y;C.x=it,C.y=O,et=!0,f&&ie.restart(!0),(U||N)&&cr(U,N)}},hr=function(W){C.event=W,L(C)},si=function(W){C.event=W,M(C)},zr=function(W){return xt(W)||cn(W,c)&&nt(C)};ie=C._dc=Ut.delayedCall(d||.25,An).pause(),C.deltaX=C.deltaY=0,C._vx=hs(0,50,!0),C._vy=hs(0,50,!0),C.scrollX=mt,C.scrollY=ii,C.isDragging=C.isGesturing=C.isPressed=!1,pu(this),C.enable=function($){return C.isEnabled||(ae(vi?Et:a,"scroll",Uo),o.indexOf("scroll")>=0&&ae(vi?Et:a,"scroll",Ne,gi,Yt),o.indexOf("wheel")>=0&&ae(a,"wheel",He,gi,Yt),(o.indexOf("touch")>=0&&fu||o.indexOf("pointer")>=0)&&(ae(a,Ye[0],Li,gi,Yt),ae(Et,Ye[2],Q),ae(Et,Ye[3],Q),Tt&&ae(a,"click",Br,!0,!0),nt&&ae(a,"click",zr),V&&ae(Et,"gesturestart",Bi),m&&ae(Et,"gestureend",ze),L&&ae(a,tr+"enter",hr),M&&ae(a,tr+"leave",si),H&&ae(a,tr+"move",zi)),C.isEnabled=!0,C.isDragging=C.isGesturing=C.isPressed=et=re=!1,C._vx.reset(),C._vy.reset(),Di=mt(),Ii=ii(),$&&$.type&&Li($),Z&&Z(C)),C},C.disable=function(){C.isEnabled&&(Fr.filter(function($){return $!==C&&dn($.target)}).length||oe(vi?Et:a,"scroll",Uo),C.isPressed&&(C._vx.reset(),C._vy.reset(),oe(z?a:Et,Ye[1],yi,!0)),oe(vi?Et:a,"scroll",Ne,Yt),oe(a,"wheel",He,Yt),oe(a,Ye[0],Li,Yt),oe(Et,Ye[2],Q),oe(Et,Ye[3],Q),oe(a,"click",Br,!0),oe(a,"click",zr),oe(Et,"gesturestart",Bi),oe(Et,"gestureend",ze),oe(a,tr+"enter",hr),oe(a,tr+"leave",si),oe(a,tr+"move",zi),C.isEnabled=C.isPressed=C.isDragging=!1,Rt&&Rt(C))},C.kill=C.revert=function(){C.disable();var $=Fr.indexOf(C);$>=0&&Fr.splice($,1),hi===C&&(hi=0)},Fr.push(C),z&&dn(a)&&(hi=C),C.enable(p)},Gf(n,[{key:"velocityX",get:function(){return this._vx.getVelocity()}},{key:"velocityY",get:function(){return this._vy.getVelocity()}}]),n}();yt.version="3.14.2";yt.create=function(n){return new yt(n)};yt.register=Du;yt.getAll=function(){return Fr.slice()};yt.getById=function(n){return Fr.filter(function(t){return t.vars.id===n})[0]};_u()&&Ut.registerPlugin(yt);var k,Pr,G,lt,Ae,rt,na,xs,xn,yn,_n,fs,Zt,Os,Qo,ce,vu,yu,Rr,Lu,Yo,Bu,ue,Zo,zu,Nu,Mi,Jo,sa,Mr,oa,bn,ta,$o,ds=1,Jt=Date.now,jo=Jt(),Le=0,mn=0,bu=function(t,e,i){var r=Fe(t)&&(t.substr(0,6)==="clamp("||t.indexOf("max")>-1);return i["_"+e+"Clamp"]=r,r?t.substr(6,t.length-7):t},Eu=function(t,e){return e&&(!Fe(t)||t.substr(0,6)!=="clamp(")?"clamp("+t+")":t},Jf=function n(){return mn&&requestAnimationFrame(n)},wu=function(){return Os=1},Cu=function(){return Os=0},Je=function(t){return t},gn=function(t){return Math.round(t*1e5)/1e5||0},Hu=function(){return typeof window!="undefined"},Wu=function(){return k||Hu()&&(k=window.gsap)&&k.registerPlugin&&k},or=function(t){return!!~na.indexOf(t)},Vu=function(t){return(t==="Height"?oa:G["inner"+t])||Ae["client"+t]||rt["client"+t]},Uu=function(t){return di(t,"getBoundingClientRect")||(or(t)?function(){return Ts.width=G.innerWidth,Ts.height=oa,Ts}:function(){return pi(t)})},td=function(t,e,i){var r=i.d,s=i.d2,o=i.a;return(o=di(t,"getBoundingClientRect"))?function(){return o()[r]}:function(){return(e?Vu(s):t["client"+s])||0}},ed=function(t,e){return!e||~$e.indexOf(t)?Uu(t):function(){return Ts}},ti=function(t,e){var i=e.s,r=e.d2,s=e.d,o=e.a;return Math.max(0,(i="scroll"+r)&&(o=di(t,i))?o()-Uu(t)()[s]:or(t)?(Ae[i]||rt[i])-Vu(r):t[i]-t["offset"+r])},ps=function(t,e){for(var i=0;i0){for(r-=o,a=0;a=r)return i[a];return i[a-1]}else for(a=i.length,r+=o;a--;)if(i[a]<=r)return i[a];return i[0]}:function(r,s,o){o===void 0&&(o=.001);var a=e(r);return!s||Math.abs(a-r)i&&(r*=e/100),t=t.substr(0,i-1)),t=r+(t in As?As[t]*e:~t.indexOf("%")?parseFloat(t)*e/100:parseFloat(t)||0)}return t},Ds=function(t,e,i,r,s,o,a,l){var u=s.startColor,c=s.endColor,f=s.fontSize,d=s.indent,h=s.fontWeight,_=lt.createElement("div"),p=or(i)||di(i,"pinType")==="fixed",g=t.indexOf("scroller")!==-1,E=p?rt:i,y=t.indexOf("start")!==-1,S=y?u:c,D="border-color:"+S+";font-size:"+f+";color:"+S+";font-weight:"+h+";pointer-events:none;white-space:nowrap;font-family:sans-serif,Arial;z-index:1000;padding:4px 8px;border-width:0;border-style:solid;";return D+="position:"+((g||l)&&p?"fixed;":"absolute;"),(g||l||!p)&&(D+=(r===Ft?aa:la)+":"+(o+parseFloat(d))+"px;"),a&&(D+="box-sizing:border-box;text-align:left;width:"+a.offsetWidth+"px;"),_._isStart=y,_.setAttribute("class","gsap-marker-"+t+(e?" marker-"+e:"")),_.style.cssText=D,_.innerText=e||e===0?t+"-"+e:t,E.children[0]?E.insertBefore(_,E.children[0]):E.appendChild(_),_._offset=_["offset"+r.op.d2],ws(_,0,r,y),_},ws=function(t,e,i,r){var s={display:"block"},o=i[r?"os2":"p2"],a=i[r?"p2":"os2"];t._isFlipped=r,s[i.a+"Percent"]=r?-100:0,s[i.a]=r?"1px":0,s["border"+o+Lr]=1,s["border"+a+Lr]=0,s[i.p]=e+"px",k.set(t,s)},q=[],ea={},Fn,xu=function(){return Jt()-Le>34&&(Fn||(Fn=requestAnimationFrame(_i)))},kr=function(){(!ue||!ue.isPressed||ue.startX>rt.clientWidth)&&(X.cache++,ue?Fn||(Fn=requestAnimationFrame(_i)):_i(),Le||lr("scrollStart"),Le=Jt())},qo=function(){Nu=G.innerWidth,zu=G.innerHeight},vn=function(t){X.cache++,(t===!0||!Zt&&!Bu&&!lt.fullscreenElement&&!lt.webkitFullscreenElement&&(!Zo||Nu!==G.innerWidth||Math.abs(G.innerHeight-zu)>G.innerHeight*.25))&&xs.restart(!0)},ar={},sd=[],Xu=function n(){return Lt(K,"scrollEnd",n)||ir(!0)},lr=function(t){return ar[t]&&ar[t].map(function(e){return e()})||sd},xe=[],qu=function(t){for(var e=0;es,a=r._startClamp&&r.start>=s;(o||a)&&r.setPositions(a?s-1:r.start,o?Math.max(a?s:r.start+1,s):r.end,!0)}),Au(!1),ta=0,i.forEach(function(r){return r&&r.render&&r.render(-1)}),X.forEach(function(r){te(r)&&(r.smooth&&requestAnimationFrame(function(){return r.target.style.scrollBehavior="smooth"}),r.rec&&r(r.rec))}),Ku(sa,1),xs.pause(),sr++,he=2,_i(2),q.forEach(function(r){return te(r.vars.onRefresh)&&r.vars.onRefresh(r)}),he=K.isRefreshing=!1,lr("refresh")},ia=0,Cs=1,Tn,_i=function(t){if(t===2||!he&&!bn){K.isUpdating=!0,Tn&&Tn.update(0);var e=q.length,i=Jt(),r=i-jo>=50,s=e&&q[0].scroll();if(Cs=ia>s?-1:1,he||(ia=s),r&&(Le&&!Os&&i-Le>200&&(Le=0,lr("scrollEnd")),_n=jo,jo=i),Cs<0){for(ce=e;ce-- >0;)q[ce]&&q[ce].update(0,r);Cs=1}else for(ce=0;ce20),i-=i-T}if(_&&(l[_]=t||-.001,t<0&&(t=0)),o){var x=t+i,w=o._isStart;g="scroll"+r.d2,ws(o,x,r,w&&x>20||!w&&(f?Math.max(rt[g],Ae[g]):o.parentNode[g])<=x+1),f&&(u=pi(a),f&&(o.style[r.op.p]=u[r.op.p]-r.op.m-o._offset+Pt))}return h&&y&&(g=pi(y),h.seek(d),E=pi(y),h._caScrollDist=g[r.p]-E[r.p],t=t/h._caScrollDist*d),h&&h.seek(p),h?t:Math.round(t)},cd=/(webkit|moz|length|cssText|inset)/i,ku=function(t,e,i,r){if(t.parentNode!==e){var s=t.style,o,a;if(e===rt){t._stOrig=s.cssText,a=Ie(t);for(o in a)!+o&&!cd.test(o)&&a[o]&&typeof s[o]=="string"&&o!=="0"&&(s[o]=a[o]);s.top=i,s.left=r}else s.cssText=t._stOrig;k.core.getCache(t).uncache=1,e.appendChild(t)}},Zu=function(t,e,i){var r=e,s=r;return function(o){var a=Math.round(t());return a!==r&&a!==s&&Math.abs(a-r)>3&&Math.abs(a-s)>3&&(o=a,i&&i()),s=r,r=Math.round(o),r}},ys=function(t,e,i){var r={};r[e.p]="+="+i,k.set(t,r)},Pu=function(t,e){var i=fi(t,e),r="_scroll"+e.p2,s=function o(a,l,u,c,f){var d=o.tween,h=l.onComplete,_={};u=u||i();var p=Zu(i,u,function(){d.kill(),o.tween=0});return f=c&&f||0,c=c||a-u,d&&d.kill(),l[r]=a,l.inherit=!1,l.modifiers=_,_[r]=function(){return p(u+c*d.ratio+f*d.ratio*d.ratio)},l.onUpdate=function(){X.cache++,o.tween&&_i()},l.onComplete=function(){o.tween=0,h&&h.call(d)},d=o.tween=k.to(t,l),d};return t[r]=i,i.wheelHandler=function(){return s.tween&&s.tween.kill()&&(s.tween=0)},Bt(t,"wheel",i.wheelHandler),K.isTouch&&Bt(t,"touchmove",i.wheelHandler),s},K=function(){function n(e,i){Pr||n.register(k)||console.warn("Please gsap.registerPlugin(ScrollTrigger)"),Jo(this),this.init(e,i)}var t=n.prototype;return t.init=function(i,r){if(this.progress=this.start=0,this.vars&&this.kill(!0,!0),!mn){this.update=this.refresh=this.kill=Je;return}i=Su(Fe(i)||Dn(i)||i.nodeType?{trigger:i}:i,gs);var s=i,o=s.onUpdate,a=s.toggleClass,l=s.id,u=s.onToggle,c=s.onRefresh,f=s.scrub,d=s.trigger,h=s.pin,_=s.pinSpacing,p=s.invalidateOnRefresh,g=s.anticipatePin,E=s.onScrubComplete,y=s.onSnapComplete,S=s.once,D=s.snap,v=s.pinReparent,T=s.pinSpacer,b=s.containerAnimation,x=s.fastScrollEnd,w=s.preventOverlaps,F=i.horizontal||i.containerAnimation&&i.horizontal!==!1?Qt:Ft,B=!f&&f!==0,A=le(i.scroller||G),P=k.core.getCache(A),L=or(A),M=("pinType"in i?i.pinType:di(A,"pinType")||L&&"fixed")==="fixed",H=[i.onEnter,i.onLeave,i.onEnterBack,i.onLeaveBack],R=B&&i.toggleActions.split(" "),z="markers"in i?i.markers:gs.markers,V=L?0:parseFloat(Ie(A)["border"+F.p2+Lr])||0,m=this,j=i.onRefreshInit&&function(){return i.onRefreshInit(m)},Z=td(A,L,F),Rt=ed(A,L),nt=0,St=0,Yt=0,Tt=fi(A,F),ee,$t,mi,ie,re,et,bt,fe,ve,C,ye,ei,gi,mt,ii,Di,Ii,Mt,vi,Et,Be,Oe,ri,Br,xt,An,ni,ur,cr,yi,Li,Q,Bi,ze,Ne,He,zi,hr,si;if(m._startClamp=m._endClamp=!1,m._dir=F,g*=45,m.scroller=A,m.scroll=b?b.time.bind(b):Tt,ie=Tt(),m.vars=i,r=r||i.animation,"refreshPriority"in i&&(Lu=1,i.refreshPriority===-9999&&(Tn=m)),P.tweenScroll=P.tweenScroll||{top:Pu(A,Ft),left:Pu(A,Qt)},m.tweenTo=ee=P.tweenScroll[F.p],m.scrubDuration=function(O){Bi=Dn(O)&&O,Bi?Q?Q.duration(O):Q=k.to(r,{ease:"expo",totalProgress:"+=0",inherit:!1,duration:Bi,paused:!0,onComplete:function(){return E&&E(m)}}):(Q&&Q.progress(1).kill(),Q=0)},r&&(r.vars.lazy=!1,r._initted&&!m.isReverted||r.vars.immediateRender!==!1&&i.immediateRender!==!1&&r.duration()&&r.render(0,!0,!0),m.animation=r.pause(),r.scrollTrigger=m,m.scrubDuration(f),yi=0,l||(l=r.vars.id)),D&&((!er(D)||D.push)&&(D={snapTo:D}),"scrollBehavior"in rt.style&&k.set(L?[rt,Ae]:A,{scrollBehavior:"auto"}),X.forEach(function(O){return te(O)&&O.target===(L?lt.scrollingElement||Ae:A)&&(O.smooth=!1)}),mi=te(D.snapTo)?D.snapTo:D.snapTo==="labels"?rd(r):D.snapTo==="labelsDirectional"?nd(r):D.directional!==!1?function(O,U){return ca(D.snapTo)(O,Jt()-St<500?0:U.direction)}:k.utils.snap(D.snapTo),ze=D.duration||{min:.1,max:2},ze=er(ze)?yn(ze.min,ze.max):yn(ze,ze),Ne=k.delayedCall(D.delay||Bi/2||.1,function(){var O=Tt(),U=Jt()-St<500,N=ee.tween;if((U||Math.abs(m.getVelocity())<10)&&!N&&!Os&&nt!==O){var Y=(O-et)/mt,It=r&&!B?r.totalProgress():Y,J=U?0:(It-Li)/(Jt()-_n)*1e3||0,wt=k.utils.clamp(-Y,1-Y,Or(J/2)*J/.185),jt=Y+(D.inertia===!1?0:wt),gt,ut,st=D,We=st.onStart,ht=st.onInterrupt,be=st.onComplete;if(gt=mi(jt,m),Dn(gt)||(gt=jt),ut=Math.max(0,Math.round(et+gt*mt)),O<=bt&&O>=et&&ut!==O){if(N&&!N._initted&&N.data<=Or(ut-O))return;D.inertia===!1&&(wt=gt-Y),ee(ut,{duration:ze(Or(Math.max(Or(jt-It),Or(gt-It))*.185/J/.05||0)),ease:D.ease||"power3",data:Or(ut-O),onInterrupt:function(){return Ne.restart(!0)&&ht&&ht(m)},onComplete:function(){m.update(),nt=Tt(),r&&!B&&(Q?Q.resetTo("totalProgress",gt,r._tTime/r._tDur):r.progress(gt)),yi=Li=r&&!B?r.totalProgress():m.progress,y&&y(m),be&&be(m)}},O,wt*mt,ut-O-wt*mt),We&&We(m,ee.tween)}}else m.isActive&&nt!==O&&Ne.restart(!0)}).pause()),l&&(ea[l]=m),d=m.trigger=le(d||h!==!0&&h),si=d&&d._gsap&&d._gsap.stRevert,si&&(si=si(m)),h=h===!0?d:le(h),Fe(a)&&(a={targets:d,className:a}),h&&(_===!1||_===Me||(_=!_&&h.parentNode&&h.parentNode.style&&Ie(h.parentNode).display==="flex"?!1:At),m.pin=h,$t=k.core.getCache(h),$t.spacer?ii=$t.pinState:(T&&(T=le(T),T&&!T.nodeType&&(T=T.current||T.nativeElement),$t.spacerIsNative=!!T,T&&($t.spacerState=vs(T))),$t.spacer=Mt=T||lt.createElement("div"),Mt.classList.add("pin-spacer"),l&&Mt.classList.add("pin-spacer-"+l),$t.pinState=ii=vs(h)),i.force3D!==!1&&k.set(h,{force3D:!0}),m.spacer=Mt=$t.spacer,cr=Ie(h),Br=cr[_+F.os2],Et=k.getProperty(h),Be=k.quickSetter(h,F.a,Pt),Go(h,Mt,cr),Ii=vs(h)),z){ei=er(z)?Su(z,Tu):Tu,C=Ds("scroller-start",l,A,F,ei,0),ye=Ds("scroller-end",l,A,F,ei,0,C),vi=C["offset"+F.op.d2];var zr=le(di(A,"content")||A);fe=this.markerStart=Ds("start",l,zr,F,ei,vi,0,b),ve=this.markerEnd=Ds("end",l,zr,F,ei,vi,0,b),b&&(hr=k.quickSetter([fe,ve],F.a,Pt)),!M&&!($e.length&&di(A,"fixedMarkers")===!0)&&(id(L?rt:A),k.set([C,ye],{force3D:!0}),An=k.quickSetter(C,F.a,Pt),ur=k.quickSetter(ye,F.a,Pt))}if(b){var $=b.vars.onUpdate,W=b.vars.onUpdateParams;b.eventCallback("onUpdate",function(){m.update(0,0,1),$&&$.apply(b,W||[])})}if(m.previous=function(){return q[q.indexOf(m)-1]},m.next=function(){return q[q.indexOf(m)+1]},m.revert=function(O,U){if(!U)return m.kill(!0);var N=O!==!1||!m.enabled,Y=Zt;N!==m.isReverted&&(N&&(He=Math.max(Tt(),m.scroll.rec||0),Yt=m.progress,zi=r&&r.progress()),fe&&[fe,ve,C,ye].forEach(function(It){return It.style.display=N?"none":"block"}),N&&(Zt=m,m.update(N)),h&&(!v||!m.isActive)&&(N?ad(h,Mt,ii):Go(h,Mt,Ie(h),xt)),N||m.update(N),Zt=Y,m.isReverted=N)},m.refresh=function(O,U,N,Y){if(!((Zt||!m.enabled)&&!U)){if(h&&O&&Le){Bt(n,"scrollEnd",Xu);return}!he&&j&&j(m),Zt=m,ee.tween&&!N&&(ee.tween.kill(),ee.tween=0),Q&&Q.pause(),p&&r&&(r.revert({kill:!1}).invalidate(),r.getChildren?r.getChildren(!0,!0,!1).forEach(function(bi){return bi.vars.immediateRender&&bi.render(0,!0,!0)}):r.vars.immediateRender&&r.render(0,!0,!0)),m.isReverted||m.revert(!0,!0),m._subPinOffset=!1;var It=Z(),J=Rt(),wt=b?b.duration():ti(A,F),jt=mt<=.01||!mt,gt=0,ut=Y||0,st=er(N)?N.end:i.end,We=i.endTrigger||d,ht=er(N)?N.start:i.start||(i.start===0||!d?0:h?"0 0":"0 100%"),be=m.pinnedContainer=i.pinnedContainer&&le(i.pinnedContainer,m),je=d&&Math.max(0,q.indexOf(m))||0,zt=je,Nt,Xt,Ni,On,qt,Ot,Xe,Ls,_a,Nr,qe,Hr,kn;for(z&&er(N)&&(Hr=k.getProperty(C,F.p),kn=k.getProperty(ye,F.p));zt-- >0;)Ot=q[zt],Ot.end||Ot.refresh(0,1)||(Zt=m),Xe=Ot.pin,Xe&&(Xe===d||Xe===h||Xe===be)&&!Ot.isReverted&&(Nr||(Nr=[]),Nr.unshift(Ot),Ot.revert(!0,!0)),Ot!==q[zt]&&(je--,zt--);for(te(ht)&&(ht=ht(m)),ht=bu(ht,"start",m),et=Ou(ht,d,It,F,Tt(),fe,C,m,J,V,M,wt,b,m._startClamp&&"_startClamp")||(h?-.001:0),te(st)&&(st=st(m)),Fe(st)&&!st.indexOf("+=")&&(~st.indexOf(" ")?st=(Fe(ht)?ht.split(" ")[0]:"")+st:(gt=Es(st.substr(2),It),st=Fe(ht)?ht:(b?k.utils.mapRange(0,b.duration(),b.scrollTrigger.start,b.scrollTrigger.end,et):et)+gt,We=d)),st=bu(st,"end",m),bt=Math.max(et,Ou(st||(We?"100% 0":wt),We,It,F,Tt()+gt,ve,ye,m,J,V,M,wt,b,m._endClamp&&"_endClamp"))||-.001,gt=0,zt=je;zt--;)Ot=q[zt]||{},Xe=Ot.pin,Xe&&Ot.start-Ot._pinPush<=et&&!b&&Ot.end>0&&(Nt=Ot.end-(m._startClamp?Math.max(0,Ot.start):Ot.start),(Xe===d&&Ot.start-Ot._pinPush=ti(A,F)))Nt=Ie(h),On=F===Ft,Ni=Tt(),Oe=parseFloat(Et(F.a))+ut,!wt&&bt>1&&(qe=(L?lt.scrollingElement||Ae:A).style,qe={style:qe,value:qe["overflow"+F.a.toUpperCase()]},L&&Ie(rt)["overflow"+F.a.toUpperCase()]!=="scroll"&&(qe.style["overflow"+F.a.toUpperCase()]="scroll")),Go(h,Mt,Nt),Ii=vs(h),Xt=pi(h,!0),Ls=M&&fi(A,On?Qt:Ft)(),_?(xt=[_+F.os2,mt+ut+Pt],xt.t=Mt,zt=_===At?Fs(h,F)+mt+ut:0,zt&&(xt.push(F.d,zt+Pt),Mt.style.flexBasis!=="auto"&&(Mt.style.flexBasis=zt+Pt)),Ir(xt),be&&q.forEach(function(bi){bi.pin===be&&bi.vars.pinSpacing!==!1&&(bi._subPinOffset=!0)}),M&&Tt(He)):(zt=Fs(h,F),zt&&Mt.style.flexBasis!=="auto"&&(Mt.style.flexBasis=zt+Pt)),M&&(qt={top:Xt.top+(On?Ni-et:Ls)+Pt,left:Xt.left+(On?Ls:Ni-et)+Pt,boxSizing:"border-box",position:"fixed"},qt[rr]=qt["max"+Lr]=Math.ceil(Xt.width)+Pt,qt[nr]=qt["max"+ua]=Math.ceil(Xt.height)+Pt,qt[Me]=qt[Me+Cn]=qt[Me+En]=qt[Me+Sn]=qt[Me+wn]="0",qt[At]=Nt[At],qt[At+Cn]=Nt[At+Cn],qt[At+En]=Nt[At+En],qt[At+Sn]=Nt[At+Sn],qt[At+wn]=Nt[At+wn],Di=ud(ii,qt,v),he&&Tt(0)),r?(_a=r._initted,Yo(1),r.render(r.duration(),!0,!0),ri=Et(F.a)-Oe+mt+ut,ni=Math.abs(mt-ri)>1,M&&ni&&Di.splice(Di.length-2,2),r.render(0,!0,!0),_a||r.invalidate(!0),r.parent||r.totalTime(r.totalTime()),Yo(0)):ri=mt,qe&&(qe.value?qe.style["overflow"+F.a.toUpperCase()]=qe.value:qe.style.removeProperty("overflow-"+F.a));else if(d&&Tt()&&!b)for(Xt=d.parentNode;Xt&&Xt!==rt;)Xt._pinOffset&&(et-=Xt._pinOffset,bt-=Xt._pinOffset),Xt=Xt.parentNode;Nr&&Nr.forEach(function(bi){return bi.revert(!1,!0)}),m.start=et,m.end=bt,ie=re=he?He:Tt(),!b&&!he&&(ie0?q.slice(0,U).reverse():q.slice(U+1);return(Fe(O)?N.filter(function(Y){return Y.vars.preventOverlaps===O}):N).filter(function(Y){return m.direction>0?Y.end<=et:Y.start>=bt})},m.update=function(O,U,N){if(!(b&&!N&&!O)){var Y=he===!0?He:m.scroll(),It=O?0:(Y-et)/mt,J=It<0?0:It>1?1:It||0,wt=m.progress,jt,gt,ut,st,We,ht,be,je;if(U&&(re=ie,ie=b?Tt():Y,D&&(Li=yi,yi=r&&!B?r.totalProgress():J)),g&&h&&!Zt&&!ds&&Le&&(!J&&etY+(Y-re)/(Jt()-_n)*g&&(J=.9999)),J!==wt&&m.enabled){if(jt=m.isActive=!!J&&J<1,gt=!!wt&&wt<1,ht=jt!==gt,We=ht||!!J!=!!wt,m.direction=J>wt?1:-1,m.progress=J,We&&!Zt&&(ut=J&&!wt?0:J===1?1:wt===1?2:3,B&&(st=!ht&&R[ut+1]!=="none"&&R[ut+1]||R[ut],je=r&&(st==="complete"||st==="reset"||st in r))),w&&(ht||je)&&(je||f||!r)&&(te(w)?w(m):m.getTrailing(w).forEach(function(Ni){return Ni.endAnimation()})),B||(Q&&!Zt&&!ds?(Q._dp._time-Q._start!==Q._time&&Q.render(Q._dp._time-Q._start),Q.resetTo?Q.resetTo("totalProgress",J,r._tTime/r._tDur):(Q.vars.totalProgress=J,Q.invalidate().restart())):r&&r.totalProgress(J,!!(Zt&&(St||O)))),h){if(O&&_&&(Mt.style[_+F.os2]=Br),!M)Be(gn(Oe+ri*J));else if(We){if(be=!O&&J>wt&&bt+1>Y&&Y+1>=ti(A,F),v)if(!O&&(jt||be)){var zt=pi(h,!0),Nt=Y-et;ku(h,rt,zt.top+(F===Ft?Nt:0)+Pt,zt.left+(F===Ft?0:Nt)+Pt)}else ku(h,Mt);Ir(jt||be?Di:Ii),ni&&J<1&&jt||Be(Oe+(J===1&&!be?ri:0))}}D&&!ee.tween&&!Zt&&!ds&&Ne.restart(!0),a&&(ht||S&&J&&(J<1||!$o))&&xn(a.targets).forEach(function(Ni){return Ni.classList[jt||S?"add":"remove"](a.className)}),o&&!B&&!O&&o(m),We&&!Zt?(B&&(je&&(st==="complete"?r.pause().totalProgress(1):st==="reset"?r.restart(!0).pause():st==="restart"?r.restart(!0):r[st]()),o&&o(m)),(ht||!$o)&&(u&&ht&&Xo(m,u),H[ut]&&Xo(m,H[ut]),S&&(J===1?m.kill(!1,1):H[ut]=0),ht||(ut=J===1?1:3,H[ut]&&Xo(m,H[ut]))),x&&!jt&&Math.abs(m.getVelocity())>(Dn(x)?x:2500)&&(pn(m.callbackAnimation),Q?Q.progress(1):pn(r,st==="reverse"?1:!J,1))):B&&o&&!Zt&&o(m)}if(ur){var Xt=b?Y/b.duration()*(b._caScrollDist||0):Y;An(Xt+(C._isFlipped?1:0)),ur(Xt)}hr&&hr(-Y/b.duration()*(b._caScrollDist||0))}},m.enable=function(O,U){m.enabled||(m.enabled=!0,Bt(A,"resize",vn),L||Bt(A,"scroll",kr),j&&Bt(n,"refreshInit",j),O!==!1&&(m.progress=Yt=0,ie=re=nt=Tt()),U!==!1&&m.refresh())},m.getTween=function(O){return O&&ee?ee.tween:Q},m.setPositions=function(O,U,N,Y){if(b){var It=b.scrollTrigger,J=b.duration(),wt=It.end-It.start;O=It.start+wt*O/J,U=It.start+wt*U/J}m.refresh(!1,!1,{start:Eu(O,N&&!!m._startClamp),end:Eu(U,N&&!!m._endClamp)},Y),m.update()},m.adjustPinSpacing=function(O){if(xt&&O){var U=xt.indexOf(F.d)+1;xt[U]=parseFloat(xt[U])+O+Pt,xt[1]=parseFloat(xt[1])+O+Pt,Ir(xt)}},m.disable=function(O,U){if(O!==!1&&m.revert(!0,!0),m.enabled&&(m.enabled=m.isActive=!1,U||Q&&Q.pause(),He=0,$t&&($t.uncache=1),j&&Lt(n,"refreshInit",j),Ne&&(Ne.pause(),ee.tween&&ee.tween.kill()&&(ee.tween=0)),!L)){for(var N=q.length;N--;)if(q[N].scroller===A&&q[N]!==m)return;Lt(A,"resize",vn),L||Lt(A,"scroll",kr)}},m.kill=function(O,U){m.disable(O,U),Q&&!U&&Q.kill(),l&&delete ea[l];var N=q.indexOf(m);N>=0&&q.splice(N,1),N===ce&&Cs>0&&ce--,N=0,q.forEach(function(Y){return Y.scroller===m.scroller&&(N=1)}),N||he||(m.scroll.rec=0),r&&(r.scrollTrigger=null,O&&r.revert({kill:!1}),U||r.kill()),fe&&[fe,ve,C,ye].forEach(function(Y){return Y.parentNode&&Y.parentNode.removeChild(Y)}),Tn===m&&(Tn=0),h&&($t&&($t.uncache=1),N=0,q.forEach(function(Y){return Y.pin===h&&N++}),N||($t.spacer=0)),i.onKill&&i.onKill(m)},q.push(m),m.enable(!1,!1),si&&si(m),r&&r.add&&!mt){var it=m.update;m.update=function(){m.update=it,X.cache++,et||bt||m.refresh()},k.delayedCall(.01,m.update),mt=.01,et=bt=0}else m.refresh();h&&od()},n.register=function(i){return Pr||(k=i||Wu(),Hu()&&window.document&&n.enable(),Pr=mn),Pr},n.defaults=function(i){if(i)for(var r in i)gs[r]=i[r];return gs},n.disable=function(i,r){mn=0,q.forEach(function(o){return o[r?"kill":"disable"](i)}),Lt(G,"wheel",kr),Lt(lt,"scroll",kr),clearInterval(fs),Lt(lt,"touchcancel",Je),Lt(rt,"touchstart",Je),_s(Lt,lt,"pointerdown,touchstart,mousedown",wu),_s(Lt,lt,"pointerup,touchend,mouseup",Cu),xs.kill(),ps(Lt);for(var s=0;s0&&o.left+a0&&o.top+a=0&&xe.splice(e,5),xe.push(t,t.style.cssText,t.getBBox&&t.getAttribute("transform"),k.core.getCache(t),Jo())}}):xe};K.revert=function(n,t){return ha(!n,t)};K.create=function(n,t){return new K(n,t)};K.refresh=function(n){return n?vn(!0):(Pr||K.register())&&ir(!0)};K.update=function(n){return++X.cache&&_i(n===!0?2:0)};K.clearScrollMemory=Ku;K.maxScroll=function(n,t){return ti(n,t?Qt:Ft)};K.getScrollFunc=function(n,t){return fi(le(n),t?Qt:Ft)};K.getById=function(n){return ea[n]};K.getAll=function(){return q.filter(function(n){return n.vars.id!=="ScrollSmoother"})};K.isScrolling=function(){return!!Le};K.snapDirectional=ca;K.addEventListener=function(n,t){var e=ar[n]||(ar[n]=[]);~e.indexOf(t)||e.push(t)};K.removeEventListener=function(n,t){var e=ar[n],i=e&&e.indexOf(t);i>=0&&e.splice(i,1)};K.batch=function(n,t){var e=[],i={},r=t.interval||.016,s=t.batchMax||1e9,o=function(u,c){var f=[],d=[],h=k.delayedCall(r,function(){c(f,d),f=[],d=[]}).pause();return function(_){f.length||h.restart(!0),f.push(_.trigger),d.push(_),s<=f.length&&h.progress(1)}},a;for(a in t)i[a]=a.substr(0,2)==="on"&&te(t[a])&&a!=="onRefreshInit"?o(a,t[a]):t[a];return te(s)&&(s=s(),Bt(K,"refresh",function(){return s=t.batchMax()})),xn(n).forEach(function(l){var u={};for(a in i)u[a]=i[a];u.trigger=l,e.push(K.create(u))}),e};var Ru=function(t,e,i,r){return e>r?t(r):e<0&&t(0),i>r?(r-e)/(i-e):i<0?e/(e-i):1},Ko=function n(t,e){e===!0?t.style.removeProperty("touch-action"):t.style.touchAction=e===!0?"auto":e?"pan-"+e+(yt.isTouch?" pinch-zoom":""):"none",t===Ae&&n(rt,e)},bs={auto:1,scroll:1},hd=function(t){var e=t.event,i=t.target,r=t.axis,s=(e.changedTouches?e.changedTouches[0]:e).target,o=s._gsap||k.core.getCache(s),a=Jt(),l;if(!o._isScrollT||a-o._isScrollT>2e3){for(;s&&s!==rt&&(s.scrollHeight<=s.clientHeight&&s.scrollWidth<=s.clientWidth||!(bs[(l=Ie(s)).overflowY]||bs[l.overflowX]));)s=s.parentNode;o._isScroll=s&&s!==i&&!or(s)&&(bs[(l=Ie(s)).overflowY]||bs[l.overflowX]),o._isScrollT=a}(o._isScroll||r==="x")&&(e.stopPropagation(),e._gsapAllow=!0)},Ju=function(t,e,i,r){return yt.create({target:t,capture:!0,debounce:!1,lockAxis:!0,type:e,onWheel:r=r&&hd,onPress:r,onDrag:r,onScroll:r,onEnable:function(){return i&&Bt(lt,yt.eventTypes[0],Iu,!1,!0)},onDisable:function(){return Lt(lt,yt.eventTypes[0],Iu,!0)}})},fd=/(input|label|select|textarea)/i,Mu,Iu=function(t){var e=fd.test(t.target.tagName);(e||Mu)&&(t._gsapAllow=!0,Mu=e)},dd=function(t){er(t)||(t={}),t.preventDefault=t.isNormalizer=t.allowClicks=!0,t.type||(t.type="wheel,touch"),t.debounce=!!t.debounce,t.id=t.id||"normalizer";var e=t,i=e.normalizeScrollX,r=e.momentum,s=e.allowNestedScroll,o=e.onRelease,a,l,u=le(t.target)||Ae,c=k.core.globals().ScrollSmoother,f=c&&c.get(),d=Mi&&(t.content&&le(t.content)||f&&t.content!==!1&&!f.smooth()&&f.content()),h=fi(u,Ft),_=fi(u,Qt),p=1,g=(yt.isTouch&&G.visualViewport?G.visualViewport.scale*G.visualViewport.width:G.outerWidth)/G.innerWidth,E=0,y=te(r)?function(){return r(a)}:function(){return r||2.8},S,D,v=Ju(u,t.type,!0,s),T=function(){return D=!1},b=Je,x=Je,w=function(){l=ti(u,Ft),x=yn(Mi?1:0,l),i&&(b=yn(0,ti(u,Qt))),S=sr},F=function(){d._gsap.y=gn(parseFloat(d._gsap.y)+h.offset)+"px",d.style.transform="matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, "+parseFloat(d._gsap.y)+", 0, 1)",h.offset=h.cacheID=0},B=function(){if(D){requestAnimationFrame(T);var z=gn(a.deltaY/2),V=x(h.v-z);if(d&&V!==h.v+h.offset){h.offset=V-h.v;var m=gn((parseFloat(d&&d._gsap.y)||0)-h.offset);d.style.transform="matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, "+m+", 0, 1)",d._gsap.y=m+"px",h.cacheID=X.cache,_i()}return!0}h.offset&&F(),D=!0},A,P,L,M,H=function(){w(),A.isActive()&&A.vars.scrollY>l&&(h()>l?A.progress(1)&&h(l):A.resetTo("scrollY",l))};return d&&k.set(d,{y:"+=0"}),t.ignoreCheck=function(R){return Mi&&R.type==="touchmove"&&B(R)||p>1.05&&R.type!=="touchstart"||a.isGesturing||R.touches&&R.touches.length>1},t.onPress=function(){D=!1;var R=p;p=gn((G.visualViewport&&G.visualViewport.scale||1)/g),A.pause(),R!==p&&Ko(u,p>1.01?!0:i?!1:"x"),P=_(),L=h(),w(),S=sr},t.onRelease=t.onGestureStart=function(R,z){if(h.offset&&F(),!z)M.restart(!0);else{X.cache++;var V=y(),m,j;i&&(m=_(),j=m+V*.05*-R.velocityX/.227,V*=Ru(_,m,j,ti(u,Qt)),A.vars.scrollX=b(j)),m=h(),j=m+V*.05*-R.velocityY/.227,V*=Ru(h,m,j,ti(u,Ft)),A.vars.scrollY=x(j),A.invalidate().duration(V).play(.01),(Mi&&A.vars.scrollY>=l||m>=l-1)&&k.to({},{onUpdate:H,duration:V})}o&&o(R)},t.onWheel=function(){A._ts&&A.pause(),Jt()-E>1e3&&(S=0,E=Jt())},t.onChange=function(R,z,V,m,j){if(sr!==S&&w(),z&&i&&_(b(m[2]===z?P+(R.startX-R.x):_()+z-m[1])),V){h.offset&&F();var Z=j[2]===V,Rt=Z?L+R.startY-R.y:h()+V-j[1],nt=x(Rt);Z&&Rt!==nt&&(L+=nt-Rt),h(nt)}(V||z)&&_i()},t.onEnable=function(){Ko(u,i?!1:"x"),K.addEventListener("refresh",H),Bt(G,"resize",H),h.smooth&&(h.target.style.scrollBehavior="auto",h.smooth=_.smooth=!1),v.enable()},t.onDisable=function(){Ko(u,!0),Lt(G,"resize",H),K.removeEventListener("refresh",H),v.kill()},t.lockAxis=t.lockAxis!==!1,a=new yt(t),a.iOS=Mi,Mi&&!h()&&h(1),Mi&&k.ticker.add(Je),M=a._dc,A=k.to(a,{ease:"power4",paused:!0,inherit:!1,scrollX:i?"+=0.1":"+=0",scrollY:"+=0.1",modifiers:{scrollY:Zu(h,h(),function(){return A.pause()})},onUpdate:_i,onComplete:M.vars.onComplete}),a};K.sort=function(n){if(te(n))return q.sort(n);var t=G.pageYOffset||0;return K.getAll().forEach(function(e){return e._sortY=e.trigger?t+e.trigger.getBoundingClientRect().top:e.start+G.innerHeight}),q.sort(n||function(e,i){return(e.vars.refreshPriority||0)*-1e6+(e.vars.containerAnimation?1e6:e._sortY)-((i.vars.containerAnimation?1e6:i._sortY)+(i.vars.refreshPriority||0)*-1e6)})};K.observe=function(n){return new yt(n)};K.normalizeScroll=function(n){if(typeof n=="undefined")return ue;if(n===!0&&ue)return ue.enable();if(n===!1){ue&&ue.kill(),ue=n;return}var t=n instanceof yt?n:dd(n);return ue&&ue.target===t.target&&ue.kill(),or(t.target)&&(ue=t),t};K.core={_getVelocityProp:hs,_inputObserver:Ju,_scrollers:X,_proxies:$e,bridge:{ss:function(){Le||lr("scrollStart"),Le=Jt()},ref:function(){return Zt}}};Wu()&&k.registerPlugin(K);_t.registerPlugin(Ji);_t.registerPlugin(K);var ks=class extends de{constructor(n){super(n),this.onProgressBind=this.onProgress.bind(this),this.delay=parseFloat(this.getData("delay"))||0,this.stagger=parseFloat(this.getData("stagger"))||.1,this.metrics=[]}init(){this.bindEvents(),Nn(Cr.EAGER).then(n=>{this.onFontsLoaded(n)})}destroy(){super.destroy(),this.unbindEvents(),this.splitObject&&this.splitObject.revert()}unbindEvents(){window.removeEventListener("progressEvent",this.onProgressBind)}bindEvents(){window.addEventListener("progressEvent",this.onProgressBind)}onFontsLoaded(n){this.split()}onProgress(n){if(!this.splitObject||!this.splitObject.lines)return;let{progress:t}=n.detail;this.splitObject.lines.forEach((e,i)=>{if(this.metrics[i]){let{from:r,to:s}=this.metrics[i],o=_t.utils.clamp(-25,0,_t.utils.mapRange(r,s,-25,0,t)),a=_t.utils.clamp(0,1,_t.utils.mapRange(r,s,0,1,t));_t.set(e,{x:o,opacity:a})}})}split(){this.splitObject=Ji.create(this.el,{type:"lines",tag:"span",autoSplit:!0,onSplit:n=>{this.computeMetrics(n)}}),this.computeMetrics(this.splitObject)}computeMetrics(n){this.metrics=n.lines.map((t,e,i)=>{let r=e/i.length,s=r+1/i.length;return{from:r,to:s}})}};_t.registerPlugin(Ji);var Ps=class extends de{constructor(n){super(n),this.onFadeinTextProgressBind=this.onFadeinTextProgress.bind(this),this.$texts=this.el,this.progress=0,this.metrics=[],this.baseColor=getComputedStyle(this.el).getPropertyValue("--color-cta-fadein"),this.targetColor=getComputedStyle(this.el).getPropertyValue("--color")}init(){this.bindEvents(),this.splitText()}destroy(){this.unbindEvents(),this.split&&this.split.revert()}bindEvents(){window.addEventListener("fadeinTextProgress",this.onFadeinTextProgressBind)}unbindEvents(){window.removeEventListener("fadeinTextProgress",this.onFadeinTextProgressBind)}onFadeinTextProgress(n){let{target:t,progress:e}=n.detail;!this.el.contains(t)||!this.split||(this.progress=e,this.computeProgress())}splitText(){this.split=Ji.create(this.$texts,{type:"lines",linesClass:"c-fadein-text_line",autoSplit:!0,onSplit:n=>{this.computeMetrics(n),requestAnimationFrame(()=>{this.computeProgress()})}}),this.computeMetrics(this.split)}computeMetrics(n){this.metrics=[];let t=n.lines.map(r=>r.getBoundingClientRect().width),e=t.reduce((r,s)=>r+s,0),i=0;for(let r=0;r0;i--){var r=Math.floor(Math.random()*(i+1)),s=t[i];t[i]=t[r],t[r]=s}return t.join("")}onItemEnter(n){let t=n.currentTarget,e=[];t.dataset.hoverShuffle=="children"?t.querySelectorAll("[data-hover-shuffle-child]").forEach(s=>{e.push(s),s.setAttribute("aria-label",s.innerText)}):(e=[t],t.setAttribute("aria-label",t.innerText)),this.tw=_t.timeline({onComplete:()=>{e.forEach(s=>{s.innerText=s.getAttribute("aria-label"),s.removeAttribute("aria-label")})}});let i=4,r=pd/i;for(let s=0;s{this.shuffleElementTexts(o,this.shuffle)},r*s)}onItemLeave(n){this.tw&&this.tw.kill();let t=n.currentTarget,e=[];t.dataset.hoverShuffle=="children"?t.querySelectorAll("[data-hover-shuffle-child]").forEach(i=>{e.push(i)}):e=[t],e.forEach(i=>{i.getAttribute("aria-label")&&(i.innerText=i.getAttribute("aria-label")),i.removeAttribute("aria-label")})}shuffleElementTexts(n,t){if(!n.children.length&&n.innerText){let e=n.innerText.replace(` `,` `).split(" ");for(let i=0;is[0]);e[i]=t(r)}n.innerText=e.join(" ")}}};var sc=vc(ec(),1);var Is;Hi(void 0,null,function*(){if(Qi.IS_DEV){let n=yield Promise.resolve().then(()=>(nc(),rc));Is=n==null?void 0:n.gridHelper}});function oc(){(0,sc.default)(),Is==null||Is()}var da=(n,t,e=!1)=>{let i=null;return(...r)=>{clearTimeout(i);let s=()=>{i=null,e||n(...r)};e&&!i&&n(...r),i=setTimeout(s,t)}};var Ed="@vercel/analytics",wd="1.6.1",Cd=()=>{window.va||(window.va=function(...t){(window.vaq=window.vaq||[]).push(t)})};function ac(){return typeof window!="undefined"}function lc(){try{let n="production";if(n==="development"||n==="test")return"development"}catch(n){}return"production"}function Sd(n="auto"){if(n==="auto"){window.vam=lc();return}window.vam=n}function Td(){return(ac()?window.vam:lc())||"production"}function pa(){return Td()==="development"}function xd(n){return n.scriptSrc?n.scriptSrc:pa()?"https://va.vercel-scripts.com/v1/script.debug.js":n.basePath?`${n.basePath}/insights/script.js`:"/_vercel/insights/script.js"}function uc(n={debug:!0}){var t;if(!ac())return;Sd(n.mode),Cd(),n.beforeSend&&((t=window.va)==null||t.call(window,"beforeSend",n.beforeSend));let e=xd(n);if(document.head.querySelector(`script[src*="${e}"]`))return;let i=document.createElement("script");i.src=e,i.defer=!0,i.dataset.sdkn=Ed+(n.framework?`/${n.framework}`:""),i.dataset.sdkv=wd,n.disableAutoTrack&&(i.dataset.disableAutoTrack="1"),n.endpoint?i.dataset.endpoint=n.endpoint:n.basePath&&(i.dataset.endpoint=`${n.basePath}/insights`),n.dsn&&(i.dataset.dsn=n.dsn),i.onerror=()=>{let r=pa()?"Please check if any ad blockers are enabled and try again.":"Be sure to enable Web Analytics for your project and deploy again. See https://vercel.com/docs/analytics/quickstart for more information.";console.log(`[Vercel Web Analytics] Failed to load script from ${e}. ${r}`)},pa()&&n.debug===!1&&(i.dataset.debug="false"),document.head.appendChild(i)}var cc=new wa({modules:fa});function Fd(){Ad(),hc(),document.documentElement.style.setProperty("--vh-initial",`${window.innerHeight*.01}px`),oc(),cc.init(cc),setTimeout(()=>{Ve.classList.add(wr.FIRST_LOADED),Ve.classList.add(wr.LOADED),Ve.classList.remove(wr.LOADING),setTimeout(()=>{Ve.classList.add(wr.READY)},100)},100),uc(),Fa&&Oa(Cr.EAGER,Qi.IS_DEV).then(n=>{Ve.classList.add(wr.FONTS_LOADED),Qi.IS_DEV&&(console.group("Eager fonts loaded!",n.length,"/",document.fonts.size),console.group("State of eager fonts:"),n.forEach(t=>console.log(t.family,t.style,t.weight,t.status)),console.groupEnd(),console.group("State of all fonts:"),document.fonts.forEach(t=>console.log(t.family,t.style,t.weight,t.status)),console.groupEnd())})}function Ad(){let n=new CustomEvent(tn.RESIZE_END);window.addEventListener("resize",da(()=>{window.dispatchEvent(n)},200,!1)),window.addEventListener("resize",hc),window.addEventListener("orientationchange",da(()=>{Od()},200,!1))}function Od(){document.documentElement.style.setProperty("--vh-initial",`${window.innerHeight*.01}px`)}function hc(){let n=Ve.offsetWidth*.01,t=window.innerHeight*.01;document.documentElement.style.setProperty("--vw",`${n}px`),document.documentElement.style.setProperty("--vh",`${t}px`)}window.addEventListener("load",n=>{Fd()});})(); /*! Bundled license information: svg4everybody/dist/svg4everybody.js: (*! svg4everybody v2.1.9 | github.com/jonathantneal/svg4everybody *) gsap/gsap-core.js: (*! * GSAP 3.14.2 * https://gsap.com * * @license Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) gsap/CSSPlugin.js: (*! * CSSPlugin 3.14.2 * https://gsap.com * * Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) gsap/utils/strings.js: (*! * strings: 3.14.2 * https://gsap.com * * Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) gsap/ScrambleTextPlugin.js: (*! * ScrambleTextPlugin 3.14.2 * https://gsap.com * * @license Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) gsap/SplitText.js: (*! * SplitText 3.14.2 * https://gsap.com * * @license Copyright 2025, GreenSock. All rights reserved. Subject to the terms at https://gsap.com/standard-license. * @author: Jack Doyle *) gsap/Observer.js: (*! * Observer 3.14.2 * https://gsap.com * * @license Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) gsap/ScrollTrigger.js: (*! * ScrollTrigger 3.14.2 * https://gsap.com * * @license Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com *) */ //# sourceMappingURL=app.js.map ================================================ FILE: www/landing/assets/scripts/vendors.js ================================================ ================================================ FILE: www/landing/assets/site.webmanifest ================================================ { "name": "Locomotive Boilerplate", "short_name": "Boilerplate", "icons": [ { "src": "assets/images/favicons/android-chrome-144x144.png", "sizes": "144x144", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" } ================================================ FILE: www/landing/assets/styles/critical.css ================================================ ================================================ FILE: www/landing/assets/styles/main.css ================================================ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html.lenis,html.lenis body{height:auto}.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}.lenis [data-lenis-prevent],.lenis [data-lenis-prevent-wheel],.lenis [data-lenis-prevent-touch]{overscroll-behavior:contain}.lenis.lenis-smooth iframe{pointer-events:none}.lenis.lenis-autoToggle{transition-property:overflow;transition-duration:1ms;transition-behavior:allow-discrete}:root{--spacing-2xs-mobile: 6;--spacing-2xs-desktop: 10;--spacing-xs-mobile: 14;--spacing-xs-desktop: 16;--spacing-sm-mobile: 28;--spacing-sm-desktop: 32;--spacing-md-mobile: 42;--spacing-md-desktop: 56;--spacing-lg-mobile: 72;--spacing-lg-desktop: 96;--spacing-xl-mobile: 90;--spacing-xl-desktop: 120;--spacing-2xl-mobile: 96;--spacing-2xl-desktop: 160;--spacing-3xl-mobile: 112;--spacing-3xl-desktop: 224}:root{--grid-columns: 4;--grid-gutter: 0.625rem;--grid-margin: 0.625rem;--container-width: calc(100% - 2 * var(--grid-margin))}@media(max-width: 699px){:root{--header-height: 2.125rem}}@media(min-width: 700px){:root{--grid-gutter: 1rem;--grid-margin: 1.25rem;--header-height: 3.75rem}}html{box-sizing:border-box}template,[hidden]{display:none}*,:before,:after{box-sizing:inherit}address{font-style:inherit}dfn,cite,em,i{font-style:italic}b,strong{font-weight:500}a{text-decoration:none}a svg{pointer-events:none}ul,ol{margin:0;padding:0;list-style:none}p,figure{margin:0;padding:0}h1,h2,h3,h4,h5,h6{margin:0}a,area,button,input,label,select,textarea,[tabindex]{touch-action:manipulation}[hreflang]>abbr[title]{text-decoration:none}table{border-spacing:0;border-collapse:collapse}hr{display:block;margin:1em 0;padding:0;height:1px;border:0;border-top:1px solid #ccc}audio,canvas,iframe,img,svg,video{vertical-align:middle}audio:not([controls]){display:none;height:0}img,svg{height:auto}img[width],img[height],svg[width],svg[height]{max-width:none}img{font-style:italic}svg{fill:currentColor}button:focus,button:hover,.c-button:focus,.c-button:hover{text-decoration:none}button,.c-button{display:inline-block;overflow:visible;margin:0;padding:0;outline:0;border:0;background:none rgba(0,0,0,0);color:inherit;vertical-align:middle;text-align:center;text-decoration:none;text-transform:none;font:inherit;line-height:normal;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}@font-face{font-display:swap;font-family:"Helvetica Now Display";src:url("../fonts/HelveticaNowDisplay-Medium.woff2") format("woff2"),url("../fonts/HelveticaNowDisplay-Medium.woff") format("woff");font-weight:500;font-style:normal}@font-face{font-display:swap;font-family:"Helvetica Now Display";src:url("../fonts/HelveticaNowDisplay-Regular.woff2") format("woff2"),url("../fonts/HelveticaNowDisplay-Regular.woff") format("woff");font-weight:400;font-style:normal}@font-face{font-display:swap;font-family:"PP Locomotive New";src:url("../fonts/PPLocomotiveNew-Light.woff2") format("woff2"),url("../fonts/PPLocomotiveNew-Light.woff") format("woff");font-weight:300;font-style:normal}html{min-height:100%;line-height:1.5;font-family:"Helvetica Now Display",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;color:#000;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media(max-width: 699px){html{font-size:14px}}@media(min-width: 700px)and (max-width: 999px){html{font-size:14px}}@media(min-width: 1000px)and (max-width: 1199px){html{font-size:15px}}@media(min-width: 1200px)and (max-width: 1599px){html{font-size:16px}}@media(min-width: 1600px)and (max-width: 1999px){html{font-size:17px}}@media(min-width: 2000px)and (max-width: 2399px){html{font-size:18px}}@media(min-width: 2400px){html{font-size:20px}}html.is-loading{cursor:wait}::-moz-selection{background-color:#fff;color:#000;text-shadow:none}::selection{background-color:#fff;color:#000;text-shadow:none}a{color:inherit}[data-theme=dark] *:focus-visible:not(input):not(textarea):not(select){outline-color:#fff;outline-style:solid;outline-width:2px;outline-offset:2px}[data-theme=white] *:focus-visible:not(input):not(textarea):not(select){outline-color:#202ded;outline-style:solid;outline-width:2px;outline-offset:2px}[data-theme=blue] *:focus-visible:not(input):not(textarea):not(select){outline-color:#fff;outline-style:solid;outline-width:2px;outline-offset:2px}*:focus-visible:not(input):not(textarea):not(select){outline-color:currentColor;outline-style:solid;outline-width:2px;outline-offset:2px}input:focus-visible,select:focus-visible,textarea:focus-visible{outline-color:var(--color-theme-dark);outline-style:solid;outline-width:2px;outline-offset:0px}.o-container{margin-right:auto;margin-left:auto;padding-left:var(--grid-margin);padding-right:var(--grid-margin)}:root{--icon-width: 1.5rem;--icon-ratio: 1}.o-icon{display:inline-block;vertical-align:middle}.o-icon svg{display:block;width:var(--icon-width);height:calc(var(--icon-width)*1/(var(--icon-ratio)))}.svg-logo-lenis{--icon-width: 4.1875rem;--icon-ratio: 67/72}.svg-logo-locomotive{--icon-width: 4.9375rem;--icon-ratio: 79/35}.svg-arrow-external{--icon-width: 0.75rem}.svg-inview-01,.svg-inview-02,.svg-inview-03,.svg-inview-04,.svg-inview-05,.svg-inview-06{--icon-width: calc(24 * var(--vw, 1vw))}.svg-progress-01,.svg-progress-02,.svg-progress-03,.svg-progress-04,.svg-progress-05{--icon-width: calc(24 * var(--vw, 1vw));--icon-ratio: 224/350}.svg-parallax-01,.svg-parallax-02,.svg-parallax-03,.svg-parallax-04,.svg-parallax-05{--icon-width: calc(24 * var(--vw, 1vw))}.o-grid{display:grid;width:100%}.o-grid:is(ul,ol){margin:0;padding:0;list-style:none}.o-grid.-cols{grid-template-columns:repeat(var(--grid-columns), 1fr)}.o-grid.-col-4{grid-template-columns:repeat(4, 1fr)}.o-grid.-col-2{grid-template-columns:repeat(2, 1fr)}@media(min-width: 1000px){.o-grid.-col-4\@from-medium{grid-template-columns:repeat(4, 1fr)}}.o-grid.-gutters{gap:var(--grid-gutter);-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter)}.o-grid.-full-height{height:100%}.o-grid.-top-items{align-items:start}.o-grid.-right-items{justify-items:end}.o-grid.-bottom-items{align-items:end}.o-grid.-left-items{justify-items:start}.o-grid.-center-items{align-items:center;justify-items:center}.o-grid.-center-items-x{justify-items:center}.o-grid.-center-items-y{align-items:center}.o-grid.-stretch-items{align-items:stretch;justify-items:stretch}.o-grid.-top-cells{align-content:start}.o-grid.-right-cells{justify-content:end}.o-grid.-bottom-cells{align-content:end}.o-grid.-left-cells{justify-content:start}.o-grid.-center-cells{align-content:center;justify-content:center}.o-grid.-center-cells-x{justify-content:center}.o-grid.-center-cells-y{align-content:center}.o-grid.-stretch-cells{align-content:stretch;justify-content:stretch}.o-grid.-space-around-cells{align-content:space-around;justify-content:space-around}.o-grid.-space-around-cells-x{justify-content:space-around}.o-grid.-space-around-cells-y{align-content:space-around}.o-grid.-space-between-cells{justify-content:space-between;align-content:space-between}.o-grid.-space-between-cells-x{justify-content:space-between}.o-grid.-space-between-cells-y{align-content:space-between}.o-grid.-space-evenly-cells{justify-content:space-evenly;align-content:space-evenly}.o-grid.-space-evenly-cells-x{justify-content:space-evenly}.o-grid.-space-evenly-cells-y{align-content:space-evenly}.o-grid_item{grid-column-start:var(--gc-start, 1);grid-column-end:var(--gc-end, -1)}.o-grid_item.-align-end{align-self:end}:root{--font-size-serif-large: clamp(100px, 0.2083333333 * calc(100 * var(--vw, 1vw)), 300px);--font-size-serif-medium: clamp(40px, 0.0833333333 * calc(100 * var(--vw, 1vw)), 120px);--font-size-h1: clamp(54px, 0.0833333333 * calc(100 * var(--vw, 1vw)), 120px);--font-size-h2: clamp(32px, 0.0625 * calc(100 * var(--vw, 1vw)), 90px);--font-size-h3: clamp(14px, 0.0194444444 * calc(100 * var(--vw, 1vw)), 28px)}.c-heading{font-family:"Helvetica Now Display",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:400;line-height:1;letter-spacing:-0.03em}.c-heading.-h1{font-size:var(--font-size-h1)}.c-heading.-h2{font-size:var(--font-size-h2)}.c-heading.-h3{font-size:var(--font-size-h3)}.c-heading-serif{font-family:"PP Locomotive New",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:300;line-height:.85;letter-spacing:-0.03em}.c-heading-serif.-large{font-size:var(--font-size-serif-large)}.c-heading-serif.-medium{font-size:var(--font-size-serif-medium)}:root{--font-size-body-regular: 1rem;--font-size-body-medium: 1.3125rem;--font-size-label: 1rem;--font-size-label-small: 0.75rem}.c-text{font-family:"Helvetica Now Display",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:500;line-height:1.2;letter-spacing:-0.01em}.c-text.-body-regular{font-size:var(--font-size-body-regular)}.c-text.-body-medium{font-size:var(--font-size-body-medium)}.c-text.-label{font-size:var(--font-size-label)}.c-text.-label-small{font-size:var(--font-size-label-small)}.c-button{--button-height: 3.5rem;--button-padding: 1rem;--button-gap: 1rem;--button-color-text: var(--color-background);--button-color-background: var(--color-text);--button-color-stroke: var(--color-text);--button-hover-color-text: var(--color-text);--button-hover-color-background: var(--color-background);position:relative;overflow:hidden;border-radius:4px;color:var(--button-color-text);border:1px solid var(--button-color-stroke);background-color:var(--button-color-background)}.c-button.-stroke{--button-color-text: currentColor;--button-color-background: transparent;--button-hover-color-text: var(--color-background);--button-hover-color-background: var(--color-text)}.c-button_wrap:nth-child(2){position:absolute;top:-1px;left:-1px;width:calc(100% + 2px);height:calc(100% + 2px);color:var(--button-hover-color-text);background-color:var(--button-hover-color-background);clip-path:inset(10% -100% 10% 100% round 4px 4px 4px 4px);transition:clip-path .5s cubic-bezier(0.77, 0, 0.175, 1)}.c-button:hover .c-button_wrap:nth-child(2){clip-path:inset(0 0 0 0 round 4px 4px 4px 4px)}.c-button_inner{display:inline-flex;align-items:center;-moz-column-gap:var(--button-gap);column-gap:var(--button-gap);min-height:var(--button-height);padding:0 var(--button-padding);transition:transform .5s cubic-bezier(0.77, 0, 0.175, 1)}.c-button:hover .c-button_wrap:nth-child(1) .c-button_inner{transform:translate3d(calc(-1 * var(--icon-width) - var(--button-gap)), 0, 0)}.c-button_wrap:nth-child(2) .c-button_inner{position:absolute;top:-1px;left:-1px;width:calc(100% + 2px);height:calc(100% + 2px);flex-direction:row-reverse;transform:translate3d(calc(var(--icon-width) + var(--button-gap)), 0, 0)}.c-button:hover .c-button_wrap:nth-child(2) .c-button_inner{transform:translate3d(0, 0, 0)}.c-button_label{font-family:"Helvetica Now Display",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:500;line-height:1.2;letter-spacing:-0.01em;font-size:var(--font-size-label)}.c-form_item{position:relative;margin-bottom:1.875rem}.c-form_label,.c-form_checkboxLabel,.c-form_radioLabel{display:block;margin-bottom:.625rem}.c-form_input,.c-form_textarea,.c-form_select_input{padding:.625rem;border:1px solid #d3d3d3;background-color:#fff}.c-form_input:hover,.c-form_textarea:hover,.c-form_select_input:hover{border-color:#a9a9a9}.c-form_input:focus,.c-form_textarea:focus,.c-form_select_input:focus{border-color:dimgray}.c-form_input::-moz-placeholder, .c-form_textarea::-moz-placeholder, .c-form_select_input::-moz-placeholder{color:gray}.c-form_input::placeholder,.c-form_textarea::placeholder,.c-form_select_input::placeholder{color:gray}.c-form_checkboxLabel,.c-form_radioLabel{position:relative;display:inline-block;margin-right:.625rem;margin-bottom:0;padding-left:1.75rem;cursor:pointer}.c-form_checkboxLabel::before,.c-form_radioLabel::before,.c-form_checkboxLabel::after,.c-form_radioLabel::after{position:absolute;top:50%;left:0;display:inline-block;margin-top:-0.5625rem;padding:0;width:1.125rem;height:1.125rem;content:""}.c-form_checkboxLabel::before,.c-form_radioLabel::before{background-color:#fff;border:1px solid #d3d3d3}.c-form_checkboxLabel::after,.c-form_radioLabel::after{border-color:rgba(0,0,0,0);background-color:rgba(0,0,0,0);background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2210.5%22%20viewBox%3D%220%200%2013%2010.5%22%20enable-background%3D%22new%200%200%2013%2010.5%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23424242%22%20d%3D%22M4.8%205.8L2.4%203.3%200%205.7l4.8%204.8L13%202.4c0%200-2.4-2.4-2.4-2.4L4.8%205.8z%22%2F%3E%3C%2Fsvg%3E");background-position:center;background-size:.75rem;background-repeat:no-repeat;opacity:0}.c-form_checkboxLabel:hover::before,.c-form_radioLabel:hover::before{border-color:#a9a9a9}.c-form_checkbox:focus+.c-form_checkboxLabel::before,.c-form_radio:focus+.c-form_checkboxLabel::before,.c-form_checkbox:focus+.c-form_radioLabel::before,.c-form_radio:focus+.c-form_radioLabel::before{border-color:dimgray}.c-form_checkbox:checked+.c-form_checkboxLabel::after,.c-form_radio:checked+.c-form_checkboxLabel::after,.c-form_checkbox:checked+.c-form_radioLabel::after,.c-form_radio:checked+.c-form_radioLabel::after{opacity:1}.c-form_checkbox,.c-form_radio{position:absolute;width:0;opacity:0}.c-form_radioLabel::before,.c-form_radioLabel::after{border-radius:50%}.c-form_radioLabel::after{background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2213%22%20viewBox%3D%220%200%2013%2013%22%20enable-background%3D%22new%200%200%2013%2013%22%20xml%3Aspace%3D%22preserve%22%3E%3Ccircle%20fill%3D%22%23424242%22%20cx%3D%226.5%22%20cy%3D%226.5%22%20r%3D%226.5%22%2F%3E%3C%2Fsvg%3E");background-size:.375rem}.c-form_select{position:relative;cursor:pointer}.c-form_select::after{position:absolute;top:0;right:0;bottom:0;z-index:2;width:2.5rem;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2211.3%22%20viewBox%3D%220%200%2013%2011.3%22%20enable-background%3D%22new%200%200%2013%2011.3%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23424242%22%20points%3D%226.5%2011.3%203.3%205.6%200%200%206.5%200%2013%200%209.8%205.6%20%22%2F%3E%3C%2Fsvg%3E");background-position:center;background-size:.5rem;background-repeat:no-repeat;content:"";pointer-events:none}.c-form_select_input{position:relative;z-index:1;padding-right:2.5rem;cursor:pointer}.c-form_textarea{min-height:12.5rem}[data-header-theme=blue] .c-header{--color-header-text: white;--color-header-background: #202ded}[data-header-theme=black] .c-header{--color-header-text: white;--color-header-background: black}[data-header-theme=white] .c-header{--color-header-text: #202ded;--color-header-background: white}.c-header{position:fixed;top:0;width:100%;display:grid;align-items:center;grid-template-columns:repeat(3, 1fr);-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter);color:var(--color-header-text);background-color:var(--color-header-background);padding:var(--grid-margin);z-index:200}@media(max-width: 699px){.c-header{align-items:flex-start}}@media(min-width: 700px){.c-header{height:var(--header-height)}}html.is-first-loaded .c-header{transition:color .15s,background-color .15s}.c-header_col{display:flex;justify-content:space-between;align-items:center}.c-header_col.-center{justify-content:center;align-items:center}@media(max-width: 699px){.c-header_col{flex-direction:column;align-items:flex-start;gap:var(--grid-margin)}.c-header_col:last-of-type{align-items:flex-end}}.c-header-theme-toggler{position:absolute;top:calc(-1*var(--header-height));bottom:var(--header-height);left:0;width:100%;pointer-events:none}.c-hero_sticky{position:sticky;bottom:0}.c-hero_main{display:grid;gap:var(--grid-gutter);align-items:flex-end;min-height:100svh;padding-bottom:var(--grid-margin);position:relative;z-index:10}.c-hero_main.-footer{min-height:100svh}@media(max-width: 699px){.c-hero_main{grid-template-rows:1fr auto 1fr}}@media(min-width: 700px){.c-hero_main{grid-template-columns:1fr 1fr}}.c-hero_heading{display:flex;flex-direction:column;row-gap:clamp(calc(0.0625rem * var(--spacing-md-mobile)),var(--spacing-md-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-md-desktop)));height:100%}@media(max-width: 699px){.c-hero_heading{grid-row:2/3}}@media(min-width: 700px){.c-hero_heading{grid-column:2/3;grid-row:1/2}}@media(max-width: 699px){.c-hero_nav{grid-row:3/4}}@media(min-width: 700px){.c-hero_nav{grid-column:1/2;grid-row:1/2}}.c-hero_title{margin-block:auto}.c-footer .c-hero_title{margin-top:auto;margin-bottom:0}.c-hero_links_item+.c-hero_links_item{margin-top:.5rem}.c-hero_description_container{display:grid;grid-template-columns:1fr 1fr;-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter);align-items:flex-end}.c-hero_version{display:flex;justify-content:flex-end;-moz-column-gap:.5em;column-gap:.5em}@media(max-width: 699px){.c-hero_version{position:absolute;bottom:var(--grid-margin);right:0}}.c-hero_version span{transition:transform .75s cubic-bezier(0.165, 0.84, 0.44, 1);transform:translate3d(0, 100%, 0)}.c-hero_version span:nth-child(1){position:relative;top:.1em}.c-hero_version.is-inview span{transform:translate3d(0, 0, 0)}.c-hero_line{display:block;clip-path:inset(-5% 0)}.c-hero_word{display:inline-block;transform:translate3d(0, -110%, 0)}html.is-first-loaded .c-hero_word{transition:transform .6s cubic-bezier(0.165, 0.84, 0.44, 1);transition-delay:calc(var(--index)*.1s)}html.is-ready .c-hero_word.-hero{transform:translate3d(0, 0, 0)}.is-inview .c-hero_word.-footer{transform:translate3d(0, 0, 0)}.c-rail{position:relative;overflow:hidden;padding:clamp(calc(0.0625rem * var(--spacing-md-mobile)),var(--spacing-md-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-md-desktop))) 0}.c-rail_inner{display:inline-flex;align-items:center;white-space:nowrap}.c-rail_item{font-family:"PP Locomotive New",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:300;line-height:.85;letter-spacing:-0.03em;font-size:var(--font-size-serif-medium);padding-right:.5em}.c-rail_item .c-icon{transform:translateY(-0.075em)}.c-rail_glyph{display:inline-block;padding-left:.5em;width:100px;text-align:center}@media(min-width: 1000px){.c-rail_glyph{width:140px}}@media(min-width: 1200px){.c-rail_glyph{width:170px}}.c-cascade{position:relative}.c-cascade_text{display:flex;flex-direction:column;max-width:13.75rem}@media(max-width: 1199px){.c-cascade_text{margin-left:50%;margin-bottom:clamp(calc(0.0625rem * var(--spacing-xl-mobile)),var(--spacing-xl-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-xl-desktop)))}}@media(min-width: 1200px){.c-cascade_text{position:absolute;top:0;left:calc((100% + var(--grid-gutter))/4*3)}}.c-cascade_container{display:flex;justify-content:center;margin-left:calc(-1*var(--grid-margin));margin-right:calc(-1*var(--grid-margin));overflow:hidden}.c-cascade_inner{width:auto}.c-cascade_item+.c-cascade_item{margin-left:calc(var(--item-index)*2.35em);margin-top:-1em}.c-cascade_item:nth-child(odd){opacity:.35}.c-cascade_line{display:block;perspective:100vw;transform-style:preserve-3d}.c-cascade_word{display:inline-block;white-space:nowrap;transform:rotate3d(1, 0, 0, clamp(0deg, 90deg + (var(--progress) - calc(var(--item-index) / 8)) / (calc((var(--item-index) + 1) / 8) - calc(var(--item-index) / 8)) * (0deg - 90deg), 90deg));opacity:clamp(0,0 + (var(--progress) - calc(var(--item-index) / 8))/(calc((var(--item-index) + 1) / 8) - calc(var(--item-index) / 8))*(1 - 0),1);transform-origin:top;clip-path:inset(0)}.c-cascade_glyph{position:absolute;bottom:0;left:0;font-size:var(--font-size-serif-medium)}.c-section-heading{display:flex;flex-direction:column}.c-section-heading_line+.c-section-heading_line{margin-top:.05em}@media(max-width: 999px){.c-section-heading_line{display:flex;flex-wrap:wrap}}@media(min-width: 1000px){.c-section-heading_line:last-child{display:grid;grid-template-columns:1fr auto 1fr;-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter);align-items:flex-end}}.c-section-heading_word{display:inline-block;width:100%;text-align:center}@media(max-width: 999px){.c-section-heading_line:last-child .c-section-heading_word{margin-bottom:3.125rem}}@media(min-width: 1000px){.c-section-heading_line:last-child .c-section-heading_word{grid-column:2/3;margin-bottom:-0.02em}}.c-section-heading_label{display:flex;align-items:center}@media(max-width: 999px){.c-section-heading_label{flex:0 0 50%}.c-section-heading_label.-left{flex-direction:column}.c-section-heading_label.-right{flex-direction:column-reverse}}@media(min-width: 1000px){.c-section-heading_label{justify-content:center;-moz-column-gap:.5em;column-gap:.5em;grid-row:1/2}.c-section-heading_label.-left{grid-column:1/2}.c-section-heading_label.-right{grid-column:3/4}}.c-tool{position:relative;background-color:#202ded;overflow:hidden}.c-tool.-parallax{padding-bottom:clamp(calc(0.0625rem * var(--spacing-sm-mobile)),var(--spacing-sm-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-sm-desktop)))}.c-tool.-parallax:before{content:"";position:absolute;bottom:0;left:0;width:100%;height:clamp(calc(0.0625rem * var(--spacing-xl-mobile)),var(--spacing-xl-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-xl-desktop)));background-image:linear-gradient(to bottom, hsla(236, 85%, 53%, 0) 0%, hsla(236, 85%, 53%, 0.013) 8.1%, hsla(236, 85%, 53%, 0.049) 15.5%, hsla(236, 85%, 53%, 0.104) 22.5%, hsla(236, 85%, 53%, 0.175) 29%, hsla(236, 85%, 53%, 0.259) 35.3%, hsla(236, 85%, 53%, 0.352) 41.2%, hsla(236, 85%, 53%, 0.45) 47.1%, hsla(236, 85%, 53%, 0.55) 52.9%, hsla(236, 85%, 53%, 0.648) 58.8%, hsla(236, 85%, 53%, 0.741) 64.7%, hsla(236, 85%, 53%, 0.825) 71%, hsla(236, 85%, 53%, 0.896) 77.5%, hsla(236, 85%, 53%, 0.951) 84.5%, hsla(236, 85%, 53%, 0.987) 91.9%, hsl(236, 85%, 53%) 100%);z-index:1}.c-tool_head{position:relative;display:grid;-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter);z-index:1;padding-top:.5rem}@media(max-width: 999px){.c-tool_head{grid-template-columns:repeat(4, 1fr)}}@media(min-width: 1000px){.c-tool_head{grid-template-columns:repeat(3, 1fr)}}.c-tool_head::before{content:"";position:absolute;top:0;left:0;width:100%;height:1px;background-color:currentColor;transform-origin:top right;transform:scale3d(0, 1, 1)}html.is-first-loaded .c-tool_head::before{transition:transform .75s cubic-bezier(0.77, 0, 0.175, 1)}html.is-first-loaded .c-tool.is-inview .c-tool_head::before{transform:scale3d(1, 1, 1)}@media(min-width: 1000px){.c-tool_head{margin-left:calc((100% + var(--grid-gutter))/4)}}@media(max-width: 999px){.c-tool_index{grid-column:span 2}}@media(min-width: 1000px){.c-tool_index{grid-column:span 1}}@media(max-width: 699px){.c-tool_label{grid-column:span 2}}@media(min-width: 700px){.c-tool_label{grid-column:span 1}}@media(max-width: 699px){.c-tool_description{grid-column:1/-1;grid-row:3/4;max-width:17.5rem;margin-top:.625rem}}@media(min-width: 700px){.c-tool_description{grid-column:span 1;grid-column-end:-1}}.c-tool_title{grid-column:1/-1;max-width:31.25rem}@media(max-width: 699px){.c-tool_title{grid-row:2/3;margin-top:.8em;margin-bottom:.4em}}@media(max-width: 699px){.c-tool.-parallax .c-tool_playground{margin-top:clamp(calc(0.0625rem * var(--spacing-md-mobile)),var(--spacing-md-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-md-desktop)))}}.c-tool_playground{position:relative;aspect-ratio:var(--container-ratio);width:100%}.c-tool_shape{position:absolute}.c-tool.-inview .c-tool_shape{left:calc(var(--left, 0)/1440*100%);top:calc(var(--top, 0)/(1/(var(--container-ratio))*1440)*100%);transform:translateX(-50%) translateY(-50%)}.c-tool.-inview .c-tool_shape.-first{--top: 502;--left: 90}.c-tool.-inview .c-tool_shape.-second{--top: 700;--left: 355}.c-tool.-inview .c-tool_shape.-third{--top: 475;--left: 655}.c-tool.-inview .c-tool_shape.-fourth{--top: 250;--left: 987}.c-tool.-inview .c-tool_shape.-fifth{--top: 345;--left: 1310}.c-tool.-inview .c-tool_shape.-sixth{--top: 750;--left: 1050}.c-tool.-inview .c-tool_shape.-first svg{transform:rotate(calc(var(--progress) * -90deg))}.c-tool.-inview .c-tool_shape.-fifth svg{transform:rotate(calc(var(--progress) * 150deg))}.c-tool.-progress .c-tool_shape{top:50%;left:50%;transform:translateX(-50%) translateY(-50%)}.c-tool.-progress .c-tool_shape.-fourth{z-index:1}.c-tool.-parallax .c-tool_shape{left:calc(var(--left, 0));top:calc(var(--top, 0));transform:translateX(-50%) translateY(-50%)}.c-tool.-parallax .c-tool_shape.-first{--top: 40%;--left: 16%}.c-tool.-parallax .c-tool_shape.-second{--top: 55%;--left: 34%}.c-tool.-parallax .c-tool_shape.-third{--top: 30%;--left: 53%}.c-tool.-parallax .c-tool_shape.-fourth{--top: 65%;--left: 68%}.c-tool.-parallax .c-tool_shape.-fifth{--top: 40%;--left: 86%}.c-tool.-inview .c-tool_shape_svg{transform:scale3d(0, 0, 1)}html.is-first-loaded .c-tool.-inview .c-tool_shape_svg{transition:transform .5s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-tool.-inview .c-tool_shape.is-inview .c-tool_shape_svg{transform:scale3d(1, 1, 1)}.c-tool.-progress .c-tool_shape_svg{display:block;transform:translate3d(calc((0.5 + 0.5 * var(--progress)) * var(--index) * 100%), 0, 0)}@media(max-width: 699px){.c-features-grid{--rows: 6;--columns: 2;--cell-ratio: 180/220;--grid-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 361.5 1980'%3E%3Cpath fill='%23282828' fill-rule='evenodd' d='M173 1760H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v211h1v-211c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8v-203c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V889c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V669c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V449c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V229c0-4.4 3.6-8 8-8h171.5v-1H190c-4.4 0-8-3.6-8-8V.5c0-.3-.2-.5-.5-.5s-.5.2-.5.5V212c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8H1.5c-.3 0-.5.2-.5.5s.2.5.5.5H173c4.4 0 8 3.6 8 8v203c0 4.4-3.6 8-8 8z' clip-rule='evenodd'/%3E%3C/svg%3E%0A")}}@media(min-width: 700px){.c-features-grid{--rows: 6;--columns: 4;--cell-ratio: 1;--grid-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 1400 4198'%3E%3Cpath fill='%23282828' fill-rule='evenodd' d='M342 349a8 8 0 0 0 8-8V.5a.5.5 0 0 1 1 0V341a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V.5a.5.5 0 0 1 1 0V341a8 8 0 0 0 8 8h333a8 8 0 0 0 8-8V.5c0-.276.22-.5.5-.5s.5.224.5.5V341a8 8 0 0 0 8 8h340c.28 0 .5.224.5.5s-.22.5-.5.5h-340a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h340c.28 0 .5.224.5.5s-.22.5-.5.5h-340a8 8 0 0 0-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v333c0 4.42 3.58 8 8 8h339c.28 0 .5.22.5.5s-.22.5-.5.5h-339c-4.42 0-8 3.58-8 8v339.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V3858c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v339.5c0 .28-.224.5-.5.5s-.5-.22-.5-.5V3858a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v339.5c0 .28-.224.5-.5.5s-.5-.22-.5-.5V3858a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H.5c-.276 0-.5-.22-.5-.5s.224-.5.5-.5H342a8 8 0 0 0 8-8V708a8 8 0 0 0-8-8H.5a.5.5 0 0 1 0-1H342a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8H.5a.5.5 0 0 1 0-1H342Zm17 1a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8H359Zm350.5 0a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333a8 8 0 0 0 8-8V358a8 8 0 0 0-8-8h-333Zm341 358a8 8 0 0 0-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8V708Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm0 350c0-4.42-3.58-8-8-8h-333a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333c4.42 0 8-3.58 8-8v-333Zm-358 341a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333a8 8 0 0 0 8 8h333.5ZM351 3491a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8v-333a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Zm0-350a8 8 0 0 0 8 8h333.5a8 8 0 0 0 8-8V708a8 8 0 0 0-8-8H359a8 8 0 0 0-8 8v333Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A")}}.c-features-grid{position:relative}.c-features-grid:before{content:"";display:block;width:100%;padding-top:calc((var(--rows) + 2)/var(--columns)*1/(var(--cell-ratio))*100%);pointer-events:none}.c-features-grid:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-image:var(--grid-image);pointer-events:none}.c-features-grid_container{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.c-features-grid_container::before,.c-features-grid_container::after{content:"";position:absolute;left:0;width:100%;padding-top:calc(1/var(--columns)*1/(var(--cell-ratio))*100%);background-color:var(--color-background);z-index:1}.c-features-grid_container::before{top:0;-webkit-mask-image:-webkit-linear-gradient(bottom, rgba(0, 0, 0, 0), rgb(0, 0, 0))}.c-features-grid_container::after{bottom:0;-webkit-mask-image:-webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgb(0, 0, 0))}.c-features-grid_inner{position:relative;display:grid;grid-template-columns:repeat(var(--columns), 1fr);grid-template-rows:repeat(var(--rows), 1fr);width:100%;height:100%;padding-top:calc(1/var(--columns)*1/(var(--cell-ratio))*100%);padding-bottom:calc(1/var(--columns)*1/(var(--cell-ratio))*100%);pointer-events:none}@media(max-width: 699px){.c-features-grid_inner{grid-template-areas:"locomotive lenis" ". scrollbar" "normalized sticky" "io sticky" "direction scroll-to" ". custom-easing"}}@media(min-width: 700px){.c-features-grid_inner{grid-template-areas:"scrollbar . lenis locomotive" ". normalized . ." ". . sticky ." "io . sticky ." ". scroll-to sticky direction" ". custom-easing sticky direction"}}.c-features-grid_cell{position:relative;text-align:left}.c-features-grid_cell.-lenis{grid-area:lenis}.c-features-grid_cell.-locomotive{grid-area:locomotive}.c-features-grid_cell.-scrollbar{grid-area:scrollbar}.c-features-grid_cell.-normalized{grid-area:normalized}.c-features-grid_cell.-sticky{grid-area:sticky}.c-features-grid_cell.-io{grid-area:io}.c-features-grid_cell.-scroll-to{grid-area:scroll-to}.c-features-grid_cell.-direction{grid-area:direction}.c-features-grid_cell.-custom-easing{grid-area:custom-easing}@media(min-width: 700px){.c-features-grid_cell.-io{transition:transform .75s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-features-grid_cell.-io.is-inview{transform:translate3d(100%, 0, 0)}}.c-features-grid_cell_ratio{display:flex;aspect-ratio:var(--cell-ratio);padding:1rem;pointer-events:auto}@media(min-width: 700px){.c-features-grid_cell.-sticky .c-features-grid_cell_ratio{position:sticky;top:calc(var(--header-height) + 1px);z-index:1}.c-features-grid_cell.-sticky .c-features-grid_cell_ratio::before{content:"";position:absolute;background-color:var(--color-background);z-index:-1;top:-1px;left:0px;width:calc(100% + 1px);height:calc(100% + 1px);border:1px solid #282828;border-radius:calc(var(--vw, 1vw)*.6)}}.c-features-grid_cell.-direction .c-features-grid_cell_ratio{position:relative}@media(min-width: 700px){.c-features-grid_cell.-io .c-features-grid_cell_ratio{z-index:1}.c-features-grid_cell.-io .c-features-grid_cell_ratio::before{content:"";position:absolute;top:-1px;left:0px;width:calc(100% + 1px);height:calc(100% + 1px);background-color:var(--color-background);border-top:1px solid #282828;border-right:1px solid #282828;border-bottom:1px solid #282828;border-top-right-radius:calc(var(--vw, 1vw)*.6);border-bottom-right-radius:calc(var(--vw, 1vw)*.6);z-index:-1}.c-features-grid_cell.-io.is-inview .c-features-grid_cell_ratio::before{border-left:1px solid #282828;border-radius:calc(var(--vw, 1vw)*.6)}}.c-features-grid_cell_inner{position:relative;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;width:100%;padding-right:1.875rem}.c-features-grid_cell_inner.-logo{padding-right:0}.c-features-grid_link{position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}@media(hover: hover){.c-features-grid_link::before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;opacity:.07;background-color:currentColor;clip-path:polygon(80% 20%, 80% 20%, 80% 20%, 20% 80%, 20% 80%, 20% 80%, 20% 80%, 80% 20%);transition:clip-path .25s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-features-grid_link:hover::before{animation-play-state:running;clip-path:polygon(15% 5%, 85% 5%, 95% 15%, 95% 85%, 85% 95%, 15% 95%, 5% 85%, 5% 15%)}}.c-features-grid_title{display:inline-block;text-wrap:balance}.c-features-grid_cell.-direction .c-features-grid_title{transition:transform .5s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-features-grid_cell.-link .c-features-grid_title{position:absolute;bottom:0;left:0;clip-path:inset(0 -1rem)}.c-features_direction_title{display:flex;gap:.625rem}.c-features_direction_arrows{position:relative;clip-path:inset(0)}.c-features_direction_arrows span{--translate: 0%;display:block;transition:transform .6s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-features_direction_arrows span:nth-of-type(1){transform:translate3d(0, var(--translate), 0)}.c-features_direction_arrows span:nth-of-type(2){position:absolute;transform:translate3d(0, calc(var(--translate) + 100%), 0);top:0;left:0}html.is-scrolling-up .c-features_direction_arrows span{--translate: -100%}.c-features-grid_title_label{display:inline-block}@media(hover: none){.c-features-grid_title_label:nth-child(2){display:none}}@media(hover: hover){.c-features-grid_title_label{transition:transform .5s cubic-bezier(0.23, 1, 0.32, 1)}.c-features-grid_title_label:nth-child(2){position:absolute;top:0;left:0;transform:translate3d(-1rem, -100%, 0)}.c-features-grid_cell:hover .c-features-grid_title_label:nth-child(1){transform:translate3d(1rem, 100%, 0)}.c-features-grid_cell:hover .c-features-grid_title_label:nth-child(2){transform:translate3d(0, 0, 0)}}.c-features-grid_desc{max-width:13.75rem}@media(max-width: 699px){.c-features-grid_desc{opacity:.6}}.c-features-grid_index{position:absolute;top:0;right:0}.c-features-grid_logo{display:flex;justify-content:center;align-items:center;width:100%;height:100%}@media(hover: hover){.c-features-grid_logo svg{transition:transform .5s cubic-bezier(0.23, 1, 0.32, 1)}.c-features-grid_cell:hover .c-features-grid_logo svg{transform:scale3d(0.95, 0.95, 1);animation:blink 100ms step-start 3}}.c-features-grid_icon-container{position:absolute;top:.625rem;right:.625rem;display:flex;overflow:hidden}.c-features-grid_icon{padding:.375rem}.c-features-grid_icon:nth-child(2){position:absolute;top:0;left:0;width:100%;height:100%;transform:translate3d(-100%, 100%, 0)}html.is-first-loaded .c-features-grid_icon{transition:transform .5s cubic-bezier(0.23, 1, 0.32, 1)}html.is-first-loaded .c-features-grid_cell:hover .c-features-grid_icon:nth-child(1){transform:translate3d(100%, -100%, 0)}html.is-first-loaded .c-features-grid_cell:hover .c-features-grid_icon:nth-child(2){transform:translate3d(0, 0, 0)}@keyframes blink{50%{opacity:0}}.c-sticky-heading{height:300vh}@media(max-aspect-ratio: 1.25/1){.c-sticky-heading{height:auto;padding-top:clamp(calc(0.0625rem * var(--spacing-2xl-mobile)),var(--spacing-2xl-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-2xl-desktop)))}}.c-sticky-heading_inner{position:sticky;top:0;display:flex;align-items:flex-end;width:100%;height:calc(var(--vh, 1vh)*100);overflow:hidden}@media(max-aspect-ratio: 1.25/1){.c-sticky-heading_inner{align-items:start;height:auto;position:relative;padding-top:clamp(calc(0.0625rem * var(--spacing-xs-mobile)),var(--spacing-xs-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-xs-desktop)))}}.c-sticky-heading_title{font-family:"PP Locomotive New",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-weight:300;line-height:.85;letter-spacing:-0.03em;font-size:calc(0.5*100*var(--vw, 1vw));width:auto;padding-right:calc(var(--grid-margin)*2);transform:translate3d(calc(-1 * var(--progress) * (100% - var(--vw, 1vw) * 100)), 0, 0)}@media(max-aspect-ratio: 1.25/1){.c-sticky-heading_title{font-size:calc(0.6041666667*49*var(--vw, 1vw));transform:none}}.c-sticky-heading_description{margin-left:calc(100vw - 13.75rem - var(--grid-margin)*2);max-width:13.75rem;margin-bottom:clamp(calc(0.0625rem * var(--spacing-sm-mobile)),var(--spacing-sm-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-sm-desktop)))}@media(min-width: 1000px){.c-sticky-heading_description{max-width:17.5rem;margin-left:50vw;font-size:calc(0.5*100*var(--vw, 1vw));margin-bottom:-0.075em}}.c-sticky_scroll{display:block;position:absolute;top:0;height:100%;width:100%}.c-list{position:relative}.c-list::before{content:"";position:absolute;top:0;left:0;width:100%;height:1px;background-color:currentColor;transform-origin:top right;transform:scale3d(0, 1, 1)}html.is-first-loaded .c-list::before{transition:transform .5s cubic-bezier(0.77, 0, 0.175, 1)}html.is-first-loaded .c-list.is-inview::before{transform:scale3d(1, 1, 1)}@media(min-width: 1000px){.c-list{display:grid;grid-template-columns:repeat(4, 1fr);-moz-column-gap:var(--grid-gutter);column-gap:var(--grid-gutter)}}[data-theme=dark] .c-list *:focus-visible:not(input):not(textarea):not(select){outline-color:#fff;outline-style:solid;outline-width:2px;outline-offset:-4px}[data-theme=white] .c-list *:focus-visible:not(input):not(textarea):not(select){outline-color:#202ded;outline-style:solid;outline-width:2px;outline-offset:-4px}[data-theme=blue] .c-list *:focus-visible:not(input):not(textarea):not(select){outline-color:#fff;outline-style:solid;outline-width:2px;outline-offset:-4px}.c-list *:focus-visible:not(input):not(textarea):not(select){outline-color:currentColor;outline-style:solid;outline-width:2px;outline-offset:2px;padding-inline:.5rem}.c-list_inner{font-size:0}@media(min-width: 1000px){.c-list_inner{grid-column:2/-1}}.c-list_item_inner{position:relative;clip-path:inset(0);padding-block:.75rem}.c-list_item_inner::before{content:"";position:absolute;top:0;left:0;width:100%;height:1px;background-color:currentColor;transform-origin:top right;transform:scale3d(0, 1, 1)}html.is-first-loaded .c-list_item_inner::before{transition:transform .5s cubic-bezier(0.77, 0, 0.175, 1)}html.is-first-loaded .c-list.is-inview .c-list_item_inner::before{transform:scale3d(1, 1, 1);transition-delay:calc((var(--index) + 1)*.08s)}@media(max-width: 699px){.c-list_item_inner{display:flex;flex-direction:column}.-showcase .c-list_item_inner{display:grid;grid-template-columns:1fr;gap:.9375rem;padding-right:1.875rem}.-perks .c-list_item_inner{padding-block:1.25rem}}@media(min-width: 700px){.c-list_item_inner{display:grid;align-items:center}}@media(min-width: 700px)and (max-width: 999px){.c-list_item_inner{grid-template-columns:1fr 1fr}.c-list.-showcase .c-list_item_inner{grid-template-columns:4fr 2fr 1fr}}@media(min-width: 1000px){.c-list_item_inner{grid-template-columns:1fr 2fr}.c-list.-showcase .c-list_item_inner{grid-template-columns:4fr 2fr 1fr}}.c-list_item_inner .c-list-anchor{text-align:end}@media(max-width: 699px){.c-list_item_inner .c-list-anchor{position:absolute;right:0;bottom:0}.-showcase .c-list_item_inner .c-list-anchor{top:.625rem}}@media(max-width: 699px){.c-list_title.-body-regular{--font-size-body-regular: var(--font-size-body-medium)}}@media(min-width: 1000px){.c-list_title{grid-column:1/2}}@media(max-width: 699px){.c-list_description{margin-top:.5rem}.-showcase .c-list_description{display:none}}@media(min-width: 1000px){.c-list_description{grid-column:2/-1}.c-list.-showcase .c-list_description{grid-column:auto}}.c-list_title,.c-list_description,.c-list-anchor{opacity:0;transform:translate3d(0, 100%, 0)}html.is-first-loaded .c-list_title,html.is-first-loaded .c-list_description,html.is-first-loaded .c-list-anchor{transition:transform .5s cubic-bezier(0.165, 0.84, 0.44, 1),opacity .5s cubic-bezier(0.165, 0.84, 0.44, 1)}html.is-first-loaded .c-list.is-inview .c-list_title,html.is-first-loaded .c-list.is-inview .c-list_description,html.is-first-loaded .c-list.is-inview .c-list-anchor{opacity:1;transform:translate3d(0, 0, 0);transition-delay:calc((var(--index) + 1)*.08s + .25s)}.c-footer_attributes{align-items:flex-start}@media(max-width: 999px){.c-footer_attributes div{white-space:nowrap}}.c-footer_attributes div p{line-height:1.25;opacity:0}@media(max-width: 999px){.c-footer_attributes div:nth-of-type(1){grid-column:3/1}}@media(max-width: 999px){.c-footer_attributes div:nth-of-type(2){grid-column:5/3;margin-top:10rem}}@media(min-width: 1000px){.c-footer_attributes div:nth-of-type(2){margin-top:15rem}}@media(max-width: 999px){.c-footer_attributes div:nth-of-type(3){grid-column:5/2;margin-top:5rem}}@media(min-width: 1000px){.c-footer_attributes div:nth-of-type(3){margin-top:10rem}}@media(max-width: 999px){.c-footer_attributes div:nth-of-type(4){grid-column:3/1;margin-top:5rem}}@media(min-width: 1000px){.c-footer_attributes div:nth-of-type(4){margin-top:25rem}}.c-footer_thanks{grid-column:5/2;margin-top:calc(clamp(calc(0.0625rem * var(--spacing-3xl-mobile)),var(--spacing-3xl-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-3xl-desktop)))*1.5)}@media(min-width: 1000px){.c-footer_thanks{grid-column:5/3;margin-top:clamp(calc(0.0625rem * var(--spacing-3xl-mobile)),var(--spacing-3xl-desktop)/14.4*1vw,calc(0.0625rem * var(--spacing-3xl-desktop)))}}.c-footer_website{opacity:0;transform:translate3d(0, 100%, 0);transition:transform .75s cubic-bezier(0.165, 0.84, 0.44, 1),opacity .75s cubic-bezier(0.165, 0.84, 0.44, 1)}.c-footer_website.is-inview{opacity:1;transform:translate3d(0, 0, 0)}.c-fadein-text_line{background:var(--background);-webkit-background-clip:text;color:rgba(0,0,0,0)}.fadeInText.-blue{--color-cta-fadein: #898fe9;--color: white}.fadeInText.-white{--color-cta-fadein: #cacdff;--color: #202ded}.fadeInText.-black{--color-cta-fadein: #424242;--color: white}.c-preloader{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1000;cursor:wait;background-color:#202ded;pointer-events:none}html.is-ready .c-preloader{transition:opacity .6s cubic-bezier(0.215, 0.61, 0.355, 1),visibility 0s .6s cubic-bezier(0.215, 0.61, 0.355, 1);opacity:0;visibility:hidden}.u-gc-2\/3{--gc-start: 2;--gc-end: 3}@media(min-width: 1000px){.u-gc-2\/5\@from-medium{--gc-start: 2;--gc-end: 5}}:root{--color-text: #202ded;--color-background: white}[data-theme=white]{color:var(--color-text);background-color:var(--color-background)}[data-theme=blue]{--color-text: white;--color-background: #202ded;color:var(--color-text);background-color:var(--color-background)}[data-theme=black]{--color-text: white;--color-background: black;color:var(--color-text);background-color:var(--color-background)}.u-relative{position:relative}.u-clipped{clip-path:polygon(0% 0, 100% 0%, 100% 100%, 0 100%)}.u-max-w300{max-width:18.75rem}.u-max-w440{max-width:27.5rem}.u-glyph{font-family:"PP Locomotive New",-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-feature-settings:"dlig" on,"ss01" on,"salt" on;font-weight:400}.u-hover-underline{position:relative}@media(hover: hover){.u-hover-underline::before{content:"";position:absolute;bottom:-0.1em;left:0;width:100%;height:1px;background-color:currentColor;transform:scale3d(0, 1, 1);transition:transform .25s cubic-bezier(0.165, 0.84, 0.44, 1);transform-origin:top right}.u-hover-underline:hover::before,.u-hover:hover .u-hover-underline::before{transform:scale3d(1, 1, 1);transform-origin:top left}}.u-screen-reader-text{position:absolute !important;overflow:hidden;clip:rect(0 0 0 0);margin:0;padding:0;width:1px;height:1px;border:0}@media not print{.u-screen-reader-text\@screen{position:absolute !important;overflow:hidden;clip:rect(0 0 0 0);margin:0;padding:0;width:1px;height:1px;border:0}}.u-screen-reader-text.-focusable:focus,.u-screen-reader-text.-focusable:active{clip:auto;width:auto;height:auto}.u-external-icon{font-size:.85em}.u-text-balance{text-wrap:balance}@media(max-width: 999px){.u-hidden-md{display:none}}.u-padding-top-sm{padding-top:clamp(calc(0.0625rem * var(--spacing-sm-mobile)), var(--spacing-sm-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-sm-desktop))) !important}.u-padding-top-md{padding-top:clamp(calc(0.0625rem * var(--spacing-md-mobile)), var(--spacing-md-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-md-desktop))) !important}.u-padding-top-lg{padding-top:clamp(calc(0.0625rem * var(--spacing-lg-mobile)), var(--spacing-lg-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-lg-desktop))) !important}.u-padding-bottom-lg{padding-bottom:clamp(calc(0.0625rem * var(--spacing-lg-mobile)), var(--spacing-lg-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-lg-desktop))) !important}.u-padding-bottom-2xl{padding-bottom:clamp(calc(0.0625rem * var(--spacing-2xl-mobile)), var(--spacing-2xl-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-2xl-desktop))) !important}.u-padding-bottom-3xl{padding-bottom:clamp(calc(0.0625rem * var(--spacing-3xl-mobile)), var(--spacing-3xl-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-3xl-desktop))) !important}.u-margin-top-2xs{margin-top:clamp(calc(0.0625rem * var(--spacing-2xs-mobile)), var(--spacing-2xs-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-2xs-desktop))) !important}.u-margin-top-sm{margin-top:clamp(calc(0.0625rem * var(--spacing-sm-mobile)), var(--spacing-sm-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-sm-desktop))) !important}.u-margin-top-xl{margin-top:clamp(calc(0.0625rem * var(--spacing-xl-mobile)), var(--spacing-xl-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-xl-desktop))) !important}.u-margin-bottom-3xl{margin-bottom:clamp(calc(0.0625rem * var(--spacing-3xl-mobile)), var(--spacing-3xl-desktop) / 14.4 * 1vw, calc(0.0625rem * var(--spacing-3xl-desktop))) !important} ================================================ FILE: www/landing/assets.json ================================================ { "version": 1768591940829 } ================================================ FILE: www/landing/index.html ================================================ Locomotive Scroll — Detection of elements in viewport & smooth scrolling with parallax effects

Locomotive Scroll

Version 5.0

Github↗
Documentation↗

©2026

🟦🔁 Locomotive® Scroll

A lightweight scroll library for modern web experiences. Detection, animation, and smooth scrolling — all in 9.4kB. Built on top of Lenis.

🔻 Version 5.0

  • View on Github View on Github
  • Read documentation Read documentation

Version (5.0) 🏢

  • Built on top of Lenis

    Latest stable release with improved touch handling and performance fixes. No more greedy CSS transforms!

  • TypeScript First

    Fully typed. Better autocomplete, fewer bugs, happier developers.

  • Dual Intersection Observers

    Separate observers for simple triggers vs. continuous animations. No wasted RAF cycles.

  • Smart Touch Detection

    Parallax auto-disabled on mobile. Opt-in with one attribute.

  • Production Ready

    Accessible by default. Native scrollbar, keyboard nav, and proper cleanup for SPAs.

  • Lightweight

    Only 9.4kB gzipped

Version 5 is a complete rewrite. Designed for modern workflows, built on top of Lenis, and optimized for production.

⛵
This library has evolved considerably over the years. From jQuery to vanilla ES6, from custom engines to Lenis foundation.
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll
Locomotive® Scroll

👀

Built-in Tools 🔴Work faster Work smarter🔴

01 In-view detection Add classes, trigger callbacks, or fire custom events when elements enter the viewport. Built on Intersection Observer for native performance.

In-view detection

02 Progress tracking Get real-time scroll progress (0-1) as CSS variables or JavaScript events. Perfect for progress bars, scroll-driven animations, or custom interactions.

Progress tracking

02 Parallax effects Create smooth parallax effects instantly with a single data-scroll-speed attribute. No complex setup, no math, just add a number and it works.

Parallax effects

Features

⛵
Locomotive Scroll is a thin, opinionated wrapper around Lenis. You get all of Lenis's power plus our detection and animation layer.
Visit Lenis website
Lenis Lenis
Visit Locomotive website
Locomotive Locomotive
01

Native scrollbar

Real browser scrollbar. No fake alternatives. Accessible, performant, and familiar to users.

02

Smooth easing

Configurable lerp, duration, and custom easing functions thanks to Lenis options.

03

CSS sticky

Works perfectly with position: sticky. No conflicts, no workarounds.

04

Intersection Observer API

Browser-native detection. No polling, no performance hits, just efficient viewport tracking.

05 Scroll to Programmatic scrolling to any element, selector, or pixel value via Lenis. Supports offset, duration, and custom easing.
06

Scroll direction

↓ ↑

Track direction changes in real-time thanks to Lenis. Perfect for hiding/showing headers or reversing animations.

07

No layout shifts

No more greedy CSS transforms breaking your layouts. Plays nice with position: sticky, fixed headers, and existing CSS.

⛵
Real projects built with Locomotive Scroll by leading studios and developers.

Showcase

  • Locomotive https://locomotive.ca ↗
  • Destigmatize https://2024.destigmatize.ca/ ↗
  • Scout Motors https://scoutmotors.com/ ↗
  • Lightship https://lightshiprv.com/ ↗
  • Vooban https://vooban.com/ ↗
  • Construction Desourdy https://constructiondesourdy.com/ ↗
  • GKC https://gkc.ca/ ↗
  • Troa https://www.troa.fr/ ↗
  • Vazzi https://vazzi.fun/ ↗
  • 21TSI https://21tsi.com/ ↗
  • Eduard Bodak https://www.eduardbodak.com/ ↗
  • Mindmarket https://mindmarket.com/ ↗

npm

npm install

npm install locomotive-scroll

npm install

npm

locomotive

locomotive-scroll

locomotive-scroll v5.x

locomotive-scroll v5.x

locomotive-scroll

locomotive

data

data-scroll

data-scroll-speed

data-scroll-class

data-scroll-repeat

data

data-scroll

data-scroll-position

data-scroll-call

data-scroll-event-progress

We hope that you enjoy our library.

🟦🔁 Locomotive® Scroll

Made by Locomotive

  • View on Github View on Github
  • Read documentation Read documentation