Showing preview only (1,053K chars total). Download the full file or copy to clipboard to get everything.
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
[](https://www.npmjs.com/package/locomotive-scroll)
[](https://www.npmjs.com/package/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
<div data-scroll data-scroll-speed="0.5">I move at half speed</div>
```
## 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
================================================
<p align="center">
<a href="https://github.com/locomotivemtl/locomotive-boilerplate">
<img src="https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png" height="140">
</a>
</p>
<h1 align="center">Locomotive Astro Boilerplate</h1>
<p align="center">Front-end Astro boilerplate for projects by <a href="https://locomotive.ca/">Locomotive</a>.</p>
## 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
================================================
<button class="c-scroll-toggler" type="button" id="scrollToggler">
stop scroll
</button>
<script>
import { isScrollStopped } from '../../stores/scroll';
const $scrollToggler = document.getElementById('scrollToggler');
$scrollToggler?.addEventListener('click', () => {
isScrollStopped.set(!isScrollStopped.get());
});
isScrollStopped.subscribe(value => {
if ($scrollToggler) {
if (value) {
$scrollToggler.innerHTML = 'start scroll';
} else {
$scrollToggler.innerHTML = 'stop scroll';
}
}
});
</script>
================================================
FILE: packages/demo/src/env.d.ts
================================================
/// <reference types="astro/client" />
================================================
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',
]
---
<!doctype html>
<html lang="en" data-scroll-orientation-settings={scrollOrientation}>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<!--
<SEO
title={seo?.title || title || defaultSeo.title}
description={seo?.description || defaultSeo.description}
openGraph={{
basic: {
type: 'website',
title: seo?.social?.facebook?.title || defaultSeo.social.facebook.title,
image: seo?.social?.facebook?.image?.url || defaultSeo.social.facebook.image.url
}
}}
twitter={{
creator: seo?.social?.twitter?.creator || defaultSeo.social.twitter.creator,
image: seo?.social?.twitter?.image?.url || defaultSeo.social.twitter.image.url,
title: seo?.social?.twitter?.title || defaultSeo.social.twitter.title,
description:
seo?.social?.twitter?.description || defaultSeo.social.twitter.description
}}
canonical={seo?.advanced?.canonical || defaultSeo.advanced.canonical}
noindex={seo?.advanced?.robots?.includes('noindex') ||
defaultSeo?.advanced?.robots?.includes('noindex')}
nofollow={seo?.advanced?.robots?.includes('nofollow') ||
defaultSeo?.advanced?.robots?.includes('noindex')}
extend={{
meta: [
{
name: 'robots',
content:
seo?.advanced?.robots?.join(',') || defaultSeo.advanced.robots.join(',')
}
]
}}
/> -->
<!-- Fonts -->
{FONTS.map(font =>
<link
rel="preload"
href={`/fonts/${font}`}
as="font"
type="font/woff2"
crossorigin
/>
)}
</head>
<body>
<main role="main" id="swup" class="transition-fade">
<div id="content">
<slot />
</div>
</main>
<script src="../scripts/app.ts"></script>
</body>
</html>
================================================
FILE: packages/demo/src/pages/horizontal.astro
================================================
---
import Layout from '@layouts/Layout.astro';
---
<Layout title="About" scrollOrientation="horizontal">
<div>
<main id="top">
<div class="c-scroll_offset" style="--offset-start: 10%; --offset-end: 10%;"></div>
<div class="c-scroll_section -full -centered">
<h1 style="font-size: 10vw; opacity: calc(1 - var(--progress));" data-scroll data-scroll-css-progress data-scroll-enable-touch-speed>Locomotive Scroll</h1>
<div style="display:flex; font-size: 10vw">
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed=".25">V</p>
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed=".2">.</p>
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed=".15">0</p>
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed=".1">X</p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-speed</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full -parallax" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-2)" data-scroll data-scroll-enable-touch-speed data-scroll-speed="-.1">
<p>-.1</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll data-scroll-enable-touch-speed data-scroll-speed="0">
<p>0</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-4); color:white;" data-scroll data-scroll-enable-touch-speed data-scroll-speed=".5">
<p>.5</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-scroll data-scroll-enable-touch-speed data-scroll-speed="-.2">
<p>-.2</p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-class</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-2); opacity: 0;" data-scroll data-scroll-offset="10%, 10%" data-scroll-class="c-scroll-opacity">
<p>c-scroll-opacity</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white; transform: rotate(0);" data-scroll data-scroll-offset="10%, 10%" data-scroll-class="c-scroll-rotate">
<p>c-scroll-rotate</p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-repeat</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-5); color:white; opacity: 0;" data-scroll data-scroll-offset="10%, 10%" data-scroll-repeat data-scroll-class="c-scroll-opacity">
<p>c-scroll-opacity</p>
<p>REPEAT</p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-css-progress</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-4); opacity: var(--progress); color:white;" data-scroll data-scroll-css-progress data-scroll-offset="10%, 10%">
<p>css progress variable</p>
</div>
</div>
<!-- <div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-module-progress</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-module-progress data-scroll data-scroll-module-progress data-scroll-offset="10%, 10%">
<p data-progress="progress"></p>
</div>
</div> -->
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-event-progress: Custom Event</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll data-scroll-event-progress="progressEvent" data-scroll-offset="10%, 10%">
<p data-custom-event="progress"></p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-position</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-2); color:white;" data-scroll-position="start,end" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: start</p>
<p>Position Leave: end</p>
<p data-position-progress></p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll-position="middle,middle" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: middle</p>
<p>Position Leave: middle</p>
<p data-position-progress></p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-scroll-position="end,start" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: end</p>
<p>Position Leave: start</p>
<p data-position-progress></p>
</div>
</div>
<div class="c-scroll_section">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">data-scroll-call: Custom Event</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-4); color:white;" data-scroll-position="end,start" data-scroll-repeat data-scroll data-scroll-call="scrollEvent" data-scroll-offset="10%, 10%">
<p data-custom-event="event"></p>
</div>
</div>
<div class="c-scroll_section" id="Direction">
<div class="u-whitespace-nowrap">
<h2 style="font-size: 3vw">Lenis scroll calback: direction</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 10vw;">
<div class="c-scroll_box c-scroll-direction">
<button data-scroll-to data-scroll-to-href="#top">
⬇️
</button>
</div>
</div>
</main>
</div>
</Layout>
================================================
FILE: packages/demo/src/pages/index.astro
================================================
---
import Layout from "../layouts/Layout.astro"
import ScrollToggler from '@components/ScrollToggler/ScrollToggler.astro';
---
<Layout title="test">
<div>
<div>
<ScrollToggler />
<main id="top">
<div class="c-scroll_offset" style="--offset-start: 10%; --offset-end: 10%;"></div>
<div class="c-scroll_section -full -centered">
<h1 style="font-size: 10vw; opacity: calc(1 - var(--progress));" data-scroll data-scroll-css-progress data-scroll-enable-touch-speed>Locomotive Scroll</h1>
<div style="display:flex; font-size: 10vw">
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed="-.1">V</p>
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed="0">.</p>
<p data-scroll data-scroll-enable-touch-speed data-scroll-speed=".1">5</p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-speed</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-2)" data-scroll data-scroll-speed="-.1">
<p>-.1</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll data-scroll-speed="0">
<p>0</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-4); color:white;" data-scroll data-scroll-speed=".5">
<p>.5</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-scroll data-scroll-speed="-.2">
<p>-.2</p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-class</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-2); opacity: 0;" data-scroll data-scroll-offset="10%, 10%" data-scroll-class="c-scroll-opacity">
<p>c-scroll-opacity</p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white; transform: rotate(0);" data-scroll data-scroll-offset="10%, 10%" data-scroll-class="c-scroll-rotate">
<p>c-scroll-rotate</p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-repeat</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw">
<div class="c-scroll_box" style="background-color: var(--color-5); color:white; opacity: 0;" data-scroll data-scroll-offset="10%, 10%" data-scroll-repeat data-scroll-class="c-scroll-opacity">
<p>c-scroll-opacity</p>
<p>REPEAT</p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-css-progress</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-4); opacity: var(--progress); color:white;" data-scroll data-scroll-css-progress data-scroll-offset="10%, 10%">
<p>css progress variable</p>
</div>
</div>
<!-- <div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-module-progress</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-module-progress data-scroll data-scroll-module-progress data-scroll-offset="10%, 10%">
<p data-progress="progress"></p>
</div>
</div> -->
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-event-progress: Custom Event</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll data-scroll-event-progress="progressEvent" data-scroll-offset="10%, 10%">
<p data-custom-event="progress"></p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-position</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-2); color:white;" data-scroll-position="start,end" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: start</p>
<p>Position Leave: end</p>
<p data-position-progress></p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-3); color:white;" data-scroll-position="middle,middle" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: middle</p>
<p>Position Leave: middle</p>
<p data-position-progress></p>
</div>
<div class="c-scroll_box" style="background-color: var(--color-5); color:white;" data-scroll-position="end,start" data-scroll data-scroll-event-progress="progressPositionEvent" data-scroll-offset="10%, 10%">
<p>Position Enter: end</p>
<p>Position Leave: start</p>
<p data-position-progress></p>
</div>
</div>
<div class="c-scroll_section">
<div class="o-container">
<h2 style="font-size: 3vw">data-scroll-call: Custom Event</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 2vw;">
<div class="c-scroll_box" style="background-color: var(--color-4); color:white;" data-scroll-position="end,start" data-scroll-repeat data-scroll data-scroll-call="scrollEvent" data-scroll-offset="10%, 10%">
<p data-custom-event="event"></p>
</div>
</div>
<div class="c-scroll_section" id="Direction">
<div class="o-container">
<h2 style="font-size: 3vw">Lenis scroll calback: direction</h2>
</div>
</div>
<div class="c-scroll_section -centered -row -full" style="font-size: 10vw;">
<div class="c-scroll_box c-scroll-direction">
<button data-scroll-to data-scroll-to-href="#top">
⬇️
</button>
</div>
</div>
</main>
</div>
</div>
</Layout>
================================================
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<IScreenValues>({
width: window.innerWidth,
height: window.innerHeight
});
export const $screenDebounce = map<IScreenDebounceValues>({
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<number>} 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<number>} 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<number>} 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<number>} 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<number>}
@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=<Your GitHub username> 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`

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


Here's an example of using Locomotive Scroll's data-scroll-position attribute on an HTML element:
```html
<div data-scroll data-scroll-position="start, start"></div>
```
## data-scroll-offset
- **Type:** `string`
- **Default:** `0,0`

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

Here's an example of using Locomotive Scroll's data-scroll-offset attribute on an HTML element:
```html
<div data-scroll data-scroll-offset="400, 200"></div>
```
## 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
<div data-scroll data-scroll-call="scrollEvent">Trigger</div>
```
```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
<div data-scroll data-scroll-event-progress="progressEvent">
Progress custom event
</div>
```
```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 (`<a>`), 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 disabled on touch devices (default) -->
<div data-scroll data-scroll-speed="0.5">
Parallax only on desktop
</div>
<!-- Parallax enabled on all devices including touch -->
<div data-scroll data-scroll-speed="0.5" data-scroll-enable-touch-speed>
Parallax on desktop AND mobile
</div>
```
**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 `<html>` 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
<div class="scroll-container" style="height: 100vh; overflow: hidden;">
<div class="scroll-content">
<div data-scroll data-scroll-speed="0.5">Parallax element</div>
</div>
</div>
```
**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
<main class="main">
<h1 class="title">Third Party Injected Popups</h1>
<!-- Injected popup DOM -->
<div id="modalSelector">
<h2>Scroll inside the modal</h2>
<!-- Modal content -->
</div>
</main>
```
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
<div data-scroll-container>
<div data-scroll-section>
<h1 data-scroll>Hey</h1>
</div>
</div>
```
```js
import LocomotiveScroll from 'locomotive-scroll';
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
});
```
### v5: Simpler Setup
```html
<main>
<h1 data-scroll>Hey</h1>
</main>
```
```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
<div data-scroll data-scroll-speed="2">Fast parallax</div>
```
### v5
```html
<!-- Parallax disabled on touch by default -->
<div data-scroll data-scroll-speed=".5">Fast parallax (desktop only)</div>
<!-- Enable on touch devices -->
<div data-scroll data-scroll-speed=".5" data-scroll-enable-touch-speed>
Fast parallax (all devices)
</div>
```
:::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
<div data-scroll data-scroll-call="myMethod, MyModule">Trigger</div>
```
```js
scroll.on('call', (func) => {
this.call(...func); // ModularJS
});
```
### v5: Native CustomEvents
```html
<div data-scroll data-scroll-call="videoTrigger">Trigger</div>
```
```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
<h1 data-scroll data-scroll-id="hero">Hero</h1>
```
```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
<h1 data-scroll data-scroll-event-progress="heroProgress">Hero</h1>
```
```js
window.addEventListener('heroProgress', (e) => {
console.log(e.detail.progress); // 0 to 1
});
```
### v5: Using CSS Variables
```html
<h1 data-scroll data-scroll-css-progress>Hero</h1>
```
```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
<div data-scroll data-scroll-sticky data-scroll-target="#target">
Sticky element
</div>
<div id="target">Target</div>
```
### v5: Use CSS `position: sticky`
```html
<div class="sticky-element">Sticky element</div>
```
```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
<div class="scroll-container" style="height: 100vh; overflow: hidden;">
<div class="scroll-content">
<div data-scroll data-scroll-speed="0.5">Parallax works here too!</div>
</div>
</div>
```
---
## Performance Improvements
### v4 Performance Patterns
```html
<!-- Recommended: Split into sections for better performance -->
<div data-scroll-container>
<div data-scroll-section>
<!-- Content here -->
</div>
<div data-scroll-section>
<!-- More content -->
</div>
</div>
```
### 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
<div data-scroll-container>
<div data-scroll-section>
<h1 data-scroll data-scroll-id="hero">Hero</h1>
<div data-scroll data-scroll-speed="2">Parallax</div>
<div data-scroll data-scroll-call="videoPlay, VideoModule">Video</div>
<div data-scroll-sticky data-scroll-target="#end">Sticky</div>
</div>
<div id="end" data-scroll-section>End</div>
</div>
```
```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
<main>
<h1 data-scroll data-scroll-event-progress="heroProgress">Hero</h1>
<div data-scroll data-scroll-speed="2">Parallax</div>
<div data-scroll data-scroll-call="videoPlay">Video</div>
<div class="sticky">Sticky</div>
<div>End</div>
</main>
```
```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
<script src="https://cdn.jsdelivr.net/npm/locomotive-scroll/bundled/locomotive-scroll.min.js"></script>
<script>
const locomotiveScroll = new LocomotiveScroll();
</script>
```
## 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
<script src="https://cdn.jsdelivr.net/npm/locomotive-scroll/bundled/locomotive-scroll.min.js"></script>
<script>
const locomotiveScroll = new LocomotiveScroll();
</script>
```
## 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
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/locomotive-scroll/bundled/locomotive-scroll.css"
/>
```
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
<main style="height: 150vh;">
<div>
<h1>Hello 👋</h1>
</div>
<div>
<h2 data-scroll data-scroll-speed="0.5">What's up?</h2>
<p data-scroll data-scroll-speed="0.8">😬</p>
</div>
</main>
```
```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 /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
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()} - <a href="https://locomotive.ca">Locomotive</a>`,
},
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
================================================
<p align="center">
<a href="https://github.com/locomotivemtl/locomotive-boilerplate">
<img src="https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png" height="140">
</a>
</p>
<h1 align="center">Locomotive Boilerplate</h1>
<p align="center">Front-end boilerplate for projects by <a href="https://locomotive.ca/">Locomotive</a>.</p>
## 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<words.length;i++) {
const chars = [...words[i].matchAll(SHUFFLE_PATTERN)].map(item => 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<T>} 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<T>} 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: mapRangeCl
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
SYMBOL INDEX (660 symbols across 51 files)
FILE: packages/demo/src/scripts/classes/Scroll.ts
class Scroll (line 9) | class Scroll {
method init (line 17) | static init() {
method destroy (line 54) | static destroy() {
method start (line 61) | static start() {
method stop (line 65) | static stop() {
method addScrollElements (line 69) | static addScrollElements(container: HTMLElement) {
method removeScrollElements (line 73) | static removeScrollElements(container: HTMLElement) {
method scrollTo (line 77) | static scrollTo(target: lenisTargetScrollTo, options?: ILenisScrollToO...
FILE: packages/demo/src/scripts/utils/setViewportSize.ts
constant SUPPORTS_VH (line 1) | const SUPPORTS_VH: boolean =
FILE: packages/demo/src/stores/screen.ts
type IScreenValues (line 4) | interface IScreenValues {
type IScreenDebounceValues (line 9) | interface IScreenDebounceValues {
FILE: packages/demo/types/global.d.ts
type Seo (line 1) | type Seo = {
FILE: packages/demo/types/swup.d.ts
type VisitType (line 1) | type VisitType = {
FILE: packages/landing/assets/scripts/app.js
function init (line 15) | function init() {
function bindEvents (line 86) | function bindEvents() {
function onOnrientationChange (line 117) | function onOnrientationChange() {
function onResize (line 124) | function onResize() {
FILE: packages/landing/assets/scripts/config.js
constant NODE_ENV (line 11) | const NODE_ENV = process.env.NODE_ENV
constant IS_MOBILE (line 12) | const IS_MOBILE = window.matchMedia('(any-pointer:coarse)').matches
constant ENV (line 15) | const ENV = Object.freeze({
constant CSS_CLASS (line 27) | const CSS_CLASS = Object.freeze({
constant CUSTOM_EVENT (line 39) | const CUSTOM_EVENT = Object.freeze({
constant FONT (line 45) | const FONT = Object.freeze({
FILE: packages/landing/assets/scripts/modules/Example.js
method constructor (line 6) | constructor(m) {
method init (line 10) | init() {
method onFontsLoaded (line 14) | onFontsLoaded(fonts) {
FILE: packages/landing/assets/scripts/modules/FadeInText.js
method constructor (line 7) | constructor(m) {
method init (line 26) | init() {
method destroy (line 34) | destroy() {
method bindEvents (line 47) | bindEvents() {
method unbindEvents (line 51) | unbindEvents() {
method onFadeinTextProgress (line 58) | onFadeinTextProgress(e) {
method splitText (line 68) | splitText() {
method computeMetrics (line 88) | computeMetrics(split) {
method computeProgress (line 108) | computeProgress() {
method updateGradient (line 119) | updateGradient($item, progress) {
FILE: packages/landing/assets/scripts/modules/HoverShuffle.js
constant DURATION (line 3) | const DURATION = 0.25;
constant SHUFFLE_PATTERN (line 4) | const SHUFFLE_PATTERN = /\p{Extended_Pictographic}|\r|\n|./gu
method constructor (line 7) | constructor(m) {
method init (line 11) | init() {
method update (line 18) | update() {
method reset (line 25) | reset() {
method shuffle (line 30) | shuffle(input) {
method onItemEnter (line 43) | onItemEnter(e) {
method onItemLeave (line 78) | onItemLeave(e) {
method shuffleElementTexts (line 97) | shuffleElementTexts(item, shuffleFn) {
FILE: packages/landing/assets/scripts/modules/Load.js
method constructor (line 5) | constructor(m) {
method init (line 9) | init() {
FILE: packages/landing/assets/scripts/modules/MaskLines.js
method constructor (line 11) | constructor(m) {
method init (line 28) | init() {
method destroy (line 35) | destroy() {
method unbindEvents (line 48) | unbindEvents() {
method bindEvents (line 52) | bindEvents() {
method onFontsLoaded (line 59) | onFontsLoaded(fonts) {
method onProgress (line 63) | onProgress(e) {
method split (line 80) | split() {
method computeMetrics (line 96) | computeMetrics(split) {
FILE: packages/landing/assets/scripts/modules/Rail.js
method constructor (line 7) | constructor(m) {
method init (line 39) | init() {
method destroy (line 46) | destroy() {
method bindEvents (line 55) | bindEvents() {
method unbindEvents (line 60) | unbindEvents() {
method onResize (line 68) | onResize() {
method onFontsLoaded (line 72) | onFontsLoaded() {
method onUpdate (line 76) | onUpdate() {
method onScroll (line 104) | onScroll(scrollData) {
method onToggle (line 117) | onToggle(e) {
method start (line 125) | start() {
method stop (line 131) | stop() {
method computeMetrics (line 137) | computeMetrics(reset = false) {
method repeatPattern (line 160) | repeatPattern() {
FILE: packages/landing/assets/scripts/modules/Randomize.js
class Ramdomize (line 7) | class Ramdomize extends module {
method constructor (line 8) | constructor(m) {
method init (line 15) | init() {
method destroy (line 19) | destroy() {
method bindEvents (line 23) | bindEvents() {
method unbindEvents (line 27) | unbindEvents() {
method inView (line 31) | inView(args) {
FILE: packages/landing/assets/scripts/modules/Scroll.js
class Scroll (line 5) | class Scroll extends module {
method constructor (line 6) | constructor(m) {
method init (line 14) | init() {
method destroy (line 30) | destroy() {
method bindEvents (line 38) | bindEvents() {
method unbindEvents (line 42) | unbindEvents() {
method onScroll (line 49) | onScroll({ scroll, limit, velocity, direction, progress }) {
method scrollTo (line 76) | scrollTo(params) {
method changeHeaderTheme (line 87) | changeHeaderTheme(args) {
method addScrollElements (line 101) | addScrollElements($newContainer) {
method removeScrollElements (line 110) | removeScrollElements($oldContainer) {
FILE: packages/landing/assets/scripts/utils/fonts.js
function conformsToReference (line 36) | function conformsToReference(font, criterion)
function conformsToShorthand (line 82) | function conformsToShorthand(font, criterion)
function conformsToAnyReference (line 110) | function conformsToAnyReference(font, criteria)
function findManyByReference (line 129) | function findManyByReference(search)
function findManyByShorthand (line 150) | function findManyByShorthand(search)
function findOneByReference (line 171) | function findOneByReference(search)
function findOneByShorthand (line 195) | function findOneByShorthand(search)
function getAny (line 219) | function getAny(search) {
function getMany (line 245) | function getMany(queries) {
function hasAny (line 284) | function hasAny(search) {
function loadFonts (line 305) | async function loadFonts(fontsToLoad, debug = false)
function loadFontFaceWithAPI (line 323) | async function loadFontFaceWithAPI(font)
function loadFontsWithAPI (line 339) | async function loadFontsWithAPI(fontsToLoad, debug = false)
function trim (line 376) | function trim(value) {
function whenReady (line 388) | async function whenReady(queries)
FILE: packages/landing/assets/scripts/utils/grid-helper.js
constant GRID_HELPER_GUTTER_CSS_VAR (line 18) | const GRID_HELPER_GUTTER_CSS_VAR = '--grid-gutter';
constant GRID_HELPER_MARGIN_CSS_VAR (line 19) | const GRID_HELPER_MARGIN_CSS_VAR = '--grid-margin';
constant GRID_HELPER_RGBA_COLOR (line 20) | const GRID_HELPER_RGBA_COLOR = 'rgba(255, 0, 0, .1)';
function gridHelper (line 28) | function gridHelper({
function setGridHelperStyles (line 53) | function setGridHelperStyles($container, gutterCssVar, marginCssVar) {
function setGridHelperColumns (line 76) | function setGridHelperColumns($container, rgbaColor) {
function setGridEvents (line 104) | function setGridEvents($container, rgbaColor) {
FILE: packages/landing/assets/scripts/utils/image.js
constant LAZY_LOADED_IMAGES (line 67) | const LAZY_LOADED_IMAGES = []
FILE: packages/landing/assets/scripts/utils/visibility.js
method isEnabled (line 39) | get isEnabled() {
method disableCustomEvents (line 49) | disableCustomEvents() {
method enableCustomEvents (line 65) | enableCustomEvents() {
function handleCustomVisibilityChange (line 96) | function handleCustomVisibilityChange(event) {
FILE: packages/landing/build/helpers/glob.js
function importGlob (line 76) | async function importGlob() {
function createArrayableGlob (line 131) | function createArrayableGlob(globFn) {
function createPresetGlob (line 153) | function createPresetGlob(globFn, presets) {
FILE: packages/landing/build/helpers/message.js
function message (line 14) | function message(text, type, timerID) {
FILE: packages/landing/build/helpers/notification.js
function notification (line 19) | function notification(options, callback) {
FILE: packages/landing/build/helpers/postcss.js
function createProcessor (line 89) | function createProcessor(pluginsListOrMap, options)
function parsePlugins (line 109) | function parsePlugins(pluginsListOrMap, options)
FILE: packages/landing/build/helpers/template.js
function resolve (line 29) | function resolve(input, data = templateData) {
function resolveValue (line 63) | function resolveValue(input, data = templateData) {
FILE: packages/landing/build/tasks/concats.js
function concatFiles (line 67) | async function concatFiles(globOptions = null, concatOptions = null) {
FILE: packages/landing/build/tasks/eleventy.js
function buildEleventy (line 18) | async function buildEleventy(eleventyOptions = null) {
FILE: packages/landing/build/tasks/scripts.js
function compileScripts (line 47) | async function compileScripts(esBuildOptions = null) {
FILE: packages/landing/build/tasks/styles.js
function compileStyles (line 85) | async function compileStyles(sassOptions = null, postcssOptions = null, ...
function purgeUnusedCSS (line 220) | async function purgeUnusedCSS(outfile, label) {
FILE: packages/landing/build/tasks/svgs.js
function compileSVGs (line 41) | async function compileSVGs(mixerOptions = null) {
FILE: packages/landing/build/tasks/versions.js
constant REGEXP_SEMVER (line 24) | const REGEXP_SEMVER = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<p...
function bumpVersions (line 93) | async function bumpVersions(versionOptions = null) {
function createVersionNumber (line 145) | function createVersionNumber(format, oldValue = null) {
function handleBumpVersion (line 208) | async function handleBumpVersion(outfile, label, options) {
function handleBumpVersionInJson (line 243) | async function handleBumpVersionInJson(outfile, label, options) {
function handleBumpVersionWithRegExp (line 277) | async function handleBumpVersionWithRegExp(outfile, label, options) {
function incrementNumber (line 328) | function incrementNumber(value, increment = 1) {
function incrementSemVer (line 356) | function incrementSemVer(value, target = 'patch', increment = 1) {
function parseVersionKey (line 408) | function parseVersionKey(key) {
FILE: packages/landing/build/utils/index.js
function escapeRegExp (line 16) | function escapeRegExp(str) {
function flatten (line 49) | function flatten(input, prefix, target = {}) {
function isObjectLike (line 70) | function isObjectLike(value) {
function merge (line 83) | function merge(target, ...sources) {
FILE: packages/landing/build/watch.js
function configureServer (line 52) | function configureServer(server, { paths, tasks }) {
function createServerOptions (line 112) | function createServerOptions({
function createViewsArray (line 154) | function createViewsArray(views) {
function prependSchemeToUrl (line 181) | function prependSchemeToUrl(url, scheme = 'http') {
function isNonEmptyString (line 196) | function isNonEmptyString(value) {
FILE: packages/landing/eleventy.config.cjs
function getAssetsVersion (line 13) | function getAssetsVersion() {
FILE: packages/lib/bundled/locomotive-scroll.js
function _arrayLikeToArray (line 6) | function _arrayLikeToArray(r, a) {
function _createForOfIteratorHelperLoose (line 11) | function _createForOfIteratorHelperLoose(r, e) {
function _extends (line 28) | function _extends() {
function _unsupportedIterableToArray (line 37) | function _unsupportedIterableToArray(r, a) {
function clamp$1 (line 51) | function clamp$1(min, input, max) {
function lerp (line 54) | function lerp(x, y, t) {
function damp (line 57) | function damp(x, y, lambda, deltaTime) {
function modulo (line 60) | function modulo(n, d) {
method advance (line 81) | advance(deltaTime) {
method stop (line 106) | stop() {
method fromTo (line 117) | fromTo(from, to, { lerp: lerp2, duration, easing, onStart, onUpdate }) {
function debounce (line 131) | function debounce(callback, delay) {
method constructor (line 145) | constructor(wrapper, content, { autoResize = true, debounce: debounceVal...
method destroy (line 169) | destroy() {
method limit (line 198) | get limit() {
method emit (line 214) | emit(event, ...args) {
method on (line 226) | on(event, cb) {
method off (line 237) | off(event, callback) {
method destroy (line 243) | destroy() {
method constructor (line 252) | constructor(element, options = { wheelMultiplier: 1, touchMultiplier: 1 ...
method on (line 289) | on(event, callback) {
method destroy (line 293) | destroy() {
method constructor (line 437) | constructor({
method destroy (line 543) | destroy() {
method on (line 572) | on(event, callback) {
method off (line 575) | off(event, callback) {
method overflow (line 596) | get overflow() {
method checkOverflow (line 600) | checkOverflow() {
method setScroll (line 612) | setScroll(scroll) {
method resize (line 723) | resize() {
method emit (line 728) | emit() {
method reset (line 762) | reset() {
method start (line 772) | start() {
method internalStart (line 780) | internalStart() {
method stop (line 789) | stop() {
method internalStop (line 797) | internalStop() {
method scrollTo (line 836) | scrollTo(target, {
method preventNextNativeScrollEvent (line 955) | preventNextNativeScrollEvent() {
method checkNestedScroll (line 961) | checkNestedScroll(node, { deltaX, deltaY }) {
method rootElement (line 1046) | get rootElement() {
method limit (line 1052) | get limit() {
method isHorizontal (line 1066) | get isHorizontal() {
method actualScroll (line 1072) | get actualScroll() {
method scroll (line 1079) | get scroll() {
method progress (line 1085) | get progress() {
method isScrolling (line 1091) | get isScrolling() {
method isScrolling (line 1094) | set isScrolling(value) {
method isStopped (line 1103) | get isStopped() {
method isStopped (line 1106) | set isStopped(value) {
method isLocked (line 1115) | get isLocked() {
method isLocked (line 1118) | set isLocked(value) {
method isSmooth (line 1127) | get isSmooth() {
method className (line 1133) | get className() {
method updateClassName (line 1142) | updateClassName() {
method cleanUpClassName (line 1146) | cleanUpClassName() {
function IO (line 1166) | function IO(_ref) {
function clamp (line 1295) | function clamp(min, max, value) {
function mapRange (line 1309) | function mapRange(inMin, inMax, outMin, outMax, value) {
function normalize (line 1323) | function normalize(min, max, value) {
function closestNumber (line 1334) | function closestNumber(array, target) {
function ScrollElement (line 1360) | function ScrollElement(_ref) {
function Core (line 1798) | function Core(_ref) {
function LocomotiveScroll (line 2129) | function LocomotiveScroll(_temp) {
FILE: packages/lib/core/Core.ts
constant ATTRIBUTES_THAT_NEED_RAF (line 14) | const ATTRIBUTES_THAT_NEED_RAF = [
constant TRIGGER_ROOT_MARGIN (line 23) | const TRIGGER_ROOT_MARGIN = '-1px -1px -1px -1px';
constant RAF_ROOT_MARGIN (line 24) | const RAF_ROOT_MARGIN = '100% 100% 100% 100%';
constant DEFAULT_SCROLL_OFFSET (line 27) | const DEFAULT_SCROLL_OFFSET = '0,0';
constant DEFAULT_SCROLL_POSITION (line 28) | const DEFAULT_SCROLL_POSITION = 'top,bottom';
class Core (line 30) | class Core {
method constructor (line 43) | constructor({
method _init (line 84) | private _init() {
method destroy (line 116) | public destroy() {
method onResize (line 125) | onResize({ currentScroll }: IScrollElementCallbacksValues) {
method onRender (line 136) | onRender({ currentScroll, smooth }: IScrollElementCallbacksValues) {
method removeScrollElements (line 150) | removeScrollElements($oldContainer: HTMLElement) {
method addScrollElements (line 202) | addScrollElements($newContainer: HTMLElement) {
method _subscribeScrollElements (line 230) | _subscribeScrollElements(
method _unsubscribeAllScrollElements (line 285) | _unsubscribeAllScrollElements() {
method _subscribeElementUpdate (line 305) | _subscribeElementUpdate(scrollElement: ScrollElement) {
method _unsubscribeElementUpdate (line 317) | _unsubscribeElementUpdate(scrollElement: ScrollElement) {
method toElementArray (line 333) | private toElementArray(elements: NodeListOf<Element>): HTMLElement[] {
method _checkRafNeeded (line 346) | _checkRafNeeded($scrollElement: HTMLElement) {
FILE: packages/lib/core/IO.ts
class IO (line 19) | class IO {
method constructor (line 26) | constructor({
method _init (line 47) | private _init() {
method destroy (line 84) | public destroy() {
method observe (line 93) | public observe($scrollElement: HTMLElement) {
method unobserve (line 106) | public unobserve($scrollElement: HTMLElement) {
method _setInview (line 121) | private _setInview(entry: IntersectionObserverEntry) {
method _setOutOfView (line 137) | private _setOutOfView(entry: IntersectionObserverEntry) {
FILE: packages/lib/core/ScrollElement.ts
constant INVIEW_CLASS (line 29) | const INVIEW_CLASS = 'is-inview';
constant PROGRESS_CSS_VAR (line 30) | const PROGRESS_CSS_VAR = '--progress';
class ScrollElement (line 32) | class ScrollElement {
method constructor (line 75) | constructor({
method _init (line 169) | private _init() {
method onResize (line 181) | public onResize({ currentScroll }: IScrollElementCallbacksValues) {
method onRender (line 189) | public onRender({ currentScroll, smooth }: IScrollElementCallbacksValu...
method setInview (line 230) | public setInview() {
method setOutOfView (line 246) | public setOutOfView() {
method setInteractivityOn (line 263) | public setInteractivityOn() {
method setInteractivityOff (line 276) | public setInteractivityOff() {
method _resize (line 294) | private _resize() {
method _computeMetrics (line 314) | private _computeMetrics() {
method _computeIntersection (line 339) | private _computeIntersection() {
method _computeProgress (line 405) | private _computeProgress(forcedProgress?: number) {
method _setCssProgress (line 445) | _setCssProgress(currentProgress = 0) {
method _setCustomEventProgress (line 459) | _setCustomEventProgress(currentProgress = 0) {
method _getScrollCallFrom (line 478) | _getScrollCallFrom() {
method destroy (line 493) | public destroy(): void {
method _dispatchCall (line 518) | _dispatchCall(way: string, from: string) {
FILE: packages/lib/dist/locomotive-scroll.cjs
function t (line 1) | function t(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}
function s (line 1) | function s(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(...
function i (line 1) | function i(t,e){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t[...
function n (line 1) | function n(){return n=Object.assign?Object.assign.bind():function(t){for...
function t (line 1) | function t(t){var e=t.scrollElements,s=t.rootMargin,i=void 0===s?"-1px -...
function o (line 1) | function o(t,e,s,i,n){return s+((n-t)/(e-t)*(i-s)||0)}
function l (line 1) | function l(t,e){return t.reduce(function(t,s){return Math.abs(s-e)<Math....
function t (line 1) | function t(t){var e,s,i,n,r,o=this,l=t.$el,a=t.id,c=t.subscribeElementUp...
function t (line 1) | function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.sc...
function t (line 1) | function t(t){var e=void 0===t?{}:t,s=e.lenisOptions,i=void 0===s?{}:s,n...
FILE: packages/lib/dist/locomotive-scroll.mjs
function e (line 1) | function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(...
function s (line 1) | function s(t,s){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t[...
function i (line 1) | function i(){return i=Object.assign?Object.assign.bind():function(t){for...
function t (line 1) | function t(t){var e=t.scrollElements,s=t.rootMargin,i=void 0===s?"-1px -...
function r (line 1) | function r(t,e,s,i,n){return s+((n-t)/(e-t)*(i-s)||0)}
function o (line 1) | function o(t,e){return t.reduce(function(t,s){return Math.abs(s-e)<Math....
function t (line 1) | function t(t){var e,s,i,n,r,o=this,l=t.$el,a=t.id,c=t.subscribeElementUp...
function t (line 1) | function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.sc...
function e (line 1) | function e(t){var e=void 0===t?{}:t,s=e.lenisOptions,i=void 0===s?{}:s,n...
FILE: packages/lib/dist/locomotive-scroll.modern.mjs
function s (line 1) | function s(){return s=Object.assign?Object.assign.bind():function(t){for...
class e (line 1) | class e{constructor({scrollElements:t,rootMargin:s="-1px -1px -1px -1px"...
method constructor (line 1) | constructor({scrollElements:t,rootMargin:s="-1px -1px -1px -1px",root:...
method _init (line 1) | _init(){this.observer=new IntersectionObserver(t=>{t.forEach(t=>{const...
method destroy (line 1) | destroy(){this.observer.disconnect()}
method observe (line 1) | observe(t){t&&this.observer.observe(t)}
method unobserve (line 1) | unobserve(t){t&&this.observer.unobserve(t)}
method _setInview (line 1) | _setInview(t){const s=this.scrollElements.find(s=>s.$el===t.target);th...
method _setOutOfView (line 1) | _setOutOfView(t){const s=this.scrollElements.find(s=>s.$el===t.target)...
function i (line 1) | function i(t,s,e,i,n){return e+((n-t)/(s-t)*(i-e)||0)}
function n (line 1) | function n(t,s){return t.reduce((t,e)=>Math.abs(e-s)<Math.abs(t-s)?e:t)}
class l (line 1) | class l{constructor({$el:t,id:s,subscribeElementUpdateFn:e,unsubscribeEl...
method constructor (line 1) | constructor({$el:t,id:s,subscribeElementUpdateFn:e,unsubscribeElementU...
method _init (line 1) | _init(){this.needRaf&&this._resize()}
method onResize (line 1) | onResize({currentScroll:t}){this.currentScroll=t,this._resize()}
method onRender (line 1) | onRender({currentScroll:t,smooth:s}){const e=this.getWindowSize();if(t...
method setInview (line 1) | setInview(){if(this.isInview)return;this.isInview=!0,this.$el.classLis...
method setOutOfView (line 1) | setOutOfView(){if(!this.isInview||!this.attributes.scrollRepeat)return...
method setInteractivityOn (line 1) | setInteractivityOn(){this.isInteractive||(this.isInteractive=!0,this.s...
method setInteractivityOff (line 1) | setInteractivityOff(){this.isInteractive&&(this.isInteractive=!1,this....
method _resize (line 1) | _resize(){this.metrics.bcr=this.$el.getBoundingClientRect(),this._comp...
method _computeMetrics (line 1) | _computeMetrics(){const t=this.getWindowSize(),s=this.getMetricsStart(...
method _computeIntersection (line 1) | _computeIntersection(){var t,s,e,i,n,r,l,o;const a=this.getWindowSize(...
method _computeProgress (line 1) | _computeProgress(t){const s=null!=t?t:(e=i(this.intersection.start,thi...
method _setCssProgress (line 1) | _setCssProgress(t=0){this.$el.style.setProperty(r,t.toString())}
method _setCustomEventProgress (line 1) | _setCustomEventProgress(t=0){const s=this.attributes.scrollEventProgre...
method _getScrollCallFrom (line 1) | _getScrollCallFrom(){const t=n([this.intersection.start,this.intersect...
method destroy (line 1) | destroy(){this.attributes.scrollCssProgress&&this.$el.style.removeProp...
method _dispatchCall (line 1) | _dispatchCall(t,s){const e=this.attributes.scrollCall;if(!e)return;con...
class a (line 1) | class a{constructor({$el:t,triggerRootMargin:s,rafRootMargin:e,scrollOri...
method constructor (line 1) | constructor({$el:t,triggerRootMargin:s,rafRootMargin:e,scrollOrientati...
method _init (line 1) | _init(){const t=this.$scrollContainer.querySelectorAll("[data-scroll]"...
method destroy (line 1) | destroy(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(...
method onResize (line 1) | onResize({currentScroll:t}){for(const s of this.RAFScrollElements)s.on...
method onRender (line 1) | onRender({currentScroll:t,smooth:s}){for(const e of this.scrollElement...
method removeScrollElements (line 1) | removeScrollElements(t){const s=t.querySelectorAll("[data-scroll]");if...
method addScrollElements (line 1) | addScrollElements(t){const s=t.querySelectorAll("[data-scroll]"),e=[];...
method _subscribeScrollElements (line 1) | _subscribeScrollElements(t,s=0,e=!1){for(let i=0;i<t.length;i++){const...
method _unsubscribeAllScrollElements (line 1) | _unsubscribeAllScrollElements(){for(const t of this.scrollElements)t.d...
method _subscribeElementUpdate (line 1) | _subscribeElementUpdate(t){this.scrollElementsToUpdate.push(t)}
method _unsubscribeElementUpdate (line 1) | _unsubscribeElementUpdate(t){this.scrollElementsToUpdate=this.scrollEl...
method toElementArray (line 1) | toElementArray(t){return Array.from(t)}
method _checkRafNeeded (line 1) | _checkRafNeeded(t){let s=[...o];const e=t=>{s=s.filter(s=>s!==t)};if(t...
class c (line 1) | class c{constructor({lenisOptions:t={},triggerRootMargin:s,rafRootMargin...
method constructor (line 1) | constructor({lenisOptions:t={},triggerRootMargin:s,rafRootMargin:e,aut...
method _init (line 1) | _init(){this.lenisInstance=new t(s({},this.lenisOptions)),this.scrollC...
method destroy (line 1) | destroy(){var t;this.stop(),this._unbindEvents(),null==(t=this.lenisIn...
method _bindEvents (line 1) | _bindEvents(){this._bindScrollToEvents(),this.lenisInstance&&(this._or...
method _unbindEvents (line 1) | _unbindEvents(){this._unbindScrollToEvents(),this.lenisInstance&&(this...
method _bindScrollToEvents (line 1) | _bindScrollToEvents(t){var s;const e=t||(null==(s=this.lenisInstance)?...
method _unbindScrollToEvents (line 1) | _unbindScrollToEvents(t){var s;const e=t||(null==(s=this.lenisInstance...
method _onResize (line 1) | _onResize(){var t,s,e;null==(t=this.coreInstance)||t.onResize({current...
method _onRender (line 1) | _onRender(){var t,s,e,i;null==(t=this.lenisInstance)||t.raf(Date.now()...
method _onScrollTo (line 1) | _onScrollTo(t){var s,e;t.preventDefault();const i=null!=(s=t.currentTa...
method start (line 1) | start(){var t;this.rafPlaying||(null==(t=this.lenisInstance)||t.start(...
method stop (line 1) | stop(){var t;this.rafPlaying&&(null==(t=this.lenisInstance)||t.stop(),...
method removeScrollElements (line 1) | removeScrollElements(t){var s;t?(this._unbindScrollToEvents(t),null==(...
method addScrollElements (line 1) | addScrollElements(t){var s;t?(null==(s=this.coreInstance)||s.addScroll...
method resize (line 1) | resize(){this._onResizeBind()}
method scrollTo (line 1) | scrollTo(t,s){var e;null==(e=this.lenisInstance)||e.scrollTo(t,{offset...
method _raf (line 1) | _raf(){this._onRenderBind(),this.rafInstance=requestAnimationFrame(()=...
FILE: packages/lib/dist/locomotive-scroll.umd.js
function e (line 1) | function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}
function i (line 1) | function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=Array(...
function n (line 1) | function n(t,e){var s="undefined"!=typeof Symbol&&t[Symbol.iterator]||t[...
function r (line 1) | function r(){return r=Object.assign?Object.assign.bind():function(t){for...
function t (line 1) | function t(t){var e=t.scrollElements,s=t.rootMargin,i=void 0===s?"-1px -...
function l (line 1) | function l(t,e,s,i,n){return s+((n-t)/(e-t)*(i-s)||0)}
function a (line 1) | function a(t,e){return t.reduce(function(t,s){return Math.abs(s-e)<Math....
function t (line 1) | function t(t){var e,s,i,n,r,o=this,l=t.$el,a=t.id,c=t.subscribeElementUp...
function t (line 1) | function t(t){var e=t.$el,s=t.triggerRootMargin,i=t.rafRootMargin,n=t.sc...
function t (line 2) | function t(t){var e=void 0===t?{}:t,s=e.lenisOptions,i=void 0===s?{}:s,n...
FILE: packages/lib/dist/types/core/Core.d.ts
class Core (line 6) | class Core {
FILE: packages/lib/dist/types/core/IO.d.ts
class IO (line 17) | class IO {
FILE: packages/lib/dist/types/core/ScrollElement.d.ts
class ScrollElement (line 18) | class ScrollElement {
FILE: packages/lib/dist/types/index.d.ts
class LocomotiveScroll (line 13) | class LocomotiveScroll {
FILE: packages/lib/dist/types/types.d.ts
type ILenisScrollValues (line 13) | interface ILenisScrollValues {
type lenisTargetScrollTo (line 23) | type lenisTargetScrollTo = number | HTMLElement | string;
type ILenisScrollTo (line 31) | interface ILenisScrollTo {
type ILenisScrollToOptions (line 47) | interface ILenisScrollToOptions {
type ILocomotiveScrollOptions (line 66) | interface ILocomotiveScrollOptions {
type IScrollElementOptions (line 86) | interface IScrollElementOptions {
type IScrollElementAttributes (line 110) | interface IScrollElementAttributes {
type IScrollElementIntersection (line 128) | interface IScrollElementIntersection {
type IScrollElementMetrics (line 139) | interface IScrollElementMetrics {
type IScrollElementCallbacksValues (line 150) | interface IScrollElementCallbacksValues {
type IIOOptions (line 162) | interface IIOOptions {
type CoreOptions (line 176) | interface CoreOptions {
type scrollOrientation (line 183) | type scrollOrientation = 'vertical' | 'horizontal';
type gestureOrientation (line 184) | type gestureOrientation = 'vertical' | 'horizontal' | 'both';
type Window (line 186) | interface Window {
FILE: packages/lib/index.ts
class LocomotiveScroll (line 23) | class LocomotiveScroll {
method constructor (line 44) | constructor({
method _init (line 89) | private _init(): void {
method destroy (line 140) | public destroy(): void {
method _bindEvents (line 158) | private _bindEvents() {
method _unbindEvents (line 187) | private _unbindEvents() {
method _bindScrollToEvents (line 204) | private _bindScrollToEvents($container?: HTMLElement) {
method _unbindScrollToEvents (line 220) | private _unbindScrollToEvents($container?: HTMLElement) {
method _onResize (line 238) | private _onResize() {
method _onRender (line 248) | private _onRender() {
method _onScrollTo (line 260) | private _onScrollTo(event: MouseEvent) {
method start (line 284) | public start(): void {
method stop (line 301) | public stop(): void {
method removeScrollElements (line 318) | public removeScrollElements($oldContainer: HTMLElement): void {
method addScrollElements (line 331) | public addScrollElements($newContainer: HTMLElement): void {
method resize (line 346) | public resize(): void {
method scrollTo (line 353) | public scrollTo(
method _raf (line 375) | private _raf() {
FILE: packages/lib/types.ts
type ILenisScrollValues (line 14) | interface ILenisScrollValues {
type lenisTargetScrollTo (line 25) | type lenisTargetScrollTo = number | HTMLElement | string;
type ILenisScrollTo (line 34) | interface ILenisScrollTo {
type ILenisScrollToOptions (line 51) | interface ILenisScrollToOptions {
type ILocomotiveScrollOptions (line 71) | interface ILocomotiveScrollOptions {
type IScrollElementOptions (line 92) | interface IScrollElementOptions {
type IScrollElementAttributes (line 117) | interface IScrollElementAttributes {
type IScrollElementIntersection (line 136) | interface IScrollElementIntersection {
type IScrollElementMetrics (line 148) | interface IScrollElementMetrics {
type IScrollElementCallbacksValues (line 160) | interface IScrollElementCallbacksValues {
type IIOOptions (line 173) | interface IIOOptions {
type CoreOptions (line 188) | interface CoreOptions {
type scrollOrientation (line 196) | type scrollOrientation = 'vertical' | 'horizontal';
type gestureOrientation (line 198) | type gestureOrientation = 'vertical' | 'horizontal' | 'both';
type Window (line 201) | interface Window {
FILE: packages/lib/utils/maths.ts
function clamp (line 12) | function clamp(min: number, max: number, value: number): number {
function mapRange (line 27) | function mapRange(
function normalize (line 48) | function normalize(min: number, max: number, value: number): number {
function closestNumber (line 60) | function closestNumber(array: number[], target: number): number {
FILE: scripts/ignore-build-step.js
constant SKIP_BUILD_EXIT_CODE (line 23) | const SKIP_BUILD_EXIT_CODE = 0;
constant PROCEED_BUILD_EXIT_CODE (line 24) | const PROCEED_BUILD_EXIT_CODE = 1;
function log (line 26) | function log(message) {
function getChangedFiles (line 30) | function getChangedFiles() {
function shouldSkipBuild (line 51) | function shouldSkipBuild(changedFiles) {
function main (line 92) | function main() {
FILE: www/landing/assets/scripts/app.js
function n (line 1) | function n(r,s,o){if(o){var a=document.createDocumentFragment(),l=!s.has...
function t (line 1) | function t(r){r.onreadystatechange=function(){if(r.readyState===4){var s...
function e (line 1) | function e(r){function s(){for(var E=0;E<p.length;){var y=p[E],S=y.paren...
function i (line 1) | function i(r){for(var s=r;s.nodeName.toLowerCase()!=="svg"&&(s=s.parentN...
function vd (line 1) | function vd({gutterCssVar:n=md,marginCssVar:t=gd,rgbaColor:e=Dd}={}){let...
function yd (line 1) | function yd(n,t,e){let i=n.style;i.zIndex="10000",i.position="fixed",i.t...
function ic (line 1) | function ic(n,t){n.innerHTML="";let e=Number(window.getComputedStyle(n)....
function bd (line 1) | function bd(n,t){window.addEventListener("resize",ic(n,t));let e=!1,i=!1...
function Ln (line 1) | function Ln(n){"@babel/helpers - typeof";return typeof Symbol=="function...
function ya (line 1) | function ya(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a...
function Da (line 1) | function Da(n,t){for(var e=0;e<t.length;e++){var i=t[e];i.enumerable=i.e...
function ba (line 1) | function ba(n,t,e){return t&&Da(n.prototype,t),e&&Da(n,e),n}
function va (line 1) | function va(n,t,e){return t in n?Object.defineProperty(n,t,{value:e,enum...
function In (line 1) | function In(n,t){return Ec(n)||Cc(n,t)||Ea(n,t)||Tc()}
function yc (line 1) | function yc(n){return bc(n)||wc(n)||Ea(n)||Sc()}
function bc (line 1) | function bc(n){if(Array.isArray(n))return Hs(n)}
function Ec (line 1) | function Ec(n){if(Array.isArray(n))return n}
function wc (line 1) | function wc(n){if(typeof Symbol!="undefined"&&Symbol.iterator in Object(...
function Cc (line 1) | function Cc(n,t){if(!(typeof Symbol=="undefined"||!(Symbol.iterator in O...
function Ea (line 1) | function Ea(n,t){if(n){if(typeof n=="string")return Hs(n,t);var e=Object...
function Hs (line 1) | function Hs(n,t){(t==null||t>n.length)&&(t=n.length);for(var e=0,i=new A...
function Sc (line 1) | function Sc(){throw new TypeError(`Invalid attempt to spread non-iterabl...
function Tc (line 2) | function Tc(){throw new TypeError(`Invalid attempt to destructure non-it...
function n (line 3) | function n(t){ya(this,n),this.mAttr="data-"+t.dataName,this.mCaptureEven...
function n (line 3) | function n(t){ya(this,n),this.app,this.modules=t.modules,this.currentMod...
function Fc (line 3) | function Fc(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a...
function Ca (line 3) | function Ca(n,t){for(var e=0;e<t.length;e++){var i=t[e];i.enumerable=i.e...
function Ac (line 3) | function Ac(n,t,e){return t&&Ca(n.prototype,t),e&&Ca(n,e),n}
function Oc (line 3) | function Oc(n,t){return kc(n)||Pc(n,t)||Rc(n,t)||Mc()}
function kc (line 3) | function kc(n){if(Array.isArray(n))return n}
function Pc (line 3) | function Pc(n,t){var e=n==null?null:typeof Symbol!="undefined"&&n[Symbol...
function Rc (line 3) | function Rc(n,t){if(n){if(typeof n=="string")return Sa(n,t);var e=Object...
function Sa (line 3) | function Sa(n,t){(t==null||t>n.length)&&(t=n.length);for(var e=0,i=new A...
function Mc (line 3) | function Mc(){throw new TypeError(`Invalid attempt to destructure non-it...
function n (line 4) | function n(t){Fc(this,n),this.defaults={name:"load",loadingClass:"is-loa...
method constructor (line 4) | constructor(n){super(n)}
method init (line 4) | init(){new Ta({enterDelay:0,transitions:{customTransition:{}}}).on("load...
function Lc (line 4) | function Lc(n,t){for(let[e,i]of Object.entries(t))switch(e){case"family"...
function Bc (line 4) | function Bc(n,t){let e=zn(n.family);return zn(e)===t||t.endsWith(zn(e))&...
function zc (line 4) | function zc(n){let t=[];for(let e of document.fonts)Lc(e,n)&&t.push(e);r...
function Nc (line 4) | function Nc(n){let t=[];for(let e of document.fonts)Bc(e,n)&&t.push(e);r...
function Aa (line 4) | function Aa(n){Array.isArray(n)||(n=[n]);let t=new Set;return n.forEach(...
function Oa (line 4) | function Oa(n,t=!1){return Hi(this,null,function*(){var e;if(((e=n.size)...
function xa (line 4) | function xa(n){return Hi(this,null,function*(){return yield(n.status==="...
function Hc (line 4) | function Hc(n,t=!1){return Hi(this,null,function*(){t&&console.group("[l...
function zn (line 4) | function zn(n){return n.replace(/['"]+/g,"")}
function Nn (line 4) | function Nn(n){return Hi(this,null,function*(){let t=Aa(n);return yield ...
function oi (line 4) | function oi(n){if(n===void 0)throw new ReferenceError("this hasn't been ...
function Na (line 4) | function Na(n,t){n.prototype=Object.create(t.prototype),n.prototype.cons...
function n (line 4) | function n(e){this.vars=e,this._delay=+e.delay||0,(this._repeat=e.repeat...
function t (line 4) | function t(i,r){var s;return i===void 0&&(i={}),s=n.call(this,i)||this,s...
function t (line 4) | function t(i,r,s,o){var a;typeof r=="number"&&(s.duration=r,r=s,s=null),...
function n (line 4) | 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...
function n (line 4) | function n(e,i){this.selector=i&&to(i),this.data=[],this._r=[],this.isRe...
function n (line 4) | function n(e){this.contexts=[],this.scope=e,ct&&ct.data.push(this)}
method constructor (line 4) | constructor(n){super(n),this.onUpdateBind=this.onUpdate.bind(this),this....
method init (line 4) | init(){this.bindEvents(),Nn(Cr.EAGER).then(n=>{this.onFontsLoaded(n)})}
method destroy (line 4) | destroy(){super.destroy(),this.unbindEvents(),this.stop()}
method bindEvents (line 4) | bindEvents(){window.addEventListener(tn.RESIZE_END,this.onResizeBind),wi...
method unbindEvents (line 4) | unbindEvents(){window.removeEventListener(tn.RESIZE_END,this.onResizeBin...
method onResize (line 4) | onResize(){this.repeatPattern()}
method onFontsLoaded (line 4) | onFontsLoaded(){this.repeatPattern()}
method onUpdate (line 4) | onUpdate(){this.currentTranslate=(this.currentTranslate+this.idleVelocit...
method onScroll (line 4) | onScroll(n){let{velocity:t,direction:e}=n;Qi.IS_MOBILE||(this.scrollDire...
method onToggle (line 4) | onToggle(n){let{way:t}=n.detail;t==="enter"?this.start():this.stop()}
method start (line 4) | start(){this.isPlaying||(this.isPlaying=!0,_t.ticker.add(this.onUpdateBi...
method stop (line 4) | stop(){this.isPlaying&&(this.isPlaying=!1,_t.ticker.remove(this.onUpdate...
method computeMetrics (line 4) | computeMetrics(n=!1){if(n){this.$items=this.el.querySelectorAll("[data-r...
method repeatPattern (line 4) | repeatPattern(){if(this.showFrom&&window.innerWidth<this.showFrom||this....
function Vl (line 4) | function Vl(n,t,e){return Math.max(n,Math.min(t,e))}
function xf (line 4) | function xf(n,t,e){return(1-e)*n+e*t}
function Ff (line 4) | function Ff(n,t,e,i){return xf(n,t,1-Math.exp(-e*i))}
function Af (line 4) | function Af(n,t){return(n%t+t)%t}
method constructor (line 4) | constructor(){I(this,"isRunning",!1);I(this,"value",0);I(this,"from",0);...
method advance (line 4) | advance(n){var e;if(!this.isRunning)return;let t=!1;if(this.duration&&th...
method stop (line 4) | stop(){this.isRunning=!1}
method fromTo (line 4) | fromTo(n,t,{lerp:e,duration:i,easing:r,onStart:s,onUpdate:o}){this.from=...
function kf (line 4) | function kf(n,t){let e;return function(...i){let r=this;clearTimeout(e),...
method constructor (line 4) | constructor(n,t,{autoResize:e=!0,debounce:i=250}={}){I(this,"width",0);I...
method destroy (line 4) | destroy(){var n,t;(n=this.wrapperResizeObserver)==null||n.disconnect(),(...
method limit (line 4) | get limit(){return{x:this.scrollWidth-this.width,y:this.scrollHeight-thi...
method constructor (line 4) | constructor(){I(this,"events",{})}
method emit (line 4) | emit(n,...t){var i;let e=this.events[n]||[];for(let r=0,s=e.length;r<s;r...
method on (line 4) | on(n,t){var e;return(e=this.events[n])!=null&&e.push(t)||(this.events[n]...
method off (line 4) | off(n,t){var e;this.events[n]=(e=this.events[n])==null?void 0:e.filter(i...
method destroy (line 4) | destroy(){this.events={}}
method constructor (line 4) | constructor(n,t={wheelMultiplier:1,touchMultiplier:1}){I(this,"touchStar...
method on (line 4) | on(n,t){return this.emitter.on(n,t)}
method destroy (line 4) | destroy(){this.emitter.destroy(),window.removeEventListener("resize",thi...
method constructor (line 4) | constructor({wrapper:n=window,content:t=document.documentElement,eventsT...
method destroy (line 4) | destroy(){this.emitter.destroy(),this.options.wrapper.removeEventListene...
method on (line 4) | on(n,t){return this.emitter.on(n,t)}
method off (line 4) | off(n,t){return this.emitter.off(n,t)}
method overflow (line 4) | get overflow(){let n=this.isHorizontal?"overflow-x":"overflow-y";return ...
method checkOverflow (line 4) | checkOverflow(){["hidden","clip"].includes(this.overflow)?this.internalS...
method setScroll (line 4) | setScroll(n){this.isHorizontal?this.options.wrapper.scrollTo({left:n,beh...
method resize (line 4) | resize(){this.dimensions.resize(),this.animatedScroll=this.targetScroll=...
method emit (line 4) | emit(){this.emitter.emit("scroll",this)}
method reset (line 4) | reset(){this.isLocked=!1,this.isScrolling=!1,this.animatedScroll=this.ta...
method start (line 4) | start(){if(this.isStopped){if(this.options.autoToggle){this.rootElement....
method internalStart (line 4) | internalStart(){this.isStopped&&(this.reset(),this.isStopped=!1,this.emi...
method stop (line 4) | stop(){if(!this.isStopped){if(this.options.autoToggle){this.rootElement....
method internalStop (line 4) | internalStop(){this.isStopped||(this.reset(),this.isStopped=!0,this.emit...
method scrollTo (line 4) | scrollTo(n,{offset:t=0,immediate:e=!1,lock:i=!1,programmatic:r=!0,lerp:s...
method preventNextNativeScrollEvent (line 4) | preventNextNativeScrollEvent(){this._preventNextNativeScrollEvent=!0,req...
method checkNestedScroll (line 4) | checkNestedScroll(n,{deltaX:t,deltaY:e}){var v,T;let i=Date.now(),r=(v=n...
method rootElement (line 4) | get rootElement(){return this.options.wrapper===window?document.document...
method limit (line 4) | get limit(){return this.options.naiveDimensions?this.isHorizontal?this.r...
method isHorizontal (line 4) | get isHorizontal(){return this.options.orientation==="horizontal"}
method actualScroll (line 4) | get actualScroll(){var t,e;let n=this.options.wrapper;return this.isHori...
method scroll (line 4) | get scroll(){return this.options.infinite?Af(this.animatedScroll,this.li...
method progress (line 4) | get progress(){return this.limit===0?1:this.scroll/this.limit}
method isScrolling (line 4) | get isScrolling(){return this._isScrolling}
method isScrolling (line 4) | set isScrolling(n){this._isScrolling!==n&&(this._isScrolling=n,this.upda...
method isStopped (line 4) | get isStopped(){return this._isStopped}
method isStopped (line 4) | set isStopped(n){this._isStopped!==n&&(this._isStopped=n,this.updateClas...
method isLocked (line 4) | get isLocked(){return this._isLocked}
method isLocked (line 4) | set isLocked(n){this._isLocked!==n&&(this._isLocked=n,this.updateClassNa...
method isSmooth (line 4) | get isSmooth(){return this.isScrolling==="smooth"}
method className (line 4) | get className(){let n="lenis";return this.options.autoToggle&&(n+=" leni...
method updateClassName (line 4) | updateClassName(){this.cleanUpClassName(),this.rootElement.className=`${...
method cleanUpClassName (line 4) | cleanUpClassName(){this.rootElement.className=this.rootElement.className...
method constructor (line 4) | constructor({scrollElements:t,rootMargin:e="-1px -1px -1px -1px",root:i=...
method _init (line 4) | _init(){let t={root:this.root,rootMargin:this.rootMargin},e=i=>{i.forEac...
method destroy (line 4) | destroy(){this.observer.disconnect()}
method observe (line 4) | observe(t){t&&this.observer.observe(t)}
method unobserve (line 4) | unobserve(t){t&&this.observer.unobserve(t)}
method _setInview (line 4) | _setInview(t){let e=this.scrollElements.find(i=>i.$el===t.target);this.I...
method _setOutOfView (line 4) | _setOutOfView(t){let e=this.scrollElements.find(i=>i.$el===t.target);thi...
function $l (line 4) | function $l(n,t,e){return e<n?n:e>t?t:e}
function Io (line 4) | function Io(n,t,e,i,r){let s=t-n,o=i-e;return e+((r-n)/s*o||0)}
function jl (line 4) | function jl(n,t,e){return Io(n,t,0,1,e)}
function Lo (line 4) | function Lo(n,t){return n.reduce((e,i)=>Math.abs(i-t)<Math.abs(e-t)?i:e)}
method constructor (line 4) | constructor({$el:t,id:e,subscribeElementUpdateFn:i,unsubscribeElementUpd...
method _init (line 4) | _init(){this.needRaf&&this._resize()}
method onResize (line 4) | onResize({currentScroll:t}){this.currentScroll=t,this._resize()}
method onRender (line 4) | onRender({currentScroll:t,smooth:e}){let i=this.getWindowSize();if(this....
method setInview (line 4) | setInview(){if(this.isInview)return;this.isInview=!0,this.$el.classList....
method setOutOfView (line 4) | setOutOfView(){if(!(this.isInview&&this.attributes.scrollRepeat))return;...
method setInteractivityOn (line 4) | setInteractivityOn(){this.isInteractive||(this.isInteractive=!0,this.sub...
method setInteractivityOff (line 4) | setInteractivityOff(){this.isInteractive&&(this.isInteractive=!1,this.un...
method _resize (line 4) | _resize(){this.metrics.bcr=this.$el.getBoundingClientRect(),this._comput...
method _computeMetrics (line 4) | _computeMetrics(){let t=this.getWindowSize(),e=this.getMetricsStart(this...
method _computeIntersection (line 4) | _computeIntersection(){var h,_,p,g,E,y,S,D;let t=this.getWindowSize(),e=...
method _computeProgress (line 4) | _computeProgress(t){let e=t!=null?t:$l(0,1,jl(this.intersection.start,th...
method _setCssProgress (line 4) | _setCssProgress(t=0){this.$el.style.setProperty(Xl,t.toString())}
method _setCustomEventProgress (line 4) | _setCustomEventProgress(t=0){let e=this.attributes.scrollEventProgress;i...
method _getScrollCallFrom (line 4) | _getScrollCallFrom(){let t=Lo([this.intersection.start,this.intersection...
method destroy (line 4) | destroy(){this.attributes.scrollCssProgress&&this.$el.style.removeProper...
method _dispatchCall (line 4) | _dispatchCall(t,e){let i=this.attributes.scrollCall;if(!i)return;let r=n...
method constructor (line 4) | constructor({$el:t,triggerRootMargin:e,rafRootMargin:i,scrollOrientation...
method _init (line 4) | _init(){let t=this.$scrollContainer.querySelectorAll("[data-scroll]"),e=...
method destroy (line 4) | destroy(){this.IOTriggerInstance.destroy(),this.IORafInstance.destroy(),...
method onResize (line 4) | onResize({currentScroll:t}){for(let e of this.RAFScrollElements)e.onResi...
method onRender (line 4) | onRender({currentScroll:t,smooth:e}){for(let i of this.scrollElementsToU...
method removeScrollElements (line 4) | removeScrollElements(t){let e=t.querySelectorAll("[data-scroll]");if(!e....
method addScrollElements (line 4) | addScrollElements(t){let e=t.querySelectorAll("[data-scroll]"),i=[];this...
method _subscribeScrollElements (line 4) | _subscribeScrollElements(t,e=0,i=!1){for(let r=0;r<t.length;r++){let s=t...
method _unsubscribeAllScrollElements (line 4) | _unsubscribeAllScrollElements(){for(let t of this.scrollElements)t.destr...
method _subscribeElementUpdate (line 4) | _subscribeElementUpdate(t){this.scrollElementsToUpdate.push(t)}
method _unsubscribeElementUpdate (line 4) | _unsubscribeElementUpdate(t){this.scrollElementsToUpdate=this.scrollElem...
method toElementArray (line 4) | toElementArray(t){return Array.from(t)}
method _checkRafNeeded (line 4) | _checkRafNeeded(t){let e=[...If],i=r=>{e=e.filter(s=>s!==r)};if(t.datase...
method constructor (line 4) | constructor({lenisOptions:t={},triggerRootMargin:e,rafRootMargin:i,autoS...
method _init (line 4) | _init(){this.lenisInstance=new Yl(Mn({},this.lenisOptions)),this.scrollC...
method destroy (line 4) | destroy(){var t;this.stop(),this._unbindEvents(),(t=this.lenisInstance)=...
method _bindEvents (line 4) | _bindEvents(){this._bindScrollToEvents(),this.lenisInstance&&(this._orig...
method _unbindEvents (line 4) | _unbindEvents(){this._unbindScrollToEvents(),this.lenisInstance&&(this._...
method _bindScrollToEvents (line 4) | _bindScrollToEvents(t){var r;let e=t||((r=this.lenisInstance)==null?void...
method _unbindScrollToEvents (line 4) | _unbindScrollToEvents(t){var r;let e=t||((r=this.lenisInstance)==null?vo...
method _onResize (line 4) | _onResize(){var t,e,i;(i=this.coreInstance)==null||i.onResize({currentSc...
method _onRender (line 4) | _onRender(){var t,e,i,r;(t=this.lenisInstance)==null||t.raf(Date.now()),...
method _onScrollTo (line 4) | _onScrollTo(t){var o,a;t.preventDefault();let e=(o=t.currentTarget)!=nul...
method start (line 4) | start(){var t;this.rafPlaying||((t=this.lenisInstance)==null||t.start(),...
method stop (line 4) | stop(){var t;this.rafPlaying&&((t=this.lenisInstance)==null||t.stop(),th...
method removeScrollElements (line 4) | removeScrollElements(t){var e;if(!t){console.error("Please provide a DOM...
method addScrollElements (line 4) | addScrollElements(t){var e;if(!t){console.error("Please provide a DOM El...
method resize (line 4) | resize(){this._onResizeBind()}
method scrollTo (line 4) | scrollTo(t,e){var i;(i=this.lenisInstance)==null||i.scrollTo(t,{offset:e...
method _raf (line 4) | _raf(){this._onRenderBind(),this.rafInstance=requestAnimationFrame(()=>t...
method constructor (line 4) | constructor(t){super(t),this.onScrollBind=this.onScroll.bind(this),this....
method init (line 4) | init(){this.scroll=new nn({modularInstance:this,scrollCallback:this.onSc...
method destroy (line 4) | destroy(){this.unbindEvents(),this.scroll.destroy()}
method bindEvents (line 4) | bindEvents(){window.addEventListener("changeHeaderTheme",this.changeHead...
method unbindEvents (line 4) | unbindEvents(){window.removeEventListener("changeHeaderTheme",this.chang...
method onScroll (line 4) | onScroll({scroll:t,limit:e,velocity:i,direction:r,progress:s}){s>this.la...
method scrollTo (line 4) | scrollTo(t){var s;let r=t,{target:e}=r,i=Ns(r,["target"]);i=Object.assig...
method changeHeaderTheme (line 4) | changeHeaderTheme(t){var r;let{target:e,way:i}=t.detail;if(i=="enter"){l...
method addScrollElements (line 4) | addScrollElements(t){var e;(e=this.scroll)==null||e.addScrollElements(t)}
method removeScrollElements (line 4) | removeScrollElements(t){var e;(e=this.scroll)==null||e.removeScrollEleme...
function ns (line 4) | function ns(n){var t=n.nodeType,e="";if(t===1||t===9||t===11){if(typeof ...
function Ue (line 4) | function Ue(n,t,e,i,r){if(n+="",e&&(n=n.trim?n.trim():n.replace(Hf,"")),...
function n (line 4) | function n(e){this.chars=Ue(e),this.sets=[],this.length=50;for(var i=0;i...
method constructor (line 4) | constructor(t){super(t),this.inViewBind=this.inView.bind(this)}
method init (line 4) | init(){this.bindEvents()}
method destroy (line 4) | destroy(){this.unbindEvents()}
method bindEvents (line 4) | bindEvents(){window.addEventListener("randomize",this.inViewBind)}
method unbindEvents (line 4) | unbindEvents(){window.removeEventListener("randomize",this.inViewBind)}
method inView (line 4) | inView(t){let{target:e}=t.detail;[...e.querySelectorAll("p")].forEach((r...
method constructor (line 6) | constructor(t,e){this.isSplit=!1,Yf(),this.elements=iu(t),this.chars=[],...
method split (line 6) | split(t){return(this._ctx||$f).add(()=>{this.isSplit&&this.revert(),this...
method kill (line 6) | kill(){let{obs:t}=this._data;t&&t.disconnect(),Tr==null||Tr.removeEventL...
method revert (line 6) | revert(){var t,e;if(this.isSplit){let{orig:i,anim:r}=this._data;this.kil...
method create (line 6) | static create(t,e){return new uu(t,e)}
method register (line 6) | static register(t){ln=ln||t||window.gsap,ln&&(un=ln.utils.toArray,zo=ln....
function cu (line 6) | function cu(n,t){for(var e=0;e<t.length;e++){var i=t[e];i.enumerable=i.e...
function Gf (line 6) | function Gf(n,t,e){return t&&cu(n.prototype,t),e&&cu(n,e),n}
function n (line 6) | function n(e){this.init(e)}
function n (line 6) | function n(e,i){Pr||n.register(k)||console.warn("Please gsap.registerPlu...
method constructor (line 6) | constructor(n){super(n),this.onProgressBind=this.onProgress.bind(this),t...
method init (line 6) | init(){this.bindEvents(),Nn(Cr.EAGER).then(n=>{this.onFontsLoaded(n)})}
method destroy (line 6) | destroy(){super.destroy(),this.unbindEvents(),this.splitObject&&this.spl...
method unbindEvents (line 6) | unbindEvents(){window.removeEventListener("progressEvent",this.onProgres...
method bindEvents (line 6) | bindEvents(){window.addEventListener("progressEvent",this.onProgressBind)}
method onFontsLoaded (line 6) | onFontsLoaded(n){this.split()}
method onProgress (line 6) | onProgress(n){if(!this.splitObject||!this.splitObject.lines)return;let{p...
method split (line 6) | split(){this.splitObject=Ji.create(this.el,{type:"lines",tag:"span",auto...
method computeMetrics (line 6) | computeMetrics(n){this.metrics=n.lines.map((t,e,i)=>{let r=e/i.length,s=...
method constructor (line 6) | constructor(n){super(n),this.onFadeinTextProgressBind=this.onFadeinTextP...
method init (line 6) | init(){this.bindEvents(),this.splitText()}
method destroy (line 6) | destroy(){this.unbindEvents(),this.split&&this.split.revert()}
method bindEvents (line 6) | bindEvents(){window.addEventListener("fadeinTextProgress",this.onFadeinT...
method unbindEvents (line 6) | unbindEvents(){window.removeEventListener("fadeinTextProgress",this.onFa...
method onFadeinTextProgress (line 6) | onFadeinTextProgress(n){let{target:t,progress:e}=n.detail;!this.el.conta...
method splitText (line 6) | splitText(){this.split=Ji.create(this.$texts,{type:"lines",linesClass:"c...
method computeMetrics (line 6) | computeMetrics(n){this.metrics=[];let t=n.lines.map(r=>r.getBoundingClie...
method computeProgress (line 6) | computeProgress(){for(let n=0;n<this.split.lines.length;n++){let t=this....
method updateGradient (line 6) | updateGradient(n,t){let i=t,r=0,s=100,o=_t.utils.mapRange(0,1,-100,100+1...
method constructor (line 6) | constructor(n){super(n)}
method init (line 6) | init(){this.onItemEnterBind=this.onItemEnter.bind(this),this.onItemLeave...
method update (line 6) | update(){this.reset(),this.el.addEventListener("mouseenter",this.onItemE...
method reset (line 6) | reset(){this.el.removeEventListener("mouseenter",this.onItemEnterBind),t...
method shuffle (line 6) | shuffle(n){for(var t=typeof n=="string"?n.split(""):n,e=t.length,i=e-1;i...
method onItemEnter (line 6) | onItemEnter(n){let t=n.currentTarget,e=[];t.dataset.hoverShuffle=="child...
method onItemLeave (line 6) | onItemLeave(n){this.tw&&this.tw.kill();let t=n.currentTarget,e=[];t.data...
method shuffleElementTexts (line 6) | shuffleElementTexts(n,t){if(!n.children.length&&n.innerText){let e=n.inn...
function oc (line 8) | function oc(){(0,sc.default)(),Is==null||Is()}
function ac (line 8) | function ac(){return typeof window!="undefined"}
function lc (line 8) | function lc(){try{let n="production";if(n==="development"||n==="test")re...
function Sd (line 8) | function Sd(n="auto"){if(n==="auto"){window.vam=lc();return}window.vam=n}
function Td (line 8) | function Td(){return(ac()?window.vam:lc())||"production"}
function pa (line 8) | function pa(){return Td()==="development"}
function xd (line 8) | function xd(n){return n.scriptSrc?n.scriptSrc:pa()?"https://va.vercel-sc...
function uc (line 8) | function uc(n={debug:!0}){var t;if(!ac())return;Sd(n.mode),Cd(),n.before...
function Fd (line 8) | function Fd(){Ad(),hc(),document.documentElement.style.setProperty("--vh...
function Ad (line 8) | function Ad(){let n=new CustomEvent(tn.RESIZE_END);window.addEventListen...
function Od (line 8) | function Od(){document.documentElement.style.setProperty("--vh-initial",...
function hc (line 8) | function hc(){let n=Ve.offsetWidth*.01,t=window.innerHeight*.01;document...
Condensed preview — 231 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,059K chars).
[
{
"path": ".editorconfig",
"chars": 278,
"preview": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_size = 2\nindent"
},
{
"path": ".eslintrc.json",
"chars": 593,
"preview": "{\n \"root\": true,\n \"parserOptions\": {\n \"sourceType\": \"module\"\n },\n \"env\": {\n \"browser\": true,\n "
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 786,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nHello 👋\n\n**Descr"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".gitignore",
"chars": 569,
"preview": "node_modules\n.DS_Store\nThumbs.db\n\n# Turborepo\n.turbo\n\n# Ignore nested folders: https://stackoverflow.com/questions/32032"
},
{
"path": ".nvmrc",
"chars": 7,
"preview": "v20.14\n"
},
{
"path": ".prettierrc.json",
"chars": 115,
"preview": "{\n \"printWidth\": 80,\n \"tabWidth\": 4,\n \"trailingComma\": \"es5\",\n \"semi\": true,\n \"singleQuote\": true\n}\n"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2026 Locomotive Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 1561,
"preview": "# Locomotive Scroll\n\n[](https://www.npmjs.com/package/"
},
{
"path": "context7.json",
"chars": 1525,
"preview": "{\n \"url\": \"https://context7.com/locomotivemtl/locomotive-scroll\",\n \"public_key\": \"pk_fFYCHZCsmlAjABaNmHyFM\",\n \"folder"
},
{
"path": "package.json",
"chars": 1221,
"preview": "{\n \"private\": true,\n \"name\": \"locomotive-scroll\",\n \"description\": \"Monorepo for Locomotive Scroll\",\n \"licens"
},
{
"path": "packages/demo/.editorconfig",
"chars": 218,
"preview": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_n"
},
{
"path": "packages/demo/.gitignore",
"chars": 67,
"preview": "# build output\ndist/\n\n# SVG sprite\npublic/assets/images/sprite.svg\n"
},
{
"path": "packages/demo/.nvmrc",
"chars": 6,
"preview": "v20.14"
},
{
"path": "packages/demo/.prettierignore",
"chars": 15,
"preview": "node_modules/**"
},
{
"path": "packages/demo/.prettierrc",
"chars": 212,
"preview": "{\n \"useTabs\": false,\n \"tabWidth\": 4,\n \"singleQuote\": true,\n \"trailingComma\": \"none\",\n \"semi\": true,\n \""
},
{
"path": "packages/demo/.vscode/extensions.json",
"chars": 87,
"preview": "{\n \"recommendations\": [\"astro-build.astro-vscode\"],\n \"unwantedRecommendations\": []\n}\n"
},
{
"path": "packages/demo/.vscode/launch.json",
"chars": 207,
"preview": "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"command\": \"./node_modules/.bin/astro dev\",\n \"name\": \"Dev"
},
{
"path": "packages/demo/.vscode/settings.json",
"chars": 94,
"preview": "{\n \"css.customData\": [\".vscode/tailwind.json\"],\n \"scss.lint.unknownAtRules\": \"ignore\",\n}"
},
{
"path": "packages/demo/.vscode/tailwind.json",
"chars": 2402,
"preview": "{\n \"version\": 1.1,\n \"atDirectives\": [\n {\n \"name\": \"@tailwind\",\n \"description\": \"Use the `@tailw"
},
{
"path": "packages/demo/LICENSE",
"chars": 1077,
"preview": "The MIT License (MIT)\n\nCopyright (c) Locomotive, Inc.\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "packages/demo/README.md",
"chars": 3337,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/locomotivemtl/locomotive-boilerplate\">\n <img src=\"https://user"
},
{
"path": "packages/demo/astro.config.ts",
"chars": 1173,
"preview": "import { defineConfig } from 'astro/config';\nimport tailwind from '@astrojs/tailwind';\nimport tailwindConfig from './tai"
},
{
"path": "packages/demo/package.json",
"chars": 1381,
"preview": "{\n \"private\": true,\n \"name\": \"@locomotivemtl/astro-boilerplate\",\n \"title\": \"Locomotive Boilerplate\",\n \"type\""
},
{
"path": "packages/demo/public/fonts/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/demo/src/components/ScrollToggler/ScrollToggler.astro",
"chars": 625,
"preview": "<button class=\"c-scroll-toggler\" type=\"button\" id=\"scrollToggler\">\n stop scroll\n</button>\n\n<script>\n import { isSc"
},
{
"path": "packages/demo/src/env.d.ts",
"chars": 39,
"preview": "/// <reference types=\"astro/client\" />\n"
},
{
"path": "packages/demo/src/layouts/Layout.astro",
"chars": 2711,
"preview": "---\nimport '@styles/main.scss';\nimport { SEO } from 'astro-seo';\n\ninterface Props {\n title: string;\n seo?: Seo;\n "
},
{
"path": "packages/demo/src/pages/horizontal.astro",
"chars": 8082,
"preview": "---\nimport Layout from '@layouts/Layout.astro';\n---\n\n<Layout title=\"About\" scrollOrientation=\"horizontal\">\n <div>\n "
},
{
"path": "packages/demo/src/pages/index.astro",
"chars": 8393,
"preview": "---\nimport Layout from \"../layouts/Layout.astro\"\nimport ScrollToggler from '@components/ScrollToggler/ScrollToggler.astr"
},
{
"path": "packages/demo/src/scripts/app.ts",
"chars": 1505,
"preview": "import { Scroll } from '@scripts/classes/Scroll';\nimport { $screenDebounce } from \"../stores/screen\";\nimport { setViewpo"
},
{
"path": "packages/demo/src/scripts/classes/Scroll.ts",
"chars": 2642,
"preview": "import { isScrollStopped } from '@root/src/stores/scroll';\nimport LocomotiveScroll from '../../../../lib/index';\n\nimport"
},
{
"path": "packages/demo/src/scripts/utils/maths.ts",
"chars": 637,
"preview": "const mapRange = (min: number, max: number, nmin: number, nmax: number, value: number) => {\n return ((value - min) / "
},
{
"path": "packages/demo/src/scripts/utils/setViewportSize.ts",
"chars": 1404,
"preview": "const SUPPORTS_VH: boolean =\n 'CSS' in window &&\n 'supports' in window.CSS &&\n window.CSS.supports('height: 100"
},
{
"path": "packages/demo/src/scripts/utils/string.ts",
"chars": 131,
"preview": "const toDash = (str: string) =>\n str\n .split(/(?=[A-Z])/)\n .join('-')\n .toLowerCase();\n\nexport {"
},
{
"path": "packages/demo/src/stores/screen.ts",
"chars": 839,
"preview": "import { map } from 'nanostores';\nimport { debounce } from 'ts-debounce';\n\nexport interface IScreenValues {\n width: n"
},
{
"path": "packages/demo/src/stores/scroll.ts",
"chars": 80,
"preview": "import { atom } from \"nanostores\";\n\nexport const isScrollStopped = atom(false);\n"
},
{
"path": "packages/demo/src/styles/main.scss",
"chars": 5250,
"preview": "// ==========================================================================\n// Tailwind CSS\n// ======================="
},
{
"path": "packages/demo/src/styles/tools/functions.scss",
"chars": 1371,
"preview": "// ==========================================================================\n// Tools / Functions\n// =================="
},
{
"path": "packages/demo/src/styles/tools/maths.scss",
"chars": 3231,
"preview": "// ==========================================================================\n// Tools / Maths\n// ======================"
},
{
"path": "packages/demo/tailwind.config.ts",
"chars": 4985,
"preview": "import defaultTheme from 'tailwindcss/defaultTheme';\n\nimport type { Config } from 'tailwindcss';\n\nexport default {\n c"
},
{
"path": "packages/demo/tsconfig.json",
"chars": 744,
"preview": "{\n \"extends\": \"astro/tsconfigs/strict\",\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"moduleResolution\": "
},
{
"path": "packages/demo/types/global.d.ts",
"chars": 513,
"preview": "type Seo = {\n title?: string;\n description?: string;\n social?: {\n facebook?: {\n title?: strin"
},
{
"path": "packages/demo/types/swup.d.ts",
"chars": 85,
"preview": "type VisitType = {\n fragmentVisit: any;\n to: {\n html: string;\n };\n};\n"
},
{
"path": "packages/docs/.gitignore",
"chars": 233,
"preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
},
{
"path": "packages/docs/README.md",
"chars": 768,
"preview": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Ins"
},
{
"path": "packages/docs/babel.config.js",
"chars": 89,
"preview": "module.exports = {\n presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
},
{
"path": "packages/docs/docs/documentation/attributes.md",
"chars": 8944,
"preview": "---\nsidebar_position: 3\n---\n\n# Attributes\n\n## data-scroll\n\nEnable viewport detection on an element.\n\n## data-scroll-posi"
},
{
"path": "packages/docs/docs/documentation/methods.md",
"chars": 5933,
"preview": "---\nsidebar_position: 2\n---\n\n# Methods\n\n## destroy()\n\nThe `destroy()` method allows you to destroy the Locomotive Scroll"
},
{
"path": "packages/docs/docs/documentation/options.md",
"chars": 6965,
"preview": "---\nsidebar_position: 1\n---\n\n# Options\n\n## lenisOptions\n\n- **Type:** `object`\n\n_(Optional)_ The `lenisOptions` paramet"
},
{
"path": "packages/docs/docs/examples.md",
"chars": 1529,
"preview": "---\nsidebar_position: 4\n---\n\n# Examples\n\nBelow are several CodeSandbox examples showcasing different usage scenarios:\n\n1"
},
{
"path": "packages/docs/docs/extras/limitations.md",
"chars": 2363,
"preview": "---\nsidebar_position: 1\n---\n\n# Limitations\n\n> We encourage the open-source community to develop and share their own solu"
},
{
"path": "packages/docs/docs/extras/migration-guide.md",
"chars": 16980,
"preview": "---\nsidebar_position: 1\n---\n\n# Migration Guide: v4 to v5\n\nThis guide will help you migrate from Locomotive Scroll v4 to "
},
{
"path": "packages/docs/docs/extras/showcase.md",
"chars": 915,
"preview": "---\nsidebar_position: 2\n---\n\n# Showcase\n\nHere is a curated list of websites that utilize Locomotive Scroll:\n\n- [Locomoti"
},
{
"path": "packages/docs/docs/getting-started/installation.md",
"chars": 700,
"preview": "---\nsidebar_position: 1\n---\n\n# Installation\n\nTo install Locomotive Scroll, you have two options:\n\n## NPM (Recommended)\n\n"
},
{
"path": "packages/docs/docs/getting-started/usage.md",
"chars": 1620,
"preview": "---\nsidebar_position: 2\n---\n\n# Usage\n\n## Javascript\n\n### With a bundler\n\n```js\nimport LocomotiveScroll from 'locomotive-"
},
{
"path": "packages/docs/docs/intro.md",
"chars": 2671,
"preview": "---\nslug: /\nsidebar_position: 1\ntitle: Introduction\n---\n\n# Locomotive Scroll v5\n\n> Detection of elements in viewport & s"
},
{
"path": "packages/docs/docusaurus.config.js",
"chars": 7062,
"preview": "// @ts-check\n// `@type` JSDoc annotations allow editor autocompletion and type checking\n// (when paired with `@ts-check`"
},
{
"path": "packages/docs/package.json",
"chars": 1246,
"preview": "{\n \"name\": \"docs-v-2\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"scripts\": {\n \"docusaurus\": \"docusaurus\",\n \"sta"
},
{
"path": "packages/docs/sidebars.js",
"chars": 1366,
"preview": "/**\n * Creating a sidebar enables you to:\n - create an ordered group of docs\n - render a sidebar for each doc of that gr"
},
{
"path": "packages/docs/src/css/custom.css",
"chars": 1935,
"preview": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framewor"
},
{
"path": "packages/docs/static/.nojekyll",
"chars": 0,
"preview": ""
},
{
"path": "packages/landing/.browserslistrc",
"chars": 9,
"preview": "defaults\n"
},
{
"path": "packages/landing/.editorconfig",
"chars": 266,
"preview": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_n"
},
{
"path": "packages/landing/.gitignore",
"chars": 98,
"preview": "node_modules\n.DS_Store\nThumbs.db\nloconfig.*.json\n!loconfig.example.json\n.prettierrc\nwww/**/*.html\n"
},
{
"path": "packages/landing/.npmrc",
"chars": 109,
"preview": "//npm.greensock.com/:_authToken=8d111942-fc15-4353-9a4e-ee25d3f5d4da\n@gsap:registry=https://npm.greensock.com"
},
{
"path": "packages/landing/.nvmrc",
"chars": 7,
"preview": "v20.10\n"
},
{
"path": "packages/landing/LICENSE",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) Locomotive, Inc.\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "packages/landing/README.md",
"chars": 3429,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/locomotivemtl/locomotive-boilerplate\">\n <img src=\"https://user"
},
{
"path": "packages/landing/assets/images/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/landing/assets/images/sprite/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/landing/assets/scripts/app.js",
"chars": 3437,
"preview": "import modular from 'modujs';\nimport * as modules from './modules';\nimport globals from './globals';\nimport { debounce }"
},
{
"path": "packages/landing/assets/scripts/config.js",
"chars": 1601,
"preview": "/**\n * > When using the esBuild API, all `process.env.NODE_ENV` expressions\n * > are automatically defined to `\"producti"
},
{
"path": "packages/landing/assets/scripts/globals.js",
"chars": 471,
"preview": "import svg4everybody from 'svg4everybody';\nimport { ENV } from './config';\n\n// Dynamic imports for development mode only"
},
{
"path": "packages/landing/assets/scripts/modules/Example.js",
"chars": 384,
"preview": "import { module } from 'modujs';\nimport { FONT } from '../config';\nimport { whenReady } from '../utils/fonts';\n\nexport d"
},
{
"path": "packages/landing/assets/scripts/modules/FadeInText.js",
"chars": 3908,
"preview": "import { module } from 'modujs';\nimport gsap from \"gsap\";\nimport { SplitText } from \"gsap/all\";\ngsap.registerPlugin(Spli"
},
{
"path": "packages/landing/assets/scripts/modules/HoverShuffle.js",
"chars": 3305,
"preview": "import { module } from 'modujs';\nimport { gsap } from 'gsap';\nconst DURATION = 0.25;\nconst SHUFFLE_PATTERN = /\\p{Extende"
},
{
"path": "packages/landing/assets/scripts/modules/Load.js",
"chars": 528,
"preview": "import { module } from 'modujs';\nimport modularLoad from 'modularload';\n\nexport default class extends module {\n const"
},
{
"path": "packages/landing/assets/scripts/modules/MaskLines.js",
"chars": 2822,
"preview": "import { module } from 'modujs';\nimport { whenReady } from \"../utils/fonts\";\nimport gsap from 'gsap';\nimport { SplitText"
},
{
"path": "packages/landing/assets/scripts/modules/Rail.js",
"chars": 5918,
"preview": "import { module as Module } from 'modujs';\nimport { whenReady } from '../utils/fonts';\nimport gsap from 'gsap';\nimport {"
},
{
"path": "packages/landing/assets/scripts/modules/Randomize.js",
"chars": 937,
"preview": "import { module } from 'modujs';\nimport gsap from \"gsap\";\nimport { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';\n"
},
{
"path": "packages/landing/assets/scripts/modules/Scroll.js",
"chars": 2786,
"preview": "import { module } from 'modujs'\nimport { $html } from '../utils/dom'\nimport LocomotiveScroll from '../../../../lib/index"
},
{
"path": "packages/landing/assets/scripts/modules.js",
"chars": 402,
"preview": "export { default as Load } from './modules/Load';\nexport { default as Rail } from './modules/Rail';\nexport { default as "
},
{
"path": "packages/landing/assets/scripts/utils/dom.js",
"chars": 101,
"preview": "const $html = document.documentElement\nconst $body = document.body\n\nexport {\n $html,\n $body,\n}\n"
},
{
"path": "packages/landing/assets/scripts/utils/fonts.js",
"chars": 10027,
"preview": "/**\n * Font Faces\n *\n * Provides utilities to facilitate interactions with the CSS Font Loading API.\n *\n * Features func"
},
{
"path": "packages/landing/assets/scripts/utils/grid-helper.js",
"chars": 3939,
"preview": "/**\n * Grid Helper\n *\n * Provides a grid based on the design guidelines and is helpful for web integration.\n *\n * - `Con"
},
{
"path": "packages/landing/assets/scripts/utils/html.js",
"chars": 2618,
"preview": "/**\n * Escape HTML string\n * @param {string} str - string to escape\n * @return {string} escaped string\n */\n\nconst esc"
},
{
"path": "packages/landing/assets/scripts/utils/image.js",
"chars": 2605,
"preview": "import { CSS_CLASS } from '../config'\n\n/**\n * Get an image meta data\n *\n * @param {HTMLImageElement} $img - The ima"
},
{
"path": "packages/landing/assets/scripts/utils/is.js",
"chars": 474,
"preview": "/**\n * Determines if the argument is object-like.\n *\n * A value is object-like if it's not `null` and has a `typeof` res"
},
{
"path": "packages/landing/assets/scripts/utils/maths.js",
"chars": 1245,
"preview": "/**\n * Clamp value\n * @param {number} min - start value\n * @param {number} max - end value\n * @param {number} a - a"
},
{
"path": "packages/landing/assets/scripts/utils/tickers.js",
"chars": 1828,
"preview": "/**\n * Creates a debounced function.\n *\n * A debounced function delays invoking `callback` until after\n * `delay` millis"
},
{
"path": "packages/landing/assets/scripts/utils/transform.js",
"chars": 849,
"preview": "/**\n * Get translate function\n * @param {HTMLElement} $el - DOM Element\n * @return {number|object} translate va"
},
{
"path": "packages/landing/assets/scripts/utils/visibility.js",
"chars": 3334,
"preview": "/**\n * The `PageVisibility` interface provides support for dispatching\n * a custom event derived from the value of {@see"
},
{
"path": "packages/landing/assets/scripts/vendors/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/landing/assets/styles/_core.scss",
"chars": 662,
"preview": "// ==========================================================================\n// Core\n// ==============================="
},
{
"path": "packages/landing/assets/styles/components/_button.scss",
"chars": 2466,
"preview": "// ==========================================================================\n// Components / Buttons\n// ==============="
},
{
"path": "packages/landing/assets/styles/components/_cascade.scss",
"chars": 1649,
"preview": "// ==========================================================================\n// Components / Cascade\n// ==============="
},
{
"path": "packages/landing/assets/styles/components/_fadeInText.scss",
"chars": 410,
"preview": "@use \"../core\" as *;\n\n.c-fadein-text_line {\n background: var(--background);\n -webkit-background-clip: text;\n co"
},
{
"path": "packages/landing/assets/styles/components/_features-grid.scss",
"chars": 15282,
"preview": "// ==========================================================================\n// Components / Features grid\n// ========="
},
{
"path": "packages/landing/assets/styles/components/_footer.scss",
"chars": 1959,
"preview": "@use \"../core\" as *;\n\n.c-footer_attributes {\n align-items: flex-start;\n div {\n @media (max-width: $to-mediu"
},
{
"path": "packages/landing/assets/styles/components/_form.scss",
"chars": 4791,
"preview": "// ==========================================================================\n// Components / Form\n// =================="
},
{
"path": "packages/landing/assets/styles/components/_header.scss",
"chars": 1804,
"preview": "// ==========================================================================\n// Components / Header\n// ================"
},
{
"path": "packages/landing/assets/styles/components/_heading.scss",
"chars": 1544,
"preview": "// ==========================================================================\n// Components / Headings\n// =============="
},
{
"path": "packages/landing/assets/styles/components/_hero.scss",
"chars": 2482,
"preview": "@use \"../core\" as *;\n\n// Components / Hero\n// =========================================================================="
},
{
"path": "packages/landing/assets/styles/components/_list.scss",
"chars": 4186,
"preview": "// ==========================================================================\n// Components / Perks list\n// ============"
},
{
"path": "packages/landing/assets/styles/components/_perks-list.scss",
"chars": 2744,
"preview": "// ==========================================================================\n// Components / Perks list\n// ============"
},
{
"path": "packages/landing/assets/styles/components/_preloader.scss",
"chars": 426,
"preview": "@use \"../core\" as *;\n\n.c-preloader {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n "
},
{
"path": "packages/landing/assets/styles/components/_rail.scss",
"chars": 835,
"preview": "// ==========================================================================\n// Components / Rail\n// =================="
},
{
"path": "packages/landing/assets/styles/components/_scrollbar.scss",
"chars": 804,
"preview": "// ==========================================================================\n// Components / Scrollbar\n// ============="
},
{
"path": "packages/landing/assets/styles/components/_section-heading.scss",
"chars": 1636,
"preview": "// ==========================================================================\n// Components / Section heading\n// ======="
},
{
"path": "packages/landing/assets/styles/components/_sticky-heading.scss",
"chars": 1730,
"preview": "@use \"../core\" as *;\n@use \"heading\" as *;\n\n// Components / Sticky heading\n// ==========================================="
},
{
"path": "packages/landing/assets/styles/components/_text.scss",
"chars": 1127,
"preview": "// ==========================================================================\n// Components / Headings\n// =============="
},
{
"path": "packages/landing/assets/styles/components/_tool.scss",
"chars": 6356,
"preview": "// ==========================================================================\n// Components / Tool\n// =================="
},
{
"path": "packages/landing/assets/styles/critical.scss",
"chars": 198,
"preview": "// ==========================================================================\n// Critical CSS\n// ======================="
},
{
"path": "packages/landing/assets/styles/elements/_document.scss",
"chars": 2177,
"preview": "// ==========================================================================\n// Elements / Document\n// ================"
},
{
"path": "packages/landing/assets/styles/generic/_button.scss",
"chars": 1016,
"preview": "// ==========================================================================\n// Generic / Buttons\n// =================="
},
{
"path": "packages/landing/assets/styles/generic/_form.scss",
"chars": 873,
"preview": "// ==========================================================================\n// Generic / Forms\n// ===================="
},
{
"path": "packages/landing/assets/styles/generic/_generic.scss",
"chars": 1240,
"preview": "// ==========================================================================\n// Generic\n// ============================"
},
{
"path": "packages/landing/assets/styles/generic/_media.scss",
"chars": 1075,
"preview": "// ==========================================================================\n// Generic / Media\n// ===================="
},
{
"path": "packages/landing/assets/styles/main.scss",
"chars": 1573,
"preview": "// ==========================================================================\n// Main\n// ==============================="
},
{
"path": "packages/landing/assets/styles/objects/_container.scss",
"chars": 705,
"preview": "// ==========================================================================\n// Objects / Container\n// ================"
},
{
"path": "packages/landing/assets/styles/objects/_grid.scss",
"chars": 4535,
"preview": "// ==========================================================================\n// Grid helper\n// ========================"
},
{
"path": "packages/landing/assets/styles/objects/_icons.scss",
"chars": 1887,
"preview": "// ==========================================================================\n// Objects / SVG Icons\n// ================"
},
{
"path": "packages/landing/assets/styles/objects/_layout.scss",
"chars": 2720,
"preview": "// ==========================================================================\n// Objects / Layout\n// ==================="
},
{
"path": "packages/landing/assets/styles/objects/_ratio.scss",
"chars": 840,
"preview": "// ==========================================================================\n// Objects / Ratio\n// ===================="
},
{
"path": "packages/landing/assets/styles/objects/_table.scss",
"chars": 426,
"preview": "// ==========================================================================\n// Objects / Tables\n// ==================="
},
{
"path": "packages/landing/assets/styles/settings/_config.breakpoints.scss",
"chars": 3375,
"preview": "// ==========================================================================\n// Settings / Config / Breakpoints\n// ===="
},
{
"path": "packages/landing/assets/styles/settings/_config.colors.scss",
"chars": 1558,
"preview": "// ==========================================================================\n// Settings / Config / Colors\n// ========="
},
{
"path": "packages/landing/assets/styles/settings/_config.eases.scss",
"chars": 2726,
"preview": "// ==========================================================================\n// Settings / Config / Eases\n// =========="
},
{
"path": "packages/landing/assets/styles/settings/_config.fonts.scss",
"chars": 3593,
"preview": "// ==========================================================================\n// Settings / Config / Breakpoints\n// ===="
},
{
"path": "packages/landing/assets/styles/settings/_config.scss",
"chars": 1375,
"preview": "// ==========================================================================\n// Settings / Config\n// =================="
},
{
"path": "packages/landing/assets/styles/settings/_config.spacers.scss",
"chars": 1777,
"preview": "// ==========================================================================\n// Settings / Config / Spacers\n// ========"
},
{
"path": "packages/landing/assets/styles/settings/_config.timings.scss",
"chars": 989,
"preview": "// ==========================================================================\n// Settings / Config / Timings\n// ========"
},
{
"path": "packages/landing/assets/styles/settings/_config.variables.scss",
"chars": 681,
"preview": "// ==========================================================================\n// Settings / Config / CSS VARS\n// ======="
},
{
"path": "packages/landing/assets/styles/settings/_config.zindexes.scss",
"chars": 1411,
"preview": "// ==========================================================================\n// Settings / Config / Z-indexes\n// ======"
},
{
"path": "packages/landing/assets/styles/tools/_family.scss",
"chars": 10711,
"preview": "// ==========================================================================\n// Tools / Family\n// ====================="
},
{
"path": "packages/landing/assets/styles/tools/_functions.scss",
"chars": 5997,
"preview": "// ==========================================================================\n// Tools / Functions\n// =================="
},
{
"path": "packages/landing/assets/styles/tools/_layout.scss",
"chars": 1738,
"preview": "// ==========================================================================\n// Tools / Layout\n// ====================="
},
{
"path": "packages/landing/assets/styles/tools/_maths.scss",
"chars": 4128,
"preview": "// ==========================================================================\n// Tools / Maths\n// ======================"
},
{
"path": "packages/landing/assets/styles/tools/_mixins.scss",
"chars": 7153,
"preview": "// ==========================================================================\n// Tools / Mixins\n// ====================="
},
{
"path": "packages/landing/assets/styles/tools/_widths.scss",
"chars": 3109,
"preview": "// ==========================================================================\n// Tools / Widths\n// ====================="
},
{
"path": "packages/landing/assets/styles/utilities/_align.scss",
"chars": 1013,
"preview": "// ==========================================================================\n// Utilities / Alignment\n// =============="
},
{
"path": "packages/landing/assets/styles/utilities/_grid-column.scss",
"chars": 1389,
"preview": "// ==========================================================================\n// Tools / Grid Columns\n// ==============="
},
{
"path": "packages/landing/assets/styles/utilities/_helpers.scss",
"chars": 1989,
"preview": "// ==========================================================================\n// Utilities / Helpers\n// ================"
},
{
"path": "packages/landing/assets/styles/utilities/_print.scss",
"chars": 1729,
"preview": "// ==========================================================================\n// Utilities / Print Mode\n// ============="
},
{
"path": "packages/landing/assets/styles/utilities/_ratio.scss",
"chars": 1040,
"preview": "// ==========================================================================\n// Utilities / Ratio\n// =================="
},
{
"path": "packages/landing/assets/styles/utilities/_spacing.scss",
"chars": 2830,
"preview": "// ==========================================================================\n// Utilities / Spacing\n// ================"
},
{
"path": "packages/landing/assets/styles/utilities/_states.scss",
"chars": 1324,
"preview": "// ==========================================================================\n// Utilities / States\n// ================="
},
{
"path": "packages/landing/assets/styles/utilities/_theme.scss",
"chars": 746,
"preview": "// ==========================================================================\n// Utilities / Theme\n// =================="
},
{
"path": "packages/landing/assets/styles/utilities/_widths.scss",
"chars": 707,
"preview": "// ==========================================================================\n// Utilities / Widths\n// ================="
},
{
"path": "packages/landing/assets/styles/vendors/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/landing/assets.json",
"chars": 32,
"preview": "{\n \"version\": 1767911710101\n}"
},
{
"path": "packages/landing/build/build.js",
"chars": 381,
"preview": "import buildEleventy from './tasks/eleventy.js';\nimport concatFiles from './tasks/concats.js';\nimport compileScripts fro"
},
{
"path": "packages/landing/build/helpers/config.js",
"chars": 451,
"preview": "/**\n * @file Provides simple user configuration options.\n */\n\nimport loconfig from '../../loconfig.json' with { type: 'j"
},
{
"path": "packages/landing/build/helpers/glob.js",
"chars": 4331,
"preview": "/**\n * @file Retrieve the first available glob library.\n *\n * Note that options vary between libraries.\n *\n * Candidates"
},
{
"path": "packages/landing/build/helpers/message.js",
"chars": 1327,
"preview": "/**\n * @file Provides a decorator for console messages.\n */\n\nimport kleur from 'kleur';\n\n/**\n * Outputs a message to the"
},
{
"path": "packages/landing/build/helpers/notification.js",
"chars": 1514,
"preview": "/**\n * @file Provides a decorator for cross-platform notification.\n */\n\nimport notifier from 'node-notifier';\n\n/**\n * Se"
},
{
"path": "packages/landing/build/helpers/postcss.js",
"chars": 3003,
"preview": "/**\n * @file If available, returns the PostCSS Processor creator and\n * any the Autoprefixer PostCSS plugin.\n */\n\n/**\n *"
},
{
"path": "packages/landing/build/helpers/template.js",
"chars": 2839,
"preview": "/**\n * @file Provides simple template tags.\n */\n\nimport loconfig from './config.js';\nimport {\n escapeRegExp,\n flat"
},
{
"path": "packages/landing/build/migrate_imports.js",
"chars": 2270,
"preview": "\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(im"
},
{
"path": "packages/landing/build/tasks/concats.js",
"chars": 4923,
"preview": "import loconfig from '../helpers/config.js';\nimport glob, { supportsGlob } from '../helpers/glob.js';\nimport message fro"
},
{
"path": "packages/landing/build/tasks/eleventy.js",
"chars": 1233,
"preview": "import message from '../helpers/message.js';\nimport { merge } from '../utils/index.js';\nimport Eleventy from \"@11ty/elev"
},
{
"path": "packages/landing/build/tasks/scripts.js",
"chars": 3739,
"preview": "import loconfig from '../helpers/config.js';\nimport message from '../helpers/message.js';\nimport notification from '../h"
},
{
"path": "packages/landing/build/tasks/styles.js",
"chars": 8700,
"preview": "import loconfig from '../helpers/config.js';\nimport message from '../helpers/message.js';\nimport notification from '../h"
},
{
"path": "packages/landing/build/tasks/svgs.js",
"chars": 3021,
"preview": "import loconfig from '../helpers/config.js';\nimport message from '../helpers/message.js';\nimport notification from '../h"
},
{
"path": "packages/landing/build/tasks/versions.js",
"chars": 12605,
"preview": "import loconfig from '../helpers/config.js';\nimport message from '../helpers/message.js';\nimport resolve from '../helper"
},
{
"path": "packages/landing/build/utils/index.js",
"chars": 2756,
"preview": "/**\n * @file Provides generic functions and constants.\n */\n\n/**\n * @type {RegExp} - Match all special characters.\n */\nco"
},
{
"path": "packages/landing/build/watch.js",
"chars": 5616,
"preview": "import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js';\nimport compileScripts, { developmentScript"
},
{
"path": "packages/landing/data/features.json",
"chars": 1494,
"preview": "{\n \"title\": \"Features\",\n \"description\": \"Locomotive Scroll is a thin, opinionated wrapper around Lenis. You get al"
},
{
"path": "packages/landing/data/general.json",
"chars": 386,
"preview": "{\n \"title\": \"Locomotive Scroll\",\n \"description\": \"A lightweight scroll library for modern web experiences. Detecti"
},
{
"path": "packages/landing/data/metadata.json",
"chars": 460,
"preview": "{\n \"title\": \"Locomotive Scroll — Detection of elements in viewport & smooth scrolling with parallax effects\",\n \"de"
},
{
"path": "packages/landing/data/perks.json",
"chars": 1288,
"preview": "{\n \"bigText\": \"Version 5 is a complete rewrite. Designed for modern workflows, built on top of Lenis, and optimized f"
},
{
"path": "packages/landing/data/showcase.json",
"chars": 1990,
"preview": "{\n \"title\": \"Showcase\",\n \"description\": \"Real projects built with Locomotive Scroll by leading studios and develop"
},
{
"path": "packages/landing/data/tools.json",
"chars": 702,
"preview": "{\n \"inview\": {\n \"title\": \"In-view detection\",\n \"description\": \"Add classes, trigger callbacks, or fire "
},
{
"path": "packages/landing/docs/development.md",
"chars": 10980,
"preview": "# Development\n\n* [Installation](#installation)\n* [Usage](#usage)\n* [Configuration](#configuration)\n * [Environment Conf"
},
{
"path": "packages/landing/docs/grid.md",
"chars": 3981,
"preview": "# Grid system\n\n* [Architectures](#architecture)\n * [Build tasks](#build-tasks)\n * [Configuration](#configuration)\n* "
},
{
"path": "packages/landing/docs/technologies.md",
"chars": 6095,
"preview": "# Technologies\n\n* [Styles](#styles)\n * [CSS Architecture](#css-architecture)\n * [CSS Naming Convention](#css-naming-co"
},
{
"path": "packages/landing/eleventy.config.cjs",
"chars": 2701,
"preview": "// Import required modules\nconst twig = require(\"twig\");\nconst fs = require('fs');\nconst path = require('path');\nconst e"
},
{
"path": "packages/landing/loconfig.example.json",
"chars": 197,
"preview": "{\n \"server\": {\n \"https\": {\n \"key\": \"~/.config/valet/Certificates/{% paths.url %}.key\",\n "
},
{
"path": "packages/landing/loconfig.json",
"chars": 2070,
"preview": "{\n \"paths\": {\n \"url\": \"locomotive-scroll-website.test\",\n \"src\": \"./assets\",\n \"dest\": \"../../www/"
},
{
"path": "packages/landing/package.json",
"chars": 1380,
"preview": "{\n \"private\": true,\n \"name\": \"@locomotivemtl/boilerplate\",\n \"title\": \"Locomotive Boilerplate\",\n \"version\": \""
},
{
"path": "packages/landing/views/layouts/base.twig",
"chars": 3818,
"preview": "<!doctype html>\n<html class=\"is-loading\" lang=\"en\" data-header-theme=\"blue\">\n <head>\n <meta charset=\"utf-8\">\n\n"
},
{
"path": "packages/landing/views/layouts/features.twig",
"chars": 9114,
"preview": "<section class=\"u-relative || o-container\" data-theme=\"black\" id=\"features\">\n\n <div\n class=\"c-header-theme-tog"
},
{
"path": "packages/landing/views/layouts/hero.twig",
"chars": 2862,
"preview": "<section class=\"o-container || u-relative\" data-theme=\"blue\">\n\n <div\n class=\"c-header-theme-toggler -hero\"\n "
},
{
"path": "packages/landing/views/layouts/perks.twig",
"chars": 2147,
"preview": "<section class=\"u-relative\" data-theme=\"white\">\n\n <div\n class=\"c-header-theme-toggler\"\n data-scroll\n "
},
{
"path": "packages/landing/views/layouts/showcase.twig",
"chars": 1395,
"preview": "<section class=\"u-relative || u-padding-bottom-2xl\" data-theme=\"white\" id=\"showcase\">\n\n <div\n class=\"c-header-"
},
{
"path": "packages/landing/views/layouts/tools.twig",
"chars": 7734,
"preview": "<section class=\"u-relative || u-clipped || u-padding-bottom-2xl\" data-theme=\"blue\">\n\n <div\n class=\"c-header-th"
},
{
"path": "packages/landing/views/partials/footer.twig",
"chars": 3787,
"preview": "<footer class=\"u-relative || c-footer || o-container || u-padding-top-sm\" data-theme=\"blue\">\n\n <div\n class=\"c-"
},
{
"path": "packages/landing/views/partials/header.twig",
"chars": 772,
"preview": "<header class=\"c-header\">\n <div class=\"c-header_col\">\n <p class=\"c-text -body-regular\">{{ general.title }}</p>"
},
{
"path": "packages/landing/views/partials/list.twig",
"chars": 1325,
"preview": "{# List #}\n{% set _items = items %}\n{% set _classes = classes | default(null) %}\n{% set _modifiers = modifiers | d"
},
{
"path": "packages/landing/views/partials/rail.twig",
"chars": 502,
"preview": "<div\n class=\"c-rail\"\n data-module-rail\n data-values=\"rail\"\n data-rail-direction=\"-1\"\n data-scroll\n dat"
},
{
"path": "packages/landing/views/partials/section-heading.twig",
"chars": 762,
"preview": "<h2 class=\"c-heading-serif -large || c-section-heading || u-padding-bottom-3xl u-padding-top-md\">\n <span class=\"c-sec"
},
{
"path": "packages/landing/views/snippets/button.twig",
"chars": 1914,
"preview": "{# --- Parameters ------------------------- #}\n\n{% set _tag = tag | default('button') %}\n{% set _href "
},
{
"path": "packages/landing/views/snippets/icon.twig",
"chars": 498,
"preview": "{# --- Parameters ------------------------- #}\n\n{% set _icon = icon %}\n{% set _classes = classes | default(null) "
},
{
"path": "packages/landing/views/templates/index.twig",
"chars": 279,
"preview": "\n{% extends \"@layouts/base.twig\" %}\n\n{% block content %}\n\n {% include \"@layouts/hero.twig\" %}\n {% include \"@layout"
},
{
"path": "packages/lib/README.md",
"chars": 1561,
"preview": "# Locomotive Scroll\n\n[](https://www.npmjs.com/package/"
},
{
"path": "packages/lib/bundled/locomotive-scroll.css",
"chars": 380,
"preview": "html.lenis,html.lenis body{height:auto}.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}.lenis [data-lenis-prev"
},
{
"path": "packages/lib/bundled/locomotive-scroll.js",
"chars": 86526,
"preview": "(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory("
},
{
"path": "packages/lib/core/Core.ts",
"chars": 12863,
"preview": "/**\n * Integrates Lenis with Locomotive's built-in animation system\n */\n\nimport type {\n CoreOptions,\n IScrollEleme"
},
{
"path": "packages/lib/core/IO.ts",
"chars": 4248,
"preview": "/**\n * Intersection Observer\n *\n * Detecting visibility of an element in the viewport.\n *\n * Features functions to:\n *\n "
}
]
// ... and 31 more files (download for full content)
About this extraction
This page contains the full source code of the locomotivemtl/locomotive-scroll GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 231 files (986.1 KB), approximately 283.2k tokens, and a symbol index with 660 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.