mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Compare commits
25 Commits
feature/bu
...
feature/la
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e12fc31f0 | ||
|
|
70b36052e6 | ||
|
|
f1e2e2270f | ||
|
|
13735c64f9 | ||
|
|
659ef3767b | ||
|
|
e162af17dd | ||
|
|
e16ba2ca16 | ||
|
|
43a5eb1ad3 | ||
|
|
217a1adba7 | ||
|
|
a11e98e31e | ||
|
|
6ef90dbe11 | ||
|
|
6726d665f2 | ||
|
|
dca6c5de1d | ||
|
|
05a00c4258 | ||
|
|
7517be0e76 | ||
|
|
dcec21adf4 | ||
|
|
297e0b4ec8 | ||
|
|
9a2083d894 | ||
|
|
1a81c865ae | ||
|
|
d6b5784cdd | ||
|
|
8894664743 | ||
|
|
be71474633 | ||
|
|
810df92a61 | ||
|
|
bf28fe21a3 | ||
|
|
7b3cefd8df |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": 1675955478084
|
||||
"version": 1691082391092
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import modular from 'modujs';
|
||||
import * as modules from './modules';
|
||||
import globals from './globals';
|
||||
import { html } from './utils/environment';
|
||||
import config from './config'
|
||||
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';
|
||||
|
||||
const app = new modular({
|
||||
@@ -25,28 +26,32 @@ window.onload = (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const EAGER_FONTS = [
|
||||
{ family: 'Source Sans', style: 'normal', weight: 400 },
|
||||
{ family: 'Source Sans', style: 'normal', weight: 700 },
|
||||
];
|
||||
|
||||
function init() {
|
||||
globals();
|
||||
|
||||
app.init(app);
|
||||
|
||||
html.classList.add('is-loaded');
|
||||
html.classList.add('is-ready');
|
||||
html.classList.remove('is-loading');
|
||||
$html.classList.add(CSS_CLASS.LOADED);
|
||||
$html.classList.add(CSS_CLASS.READY);
|
||||
$html.classList.remove(CSS_CLASS.LOADING);
|
||||
|
||||
// Bind window resize event with default vars
|
||||
const resizeEndEvent = new CustomEvent(CUSTOM_EVENT.RESIZE_END)
|
||||
window.addEventListener('resize', () => {
|
||||
$html.style.setProperty('--vw', `${document.documentElement.clientWidth * 0.01}px`)
|
||||
debounce(() => {
|
||||
window.dispatchEvent(resizeEndEvent)
|
||||
}, 200, false)
|
||||
})
|
||||
|
||||
/**
|
||||
* Eagerly load the following fonts.
|
||||
*/
|
||||
if (isFontLoadingAPIAvailable) {
|
||||
loadFonts(EAGER_FONTS, config.IS_DEV).then((eagerFonts) => {
|
||||
html.classList.add('fonts-loaded');
|
||||
loadFonts(FONT.EAGER, ENV.IS_DEV).then((eagerFonts) => {
|
||||
$html.classList.add(CSS_CLASS.FONTS_LOADED);
|
||||
|
||||
if (config.IS_DEV) {
|
||||
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*/))
|
||||
|
||||
@@ -7,18 +7,51 @@
|
||||
* > (since `process` is a Node API, not a web API).
|
||||
* > — https://esbuild.github.io/api/#platform
|
||||
*/
|
||||
const env = process.env.NODE_ENV
|
||||
|
||||
export default config = Object.freeze({
|
||||
// Environments
|
||||
ENV: env,
|
||||
IS_PROD: env === 'production',
|
||||
IS_DEV: env === 'development',
|
||||
const NODE_ENV = process.env.NODE_ENV
|
||||
const IS_DESKTOP = typeof window.orientation === 'undefined'
|
||||
|
||||
// CSS class names
|
||||
CSS_CLASS: {
|
||||
LOADING: 'is-loading',
|
||||
READY: 'is-ready',
|
||||
LOADED: 'is-loaded',
|
||||
},
|
||||
// Main environment variables
|
||||
const ENV = Object.freeze({
|
||||
// Node environment
|
||||
NAME: NODE_ENV,
|
||||
IS_PROD: NODE_ENV === 'production',
|
||||
IS_DEV: NODE_ENV === 'development',
|
||||
|
||||
// Device
|
||||
IS_DESKTOP,
|
||||
IS_MOBILE: !IS_DESKTOP,
|
||||
})
|
||||
|
||||
// Main CSS classes used within the project
|
||||
const CSS_CLASS = Object.freeze({
|
||||
LOADING: 'is-loading',
|
||||
LOADED: 'is-loaded',
|
||||
READY: 'is-ready',
|
||||
FONTS_LOADED: 'fonts-loaded',
|
||||
IMAGE: "c-image",
|
||||
IMAGE_LAZY_LOADED: "-lazy-loaded",
|
||||
IMAGE_LAZY_LOADING: "-lazy-loading",
|
||||
IMAGE_LAZY_ERROR: "-lazy-error",
|
||||
})
|
||||
|
||||
// Custom js events
|
||||
const CUSTOM_EVENT = Object.freeze({
|
||||
RESIZE_END: 'loco.resizeEnd',
|
||||
// ...
|
||||
})
|
||||
|
||||
// Fonts parameters
|
||||
const FONT = Object.freeze({
|
||||
EAGER: [
|
||||
{ family: 'Source Sans', style: 'normal', weight: 400 },
|
||||
{ family: 'Source Sans', style: 'normal', weight: 700 },
|
||||
],
|
||||
})
|
||||
|
||||
export {
|
||||
ENV,
|
||||
CSS_CLASS,
|
||||
CUSTOM_EVENT,
|
||||
FONT,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import svg4everybody from 'svg4everybody';
|
||||
import config from './config';
|
||||
import { ENV } from './config';
|
||||
import { triggerLazyloadCallbacks } from './utils/image';
|
||||
|
||||
// Dynamic imports for development mode only
|
||||
let gridHelper;
|
||||
(async () => {
|
||||
if (config.IS_DEV) {
|
||||
if (ENV.IS_DEV) {
|
||||
const gridHelperModule = await import('./utils/grid-helper');
|
||||
gridHelper = gridHelperModule?.gridHelper;
|
||||
}
|
||||
@@ -20,4 +21,9 @@ export default function () {
|
||||
* Add grid helper
|
||||
*/
|
||||
gridHelper?.();
|
||||
|
||||
/**
|
||||
* Trigger lazyload
|
||||
*/
|
||||
triggerLazyloadCallbacks();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { module } from 'modujs';
|
||||
import { EAGER_FONTS } from '../app';
|
||||
import { FONT } from '../config';
|
||||
import { whenReady } from '../utils/fonts';
|
||||
|
||||
export default class extends module {
|
||||
@@ -8,7 +8,7 @@ export default class extends module {
|
||||
}
|
||||
|
||||
init() {
|
||||
whenReady(EAGER_FONTS).then((fonts) => this.onFontsLoaded(fonts));
|
||||
whenReady(FONT.EAGER).then((fonts) => this.onFontsLoaded(fonts));
|
||||
}
|
||||
|
||||
onFontsLoaded(fonts) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { module } from 'modujs';
|
||||
import modularLoad from 'modularload';
|
||||
import { resetLazyloadCallbacks, triggerLazyloadCallbacks } from "../utils/image";
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
@@ -7,16 +8,28 @@ export default class extends module {
|
||||
}
|
||||
|
||||
init() {
|
||||
const load = new modularLoad({
|
||||
this.load = new modularLoad({
|
||||
enterDelay: 0,
|
||||
transitions: {
|
||||
customTransition: {}
|
||||
}
|
||||
});
|
||||
|
||||
load.on('loaded', (transition, oldContainer, newContainer) => {
|
||||
this.load.on('loaded', (transition, oldContainer, newContainer) => {
|
||||
this.call('destroy', oldContainer, 'app');
|
||||
this.call('update', newContainer, 'app');
|
||||
|
||||
/**
|
||||
* Trigger lazyload
|
||||
*/
|
||||
triggerLazyloadCallbacks();
|
||||
});
|
||||
|
||||
this.load.on("loading", () => {
|
||||
/**
|
||||
* Remove previous lazyload callbacks
|
||||
*/
|
||||
resetLazyloadCallbacks();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { module } from 'modujs';
|
||||
import { lazyLoadImage } from '../utils/image';
|
||||
import LocomotiveScroll from 'locomotive-scroll';
|
||||
import { module } from 'modujs'
|
||||
import LocomotiveScroll from 'locomotive-scroll'
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
@@ -9,41 +8,43 @@ export default class extends module {
|
||||
|
||||
init() {
|
||||
this.scroll = new LocomotiveScroll({
|
||||
el: this.el,
|
||||
smooth: true
|
||||
});
|
||||
|
||||
this.scroll.on('call', (func, way, obj, id) => {
|
||||
// Using modularJS
|
||||
this.call(func[0], { way, obj }, func[1], func[2]);
|
||||
});
|
||||
|
||||
this.scroll.on('scroll', (args) => {
|
||||
// console.log(args.scroll);
|
||||
modularInstance: this,
|
||||
})
|
||||
|
||||
// // Force scroll to top
|
||||
// if (history.scrollRestoration) {
|
||||
// history.scrollRestoration = 'manual'
|
||||
// window.scrollTo(0, 0)
|
||||
// }
|
||||
}
|
||||
|
||||
scrollTo(params) {
|
||||
let { target, ...options } = params
|
||||
|
||||
options = Object.assign({
|
||||
// Defaults
|
||||
duration: 1,
|
||||
}, options)
|
||||
|
||||
this.scroll?.scrollTo(target, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load the related image.
|
||||
*
|
||||
* @see ../utils/image.js
|
||||
*
|
||||
* It is recommended to wrap your `<img>` into an element with the
|
||||
* CSS class name `.c-lazy`. The CSS class name modifier `.-lazy-loaded`
|
||||
* will be applied on both the image and the parent wrapper.
|
||||
*
|
||||
* ```html
|
||||
* <div class="c-lazy o-ratio u-4:3">
|
||||
* <img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
|
||||
*/
|
||||
lazyLoad(args) {
|
||||
lazyLoadImage(args.obj.el, null, () => {
|
||||
//callback
|
||||
})
|
||||
* 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)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
7
assets/scripts/utils/dom.js
Normal file
7
assets/scripts/utils/dom.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const $html = document.documentElement
|
||||
const $body = document.body
|
||||
|
||||
export {
|
||||
$html,
|
||||
$body,
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
const APP_NAME = 'Boilerplate';
|
||||
const DATA_API_KEY = '.data-api';
|
||||
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
const isDebug = html.hasAttribute('data-debug');
|
||||
|
||||
|
||||
export { APP_NAME, DATA_API_KEY, html, body, isDebug };
|
||||
@@ -4,15 +4,18 @@
|
||||
* @return {string} escaped string
|
||||
*/
|
||||
|
||||
const escapeHtml = str =>
|
||||
str.replace(/[&<>'"]/g, tag => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
"'": ''',
|
||||
'"': '"'
|
||||
}[tag]))
|
||||
|
||||
const escapeHtml = (str) =>
|
||||
str.replace(
|
||||
/[&<>'"]/g,
|
||||
(tag) =>
|
||||
({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"'": "'",
|
||||
'"': """,
|
||||
}[tag])
|
||||
);
|
||||
|
||||
/**
|
||||
* Unescape HTML string
|
||||
@@ -20,13 +23,13 @@ const escapeHtml = str =>
|
||||
* @return {string} unescaped string
|
||||
*/
|
||||
|
||||
const unescapeHtml = str =>
|
||||
str.replace('&', '&')
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace(''', "'")
|
||||
.replace('"', '"')
|
||||
|
||||
const unescapeHtml = (str) =>
|
||||
str
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace(""", '"');
|
||||
|
||||
/**
|
||||
* Get element data attributes
|
||||
@@ -34,46 +37,41 @@ const unescapeHtml = str =>
|
||||
* @return {array} node data
|
||||
*/
|
||||
|
||||
const getNodeData = node => {
|
||||
|
||||
const getNodeData = (node) => {
|
||||
// All attributes
|
||||
const attributes = node.attributes
|
||||
const attributes = node.attributes;
|
||||
|
||||
// Regex Pattern
|
||||
const pattern = /^data\-(.+)$/
|
||||
const pattern = /^data\-(.+)$/;
|
||||
|
||||
// Output
|
||||
const data = {}
|
||||
const data = {};
|
||||
|
||||
for (let i in attributes) {
|
||||
if (!attributes[i]) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attributes name (ex: data-module)
|
||||
let name = attributes[i].name
|
||||
let name = attributes[i].name;
|
||||
|
||||
// This happens.
|
||||
if (!name) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
let match = name.match(pattern)
|
||||
let match = name.match(pattern);
|
||||
if (!match) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this throws an error, you have some
|
||||
// serious problems in your HTML.
|
||||
data[match[1]] = getData(node.getAttribute(name))
|
||||
data[match[1]] = getData(node.getAttribute(name));
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse value to data type.
|
||||
@@ -83,32 +81,31 @@ const getNodeData = node => {
|
||||
* @return {mixed} value in its natural data type
|
||||
*/
|
||||
|
||||
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
|
||||
const getData = data => {
|
||||
if (data === 'true') {
|
||||
return true
|
||||
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
|
||||
const getData = (data) => {
|
||||
if (data === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data === 'false') {
|
||||
return false
|
||||
if (data === "false") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data === 'null') {
|
||||
return null
|
||||
if (data === "null") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only convert to a number if it doesn't change the string
|
||||
if (data === +data+'') {
|
||||
return +data
|
||||
if (data === +data + "") {
|
||||
return +data;
|
||||
}
|
||||
|
||||
if (rbrace.test(data)) {
|
||||
return JSON.parse(data)
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array containing all the parent nodes of the given node
|
||||
@@ -116,20 +113,45 @@ const getData = data => {
|
||||
* @return {array} parent nodes
|
||||
*/
|
||||
|
||||
const getParents = $el => {
|
||||
|
||||
const getParents = ($el) => {
|
||||
// Set up a parent array
|
||||
let parents = []
|
||||
let parents = [];
|
||||
|
||||
// Push each parent element to the array
|
||||
for (; $el && $el !== document; $el = $el.parentNode) {
|
||||
parents.push($el)
|
||||
parents.push($el);
|
||||
}
|
||||
|
||||
// Return our parent array
|
||||
return parents
|
||||
}
|
||||
return parents;
|
||||
};
|
||||
|
||||
// https://gomakethings.com/how-to-get-the-closest-parent-element-with-a-matching-selector-using-vanilla-javascript/
|
||||
const queryClosestParent = ($el, selector) => {
|
||||
// Element.matches() polyfill
|
||||
if (!Element.prototype.matches) {
|
||||
Element.prototype.matches =
|
||||
Element.prototype.matchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector ||
|
||||
function (s) {
|
||||
var matches = (
|
||||
this.document || this.ownerDocument
|
||||
).querySelectorAll(s),
|
||||
i = matches.length;
|
||||
while (--i >= 0 && matches.item(i) !== this) {}
|
||||
return i > -1;
|
||||
};
|
||||
}
|
||||
|
||||
// Get the closest matching element
|
||||
for (; $el && $el !== document; $el = $el.parentNode) {
|
||||
if ($el.matches(selector)) return $el;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export {
|
||||
escapeHtml,
|
||||
@@ -137,4 +159,5 @@ export {
|
||||
getNodeData,
|
||||
getData,
|
||||
getParents,
|
||||
}
|
||||
queryClosestParent,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { CSS_CLASS } from '../config'
|
||||
import { queryClosestParent } from './html'
|
||||
|
||||
/**
|
||||
* Get an image meta data
|
||||
*
|
||||
@@ -89,22 +92,111 @@ const lazyLoadImage = async ($el, url, callback) => {
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
let lazyParent = $el.closest('.c-lazy')
|
||||
let lazyParent = $el.closest(`.${CSS_CLASS.IMAGE}`)
|
||||
|
||||
if(lazyParent) {
|
||||
lazyParent.classList.add('-lazy-loaded')
|
||||
lazyParent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
|
||||
lazyParent.style.backgroundImage = ''
|
||||
}
|
||||
|
||||
$el.classList.add('-lazy-loaded')
|
||||
$el.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
|
||||
|
||||
callback?.()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazyload Callbacks
|
||||
*
|
||||
*/
|
||||
const lazyImageLoad = (e) => {
|
||||
const $img = e.currentTarget;
|
||||
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if ($parent) {
|
||||
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
}
|
||||
|
||||
$img.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
});
|
||||
};
|
||||
|
||||
const lazyImageError = (e) => {
|
||||
const $img = e.currentTarget;
|
||||
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if ($parent) {
|
||||
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_ERROR);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* Trigger Lazyload Callbacks */
|
||||
const triggerLazyloadCallbacks = ($lazyImagesArgs) => {
|
||||
const $lazyImages = $lazyImagesArgs
|
||||
? $lazyImagesArgs
|
||||
: document.querySelectorAll('[loading="lazy"]');
|
||||
|
||||
if ("loading" in HTMLImageElement.prototype) {
|
||||
for (const $img of $lazyImages) {
|
||||
const $parent = queryClosestParent(
|
||||
$img,
|
||||
`.${CSS_CLASS.IMAGE}`
|
||||
);
|
||||
|
||||
|
||||
if (!$img.complete) {
|
||||
if($parent) {
|
||||
$parent.classList.add(
|
||||
CSS_CLASS.IMAGE_LAZY_LOADING
|
||||
);
|
||||
}
|
||||
|
||||
$img.addEventListener("load", lazyImageLoad, { once: true });
|
||||
$img.addEventListener("error", lazyImageError, { once: true });
|
||||
} else {
|
||||
if (!$img.complete) {
|
||||
$parent.classList.add(
|
||||
CSS_CLASS.IMAGE_LAZY_LOADED
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if 'loading' supported
|
||||
for (const $img of $lazyImages) {
|
||||
const $parent = queryClosestParent(
|
||||
$img,
|
||||
`.${CSS_CLASS.IMAGE}`
|
||||
);
|
||||
|
||||
if($parent) {
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Reset Lazyload Callbacks */
|
||||
const resetLazyloadCallbacks = () => {
|
||||
if ("loading" in HTMLImageElement.prototype) {
|
||||
const $lazyImages = document.querySelectorAll('[loading="lazy"]');
|
||||
for (const $img of $lazyImages) {
|
||||
$img.removeEventListener("load", lazyImageLoad, { once: true });
|
||||
$img.removeEventListener("error", lazyImageError, { once: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
getImageMetadata,
|
||||
loadImage,
|
||||
lazyLoadImage
|
||||
lazyLoadImage,
|
||||
triggerLazyloadCallbacks,
|
||||
resetLazyloadCallbacks
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ $input-icon-color: 424242; // No #
|
||||
.c-form_input {
|
||||
padding: rem(10px);
|
||||
border: 1px solid lightgray;
|
||||
background-color: $color-lightest;
|
||||
background-color: color(lightest);
|
||||
|
||||
&:hover {
|
||||
border-color: darkgray;
|
||||
@@ -71,7 +71,7 @@ $checkbox-icon-color: $input-icon-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: $color-lightest;
|
||||
background-color: color(lightest);
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
|
||||
20
assets/styles/components/_image.scss
Normal file
20
assets/styles/components/_image.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
// ==========================================================================
|
||||
// Components / Image
|
||||
// ==========================================================================
|
||||
|
||||
.c-image {
|
||||
|
||||
}
|
||||
|
||||
.c-image_img {
|
||||
|
||||
// Lazy loading styles
|
||||
.c-image.-lazy-load & {
|
||||
transition: opacity $speed $easing;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.c-image.-lazy-loaded & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Components / Scrollbar
|
||||
// ==========================================================================
|
||||
|
||||
.c-scrollbar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 11px;
|
||||
height: 100vh;
|
||||
transform-origin: center right;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scaleX(1.45);
|
||||
}
|
||||
|
||||
&:hover, .has-scroll-scrolling &, .has-scroll-dragging & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.c-scrollbar_thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: $color-darkest;
|
||||
opacity: 0.5;
|
||||
width: 7px;
|
||||
border-radius: 10px;
|
||||
margin: 2px;
|
||||
cursor: grab;
|
||||
|
||||
.has-scroll-dragging & {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
@@ -51,30 +51,14 @@ html {
|
||||
&.is-loading {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
&.has-scroll-smooth {
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.has-scroll-dragging {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
.has-scroll-smooth & {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: $selection-background-color;
|
||||
color: $selection-text-color;
|
||||
background-color: $color-selection-background;
|
||||
color: $color-selection-text;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
@import "generic/form";
|
||||
@import "generic/button";
|
||||
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
@import "node_modules/locomotive-scroll/dist/locomotive-scroll";
|
||||
|
||||
// Elements
|
||||
// ==========================================================================
|
||||
|
||||
@@ -40,7 +44,6 @@
|
||||
// Objects
|
||||
// ==========================================================================
|
||||
|
||||
@import "objects/scroll";
|
||||
@import "objects/container";
|
||||
@import "objects/ratio";
|
||||
@import "objects/icons";
|
||||
@@ -48,17 +51,13 @@
|
||||
// @import "objects/layout";
|
||||
// @import "objects/table";
|
||||
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
// @import "vendors/vendor";
|
||||
|
||||
// Components
|
||||
// ==========================================================================
|
||||
|
||||
@import "components/scrollbar";
|
||||
@import "components/heading";
|
||||
@import "components/button";
|
||||
@import "components/form";
|
||||
@import "components/image";
|
||||
|
||||
// Utilities
|
||||
// ==========================================================================
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
vertical-align: middle;
|
||||
|
||||
svg {
|
||||
--icon-height: calc(var(--icon-width) * (1 / (var(--icon-ratio))));
|
||||
--icon-height: calc(var(--icon-width) * math.div(1, (var(--icon-ratio))));
|
||||
|
||||
display: block;
|
||||
width: var(--icon-width);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Objects / Scroll
|
||||
// ==========================================================================
|
||||
|
||||
.o-scroll {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -5,24 +5,26 @@
|
||||
// Palette
|
||||
// =============================================================================
|
||||
|
||||
$color-lightest: #FFFFFF;
|
||||
$color-darkest: #000000;
|
||||
$colors: (
|
||||
primary: #3297FD,
|
||||
lightest: #FFFFFF,
|
||||
darkest: #000000,
|
||||
);
|
||||
|
||||
// Specific
|
||||
|
||||
// Specifics
|
||||
// =============================================================================
|
||||
|
||||
// Link
|
||||
$color-link: #1A0DAB;
|
||||
$color-link-focus: #1A0DAB;
|
||||
$color-link-hover: darken(#1A0DAB, 10%);
|
||||
$color-link: color(primary);
|
||||
$color-link-focus: color(primary);
|
||||
$color-link-hover: darken(color(primary), 10%);
|
||||
|
||||
// Selection
|
||||
$selection-text-color: #3297FD;
|
||||
$selection-background-color: #FFFFFF;
|
||||
|
||||
// Social Colors
|
||||
// =============================================================================
|
||||
$color-selection-text: color(darkest);
|
||||
$color-selection-background: color(lightest);
|
||||
|
||||
// Socials
|
||||
$color-facebook: #3B5998;
|
||||
$color-instagram: #E1306C;
|
||||
$color-youtube: #CD201F;
|
||||
|
||||
@@ -49,7 +49,7 @@ $font-faces: (
|
||||
// Base
|
||||
$font-size: 16px;
|
||||
$line-height: math.div(24px, $font-size);
|
||||
$font-color: $color-darkest;
|
||||
$font-color: color(darkest);
|
||||
|
||||
// Weights
|
||||
$font-weight-light: 300;
|
||||
|
||||
@@ -14,13 +14,18 @@
|
||||
--container-width: calc(100% - 2 * var(--grid-margin));
|
||||
|
||||
// Font sizes
|
||||
--font-size-h1: #{rem(36px)};
|
||||
--font-size-h1: #{responsive-type(36px, 72px, 1400px)};
|
||||
--font-size-h2: #{rem(28px)};
|
||||
--font-size-h3: #{rem(24px)};
|
||||
--font-size-h4: #{rem(20px)};
|
||||
--font-size-h5: #{rem(18px)};
|
||||
--font-size-h6: #{rem(16px)};
|
||||
|
||||
// // Colors
|
||||
// @each $color, $value in $colors {
|
||||
// --color-#{"" + $color}: #{$value};
|
||||
// }
|
||||
|
||||
@media (min-width: $from-small) {
|
||||
--grid-columns: #{$base-column-nb};
|
||||
--grid-gutter: #{rem(16px)};
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin middle($num) {
|
||||
&:nth-child(#{round($num / 2)}) {
|
||||
&:nth-child(#{round(math.div($num, 2))}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,3 +139,89 @@
|
||||
}
|
||||
|
||||
$context: 'frontend' !default;
|
||||
|
||||
// Returns calculation of a percentage of the grid cell width
|
||||
// with optional inset of grid gutter.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: grid-space(6/12);
|
||||
// margin-left: grid-space(1/12, 1);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage spacer
|
||||
// @param {number} $inset - The grid gutter inset
|
||||
// @return {function<number>}
|
||||
@function grid-space($percentage, $inset: 0) {
|
||||
@return calc(#{$percentage} * (100vw - 2 * var(--grid-margin, 0px)) - (1 - #{$percentage}) * var(--grid-gutter, 0px) + #{$inset} * var(--grid-gutter, 0px));
|
||||
}
|
||||
|
||||
// Returns calculation of a percentage of the viewport height.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// height: vh(100);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage number
|
||||
// @return {function<number>} in vh
|
||||
@function vh($number) {
|
||||
@return calc(#{$number} * var(--vh, 1vh));
|
||||
}
|
||||
|
||||
// Returns calculation of a percentage of the viewport width.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: vw(100);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage number
|
||||
// @return {function<number>} in vw
|
||||
|
||||
@function vw($number) {
|
||||
@return calc(#{$number} * var(--vw, 1vw));
|
||||
}
|
||||
|
||||
// Returns clamp of calculated preferred responsive font size
|
||||
// within a font size and breakpoint range.
|
||||
//
|
||||
// ```scss
|
||||
// .c-heading.-h1 {
|
||||
// font-size: responsive-type(30px, 60px, 1800);
|
||||
// }
|
||||
//
|
||||
// .c-heading.-h2 {
|
||||
// font-size: responsive-type(20px, 40px, $from-big);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $min-size - Minimum font size in pixels.
|
||||
// @param {number} $max-size - Maximum font size in pixels.
|
||||
// @param {number} $breakpoint - Maximum breakpoint.
|
||||
// @return {function<number, function<number>, number>}
|
||||
@function responsive-type($min-size, $max-size, $breakpoint) {
|
||||
$delta: math.div($max-size, $breakpoint);
|
||||
@return clamp($min-size, calc(#{strip-unit($delta)} * #{vw(100)}), $max-size);
|
||||
}
|
||||
|
||||
// Returns color code.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: color(primary);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {string} $key - The color key in $colors.
|
||||
// @return {color}
|
||||
|
||||
@function color($key) {
|
||||
@if not map-has-key($colors, $key) {
|
||||
@error "Unknown '#{$key}' in $colors.";
|
||||
}
|
||||
@return map-get($colors, $key);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
// Tools / Maths
|
||||
// ==========================================================================
|
||||
|
||||
// Removes the unit from the given number.
|
||||
// Remove the unit of a length
|
||||
//
|
||||
// @param {number} $number The number to strip.
|
||||
// @return {number}
|
||||
// @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);
|
||||
}
|
||||
|
||||
@function strip-units($number) {
|
||||
@return $number / ($number * 0 + 1);
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// Returns the square root of the given number.
|
||||
@@ -21,7 +26,7 @@
|
||||
$value: $x;
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
$value: $x - ($x * $x - abs($number)) / (2 * $x);
|
||||
$value: $x - math.div(($x * $x - abs($number)), (2 * $x));
|
||||
$x: $value;
|
||||
}
|
||||
|
||||
@@ -43,7 +48,7 @@
|
||||
}
|
||||
} @else if $exp < 0 {
|
||||
@for $i from 1 through -$exp {
|
||||
$value: $value / $number;
|
||||
$value: math.div($value, $number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +93,7 @@
|
||||
|
||||
// If the angle has `deg` as unit, convert to radians.
|
||||
@if ($unit == deg) {
|
||||
@return $angle / 180 * pi();
|
||||
@return math.div($angle, 180) * pi();
|
||||
}
|
||||
|
||||
@return $angle;
|
||||
@@ -104,7 +109,7 @@
|
||||
$angle: rad($angle);
|
||||
|
||||
@for $i from 0 through 10 {
|
||||
$sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
|
||||
$sin: $sin + pow(-1, $i) * math.div(pow($angle, (2 * $i + 1)), fact(2 * $i + 1));
|
||||
}
|
||||
|
||||
@return $sin;
|
||||
@@ -120,7 +125,7 @@
|
||||
$angle: rad($angle);
|
||||
|
||||
@for $i from 0 through 10 {
|
||||
$cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 * $i);
|
||||
$cos: $cos + pow(-1, $i) * math.div(pow($angle, 2 * $i), fact(2 * $i));
|
||||
}
|
||||
|
||||
@return $cos;
|
||||
@@ -132,5 +137,5 @@
|
||||
// @return {number}
|
||||
|
||||
@function tan($angle) {
|
||||
@return sin($angle) / cos($angle);
|
||||
@return math.div(sin($angle), cos($angle));
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
font-size: rem($font-size) $important;
|
||||
|
||||
@if ($line-height == "auto") {
|
||||
line-height: ceil($font-size / $line-height) * ($line-height / $font-size) $important;
|
||||
line-height: ceil(math.div($font-size, $line-height)) * math.div($line-height, $font-size) $important;
|
||||
}
|
||||
@else {
|
||||
@if (type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") {
|
||||
|
||||
@@ -58,7 +58,7 @@ $breakpoint-delimiter: \@ !default;
|
||||
@for $numerator from 1 through $denominator {
|
||||
// Build a class in the format `.u-3/4[@<breakpoint>]`.
|
||||
.u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
width: ($numerator / $denominator) * 100% $important;
|
||||
width: math.div($numerator, $denominator) * 100% $important;
|
||||
}
|
||||
|
||||
@if ($widths-offsets == true) {
|
||||
@@ -66,13 +66,13 @@ $breakpoint-delimiter: \@ !default;
|
||||
.u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
position: relative $important;
|
||||
right: auto $important;
|
||||
left: ($numerator / $denominator) * 100% $important;
|
||||
left: math.div($numerator, $denominator) * 100% $important;
|
||||
}
|
||||
|
||||
// Build a class in the format `.u-pull-5/6[@<breakpoint>]`.
|
||||
.u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
position: relative $important;
|
||||
right: ($numerator / $denominator) * 100% $important;
|
||||
right: math.div($numerator, $denominator) * 100% $important;
|
||||
left: auto $important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +181,7 @@ detection and smooth scrolling with parallax.
|
||||
```js
|
||||
import LocomotiveScroll from 'locomotive-scroll';
|
||||
|
||||
this.scroll = new LocomotiveScroll({
|
||||
el: this.el,
|
||||
smooth: true
|
||||
});
|
||||
this.scroll = new LocomotiveScroll({})
|
||||
````
|
||||
|
||||
Learn more about [Locomotive Scroll][locomotive-scroll].
|
||||
|
||||
135
package-lock.json
generated
135
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "@locomotivemtl/boilerplate",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"locomotive-scroll": "^4.1.4",
|
||||
"locomotive-scroll": "^5.0.0-beta.8",
|
||||
"modujs": "^1.4.2",
|
||||
"modularload": "^1.2.6",
|
||||
"normalize.css": "^8.0.1",
|
||||
@@ -390,6 +390,11 @@
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@studio-freight/lenis": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.12.tgz",
|
||||
"integrity": "sha512-iN3JQ6qJVc+L7vh8NjlDibw9dvjjifvlJ+s0UAkfwty+vW9MJxRfszb5X/eAhcbDLZW6MlvjRHoH9WSQH3J8eA=="
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
@@ -686,11 +691,6 @@
|
||||
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -700,11 +700,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bindall-standalone": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/bindall-standalone/-/bindall-standalone-1.0.5.tgz",
|
||||
"integrity": "sha1-90MDiPHH+amt4Px1pADnEPZpKh0="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -1590,9 +1585,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
|
||||
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz",
|
||||
"integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
@@ -2708,11 +2703,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/lethargy": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/lethargy/-/lethargy-1.0.9.tgz",
|
||||
"integrity": "sha512-nFM8blpCF9rqIL5mRAaTGc78W8oQixVtsD86jbEPvcI13+lDUYJf3R7DZQQL7tCiBpbGpGKMX2gwJFO9hiaOkg=="
|
||||
},
|
||||
"node_modules/limiter": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
@@ -2802,13 +2792,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/locomotive-scroll": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/locomotive-scroll/-/locomotive-scroll-4.1.4.tgz",
|
||||
"integrity": "sha512-6i98cFF2SKg6wIPpwVPuo2FG8qL3USsdDeew78TEYZyLoqleMWNfkSDpWA6mPym4dOfTIBXc678VmGlkgx3fTA==",
|
||||
"version": "5.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/locomotive-scroll/-/locomotive-scroll-5.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-4Nq12voKr5719aaySZg3FX/gQKQIAaAC4NkBiU27aKEKFnYcTeOB1r8VO5bGpEIOGQo94wsiJQNZuRb3TsxjHA==",
|
||||
"dependencies": {
|
||||
"bezier-easing": "^2.1.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"virtual-scroll": "^1.5.2"
|
||||
"@studio-freight/lenis": "1.0.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
@@ -3163,6 +3154,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -4113,11 +4105,6 @@
|
||||
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg=="
|
||||
},
|
||||
"node_modules/snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
@@ -4344,9 +4331,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
@@ -4814,11 +4801,6 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-emitter": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-1.2.0.tgz",
|
||||
"integrity": "sha1-bchFBSywjr78GHRyO1jySmSMO28="
|
||||
},
|
||||
"node_modules/tiny-glob": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
|
||||
@@ -5108,17 +5090,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/virtual-scroll": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/virtual-scroll/-/virtual-scroll-1.5.2.tgz",
|
||||
"integrity": "sha512-7jDHwlKbHUho7CYU/HojE/VKFH8GV9P5fVWP2HCa7dRUOpVvwl93OBOKIIcb2mKd+vqsbVR/0zl0X70+3sUZqA==",
|
||||
"dependencies": {
|
||||
"bindall-standalone": "^1.0.5",
|
||||
"lethargy": "^1.0.2",
|
||||
"object-assign": "^4.0.1",
|
||||
"tiny-emitter": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -5420,6 +5391,11 @@
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"@studio-freight/lenis": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.12.tgz",
|
||||
"integrity": "sha512-iN3JQ6qJVc+L7vh8NjlDibw9dvjjifvlJ+s0UAkfwty+vW9MJxRfszb5X/eAhcbDLZW6MlvjRHoH9WSQH3J8eA=="
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
@@ -5641,22 +5617,12 @@
|
||||
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"bindall-standalone": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/bindall-standalone/-/bindall-standalone-1.0.5.tgz",
|
||||
"integrity": "sha1-90MDiPHH+amt4Px1pADnEPZpKh0="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -6354,9 +6320,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
|
||||
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz",
|
||||
"integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
@@ -7221,11 +7187,6 @@
|
||||
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lethargy": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/lethargy/-/lethargy-1.0.9.tgz",
|
||||
"integrity": "sha512-nFM8blpCF9rqIL5mRAaTGc78W8oQixVtsD86jbEPvcI13+lDUYJf3R7DZQQL7tCiBpbGpGKMX2gwJFO9hiaOkg=="
|
||||
},
|
||||
"limiter": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
@@ -7297,13 +7258,11 @@
|
||||
}
|
||||
},
|
||||
"locomotive-scroll": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/locomotive-scroll/-/locomotive-scroll-4.1.4.tgz",
|
||||
"integrity": "sha512-6i98cFF2SKg6wIPpwVPuo2FG8qL3USsdDeew78TEYZyLoqleMWNfkSDpWA6mPym4dOfTIBXc678VmGlkgx3fTA==",
|
||||
"version": "5.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/locomotive-scroll/-/locomotive-scroll-5.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-4Nq12voKr5719aaySZg3FX/gQKQIAaAC4NkBiU27aKEKFnYcTeOB1r8VO5bGpEIOGQo94wsiJQNZuRb3TsxjHA==",
|
||||
"requires": {
|
||||
"bezier-easing": "^2.1.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"virtual-scroll": "^1.5.2"
|
||||
"@studio-freight/lenis": "1.0.12"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
@@ -7588,7 +7547,8 @@
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
@@ -8350,11 +8310,6 @@
|
||||
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
|
||||
"dev": true
|
||||
},
|
||||
"smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg=="
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
@@ -8547,9 +8502,9 @@
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
@@ -8912,11 +8867,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-1.2.0.tgz",
|
||||
"integrity": "sha1-bchFBSywjr78GHRyO1jySmSMO28="
|
||||
},
|
||||
"tiny-glob": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
|
||||
@@ -9127,17 +9077,6 @@
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"dev": true
|
||||
},
|
||||
"virtual-scroll": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/virtual-scroll/-/virtual-scroll-1.5.2.tgz",
|
||||
"integrity": "sha512-7jDHwlKbHUho7CYU/HojE/VKFH8GV9P5fVWP2HCa7dRUOpVvwl93OBOKIIcb2mKd+vqsbVR/0zl0X70+3sUZqA==",
|
||||
"requires": {
|
||||
"bindall-standalone": "^1.0.5",
|
||||
"lethargy": "^1.0.2",
|
||||
"object-assign": "^4.0.1",
|
||||
"tiny-emitter": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"build": "node --experimental-json-modules --no-warnings build/build.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"locomotive-scroll": "^4.1.4",
|
||||
"locomotive-scroll": "^5.0.0-beta.8",
|
||||
"modujs": "^1.4.2",
|
||||
"modularload": "^1.2.6",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,8 +18,8 @@
|
||||
</head>
|
||||
<body data-module-load>
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<div data-module-scroll="main">
|
||||
<header>
|
||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||
<nav>
|
||||
<ul>
|
||||
@@ -30,7 +30,7 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-scroll-section>
|
||||
<main>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Page</h1>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<footer>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
<body data-module-load>
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<div data-module-scroll="main">
|
||||
<header>
|
||||
<a href="/">
|
||||
<h1>Locomotive Boilerplate</h1>
|
||||
</a>
|
||||
@@ -48,7 +48,7 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-scroll-section>
|
||||
<main>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Hello</h1>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<footer>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
||||
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
</head>
|
||||
<body data-module-load>
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<div data-module-scroll="main">
|
||||
<header>
|
||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||
<nav>
|
||||
<ul>
|
||||
@@ -30,7 +30,7 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-scroll-section>
|
||||
<main>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Images</h1>
|
||||
|
||||
@@ -39,84 +39,24 @@
|
||||
|
||||
<h3 class="c-heading -h3">Basic</h3>
|
||||
|
||||
<div style="width: 640px; max-width: 100%;">
|
||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
|
||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=1);"></div>
|
||||
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=2);"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="c-heading -h3">Relative to scroll</h3>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & img</h3>
|
||||
<img src="http://picsum.photos/800/600?v=1" alt="" loading="lazy" class="c-image_img" width="800" height="600"/>
|
||||
|
||||
<div style="width: 640px; max-width: 100%;">
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="c-image"><img src="http://picsum.photos/800/600?v=2" alt="" loading="lazy" class="c-image_img" width="800" height="600"/></div>
|
||||
<div class="c-image"><img src="http://picsum.photos/800/600?v=3" alt="" loading="lazy" class="c-image_img" width="800" height="600"/></div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||
<h4 class="c-heading -h3">Using o-ratio</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=1"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=2"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=3"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=4"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=5"></div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using SVG viewport for ratio</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=6"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=7"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=8"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
<div class="o-ratio u-4:3"><div class="c-image || o-ratio_content"><img src="http://picsum.photos/800/600?v=4" alt="" loading="lazy" class="c-image_img"/></div></div>
|
||||
<div class="o-ratio u-4:3"><div class="c-image || o-ratio_content"><img src="http://picsum.photos/800/600?v=5" alt="" loading="lazy" class="c-image_img"/></div></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<footer>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
<body data-module-load>
|
||||
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<div data-module-scroll="main">
|
||||
<header>
|
||||
<a href="/">
|
||||
<h1>Locomotive Boilerplate</h1>
|
||||
</a>
|
||||
@@ -56,13 +56,13 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-module-example data-scroll-section>
|
||||
<main data-module-example>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Hello</h1>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<footer>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
||||
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user