mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Compare commits
231 Commits
feature/js
...
6f04e21146
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f04e21146 | ||
|
|
97b0d57dbd | ||
|
|
c434d0843f | ||
|
|
5f8767f04d | ||
|
|
b4ee0955c3 | ||
|
|
1ec1229fe4 | ||
|
|
d593fe5409 | ||
|
|
f8a46043a6 | ||
|
|
25823286d5 | ||
|
|
605f30c948 | ||
|
|
27a41aba66 | ||
|
|
353a38915d | ||
|
|
6d37049989 | ||
|
|
bc3fd3a492 | ||
|
|
45d8be5525 | ||
|
|
ea8f98a52d | ||
|
|
56bbd9e3c5 | ||
|
|
dcb7e91b91 | ||
|
|
2b1eb8e0dd | ||
|
|
f4afd9c6b2 | ||
|
|
7578397a8e | ||
|
|
27effb470d | ||
|
|
7a91cbce61 | ||
|
|
65a265c0ea | ||
|
|
afb3a4aa6a | ||
|
|
40521c3f2b | ||
|
|
2d395cf73a | ||
|
|
81d47b88b8 | ||
|
|
c16407c8c1 | ||
|
|
522c9c0bcb | ||
|
|
ddd12ffc38 | ||
|
|
9e6d7ae182 | ||
|
|
d5bff3ab50 | ||
|
|
962ba66b96 | ||
|
|
5b6bca6ce3 | ||
|
|
4ae90a5821 | ||
|
|
31061daf60 | ||
|
|
ceefeb554e | ||
|
|
276b5eebc0 | ||
|
|
a37c5b047a | ||
|
|
0af2be4599 | ||
|
|
98ba8c4972 | ||
|
|
3f7077b488 | ||
|
|
a674a16c4b | ||
|
|
dd2c783938 | ||
|
|
61b6222525 | ||
|
|
5acd27d1b0 | ||
|
|
9a5a91b221 | ||
|
|
7021666c46 | ||
|
|
43c86c3b50 | ||
|
|
0b4c82ceda | ||
|
|
b72fdabe23 | ||
|
|
f1ebc27a69 | ||
|
|
f2898c8c8e | ||
|
|
12d65db09f | ||
|
|
657fd41f70 | ||
|
|
65c486b910 | ||
|
|
3d0e4d26a2 | ||
|
|
df1a3a6f3c | ||
|
|
7f1b6dad2e | ||
|
|
63e46cde26 | ||
|
|
6564fb330a | ||
|
|
b5753148f1 | ||
|
|
7b415af8c2 | ||
|
|
dc0bc2042c | ||
|
|
e9dbb03207 | ||
|
|
95caf9ebb5 | ||
|
|
596ff7a8ee | ||
|
|
70b36052e6 | ||
|
|
f1e2e2270f | ||
|
|
13735c64f9 | ||
|
|
659ef3767b | ||
|
|
e162af17dd | ||
|
|
93559a0c84 | ||
|
|
c1bcf7fb0d | ||
|
|
d0fcfaac86 | ||
|
|
4a958c5fb5 | ||
|
|
5e7e92c7f5 | ||
|
|
380fbd40c3 | ||
|
|
e16ba2ca16 | ||
|
|
43a5eb1ad3 | ||
|
|
99801a2d8b | ||
|
|
217a1adba7 | ||
|
|
a11e98e31e | ||
|
|
6ef90dbe11 | ||
|
|
6726d665f2 | ||
|
|
dca6c5de1d | ||
|
|
05a00c4258 | ||
|
|
7517be0e76 | ||
|
|
dcec21adf4 | ||
|
|
297e0b4ec8 | ||
|
|
9a2083d894 | ||
|
|
1a81c865ae | ||
|
|
d6b5784cdd | ||
|
|
8894664743 | ||
|
|
8aac2ffea6 | ||
|
|
349d110dee | ||
|
|
7be5e48f22 | ||
|
|
1ee315663e | ||
|
|
89bb00790f | ||
|
|
9db0c71a82 | ||
|
|
7742bbb9d0 | ||
|
|
c9a9209b4b | ||
|
|
9d758f3b2c | ||
|
|
0738dd6491 | ||
|
|
a4656f59ed | ||
|
|
7ff6094e40 | ||
|
|
3fee6f4888 | ||
|
|
f774482255 | ||
|
|
b7d9311ac6 | ||
|
|
c22a006079 | ||
|
|
af57ebd9cb | ||
|
|
c82e9916d0 | ||
|
|
943324220a | ||
|
|
477cec7763 | ||
|
|
5d38685460 | ||
|
|
9079d735bc | ||
|
|
87238fcdd5 | ||
|
|
2f75d8f3d2 | ||
|
|
0346a15b57 | ||
|
|
a2d658bc13 | ||
|
|
7c1b61eda9 | ||
|
|
1fe30a9837 | ||
|
|
e4ae03a94c | ||
|
|
3cde7d40ee | ||
|
|
d1d4fb5fe5 | ||
|
|
3cd81bdb3e | ||
|
|
b6970832a3 | ||
|
|
b8f0a24cdc | ||
|
|
0c718a2644 | ||
|
|
be71474633 | ||
|
|
56d255eac8 | ||
|
|
e7e343e62c | ||
|
|
590e06fc03 | ||
|
|
810df92a61 | ||
|
|
4fd7968b86 | ||
|
|
aba77ea2d9 | ||
|
|
bf28fe21a3 | ||
|
|
7b3cefd8df | ||
|
|
20b167da33 | ||
|
|
f7ca837782 | ||
|
|
0c8ed9595f | ||
|
|
eead1d27cd | ||
|
|
2e3db21ec8 | ||
|
|
9c478f5f7d | ||
|
|
ebcbb6dc84 | ||
|
|
b7c49086c9 | ||
|
|
9219a4cc0a | ||
|
|
14afe2295a | ||
|
|
1bdd2def8d | ||
|
|
84ce496df7 | ||
|
|
05e631dbca | ||
|
|
f8f0a7779c | ||
|
|
8e320f2cd0 | ||
|
|
a8314d064f | ||
|
|
76614e8126 | ||
|
|
bed84ce392 | ||
|
|
feb2241164 | ||
|
|
b1f5a00b8c | ||
|
|
2b30d9ac5c | ||
|
|
9e5704238e | ||
|
|
1ede84e1b1 | ||
|
|
3a83a3209b | ||
|
|
98957eb6c4 | ||
|
|
6712d2d24d | ||
|
|
139a6739f6 | ||
|
|
3272521dba | ||
|
|
e7f0455ce4 | ||
|
|
bf425521c4 | ||
|
|
4bdaa5d085 | ||
|
|
a385f6ed11 | ||
|
|
4079752fe0 | ||
|
|
14d7e09b2b | ||
|
|
e70bf33409 | ||
|
|
8b0926269a | ||
|
|
8d1b548ad0 | ||
|
|
7ca7486913 | ||
|
|
520b75185f | ||
|
|
a056a87855 | ||
|
|
9b99a1958b | ||
|
|
d6193a41fa | ||
|
|
4fafcb8e1d | ||
|
|
aadc410e44 | ||
|
|
0439b165cf | ||
|
|
f98eebc9e1 | ||
|
|
cb80a2ed13 | ||
|
|
07c3155c29 | ||
|
|
c264cb7905 | ||
|
|
1050b83326 | ||
|
|
d0a075ff24 | ||
|
|
ad4a1c7d47 | ||
|
|
de6b3d73a1 | ||
|
|
e8af22009c | ||
|
|
de1a5904a8 | ||
|
|
f527488464 | ||
|
|
cf3f40c956 | ||
|
|
ebb55769f9 | ||
|
|
c8d4e7c154 | ||
|
|
bce37afb6e | ||
|
|
7a23abff92 | ||
|
|
27d8fdee22 | ||
|
|
9154deb036 | ||
|
|
17e8004515 | ||
|
|
9a461ab4c0 | ||
|
|
6b3edefa48 | ||
|
|
fd5efe3531 | ||
|
|
2783fb5138 | ||
|
|
f1e4cd2c55 | ||
|
|
b7d25c5865 | ||
|
|
0a199afe01 | ||
|
|
8ca570b37a | ||
|
|
e5417ff6ab | ||
|
|
3a94c6aba9 | ||
|
|
648109fc9b | ||
|
|
eddc0ee156 | ||
|
|
202e4064f8 | ||
|
|
e20111fd2e | ||
|
|
de0c4993cb | ||
|
|
b162c62930 | ||
|
|
cb27975087 | ||
|
|
d7de1b2566 | ||
|
|
34bca7d68a | ||
|
|
f44093ec19 | ||
|
|
6a4043408b | ||
|
|
92500f908f | ||
|
|
d97fa82c77 | ||
|
|
ccf813f6be | ||
|
|
141a8ffa97 | ||
|
|
e875495928 | ||
|
|
c2db2e1922 | ||
|
|
da66f89d7f |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -3,3 +3,11 @@ node_modules
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
loconfig.*.json
|
loconfig.*.json
|
||||||
!loconfig.example.json
|
!loconfig.example.json
|
||||||
|
.prettierrc
|
||||||
|
|
||||||
|
www/assets/scripts/*
|
||||||
|
!www/assets/scripts/.gitkeep
|
||||||
|
www/assets/styles/*
|
||||||
|
!www/assets/styles/.gitkeep
|
||||||
|
|
||||||
|
assets.json
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> This repository is no longer maintained. We recommend checking out our [Astro](https://github.com/locomotivemtl/astro-boilerplate) or [Craft](https://github.com/locomotivemtl/craft-boilerplate) boilerplates instead.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/locomotivemtl/locomotive-boilerplate">
|
<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">
|
<img src="https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png" height="140">
|
||||||
@@ -15,6 +18,7 @@
|
|||||||
* Uses [SVG Mixer] for processing SVG files and generating spritesheets.
|
* Uses [SVG Mixer] for processing SVG files and generating spritesheets.
|
||||||
* Uses [ITCSS] for a sane and scalable CSS architecture.
|
* Uses [ITCSS] for a sane and scalable CSS architecture.
|
||||||
* Uses [Locomotive Scroll] for smooth scrolling with parallax effects.
|
* 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).
|
Learn more about [languages and technologies](docs/technologies.md).
|
||||||
|
|
||||||
@@ -22,8 +26,8 @@ Learn more about [languages and technologies](docs/technologies.md).
|
|||||||
|
|
||||||
Make sure you have the following installed:
|
Make sure you have the following installed:
|
||||||
|
|
||||||
* [Node] — at least 14.17, the latest LTS is recommended.
|
* [Node] — at least 20, the latest LTS is recommended.
|
||||||
* [NPM] — at least 6.0, the latest LTS is recommended.
|
* [NPM] — at least 10, the latest LTS is recommended.
|
||||||
|
|
||||||
> 💡 You can use [NVM] to install and use different versions of Node via the command-line.
|
> 💡 You can use [NVM] to install and use different versions of Node via the command-line.
|
||||||
|
|
||||||
@@ -84,6 +88,7 @@ Learn more about [development and building](docs/development.md).
|
|||||||
|
|
||||||
* [Development and building](docs/development.md)
|
* [Development and building](docs/development.md)
|
||||||
* [Languages and technologies](docs/technologies.md)
|
* [Languages and technologies](docs/technologies.md)
|
||||||
|
* [Grid system](docs/grid.md)
|
||||||
|
|
||||||
[BrowserSync]: https://npmjs.com/package/browser-sync
|
[BrowserSync]: https://npmjs.com/package/browser-sync
|
||||||
[ESBuild]: https://npmjs.com/package/esbuild
|
[ESBuild]: https://npmjs.com/package/esbuild
|
||||||
|
|||||||
@@ -1,35 +1,137 @@
|
|||||||
import modular from 'modujs';
|
import modular from 'modujs';
|
||||||
import * as modules from './modules';
|
import * as modules from './modules';
|
||||||
import globals from './globals';
|
import globals from './globals';
|
||||||
import { html } from './utils/environment';
|
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({
|
const app = new modular({
|
||||||
modules: modules
|
modules,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onload = (event) => {
|
function init() {
|
||||||
|
bindEvents();
|
||||||
|
globals();
|
||||||
|
setViewportSizes();
|
||||||
|
|
||||||
|
app.init(app);
|
||||||
|
|
||||||
|
$html.classList.add(CSS_CLASS.LOADED, CSS_CLASS.READY);
|
||||||
|
$html.classList.remove(CSS_CLASS.LOADING);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug focus
|
||||||
|
*/
|
||||||
|
// document.addEventListener(
|
||||||
|
// "focusin",
|
||||||
|
// function () {
|
||||||
|
// console.log('focused: ', document.activeElement)
|
||||||
|
// }, true
|
||||||
|
// );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eagerly load the following fonts.
|
||||||
|
*/
|
||||||
|
if (isFontLoadingAPIAvailable) {
|
||||||
|
loadFonts(FONT.EAGER, ENV.IS_DEV).then((eagerFonts) => {
|
||||||
|
$html.classList.add(CSS_CLASS.FONTS_LOADED);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug fonts loading
|
||||||
|
*/
|
||||||
|
// 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));
|
||||||
|
// console.groupEnd();
|
||||||
|
// console.group('State of all fonts:');
|
||||||
|
// document.fonts.forEach(font => console.log(font.family, font.style, font.weight, font.status));
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
setViewportSizes()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setViewportSizes() {
|
||||||
|
|
||||||
|
// Document styles
|
||||||
|
const documentStyles = document.documentElement.style;
|
||||||
|
|
||||||
|
// Viewport width
|
||||||
|
const vw = document.body.clientWidth * 0.01;
|
||||||
|
documentStyles.setProperty('--vw', `${vw}px`);
|
||||||
|
|
||||||
|
// Return if browser supports vh, svh, dvh, & lvh
|
||||||
|
if (ENV.SUPPORTS_VH) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viewport height
|
||||||
|
const svh = document.documentElement.clientHeight * 0.01;
|
||||||
|
documentStyles.setProperty('--svh', `${svh}px`);
|
||||||
|
|
||||||
|
const dvh = 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);
|
||||||
|
|
||||||
|
var fixedHeight = fixed.clientHeight;
|
||||||
|
|
||||||
|
fixed.remove();
|
||||||
|
|
||||||
|
const lvh = fixedHeight * 0.01;
|
||||||
|
|
||||||
|
documentStyles.setProperty('--lvh', `${lvh}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Execute
|
||||||
|
////////////////
|
||||||
|
window.addEventListener('load', () => {
|
||||||
const $style = document.getElementById('main-css');
|
const $style = document.getElementById('main-css');
|
||||||
|
|
||||||
if ($style) {
|
if ($style) {
|
||||||
if ($style.isLoaded) {
|
if ($style.isLoaded) {
|
||||||
init();
|
init();
|
||||||
} else {
|
} else {
|
||||||
$style.addEventListener('load', (event) => {
|
$style.addEventListener('load', init);
|
||||||
init();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('The "main-css" stylesheet not found');
|
console.warn('The "main-css" stylesheet not found');
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
function init() {
|
|
||||||
globals();
|
|
||||||
|
|
||||||
app.init(app);
|
|
||||||
|
|
||||||
html.classList.add('is-loaded');
|
|
||||||
html.classList.add('is-ready');
|
|
||||||
html.classList.remove('is-loading');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
65
assets/scripts/config.js
Normal file
65
assets/scripts/config.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* > 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,
|
||||||
|
|
||||||
|
// Supports
|
||||||
|
SUPPORTS_VH: (
|
||||||
|
'CSS' in window
|
||||||
|
&& 'supports' in window.CSS
|
||||||
|
&& window.CSS.supports('height: 100svh')
|
||||||
|
&& window.CSS.supports('height: 100dvh')
|
||||||
|
&& window.CSS.supports('height: 100lvh')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
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: 'Source Sans', style: 'normal', weight: 400 },
|
||||||
|
{ family: 'Source Sans', style: 'normal', weight: 700 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export {
|
||||||
|
ENV,
|
||||||
|
CSS_CLASS,
|
||||||
|
CUSTOM_EVENT,
|
||||||
|
FONT,
|
||||||
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
import svg4everybody from 'svg4everybody';
|
import { ENV } from './config';
|
||||||
|
|
||||||
export default function() {
|
// Dynamic imports for development mode only
|
||||||
svg4everybody();
|
let gridHelper;
|
||||||
|
(async () => {
|
||||||
|
if (ENV.IS_DEV) {
|
||||||
|
const gridHelperModule = await import('./utils/grid-helper');
|
||||||
|
gridHelper = gridHelperModule?.gridHelper;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
/**
|
||||||
|
* Add grid helper
|
||||||
|
*/
|
||||||
|
gridHelper?.();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
export {default as Example} from './modules/Example';
|
||||||
export {default as Load} from './modules/Load';
|
export {default as Load} from './modules/Load';
|
||||||
export {default as Scroll} from './modules/Scroll';
|
export {default as Scroll} from './modules/Scroll';
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { module } from 'modujs';
|
import { module } from 'modujs';
|
||||||
|
import { FONT } from '../config';
|
||||||
|
import { whenReady } from '../utils/fonts';
|
||||||
|
|
||||||
export default class extends module {
|
export default class extends module {
|
||||||
constructor(m) {
|
constructor(m) {
|
||||||
@@ -6,5 +8,10 @@ export default class extends module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
whenReady(FONT.EAGER).then((fonts) => this.onFontsLoaded(fonts));
|
||||||
|
}
|
||||||
|
|
||||||
|
onFontsLoaded(fonts) {
|
||||||
|
console.log('Example: Eager Fonts Loaded!', fonts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { module } from 'modujs';
|
import { module } from 'modujs'
|
||||||
import { lazyLoadImage } from '../utils/image';
|
import { lazyLoadImage } from '../utils/image'
|
||||||
import LocomotiveScroll from 'locomotive-scroll';
|
import LocomotiveScroll from 'locomotive-scroll'
|
||||||
|
|
||||||
export default class extends module {
|
export default class extends module {
|
||||||
constructor(m) {
|
constructor(m) {
|
||||||
@@ -9,18 +9,14 @@ export default class extends module {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.scroll = new LocomotiveScroll({
|
this.scroll = new LocomotiveScroll({
|
||||||
el: this.el,
|
modularInstance: this,
|
||||||
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);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// // Force scroll to top
|
||||||
|
// if (history.scrollRestoration) {
|
||||||
|
// history.scrollRestoration = 'manual'
|
||||||
|
// window.scrollTo(0, 0)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,11 +37,22 @@ export default class extends module {
|
|||||||
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
|
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
|
||||||
*/
|
*/
|
||||||
lazyLoad(args) {
|
lazyLoad(args) {
|
||||||
lazyLoadImage(args.obj.el, null, () => {
|
lazyLoadImage(args.target, null, () => {
|
||||||
//callback
|
//callback
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTo(params) {
|
||||||
|
let { target, ...options } = params
|
||||||
|
|
||||||
|
options = Object.assign({
|
||||||
|
// Defaults
|
||||||
|
duration: 1,
|
||||||
|
}, options)
|
||||||
|
|
||||||
|
this.scroll?.scrollTo(target, options)
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.scroll.destroy();
|
this.scroll.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import { isArray } from './is';
|
|
||||||
|
|
||||||
export function addToArray ( array, value ) {
|
|
||||||
const index = array.indexOf( value );
|
|
||||||
|
|
||||||
if ( index === -1 ) {
|
|
||||||
array.push( value );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayContains ( array, value ) {
|
|
||||||
for ( let i = 0, c = array.length; i < c; i++ ) {
|
|
||||||
if ( array[i] == value ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayContentsMatch ( a, b ) {
|
|
||||||
let i;
|
|
||||||
|
|
||||||
if ( !isArray( a ) || !isArray( b ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( a.length !== b.length ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = a.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
if ( a[i] !== b[i] ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureArray ( x ) {
|
|
||||||
if ( typeof x === 'string' ) {
|
|
||||||
return [ x ];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( x === undefined ) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lastItem ( array ) {
|
|
||||||
return array[ array.length - 1 ];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeFromArray ( array, member ) {
|
|
||||||
if ( !array ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = array.indexOf( member );
|
|
||||||
|
|
||||||
if ( index !== -1 ) {
|
|
||||||
array.splice( index, 1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toArray ( arrayLike ) {
|
|
||||||
const array = [];
|
|
||||||
let i = arrayLike.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
array[i] = arrayLike[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findByKeyValue( array, key, value ) {
|
|
||||||
return array.filter(function( obj ) {
|
|
||||||
return obj[key] === value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function cloneArray( array ) {
|
|
||||||
return JSON.parse(JSON.stringify(array));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuffles array in place. ES6 version
|
|
||||||
* @param {Array} a items An array containing the items.
|
|
||||||
*/
|
|
||||||
export function shuffle(a) {
|
|
||||||
for (let i = a.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[a[i], a[j]] = [a[j], a[i]];
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
export default function(func, wait, immediate) {
|
|
||||||
let timeout;
|
|
||||||
return function() {
|
|
||||||
const context = this;
|
|
||||||
const args = arguments;
|
|
||||||
const later = function() {
|
|
||||||
timeout = null;
|
|
||||||
if (!immediate) func.apply(context, args);
|
|
||||||
};
|
|
||||||
const callNow = immediate && !timeout;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
if (callNow) func.apply(context, args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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,8 +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 };
|
|
||||||
402
assets/scripts/utils/fonts.js
Normal file
402
assets/scripts/utils/fonts.js
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
return await Promise.all(fonts.map((font) => font.loaded));
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAny,
|
||||||
|
getMany,
|
||||||
|
hasAny,
|
||||||
|
isFontLoadingAPIAvailable,
|
||||||
|
loadFonts,
|
||||||
|
whenReady,
|
||||||
|
}
|
||||||
138
assets/scripts/utils/grid-helper.js
Normal file
138
assets/scripts/utils/grid-helper.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 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 };
|
||||||
@@ -1,141 +1,140 @@
|
|||||||
/**
|
/**
|
||||||
* @see https://github.com/ractivejs/ractive/blob/dev/src/utils/html.js
|
* Escape HTML string
|
||||||
|
* @param {string} str - string to escape
|
||||||
|
* @return {string} escaped string
|
||||||
*/
|
*/
|
||||||
export function escapeHtml(str) {
|
|
||||||
return str
|
const escapeHtml = str =>
|
||||||
.replace(/&/g, '&')
|
str.replace(/[&<>'"]/g, tag => ({
|
||||||
.replace(/</g, '<')
|
'&': '&',
|
||||||
.replace(/>/g, '>');
|
'<': '<',
|
||||||
}
|
'>': '>',
|
||||||
|
"'": ''',
|
||||||
|
'"': '"'
|
||||||
|
}[tag]))
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare HTML content that contains mustache characters for use with Ractive
|
* Unescape HTML string
|
||||||
* @param {string} str
|
* @param {string} str - string to unescape
|
||||||
* @return {string}
|
* @return {string} unescaped string
|
||||||
*/
|
*/
|
||||||
export function unescapeHtml(str) {
|
|
||||||
return str
|
const unescapeHtml = str =>
|
||||||
.replace(/</g, '<')
|
str.replace('&', '&')
|
||||||
.replace(/>/g, '>')
|
.replace('<', '<')
|
||||||
.replace(/&/g, '&');
|
.replace('>', '>')
|
||||||
}
|
.replace(''', "'")
|
||||||
|
.replace('"', '"')
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get element data attributes
|
* Get element data attributes
|
||||||
* @param {DOMElement} node
|
* @param {HTMLElement} node - node element
|
||||||
* @return {Array} data
|
* @return {array} node data
|
||||||
*/
|
*/
|
||||||
export function getNodeData(node) {
|
|
||||||
|
const getNodeData = node => {
|
||||||
|
|
||||||
// All attributes
|
// All attributes
|
||||||
const attributes = node.attributes;
|
const attributes = node.attributes
|
||||||
|
|
||||||
// Regex Pattern
|
// Regex Pattern
|
||||||
const pattern = /^data\-(.+)$/;
|
const pattern = /^data\-(.+)$/
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
const data = {};
|
const data = {}
|
||||||
|
|
||||||
for (let i in attributes) {
|
for (let i in attributes) {
|
||||||
if (!attributes[i]) {
|
if (!attributes[i]) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes name (ex: data-module)
|
// Attributes name (ex: data-module)
|
||||||
let name = attributes[i].name;
|
let name = attributes[i].name
|
||||||
|
|
||||||
// This happens.
|
// This happens.
|
||||||
if (!name) {
|
if (!name) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let match = name.match(pattern);
|
let match = name.match(pattern)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this throws an error, you have some
|
// If this throws an error, you have some
|
||||||
// serious problems in your HTML.
|
// serious problems in your HTML.
|
||||||
data[match[1]] = getData(node.getAttribute(name));
|
data[match[1]] = getData(node.getAttribute(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse value to data type.
|
* Parse value to data type.
|
||||||
*
|
*
|
||||||
* @link https://github.com/jquery/jquery/blob/3.1.1/src/data.js
|
* @link https://github.com/jquery/jquery/blob/3.1.1/src/data.js
|
||||||
* @param {string} data - A value to convert.
|
* @param {string} data - value to convert
|
||||||
* @return {mixed} Returns the value in its natural data type.
|
* @return {mixed} value in its natural data type
|
||||||
*/
|
*/
|
||||||
export function getData(data) {
|
|
||||||
|
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
|
||||||
|
const getData = data => {
|
||||||
if (data === 'true') {
|
if (data === 'true') {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data === 'false') {
|
if (data === 'false') {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data === 'null') {
|
if (data === 'null') {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only convert to a number if it doesn't change the string
|
// Only convert to a number if it doesn't change the string
|
||||||
if (data === +data+'') {
|
if (data === +data+'') {
|
||||||
return +data;
|
return +data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rbrace.test( 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
|
* Returns an array containing all the parent nodes of the given node
|
||||||
* @param {object} node
|
* @param {HTMLElement} $el - DOM Element
|
||||||
* @return {array} parent nodes
|
* @return {array} parent nodes
|
||||||
*/
|
*/
|
||||||
export function getParents(elem) {
|
|
||||||
|
const getParents = $el => {
|
||||||
|
|
||||||
// Set up a parent array
|
// Set up a parent array
|
||||||
let parents = [];
|
let parents = []
|
||||||
|
|
||||||
// Push each parent element to the array
|
// Push each parent element to the array
|
||||||
for ( ; elem && elem !== document; elem = elem.parentNode ) {
|
for (; $el && $el !== document; $el = $el.parentNode) {
|
||||||
parents.push(elem);
|
parents.push($el)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return our parent array
|
// 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/
|
|
||||||
export function queryClosestParent(elem, selector) {
|
|
||||||
|
|
||||||
// Element.matches() polyfill
|
export {
|
||||||
if (!Element.prototype.matches) {
|
escapeHtml,
|
||||||
Element.prototype.matches =
|
unescapeHtml,
|
||||||
Element.prototype.matchesSelector ||
|
getNodeData,
|
||||||
Element.prototype.mozMatchesSelector ||
|
getData,
|
||||||
Element.prototype.msMatchesSelector ||
|
getParents,
|
||||||
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 ( ; elem && elem !== document; elem = elem.parentNode ) {
|
|
||||||
if ( elem.matches( selector ) ) return elem;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,18 +1,41 @@
|
|||||||
const LAZY_LOADED_IMAGES = []
|
import { CSS_CLASS } from '../config'
|
||||||
|
|
||||||
export function loadImage(url, options = {}) {
|
/**
|
||||||
|
* 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const $img = new Image();
|
const $img = new Image()
|
||||||
|
|
||||||
if (options.crossOrigin) {
|
if (options.crossOrigin) {
|
||||||
$img.crossOrigin = options.crossOrigin;
|
$img.crossOrigin = options.crossOrigin
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadCallback = () => {
|
const loadCallback = () => {
|
||||||
resolve({
|
resolve({
|
||||||
element: $img,
|
element: $img,
|
||||||
...getImageMetadata($img),
|
...getImageMetadata($img),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if($img.decode) {
|
if($img.decode) {
|
||||||
@@ -23,31 +46,26 @@ export function loadImage(url, options = {}) {
|
|||||||
} else {
|
} else {
|
||||||
$img.onload = loadCallback
|
$img.onload = loadCallback
|
||||||
$img.onerror = (e) => {
|
$img.onerror = (e) => {
|
||||||
reject(e);
|
reject(e)
|
||||||
};
|
}
|
||||||
$img.src = url
|
$img.src = url
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getImageMetadata($img) {
|
|
||||||
return {
|
|
||||||
url: $img.src,
|
|
||||||
width: $img.naturalWidth,
|
|
||||||
height: $img.naturalHeight,
|
|
||||||
ratio: $img.naturalWidth / $img.naturalHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy load the given image.
|
* Lazy load the given image.
|
||||||
*
|
*
|
||||||
* @param {HTMLImageElement} $el - The image element.
|
* @param {HTMLImageElement} $el - The image element.
|
||||||
* @param {?string} url - The URI to lazy load into $el.
|
* @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.
|
* 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.
|
* @param {?function} callback - A function to call when the image is loaded.
|
||||||
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
export async function lazyLoadImage($el, url, callback) {
|
|
||||||
|
const LAZY_LOADED_IMAGES = []
|
||||||
|
const lazyLoadImage = async ($el, url, callback) => {
|
||||||
let src = url ? url : $el.dataset.src
|
let src = url ? url : $el.dataset.src
|
||||||
|
|
||||||
let loadedImage = LAZY_LOADED_IMAGES.find(image => image.url === src)
|
let loadedImage = LAZY_LOADED_IMAGES.find(image => image.url === src)
|
||||||
@@ -56,7 +74,7 @@ export async function lazyLoadImage($el, url, callback) {
|
|||||||
loadedImage = await loadImage(src)
|
loadedImage = await loadImage(src)
|
||||||
|
|
||||||
if (!loadedImage.url) {
|
if (!loadedImage.url) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
LAZY_LOADED_IMAGES.push(loadedImage)
|
LAZY_LOADED_IMAGES.push(loadedImage)
|
||||||
@@ -67,21 +85,28 @@ export async function lazyLoadImage($el, url, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($el.tagName === 'IMG') {
|
if ($el.tagName === 'IMG') {
|
||||||
$el.src = loadedImage.url;
|
$el.src = loadedImage.url
|
||||||
} else {
|
} else {
|
||||||
$el.style.backgroundImage = `url(${loadedImage.url})`;
|
$el.style.backgroundImage = `url(${loadedImage.url})`
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
let lazyParent = $el.closest('.c-lazy');
|
let lazyParent = $el.closest(`.${CSS_CLASS.LAZY_CONTAINER}`)
|
||||||
|
|
||||||
if(lazyParent) {
|
if(lazyParent) {
|
||||||
lazyParent.classList.add('-lazy-loaded')
|
lazyParent.classList.add(CSS_CLASS.LAZY_LOADED)
|
||||||
lazyParent.style.backgroundImage = ''
|
lazyParent.style.backgroundImage = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
$el.classList.add('-lazy-loaded')
|
$el.classList.add(CSS_CLASS.LAZY_LOADED)
|
||||||
|
|
||||||
callback?.()
|
callback?.()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
getImageMetadata,
|
||||||
|
loadImage,
|
||||||
|
lazyLoadImage
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,25 @@
|
|||||||
const toString = Object.prototype.toString;
|
/**
|
||||||
const arrayLikePattern = /^\[object (?:Array|FileList)\]$/;
|
* 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}
|
||||||
|
*/
|
||||||
|
|
||||||
// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
|
const isObject = x => (x && typeof x === 'object')
|
||||||
export function isArray ( thing ) {
|
|
||||||
return toString.call( thing ) === '[object Array]';
|
/**
|
||||||
}
|
* Determines if the argument is a function.
|
||||||
|
*
|
||||||
export function isArrayLike ( obj ) {
|
* @param {*} x - The value to be checked.
|
||||||
return arrayLikePattern.test( toString.call( obj ) );
|
* @return {boolean}
|
||||||
}
|
*/
|
||||||
|
|
||||||
export function isEqual ( a, b ) {
|
const isFunction = x => typeof x === 'function'
|
||||||
if ( a === null && b === null ) {
|
|
||||||
return true;
|
|
||||||
}
|
export {
|
||||||
|
isObject,
|
||||||
if ( typeof a === 'object' || typeof b === 'object' ) {
|
isFunction
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
|
|
||||||
export function isNumeric ( thing ) {
|
|
||||||
return !isNaN( parseFloat( thing ) ) && isFinite( thing );
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isObject ( thing ) {
|
|
||||||
return ( thing && toString.call( thing ) === '[object Object]' );
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFunction( thing ) {
|
|
||||||
const getType = {};
|
|
||||||
return thing && getType.toString.call(thing) === '[object Function]';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,54 @@
|
|||||||
export function lerp(start, end, amt){
|
/**
|
||||||
return (1 - amt) * start + amt * end
|
* 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
|
||||||
}
|
}
|
||||||
|
|||||||
78
assets/scripts/utils/tickers.js
Normal file
78
assets/scripts/utils/tickers.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
@@ -1,21 +1,35 @@
|
|||||||
export function transform(el, transformValue){
|
/**
|
||||||
el.style.webkitTransform = transformValue;
|
* Get translate function
|
||||||
el.style.msTransform = transformValue;
|
* @param {HTMLElement} $el - DOM Element
|
||||||
el.style.transform = transformValue;
|
* @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 function getTranslate(el){
|
|
||||||
const translate = {}
|
|
||||||
if(!window.getComputedStyle) return;
|
|
||||||
|
|
||||||
const style = getComputedStyle(el);
|
export {
|
||||||
const transform = style.transform || style.webkitTransform || style.mozTransform;
|
transform,
|
||||||
|
getTranslate
|
||||||
let mat = transform.match(/^matrix3d\((.+)\)$/);
|
|
||||||
if(mat) return parseFloat(mat[1].split(', ')[13]);
|
|
||||||
mat = transform.match(/^matrix\((.+)\)$/);
|
|
||||||
translate.x = mat ? parseFloat(mat[1].split(', ')[4]) : 0;
|
|
||||||
translate.y = mat ? parseFloat(mat[1].split(', ')[5]) : 0;
|
|
||||||
|
|
||||||
return translate;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ $input-icon-color: 424242; // No #
|
|||||||
.c-form_input {
|
.c-form_input {
|
||||||
padding: rem(10px);
|
padding: rem(10px);
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
background-color: white;
|
background-color: colorCode(lightest);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: darkgray;
|
border-color: darkgray;
|
||||||
@@ -63,7 +63,7 @@ $checkbox-icon-color: $input-icon-color;
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: (-$checkbox / 2);
|
margin-top: math.div(-$checkbox, 2);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: $checkbox;
|
width: $checkbox;
|
||||||
height: $checkbox;
|
height: $checkbox;
|
||||||
@@ -71,7 +71,7 @@ $checkbox-icon-color: $input-icon-color;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: $white;
|
background-color: colorCode(lightest);
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,31 +2,78 @@
|
|||||||
// Components / Headings
|
// Components / Headings
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Font sizes
|
||||||
|
// ==========================================================================
|
||||||
|
:root {
|
||||||
|
// Default
|
||||||
|
--font-size-h1: #{responsive-value(38px, 90px, $from-xl)};
|
||||||
|
--font-size-h2: #{responsive-value(34px, 72px, $from-xl)};
|
||||||
|
--font-size-h3: #{responsive-value(28px, 54px, $from-xl)};
|
||||||
|
--font-size-h4: #{responsive-value(24px, 40px, $from-xl)};
|
||||||
|
--font-size-h5: #{responsive-value(20px, 30px, $from-xl)};
|
||||||
|
--font-size-h6: #{responsive-value(18px, 23px, $from-xl)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixins
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
@mixin heading {
|
||||||
|
font-family: ff('sans');
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h1 {
|
||||||
|
font-size: var(--font-size-h1);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h2 {
|
||||||
|
font-size: var(--font-size-h2);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h3 {
|
||||||
|
font-size: var(--font-size-h3);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h4 {
|
||||||
|
font-size: var(--font-size-h4);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h5 {
|
||||||
|
font-size: var(--font-size-h5);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin heading-h6 {
|
||||||
|
font-size: var(--font-size-h6);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
.c-heading {
|
.c-heading {
|
||||||
line-height: $line-height-h;
|
@include heading;
|
||||||
margin-bottom: rem(30px);
|
|
||||||
|
|
||||||
&.-h1 {
|
&.-h1 {
|
||||||
font-size: rem($font-size-h1);
|
@include heading-h1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-h2 {
|
&.-h2 {
|
||||||
font-size: rem($font-size-h2);
|
@include heading-h2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-h3 {
|
&.-h3 {
|
||||||
font-size: rem($font-size-h3);
|
@include heading-h3;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-h4 {
|
&.-h4 {
|
||||||
font-size: rem($font-size-h4);
|
@include heading-h4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-h5 {
|
&.-h5 {
|
||||||
font-size: rem($font-size-h5);
|
@include heading-h5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-h6 {
|
&.-h6 {
|
||||||
font-size: rem($font-size-h6);
|
@include heading-h6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
width: 11px;
|
width: 11px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
transform-origin: center right;
|
transform-origin: center right;
|
||||||
transition: transform 0.3s, opacity 0.3s;
|
transition: transform t(normal), opacity t(normal);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: black;
|
background-color: colorCode(darkest);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
width: 7px;
|
width: 7px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|||||||
53
assets/styles/components/_text.scss
Normal file
53
assets/styles/components/_text.scss
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Components / Texts
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Font sizes
|
||||||
|
// ==========================================================================
|
||||||
|
:root {
|
||||||
|
--font-size-body-regular: #{responsive-value(15px, 17px, $from-lg)};
|
||||||
|
--font-size-body-medium: #{responsive-value(18px, 23px, $from-lg)};
|
||||||
|
--font-size-body-small: #{responsive-value(13px, 16px, $from-lg)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixins
|
||||||
|
// ==========================================================================
|
||||||
|
@mixin text {
|
||||||
|
font-family: ff('sans');
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin body-regular {
|
||||||
|
font-size: var(--font-size-body-regular);
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin body-medium {
|
||||||
|
font-size: var(--font-size-body-medium);
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin body-small {
|
||||||
|
font-size: var(--font-size-body-small);
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
// ==========================================================================
|
||||||
|
.c-text {
|
||||||
|
@include text;
|
||||||
|
|
||||||
|
&.-body-regular {
|
||||||
|
@include body-regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-body-medium {
|
||||||
|
@include body-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-body-small {
|
||||||
|
@include body-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
// Elements / Document
|
// Elements / Document
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
//
|
||||||
// Simple page-level setup.
|
// Simple page-level setup.
|
||||||
//
|
//
|
||||||
// 1. Include web fonts
|
// 1. Includes fonts
|
||||||
// 2. Ensure the page always fills at least the entire height of the viewport.
|
// 2. Ensure the page always fills at least the entire height of the viewport.
|
||||||
// 3. Set the default `font-size` and `line-height` for the entire project,
|
// 3. Set the default `font-size` and `line-height` for the entire project,
|
||||||
// sourced from our default variables.
|
// sourced from our default variables.
|
||||||
@@ -13,70 +14,50 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
min-height: 100%; // [2]
|
min-height: 100%; // [2]
|
||||||
line-height: $line-height;
|
|
||||||
font-family: ff("sans");
|
|
||||||
color: $color;
|
|
||||||
line-height: $line-height; // [3]
|
line-height: $line-height; // [3]
|
||||||
|
font-family: ff("sans");
|
||||||
|
color: $font-color;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
@media (max-width: $to-small) {
|
@media (max-width: $to-sm) {
|
||||||
font-size: $font-size - 2px;
|
font-size: $font-size - 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $from-small) and (max-width: $to-medium) {
|
@media (min-width: $from-sm) and (max-width: $to-lg) {
|
||||||
font-size: $font-size - 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $from-medium) and (max-width: $to-large) {
|
|
||||||
font-size: $font-size - 1px;
|
font-size: $font-size - 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $from-large) and (max-width: $to-huge) {
|
@media (min-width: $from-lg) and (max-width: $to-2xl) {
|
||||||
font-size: $font-size; // [1]
|
font-size: $font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $from-huge) and (max-width: $to-gigantic) {
|
@media (min-width: $from-2xl) and (max-width: $to-3xl) {
|
||||||
font-size: $font-size + 1px;
|
font-size: $font-size + 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $from-gigantic) and (max-width: $to-colossal) {
|
@media (min-width: $from-3xl) {
|
||||||
font-size: $font-size + 2px;
|
font-size: $font-size + 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $from-colossal) {
|
|
||||||
font-size: $font-size + 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-loading {
|
&.is-loading {
|
||||||
cursor: wait;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-scroll-smooth {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-scroll-dragging {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
.has-scroll-smooth & {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background-color: $selection-background-color;
|
background-color: $color-selection-background;
|
||||||
color: $selection-text-color;
|
color: $color-selection-text;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $link-color;
|
color: $color-link;
|
||||||
|
|
||||||
@include u-hocus {
|
@include u-hocus {
|
||||||
color: $link-hover-color;
|
color: $color-link-hover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
assets/styles/elements/_normalize.scss
Normal file
124
assets/styles/elements/_normalize.scss
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Elements / Normalize
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Modern CSS Normalize
|
||||||
|
// Based on the reset by Andy.set with some tweaks.
|
||||||
|
// Original by Andy.set: https://piccalil.li/blog/a-more-modern-css-reset/
|
||||||
|
// Review by Chris collier: https://chriscoyier.net/2023/10/03/being-picky-about-a-css-reset-for-fun-pleasure/
|
||||||
|
|
||||||
|
|
||||||
|
// Box sizing rules
|
||||||
|
*,
|
||||||
|
*:after,
|
||||||
|
*:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent font size inflation
|
||||||
|
html {
|
||||||
|
-moz-text-size-adjust: none;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove default margin in favour of better control in authored CSS
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
figure,
|
||||||
|
blockquote {
|
||||||
|
margin-block: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove list styles on ul, ol elements with a class, which suggests default styling will be removed
|
||||||
|
ul[class],
|
||||||
|
ol[class] {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set core defaults
|
||||||
|
html {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set shorter line heights on headings and interactive elements
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
input,
|
||||||
|
label,
|
||||||
|
button {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balance text wrapping on headings
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a elements default styles if they have a class
|
||||||
|
a[class] {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make assets easier to work with
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
canvas,
|
||||||
|
picture {
|
||||||
|
display: block;
|
||||||
|
max-inline-size: 100%;
|
||||||
|
block-size: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit fonts for inputs and buttons
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure textareas without a rows attribute are not tiny
|
||||||
|
textarea:not([rows]) {
|
||||||
|
min-height: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything that has been anchored to should have extra scroll margin
|
||||||
|
:target {
|
||||||
|
scroll-margin-block: 1rlh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduced mootion preference
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*:after,
|
||||||
|
*:before {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Generic / Buttons
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// 1. Allow us to style box model properties.
|
|
||||||
// 2. Fixes odd inner spacing in IE7.
|
|
||||||
// 3. Reset/normalize some styles.
|
|
||||||
// 4. Line different sized buttons up a little nicer.
|
|
||||||
// 5. Make buttons inherit font styles (often necessary when styling `input`s as buttons).
|
|
||||||
// 6. Force all button-styled elements to appear clickable.
|
|
||||||
|
|
||||||
button,
|
|
||||||
.c-button {
|
|
||||||
@include u-hocus {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
display: inline-block; // [1]
|
|
||||||
overflow: visible; // [2]
|
|
||||||
margin: 0; // [3]
|
|
||||||
padding: 0;
|
|
||||||
outline: 0;
|
|
||||||
border: 0;
|
|
||||||
background: none transparent;
|
|
||||||
color: inherit;
|
|
||||||
vertical-align: middle; // [4]
|
|
||||||
text-align: center; // [3]
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: none;
|
|
||||||
font: inherit; // [5]
|
|
||||||
line-height: normal;
|
|
||||||
cursor: pointer; // [6]
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Generic / Forms
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
input,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
outline: 0;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: none transparent;
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
line-height: normal;
|
|
||||||
appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
|
|
||||||
&::-ms-expand {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-value {
|
|
||||||
background: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Remove Firefox :focus dotted outline, breaks color inherit
|
|
||||||
// // &:-moz-focusring {
|
|
||||||
// // color: transparent;
|
|
||||||
// // text-shadow: 0 0 0 #000000; // Text :focus color
|
|
||||||
// // }
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Generic
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
html {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the correct display in IE 10-.
|
|
||||||
// 1. Add the correct display in IE.
|
|
||||||
|
|
||||||
template, // [1]
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
:before,
|
|
||||||
:after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
address {
|
|
||||||
font-style: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
dfn,
|
|
||||||
cite,
|
|
||||||
em,
|
|
||||||
i {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: $bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
|
||||||
figure {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Single taps should be dispatched immediately on clickable elements
|
|
||||||
|
|
||||||
a, area, button, input, label, select, textarea, [tabindex] {
|
|
||||||
-ms-touch-action: manipulation; // [1]
|
|
||||||
touch-action: manipulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hreflang] > abbr[title] {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-spacing: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
display: block;
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 1px;
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid #CCCCCC;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Generic / Media
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// 1. Setting `vertical-align` removes the whitespace that appears under `img`
|
|
||||||
// elements when they are dropped into a page as-is. Safer alternative to
|
|
||||||
// using `display: block;`.
|
|
||||||
|
|
||||||
audio,
|
|
||||||
canvas,
|
|
||||||
iframe,
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video {
|
|
||||||
vertical-align: middle; // [1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the correct display in iOS 4-7.
|
|
||||||
|
|
||||||
audio:not([controls]) {
|
|
||||||
display: none;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Fluid media for responsive purposes.
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
max-width: 100%; // [2]
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
// 4. If a `width` and/or `height` attribute have been explicitly defined,
|
|
||||||
// let’s not make the image fluid.
|
|
||||||
|
|
||||||
&[width], // [4]
|
|
||||||
&[height] {
|
|
||||||
// [4]
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Offset `alt` text from surrounding copy.
|
|
||||||
|
|
||||||
img {
|
|
||||||
font-style: italic; // [4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. SVG elements should fallback to their surrounding text color.
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: currentColor; // [5]
|
|
||||||
}
|
|
||||||
@@ -2,71 +2,70 @@
|
|||||||
// Main
|
// Main
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Settings
|
// Modules
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@import "settings/config.eases";
|
@use "sass:math";
|
||||||
@import "settings/config.colors";
|
|
||||||
@import "settings/config";
|
|
||||||
|
|
||||||
// ==========================================================================
|
|
||||||
// Tools
|
// Tools
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
@import "tools/maths";
|
||||||
@import "tools/functions";
|
@import "tools/functions";
|
||||||
@import "tools/mixins";
|
@import "tools/mixins";
|
||||||
@import "tools/fonts";
|
// @import "tools/layout";
|
||||||
@import "tools/layout";
|
// @import "tools/widths";
|
||||||
@import "tools/widths";
|
|
||||||
// @import "tools/family";
|
// @import "tools/family";
|
||||||
|
|
||||||
// Generic
|
// Settings
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@import "node_modules/normalize.css/normalize";
|
@import "settings/config";
|
||||||
@import "generic/generic";
|
@import "settings/config.breakpoints";
|
||||||
@import "generic/media";
|
@import "settings/config.colors";
|
||||||
@import "generic/form";
|
@import "settings/config.eases";
|
||||||
@import "generic/button";
|
@import "settings/config.fonts";
|
||||||
|
@import "settings/config.spacings";
|
||||||
|
@import "settings/config.speeds";
|
||||||
|
@import "settings/config.zindexes";
|
||||||
|
@import "settings/config.variables";
|
||||||
|
|
||||||
|
// Vendors
|
||||||
|
// ==========================================================================
|
||||||
|
@import "../../node_modules/locomotive-scroll/dist/locomotive-scroll";
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
@import "elements/normalize";
|
||||||
@import "elements/document";
|
@import "elements/document";
|
||||||
|
|
||||||
// Objects
|
// Objects
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@import "objects/scroll";
|
|
||||||
@import "objects/container";
|
@import "objects/container";
|
||||||
@import "objects/ratio";
|
@import "objects/ratio";
|
||||||
@import "objects/layout";
|
@import "objects/icons";
|
||||||
// @import "objects/crop";
|
@import "objects/grid";
|
||||||
|
// @import "objects/layout";
|
||||||
// @import "objects/table";
|
// @import "objects/table";
|
||||||
|
|
||||||
// Vendors
|
|
||||||
// ==========================================================================
|
|
||||||
// @import "vendors/vendor";
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@import "components/scrollbar";
|
|
||||||
@import "components/heading";
|
@import "components/heading";
|
||||||
|
@import "components/text";
|
||||||
@import "components/button";
|
@import "components/button";
|
||||||
@import "components/form";
|
@import "components/form";
|
||||||
|
|
||||||
// Templates
|
|
||||||
// ==========================================================================
|
|
||||||
// @import "templates/template";
|
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@import "utilities/ratio";
|
@import "utilities/ratio";
|
||||||
@import "utilities/widths";
|
@import "utilities/grid-column";
|
||||||
|
// @import "utilities/widths";
|
||||||
// @import "utilities/align";
|
// @import "utilities/align";
|
||||||
// @import "utilities/helpers";
|
// @import "utilities/helpers";
|
||||||
// @import "utilities/states";
|
// @import "utilities/states";
|
||||||
// @import "utilities/spacing";
|
@import "utilities/spacing";
|
||||||
// @import "utilities/print";
|
// @import "utilities/print";
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
.o-container {
|
.o-container {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding-right: rem($padding);
|
padding-left: var(--grid-margin);
|
||||||
padding-left: rem($padding);
|
padding-right: var(--grid-margin);
|
||||||
max-width: rem($container-width + ($padding * 2));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Objects / Crop
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.crop.scss
|
|
||||||
|
|
||||||
|
|
||||||
// A list of cropping ratios that get generated as modifier classes.
|
|
||||||
|
|
||||||
$crop-ratios: (
|
|
||||||
(2:1),
|
|
||||||
(4:3),
|
|
||||||
(16:9),
|
|
||||||
) !default;
|
|
||||||
|
|
||||||
// Provide a cropping container in order to display media (usually images)
|
|
||||||
// cropped to certain ratios.
|
|
||||||
//
|
|
||||||
// 1. Set up a positioning context in which the image can sit.
|
|
||||||
// 2. This is the crucial part: where the cropping happens.
|
|
||||||
|
|
||||||
.o-crop {
|
|
||||||
position: relative; // [1]
|
|
||||||
display: block;
|
|
||||||
overflow: hidden; // [2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply this class to the content (usually `img`) that needs cropping.
|
|
||||||
//
|
|
||||||
// 1. Image’s default positioning is top-left in the cropping box.
|
|
||||||
// 2. Make sure the media doesn’t stop itself too soon.
|
|
||||||
|
|
||||||
.o-crop_content {
|
|
||||||
position: absolute;
|
|
||||||
top: 0; // [1]
|
|
||||||
left: 0; // [1]
|
|
||||||
max-width: none; // [2]
|
|
||||||
|
|
||||||
// We can position the media in different locations within the cropping area.
|
|
||||||
|
|
||||||
&.-right {
|
|
||||||
right: 0;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-bottom {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-center {
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-disable */
|
|
||||||
|
|
||||||
// Generate a series of crop classes to be used like so:
|
|
||||||
//
|
|
||||||
// @example
|
|
||||||
// <div class="o-crop -16:9">
|
|
||||||
|
|
||||||
.o-crop {
|
|
||||||
@each $crop in $crop-ratios {
|
|
||||||
@each $antecedent, $consequent in $crop {
|
|
||||||
@if (type-of($antecedent) != number) {
|
|
||||||
@error "`#{$antecedent}` needs to be a number.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (type-of($consequent) != number) {
|
|
||||||
@error "`#{$consequent}` needs to be a number.";
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-#{$antecedent}\:#{$consequent} {
|
|
||||||
padding-bottom: ($consequent/$antecedent) * 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-enable */
|
|
||||||
178
assets/styles/objects/_grid.scss
Normal file
178
assets/styles/objects/_grid.scss
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Grid helper
|
||||||
|
// ==========================================================================
|
||||||
|
// Help: https://css-tricks.com/snippets/css/complete-guide-grid/
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <div class="o-grid -col-4 -col-12@from-medium -gutters">
|
||||||
|
* <div class="o-grid_item u-gc-1/2 u-gc-3/9@from-medium">
|
||||||
|
* <p>Hello</p>
|
||||||
|
* </div>
|
||||||
|
* <div class="o-grid_item u-gc-3/4 u-gc-9/13@from-medium">
|
||||||
|
* <p>Hello</p>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
.o-grid {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:is(ul, ol) {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Cols
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Responsive grid columns based on `--grid-columns`
|
||||||
|
&.-cols {
|
||||||
|
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-col-#{$base-column-nb} {
|
||||||
|
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-col-4 {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-col-#{$base-column-nb}\@from-md {
|
||||||
|
@media (min-width: $from-md) {
|
||||||
|
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Gutters
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Gutters rows and columns
|
||||||
|
&.-gutters {
|
||||||
|
gap: var(--grid-gutter);
|
||||||
|
column-gap: var(--grid-gutter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Modifiers
|
||||||
|
// ==========================================================================
|
||||||
|
&.-full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Aligns
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Items inside cells
|
||||||
|
//
|
||||||
|
&.-top-items {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
&.-right-items {
|
||||||
|
justify-items: end;
|
||||||
|
}
|
||||||
|
&.-bottom-items {
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
&.-left-items {
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
&.-center-items {
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
&.-center-items-x {
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
&.-center-items-y {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&.-stretch-items {
|
||||||
|
align-items: stretch;
|
||||||
|
justify-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Cells
|
||||||
|
//
|
||||||
|
&.-top-cells {
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
&.-right-cells {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
&.-bottom-cells {
|
||||||
|
align-content: end;
|
||||||
|
}
|
||||||
|
&.-left-cells {
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
&.-center-cells {
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
&.-center-cells-x {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
&.-center-cells-y {
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
&.-stretch-cells {
|
||||||
|
align-content: stretch;
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
&.-space-around-cells {
|
||||||
|
align-content: space-around;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
&.-space-around-cells-x {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
&.-space-around-cells-y {
|
||||||
|
align-content: space-around;
|
||||||
|
}
|
||||||
|
&.-space-between-cells {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: space-between;
|
||||||
|
}
|
||||||
|
&.-space-between-cells-x {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
&.-space-between-cells-y {
|
||||||
|
align-content: space-between;
|
||||||
|
}
|
||||||
|
&.-space-evenly-cells {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-content: space-evenly;
|
||||||
|
}
|
||||||
|
&.-space-evenly-cells-x {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
&.-space-evenly-cells-y {
|
||||||
|
align-content: space-evenly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Grid item
|
||||||
|
// ==========================================================================
|
||||||
|
// By default, a grid item takes full width of its parent.
|
||||||
|
//
|
||||||
|
.o-grid_item {
|
||||||
|
grid-column-start: var(--gc-start, 1);
|
||||||
|
grid-column-end: var(--gc-end, -1);
|
||||||
|
|
||||||
|
&.-align-end {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
assets/styles/objects/_icons.scss
Normal file
58
assets/styles/objects/_icons.scss
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Objects / SVG Icons
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// Markup
|
||||||
|
//
|
||||||
|
// 1. If icon is accessible and has a title
|
||||||
|
// 2. If icon is decorative
|
||||||
|
//
|
||||||
|
// <i class="o-icon ${modifier}">
|
||||||
|
// <svg
|
||||||
|
// class="svg-${icon-name}"
|
||||||
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
|
// role="img" [1]
|
||||||
|
// aria-hidden="true" [2]
|
||||||
|
// focusable="false" [2]
|
||||||
|
// aria-labelledby="${id}" [1]
|
||||||
|
// >
|
||||||
|
// <title id="${id}"> [1]
|
||||||
|
// Locomotive
|
||||||
|
// </title>
|
||||||
|
// <use xlink:href="assets/images/sprite.svg#${icon-name}" xmlns:xlink="http://www.w3.org/1999/xlink"/>
|
||||||
|
// </svg>
|
||||||
|
// </i>
|
||||||
|
|
||||||
|
// Global styles for icones
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
.o-icon {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
--icon-height: calc(var(--icon-width) * math.div(1, (var(--icon-ratio))));
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: var(--icon-width);
|
||||||
|
height: var(--icon-height);
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SVG sizes
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// // Logo
|
||||||
|
// .svg-logo {
|
||||||
|
// --icon-width: #{rem(100px)};
|
||||||
|
// --icon-ratio: math.div(20, 30); // width/height based on svg viewBox
|
||||||
|
|
||||||
|
// // Sizes
|
||||||
|
// .o-icon.-big & {
|
||||||
|
// --icon-width: #{rem(200px)};
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Objects / Scroll
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
.o-scroll {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
92
assets/styles/settings/_config.breakpoints.scss
Normal file
92
assets/styles/settings/_config.breakpoints.scss
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / Breakpoints
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Breakpoints
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$breakpoints: (
|
||||||
|
"2xs": 340px,
|
||||||
|
"xs": 500px,
|
||||||
|
"sm": 700px,
|
||||||
|
"md": 1000px,
|
||||||
|
"lg": 1200px,
|
||||||
|
"xl": 1400px,
|
||||||
|
"2xl": 1600px,
|
||||||
|
"3xl": 1800px,
|
||||||
|
"4xl": 2000px,
|
||||||
|
"5xl": 2400px
|
||||||
|
);
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Creates a min-width or max-width media query expression.
|
||||||
|
//
|
||||||
|
// @param {string} $breakpoint The breakpoint.
|
||||||
|
// @param {string} $type Either "min" or "max".
|
||||||
|
// @return {string}
|
||||||
|
|
||||||
|
@function mq($breakpoint, $type: "min") {
|
||||||
|
@if not map-has-key($breakpoints, $breakpoint) {
|
||||||
|
@warn "Unknown media query breakpoint: `#{$breakpoint}`";
|
||||||
|
}
|
||||||
|
|
||||||
|
$value: map-get($breakpoints, $breakpoint);
|
||||||
|
|
||||||
|
@if ($type == "min") {
|
||||||
|
@return "(min-width: #{$value})";
|
||||||
|
}
|
||||||
|
@if ($type == "max") {
|
||||||
|
@return "(max-width: #{$value - 1px})";
|
||||||
|
}
|
||||||
|
|
||||||
|
@error "Unknown media query type: #{$type}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a min-width media query expression.
|
||||||
|
//
|
||||||
|
// @param {string} $breakpoint The breakpoint.
|
||||||
|
// @return {string}
|
||||||
|
|
||||||
|
@function mq-min($breakpoint) {
|
||||||
|
@return mq($breakpoint, "min");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a max-width media query expression.
|
||||||
|
//
|
||||||
|
// @param {string} $breakpoint The breakpoint.
|
||||||
|
// @return {string}
|
||||||
|
|
||||||
|
@function mq-max($breakpoint) {
|
||||||
|
@return mq($breakpoint, "max");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a min-width and max-width media query expression.
|
||||||
|
//
|
||||||
|
// @param {string} $from The min-width breakpoint.
|
||||||
|
// @param {string} $until The max-width breakpoint.
|
||||||
|
// @return {string}
|
||||||
|
|
||||||
|
@function mq-between($breakpointMin, $breakpointMax) {
|
||||||
|
@return "#{mq-min($breakpointMin)} and #{mq-max($breakpointMax)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$from-xs: map-get($breakpoints, "xs") !default;
|
||||||
|
$to-xs: map-get($breakpoints, "xs") - 1 !default;
|
||||||
|
$from-sm: map-get($breakpoints, "sm") !default;
|
||||||
|
$to-sm: map-get($breakpoints, "sm") - 1 !default;
|
||||||
|
$from-md: map-get($breakpoints, "md") !default;
|
||||||
|
$to-md: map-get($breakpoints, "md") - 1 !default;
|
||||||
|
$from-lg: map-get($breakpoints, "lg") !default;
|
||||||
|
$to-lg: map-get($breakpoints, "lg") - 1 !default;
|
||||||
|
$from-xl: map-get($breakpoints, "xl") !default;
|
||||||
|
$to-xl: map-get($breakpoints, "xl") - 1 !default;
|
||||||
|
$from-2xl: map-get($breakpoints, "2xl") !default;
|
||||||
|
$to-2xl: map-get($breakpoints, "2xl") - 1 !default;
|
||||||
|
$from-3xl: map-get($breakpoints, "3xl") !default;
|
||||||
|
$to-3xl: map-get($breakpoints, "3xl") - 1 !default;
|
||||||
@@ -1,29 +1,61 @@
|
|||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Settings / Config / Colors
|
// Settings / Config / Colors
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Palette
|
// Palette
|
||||||
// =============================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$white: #FFFFFF;
|
$colors: (
|
||||||
$black: #000000;
|
primary: #3297FD,
|
||||||
|
lightest: #FFFFFF,
|
||||||
|
darkest: #000000,
|
||||||
|
);
|
||||||
|
|
||||||
// Specific
|
// Function
|
||||||
// =============================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Returns color code.
|
||||||
|
//
|
||||||
|
// ```scss
|
||||||
|
// .c-box {
|
||||||
|
// color: colorCode(primary);
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// @param {string} $key - The color key in $colors.
|
||||||
|
// @param {number} $alpha - The alpha for the color value.
|
||||||
|
// @return {color}
|
||||||
|
|
||||||
|
@function colorCode($key, $alpha: 1) {
|
||||||
|
@if not map-has-key($colors, $key) {
|
||||||
|
@error "Unknown '#{$key}' in $colors.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if($alpha < 0 or $alpha > 1) {
|
||||||
|
@error "Alpha '#{$alpha}' must be in range [0, 1].";
|
||||||
|
}
|
||||||
|
|
||||||
|
$color: map-get($colors, $key);
|
||||||
|
|
||||||
|
@return rgba($color, $alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specifics
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
// Link
|
// Link
|
||||||
$link-color: #1A0DAB;
|
$color-link: colorCode(primary);
|
||||||
$link-focus-color: #1A0DAB;
|
$color-link-focus: colorCode(primary);
|
||||||
$link-hover-color: darken(#1A0DAB, 10%);
|
$color-link-hover: color.adjust(colorCode(primary), $lightness: -10%);
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
$selection-text-color: #3297FD;
|
$color-selection-text: colorCode(darkest);
|
||||||
$selection-background-color: #FFFFFF;
|
$color-selection-background: colorCode(lightest);
|
||||||
|
|
||||||
// Social Colors
|
// Socials
|
||||||
// =============================================================================
|
$color-facebook: #3B5998;
|
||||||
|
$color-instagram: #E1306C;
|
||||||
$facebook-color: #3B5998;
|
$color-youtube: #CD201F;
|
||||||
$instagram-color: #E1306C;
|
$color-twitter: #1DA1F2;
|
||||||
$youtube-color: #CD201F;
|
|
||||||
$twitter-color: #1DA1F2;
|
|
||||||
|
|||||||
@@ -2,22 +2,77 @@
|
|||||||
// Settings / Config / Eases
|
// Settings / Config / Eases
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$Power1EaseOut: cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
// Eases
|
||||||
$Power2EaseOut: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
// ==========================================================================
|
||||||
$Power3EaseOut: cubic-bezier(0.165, 0.840, 0.440, 1.000);
|
|
||||||
$Power4EaseOut: cubic-bezier(0.230, 1.000, 0.320, 1.000);
|
$eases: (
|
||||||
$Power1EaseIn: cubic-bezier(0.550, 0.085, 0.680, 0.530) ;
|
// Power 1
|
||||||
$Power2EaseIn: cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
"power1.in": cubic-bezier(0.550, 0.085, 0.680, 0.530),
|
||||||
$Power3EaseIn: cubic-bezier(0.895, 0.030, 0.685, 0.220);
|
"power1.out": cubic-bezier(0.250, 0.460, 0.450, 0.940),
|
||||||
$Power4EaseIn: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
"power1.inOut": cubic-bezier(0.455, 0.030, 0.515, 0.955),
|
||||||
$ExpoEaseOut: cubic-bezier(0.190, 1.000, 0.220, 1.000);
|
|
||||||
$ExpoEaseIn: cubic-bezier(0.950, 0.050, 0.795, 0.035);
|
// Power 2
|
||||||
$ExpoEaseInOut: cubic-bezier(1.000, 0.000, 0.000, 1.000);
|
"power2.in": cubic-bezier(0.550, 0.055, 0.675, 0.190),
|
||||||
$SineEaseOut: cubic-bezier(0.390, 0.575, 0.565, 1.000);
|
"power2.out": cubic-bezier(0.215, 0.610, 0.355, 1.000),
|
||||||
$SineEaseIn: cubic-bezier(0.470, 0.000, 0.745, 0.715);
|
"power2.inOut": cubic-bezier(0.645, 0.045, 0.355, 1.000),
|
||||||
$Power1EaseInOut: cubic-bezier(0.455, 0.030, 0.515, 0.955);
|
|
||||||
$Power2EaseInOut: cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
// Power 3
|
||||||
$Power3EaseInOut: cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
"power3.in": cubic-bezier(0.895, 0.030, 0.685, 0.220),
|
||||||
$Power4EaseInOut: cubic-bezier(0.860, 0.000, 0.070, 1.000);
|
"power3.out": cubic-bezier(0.165, 0.840, 0.440, 1.000),
|
||||||
$SlowEaseOut: cubic-bezier(.04,1.15,0.4,.99);
|
"power3.inOut": cubic-bezier(0.770, 0.000, 0.175, 1.000),
|
||||||
$bounce: cubic-bezier(0.17, 0.67, 0.3, 1.33);
|
|
||||||
|
// Power 4
|
||||||
|
"power4.in": cubic-bezier(0.755, 0.050, 0.855, 0.060),
|
||||||
|
"power4.out": cubic-bezier(0.230, 1.000, 0.320, 1.000),
|
||||||
|
"power4.inOut": cubic-bezier(0.860, 0.000, 0.070, 1.000),
|
||||||
|
|
||||||
|
// Expo
|
||||||
|
"expo.in": cubic-bezier(0.950, 0.050, 0.795, 0.035),
|
||||||
|
"expo.out": cubic-bezier(0.190, 1.000, 0.220, 1.000),
|
||||||
|
"expo.inOut": cubic-bezier(1.000, 0.000, 0.000, 1.000),
|
||||||
|
|
||||||
|
// Back
|
||||||
|
"back.in": cubic-bezier(0.600, -0.280, 0.735, 0.045),
|
||||||
|
"back.out": cubic-bezier(0.175, 00.885, 0.320, 1.275),
|
||||||
|
"back.inOut": cubic-bezier(0.680, -0.550, 0.265, 1.550),
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
"sine.in": cubic-bezier(0.470, 0.000, 0.745, 0.715),
|
||||||
|
"sine.out": cubic-bezier(0.390, 0.575, 0.565, 1.000),
|
||||||
|
"sine.inOut": cubic-bezier(0.445, 0.050, 0.550, 0.950),
|
||||||
|
|
||||||
|
// Circ
|
||||||
|
"circ.in": cubic-bezier(0.600, 0.040, 0.980, 0.335),
|
||||||
|
"circ.out": cubic-bezier(0.075, 0.820, 0.165, 1.000),
|
||||||
|
"circ.inOut": cubic-bezier(0.785, 0.135, 0.150, 0.860),
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
"bounce": cubic-bezier(0.17, 0.67, 0.3, 1.33),
|
||||||
|
"slow.out": cubic-bezier(.04,1.15,0.4,.99),
|
||||||
|
"smooth": cubic-bezier(0.380, 0.005, 0.215, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default value for ease()
|
||||||
|
$ease-default: "power2.out" !default;
|
||||||
|
|
||||||
|
// Function
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Returns ease curve.
|
||||||
|
//
|
||||||
|
// ```scss
|
||||||
|
// .c-box {
|
||||||
|
// transition-timing-function: ease("power2.out");
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// @param {string} $key - The ease key in $eases.
|
||||||
|
// @return {easing-function}
|
||||||
|
|
||||||
|
@function ease($key: $ease-default) {
|
||||||
|
@if not map-has-key($eases, $key) {
|
||||||
|
@error "Unknown '#{$key}' in $eases.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@return map-get($eases, $key);
|
||||||
|
}
|
||||||
|
|||||||
98
assets/styles/settings/_config.fonts.scss
Normal file
98
assets/styles/settings/_config.fonts.scss
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / Breakpoints
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Font fallbacks (retrieved from systemfontstack.com on 2022-05-31)
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$font-fallback-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
|
||||||
|
$font-fallback-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
||||||
|
$font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
|
||||||
|
|
||||||
|
// Typefaces
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// List of custom font faces as tuples.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// <font-name> <font-file-basename> <font-weight> <font-style>
|
||||||
|
// ```
|
||||||
|
$font-faces: (
|
||||||
|
("Source Sans", "SourceSans3-Bold", 700, normal),
|
||||||
|
("Source Sans", "SourceSans3-BoldIt", 700, italic),
|
||||||
|
("Source Sans", "SourceSans3-Regular", 400, normal),
|
||||||
|
("Source Sans", "SourceSans3-RegularIt", 400, italic),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Map of font families.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// <font-id>: (<font-name>, <font-fallbacks>)
|
||||||
|
// ```
|
||||||
|
$font-families: (
|
||||||
|
sans: join("Source Sans", $font-fallback-sans, $separator: comma),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Font directory
|
||||||
|
$font-dir: "../fonts/";
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Imports the custom font.
|
||||||
|
//
|
||||||
|
// The mixin expects font files to be woff and woff2.
|
||||||
|
//
|
||||||
|
// @param {List} $webfont - A custom font to import, as a tuple:
|
||||||
|
// `<font-name> <font-file-basename> <font-weight> <font-style>`.
|
||||||
|
// @param {String} $dir - The webfont directory path.
|
||||||
|
// @output The `@font-face` at-rule specifying the custom font.
|
||||||
|
|
||||||
|
@mixin font-face($webfont, $dir) {
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
font-family: nth($webfont, 1);
|
||||||
|
src: url("#{$dir}#{nth($webfont, 2)}.woff2") format("woff2"),
|
||||||
|
url("#{$dir}#{nth($webfont, 2)}.woff") format("woff");
|
||||||
|
font-weight: #{nth($webfont, 3)};
|
||||||
|
font-style: #{nth($webfont, 4)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Imports the list of custom fonts.
|
||||||
|
//
|
||||||
|
// @require {mixin} font-face
|
||||||
|
//
|
||||||
|
// @param {List<List>} $webfonts - List of custom fonts to import.
|
||||||
|
// See `font-face` mixin for details.
|
||||||
|
// @param {String} $dir - The webfont directory path.
|
||||||
|
// @output The `@font-face` at-rules specifying the custom fonts.
|
||||||
|
|
||||||
|
@mixin font-faces($webfonts, $dir) {
|
||||||
|
@if (length($webfonts) > 0) {
|
||||||
|
@if (type-of(nth($webfonts, 1)) == "list") {
|
||||||
|
@each $webfont in $webfonts {
|
||||||
|
@include font-face($webfont, $dir);
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
@include font-face($webfonts, $dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the font family stack for the given font ID.
|
||||||
|
//
|
||||||
|
// @require {variable} $font-families - See settings directory.
|
||||||
|
//
|
||||||
|
// @param {String} $font-family - The custom font ID.
|
||||||
|
// @throws Error if the $font-family does not exist.
|
||||||
|
// @return {List} The font stack.
|
||||||
|
|
||||||
|
@function ff($font-family) {
|
||||||
|
@if not map-has-key($font-families, $font-family) {
|
||||||
|
@error "No font-family found in $font-families map for `#{$font-family}`.";
|
||||||
|
}
|
||||||
|
|
||||||
|
$value: map-get($font-families, $font-family);
|
||||||
|
@return $value;
|
||||||
|
}
|
||||||
@@ -11,81 +11,35 @@ $context: frontend !default;
|
|||||||
// Path is relative to the stylesheets directory.
|
// Path is relative to the stylesheets directory.
|
||||||
$assets-path: "../" !default;
|
$assets-path: "../" !default;
|
||||||
|
|
||||||
// Typefaces
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$font-dir: "../fonts/";
|
|
||||||
|
|
||||||
$font-families: (
|
|
||||||
"sans": ("Webfont Sans", "Helvetica Neue", Arial, sans-serif),
|
|
||||||
// "serif": ("Webfont Serif", Georgia, serif)
|
|
||||||
);
|
|
||||||
|
|
||||||
$font-faces: (
|
|
||||||
// "Webfont Sans" "webfont-sans_regular" 400 normal,
|
|
||||||
// "Webfont Sans" "webfont-sans_regular-italic" 400 italic,
|
|
||||||
// "Webfont Serif" "webfont-sans_bold" 700 normal,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Typography
|
// Typography
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
$font-size: 16px;
|
$font-size: 16px;
|
||||||
$line-height: 24px / $font-size;
|
$line-height: math.div(24px, $font-size);
|
||||||
$color: #222222;
|
$font-color: colorCode(darkest);
|
||||||
|
|
||||||
// Headings
|
|
||||||
$font-size-h1: 36px !default;
|
|
||||||
$font-size-h2: 28px !default;
|
|
||||||
$font-size-h3: 24px !default;
|
|
||||||
$font-size-h4: 20px !default;
|
|
||||||
$font-size-h5: 18px !default;
|
|
||||||
$font-size-h6: 16px !default;
|
|
||||||
$line-height-h: $line-height;
|
|
||||||
|
|
||||||
// Weights
|
// Weights
|
||||||
$light: 300;
|
$font-weight-light: 300;
|
||||||
$normal: 400;
|
$font-weight-normal: 400;
|
||||||
$medium: 500;
|
$font-weight-medium: 500;
|
||||||
$bold: 700;
|
$font-weight-bold: 700;
|
||||||
|
|
||||||
// Transitions
|
// Transition defaults
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
$speed: t(normal);
|
||||||
$speed: 0.3s;
|
$easing: ease("power2.out");
|
||||||
$easing: $Power2EaseOut;
|
|
||||||
|
|
||||||
// Spacing Units
|
// Spacing Units
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
$unit: 60px;
|
||||||
$unit: 60px;
|
$unit-small: 20px;
|
||||||
$unit-small: 30px;
|
$vw-viewport: 1440;
|
||||||
|
|
||||||
// Container
|
// Container
|
||||||
// =============================================================================
|
// ==========================================================================
|
||||||
|
$padding: $unit;
|
||||||
|
|
||||||
$container-width: 2000px;
|
// Grid
|
||||||
$padding: $unit;
|
// ==========================================================================
|
||||||
|
$base-column-nb: 12;
|
||||||
// Breakpoints
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$from-tiny: 500px !default;
|
|
||||||
$to-tiny: $from-tiny - 1 !default;
|
|
||||||
$from-small: 700px !default;
|
|
||||||
$to-small: $from-small - 1 !default;
|
|
||||||
$from-medium: 1000px !default;
|
|
||||||
$to-medium: $from-medium - 1 !default;
|
|
||||||
$from-large: 1200px !default;
|
|
||||||
$to-large: $from-large - 1 !default;
|
|
||||||
$from-big: 1400px !default;
|
|
||||||
$to-big: $from-big - 1 !default;
|
|
||||||
$from-huge: 1600px !default;
|
|
||||||
$to-huge: $from-huge - 1 !default;
|
|
||||||
$from-enormous: 1800px !default;
|
|
||||||
$to-enormous: $from-enormous - 1 !default;
|
|
||||||
$from-gigantic: 2000px !default;
|
|
||||||
$to-gigantic: $from-gigantic - 1 !default;
|
|
||||||
$from-colossal: 2400px !default;
|
|
||||||
$to-colossal: $from-colossal - 1 !default;
|
|
||||||
|
|||||||
69
assets/styles/settings/_config.spacings.scss
Normal file
69
assets/styles/settings/_config.spacings.scss
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / Spacings
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--spacing-2xs-mobile: 6;
|
||||||
|
--spacing-2xs-desktop: 10;
|
||||||
|
|
||||||
|
--spacing-xs-mobile: 12;
|
||||||
|
--spacing-xs-desktop: 16;
|
||||||
|
|
||||||
|
--spacing-sm-mobile: 22;
|
||||||
|
--spacing-sm-desktop: 32;
|
||||||
|
|
||||||
|
--spacing-md-mobile: 32;
|
||||||
|
--spacing-md-desktop: 56;
|
||||||
|
|
||||||
|
--spacing-lg-mobile: 48;
|
||||||
|
--spacing-lg-desktop: 96;
|
||||||
|
|
||||||
|
--spacing-xl-mobile: 64;
|
||||||
|
--spacing-xl-desktop: 128;
|
||||||
|
|
||||||
|
--spacing-2xl-mobile: 88;
|
||||||
|
--spacing-2xl-desktop: 176;
|
||||||
|
|
||||||
|
--spacing-3xl-mobile: 122;
|
||||||
|
--spacing-3xl-desktop: 224;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacings
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$spacings: (
|
||||||
|
'gutter': var(--grid-gutter),
|
||||||
|
'2xs': #{size-clamp('2xs')},
|
||||||
|
'xs': #{size-clamp('xs')},
|
||||||
|
'sm': #{size-clamp('sm')},
|
||||||
|
'md': #{size-clamp('md')},
|
||||||
|
'lg': #{size-clamp('lg')},
|
||||||
|
'xl': #{size-clamp('xl')},
|
||||||
|
'2xl': #{size-clamp('2xl')},
|
||||||
|
'3xl': #{size-clamp('3xl')},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Function
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Returns spacing.
|
||||||
|
//
|
||||||
|
// ```scss
|
||||||
|
// .c-box {
|
||||||
|
// margin-top: spacing(gutter);
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// @param {string} $key - The spacing key in $spacings.
|
||||||
|
// @param {number} $multiplier - The multiplier of the spacing value.
|
||||||
|
// @return {size}
|
||||||
|
|
||||||
|
@function spacing($spacing: 'sm', $multiplier: 1) {
|
||||||
|
@if not map-has-key($spacings, $spacing) {
|
||||||
|
@error "Unknown master spacing: #{$spacing}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$index: map-get($spacings, $spacing);
|
||||||
|
|
||||||
|
@return calc(#{$index} * #{$multiplier});
|
||||||
|
}
|
||||||
38
assets/styles/settings/_config.speeds.scss
Normal file
38
assets/styles/settings/_config.speeds.scss
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / Speeds
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Speeds
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$speeds: (
|
||||||
|
fastest: 0.1s,
|
||||||
|
faster: 0.15s,
|
||||||
|
fast: 0.25s,
|
||||||
|
normal: 0.3s,
|
||||||
|
slow: 0.5s,
|
||||||
|
slower: 0.75s,
|
||||||
|
slowest: 1s,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Function
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Returns timing.
|
||||||
|
//
|
||||||
|
// ```scss
|
||||||
|
// .c-box {
|
||||||
|
// transition-duration: speed(slow);
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// @param {string} $key - The speed key in $speeds.
|
||||||
|
// @return {duration}
|
||||||
|
|
||||||
|
@function speed($key: "normal") {
|
||||||
|
@if not map-has-key($speeds, $key) {
|
||||||
|
@error "Unknown '#{$key}' in $speeds.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@return map-get($speeds, $key);
|
||||||
|
}
|
||||||
20
assets/styles/settings/_config.variables.scss
Normal file
20
assets/styles/settings/_config.variables.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / CSS VARS
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
:root {
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
--grid-columns: 4;
|
||||||
|
--grid-gutter: #{rem(10px)};
|
||||||
|
--grid-margin: #{rem(10px)};
|
||||||
|
|
||||||
|
// Container
|
||||||
|
--container-width: calc(100% - 2 * var(--grid-margin));
|
||||||
|
|
||||||
|
@media (min-width: $from-sm) {
|
||||||
|
--grid-columns: #{$base-column-nb};
|
||||||
|
--grid-gutter: #{rem(16px)};
|
||||||
|
--grid-margin: #{rem(20px)};
|
||||||
|
}
|
||||||
|
}
|
||||||
44
assets/styles/settings/_config.zindexes.scss
Normal file
44
assets/styles/settings/_config.zindexes.scss
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Settings / Config / Z-indexes
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Timings
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$z-indexes: (
|
||||||
|
"header": 200,
|
||||||
|
"above": 1,
|
||||||
|
"default": 0,
|
||||||
|
"below": -1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default z-index for z()
|
||||||
|
$z-index-default: "above" !default;
|
||||||
|
|
||||||
|
// Function
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Retrieves the z-index from the {@see $layers master list}.
|
||||||
|
//
|
||||||
|
// @link on http://css-tricks.com/handling-z-index/
|
||||||
|
//
|
||||||
|
// @param {string} $layer The name of the z-index.
|
||||||
|
// @param {number} $modifier A positive or negative modifier to apply
|
||||||
|
// to the returned z-index value.
|
||||||
|
// @throw Error if the $layer does not exist.
|
||||||
|
// @throw Warning if the $modifier might overlap another master z-index.
|
||||||
|
// @return {number} The computed z-index of $layer and $modifier.
|
||||||
|
|
||||||
|
@function z($layer: $z-index-default, $modifier: 0) {
|
||||||
|
@if not map-has-key($z-indexes, $layer) {
|
||||||
|
@error "Unknown master z-index layer: #{$layer}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if ($modifier >= 50 or $modifier <= -50) {
|
||||||
|
@warn "Modifier may overlap the another master z-index layer: #{$modifier}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$index: map-get($z-indexes, $layer);
|
||||||
|
|
||||||
|
@return $index + $modifier;
|
||||||
|
}
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
// @param {number} $num - id of the child
|
// @param {number} $num - id of the child
|
||||||
|
|
||||||
@mixin middle($num) {
|
@mixin middle($num) {
|
||||||
&:nth-child(#{round($num / 2)}) {
|
&:nth-child(#{round(math.div($num, 2))}) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
// ==========================================================================
|
|
||||||
// Tools / Font Faces
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// Import the webfont with font-face as woff and woff2
|
|
||||||
//
|
|
||||||
// @param {List} $webfont (font name, filename, font-weight, font-style) - Each webfont to import.
|
|
||||||
// @param {String} $dir - The webfont directory path
|
|
||||||
// @output void
|
|
||||||
|
|
||||||
@mixin font-face($webfont, $dir) {
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
font-family: nth($webfont, 1);
|
|
||||||
src: url("#{$dir}#{nth($webfont, 2)}.woff2") format("woff2"),
|
|
||||||
url("#{$dir}#{nth($webfont, 2)}.woff") format("woff");
|
|
||||||
font-weight: #{nth($webfont, 3)};
|
|
||||||
font-style: #{nth($webfont, 4)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loops through a list of local fonts and import each font-face as woff and woff2
|
|
||||||
//
|
|
||||||
// @param {List} $webfonts [(font name, filename, font-weight, font-style)] - Each webfont to import.
|
|
||||||
// @param {String} $dir - The webfont directory path
|
|
||||||
// @output void
|
|
||||||
|
|
||||||
@mixin font-faces($webfonts, $dir) {
|
|
||||||
@each $webfont in $webfonts {
|
|
||||||
@include font-face($webfont, $dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map the font-family requires with the existing imported font-families
|
|
||||||
//
|
|
||||||
// @param {String} $font-family - The name of the webfont.
|
|
||||||
// @return {String} The webfont and it's fallbacks.
|
|
||||||
|
|
||||||
@function ff($font-family) {
|
|
||||||
@if not map-has-key($font-families, $font-family) {
|
|
||||||
@error "No font-family found in $font-families map for `#{$font-family}`. Property omitted.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$value: map-get($font-families, $font-family);
|
|
||||||
@return $value;
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,15 @@
|
|||||||
// Tools / Functions
|
// Tools / Functions
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Check if the given value is a number in pixel
|
||||||
|
//
|
||||||
|
// @param {Number} $number - The value to check
|
||||||
|
// @return {Boolean}
|
||||||
|
|
||||||
|
@function is-pixel-number($number) {
|
||||||
|
@return type-of($number) == number and unit($number) == "px";
|
||||||
|
}
|
||||||
|
|
||||||
// Converts the given pixel value to its EM quivalent.
|
// Converts the given pixel value to its EM quivalent.
|
||||||
//
|
//
|
||||||
// @param {Number} $size - The pixel value to convert.
|
// @param {Number} $size - The pixel value to convert.
|
||||||
@@ -9,23 +18,15 @@
|
|||||||
// @return {Number} Scalable pixel value in EMs.
|
// @return {Number} Scalable pixel value in EMs.
|
||||||
|
|
||||||
@function em($size, $base: $font-size) {
|
@function em($size, $base: $font-size) {
|
||||||
@if (type-of($size) == number) {
|
@if not is-pixel-number($size) {
|
||||||
@if (unit($size) != "px") {
|
@error "`#{$size}` needs to be a number in pixel.";
|
||||||
@error "`#{$size}` needs to be a pixel value.";
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
@error "`#{$size}` needs to be a number.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (type-of($base) == number) {
|
@if not is-pixel-number($base) {
|
||||||
@if (unit($base) != "px") {
|
@error "`#{$base}` needs to be a number in pixel.";
|
||||||
@error "`#{$base}` needs to be a pixel value.";
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
@error "`#{$base}` needs to be a number.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@return ($size / $base) * 1em;
|
@return math.div($size, $base) * 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts the given pixel value to its REM quivalent.
|
// Converts the given pixel value to its REM quivalent.
|
||||||
@@ -35,34 +36,16 @@
|
|||||||
// @return {Number} Scalable pixel value in REMs.
|
// @return {Number} Scalable pixel value in REMs.
|
||||||
|
|
||||||
@function rem($size, $base: $font-size) {
|
@function rem($size, $base: $font-size) {
|
||||||
@if (type-of($size) == number) {
|
|
||||||
@if (unit($size) != "px") {
|
@if not is-pixel-number($size) {
|
||||||
@error "`#{$size}` needs to be a pixel value.";
|
@error "`#{$size}` needs to be a number in pixel.";
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
@error "`#{$size}` needs to be a number.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (type-of($base) == number) {
|
@if not is-pixel-number($base) {
|
||||||
@if (unit($base) != "px") {
|
@error "`#{$base}` needs to be a number in pixel.";
|
||||||
@error "`#{$base}` needs to be a pixel value.";
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
@error "`#{$base}` needs to be a number.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@return ($size / $base) * 1rem;
|
@return math.div($size, $base) * 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a number to a percentage.
|
|
||||||
//
|
|
||||||
// @alias percentage()
|
|
||||||
// @link http://sassdoc.com/annotations/#alias
|
|
||||||
// @param {Number} $number - The value to convert.
|
|
||||||
// @return {Number} A percentage.
|
|
||||||
|
|
||||||
@function span($number) {
|
|
||||||
@return percentage($number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a list contains a value(s).
|
// Checks if a list contains a value(s).
|
||||||
@@ -94,7 +77,7 @@
|
|||||||
@function important($flag: false) {
|
@function important($flag: false) {
|
||||||
@if ($flag == true) {
|
@if ($flag == true) {
|
||||||
@return !important;
|
@return !important;
|
||||||
} @elseif ($important == false) {
|
} @else if ($important == false) {
|
||||||
@return null;
|
@return null;
|
||||||
} @else {
|
} @else {
|
||||||
@error "`#{$flag}` needs to be `true` or `false`.";
|
@error "`#{$flag}` needs to be `true` or `false`.";
|
||||||
@@ -120,3 +103,112 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$context: 'frontend' !default;
|
$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} $percentage - The percentage spacer
|
||||||
|
// @param {number} $inset - The grid gutter inset
|
||||||
|
// @return {function<number>}
|
||||||
|
@function grid-space($percentage, $inset: 0) {
|
||||||
|
@return calc(#{$percentage} * (#{vw(100)} - 2 * var(--grid-margin, 0px)) - (1 - #{$percentage}) * var(--grid-gutter, 0px) + #{$inset} * var(--grid-gutter, 0px));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@function clamp-with-max($min, $size, $max) {
|
||||||
|
$vw-context: $vw-viewport * 0.01;
|
||||||
|
@return clamp(#{$min}, calc(#{$size} / #{$vw-context} * 1vw), #{$max});
|
||||||
|
}
|
||||||
|
|
||||||
|
@function size-clamp($size) {
|
||||||
|
@return clamp-with-max(
|
||||||
|
calc(#{rem(1px)} * var(--spacing-#{$size}-mobile)),
|
||||||
|
var(--spacing-#{$size}-desktop),
|
||||||
|
calc(#{rem(1px)} * var(--spacing-#{$size}-desktop))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns clamp of calculated preferred responsive font size
|
||||||
|
// within a font size and breakpoint range.
|
||||||
|
//
|
||||||
|
// ```scss
|
||||||
|
// .c-heading.-h1 {
|
||||||
|
// font-size: responsive-value(30px, 60px, 1800);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// .c-heading.-h2 {
|
||||||
|
// font-size: responsive-value(20px, 40px, $from-xl);
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// @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-value($min-size, $max-size, $breakpoint) {
|
||||||
|
$delta: math.div($max-size, $breakpoint);
|
||||||
|
@return clamp($min-size, calc(#{strip-unit($delta)} * #{vw(100)}), $max-size);
|
||||||
|
}
|
||||||
|
|||||||
141
assets/styles/tools/_maths.scss
Normal file
141
assets/styles/tools/_maths.scss
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
@@ -51,13 +51,13 @@
|
|||||||
font-size: rem($font-size) $important;
|
font-size: rem($font-size) $important;
|
||||||
|
|
||||||
@if ($line-height == "auto") {
|
@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 {
|
@else {
|
||||||
@if (type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") {
|
@if (type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") {
|
||||||
line-height: $line-height $important;
|
line-height: $line-height $important;
|
||||||
}
|
}
|
||||||
@elseif ($line-height != "none" and $line-height != false) {
|
@else if ($line-height != "none" and $line-height != false) {
|
||||||
@error "D’oh! `#{$line-height}` is not a valid value for `$line-height`.";
|
@error "D’oh! `#{$line-height}` is not a valid value for `$line-height`.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,3 +193,32 @@
|
|||||||
display: $display $important;
|
display: $display $important;
|
||||||
visibility: visible $important;
|
visibility: visible $important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aspect-ratio polyfill
|
||||||
|
//
|
||||||
|
// @param {Number} $ratio [19/6] - The ratio of the element.
|
||||||
|
// @param {Number} $width [100%] - The fallback width of element.
|
||||||
|
// @param {Boolean} $children [false] - Whether the element contains children for the fallback properties.
|
||||||
|
// @output Properties for maintaining aspect-ratio
|
||||||
|
|
||||||
|
@mixin aspect-ratio($ratio: math.div(16, 9), $width: 100%, $children: false) {
|
||||||
|
|
||||||
|
@supports (aspect-ratio: 1) {
|
||||||
|
aspect-ratio: $ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (aspect-ratio: 1) {
|
||||||
|
height: 0;
|
||||||
|
padding-top: calc(#{$width} * #{math.div(1, $ratio)});
|
||||||
|
|
||||||
|
@if ($children == true) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ $breakpoint-delimiter: \@ !default;
|
|||||||
@for $numerator from 1 through $denominator {
|
@for $numerator from 1 through $denominator {
|
||||||
// Build a class in the format `.u-3/4[@<breakpoint>]`.
|
// Build a class in the format `.u-3/4[@<breakpoint>]`.
|
||||||
.u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
.u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||||
width: ($numerator / $denominator) * 100% $important;
|
width: math.div($numerator, $denominator) * 100% $important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@if ($widths-offsets == true) {
|
@if ($widths-offsets == true) {
|
||||||
@@ -66,13 +66,13 @@ $breakpoint-delimiter: \@ !default;
|
|||||||
.u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
.u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||||
position: relative $important;
|
position: relative $important;
|
||||||
right: auto $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>]`.
|
// Build a class in the format `.u-pull-5/6[@<breakpoint>]`.
|
||||||
.u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
.u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||||
position: relative $important;
|
position: relative $important;
|
||||||
right: ($numerator / $denominator) * 100% $important;
|
right: math.div($numerator, $denominator) * 100% $important;
|
||||||
left: auto $important;
|
left: auto $important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
assets/styles/utilities/_grid-column.scss
Normal file
43
assets/styles/utilities/_grid-column.scss
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Tools / Grid Columns
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
//
|
||||||
|
// Grid layout system.
|
||||||
|
//
|
||||||
|
// This tool generates columns for all needed media queries.
|
||||||
|
// Unused classes will be purge by the css post-processor.
|
||||||
|
//
|
||||||
|
|
||||||
|
$colsMax: $base-column-nb + 1;
|
||||||
|
|
||||||
|
@each $breakpoint, $mediaquery in $breakpoints {
|
||||||
|
@for $fromIndex from 1 through $colsMax {
|
||||||
|
@for $toIndex from 1 through $colsMax {
|
||||||
|
|
||||||
|
// Columns without media query
|
||||||
|
@if $breakpoint == "tiny" {
|
||||||
|
.u-gc-#{$fromIndex}\/#{$toIndex} {
|
||||||
|
--gc-start: #{$fromIndex};
|
||||||
|
--gc-end: #{$toIndex};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns min-width breakpoints `@from-*`
|
||||||
|
.u-gc-#{$fromIndex}\/#{$toIndex}\@from-#{$breakpoint} {
|
||||||
|
@media #{mq-min($breakpoint)} {
|
||||||
|
--gc-start: #{$fromIndex};
|
||||||
|
--gc-end: #{$toIndex};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns max-width breakpoints @to-*`
|
||||||
|
.u-gc-#{$fromIndex}\/#{$toIndex}\@to-#{$breakpoint} {
|
||||||
|
@media #{mq-max($breakpoint)} {
|
||||||
|
--gc-start: #{$fromIndex};
|
||||||
|
--gc-end: #{$toIndex};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,8 +28,8 @@ $aspect-ratios: (
|
|||||||
@error "`#{$consequent}` needs to be a number."
|
@error "`#{$consequent}` needs to be a number."
|
||||||
}
|
}
|
||||||
|
|
||||||
&.u-#{$antecedent}\:#{$consequent}::before {
|
.u-#{$antecedent}\:#{$consequent}::before {
|
||||||
padding-bottom: ($consequent/$antecedent) * 100%;
|
padding-bottom: math.div($consequent, $antecedent) * 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,11 @@
|
|||||||
///
|
///
|
||||||
/// @example
|
/// @example
|
||||||
/// .u-margin-top {}
|
/// .u-margin-top {}
|
||||||
/// .u-padding-left-large {}
|
/// .u-margin-top-xs {}
|
||||||
/// .u-margin-right-small {}
|
/// .u-padding-left-lg {}
|
||||||
|
/// .u-margin-right-sm {}
|
||||||
/// .u-padding {}
|
/// .u-padding {}
|
||||||
/// .u-padding-right-none {}
|
/// .u-padding-right-none {}
|
||||||
/// .u-padding-horizontal {}
|
|
||||||
/// .u-padding-vertical-small {}
|
|
||||||
///
|
///
|
||||||
/// @link https://github.com/inuitcss/inuitcss/blob/512977a/utilities/_utilities.spacing.scss
|
/// @link https://github.com/inuitcss/inuitcss/blob/512977a/utilities/_utilities.spacing.scss
|
||||||
////
|
////
|
||||||
@@ -26,8 +25,8 @@ $spacing-directions: (
|
|||||||
'-right': '-right',
|
'-right': '-right',
|
||||||
'-bottom': '-bottom',
|
'-bottom': '-bottom',
|
||||||
'-left': '-left',
|
'-left': '-left',
|
||||||
'-horizontal': '-left' '-right',
|
'-x': '-left' '-right',
|
||||||
'-vertical': '-top' '-bottom',
|
'-y': '-top' '-bottom',
|
||||||
) !default;
|
) !default;
|
||||||
|
|
||||||
$spacing-properties: (
|
$spacing-properties: (
|
||||||
@@ -35,19 +34,47 @@ $spacing-properties: (
|
|||||||
'margin': 'margin',
|
'margin': 'margin',
|
||||||
) !default;
|
) !default;
|
||||||
|
|
||||||
$spacing-sizes: (
|
$spacing-sizes: join($spacings, (
|
||||||
null: $unit,
|
null: var(--grid-gutter),
|
||||||
'-double': $unit * 2,
|
'none': 0
|
||||||
'-small': $unit-small,
|
));
|
||||||
'-none': 0px
|
|
||||||
) !default;
|
|
||||||
|
|
||||||
@each $property-namespace, $property in $spacing-properties {
|
@each $breakpoint, $mediaquery in $breakpoints {
|
||||||
@each $direction-namespace, $direction-rules in $spacing-directions {
|
@each $property-namespace, $property in $spacing-properties {
|
||||||
@each $size-namespace, $size in $spacing-sizes {
|
@each $direction-namespace, $directions in $spacing-directions {
|
||||||
.u-#{$property-namespace}#{$direction-namespace}#{$size-namespace} {
|
@each $size-namespace, $size in $spacing-sizes {
|
||||||
@each $direction in $direction-rules {
|
|
||||||
#{$property}#{$direction}: rem($size) !important;
|
// Prepend "-" to spacing sizes if not null
|
||||||
|
$size-namespace: if($size-namespace != null, "-" + $size-namespace, $size-namespace);
|
||||||
|
|
||||||
|
// Base class
|
||||||
|
$base-class: ".u-" + #{$property-namespace}#{$direction-namespace}#{$size-namespace};
|
||||||
|
|
||||||
|
// Spacing without media query
|
||||||
|
@if $breakpoint == "xs" {
|
||||||
|
#{$base-class} {
|
||||||
|
@each $direction in $directions {
|
||||||
|
#{$property}#{$direction}: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacing min-width breakpoints `@from-*`
|
||||||
|
#{$base-class}\@from-#{$breakpoint} {
|
||||||
|
@media #{mq-min($breakpoint)} {
|
||||||
|
@each $direction in $directions {
|
||||||
|
#{$property}#{$direction}: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacing max-width breakpoints @to-*`
|
||||||
|
#{$base-class}\@to-#{$breakpoint} {
|
||||||
|
@media #{mq-max($breakpoint)} {
|
||||||
|
@each $direction in $directions {
|
||||||
|
#{$property}#{$direction}: $size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .is-hidden\@to-large {
|
// .is-hidden\@to-lg {
|
||||||
// @media (max-width: $to-large) {
|
// @media (max-width: $to-lg) {
|
||||||
// display: none;
|
// display: none;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// .is-hidden\@from-large {
|
// .is-hidden\@from-lg {
|
||||||
// @media (min-width: $from-large) {
|
// @media (min-width: $from-lg) {
|
||||||
// display: none;
|
// display: none;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ $widths-fractions: 1 2 3 4 5 !default;
|
|||||||
|
|
||||||
@include widths($widths-fractions);
|
@include widths($widths-fractions);
|
||||||
|
|
||||||
.u-1\/2\@from-small {
|
.u-1\/2\@from-sm {
|
||||||
@media (min-width: $from-small) {
|
@media (min-width: $from-sm) {
|
||||||
width: span(1/2);
|
width: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import concatFiles from './tasks/concats.js';
|
import concatFiles from './tasks/concats.js';
|
||||||
import compileScripts from './tasks/scripts.js';
|
import compileScripts from './tasks/scripts.js';
|
||||||
import compileStyles from './tasks/styles.js' ;
|
import compileStyles from './tasks/styles.js';
|
||||||
import compileSVGs from './tasks/svgs.js' ;
|
import compileSVGs from './tasks/svgs.js';
|
||||||
|
import bumpVersions from './tasks/versions.js';
|
||||||
|
|
||||||
concatFiles();
|
concatFiles();
|
||||||
compileScripts();
|
compileScripts();
|
||||||
compileStyles();
|
compileStyles();
|
||||||
compileSVGs();
|
compileSVGs();
|
||||||
|
bumpVersions();
|
||||||
|
|||||||
25
build/helpers/config.js
Normal file
25
build/helpers/config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @file Provides simple user configuration options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import loconfig from '../../loconfig.json' with { type: 'json' };
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
|
|
||||||
|
let usrconfig;
|
||||||
|
|
||||||
|
try {
|
||||||
|
usrconfig = await import('../../loconfig.local.json', {
|
||||||
|
with: { type: 'json' },
|
||||||
|
});
|
||||||
|
usrconfig = usrconfig.default;
|
||||||
|
|
||||||
|
merge(loconfig, usrconfig);
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loconfig;
|
||||||
|
|
||||||
|
export {
|
||||||
|
loconfig,
|
||||||
|
};
|
||||||
162
build/helpers/glob.js
Normal file
162
build/helpers/glob.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* @file Retrieve the first available glob library.
|
||||||
|
*
|
||||||
|
* Note that options vary between libraries.
|
||||||
|
*
|
||||||
|
* Candidates:
|
||||||
|
*
|
||||||
|
* - {@link https://npmjs.com/package/tiny-glob tiny-glob} [1][5][6]
|
||||||
|
* - {@link https://npmjs.com/package/globby globby} [2][5]
|
||||||
|
* - {@link https://npmjs.com/package/fast-glob fast-glob} [3]
|
||||||
|
* - {@link https://npmjs.com/package/glob glob} [1][4][5]
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* - [1] The library's function accepts only a single pattern.
|
||||||
|
* - [2] The library's function accepts only an array of patterns.
|
||||||
|
* - [3] The library's function accepts either a single pattern
|
||||||
|
* or an array of patterns.
|
||||||
|
* - [4] The library's function does not return a Promise but will be
|
||||||
|
* wrapped in a function that does return a Promise.
|
||||||
|
* - [5] The library's function will be wrapped in a function that
|
||||||
|
* supports a single pattern and an array of patterns.
|
||||||
|
* - [6] The library's function returns files and directories but will be
|
||||||
|
* preconfigured to return only files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback GlobFn
|
||||||
|
*
|
||||||
|
* @param {string|string[]} patterns - A string pattern
|
||||||
|
* or an array of string patterns.
|
||||||
|
* @param {object} options
|
||||||
|
*
|
||||||
|
* @returns {Promise<string[]>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} GlobOptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {GlobFn|undefined} The discovered glob function.
|
||||||
|
*/
|
||||||
|
let glob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string[]} A list of packages to attempt import.
|
||||||
|
*/
|
||||||
|
const candidates = [
|
||||||
|
'tiny-glob',
|
||||||
|
'globby',
|
||||||
|
'fast-glob',
|
||||||
|
'glob',
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
glob = await importGlob();
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} Whether a glob function was discovered (TRUE) or not (FALSE).
|
||||||
|
*/
|
||||||
|
const supportsGlob = (typeof glob === 'function');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports the first available glob function.
|
||||||
|
*
|
||||||
|
* @throws {TypeError} If no glob library was found.
|
||||||
|
*
|
||||||
|
* @returns {GlobFn}
|
||||||
|
*/
|
||||||
|
async function importGlob() {
|
||||||
|
for (let name of candidates) {
|
||||||
|
try {
|
||||||
|
let globModule = await import(name);
|
||||||
|
|
||||||
|
if (typeof globModule.default !== 'function') {
|
||||||
|
throw new TypeError(`Expected ${name} to be a function`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the function to ensure
|
||||||
|
* a common pattern.
|
||||||
|
*/
|
||||||
|
switch (name) {
|
||||||
|
case 'tiny-glob':
|
||||||
|
/** [1][5] */
|
||||||
|
return createArrayableGlob(
|
||||||
|
/** [6] */
|
||||||
|
createPresetGlob(globModule.default, {
|
||||||
|
filesOnly: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'globby':
|
||||||
|
/** [2][5] - If `patterns` is a string, wraps into an array. */
|
||||||
|
return (patterns, options) => globModule.default([].concat(patterns), options);
|
||||||
|
|
||||||
|
case 'glob':
|
||||||
|
/** [1][5] */
|
||||||
|
return createArrayableGlob(
|
||||||
|
/** [4] */
|
||||||
|
promisify(globModule.default)
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return globModule.default;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// swallow this error; skip to the next candidate.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(
|
||||||
|
`No glob library was found, expected one of: ${candidates.join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wrapper function for the glob function
|
||||||
|
* to provide support for arrays of patterns.
|
||||||
|
*
|
||||||
|
* @param {function} globFn - The glob function.
|
||||||
|
*
|
||||||
|
* @returns {GlobFn}
|
||||||
|
*/
|
||||||
|
function createArrayableGlob(globFn) {
|
||||||
|
return (patterns, options) => {
|
||||||
|
/** [2] If `patterns` is a string, wraps into an array. */
|
||||||
|
patterns = [].concat(patterns);
|
||||||
|
|
||||||
|
const globs = patterns.map((pattern) => globFn(pattern, options));
|
||||||
|
|
||||||
|
return Promise.all(globs).then((files) => {
|
||||||
|
return [].concat.apply([], files);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wrapper function for the glob function
|
||||||
|
* to define new default options.
|
||||||
|
*
|
||||||
|
* @param {function} globFn - The glob function.
|
||||||
|
* @param {GlobOptions} presets - The glob function options to preset.
|
||||||
|
*
|
||||||
|
* @returns {GlobFn}
|
||||||
|
*/
|
||||||
|
function createPresetGlob(globFn, presets) {
|
||||||
|
return (patterns, options) => globFn(patterns, Object.assign({}, presets, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default glob;
|
||||||
|
|
||||||
|
export {
|
||||||
|
glob,
|
||||||
|
supportsGlob,
|
||||||
|
};
|
||||||
@@ -11,12 +11,16 @@ import kleur from 'kleur';
|
|||||||
* @param {string} [type] - The type of message.
|
* @param {string} [type] - The type of message.
|
||||||
* @param {string} [timerID] - The console time label to output.
|
* @param {string} [timerID] - The console time label to output.
|
||||||
*/
|
*/
|
||||||
export default function message(text, type, timerID) {
|
function message(text, type, timerID) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'success':
|
case 'success':
|
||||||
console.log('✅ ', kleur.bgGreen().black(text));
|
console.log('✅ ', kleur.bgGreen().black(text));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'chore':
|
||||||
|
console.log('🧹 ', kleur.bgGreen().black(text));
|
||||||
|
break;
|
||||||
|
|
||||||
case 'notice':
|
case 'notice':
|
||||||
console.log('ℹ️ ', kleur.bgBlue().black(text));
|
console.log('ℹ️ ', kleur.bgBlue().black(text));
|
||||||
break;
|
break;
|
||||||
@@ -48,4 +52,10 @@ export default function message(text, type, timerID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default message;
|
||||||
|
|
||||||
|
export {
|
||||||
|
message,
|
||||||
};
|
};
|
||||||
@@ -16,7 +16,7 @@ import notifier from 'node-notifier';
|
|||||||
* @param {function} callback - The notification callback.
|
* @param {function} callback - The notification callback.
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
export default function notification(options, callback) {
|
function notification(options, callback) {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = {
|
options = {
|
||||||
message: options
|
message: options
|
||||||
@@ -42,4 +42,10 @@ export default function notification(options, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifier.notify(options, callback);
|
notifier.notify(options, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default notification;
|
||||||
|
|
||||||
|
export {
|
||||||
|
notification,
|
||||||
};
|
};
|
||||||
139
build/helpers/postcss.js
Normal file
139
build/helpers/postcss.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* @file If available, returns the PostCSS Processor creator and
|
||||||
|
* any the Autoprefixer PostCSS plugin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('autoprefixer').autoprefixer.Options} AutoprefixerOptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('postcss').AcceptedPlugin} AcceptedPlugin
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('postcss').Postcss} Postcss
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('postcss').ProcessOptions} ProcessOptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('postcss').Processor} Processor
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {AcceptedPlugin[]} PluginList
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object<string, AcceptedPlugin>} PluginMap
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {PluginList|PluginMap} PluginCollection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} PostCSSOptions
|
||||||
|
*
|
||||||
|
* @property {ProcessOptions} processor - The `Processor#process()` options.
|
||||||
|
* @property {AutoprefixerOptions} autoprefixer - The `autoprefixer()` options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Postcss|undefined} postcss - The discovered PostCSS function.
|
||||||
|
* @type {AcceptedPlugin|undefined} autoprefixer - The discovered Autoprefixer function.
|
||||||
|
*/
|
||||||
|
let postcss, autoprefixer;
|
||||||
|
|
||||||
|
try {
|
||||||
|
postcss = await import('postcss');
|
||||||
|
postcss = postcss.default;
|
||||||
|
|
||||||
|
autoprefixer = await import('autoprefixer');
|
||||||
|
autoprefixer = autoprefixer.default;
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} Whether PostCSS was discovered (TRUE) or not (FALSE).
|
||||||
|
*/
|
||||||
|
const supportsPostCSS = (typeof postcss === 'function');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {PluginList} A list of supported plugins.
|
||||||
|
*/
|
||||||
|
const pluginsList = [
|
||||||
|
autoprefixer,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {PluginMap} A map of supported plugins.
|
||||||
|
*/
|
||||||
|
const pluginsMap = {
|
||||||
|
'autoprefixer': autoprefixer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to create a PostCSS Processor with the given plugins and options.
|
||||||
|
*
|
||||||
|
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
|
||||||
|
* If a map of plugins, the plugin name looks up `options`.
|
||||||
|
* @param {PostCSSOptions} options - The PostCSS wrapper options.
|
||||||
|
*
|
||||||
|
* @returns {Processor|null}
|
||||||
|
*/
|
||||||
|
function createProcessor(pluginsListOrMap, options)
|
||||||
|
{
|
||||||
|
if (!postcss) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugins = parsePlugins(pluginsListOrMap, options);
|
||||||
|
|
||||||
|
return postcss(plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the PostCSS plugins and options.
|
||||||
|
*
|
||||||
|
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
|
||||||
|
* If a map of plugins, the plugin name looks up `options`.
|
||||||
|
* @param {PostCSSOptions} options - The PostCSS wrapper options.
|
||||||
|
*
|
||||||
|
* @returns {PluginList}
|
||||||
|
*/
|
||||||
|
function parsePlugins(pluginsListOrMap, options)
|
||||||
|
{
|
||||||
|
if (Array.isArray(pluginsListOrMap)) {
|
||||||
|
return pluginsListOrMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {PluginList} */
|
||||||
|
const plugins = [];
|
||||||
|
|
||||||
|
for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
|
||||||
|
if (name in options) {
|
||||||
|
plugin = plugin[name](options[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.push(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default postcss;
|
||||||
|
|
||||||
|
export {
|
||||||
|
autoprefixer,
|
||||||
|
createProcessor,
|
||||||
|
parsePlugins,
|
||||||
|
pluginsList,
|
||||||
|
pluginsMap,
|
||||||
|
postcss,
|
||||||
|
supportsPostCSS,
|
||||||
|
};
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import loconfig from './config.js';
|
import loconfig from './config.js';
|
||||||
|
import {
|
||||||
|
escapeRegExp,
|
||||||
|
flatten
|
||||||
|
} from '../utils/index.js';
|
||||||
|
|
||||||
const templateData = flatten({
|
const templateData = flatten({
|
||||||
paths: loconfig.paths
|
paths: loconfig.paths
|
||||||
@@ -22,7 +26,7 @@ const templateData = flatten({
|
|||||||
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
||||||
* @return {*} Returns the transformed value.
|
* @return {*} Returns the transformed value.
|
||||||
*/
|
*/
|
||||||
export default function resolve(input, data = templateData) {
|
function resolve(input, data = templateData) {
|
||||||
switch (typeof input) {
|
switch (typeof input) {
|
||||||
case 'string': {
|
case 'string': {
|
||||||
return resolveValue(input, data);
|
return resolveValue(input, data);
|
||||||
@@ -56,7 +60,7 @@ export default function resolve(input, data = templateData) {
|
|||||||
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
||||||
* @return {string} Returns the translated string.
|
* @return {string} Returns the translated string.
|
||||||
*/
|
*/
|
||||||
export function resolveValue(input, data = templateData) {
|
function resolveValue(input, data = templateData) {
|
||||||
const tags = [];
|
const tags = [];
|
||||||
|
|
||||||
if (data !== templateData) {
|
if (data !== templateData) {
|
||||||
@@ -93,55 +97,9 @@ export function resolveValue(input, data = templateData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export default resolve;
|
||||||
* Creates a new object with all nested object properties
|
|
||||||
* concatenated into it recursively.
|
|
||||||
*
|
|
||||||
* Nested keys are flattened into a property path:
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* {
|
|
||||||
* a: {
|
|
||||||
* b: {
|
|
||||||
* c: 1
|
|
||||||
* }
|
|
||||||
* },
|
|
||||||
* d: 1
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* {
|
|
||||||
* "a.b.c": 1,
|
|
||||||
* "d": 1
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param {object} input - The object to flatten.
|
|
||||||
* @param {string} prefix - The parent key prefix.
|
|
||||||
* @param {object} target - The object that will receive the flattened properties.
|
|
||||||
* @return {object} Returns the `target` object.
|
|
||||||
*/
|
|
||||||
function flatten(input, prefix, target = {}) {
|
|
||||||
for (let key in input) {
|
|
||||||
let field = (prefix ? prefix + '.' + key : key);
|
|
||||||
|
|
||||||
if (typeof input[key] === 'object') {
|
export {
|
||||||
flatten(input[key], field, target);
|
resolve,
|
||||||
} else {
|
resolveValue,
|
||||||
target[field] = input[key];
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quotes regular expression characters.
|
|
||||||
*
|
|
||||||
* @param {string} str - The input string.
|
|
||||||
* @return {string} Returns the quoted (escaped) string.
|
|
||||||
*/
|
|
||||||
function escapeRegExp(str) {
|
|
||||||
return str.replace(/[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g, '\\$&');
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import loconfig from '../utils/config.js';
|
import loconfig from '../helpers/config.js';
|
||||||
import glob from '../utils/glob.js';
|
import glob, { supportsGlob } from '../helpers/glob.js';
|
||||||
import message from '../utils/message.js';
|
import message from '../helpers/message.js';
|
||||||
import notification from '../utils/notification.js';
|
import notification from '../helpers/notification.js';
|
||||||
import resolve from '../utils/template.js';
|
import resolve from '../helpers/template.js';
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
import concat from 'concat';
|
import concat from 'concat';
|
||||||
import {
|
import {
|
||||||
basename,
|
basename,
|
||||||
@@ -64,7 +65,7 @@ export const productionConcatFilesArgs = [
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
export default async function concatFiles(globOptions = null, concatOptions = null) {
|
export default async function concatFiles(globOptions = null, concatOptions = null) {
|
||||||
if (glob) {
|
if (supportsGlob) {
|
||||||
if (globOptions == null) {
|
if (globOptions == null) {
|
||||||
globOptions = productionGlobOptions;
|
globOptions = productionGlobOptions;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -72,7 +73,7 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
|
|||||||
globOptions !== developmentGlobOptions &&
|
globOptions !== developmentGlobOptions &&
|
||||||
globOptions !== productionGlobOptions
|
globOptions !== productionGlobOptions
|
||||||
) {
|
) {
|
||||||
globOptions = Object.assign({}, defaultGlobOptions, globOptions);
|
globOptions = merge({}, defaultGlobOptions, globOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,10 +83,19 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
|
|||||||
concatOptions !== developmentConcatOptions &&
|
concatOptions !== developmentConcatOptions &&
|
||||||
concatOptions !== productionConcatOptions
|
concatOptions !== productionConcatOptions
|
||||||
) {
|
) {
|
||||||
concatOptions = Object.assign({}, defaultConcatOptions, concatOptions);
|
concatOptions = merge({}, defaultConcatOptions, concatOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
loconfig.tasks.concats.forEach(async ({
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {object} entry - The entrypoint to process.
|
||||||
|
* @param {string[]} entry.includes - One or more paths to process.
|
||||||
|
* @param {string} entry.outfile - The file to write to.
|
||||||
|
* @param {?string} [entry.label] - The task label.
|
||||||
|
* Defaults to the outfile name.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
loconfig.tasks.concats?.forEach(async ({
|
||||||
includes,
|
includes,
|
||||||
outfile,
|
outfile,
|
||||||
label = null
|
label = null
|
||||||
@@ -98,25 +108,25 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
|
|||||||
console.time(timeLabel);
|
console.time(timeLabel);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!Array.isArray(includes)) {
|
||||||
|
includes = [ includes ];
|
||||||
|
}
|
||||||
|
|
||||||
includes = resolve(includes);
|
includes = resolve(includes);
|
||||||
outfile = resolve(outfile);
|
outfile = resolve(outfile);
|
||||||
|
|
||||||
let files;
|
if (supportsGlob && globOptions) {
|
||||||
|
includes = await glob(includes, globOptions);
|
||||||
if (glob && globOptions) {
|
|
||||||
files = await glob(includes, globOptions);
|
|
||||||
} else {
|
|
||||||
files = includes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (concatOptions.removeDuplicates) {
|
if (concatOptions.removeDuplicates) {
|
||||||
files = files.map((path) => normalize(path));
|
includes = includes.map((path) => normalize(path));
|
||||||
files = [ ...new Set(files) ];
|
includes = [ ...new Set(includes) ];
|
||||||
}
|
}
|
||||||
|
|
||||||
await concat(files, outfile);
|
await concat(includes, outfile);
|
||||||
|
|
||||||
if (files.length) {
|
if (includes.length) {
|
||||||
message(`${label} concatenated`, 'success', timeLabel);
|
message(`${label} concatenated`, 'success', timeLabel);
|
||||||
} else {
|
} else {
|
||||||
message(`${label} is empty`, 'notice', timeLabel);
|
message(`${label} is empty`, 'notice', timeLabel);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import loconfig from '../utils/config.js';
|
import loconfig from '../helpers/config.js';
|
||||||
import message from '../utils/message.js';
|
import message from '../helpers/message.js';
|
||||||
import notification from '../utils/notification.js';
|
import notification from '../helpers/notification.js';
|
||||||
import resolve from '../utils/template.js';
|
import resolve from '../helpers/template.js';
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import { basename } from 'node:path';
|
import { basename } from 'node:path';
|
||||||
|
|
||||||
@@ -50,10 +51,21 @@ export default async function compileScripts(esBuildOptions = null) {
|
|||||||
esBuildOptions !== developmentESBuildOptions &&
|
esBuildOptions !== developmentESBuildOptions &&
|
||||||
esBuildOptions !== productionESBuildOptions
|
esBuildOptions !== productionESBuildOptions
|
||||||
) {
|
) {
|
||||||
esBuildOptions = Object.assign({}, defaultESBuildOptions, esBuildOptions);
|
esBuildOptions = merge({}, defaultESBuildOptions, esBuildOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
loconfig.tasks.scripts.forEach(async ({
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {object} entry - The entrypoint to process.
|
||||||
|
* @param {string[]} entry.includes - One or more paths to process.
|
||||||
|
* @param {string} [entry.outdir] - The directory to write to.
|
||||||
|
* @param {string} [entry.outfile] - The file to write to.
|
||||||
|
* @param {?string} [entry.label] - The task label.
|
||||||
|
* Defaults to the outdir or outfile name.
|
||||||
|
* @throws {TypeError} If outdir and outfile are missing.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
loconfig.tasks.scripts?.forEach(async ({
|
||||||
includes,
|
includes,
|
||||||
outdir = '',
|
outdir = '',
|
||||||
outfile = '',
|
outfile = '',
|
||||||
@@ -67,6 +79,10 @@ export default async function compileScripts(esBuildOptions = null) {
|
|||||||
console.time(timeLabel);
|
console.time(timeLabel);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!Array.isArray(includes)) {
|
||||||
|
includes = [ includes ];
|
||||||
|
}
|
||||||
|
|
||||||
includes = resolve(includes);
|
includes = resolve(includes);
|
||||||
|
|
||||||
if (outdir) {
|
if (outdir) {
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import loconfig from '../utils/config.js';
|
import loconfig from '../helpers/config.js';
|
||||||
import message from '../utils/message.js';
|
import message from '../helpers/message.js';
|
||||||
import notification from '../utils/notification.js';
|
import notification from '../helpers/notification.js';
|
||||||
import postcss, { pluginsMap as postcssPluginsMap } from '../utils/postcss.js';
|
import {
|
||||||
import resolve from '../utils/template.js';
|
createProcessor,
|
||||||
|
pluginsMap as postcssPluginsMap,
|
||||||
|
supportsPostCSS
|
||||||
|
} from '../helpers/postcss.js';
|
||||||
|
import resolve from '../helpers/template.js';
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
import { writeFile } from 'node:fs/promises';
|
import { writeFile } from 'node:fs/promises';
|
||||||
import { basename } from 'node:path';
|
import { basename } from 'node:path';
|
||||||
import { promisify } from 'node:util';
|
import * as sass from 'sass';
|
||||||
import sass from 'node-sass';
|
import { PurgeCSS } from 'purgecss';
|
||||||
|
|
||||||
const sassRender = promisify(sass.render);
|
|
||||||
|
|
||||||
let postcssProcessor;
|
let postcssProcessor;
|
||||||
|
|
||||||
@@ -18,15 +21,15 @@ let postcssProcessor;
|
|||||||
* @const {object} productionSassOptions - The predefined Sass options for production.
|
* @const {object} productionSassOptions - The predefined Sass options for production.
|
||||||
*/
|
*/
|
||||||
export const defaultSassOptions = {
|
export const defaultSassOptions = {
|
||||||
omitSourceMapUrl: true,
|
sourceMapIncludeSources: true,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
sourceMapContents: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
|
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
|
||||||
outputStyle: 'expanded',
|
style: 'expanded',
|
||||||
});
|
});
|
||||||
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
|
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
|
||||||
outputStyle: 'compressed',
|
style: 'compressed',
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,10 +56,12 @@ export const productionPostCSSOptions = Object.assign({}, defaultPostCSSOptions
|
|||||||
export const developmentStylesArgs = [
|
export const developmentStylesArgs = [
|
||||||
developmentSassOptions,
|
developmentSassOptions,
|
||||||
developmentPostCSSOptions,
|
developmentPostCSSOptions,
|
||||||
|
false
|
||||||
];
|
];
|
||||||
export const productionStylesArgs = [
|
export const productionStylesArgs = [
|
||||||
productionSassOptions,
|
productionSassOptions,
|
||||||
productionPostCSSOptions,
|
productionPostCSSOptions,
|
||||||
|
true
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,17 +78,17 @@ export const productionStylesArgs = [
|
|||||||
* If `false`, PostCSS processing will be ignored.
|
* If `false`, PostCSS processing will be ignored.
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
export default async function compileStyles(sassOptions = null, postcssOptions = null) {
|
export default async function compileStyles(sassOptions = null, postcssOptions = null, purge = true) {
|
||||||
if (sassOptions == null) {
|
if (sassOptions == null) {
|
||||||
sassOptions = productionSassOptions;
|
sassOptions = productionSassOptions;
|
||||||
} else if (
|
} else if (
|
||||||
sassOptions !== developmentSassOptions &&
|
sassOptions !== developmentSassOptions &&
|
||||||
sassOptions !== productionSassOptions
|
sassOptions !== productionSassOptions
|
||||||
) {
|
) {
|
||||||
sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
|
sassOptions = merge({}, defaultSassOptions, sassOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postcss) {
|
if (supportsPostCSS) {
|
||||||
if (postcssOptions == null) {
|
if (postcssOptions == null) {
|
||||||
postcssOptions = productionPostCSSOptions;
|
postcssOptions = productionPostCSSOptions;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -91,11 +96,20 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
|
|||||||
postcssOptions !== developmentPostCSSOptions &&
|
postcssOptions !== developmentPostCSSOptions &&
|
||||||
postcssOptions !== productionPostCSSOptions
|
postcssOptions !== productionPostCSSOptions
|
||||||
) {
|
) {
|
||||||
postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions);
|
postcssOptions = merge({}, defaultPostCSSOptions, postcssOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loconfig.tasks.styles.forEach(async ({
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {object} entry - The entrypoint to process.
|
||||||
|
* @param {string[]} entry.infile - The file to process.
|
||||||
|
* @param {string} entry.outfile - The file to write to.
|
||||||
|
* @param {?string} [entry.label] - The task label.
|
||||||
|
* Defaults to the outfile name.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
loconfig.tasks.styles?.forEach(async ({
|
||||||
infile,
|
infile,
|
||||||
outfile,
|
outfile,
|
||||||
label = null
|
label = null
|
||||||
@@ -109,14 +123,11 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
|
|||||||
infile = resolve(infile);
|
infile = resolve(infile);
|
||||||
outfile = resolve(outfile);
|
outfile = resolve(outfile);
|
||||||
|
|
||||||
let result = await sassRender(Object.assign({}, sassOptions, {
|
let result = sass.compile(infile, sassOptions);
|
||||||
file: infile,
|
|
||||||
outFile: outfile,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (postcss && postcssOptions) {
|
if (supportsPostCSS && postcssOptions) {
|
||||||
if (typeof postcssProcessor === 'undefined') {
|
if (typeof postcssProcessor === 'undefined') {
|
||||||
postcssProcessor = createPostCSSProcessor(
|
postcssProcessor = createProcessor(
|
||||||
postcssPluginsMap,
|
postcssPluginsMap,
|
||||||
postcssOptions
|
postcssOptions
|
||||||
);
|
);
|
||||||
@@ -142,7 +153,12 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeFile(outfile, result.css);
|
await writeFile(outfile, result.css).then(() => {
|
||||||
|
// Purge CSS once file exists.
|
||||||
|
if (outfile && purge) {
|
||||||
|
purgeUnusedCSS(outfile, `${label || `${filestem}.css`}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (result.css) {
|
if (result.css) {
|
||||||
message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel);
|
message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel);
|
||||||
@@ -185,30 +201,42 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a PostCSS Processor with the given plugins and options.
|
* Purge unused styles from CSS files.
|
||||||
*
|
*
|
||||||
* @param {array<(function|object)>|object<string, (function|object)>} pluginsListOrMap -
|
* @async
|
||||||
* A list or map of plugins.
|
*
|
||||||
* If a map of plugins, the plugin name looks up `options`.
|
* @param {string} outfile - The path of a css file
|
||||||
* @param {object} options - The PostCSS options.
|
* If missing the function stops.
|
||||||
|
* @param {string} label - The CSS file label or name.
|
||||||
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function createPostCSSProcessor(pluginsListOrMap, options)
|
async function purgeUnusedCSS(outfile, label) {
|
||||||
{
|
const contentFiles = loconfig.tasks.purgeCSS?.content;
|
||||||
let plugins;
|
if (!Array.isArray(contentFiles) || !contentFiles.length) {
|
||||||
|
return;
|
||||||
if (Array.isArray(pluginsListOrMap)) {
|
|
||||||
plugins = pluginsListOrMap;
|
|
||||||
} else {
|
|
||||||
plugins = [];
|
|
||||||
|
|
||||||
for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
|
|
||||||
if (name in options) {
|
|
||||||
plugin = plugin[name](options[name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.push(plugin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return postcss(plugins);
|
label = label ?? basename(outfile);
|
||||||
|
|
||||||
|
const timeLabel = `${label} purged in`;
|
||||||
|
console.time(timeLabel);
|
||||||
|
|
||||||
|
const purgeCSSResults = await (new PurgeCSS()).purge({
|
||||||
|
content: contentFiles,
|
||||||
|
css: [ outfile ],
|
||||||
|
defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
|
||||||
|
fontFaces: true,
|
||||||
|
keyframes: true,
|
||||||
|
safelist: {
|
||||||
|
// Keep all except .u-gc-* | .u-margin-* | .u-padding-*
|
||||||
|
standard: [ /^(?!.*\b(u-gc-|u-margin|u-padding)).*$/ ]
|
||||||
|
},
|
||||||
|
variables: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let result of purgeCSSResults) {
|
||||||
|
await writeFile(outfile, result.css)
|
||||||
|
|
||||||
|
message(`${label} purged`, 'chore', timeLabel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
import loconfig from '../utils/config.js';
|
import loconfig from '../helpers/config.js';
|
||||||
import message from '../utils/message.js';
|
import glob, { supportsGlob } from '../helpers/glob.js';
|
||||||
import notification from '../utils/notification.js';
|
import message from '../helpers/message.js';
|
||||||
import resolve from '../utils/template.js';
|
import notification from '../helpers/notification.js';
|
||||||
import { basename } from 'node:path';
|
import { resolve as resolveTemplate } from '../helpers/template.js';
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
|
import {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
extname,
|
||||||
|
resolve,
|
||||||
|
} from 'node:path';
|
||||||
|
import commonPath from 'common-path';
|
||||||
import mixer from 'svg-mixer';
|
import mixer from 'svg-mixer';
|
||||||
|
import slugify from 'url-slug';
|
||||||
|
|
||||||
|
const basePath = loconfig?.paths?.svgs?.src
|
||||||
|
? resolve(loconfig.paths.svgs.src)
|
||||||
|
: null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const {object} defaultMixerOptions - The default shared Mixer options.
|
* @const {object} defaultMixerOptions - The default shared Mixer options.
|
||||||
* @const {object} developmentMixerOptions - The predefined Mixer options for development.
|
|
||||||
* @const {object} productionMixerOptions - The predefined Mixer options for production.
|
|
||||||
*/
|
*/
|
||||||
export const defaultMixerOptions = {
|
export const defaultMixerOptions = {
|
||||||
spriteConfig: {
|
spriteConfig: {
|
||||||
usages: false,
|
usages: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @const {object} developmentMixerOptions - The predefined Mixer options for development.
|
||||||
|
* @const {object} productionMixerOptions - The predefined Mixer options for production.
|
||||||
|
*/
|
||||||
export const developmentMixerOptions = Object.assign({}, defaultMixerOptions);
|
export const developmentMixerOptions = Object.assign({}, defaultMixerOptions);
|
||||||
export const productionMixerOptions = Object.assign({}, defaultMixerOptions);
|
export const productionMixerOptions = Object.assign({}, defaultMixerOptions);
|
||||||
|
|
||||||
@@ -44,10 +60,19 @@ export default async function compileSVGs(mixerOptions = null) {
|
|||||||
mixerOptions !== developmentMixerOptions &&
|
mixerOptions !== developmentMixerOptions &&
|
||||||
mixerOptions !== productionMixerOptions
|
mixerOptions !== productionMixerOptions
|
||||||
) {
|
) {
|
||||||
mixerOptions = Object.assign({}, defaultMixerOptions, mixerOptions);
|
mixerOptions = merge({}, defaultMixerOptions, mixerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
loconfig.tasks.svgs.forEach(async ({
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {object} entry - The entrypoint to process.
|
||||||
|
* @param {string[]} entry.includes - One or more paths to process.
|
||||||
|
* @param {string} entry.outfile - The file to write to.
|
||||||
|
* @param {?string} [entry.label] - The task label.
|
||||||
|
* Defaults to the outfile name.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
loconfig.tasks.svgs?.forEach(async ({
|
||||||
includes,
|
includes,
|
||||||
outfile,
|
outfile,
|
||||||
label = null
|
label = null
|
||||||
@@ -60,10 +85,56 @@ export default async function compileSVGs(mixerOptions = null) {
|
|||||||
console.time(timeLabel);
|
console.time(timeLabel);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
includes = resolve(includes);
|
if (!Array.isArray(includes)) {
|
||||||
outfile = resolve(outfile);
|
includes = [ includes ];
|
||||||
|
}
|
||||||
|
|
||||||
const result = await mixer(includes, mixerOptions);
|
includes = resolveTemplate(includes);
|
||||||
|
outfile = resolveTemplate(outfile);
|
||||||
|
|
||||||
|
if (supportsGlob && basePath) {
|
||||||
|
includes = await glob(includes);
|
||||||
|
includes = [ ...new Set(includes) ];
|
||||||
|
|
||||||
|
const common = commonPath(includes);
|
||||||
|
if (common.commonDir) {
|
||||||
|
common.commonDir = resolve(common.commonDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the `<symbol id>` attribute and prefix any
|
||||||
|
* SVG files in subdirectories according to the paths
|
||||||
|
* common base path.
|
||||||
|
*
|
||||||
|
* Example for SVG source path `./assets/images/sprite`:
|
||||||
|
*
|
||||||
|
* | Path | ID |
|
||||||
|
* | ------------------------------------ | --------- |
|
||||||
|
* | `./assets/images/sprite/foo.svg` | `foo` |
|
||||||
|
* | `./assets/images/sprite/baz/qux.svg` | `baz-qux` |
|
||||||
|
*
|
||||||
|
* @param {string} path - The absolute path to the file.
|
||||||
|
* @param {string} [query=''] - A query string.
|
||||||
|
* @return {string} The symbol ID.
|
||||||
|
*/
|
||||||
|
mixerOptions.generateSymbolId = (path, query = '') => {
|
||||||
|
let dirName = dirname(path)
|
||||||
|
.replace(common.commonDir ?? basePath, '')
|
||||||
|
.replace(/^\/|\/$/, '')
|
||||||
|
.replace('/', '-');
|
||||||
|
if (dirName) {
|
||||||
|
dirName += '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = basename(path, extname(path));
|
||||||
|
const decodedQuery = decodeURIComponent(decodeURIComponent(query));
|
||||||
|
return `${dirName}${fileName}${slugify(decodedQuery)}`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await mixer(includes, {
|
||||||
|
...mixerOptions,
|
||||||
|
});
|
||||||
|
|
||||||
await result.write(outfile);
|
await result.write(outfile);
|
||||||
|
|
||||||
|
|||||||
441
build/tasks/versions.js
Normal file
441
build/tasks/versions.js
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
import loconfig from '../helpers/config.js';
|
||||||
|
import message from '../helpers/message.js';
|
||||||
|
import resolve from '../helpers/template.js';
|
||||||
|
import { merge } from '../utils/index.js';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
import events from 'node:events';
|
||||||
|
import {
|
||||||
|
createReadStream,
|
||||||
|
createWriteStream,
|
||||||
|
} from 'node:fs';
|
||||||
|
import {
|
||||||
|
mkdir,
|
||||||
|
rename,
|
||||||
|
rm,
|
||||||
|
readFile,
|
||||||
|
writeFile,
|
||||||
|
} from 'node:fs/promises';
|
||||||
|
import {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
} from 'node:path';
|
||||||
|
import readline from 'node:readline';
|
||||||
|
|
||||||
|
export const REGEXP_SEMVER = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} VersionOptions
|
||||||
|
* @property {string|number|null} prettyPrint - A string or number to insert
|
||||||
|
* white space into the output JSON string for readability purposes.
|
||||||
|
* @property {string} versionFormat - The version number format.
|
||||||
|
* @property {string|RegExp} versionKey - Either:
|
||||||
|
* - A string representing the JSON field name assign the version number to.
|
||||||
|
*
|
||||||
|
* Explicit:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* "key": "json:version"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Implicit:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* "key": "version"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - A `RegExp` object or regular expression string prefixed with `regexp:`.
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* "key": "regexp:(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* key: new RegExp('(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* key: /(?<=^const ASSETS_VERSION = ')(?<version>\d+)(?=';$)/
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @const {VersionOptions} defaultVersionOptions - The default shared version options.
|
||||||
|
* @const {VersionOptions} developmentVersionOptions - The predefined version options for development.
|
||||||
|
* @const {VersionOptions} productionVersionOptions - The predefined version options for production.
|
||||||
|
*/
|
||||||
|
export const defaultVersionOptions = {
|
||||||
|
prettyPrint: 4,
|
||||||
|
versionFormat: 'timestamp',
|
||||||
|
versionKey: 'version',
|
||||||
|
};
|
||||||
|
export const developmentVersionOptions = Object.assign({}, defaultVersionOptions);
|
||||||
|
export const productionVersionOptions = Object.assign({}, defaultVersionOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @const {object} developmentVersionFilesArgs - The predefined `bumpVersion()` options for development.
|
||||||
|
* @const {object} productionVersionFilesArgs - The predefined `bumpVersion()` options for production.
|
||||||
|
*/
|
||||||
|
export const developmentVersionFilesArgs = [
|
||||||
|
developmentVersionOptions,
|
||||||
|
];
|
||||||
|
export const productionVersionFilesArgs = [
|
||||||
|
productionVersionOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bumps version numbers in file.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {object} [versionOptions=null] - Customize the version options.
|
||||||
|
* If `null`, default production options are used.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
export default async function bumpVersions(versionOptions = null) {
|
||||||
|
if (versionOptions == null) {
|
||||||
|
versionOptions = productionVersionOptions;
|
||||||
|
} else if (
|
||||||
|
versionOptions !== developmentVersionOptions &&
|
||||||
|
versionOptions !== productionVersionOptions
|
||||||
|
) {
|
||||||
|
versionOptions = merge({}, defaultVersionOptions, versionOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {object} entry - The entrypoint to process.
|
||||||
|
* @param {string} entry.outfile - The file to write to.
|
||||||
|
* @param {?string} [entry.label] - The task label.
|
||||||
|
* Defaults to the outfile name.
|
||||||
|
* @param {?string} [entry.format] - The version number format.
|
||||||
|
* @param {?string} [entry.key] - The JSON field name assign the version number to.
|
||||||
|
* @param {?string|number} [entry.pretty] - The white space to use to format the JSON file.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
loconfig.tasks.versions?.forEach(({
|
||||||
|
outfile,
|
||||||
|
label = null,
|
||||||
|
...options
|
||||||
|
}) => {
|
||||||
|
if (!label) {
|
||||||
|
label = basename(outfile || 'undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
options.pretty = (options.pretty ?? versionOptions.prettyPrint);
|
||||||
|
options.format = (options.format ?? versionOptions.versionFormat);
|
||||||
|
options.key = (options.key ?? versionOptions.versionKey);
|
||||||
|
|
||||||
|
if (queue.has(outfile)) {
|
||||||
|
queue.get(outfile).then(() => handleBumpVersion(outfile, label, options));
|
||||||
|
} else {
|
||||||
|
queue.set(outfile, handleBumpVersion(outfile, label, options));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted version number or string.
|
||||||
|
*
|
||||||
|
* @param {string} format - The version format.
|
||||||
|
* @param {?string} [oldValue] - The old version value.
|
||||||
|
* @return {string|number}
|
||||||
|
* @throws TypeError If the format or value are invalid.
|
||||||
|
*/
|
||||||
|
function createVersionNumber(format, oldValue = null) {
|
||||||
|
let [ type, modifier ] = format.split(':', 2);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'hex':
|
||||||
|
case 'hexadecimal':
|
||||||
|
try {
|
||||||
|
modifier = Number.parseInt(modifier);
|
||||||
|
|
||||||
|
if (Number.isNaN(modifier)) {
|
||||||
|
modifier = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomBytes(modifier).toString('hex');
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "hexadecimal"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'inc':
|
||||||
|
case 'increment':
|
||||||
|
try {
|
||||||
|
if (modifier === 'semver') {
|
||||||
|
return incrementSemVer(oldValue, [ 'buildmetadata', 'patch' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return incrementNumber(oldValue, modifier);
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "increment"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'regex':
|
||||||
|
case 'regexp':
|
||||||
|
try {
|
||||||
|
return new RegExp(modifier);
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "regexp"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'timestamp':
|
||||||
|
return Date.now().valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(
|
||||||
|
'Expected \'format\' to be either "timestamp", "increment", or "hexadecimal"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* @param {string} outfile
|
||||||
|
* @param {string} label
|
||||||
|
* @param {object} options
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async function handleBumpVersion(outfile, label, options) {
|
||||||
|
const timeLabel = `${label} bumped in`;
|
||||||
|
console.time(timeLabel);
|
||||||
|
|
||||||
|
try {
|
||||||
|
options.key = parseVersionKey(options.key);
|
||||||
|
|
||||||
|
if (options.key instanceof RegExp) {
|
||||||
|
await handleBumpVersionWithRegExp(outfile, label, options);
|
||||||
|
} else {
|
||||||
|
await handleBumpVersionInJson(outfile, label, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
message(`${label} bumped`, 'success', timeLabel);
|
||||||
|
} catch (err) {
|
||||||
|
message(`Error bumping ${label}`, 'error');
|
||||||
|
message(err);
|
||||||
|
|
||||||
|
notification({
|
||||||
|
title: `${label} bumping failed 🚨`,
|
||||||
|
message: `${err.name}: ${err.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the version number for the provided JSON key in file.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {string} outfile
|
||||||
|
* @param {string} label
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.key
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async function handleBumpVersionInJson(outfile, label, options) {
|
||||||
|
outfile = resolve(outfile);
|
||||||
|
|
||||||
|
let json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(await readFile(outfile, { encoding: 'utf8' }));
|
||||||
|
} catch (err) {
|
||||||
|
json = {};
|
||||||
|
|
||||||
|
message(`${label} is a new file`, 'notice');
|
||||||
|
|
||||||
|
await mkdir(dirname(outfile), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
json[options.key] = createVersionNumber(options.format, json?.[options.key]);
|
||||||
|
|
||||||
|
return await writeFile(
|
||||||
|
outfile,
|
||||||
|
JSON.stringify(json, null, options.pretty),
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the version number for the provided RegExp in file.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {string} outfile
|
||||||
|
* @param {string} label
|
||||||
|
* @param {object} options
|
||||||
|
* @param {RegExp} options.key
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async function handleBumpVersionWithRegExp(outfile, label, options) {
|
||||||
|
outfile = resolve(outfile);
|
||||||
|
|
||||||
|
const bckfile = `${outfile}~`;
|
||||||
|
|
||||||
|
await rename(outfile, bckfile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: createReadStream(bckfile),
|
||||||
|
});
|
||||||
|
|
||||||
|
let newVersion = null;
|
||||||
|
|
||||||
|
const writeStream = createWriteStream(outfile, { encoding: 'utf8' });
|
||||||
|
|
||||||
|
rl.on('line', (line) => {
|
||||||
|
const found = line.match(options.key);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
const groups = (found.groups ?? {});
|
||||||
|
const oldVersion = (groups.build ?? groups.version ?? found[1] ?? found[0]);
|
||||||
|
const newVersion = createVersionNumber(options.format, oldVersion);
|
||||||
|
const replacement = found[0].replace(oldVersion, newVersion);
|
||||||
|
|
||||||
|
line = line.replace(found[0], replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeStream.write(line + "\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
await events.once(rl, 'close');
|
||||||
|
|
||||||
|
await rm(bckfile);
|
||||||
|
} catch (err) {
|
||||||
|
await rm(outfile, { force: true });
|
||||||
|
|
||||||
|
await rename(bckfile, outfile);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the given integer.
|
||||||
|
*
|
||||||
|
* @param {string|int} value - The number to increment.
|
||||||
|
* @param {string|int} [increment=1] - The amount to increment by.
|
||||||
|
* @return {int}
|
||||||
|
* @throws TypeError If the version number is invalid.
|
||||||
|
*/
|
||||||
|
function incrementNumber(value, increment = 1) {
|
||||||
|
const version = Number.parseInt(value);
|
||||||
|
if (Number.isNaN(version)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected an integer version number, received [${value}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
increment = Number.parseInt(increment);
|
||||||
|
if (Number.isNaN(increment)) {
|
||||||
|
throw new TypeError(
|
||||||
|
'Expected an integer increment number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (version + increment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the given SemVer version number.
|
||||||
|
*
|
||||||
|
* @param {string} value - The version to mutate.
|
||||||
|
* @param {string|string[]} [target] - The segment to increment, one of:
|
||||||
|
* 'major', 'minor', 'patch', ~~'prerelease'~~, 'buildmetadata'.
|
||||||
|
* @param {string|int} [increment=1] - The amount to increment by.
|
||||||
|
* @return {string}
|
||||||
|
* @throws TypeError If the version or target are invalid.
|
||||||
|
*/
|
||||||
|
function incrementSemVer(value, target = 'patch', increment = 1) {
|
||||||
|
const found = value.match(REGEXP_SEMVER);
|
||||||
|
if (!found) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected a SemVer version number, received [${value}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(target)) {
|
||||||
|
for (const group of target) {
|
||||||
|
if (found.groups[group] != null) {
|
||||||
|
target = group;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target || !found.groups[target]) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected a supported SemVer segment, received [${target}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = {
|
||||||
|
'major': '',
|
||||||
|
'minor': '.',
|
||||||
|
'patch': '.',
|
||||||
|
'prerelease': '-',
|
||||||
|
'buildmetadata': '+',
|
||||||
|
};
|
||||||
|
|
||||||
|
let replacement = '';
|
||||||
|
|
||||||
|
for (const [ segment, delimiter ] of Object.entries(segments)) {
|
||||||
|
if (found.groups?.[segment] != null) {
|
||||||
|
const newVersion = (segment === target)
|
||||||
|
? incrementNumber(found.groups[segment], increment)
|
||||||
|
: found.groups[segment];
|
||||||
|
|
||||||
|
replacement += `${delimiter}${newVersion}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.replace(found[0], replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the version key.
|
||||||
|
*
|
||||||
|
* @param {*} key - The version key.
|
||||||
|
* @return {string|RegExp}
|
||||||
|
*/
|
||||||
|
function parseVersionKey(key) {
|
||||||
|
if (key instanceof RegExp) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new TypeError(
|
||||||
|
'Expected \'key\' to be either a string or a RegExp'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delimiter = key.indexOf(':');
|
||||||
|
|
||||||
|
if (delimiter === -1) {
|
||||||
|
// Assumes its a JSON key
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = key.slice(0, delimiter);
|
||||||
|
const value = key.slice(delimiter + 1);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'json':
|
||||||
|
return value;
|
||||||
|
|
||||||
|
case 'regex':
|
||||||
|
case 'regexp':
|
||||||
|
return new RegExp(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(
|
||||||
|
'Expected \'key\' type to be either "json" or "regexp"'
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Provides simple user configuration options.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import loconfig from '../../loconfig.json';
|
|
||||||
|
|
||||||
let usrconfig;
|
|
||||||
|
|
||||||
try {
|
|
||||||
usrconfig = await import('../../loconfig.local.json');
|
|
||||||
usrconfig = usrconfig.default;
|
|
||||||
|
|
||||||
merge(loconfig, usrconfig);
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
export default loconfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new object with all nested object properties
|
|
||||||
* merged into it recursively.
|
|
||||||
*
|
|
||||||
* @param {object} target - The target object.
|
|
||||||
* @param {object[]} ...sources - The source object(s).
|
|
||||||
* @throws {TypeError} If the target and source are the same.
|
|
||||||
* @return {object} Returns the `target` object.
|
|
||||||
*/
|
|
||||||
export function merge(target, ...sources) {
|
|
||||||
for (const source of sources) {
|
|
||||||
if (target === source) {
|
|
||||||
throw new TypeError(
|
|
||||||
'Cannot merge, target and source are the same'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in source) {
|
|
||||||
if (source[key] != null) {
|
|
||||||
if (isObjectLike(source[key]) && isObjectLike(target[key])) {
|
|
||||||
merge(target[key], source[key]);
|
|
||||||
continue;
|
|
||||||
} else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
|
|
||||||
target[key] = target[key].concat(source[key]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target[key] = source[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the passed value is an `Object`.
|
|
||||||
*
|
|
||||||
* @param {*} value - The value to be checked.
|
|
||||||
* @return {boolean} Returns `true` if the value is an `Object`,
|
|
||||||
* otherwise `false`.
|
|
||||||
*/
|
|
||||||
function isObjectLike(value) {
|
|
||||||
return (value != null && typeof value === 'object');
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Retrieve the first available glob library.
|
|
||||||
*
|
|
||||||
* Note that options vary between libraries.
|
|
||||||
*
|
|
||||||
* Candidates:
|
|
||||||
*
|
|
||||||
* - {@link https://npmjs.com/package/tiny-glob tiny-glob}
|
|
||||||
* - {@link https://npmjs.com/package/globby globby}
|
|
||||||
* - {@link https://npmjs.com/package/fast-glob fast-glob}
|
|
||||||
* - {@link https://npmjs.com/package/glob glob}
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { promisify } from 'node:util';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {string[]} A list of packages to attempt import.
|
|
||||||
*/
|
|
||||||
const candidates = [
|
|
||||||
'tiny-glob',
|
|
||||||
'globby',
|
|
||||||
'fast-glob',
|
|
||||||
'glob',
|
|
||||||
];
|
|
||||||
|
|
||||||
let glob;
|
|
||||||
|
|
||||||
try {
|
|
||||||
glob = await importGlob();
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
export default glob;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports the first available glob function.
|
|
||||||
*
|
|
||||||
* @throws {TypeError} If no glob library was found.
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
async function importGlob() {
|
|
||||||
let glob, module;
|
|
||||||
|
|
||||||
for (let name of candidates) {
|
|
||||||
try {
|
|
||||||
module = await import(name);
|
|
||||||
|
|
||||||
if (typeof module.default !== 'function') {
|
|
||||||
throw new TypeError(`Expected ${name} to be a function`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap the function to ensure
|
|
||||||
* a common pattern.
|
|
||||||
*/
|
|
||||||
switch (name) {
|
|
||||||
case 'tiny-glob':
|
|
||||||
return createArrayableGlob(module.default, {
|
|
||||||
filesOnly: true
|
|
||||||
});
|
|
||||||
|
|
||||||
case 'glob':
|
|
||||||
return promisify(module.default);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return module.default;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// swallow this error; skip to the next candidate.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeError(
|
|
||||||
`No glob library was found, expected one of: ${candidates.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a wrapper function for the glob function
|
|
||||||
* to provide support for arrays of patterns.
|
|
||||||
*
|
|
||||||
* @param {function} glob - The glob function.
|
|
||||||
* @param {object} options - The glob options.
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
function createArrayableGlob(glob, options) {
|
|
||||||
return (patterns, options) => {
|
|
||||||
const globs = patterns.map((pattern) => glob(pattern, options));
|
|
||||||
|
|
||||||
return Promise.all(globs).then((files) => {
|
|
||||||
return [].concat.apply([], files);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
115
build/utils/index.js
Normal file
115
build/utils/index.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* @file Provides generic functions and constants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {RegExp} - Match all special characters.
|
||||||
|
*/
|
||||||
|
const regexUnescaped = /[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quotes regular expression characters.
|
||||||
|
*
|
||||||
|
* @param {string} str - The input string.
|
||||||
|
* @return {string} Returns the quoted (escaped) string.
|
||||||
|
*/
|
||||||
|
function escapeRegExp(str) {
|
||||||
|
return str.replace(regexUnescaped, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new object with all nested object properties
|
||||||
|
* concatenated into it recursively.
|
||||||
|
*
|
||||||
|
* Nested keys are flattened into a property path:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* a: {
|
||||||
|
* b: {
|
||||||
|
* c: 1
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* d: 1
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* "a.b.c": 1,
|
||||||
|
* "d": 1
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {object} input - The object to flatten.
|
||||||
|
* @param {string} prefix - The parent key prefix.
|
||||||
|
* @param {object} target - The object that will receive the flattened properties.
|
||||||
|
* @return {object} Returns the `target` object.
|
||||||
|
*/
|
||||||
|
function flatten(input, prefix, target = {}) {
|
||||||
|
for (const key in input) {
|
||||||
|
const field = (prefix ? prefix + '.' + key : key);
|
||||||
|
|
||||||
|
if (isObjectLike(input[key])) {
|
||||||
|
flatten(input[key], field, target);
|
||||||
|
} else {
|
||||||
|
target[field] = input[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the passed value is an `Object`.
|
||||||
|
*
|
||||||
|
* @param {*} value - The value to be checked.
|
||||||
|
* @return {boolean} Returns `true` if the value is an `Object`,
|
||||||
|
* otherwise `false`.
|
||||||
|
*/
|
||||||
|
function isObjectLike(value) {
|
||||||
|
return (value != null && typeof value === 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new object with all nested object properties
|
||||||
|
* merged into it recursively.
|
||||||
|
*
|
||||||
|
* @param {object} target - The target object.
|
||||||
|
* @param {object[]} ...sources - The source object(s).
|
||||||
|
* @throws {TypeError} If the target and source are the same.
|
||||||
|
* @return {object} Returns the `target` object.
|
||||||
|
*/
|
||||||
|
function merge(target, ...sources) {
|
||||||
|
for (const source of sources) {
|
||||||
|
if (target === source) {
|
||||||
|
throw new TypeError(
|
||||||
|
'Cannot merge, target and source are the same'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in source) {
|
||||||
|
if (source[key] != null) {
|
||||||
|
if (isObjectLike(source[key]) && isObjectLike(target[key])) {
|
||||||
|
merge(target[key], source[key]);
|
||||||
|
continue;
|
||||||
|
} else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
|
||||||
|
target[key] = target[key].concat(source[key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
escapeRegExp,
|
||||||
|
flatten,
|
||||||
|
isObjectLike,
|
||||||
|
merge,
|
||||||
|
regexUnescaped,
|
||||||
|
};
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file If available, returns the PostCSS Processor creator and
|
|
||||||
* any the Autoprefixer PostCSS plugin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let postcss, autoprefixer;
|
|
||||||
|
|
||||||
try {
|
|
||||||
postcss = await import('postcss');
|
|
||||||
postcss = postcss.default;
|
|
||||||
|
|
||||||
autoprefixer = await import('autoprefixer');
|
|
||||||
autoprefixer = autoprefixer.default;
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
export default postcss;
|
|
||||||
export const pluginsList = [
|
|
||||||
autoprefixer,
|
|
||||||
];
|
|
||||||
export const pluginsMap = {
|
|
||||||
'autoprefixer': autoprefixer,
|
|
||||||
};
|
|
||||||
export {
|
|
||||||
autoprefixer
|
|
||||||
};
|
|
||||||
@@ -2,10 +2,11 @@ import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js';
|
|||||||
import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
|
import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
|
||||||
import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
|
import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
|
||||||
import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
|
import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
|
||||||
import loconfig, { merge } from './utils/config.js';
|
import loconfig from './helpers/config.js';
|
||||||
import message from './utils/message.js';
|
import message from './helpers/message.js';
|
||||||
import notification from './utils/notification.js';
|
import notification from './helpers/notification.js';
|
||||||
import resolve from './utils/template.js';
|
import resolve from './helpers/template.js';
|
||||||
|
import { merge } from './utils/index.js';
|
||||||
import browserSync from 'browser-sync';
|
import browserSync from 'browser-sync';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
@@ -69,16 +70,18 @@ function configureServer(server, { paths, tasks }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Watch source concats
|
// Watch source concats
|
||||||
server.watch(
|
if (tasks.concats?.length) {
|
||||||
resolve(
|
server.watch(
|
||||||
tasks.concats.reduce(
|
resolve(
|
||||||
(patterns, { includes }) => patterns.concat(includes),
|
tasks.concats.reduce(
|
||||||
[]
|
(patterns, { includes }) => patterns.concat(includes),
|
||||||
|
[]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
).on('change', () => {
|
||||||
).on('change', () => {
|
concatFiles(...developmentConcatFilesArgs);
|
||||||
concatFiles(...developmentConcatFilesArgs);
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// Watch source styles
|
// Watch source styles
|
||||||
server.watch(
|
server.watch(
|
||||||
@@ -113,7 +116,8 @@ function createServerOptions({
|
|||||||
}) {
|
}) {
|
||||||
const config = {
|
const config = {
|
||||||
open: false,
|
open: false,
|
||||||
notify: false
|
notify: false,
|
||||||
|
ghostMode: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the URL for the BrowserSync server
|
// Resolve the URL for the BrowserSync server
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
* [`scripts`](#scripts)
|
* [`scripts`](#scripts)
|
||||||
* [`styles`](#styles)
|
* [`styles`](#styles)
|
||||||
* [`svgs`](#svgs)
|
* [`svgs`](#svgs)
|
||||||
|
* [`versions`](#versions)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,7 +29,9 @@ Learn more about the boilerplate's [tasks](#tasks) below.
|
|||||||
Make sure you have the following installed:
|
Make sure you have the following installed:
|
||||||
|
|
||||||
* [Node] — at least 14.17, the latest LTS is recommended.
|
* [Node] — at least 14.17, the latest LTS is recommended.
|
||||||
* [NPM] — at least 6.0, 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
|
```sh
|
||||||
# Switch to recommended Node version from .nvmrc
|
# Switch to recommended Node version from .nvmrc
|
||||||
@@ -262,8 +265,8 @@ See [`scripts.js`](../build/tasks/scripts.js) for details.
|
|||||||
|
|
||||||
### `styles`
|
### `styles`
|
||||||
|
|
||||||
A wrapper around [node-sass] (and optionally [Autoprefixer] via [PostCSS])
|
A wrapper around [sass] (with optional support for [Autoprefixer]
|
||||||
for compiling and minifying Sass into CSS.
|
via [PostCSS]) for compiling and minifying Sass into CSS.
|
||||||
|
|
||||||
By default, [PostCSS] and [Autoprefixer] are installed with the boilerplate.
|
By default, [PostCSS] and [Autoprefixer] are installed with the boilerplate.
|
||||||
|
|
||||||
@@ -298,6 +301,9 @@ Example:
|
|||||||
|
|
||||||
See [`styles.js`](../build/tasks/styles.js) for details.
|
See [`styles.js`](../build/tasks/styles.js) for details.
|
||||||
|
|
||||||
|
The task also supports [PurgeCSS] to remove unused CSS.
|
||||||
|
See the [documentation on our Grid System](grid.md#build-tasks) for details.
|
||||||
|
|
||||||
### `svgs`
|
### `svgs`
|
||||||
|
|
||||||
A wrapper around [SVG Mixer] for transforming and minifying SVG files
|
A wrapper around [SVG Mixer] for transforming and minifying SVG files
|
||||||
@@ -328,6 +334,86 @@ Example:
|
|||||||
|
|
||||||
See [`svgs.js`](../build/tasks/svgs.js) for details.
|
See [`svgs.js`](../build/tasks/svgs.js) for details.
|
||||||
|
|
||||||
|
### `versions`
|
||||||
|
|
||||||
|
A task to create and update values for use in versioning assets.
|
||||||
|
|
||||||
|
Can generate a hexadecimal value (using random bytes), use the current timestamp,
|
||||||
|
or increment a number.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"format": "timestamp",
|
||||||
|
"key": "now",
|
||||||
|
"outfile": "./assets.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "hex:8",
|
||||||
|
"key": "hex",
|
||||||
|
"outfile": "./assets.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "inc:semver",
|
||||||
|
"key": "inc",
|
||||||
|
"outfile": "./assets.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"now": 1665071717350,
|
||||||
|
"hex": "6ef54181c4ba",
|
||||||
|
"hex": "1.0.2"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The task supports replacing the value of a data key in a JSON file or replacing
|
||||||
|
a string in a file using a [regular expression](RegExp).
|
||||||
|
|
||||||
|
* Explicit JSON field name:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"key": "json:version"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* Implicit JSON field name:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"key": "version"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The regular expression can be a `RegExp` object or a pattern prefixed with `regexp:`.
|
||||||
|
|
||||||
|
* ```json
|
||||||
|
{
|
||||||
|
"key": "regexp:(?<=^const ASSETS_VERSION = ')(?<build>\\d+)(?=';$)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* ```js
|
||||||
|
{
|
||||||
|
key: new RegExp('(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* ```js
|
||||||
|
{
|
||||||
|
key: /^ \* Version: +(?:.+?)\+(.+?)$/
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The regular expression pattern will match the first occurrence and replace
|
||||||
|
the first match in the following order: `build` (named capture), `version`
|
||||||
|
(named capture), `1` (first capture), or `0` (whole match).
|
||||||
|
|
||||||
|
See [`versions.js`](../build/tasks/versions.js) for details.
|
||||||
|
|
||||||
[Autoprefixer]: https://npmjs.com/package/autoprefixer
|
[Autoprefixer]: https://npmjs.com/package/autoprefixer
|
||||||
[BrowserSync]: https://npmjs.com/package/browser-sync
|
[BrowserSync]: https://npmjs.com/package/browser-sync
|
||||||
[concat]: https://npmjs.com/package/concat
|
[concat]: https://npmjs.com/package/concat
|
||||||
@@ -336,9 +422,11 @@ See [`svgs.js`](../build/tasks/svgs.js) for details.
|
|||||||
[glob]: https://npmjs.com/package/glob
|
[glob]: https://npmjs.com/package/glob
|
||||||
[globby]: https://npmjs.com/package/globby
|
[globby]: https://npmjs.com/package/globby
|
||||||
[Node]: https://nodejs.org/
|
[Node]: https://nodejs.org/
|
||||||
[node-sass]: https://npmjs.com/package/node-sass
|
[sass]: https://npmjs.com/package/sass
|
||||||
[NPM]: https://npmjs.com/
|
[NPM]: https://npmjs.com/
|
||||||
[NVM]: https://github.com/nvm-sh/nvm
|
[NVM]: https://github.com/nvm-sh/nvm
|
||||||
[PostCSS]: https://npmjs.com/package/postcss
|
[PostCSS]: https://npmjs.com/package/postcss
|
||||||
|
[PurgeCSS]: https://purgecss.com/
|
||||||
|
[RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
||||||
[SVG Mixer]: https://npmjs.com/package/svg-mixer
|
[SVG Mixer]: https://npmjs.com/package/svg-mixer
|
||||||
[tiny-glob]: https://npmjs.com/package/tiny-glob
|
[tiny-glob]: https://npmjs.com/package/tiny-glob
|
||||||
|
|||||||
109
docs/grid.md
Normal file
109
docs/grid.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Grid system
|
||||||
|
|
||||||
|
* [Architectures](#architecture)
|
||||||
|
* [Build tasks](#build-tasks)
|
||||||
|
* [Configuration](#configuration)
|
||||||
|
* [Usage](#usage)
|
||||||
|
* [Example](#example)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The boilerplate's grid system is meant to be simple and easy to use. The goal is to create a light, flexible, and reusable way to build layouts.
|
||||||
|
The following styles are needed to work properly:
|
||||||
|
|
||||||
|
* [`o-grid`](../assets/styles/objects/_grid.scss) — Object file where the default grid styles are set such as column numbers, modifiers, and options.
|
||||||
|
* [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) — Utility file that generates the styles for every possible column based on an array of media queries and column numbers.
|
||||||
|
|
||||||
|
### Build tasks
|
||||||
|
|
||||||
|
The columns generated by [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) adds a lot of styles to the compiled CSS file. To mitigate that, [PurgeCSS] is integrated into the `styles` build task to purge unused CSS.
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
Depending on your project, you will need to specify all the files that include CSS classes from the grid system. These files will be scanned by [PurgeCSS] to your compiled CSS files.
|
||||||
|
|
||||||
|
Example of a Charcoal project:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
"purgeCSS": {
|
||||||
|
"content": [
|
||||||
|
"./views/app/template/**/*.mustache",
|
||||||
|
"./src/App/Template/*.php",
|
||||||
|
"./assets/scripts/**/*" // use case: `el.classList.add('u-gc-1/2')`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The first step is to set intial SCSS values in the following files :
|
||||||
|
|
||||||
|
- [`settings/_config.scss`](../assets/styles/settings/_config.scss)
|
||||||
|
|
||||||
|
```scss
|
||||||
|
// Grid
|
||||||
|
// ==========================================================================
|
||||||
|
$base-column-nb: 12;
|
||||||
|
$base-column-gap: $unit-small;
|
||||||
|
```
|
||||||
|
|
||||||
|
You can create multiple column layouts depending on media queries.
|
||||||
|
|
||||||
|
- [`objects/_grid.scss`](../assets/styles/objects/_grid.scss)
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.o-grid {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Cols
|
||||||
|
// ==========================================================================
|
||||||
|
&.-col-#{$base-column-nb} {
|
||||||
|
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-col-4 {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-col-#{$base-column-nb}\@from-md {
|
||||||
|
@media (min-width: $from-md) {
|
||||||
|
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// …
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
The following layout has 4 columns at `>=999px` and 12 columns at `<1000px`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="o-container">
|
||||||
|
<h1 class="c-heading -h1">Hello</h1>
|
||||||
|
|
||||||
|
<div class="o-grid -col-4 -col-12@from-medium -gutters">
|
||||||
|
<div class="o-grid_item u-gc-1/8@from-medium">
|
||||||
|
<h2 class="c-heading -h2">This grid has 4 columns and 12 columns from `medium` MQ</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-1/5@from-medium">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-5/9@from-medium">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-9/13@from-medium">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
[PurgeCSS]: https://purgecss.com/
|
||||||
@@ -80,10 +80,10 @@ Learn about [namespacing](https://csswizardry.com/2015/03/more-transparent-ui-co
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-block_heading {
|
.c-block_heading {
|
||||||
@media (max-width: $to-medium) {
|
@media (max-width: $to-md) {
|
||||||
.c-block.-large & {
|
.c-block.-large & {
|
||||||
margin-bottom: rem(40px);
|
margin-bottom: rem(40px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -181,10 +181,7 @@ detection and smooth scrolling with parallax.
|
|||||||
```js
|
```js
|
||||||
import LocomotiveScroll from 'locomotive-scroll';
|
import LocomotiveScroll from 'locomotive-scroll';
|
||||||
|
|
||||||
this.scroll = new LocomotiveScroll({
|
this.scroll = new LocomotiveScroll({})
|
||||||
el: this.el,
|
|
||||||
smooth: true
|
|
||||||
});
|
|
||||||
````
|
````
|
||||||
|
|
||||||
Learn more about [Locomotive Scroll][locomotive-scroll].
|
Learn more about [Locomotive Scroll][locomotive-scroll].
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
"dest": "./www/assets/scripts"
|
"dest": "./www/assets/scripts"
|
||||||
},
|
},
|
||||||
"svgs": {
|
"svgs": {
|
||||||
"src": "./assets/images/sprite",
|
"src": "./assets/svgs",
|
||||||
"dest": "./www/assets/images"
|
"dest": "./www/assets/images"
|
||||||
},
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"src": "./views/boilerplate/template"
|
"src": "./www/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
@@ -56,6 +56,17 @@
|
|||||||
],
|
],
|
||||||
"outfile": "{% paths.svgs.dest %}/sprite.svg"
|
"outfile": "{% paths.svgs.dest %}/sprite.svg"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"purgeCSS": {
|
||||||
|
"content": [
|
||||||
|
"./www/**/*.html",
|
||||||
|
"./assets/scripts/**/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"outfile": "./assets.json"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13624
package-lock.json
generated
13624
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -6,30 +6,42 @@
|
|||||||
"author": "Locomotive <info@locomotive.ca>",
|
"author": "Locomotive <info@locomotive.ca>",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.17",
|
"node": ">=20",
|
||||||
"npm": ">=6.0"
|
"npm": ">=10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --experimental-json-modules --no-warnings build/watch.js",
|
"start": "node --no-warnings build/watch.js",
|
||||||
"build": "node --experimental-json-modules --no-warnings build/build.js"
|
"build": "node --no-warnings build/build.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"locomotive-scroll": "^4.1.4",
|
"locomotive-scroll": "^5.0.0-beta.21",
|
||||||
"modujs": "^1.4.2",
|
"modujs": "^1.4.2",
|
||||||
"modularload": "^1.2.6",
|
"modularload": "^1.2.6"
|
||||||
"normalize.css": "^8.0.1",
|
|
||||||
"svg4everybody": "^2.1.9"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.21",
|
||||||
"browser-sync": "^2.27.9",
|
"browser-sync": "^3.0.4",
|
||||||
|
"common-path": "^1.0.1",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"esbuild": "^0.14.27",
|
"esbuild": "^0.25.8",
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.5",
|
||||||
"node-notifier": "^10.0.1",
|
"node-notifier": "^10.0.1",
|
||||||
"node-sass": "^7.0.1",
|
"postcss": "^8.5.6",
|
||||||
"postcss": "^8.4.12",
|
"purgecss": "^7.0.2",
|
||||||
|
"sass": "^1.89.2",
|
||||||
"svg-mixer": "^2.3.14",
|
"svg-mixer": "^2.3.14",
|
||||||
"tiny-glob": "^0.2.9"
|
"tiny-glob": "^0.2.9"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"browser-sync": {
|
||||||
|
"ua-parser-js": "^1.0.33"
|
||||||
|
},
|
||||||
|
"svg-mixer": {
|
||||||
|
"micromatch": "^4.0.8",
|
||||||
|
"postcss": "^8.4.49"
|
||||||
|
},
|
||||||
|
"svg-mixer-utils": {
|
||||||
|
"anymatch": "^3.1.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
www/assets/fonts/SourceSans3-Bold.woff2
Normal file
BIN
www/assets/fonts/SourceSans3-Bold.woff2
Normal file
Binary file not shown.
BIN
www/assets/fonts/SourceSans3-BoldIt.woff2
Normal file
BIN
www/assets/fonts/SourceSans3-BoldIt.woff2
Normal file
Binary file not shown.
BIN
www/assets/fonts/SourceSans3-Regular.woff2
Normal file
BIN
www/assets/fonts/SourceSans3-Regular.woff2
Normal file
Binary file not shown.
BIN
www/assets/fonts/SourceSans3-RegularIt.woff2
Normal file
BIN
www/assets/fonts/SourceSans3-RegularIt.woff2
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
0
www/assets/styles/.gitkeep
Normal file
0
www/assets/styles/.gitkeep
Normal file
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"critical.css"}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,18 +18,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body data-module-load>
|
<body data-module-load>
|
||||||
<div data-load-container>
|
<div data-load-container>
|
||||||
<div class="o-scroll" data-module-scroll="main">
|
<div data-module-scroll="main">
|
||||||
<header data-scroll-section>
|
<header>
|
||||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="images.html">Images</a></li>
|
<li><a href="images.html">Images</a></li>
|
||||||
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||||
|
<li><a href="grid.html">Grid</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main data-scroll-section>
|
<main>
|
||||||
<div class="o-container">
|
<div class="o-container">
|
||||||
<h1 class="c-heading -h1">Page</h1>
|
<h1 class="c-heading -h1">Page</h1>
|
||||||
|
|
||||||
@@ -89,15 +90,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</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>
|
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js" crossorigin="anonymous"></script>
|
|
||||||
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<script src="assets/scripts/vendors.js" defer></script>
|
<script src="assets/scripts/vendors.js" defer></script>
|
||||||
<script src="assets/scripts/app.js" defer></script>
|
<script src="assets/scripts/app.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
90
www/grid.html
Normal file
90
www/grid.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html class="is-loading" lang="en" data-page="grid">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Locomotive Boilerplate</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
|
||||||
|
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
|
||||||
|
<!-- For a dark mode managment and svg favicon add this in your favicon.svg -->
|
||||||
|
<!--
|
||||||
|
<style>
|
||||||
|
path {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
-->
|
||||||
|
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
|
||||||
|
|
||||||
|
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
|
||||||
|
onload="this.media='all'; this.onload=null; this.isLoaded=true">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-module-load>
|
||||||
|
<div data-load-container>
|
||||||
|
<div data-module-scroll="main">
|
||||||
|
<header>
|
||||||
|
<a href="/">
|
||||||
|
<h1>Locomotive Boilerplate</h1>
|
||||||
|
</a>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="images.html">Images</a></li>
|
||||||
|
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||||
|
<li><a href="grid.html">Grid</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="o-container">
|
||||||
|
<h1 class="c-heading -h1">Hello</h1>
|
||||||
|
|
||||||
|
<div class="o-grid -col-4 -col-12@from-medium -gutters">
|
||||||
|
<div class="o-grid_item u-gc-1/8@from-medium" style="background-color: gray;">
|
||||||
|
<h2 class="c-heading -h2">This grid has 4 columns and 12 columns from `medium` MQ</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-1/5@from-medium" style="background-color: yellow;">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-5/9@from-medium" style="background-color: red;">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-9/13@from-medium" style="background-color: blue;">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o-grid_item u-gc-1/3 u-gc-5/13@from-medium" style="background-color: pink;">
|
||||||
|
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
||||||
|
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="assets/scripts/vendors.js" defer></script>
|
||||||
|
<script src="assets/scripts/app.js" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -18,18 +18,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body data-module-load>
|
<body data-module-load>
|
||||||
<div data-load-container>
|
<div data-load-container>
|
||||||
<div class="o-scroll" data-module-scroll="main">
|
<div data-module-scroll="main">
|
||||||
<header data-scroll-section>
|
<header>
|
||||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="images.html">Images</a></li>
|
<li><a href="images.html">Images</a></li>
|
||||||
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||||
|
<li><a href="grid.html">Grid</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main data-scroll-section>
|
<main>
|
||||||
<div class="o-container">
|
<div class="o-container">
|
||||||
<h1 class="c-heading -h1">Images</h1>
|
<h1 class="c-heading -h1">Images</h1>
|
||||||
|
|
||||||
@@ -39,8 +40,8 @@
|
|||||||
<h3 class="c-heading -h3">Basic</h3>
|
<h3 class="c-heading -h3">Basic</h3>
|
||||||
|
|
||||||
<div style="width: 640px; max-width: 100%;">
|
<div style="width: 640px; max-width: 100%;">
|
||||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?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=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
|
||||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?v=2" 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>
|
</div>
|
||||||
|
|
||||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||||
@@ -58,30 +59,30 @@
|
|||||||
|
|
||||||
<div style="width: 640px; max-width: 100%;">
|
<div style="width: 640px; max-width: 100%;">
|
||||||
<div class="o-ratio u-4:3">
|
<div class="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==" />
|
<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>
|
||||||
<div class="o-ratio u-4:3">
|
<div class="o-ratio u-4:3">
|
||||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
<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>
|
||||||
<div class="o-ratio u-4:3">
|
<div class="o-ratio u-4:3">
|
||||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
<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>
|
||||||
<div class="o-ratio u-4:3">
|
<div class="o-ratio u-4:3">
|
||||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
<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>
|
||||||
<div class="o-ratio u-4:3">
|
<div class="o-ratio u-4:3">
|
||||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||||
|
|
||||||
<div style="width: 480px; max-width: 100%;">
|
<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/640/480?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=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/640/480?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=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/640/480?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=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/640/480?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=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/640/480?v=5"></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>
|
</div>
|
||||||
|
|
||||||
<h4 class="c-heading -h3">Using SVG viewport for ratio</h3>
|
<h4 class="c-heading -h3">Using SVG viewport for ratio</h3>
|
||||||
@@ -115,15 +116,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</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>
|
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js" crossorigin="anonymous"></script>
|
|
||||||
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<script src="assets/scripts/vendors.js" defer></script>
|
<script src="assets/scripts/vendors.js" defer></script>
|
||||||
<script src="assets/scripts/app.js" defer></script>
|
<script src="assets/scripts/app.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -29,14 +29,21 @@
|
|||||||
-->
|
-->
|
||||||
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
|
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
|
||||||
|
|
||||||
|
<!-- Preload Fonts -->
|
||||||
|
<link rel="preload" href="assets/fonts/SourceSans3-Bold.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
|
<link rel="preload" href="assets/fonts/SourceSans3-BoldIt.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
|
<link rel="preload" href="assets/fonts/SourceSans3-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
|
<link rel="preload" href="assets/fonts/SourceSans3-RegularIt.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
|
|
||||||
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
|
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
|
||||||
onload="this.media='all'; this.onload=null; this.isLoaded=true">
|
onload="this.media='all'; this.onload=null; this.isLoaded=true">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-module-load>
|
<body data-module-load>
|
||||||
|
|
||||||
<div data-load-container>
|
<div data-load-container>
|
||||||
<div class="o-scroll" data-module-scroll="main">
|
<div data-module-scroll="main">
|
||||||
<header data-scroll-section>
|
<header>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<h1>Locomotive Boilerplate</h1>
|
<h1>Locomotive Boilerplate</h1>
|
||||||
</a>
|
</a>
|
||||||
@@ -44,29 +51,24 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="images.html">Images</a></li>
|
<li><a href="images.html">Images</a></li>
|
||||||
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||||
|
<li><a href="grid.html">Grid</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main data-scroll-section>
|
<main data-module-example>
|
||||||
<div class="o-container">
|
<div class="o-container">
|
||||||
<h1 class="c-heading -h1">Hello</h1>
|
<h1 class="c-heading -h1">Hello</h1>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer data-scroll-section>
|
<footer>
|
||||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
|
||||||
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script nomodule
|
|
||||||
src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<script src="assets/scripts/vendors.js" defer></script>
|
<script src="assets/scripts/vendors.js" defer></script>
|
||||||
<script src="assets/scripts/app.js" defer></script>
|
<script src="assets/scripts/app.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user