1
0
mirror of https://github.com/locomotivemtl/locomotive-boilerplate.git synced 2026-01-15 00:55:08 +08:00

9 Commits

Author SHA1 Message Date
Deven Caron
fb5957024d Merge branch 'feature/update-node-version' into devenini/develop 2023-02-02 10:00:56 -05:00
Deven Caron
68dd79caef Format files 2023-02-01 13:29:55 -05:00
Deven Caron
be89145728 Merge branch 'feature/update-node-version' into devenini/develop 2023-02-01 13:29:09 -05:00
Deven Caron
8f3034d54a Run precommit 2023-02-01 09:49:25 -05:00
Deven Caron
3fa1de473c Sort config alphabetically 2023-02-01 09:12:48 -05:00
Deven Caron
ecf60ee507 Convert config tabs to spaces
Co-authored-by: Chauncey McAskill <chauncey@locomotive.ca>
2023-02-01 09:10:40 -05:00
Deven Caron
9632d6270e Format all styles & scripts files with prettier command 2023-01-31 16:29:44 -05:00
Deven Caron
bc3a1a6934 Add module class aliases 2023-01-31 16:26:01 -05:00
Deven Caron
2f6b353616 Add prettier & precommit command 2023-01-31 11:44:00 -05:00
97 changed files with 2754 additions and 3644 deletions

13
.gitignore vendored
View File

@@ -3,15 +3,4 @@ node_modules
Thumbs.db
loconfig.*.json
!loconfig.example.json
.prettierrc
www/assets/scripts/app.js
www/assets/scripts/app.js.map
www/assets/scripts/vendors.js
www/assets/styles/main.css
www/assets/styles/main.css.map
www/assets/styles/critical.css
www/assets/styles/critical.css.map
assets.json
.prettierrc

2
.nvmrc
View File

@@ -1 +1 @@
v20.10
v18.13

7
.prettierrc.json Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "es5",
"useTabs": false
}

View File

@@ -23,7 +23,7 @@ Learn more about [languages and technologies](docs/technologies.md).
Make sure you have the following installed:
* [Node] — at least 17.9, the latest LTS is recommended.
* [Node] — at least 18.13, 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.

3
assets.json Normal file
View File

@@ -0,0 +1,3 @@
{
"version": 1675197299100
}

View File

@@ -1,137 +1,79 @@
import modular from 'modujs';
import * as modules from './modules';
import globals from './globals';
import { debounce } from './utils/tickers';
import { $html } from './utils/dom';
import { ENV, FONT, CUSTOM_EVENT, CSS_CLASS } from './config'
import { isFontLoadingAPIAvailable, loadFonts } from './utils/fonts';
import modular from 'modujs'
import * as modules from './modules'
import globals from './globals'
import { html } from './utils/environment'
import config from './config'
import { isFontLoadingAPIAvailable, loadFonts } from './utils/fonts'
const app = new modular({
modules,
});
modules: modules,
})
window.onload = (event) => {
const $style = document.getElementById('main-css')
if ($style) {
if ($style.isLoaded) {
init()
} else {
$style.addEventListener('load', (event) => {
init()
})
}
} else {
console.warn('The "main-css" stylesheet not found')
}
}
export const EAGER_FONTS = [
{ family: 'Source Sans', style: 'normal', weight: 400 },
{ family: 'Source Sans', style: 'normal', weight: 700 },
]
function init() {
bindEvents();
globals();
setViewportSizes();
globals()
app.init(app);
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
// );
html.classList.add('is-loaded')
html.classList.add('is-ready')
html.classList.remove('is-loading')
/**
* Eagerly load the following fonts.
*/
if (isFontLoadingAPIAvailable) {
loadFonts(FONT.EAGER, ENV.IS_DEV).then((eagerFonts) => {
$html.classList.add(CSS_CLASS.FONTS_LOADED);
loadFonts(EAGER_FONTS, config.IS_DEV).then((eagerFonts) => {
html.classList.add('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();
// }
});
if (config.IS_DEV) {
console.group(
'Eager fonts loaded!',
eagerFonts.length,
'/',
document.fonts.size
)
console.group('State of eager fonts:')
eagerFonts.forEach((font) =>
console.log(
font.family,
font.style,
font.weight,
font.status /*, font*/
)
)
console.groupEnd()
console.group('State of all fonts:')
document.fonts.forEach((font) =>
console.log(
font.family,
font.style,
font.weight,
font.status /*, font*/
)
)
console.groupEnd()
}
})
}
}
////////////////
// Global events
////////////////
function bindEvents() {
// Resize event
const resizeEndEvent = new CustomEvent(CUSTOM_EVENT.RESIZE_END)
window.addEventListener(
"resize",
debounce(() => {
window.dispatchEvent(resizeEndEvent)
}, 200, false)
)
window.addEventListener(
"resize",
onResize
)
}
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');
if ($style) {
if ($style.isLoaded) {
init();
} else {
$style.addEventListener('load', init);
}
} else {
console.warn('The "main-css" stylesheet not found');
}
});

View File

@@ -7,59 +7,18 @@
* > (since `process` is a Node API, not a web API).
* > — https://esbuild.github.io/api/#platform
*/
const env = process.env.NODE_ENV
const NODE_ENV = process.env.NODE_ENV
const IS_MOBILE = window.matchMedia('(any-pointer:coarse)').matches
export default config = Object.freeze({
// Environments
ENV: env,
IS_PROD: env === 'production',
IS_DEV: env === 'development',
// 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')
)
// CSS class names
CSS_CLASS: {
LOADING: 'is-loading',
READY: 'is-ready',
LOADED: 'is-loaded',
},
})
// 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,
}

View File

@@ -1,23 +1,23 @@
import svg4everybody from 'svg4everybody';
import { ENV } from './config';
import svg4everybody from 'svg4everybody'
import config from './config'
// Dynamic imports for development mode only
let gridHelper;
(async () => {
if (ENV.IS_DEV) {
const gridHelperModule = await import('./utils/grid-helper');
gridHelper = gridHelperModule?.gridHelper;
let gridHelper
;(async () => {
if (config.IS_DEV) {
const gridHelperModule = await import('./utils/grid-helper')
gridHelper = gridHelperModule?.gridHelper
}
})();
})()
export default function () {
/**
* Use external SVG spritemaps
*/
svg4everybody();
svg4everybody()
/**
* Add grid helper
*/
gridHelper?.();
gridHelper?.()
}

View File

@@ -1,3 +1,3 @@
export {default as Example} from './modules/Example';
export {default as Load} from './modules/Load';
export {default as Scroll} from './modules/Scroll';
export { default as Example } from './modules/Example'
export { default as Load } from './modules/Load'
export { default as Scroll } from './modules/Scroll'

View File

@@ -1,14 +1,14 @@
import { module } from 'modujs';
import { FONT } from '../config';
import { whenReady } from '../utils/fonts';
import { module as Module } from 'modujs'
import { EAGER_FONTS } from '../app'
import { whenReady } from '../utils/fonts'
export default class extends module {
export default class extends Module {
constructor(m) {
super(m);
super(m)
}
init() {
whenReady(FONT.EAGER).then((fonts) => this.onFontsLoaded(fonts));
whenReady(EAGER_FONTS).then((fonts) => this.onFontsLoaded(fonts))
}
onFontsLoaded(fonts) {

View File

@@ -1,22 +1,22 @@
import { module } from 'modujs';
import modularLoad from 'modularload';
import { module as Module } from 'modujs'
import modularLoad from 'modularload'
export default class extends module {
export default class extends Module {
constructor(m) {
super(m);
super(m)
}
init() {
const load = new modularLoad({
enterDelay: 0,
transitions: {
customTransition: {}
}
});
customTransition: {},
},
})
load.on('loaded', (transition, oldContainer, newContainer) => {
this.call('destroy', oldContainer, 'app');
this.call('update', newContainer, 'app');
});
this.call('destroy', oldContainer, 'app')
this.call('update', newContainer, 'app')
})
}
}

View File

@@ -1,22 +1,26 @@
import { module } from 'modujs'
import { module as Module } from 'modujs'
import { lazyLoadImage } from '../utils/image'
import LocomotiveScroll from 'locomotive-scroll'
export default class extends module {
export default class extends Module {
constructor(m) {
super(m);
super(m)
}
init() {
this.scroll = new LocomotiveScroll({
modularInstance: this,
el: this.el,
smooth: true,
})
// // Force scroll to top
// if (history.scrollRestoration) {
// history.scrollRestoration = 'manual'
// window.scrollTo(0, 0)
// }
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);
})
}
/**
@@ -37,23 +41,12 @@ export default class extends module {
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
*/
lazyLoad(args) {
lazyLoadImage(args.target, null, () => {
lazyLoadImage(args.obj.el, null, () => {
//callback
})
}
scrollTo(params) {
let { target, ...options } = params
options = Object.assign({
// Defaults
duration: 1,
}, options)
this.scroll?.scrollTo(target, options)
}
destroy() {
this.scroll.destroy();
this.scroll.destroy()
}
}

View File

@@ -1,7 +0,0 @@
const $html = document.documentElement
const $body = document.body
export {
$html,
$body,
}

View File

@@ -0,0 +1,8 @@
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 }

View File

@@ -23,7 +23,7 @@
* @property {string} [weight] - The weight used by the font in our CSS.
*/
const isFontLoadingAPIAvailable = ('fonts' in document);
const isFontLoadingAPIAvailable = 'fonts' in document
/**
* Determines if the given font matches the given `FontFaceReference`.
@@ -33,15 +33,14 @@ const isFontLoadingAPIAvailable = ('fonts' in document);
*
* @returns {boolean}
*/
function conformsToReference(font, criterion)
{
for (const [ key, value ] of Object.entries(criterion)) {
function conformsToReference(font, criterion) {
for (const [key, value] of Object.entries(criterion)) {
switch (key) {
case 'family': {
if (trim(font[key]) !== value) {
return false;
return false
}
break;
break
}
case 'weight': {
@@ -54,21 +53,21 @@ function conformsToReference(font, criterion)
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping
*/
if (font[key] != value) {
return false;
return false
}
break;
break
}
default: {
if (font[key] !== value) {
return false;
return false
}
break;
break
}
}
}
return true;
return true
}
/**
@@ -79,24 +78,21 @@ function conformsToReference(font, criterion)
*
* @returns {boolean}
*/
function conformsToShorthand(font, criterion)
{
const family = trim(font.family);
function conformsToShorthand(font, criterion) {
const family = trim(font.family)
if (trim(family) === criterion) {
return true;
return true
}
if (
criterion.endsWith(trim(family)) && (
criterion.match(font.weight) ||
criterion.match(font.style)
)
criterion.endsWith(trim(family)) &&
(criterion.match(font.weight) || criterion.match(font.style))
) {
return true;
return true
}
return true;
return true
}
/**
@@ -107,15 +103,14 @@ function conformsToShorthand(font, criterion)
*
* @returns {boolean}
*/
function conformsToAnyReference(font, criteria)
{
function conformsToAnyReference(font, criteria) {
for (const criterion of criteria) {
if (conformsToReference(font, criterion)) {
return true;
return true
}
}
return false;
return false
}
/**
@@ -126,17 +121,16 @@ function conformsToAnyReference(font, criteria)
*
* @returns {FontFace[]}
*/
function findManyByReference(search)
{
const found = [];
function findManyByReference(search) {
const found = []
for (const font of document.fonts) {
if (conformsToReference(font, search)) {
found.push(font);
found.push(font)
}
}
return found;
return found
}
/**
@@ -147,17 +141,16 @@ function findManyByReference(search)
*
* @returns {FontFace[]}
*/
function findManyByShorthand(search)
{
const found = [];
function findManyByShorthand(search) {
const found = []
for (const font of document.fonts) {
if (conformsToShorthand(font, search)) {
found.push(font);
found.push(font)
}
}
return found;
return found
}
/**
@@ -168,15 +161,14 @@ function findManyByShorthand(search)
*
* @returns {?FontFace}
*/
function findOneByReference(search)
{
function findOneByReference(search) {
for (const font of document.fonts) {
if (conformsToReference(font, criterion)) {
return font;
return font
}
}
return null;
return null
}
/**
@@ -192,15 +184,14 @@ function findOneByReference(search)
*
* @returns {?FontFace}
*/
function findOneByShorthand(search)
{
function findOneByShorthand(search) {
for (const font of document.fonts) {
if (conformsToShorthand(font, search)) {
return font;
return font
}
}
return null;
return null
}
/**
@@ -220,16 +211,16 @@ function getAny(search) {
if (search) {
switch (typeof search) {
case 'string':
return findOneByShorthand(search);
return findOneByShorthand(search)
case 'object':
return findOneByReference(search);
return findOneByReference(search)
}
}
throw new TypeError(
'Expected font query to be font shorthand or font reference'
);
)
}
/**
@@ -244,30 +235,30 @@ function getAny(search) {
*/
function getMany(queries) {
if (!Array.isArray(queries)) {
queries = [ queries ];
queries = [queries]
}
const found = new Set();
const found = new Set()
queries.forEach((search) => {
if (search) {
switch (typeof search) {
case 'string':
found.add(...findManyByShorthand(search));
return;
found.add(...findManyByShorthand(search))
return
case 'object':
found.add(...findManyByReference(search));
return;
found.add(...findManyByReference(search))
return
}
}
throw new TypeError(
'Expected font query to be font shorthand or font reference'
);
)
})
return [ ...found ];
return [...found]
}
/**
@@ -283,10 +274,10 @@ function getMany(queries) {
*/
function hasAny(search) {
if (search instanceof FontFace) {
return document.fonts.has(search);
return document.fonts.has(search)
}
return getAny(search) != null;
return getAny(search) != null
}
/**
@@ -302,15 +293,12 @@ function hasAny(search) {
*
* @returns {Promise}
*/
async function loadFonts(fontsToLoad, debug = false)
{
async function loadFonts(fontsToLoad, debug = false) {
if ((fontsToLoad.size ?? fontsToLoad.length) === 0) {
throw new TypeError(
'Expected at least one font'
);
throw new TypeError('Expected at least one font')
}
return await loadFontsWithAPI([ ...fontsToLoad ], debug);
return await loadFontsWithAPI([...fontsToLoad], debug)
}
/**
@@ -320,12 +308,11 @@ async function loadFonts(fontsToLoad, debug = false)
*
* @returns {Promise}
*/
async function loadFontFaceWithAPI(font)
{
return await (font.status === 'unloaded'
? font.load()
: font.loaded
).then((font) => font, (err) => font)
async function loadFontFaceWithAPI(font) {
return await (font.status === 'unloaded' ? font.load() : font.loaded).then(
(font) => font,
(err) => font
)
}
/**
@@ -336,31 +323,34 @@ async function loadFontFaceWithAPI(font)
*
* @returns {Promise}
*/
async function loadFontsWithAPI(fontsToLoad, debug = false)
{
debug && console.group('[loadFonts:API]', fontsToLoad.length, '/', document.fonts.size);
async function loadFontsWithAPI(fontsToLoad, debug = false) {
debug &&
console.group(
'[loadFonts:API]',
fontsToLoad.length,
'/',
document.fonts.size
)
const fontsToBeLoaded = [];
const fontsToBeLoaded = []
for (const fontToLoad of fontsToLoad) {
if (fontToLoad instanceof FontFace) {
if (!document.fonts.has(fontToLoad)) {
document.fonts.add(fontToLoad);
document.fonts.add(fontToLoad)
}
fontsToBeLoaded.push(
loadFontFaceWithAPI(fontToLoad)
);
fontsToBeLoaded.push(loadFontFaceWithAPI(fontToLoad))
} else {
fontsToBeLoaded.push(
...getMany(fontToLoad).map((font) => loadFontFaceWithAPI(font))
);
)
}
}
debug && console.groupEnd();
debug && console.groupEnd()
return await Promise.all(fontsToBeLoaded);
return await Promise.all(fontsToBeLoaded)
}
/**
@@ -374,7 +364,7 @@ async function loadFontsWithAPI(fontsToLoad, debug = false)
* @returns {string}
*/
function trim(value) {
return value.replace(/['"]+/g, '');
return value.replace(/['"]+/g, '')
}
/**
@@ -385,11 +375,10 @@ function trim(value) {
*
* @returns {Promise}
*/
async function whenReady(queries)
{
const fonts = getMany(queries);
async function whenReady(queries) {
const fonts = getMany(queries)
return await Promise.all(fonts.map((font) => font.loaded));
return await Promise.all(fonts.map((font) => font.loaded))
}
export {

View File

@@ -15,9 +15,9 @@
* @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)';
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
@@ -31,15 +31,15 @@ function gridHelper({
rgbaColor = GRID_HELPER_RGBA_COLOR,
} = {}) {
// Set grid container
const $gridContainer = document.createElement('div');
document.body.append($gridContainer);
const $gridContainer = document.createElement('div')
document.body.append($gridContainer)
// Set grid appearence
setGridHelperColumns($gridContainer, rgbaColor);
setGridHelperStyles($gridContainer, gutterCssVar, marginCssVar);
setGridHelperColumns($gridContainer, rgbaColor)
setGridHelperStyles($gridContainer, gutterCssVar, marginCssVar)
// Set grid interactivity
setGridEvents($gridContainer, rgbaColor);
setGridEvents($gridContainer, rgbaColor)
}
/**
@@ -51,19 +51,19 @@ function gridHelper({
*
*/
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';
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'
}
/**
@@ -75,19 +75,19 @@ function setGridHelperStyles($container, gutterCssVar, marginCssVar) {
*/
function setGridHelperColumns($container, rgbaColor) {
// Clear columns
$container.innerHTML = '';
$container.innerHTML = ''
// Loop through columns
const columns = Number(
window.getComputedStyle($container).getPropertyValue('--grid-columns')
);
)
let $col;
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);
$col = document.createElement('div')
$col.style.flex = '1 1 0'
$col.style.backgroundColor = rgbaColor
$container.appendChild($col)
}
}
@@ -106,33 +106,33 @@ function setGridEvents($container, rgbaColor) {
window.addEventListener(
'resize',
setGridHelperColumns($container, rgbaColor)
);
)
// Toggle grid
let ctrlDown = false;
let isActive = false;
let ctrlDown = false
let isActive = false
document.addEventListener('keydown', (e) => {
if (e.key == 'Control') {
ctrlDown = true;
ctrlDown = true
} else {
if (ctrlDown && e.key == 'g') {
if (isActive) {
$container.style.visibility = 'hidden';
$container.style.visibility = 'hidden'
} else {
$container.style.visibility = 'visible';
$container.style.visibility = 'visible'
}
isActive = !isActive;
isActive = !isActive
}
}
});
})
document.addEventListener('keyup', (e) => {
if (e.key == 'Control') {
ctrlDown = false;
ctrlDown = false
}
});
})
}
export { gridHelper };
export { gridHelper }

View File

@@ -4,15 +4,18 @@
* @return {string} escaped string
*/
const escapeHtml = str =>
str.replace(/[&<>'"]/g, tag => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}[tag]))
const escapeHtml = (str) =>
str.replace(
/[&<>'"]/g,
(tag) =>
({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
}[tag])
)
/**
* Unescape HTML string
@@ -20,22 +23,21 @@ const escapeHtml = str =>
* @return {string} unescaped string
*/
const unescapeHtml = str =>
str.replace('&amp;', '&')
const unescapeHtml = (str) =>
str
.replace('&amp;', '&')
.replace('&lt;', '<')
.replace('&gt;', '>')
.replace('&#39;', "'")
.replace('&quot;', '"')
/**
* Get element data attributes
* @param {HTMLElement} node - node element
* @return {array} node data
*/
const getNodeData = node => {
const getNodeData = (node) => {
// All attributes
const attributes = node.attributes
@@ -68,13 +70,9 @@ const getNodeData = node => {
data[match[1]] = getData(node.getAttribute(name))
}
return data;
return data
}
/**
* Parse value to data type.
*
@@ -84,7 +82,7 @@ const getNodeData = node => {
*/
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
const getData = data => {
const getData = (data) => {
if (data === 'true') {
return true
}
@@ -98,7 +96,7 @@ const getData = data => {
}
// Only convert to a number if it doesn't change the string
if (data === +data+'') {
if (data === +data + '') {
return +data
}
@@ -109,15 +107,13 @@ const getData = data => {
return data
}
/**
* Returns an array containing all the parent nodes of the given node
* @param {HTMLElement} $el - DOM Element
* @return {array} parent nodes
*/
const getParents = $el => {
const getParents = ($el) => {
// Set up a parent array
let parents = []
@@ -130,11 +126,4 @@ const getParents = $el => {
return parents
}
export {
escapeHtml,
unescapeHtml,
getNodeData,
getData,
getParents,
}
export { escapeHtml, unescapeHtml, getNodeData, getData, getParents }

View File

@@ -1,5 +1,3 @@
import { CSS_CLASS } from '../config'
/**
* Get an image meta data
*
@@ -7,14 +5,13 @@ import { CSS_CLASS } from '../config'
* @return {object} The given image meta data
*/
const getImageMetadata = $img => ({
const getImageMetadata = ($img) => ({
url: $img.src,
width: $img.naturalWidth,
height: $img.naturalHeight,
ratio: $img.naturalWidth / $img.naturalHeight,
})
/**
* Load the given image.
*
@@ -38,11 +35,13 @@ const loadImage = (url, options = {}) => {
})
}
if($img.decode) {
if ($img.decode) {
$img.src = url
$img.decode().then(loadCallback).catch(e => {
reject(e)
})
$img.decode()
.then(loadCallback)
.catch((e) => {
reject(e)
})
} else {
$img.onload = loadCallback
$img.onerror = (e) => {
@@ -53,7 +52,6 @@ const loadImage = (url, options = {}) => {
})
}
/**
* Lazy load the given image.
*
@@ -68,7 +66,7 @@ const LAZY_LOADED_IMAGES = []
const lazyLoadImage = async ($el, url, callback) => {
let src = url ? url : $el.dataset.src
let loadedImage = LAZY_LOADED_IMAGES.find(image => image.url === src)
let loadedImage = LAZY_LOADED_IMAGES.find((image) => image.url === src)
if (!loadedImage) {
loadedImage = await loadImage(src)
@@ -80,7 +78,7 @@ const lazyLoadImage = async ($el, url, callback) => {
LAZY_LOADED_IMAGES.push(loadedImage)
}
if($el.src === src) {
if ($el.src === src) {
return
}
@@ -91,22 +89,17 @@ const lazyLoadImage = async ($el, url, callback) => {
}
requestAnimationFrame(() => {
let lazyParent = $el.closest(`.${CSS_CLASS.LAZY_CONTAINER}`)
let lazyParent = $el.closest('.c-lazy')
if(lazyParent) {
lazyParent.classList.add(CSS_CLASS.LAZY_LOADED)
if (lazyParent) {
lazyParent.classList.add('-lazy-loaded')
lazyParent.style.backgroundImage = ''
}
$el.classList.add(CSS_CLASS.LAZY_LOADED)
$el.classList.add('-lazy-loaded')
callback?.()
})
}
export {
getImageMetadata,
loadImage,
lazyLoadImage
}
export { getImageMetadata, loadImage, lazyLoadImage }

View File

@@ -7,7 +7,7 @@
* @return {boolean}
*/
const isObject = x => (x && typeof x === 'object')
const isObject = (x) => x && typeof x === 'object'
/**
* Determines if the argument is a function.
@@ -16,10 +16,6 @@ const isObject = x => (x && typeof x === 'object')
* @return {boolean}
*/
const isFunction = x => typeof x === 'function'
const isFunction = (x) => typeof x === 'function'
export {
isObject,
isFunction
}
export { isObject, isFunction }

View File

@@ -8,7 +8,6 @@
const clamp = (min = 0, max = 1, a) => Math.min(max, Math.max(min, a))
/**
* Calculate lerp
* @param {number} x - start value
@@ -19,7 +18,6 @@ const clamp = (min = 0, max = 1, a) => Math.min(max, Math.max(min, a))
const lerp = (x, y, a) => x * (1 - a) + y * a
/**
* Calculate inverted lerp
* @param {number} x - start value
@@ -28,8 +26,7 @@ const lerp = (x, y, a) => x * (1 - a) + y * a
* @return {number} inverted lerp value
*/
const invlerp = (x, y, a) => clamp((a - x)/(y - x))
const invlerp = (x, y, a) => clamp((v - x) / (a - x))
/**
* Round number to the specified precision.
@@ -42,13 +39,7 @@ const invlerp = (x, y, a) => clamp((a - x)/(y - x))
* @return {number} The rounded number.
*/
const roundNumber = (number, precision = 2) => {
return Number.parseFloat(number.toPrecision(precision));
return Number.parseFloat(number.toPrecision(precision))
}
export {
clamp,
lerp,
invlerp,
roundNumber
}
export { clamp, lerp, invlerp, roundNumber }

View File

@@ -39,7 +39,6 @@ const debounce = (callback, delay, immediate = false) => {
}
}
/**
* Creates a throttled function.
*
@@ -71,8 +70,4 @@ const throttle = (callback, delay) => {
}
}
export {
debounce,
throttle
}
export { debounce, throttle }

View File

@@ -4,32 +4,32 @@
* @return {number|object} translate value
*/
const getTranslate = $el => {
if(!window.getComputedStyle) {
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 transform =
style.msTransform ||
style.webkitTransform ||
style.MozTransform ||
style.OTransform ||
style.transform
const matrix3D = transform.match(/^matrix3d\((.+)\)$/)
if(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
x: matrix ? parseFloat(matrix[1].split(', ')[4]) : 0,
y: matrix ? parseFloat(matrix[1].split(', ')[5]) : 0,
}
}
return translate
}
export {
transform,
getTranslate
}
export { transform, getTranslate }

View File

@@ -29,7 +29,7 @@
* - {@link https://www.w3.org/TR/page-visibility/ W3 Specification}
* - {@link https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API MDN Web Docs}
*/
export default new class PageVisibility {
export default new (class PageVisibility {
/**
* Checks if the "visibilitychange" event listener has been registered.
*
@@ -37,7 +37,7 @@ export default new class PageVisibility {
* otherwise returns `true`.
*/
get isEnabled() {
return isVisibilityChangeObserved;
return isVisibilityChangeObserved
}
/**
@@ -48,12 +48,15 @@ export default new class PageVisibility {
*/
disableCustomEvents() {
if (isVisibilityChangeObserved) {
isVisibilityChangeObserved = false;
document.removeEventListener('visibilitychange', handleCustomVisibilityChange);
return true;
isVisibilityChangeObserved = false
document.removeEventListener(
'visibilitychange',
handleCustomVisibilityChange
)
return true
}
return false;
return false
}
/**
@@ -64,14 +67,17 @@ export default new class PageVisibility {
*/
enableCustomEvents() {
if (!isVisibilityChangeObserved) {
isVisibilityChangeObserved = true;
document.addEventListener('visibilitychange', handleCustomVisibilityChange);
return true;
isVisibilityChangeObserved = true
document.addEventListener(
'visibilitychange',
handleCustomVisibilityChange
)
return true
}
return false;
return false
}
}
})()
/**
* Tracks whether custom visibility event types
@@ -79,7 +85,7 @@ export default new class PageVisibility {
*
* @type {boolean}
*/
let isVisibilityChangeObserved = false;
let isVisibilityChangeObserved = false
/**
* Dispatches a custom visibility event at the document derived
@@ -94,11 +100,13 @@ let isVisibilityChangeObserved = false;
* @return {void}
*/
function handleCustomVisibilityChange(event) {
document.dispatchEvent(new CustomEvent(`visibility${document.visibilityState}`, {
detail: {
cause: event
}
}));
document.dispatchEvent(
new CustomEvent(`visibility${document.visibilityState}`, {
detail: {
cause: event,
},
})
)
}
/**

View File

@@ -3,7 +3,6 @@
// ==========================================================================
.c-form {
}
.c-form_item {
@@ -27,7 +26,7 @@ $input-icon-color: 424242; // No #
.c-form_input {
padding: rem(10px);
border: 1px solid lightgray;
background-color: color(lightest);
background-color: $color-lightest;
&:hover {
border-color: darkgray;
@@ -58,7 +57,8 @@ $checkbox-icon-color: $input-icon-color;
padding-left: ($checkbox + rem(10px));
cursor: pointer;
&::before, &::after {
&::before,
&::after {
position: absolute;
top: 50%;
left: 0;
@@ -67,18 +67,18 @@ $checkbox-icon-color: $input-icon-color;
padding: 0;
width: $checkbox;
height: $checkbox;
content: "";
content: '';
}
&::before {
background-color: color(lightest);
background-color: $color-lightest;
border: 1px solid lightgray;
}
&::after {
border-color: transparent;
background-color: transparent;
background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2210.5%22%20viewBox%3D%220%200%2013%2010.5%22%20enable-background%3D%22new%200%200%2013%2010.5%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23#{$checkbox-icon-color}%22%20d%3D%22M4.8%205.8L2.4%203.3%200%205.7l4.8%204.8L13%202.4c0%200-2.4-2.4-2.4-2.4L4.8%205.8z%22%2F%3E%3C%2Fsvg%3E");
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2210.5%22%20viewBox%3D%220%200%2013%2010.5%22%20enable-background%3D%22new%200%200%2013%2010.5%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23#{$checkbox-icon-color}%22%20d%3D%22M4.8%205.8L2.4%203.3%200%205.7l4.8%204.8L13%202.4c0%200-2.4-2.4-2.4-2.4L4.8%205.8z%22%2F%3E%3C%2Fsvg%3E');
background-position: center;
background-size: rem(12px);
background-repeat: no-repeat;
@@ -118,12 +118,13 @@ $radio-icon-color: $input-icon-color;
.c-form_radioLabel {
@extend .c-form_checkboxLabel;
&::before, &::after {
&::before,
&::after {
border-radius: 50%;
}
&::after {
background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2213%22%20viewBox%3D%220%200%2013%2013%22%20enable-background%3D%22new%200%200%2013%2013%22%20xml%3Aspace%3D%22preserve%22%3E%3Ccircle%20fill%3D%22%23#{$radio-icon-color}%22%20cx%3D%226.5%22%20cy%3D%226.5%22%20r%3D%226.5%22%2F%3E%3C%2Fsvg%3E");
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2213%22%20viewBox%3D%220%200%2013%2013%22%20enable-background%3D%22new%200%200%2013%2013%22%20xml%3Aspace%3D%22preserve%22%3E%3Ccircle%20fill%3D%22%23#{$radio-icon-color}%22%20cx%3D%226.5%22%20cy%3D%226.5%22%20r%3D%226.5%22%2F%3E%3C%2Fsvg%3E');
background-size: rem(6px);
}
}
@@ -149,11 +150,11 @@ $select-icon-color: $input-icon-color;
bottom: 0;
z-index: 2;
width: $select-icon;
background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2211.3%22%20viewBox%3D%220%200%2013%2011.3%22%20enable-background%3D%22new%200%200%2013%2011.3%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23#{$select-icon-color}%22%20points%3D%226.5%2011.3%203.3%205.6%200%200%206.5%200%2013%200%209.8%205.6%20%22%2F%3E%3C%2Fsvg%3E");
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2213%22%20height%3D%2211.3%22%20viewBox%3D%220%200%2013%2011.3%22%20enable-background%3D%22new%200%200%2013%2011.3%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23#{$select-icon-color}%22%20points%3D%226.5%2011.3%203.3%205.6%200%200%206.5%200%2013%200%209.8%205.6%20%22%2F%3E%3C%2Fsvg%3E');
background-position: center;
background-size: rem(8px);
background-repeat: no-repeat;
content: "";
content: '';
pointer-events: none;
}
}

View File

@@ -2,78 +2,31 @@
// 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 {
@include heading;
line-height: $line-height-h;
margin-bottom: rem(30px);
&.-h1 {
@include heading-h1;
font-size: rem($font-size-h1);
}
&.-h2 {
@include heading-h2;
font-size: rem($font-size-h2);
}
&.-h3 {
@include heading-h3;
font-size: rem($font-size-h3);
}
&.-h4 {
@include heading-h4;
font-size: rem($font-size-h4);
}
&.-h5 {
@include heading-h5;
font-size: rem($font-size-h5);
}
&.-h6 {
@include heading-h6;
font-size: rem($font-size-h6);
}
}

View File

@@ -9,14 +9,16 @@
width: 11px;
height: 100vh;
transform-origin: center right;
transition: transform t(normal), opacity t(normal);
transition: transform 0.3s, opacity 0.3s;
opacity: 0;
&:hover {
transform: scaleX(1.45);
}
&:hover, .has-scroll-scrolling &, .has-scroll-dragging & {
&:hover,
.has-scroll-scrolling &,
.has-scroll-dragging & {
opacity: 1;
}
}
@@ -25,7 +27,7 @@
position: absolute;
top: 0;
right: 0;
background-color: color(darkest);
background-color: $color-darkest;
opacity: 0.5;
width: 7px;
border-radius: 10px;

View File

@@ -1,53 +0,0 @@
// ==========================================================================
// 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;
}
}

View File

@@ -1,185 +0,0 @@
// ==========================================================================
// Components / Wysiwyg
// ==========================================================================
.c-wysiwyg {
// ==========================================================================
// Margins
// ==========================================================================
& > * + * {
margin-bottom: size-clamp('md');
}
&-first-element,
>:first-child {
margin-top: 0 !important;
padding-top: 0 !important;
}
&-last-element,
>:last-child {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
// ==========================================================================
// Default
// ==========================================================================
// @include text;
// @include body-large;
// ==========================================================================
// Headings
// ==========================================================================
h1,h2,h3,h4,h5,h6 {
// @include heading;
margin-top: 1.5em;
margin-bottom: 1em;
}
h1 {
// @include heading-h3;
}
h2 {
// @include heading-h3;
}
h3 {
// @include heading-h4;
}
h4 {
// @include heading-h5;
}
h5 {
// @include heading-h6;
}
h6 {
// @include heading-h6;
}
// ==========================================================================
// Lists
// ==========================================================================
ul,
ol {
list-style: none;
padding-left: 0;
> li {
position: relative;
padding-left: 1.5em;
}
}
ul {
> li {
&::before {
content: "";
position: absolute;
left: 0;
top: calc(50% - 3px);
width: 6px;
height: 6px;
background-color: currentColor;
border-radius: 50%;
}
}
}
ol {
counter-reset: counter;
> li {
counter-increment: counter;
&::before {
content: counter(counter);
position: absolute;
left: 0;
}
}
}
// ==========================================================================
// Link
// ==========================================================================
a {
color: $color-link;
&:focus-visible {
color: $color-link-focus;
}
@media (hover: hover) {
&:hover {
color: $color-link-hover;
}
}
}
// ==========================================================================
// Image
// ==========================================================================
img {
width: 100%;
}
// ==========================================================================
// iFrame
// ==========================================================================
iframe {
display: block;
margin-left: auto;
margin-right: auto;
&:not([width]) {
width: 100%;
}
&:not([height]) {
height: auto;
}
}
// ==========================================================================
// Table
// ==========================================================================
table {
width: 100%;
}
caption {
// @include text;
// @include body-small;
margin: 1em 0;
}
// ==========================================================================
// Blockquote
// ==========================================================================
blockquote {
// @include heading;
// @include heading-h3;
quotes: "" "" "" "";
padding: 0;
p {
&::before {
content: open-quote;
}
&::after {
content: close-quote;
}
}
& + p {
padding-top: 0;
}
}
}

View File

@@ -2,4 +2,4 @@
// Critical CSS
// ==========================================================================
$assets-path: "assets/";
$assets-path: 'assets/';

View File

@@ -2,10 +2,24 @@
// Elements / Document
// ==========================================================================
:root {
// Grid
--grid-columns: 4;
--grid-gutter: #{rem(10px)};
--grid-gutter-half: calc(0.5 * var(--grid-gutter));
--grid-margin: 0px;
@media (min-width: $from-small) {
--grid-columns: 12;
--grid-gutter: #{rem(16px)};
--grid-margin: #{rem(20px)};
}
}
//
// Simple page-level setup.
//
// 1. Includes fonts
// 1. Include web fonts
// 2. Ensure the page always fills at least the entire height of the viewport.
// 3. Set the default `font-size` and `line-height` for the entire project,
// sourced from our default variables.
@@ -15,41 +29,73 @@
html {
min-height: 100%; // [2]
line-height: $line-height; // [3]
font-family: ff("sans");
font-family: ff('sans');
color: $font-color;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@media (max-width: $to-sm) {
@media (max-width: $to-small) {
font-size: $font-size - 2px;
}
@media (min-width: $from-sm) and (max-width: $to-lg) {
@media (min-width: $from-small) and (max-width: $to-medium) {
font-size: $font-size - 2px;
}
@media (min-width: $from-medium) and (max-width: $to-large) {
font-size: $font-size - 1px;
}
@media (min-width: $from-lg) and (max-width: $to-2xl) {
font-size: $font-size;
@media (min-width: $from-large) and (max-width: $to-huge) {
font-size: $font-size; // [1]
}
@media (min-width: $from-2xl) and (max-width: $to-3xl) {
@media (min-width: $from-huge) and (max-width: $to-gigantic) {
font-size: $font-size + 1px;
}
@media (min-width: $from-3xl) {
@media (min-width: $from-gigantic) and (max-width: $to-colossal) {
font-size: $font-size + 2px;
}
@media (min-width: $from-colossal) {
font-size: $font-size + 4px;
}
&.is-loading {
cursor: wait;
}
&.has-scroll-smooth {
overflow: hidden;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
&.has-scroll-dragging {
user-select: none;
}
}
body {
.has-scroll-smooth & {
overflow: hidden;
}
}
::selection {
background-color: $color-selection-background;
color: $color-selection-text;
background-color: $selection-background-color;
color: $selection-text-color;
text-shadow: none;
}
a {
color: $color-link;
@include u-hocus {
color: $color-link-hover;
}
}

View File

@@ -1,124 +0,0 @@
// ==========================================================================
// 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;
}
}

View File

@@ -0,0 +1,34 @@
// ==========================================================================
// 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;
}

View File

@@ -0,0 +1,44 @@
// ==========================================================================
// 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;
}

View File

@@ -0,0 +1,99 @@
// ==========================================================================
// 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: $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;
}

View File

@@ -0,0 +1,52 @@
// ==========================================================================
// 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,
// lets 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]
}

View File

@@ -1,72 +1,72 @@
// ==========================================================================
// Main
// ==========================================================================
// Modules
// ==========================================================================
@use "sass:math";
// Tools
// ==========================================================================
@import "tools/maths";
@import "tools/functions";
@import "tools/mixins";
// @import "tools/layout";
// @import "tools/widths";
// @import "tools/family";
@use 'sass:math';
// Settings
// ==========================================================================
@import "settings/config";
@import "settings/config.breakpoints";
@import "settings/config.colors";
@import "settings/config.eases";
@import "settings/config.fonts";
@import "settings/config.spacings";
@import "settings/config.speeds";
@import "settings/config.zindexes";
@import "settings/config.variables";
@import 'settings/config.eases';
@import 'settings/config.colors';
@import 'settings/config';
// Vendors
// ==========================================================================
@import "node_modules/locomotive-scroll/dist/locomotive-scroll";
// Tools
// ==========================================================================
@import 'tools/maths';
@import 'tools/functions';
@import 'tools/mixins';
@import 'tools/fonts';
// @import "tools/layout";
// @import "tools/widths";
// @import "tools/family";
// Generic
// ==========================================================================
@import 'node_modules/normalize.css/normalize';
@import 'generic/generic';
@import 'generic/media';
@import 'generic/form';
@import 'generic/button';
// Elements
// ==========================================================================
@import "elements/normalize";
@import "elements/document";
@import 'elements/document';
// Objects
// ==========================================================================
@import "objects/container";
@import "objects/ratio";
@import "objects/icons";
@import "objects/grid";
@import 'objects/scroll';
@import 'objects/container';
@import 'objects/ratio';
@import 'objects/icons';
@import 'objects/grid';
// @import "objects/layout";
// @import "objects/table";
// Vendors
// ==========================================================================
// @import "vendors/vendor";
// Components
// ==========================================================================
@import "components/heading";
@import "components/text";
@import "components/button";
@import "components/form";
@import "components/wysiwyg";
@import 'components/scrollbar';
@import 'components/heading';
@import 'components/button';
@import 'components/form';
// Utilities
// ==========================================================================
@import "utilities/ratio";
@import "utilities/grid-column";
@import 'utilities/ratio';
@import 'utilities/grid-column';
// @import "utilities/widths";
// @import "utilities/align";
// @import "utilities/helpers";
// @import "utilities/states";
@import "utilities/spacing";
// @import "utilities/spacing";
// @import "utilities/print";

View File

@@ -13,6 +13,6 @@
.o-container {
margin-right: auto;
margin-left: auto;
padding-left: var(--grid-margin);
padding-right: var(--grid-margin);
padding-right: $base-column-gap;
padding-left: $base-column-gap;
}

View File

@@ -31,12 +31,6 @@
// ==========================================================================
// 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);
}
@@ -45,8 +39,8 @@
grid-template-columns: repeat(4, 1fr);
}
&.-col-#{$base-column-nb}\@from-md {
@media (min-width: $from-md) {
&.-col-#{$base-column-nb}\@from-medium {
@media (min-width: $from-medium) {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
}
@@ -57,8 +51,8 @@
// Gutters rows and columns
&.-gutters {
gap: var(--grid-gutter);
column-gap: var(--grid-gutter);
gap: $base-column-gap;
column-gap: $base-column-gap;
}
// ==========================================================================
@@ -169,8 +163,7 @@
// 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);
grid-column: 1 / -1;
&.-align-end {
align-self: end;

View File

@@ -2,7 +2,6 @@
// Objects / SVG Icons
// ==========================================================================
// Markup
//
// 1. If icon is accessible and has a title
@@ -32,7 +31,7 @@
vertical-align: middle;
svg {
--icon-height: calc(var(--icon-width) * math.div(1, (var(--icon-ratio))));
--icon-height: calc(var(--icon-width) * (1 / (var(--icon-ratio))));
display: block;
width: var(--icon-width);
@@ -41,18 +40,16 @@
}
}
// SVG sizes
// ==========================================================================
// // Logo
// .svg-logo {
// --icon-width: #{rem(100px)};
// --icon-ratio: math.div(20, 30); // width/height based on svg viewBox
// --icon-ratio: 20/30; // width/height based on svg viewBox
// // Sizes
// .o-icon.-big & {
// --icon-width: #{rem(200px)};
// }
// }

View File

@@ -68,17 +68,17 @@
}
&.-flex {
display: flex;
display: flex;
&.-top {
&.-top {
align-items: flex-start;
}
&.-middle {
}
&.-middle {
align-items: center;
}
&.-bottom {
}
&.-bottom {
align-items: flex-end;
}
}
}
&.-stretch {
align-items: stretch;

View File

@@ -18,15 +18,15 @@
display: block;
padding-bottom: 100%; // [1]
width: 100%;
content: "";
content: '';
}
}
.o-ratio_content,
.o-ratio > img,
.o-ratio > iframe,
.o-ratio > embed,
.o-ratio > object {
.o-ratio > img,
.o-ratio > iframe,
.o-ratio > embed,
.o-ratio > object {
position: absolute;
top: 0;
bottom: 0;

View File

@@ -0,0 +1,7 @@
// ==========================================================================
// Objects / Scroll
// ==========================================================================
.o-scroll {
min-height: 100vh;
}

View File

@@ -1,92 +0,0 @@
// ==========================================================================
// 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;

View File

@@ -3,57 +3,27 @@
// ==========================================================================
// Palette
// ==========================================================================
// =============================================================================
$colors: (
primary: #3297FD,
lightest: #FFFFFF,
darkest: #000000,
);
$color-lightest: #ffffff;
$color-darkest: #000000;
// Function
// ==========================================================================
// Returns color code.
//
// ```scss
// .c-box {
// color: color(primary);
// }
// ```
//
// @param {string} $key - The color key in $colors.
// @param {number} $alpha - The alpha for the color value.
// @return {color}
@function color($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
// ==========================================================================
// Specific
// =============================================================================
// Link
$color-link: color(primary);
$color-link-focus: color(primary);
$color-link-hover: darken(color(primary), 10%);
$color-link: #1a0dab;
$color-link-focus: #1a0dab;
$color-link-hover: darken(#1a0dab, 10%);
// Selection
$color-selection-text: color(lightest);
$color-selection-background: color(darkest);
$selection-text-color: #3297fd;
$selection-background-color: #ffffff;
// Socials
$color-facebook: #3B5998;
$color-instagram: #E1306C;
$color-youtube: #CD201F;
$color-twitter: #1DA1F2;
// Social Colors
// =============================================================================
$color-facebook: #3b5998;
$color-instagram: #e1306c;
$color-youtube: #cd201f;
$color-twitter: #1da1f2;

View File

@@ -2,77 +2,47 @@
// Settings / Config / Eases
// ==========================================================================
// Eases
// ==========================================================================
// Power 1
$ease-power1-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);
$ease-power1-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
$ease-power1-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);
$eases: (
// Power 1
"power1.in": cubic-bezier(0.550, 0.085, 0.680, 0.530),
"power1.out": cubic-bezier(0.250, 0.460, 0.450, 0.940),
"power1.inOut": cubic-bezier(0.455, 0.030, 0.515, 0.955),
// Power 2
$ease-power2-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);
$ease-power2-out: cubic-bezier(0.215, 0.61, 0.355, 1);
$ease-power2-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);
// Power 2
"power2.in": cubic-bezier(0.550, 0.055, 0.675, 0.190),
"power2.out": cubic-bezier(0.215, 0.610, 0.355, 1.000),
"power2.inOut": cubic-bezier(0.645, 0.045, 0.355, 1.000),
// Power 3
$ease-power3-in: cubic-bezier(0.895, 0.03, 0.685, 0.22);
$ease-power3-out: cubic-bezier(0.165, 0.84, 0.44, 1);
$ease-power3-in-out: cubic-bezier(0.77, 0, 0.175, 1);
// Power 3
"power3.in": cubic-bezier(0.895, 0.030, 0.685, 0.220),
"power3.out": cubic-bezier(0.165, 0.840, 0.440, 1.000),
"power3.inOut": cubic-bezier(0.770, 0.000, 0.175, 1.000),
// Power 3
$ease-power4-in: cubic-bezier(0.755, 0.05, 0.855, 0.06);
$ease-power4-out: cubic-bezier(0.23, 1, 0.32, 1);
$ease-power4-in-out: cubic-bezier(0.86, 0, 0.07, 1);
// 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
$ease-expo-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);
$ease-expo-out: cubic-bezier(0.19, 1, 0.22, 1);
$ease-expo-in-out: cubic-bezier(1, 0, 0, 1);
// 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
$ease-back-in: cubic-bezier(0.6, -0.28, 0.735, 0.045);
$ease-back-out: cubic-bezier(0.175, 00.885, 0.32, 1.275);
$ease-back-in-out: cubic-bezier(0.68, -0.55, 0.265, 1.55);
// 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
$ease-sine-in: cubic-bezier(0.47, 0, 0.745, 0.715);
$ease-sine-out: cubic-bezier(0.39, 0.575, 0.565, 1);
$ease-sine-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);
// 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
$ease-circ-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);
$ease-circ-out: cubic-bezier(0.075, 0.82, 0.165, 1);
$ease-circ-in-out: cubic-bezier(0.785, 0.135, 0.15, 0.86);
// 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);
}
// Misc
$ease-bounce: cubic-bezier(0.17, 0.67, 0.3, 1.33);
$ease-slow-out: cubic-bezier(0.04, 1.15, 0.4, 0.99);
$ease-smooth: cubic-bezier(0.38, 0.005, 0.215, 1);

View File

@@ -6,40 +6,118 @@
// =============================================================================
// The current stylesheet context. Available values: frontend, editor.
$context: frontend !default;
$context: frontend !default;
// Path is relative to the stylesheets directory.
$assets-path: "../" !default;
$assets-path: '../' !default;
// Typefaces
// =============================================================================
// Font directory
$font-dir: '../fonts/';
// 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;
// Map of font families.
//
// ```
// <font-id>: (<font-name>, <font-fallbacks>)
// ```
$font-families: (
sans: join('Source Sans', $font-fallback-sans, $separator: comma),
);
// 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)
);
// Typography
// =============================================================================
// Base
$font-size: 16px;
$line-height: math.div(24px, $font-size);
$font-color: color(darkest);
$font-size: 16px;
$line-height: math.div(24px, $font-size);
$font-color: $color-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
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 700;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 700;
// Transition defaults
// Transitions
// =============================================================================
$speed: t(normal);
$easing: ease("power2.out");
$speed: 0.3s;
$easing: $ease-power2-out;
// Spacing Units
// =============================================================================
$unit: 60px;
$unit-small: 20px;
$vw-viewport: 1440;
$unit: 60px;
$unit-small: 20px;
// Container
// ==========================================================================
$padding: $unit;
$padding: $unit;
// Grid
// ==========================================================================
$base-column-nb: 12;
$base-column-gap: $unit-small;
// 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;
// Master z-indexe
// =============================================================================
$z-indexes: (
'header': 200,
'above': 1,
'below': -1,
);

View File

@@ -1,69 +0,0 @@
// ==========================================================================
// 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});
}

View File

@@ -1,38 +0,0 @@
// ==========================================================================
// 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);
}

View File

@@ -1,20 +0,0 @@
// ==========================================================================
// 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)};
}
}

View File

@@ -1,44 +0,0 @@
// ==========================================================================
// 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;
}

View File

@@ -147,7 +147,7 @@
// @param {number} $num - id of the child
@mixin middle($num) {
&:nth-child(#{round(math.div($num, 2))}) {
&:nth-child(#{round($num / 2)}) {
@content;
}
}
@@ -229,7 +229,8 @@
$child: nth(nth($selector, -1), -1);
&:nth-last-child(n + #{$min}):nth-last-child(-n + #{$max}):first-child,
&:nth-last-child(n + #{$min}):nth-last-child(-n + #{$max}):first-child ~ #{$child} {
&:nth-last-child(n + #{$min}):nth-last-child(-n + #{$max}):first-child
~ #{$child} {
@content;
}
}
@@ -240,7 +241,7 @@
@mixin first-child() {
&:first-of-type {
@content
@content;
}
}
@@ -250,7 +251,7 @@
@mixin last-child() {
&:last-of-type {
@content
@content;
}
}

View File

@@ -1,42 +1,5 @@
// ==========================================================================
// 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
// Tools / Font Faces
// ==========================================================================
// Imports the custom font.
@@ -52,8 +15,8 @@ $font-dir: "../fonts/";
@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");
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)};
}
@@ -70,7 +33,7 @@ $font-dir: "../fonts/";
@mixin font-faces($webfonts, $dir) {
@if (length($webfonts) > 0) {
@if (type-of(nth($webfonts, 1)) == "list") {
@if (type-of(nth($webfonts, 1)) == 'list') {
@each $webfont in $webfonts {
@include font-face($webfont, $dir);
}

View File

@@ -8,7 +8,7 @@
// @return {Boolean}
@function is-pixel-number($number) {
@return type-of($number) == number and unit($number) == "px";
@return type-of($number) == number and unit($number) == 'px';
}
// Converts the given pixel value to its EM quivalent.
@@ -36,7 +36,6 @@
// @return {Number} Scalable pixel value in REMs.
@function rem($size, $base: $font-size) {
@if not is-pixel-number($size) {
@error "`#{$size}` needs to be a number in pixel.";
}
@@ -48,6 +47,42 @@
@return math.div($size, $base) * 1rem;
}
// 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, $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;
}
// Converts a number to a percentage.
//
// @alias percentage()
// @link http://sassdoc.com/annotations/#alias
// @param {Number} $number - The value to convert.
// @return {Number} A percentage.
@function span($number) {
@return percentage($number);
}
// Checks if a list contains a value(s).
//
// @link https://github.com/thoughtbot/bourbon/blob/master/core/bourbon/validators/_contains.scss
@@ -56,12 +91,9 @@
// @return {Boolean}
// @access private
@function list-contains(
$list,
$values...
) {
@function list-contains($list, $values...) {
@each $value in $values {
@if type-of(index($list, $value)) != "number" {
@if type-of(index($list, $value)) != 'number' {
@return false;
}
}
@@ -103,112 +135,3 @@
}
$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);
}

View File

@@ -2,18 +2,13 @@
// Tools / Maths
// ==========================================================================
// Remove the unit of a length
// Removes the unit from the given number.
//
// @param {Number} $number Number to remove unit from
// @return {function<number>}
@function strip-unit($value) {
@if type-of($value) != "number" {
@error "Invalid `#{type-of($value)}` type. Choose a number type instead.";
} @else if type-of($value) == "number" and not is-unitless($value) {
@return math.div($value, $value * 0 + 1);
}
// @param {number} $number The number to strip.
// @return {number}
@return $value;
@function strip-units($number) {
@return $number / ($number * 0 + 1);
}
// Returns the square root of the given number.
@@ -26,7 +21,7 @@
$value: $x;
@for $i from 1 through 10 {
$value: $x - math.div(($x * $x - abs($number)), (2 * $x));
$value: $x - ($x * $x - abs($number)) / (2 * $x);
$x: $value;
}
@@ -48,7 +43,7 @@
}
} @else if $exp < 0 {
@for $i from 1 through -$exp {
$value: math.div($value, $number);
$value: $value / $number;
}
}
@@ -93,7 +88,7 @@
// If the angle has `deg` as unit, convert to radians.
@if ($unit == deg) {
@return math.div($angle, 180) * pi();
@return $angle / 180 * pi();
}
@return $angle;
@@ -109,7 +104,7 @@
$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));
$sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
}
@return $sin;
@@ -125,7 +120,7 @@
$angle: rad($angle);
@for $i from 0 through 10 {
$cos: $cos + pow(-1, $i) * math.div(pow($angle, 2 * $i), fact(2 * $i));
$cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 * $i);
}
@return $cos;
@@ -137,5 +132,5 @@
// @return {number}
@function tan($angle) {
@return math.div(sin($angle), cos($angle));
@return sin($angle) / cos($angle);
}

View File

@@ -34,7 +34,7 @@
&::after {
display: if(list-contains($supports, table), table, block);
clear: both;
content: if(list-contains($supports, opera), " ", "");
content: if(list-contains($supports, opera), ' ', '');
}
}
@@ -50,14 +50,21 @@
$important: important($important);
font-size: rem($font-size) $important;
@if ($line-height == "auto") {
line-height: ceil(math.div($font-size, $line-height)) * math.div($line-height, $font-size) $important;
}
@else {
@if (type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") {
@if ($line-height == 'auto') {
line-height: ceil($font-size / $line-height) *
($line-height / $font-size)
$important;
} @else {
@if (
type-of($line-height) ==
number or
$line-height ==
'inherit' or
$line-height ==
'normal'
) {
line-height: $line-height $important;
}
@else if ($line-height != "none" and $line-height != false) {
} @else if ($line-height != 'none' and $line-height != false) {
@error "Doh! `#{$line-height}` is not a valid value for `$line-height`.";
}
}
@@ -77,7 +84,7 @@
&::before {
display: inline-block;
height: 100%;
content: "";
content: '';
vertical-align: middle;
}
@@ -128,7 +135,7 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal; // [2]
word-wrap: normal; // [2]
@if $width {
max-width: $width; // [1]
}
@@ -176,7 +183,7 @@
@mixin u-hidden($important: true) {
$important: important($important);
display: none $important;
display: none $important;
visibility: hidden $important;
}
@@ -191,34 +198,5 @@
@mixin u-shown($display: block, $important: true) {
$important: important($important);
display: $display $important;
visibility: visible $important;
}
// Aspect-ratio polyfill
//
// @param {Number} $ratio [19/6] - The ratio of the element.
// @param {Number} $width [100%] - The fallback width of element.
// @param {Boolean} $children [false] - Whether the element contains children for the fallback properties.
// @output Properties for maintaining aspect-ratio
@mixin aspect-ratio($ratio: math.div(16, 9), $width: 100%, $children: false) {
@supports (aspect-ratio: 1) {
aspect-ratio: $ratio;
}
@supports not (aspect-ratio: 1) {
height: 0;
padding-top: calc(#{$width} * #{math.div(1, $ratio)});
@if ($children == true) {
position: relative;
> * {
position: absolute;
top: 0;
left: 0;
}
}
}
visibility: visible $important;
}

View File

@@ -58,7 +58,7 @@ $breakpoint-delimiter: \@ !default;
@for $numerator from 1 through $denominator {
// Build a class in the format `.u-3/4[@<breakpoint>]`.
.u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
width: math.div($numerator, $denominator) * 100% $important;
width: ($numerator / $denominator) * 100% $important;
}
@if ($widths-offsets == true) {
@@ -66,13 +66,13 @@ $breakpoint-delimiter: \@ !default;
.u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
position: relative $important;
right: auto $important;
left: math.div($numerator, $denominator) * 100% $important;
left: ($numerator / $denominator) * 100% $important;
}
// Build a class in the format `.u-pull-5/6[@<breakpoint>]`.
.u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
position: relative $important;
right: math.div($numerator, $denominator) * 100% $important;
right: ($numerator / $denominator) * 100% $important;
left: auto $important;
}
}

View File

@@ -11,31 +11,51 @@
$colsMax: $base-column-nb + 1;
@each $breakpoint, $mediaquery in $breakpoints {
$breakpoints: (
'null' null,
'from-tiny' 'from-tiny',
'from-small' 'from-small',
'from-medium' 'from-medium',
'from-large' 'from-large',
'from-big' 'from-big'
) !default;
@each $breakpoint-namespace, $breakpoint in $breakpoints {
@for $fromIndex from 1 through $colsMax {
@for $toIndex from 1 through $colsMax {
// Columns without media query
@if $breakpoint == "tiny" {
@if $breakpoint == null {
.u-gc-#{$fromIndex}\/#{$toIndex} {
--gc-start: #{$fromIndex};
--gc-end: #{$toIndex};
grid-column-start: #{$fromIndex};
grid-column-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};
} @else {
.u-gc-#{$fromIndex}\/#{$toIndex}\@#{$breakpoint} {
@if $breakpoint-namespace == 'from-tiny' {
@media (min-width: $from-tiny) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-small' {
@media (min-width: $from-small) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-medium' {
@media (min-width: $from-medium) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-large' {
@media (min-width: $from-large) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-big' {
@media (min-width: $from-big) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
}
}
}
}

View File

@@ -19,13 +19,13 @@
// Visibility / Display
// ==========================================================================
[hidden][aria-hidden="false"] {
[hidden][aria-hidden='false'] {
position: absolute;
display: inherit;
clip: rect(0, 0, 0, 0);
}
[hidden][aria-hidden="false"]:focus {
[hidden][aria-hidden='false']:focus {
clip: auto;
}

View File

@@ -11,7 +11,6 @@
////
@media print {
// 1. Black prints faster: http://www.sanbeiji.com/archives/953
*,
@@ -31,19 +30,19 @@
}
a[href]:after {
content: " (" attr(href) ")";
content: ' (' attr(href) ')';
}
abbr[title]:after {
content: " (" attr(title) ")";
content: ' (' attr(title) ')';
}
// Don't show links that are fragment identifiers, or use the `javascript:`
// pseudo protocol.
a[href^="#"]:after,
a[href^="javascript:"]:after {
content: "";
a[href^='#']:after,
a[href^='javascript:']:after {
content: '';
}
pre,
@@ -63,7 +62,6 @@
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}

View File

@@ -6,9 +6,15 @@
// A list of aspect ratios that get generated as modifier classes.
$aspect-ratios: (
(2:1),
(4:3),
(16:9),
(
2: 1,
),
(
4: 3,
),
(
16: 9,
)
) !default;
/* stylelint-disable */
@@ -21,11 +27,11 @@ $aspect-ratios: (
@each $ratio in $aspect-ratios {
@each $antecedent, $consequent in $ratio {
@if (type-of($antecedent) != number) {
@error "`#{$antecedent}` needs to be a number."
@error "`#{$antecedent}` needs to be a number.";
}
@if (type-of($consequent) != number) {
@error "`#{$consequent}` needs to be a number."
@error "`#{$consequent}` needs to be a number.";
}
.u-#{$antecedent}\:#{$consequent}::before {

View File

@@ -8,11 +8,12 @@
///
/// @example
/// .u-margin-top {}
/// .u-margin-top-xs {}
/// .u-padding-left-lg {}
/// .u-margin-right-sm {}
/// .u-padding-left-large {}
/// .u-margin-right-small {}
/// .u-padding {}
/// .u-padding-right-none {}
/// .u-padding-horizontal {}
/// .u-padding-vertical-small {}
///
/// @link https://github.com/inuitcss/inuitcss/blob/512977a/utilities/_utilities.spacing.scss
////
@@ -20,61 +21,33 @@
/* stylelint-disable string-quotes */
$spacing-directions: (
null: null,
'-top': '-top',
'-right': '-right',
'-bottom': '-bottom',
'-left': '-left',
'-x': '-left' '-right',
'-y': '-top' '-bottom',
null: null,
'-top': '-top',
'-right': '-right',
'-bottom': '-bottom',
'-left': '-left',
'-horizontal': '-left' '-right',
'-vertical': '-top' '-bottom',
) !default;
$spacing-properties: (
'padding': 'padding',
'margin': 'margin',
'margin': 'margin',
) !default;
$spacing-sizes: join($spacings, (
null: var(--grid-gutter),
'none': 0
));
$spacing-sizes: (
null: $unit,
'-double': $unit * 2,
'-small': $unit-small,
'-none': 0px,
) !default;
@each $breakpoint, $mediaquery in $breakpoints {
@each $property-namespace, $property in $spacing-properties {
@each $direction-namespace, $directions in $spacing-directions {
@each $size-namespace, $size in $spacing-sizes {
// Prepend "-" to spacing sizes if not null
$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;
}
}
@each $property-namespace, $property in $spacing-properties {
@each $direction-namespace, $direction-rules in $spacing-directions {
@each $size-namespace, $size in $spacing-sizes {
.u-#{$property-namespace}#{$direction-namespace}#{$size-namespace} {
@each $direction in $direction-rules {
#{$property}#{$direction}: rem($size) !important;
}
}
}

View File

@@ -4,7 +4,7 @@
// ARIA roles display visual cursor hints
[aria-busy="true"] {
[aria-busy='true'] {
cursor: progress;
}
@@ -46,14 +46,14 @@
}
}
// .is-hidden\@to-lg {
// @media (max-width: $to-lg) {
// .is-hidden\@to-large {
// @media (max-width: $to-large) {
// display: none;
// }
// }
//
// .is-hidden\@from-lg {
// @media (min-width: $from-lg) {
// .is-hidden\@from-large {
// @media (min-width: $from-large) {
// display: none;
// }
// }

View File

@@ -21,8 +21,8 @@ $widths-fractions: 1 2 3 4 5 !default;
@include widths($widths-fractions);
.u-1\/2\@from-sm {
@media (min-width: $from-sm) {
width: 50%;
.u-1\/2\@from-small {
@media (min-width: $from-small) {
width: span(1/2);
}
}

View File

@@ -1,25 +0,0 @@
/**
* @file Provides simple user configuration options.
*/
import loconfig from '../../loconfig.json' assert { type: 'json' };
import { merge } from '../utils/index.js';
let usrconfig;
try {
usrconfig = await import('../../loconfig.local.json', {
assert: { type: 'json' },
});
usrconfig = usrconfig.default;
merge(loconfig, usrconfig);
} catch (err) {
// do nothing
}
export default loconfig;
export {
loconfig,
};

View File

@@ -1,162 +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} [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,
};

View File

@@ -1,139 +0,0 @@
/**
* @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,
};

View File

@@ -1,9 +1,8 @@
import loconfig from '../helpers/config.js';
import glob, { supportsGlob } from '../helpers/glob.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import loconfig from '../utils/config.js';
import glob from '../utils/glob.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import concat from 'concat';
import {
basename,
@@ -65,7 +64,7 @@ export const productionConcatFilesArgs = [
* @return {Promise}
*/
export default async function concatFiles(globOptions = null, concatOptions = null) {
if (supportsGlob) {
if (glob) {
if (globOptions == null) {
globOptions = productionGlobOptions;
} else if (
@@ -73,7 +72,7 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
globOptions !== developmentGlobOptions &&
globOptions !== productionGlobOptions
) {
globOptions = merge({}, defaultGlobOptions, globOptions);
globOptions = Object.assign({}, defaultGlobOptions, globOptions);
}
}
@@ -83,19 +82,10 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
concatOptions !== developmentConcatOptions &&
concatOptions !== productionConcatOptions
) {
concatOptions = merge({}, defaultConcatOptions, concatOptions);
concatOptions = Object.assign({}, defaultConcatOptions, concatOptions);
}
/**
* @async
* @param {object} entry - The entrypoint to process.
* @param {string[]} entry.includes - One or more paths to process.
* @param {string} entry.outfile - The file to write to.
* @param {?string} [entry.label] - The task label.
* Defaults to the outfile name.
* @return {Promise}
*/
loconfig.tasks.concats?.forEach(async ({
loconfig.tasks.concats.forEach(async ({
includes,
outfile,
label = null
@@ -108,25 +98,25 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
console.time(timeLabel);
try {
if (!Array.isArray(includes)) {
includes = [ includes ];
}
includes = resolve(includes);
outfile = resolve(outfile);
if (supportsGlob && globOptions) {
includes = await glob(includes, globOptions);
let files;
if (glob && globOptions) {
files = await glob(includes, globOptions);
} else {
files = includes;
}
if (concatOptions.removeDuplicates) {
includes = includes.map((path) => normalize(path));
includes = [ ...new Set(includes) ];
files = files.map((path) => normalize(path));
files = [ ...new Set(files) ];
}
await concat(includes, outfile);
await concat(files, outfile);
if (includes.length) {
if (files.length) {
message(`${label} concatenated`, 'success', timeLabel);
} else {
message(`${label} is empty`, 'notice', timeLabel);

View File

@@ -1,8 +1,7 @@
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import esbuild from 'esbuild';
import { basename } from 'node:path';
@@ -51,21 +50,10 @@ export default async function compileScripts(esBuildOptions = null) {
esBuildOptions !== developmentESBuildOptions &&
esBuildOptions !== productionESBuildOptions
) {
esBuildOptions = merge({}, defaultESBuildOptions, esBuildOptions);
esBuildOptions = Object.assign({}, defaultESBuildOptions, esBuildOptions);
}
/**
* @async
* @param {object} entry - The entrypoint to process.
* @param {string[]} entry.includes - One or more paths to process.
* @param {string} [entry.outdir] - The directory to write to.
* @param {string} [entry.outfile] - The file to write to.
* @param {?string} [entry.label] - The task label.
* Defaults to the outdir or outfile name.
* @throws {TypeError} If outdir and outfile are missing.
* @return {Promise}
*/
loconfig.tasks.scripts?.forEach(async ({
loconfig.tasks.scripts.forEach(async ({
includes,
outdir = '',
outfile = '',
@@ -79,10 +67,6 @@ export default async function compileScripts(esBuildOptions = null) {
console.time(timeLabel);
try {
if (!Array.isArray(includes)) {
includes = [ includes ];
}
includes = resolve(includes);
if (outdir) {

View File

@@ -1,17 +1,12 @@
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import {
createProcessor,
pluginsMap as postcssPluginsMap,
supportsPostCSS
} from '../helpers/postcss.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import postcss, { pluginsMap as postcssPluginsMap } from '../utils/postcss.js';
import resolve from '../utils/template.js';
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import * as sass from 'sass';
import sass from 'sass';
import { PurgeCSS } from 'purgecss';
const sassRender = promisify(sass.render);
@@ -60,12 +55,10 @@ export const productionPostCSSOptions = Object.assign({}, defaultPostCSSOptions
export const developmentStylesArgs = [
developmentSassOptions,
developmentPostCSSOptions,
false
];
export const productionStylesArgs = [
productionSassOptions,
productionPostCSSOptions,
true
];
/**
@@ -82,17 +75,17 @@ export const productionStylesArgs = [
* If `false`, PostCSS processing will be ignored.
* @return {Promise}
*/
export default async function compileStyles(sassOptions = null, postcssOptions = null, purge = true) {
export default async function compileStyles(sassOptions = null, postcssOptions = null) {
if (sassOptions == null) {
sassOptions = productionSassOptions;
} else if (
sassOptions !== developmentSassOptions &&
sassOptions !== productionSassOptions
) {
sassOptions = merge({}, defaultSassOptions, sassOptions);
sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
}
if (supportsPostCSS) {
if (postcss) {
if (postcssOptions == null) {
postcssOptions = productionPostCSSOptions;
} else if (
@@ -100,20 +93,11 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
postcssOptions !== developmentPostCSSOptions &&
postcssOptions !== productionPostCSSOptions
) {
postcssOptions = merge({}, defaultPostCSSOptions, postcssOptions);
postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions);
}
}
/**
* @async
* @param {object} entry - The entrypoint to process.
* @param {string[]} entry.infile - The file to process.
* @param {string} entry.outfile - The file to write to.
* @param {?string} [entry.label] - The task label.
* Defaults to the outfile name.
* @return {Promise}
*/
loconfig.tasks.styles?.forEach(async ({
loconfig.tasks.styles.forEach(async ({
infile,
outfile,
label = null
@@ -132,9 +116,9 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
outFile: outfile,
}));
if (supportsPostCSS && postcssOptions) {
if (postcss && postcssOptions) {
if (typeof postcssProcessor === 'undefined') {
postcssProcessor = createProcessor(
postcssProcessor = createPostCSSProcessor(
postcssPluginsMap,
postcssOptions
);
@@ -162,7 +146,7 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
try {
await writeFile(outfile, result.css).then(() => {
// Purge CSS once file exists.
if (outfile && purge) {
if (outfile) {
purgeUnusedCSS(outfile, `${label || `${filestem}.css`}`);
}
});
@@ -207,6 +191,35 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
});
};
/**
* Creates a PostCSS Processor with the given plugins and options.
*
* @param {array<(function|object)>|object<string, (function|object)>} pluginsListOrMap -
* A list or map of plugins.
* If a map of plugins, the plugin name looks up `options`.
* @param {object} options - The PostCSS options.
*/
function createPostCSSProcessor(pluginsListOrMap, options)
{
let plugins;
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);
}
/**
* Purge unused styles from CSS files.
*
@@ -218,30 +231,23 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
* @return {Promise}
*/
async function purgeUnusedCSS(outfile, label) {
const contentFiles = loconfig.tasks.purgeCSS?.content;
if (!Array.isArray(contentFiles) || !contentFiles.length) {
return;
}
label = label ?? basename(outfile);
const timeLabel = `${label} purged in`;
console.time(timeLabel);
const purgeCSSResults = await (new PurgeCSS()).purge({
content: contentFiles,
const purgeCSSContentFiles = Array.from(loconfig.tasks.purgeCSS.content);
const purgeCSSResults = await new PurgeCSS().purge({
content: purgeCSSContentFiles,
css: [ outfile ],
rejected: true,
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,
standard: [ /^((?!\bu-gc-).)*$/ ]
}
})
for (let result of purgeCSSResults) {
for(let result of purgeCSSResults) {
await writeFile(outfile, result.css)
message(`${label} purged`, 'chore', timeLabel);

View File

@@ -1,8 +1,7 @@
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import { basename } from 'node:path';
import mixer from 'svg-mixer';
@@ -45,19 +44,10 @@ export default async function compileSVGs(mixerOptions = null) {
mixerOptions !== developmentMixerOptions &&
mixerOptions !== productionMixerOptions
) {
mixerOptions = merge({}, defaultMixerOptions, mixerOptions);
mixerOptions = Object.assign({}, defaultMixerOptions, mixerOptions);
}
/**
* @async
* @param {object} entry - The entrypoint to process.
* @param {string[]} entry.includes - One or more paths to process.
* @param {string} entry.outfile - The file to write to.
* @param {?string} [entry.label] - The task label.
* Defaults to the outfile name.
* @return {Promise}
*/
loconfig.tasks.svgs?.forEach(async ({
loconfig.tasks.svgs.forEach(async ({
includes,
outfile,
label = null
@@ -70,10 +60,6 @@ export default async function compileSVGs(mixerOptions = null) {
console.time(timeLabel);
try {
if (!Array.isArray(includes)) {
includes = [ includes ];
}
includes = resolve(includes);
outfile = resolve(outfile);

View File

@@ -1,7 +1,6 @@
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 loconfig from '../utils/config.js';
import message from '../utils/message.js';
import resolve from '../utils/template.js';
import { randomBytes } from 'node:crypto';
import events from 'node:events';
import {
@@ -21,8 +20,6 @@ import {
} 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
@@ -97,23 +94,12 @@ export default async function bumpVersions(versionOptions = null) {
versionOptions !== developmentVersionOptions &&
versionOptions !== productionVersionOptions
) {
versionOptions = merge({}, defaultVersionOptions, versionOptions);
versionOptions = Object.assign({}, 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(({
loconfig.tasks.versions.forEach(({
outfile,
label = null,
...options
@@ -137,64 +123,32 @@ export default async function bumpVersions(versionOptions = null) {
/**
* Creates a formatted version number or string.
*
* @param {string} format - The version format.
* @param {?string} [oldValue] - The old version value.
* @param {string} format - The version format.
* @return {string|number}
* @throws TypeError If the format or value are invalid.
*/
function createVersionNumber(format, oldValue = null) {
function createVersionNumber(format) {
let [ type, modifier ] = format.split(':', 2);
switch (type) {
case 'hex':
case 'hexadecimal':
try {
modifier = Number.parseInt(modifier);
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 }
);
if (Number.isNaN(modifier)) {
modifier = 6;
}
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 }
);
}
return randomBytes(modifier).toString('hex');
case 'regex':
case 'regexp':
try {
return new RegExp(modifier);
} catch (err) {
throw new TypeError(
`${err.message} for \'format\' type "regexp"`,
{ cause: err }
);
}
return new RegExp(modifier);
case 'timestamp':
return Date.now().valueOf();
}
throw new TypeError(
'Expected \'format\' to be either "timestamp", "increment", or "hexadecimal"'
'Expected \'format\' to be either "timestamp" or "hexadecimal"'
);
}
@@ -255,7 +209,9 @@ async function handleBumpVersionInJson(outfile, label, options) {
await mkdir(dirname(outfile), { recursive: true });
}
json[options.key] = createVersionNumber(options.format, json?.[options.key]);
const version = createVersionNumber(options.format);
json[options.key] = version;
return await writeFile(
outfile,
@@ -286,7 +242,7 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
input: createReadStream(bckfile),
});
let newVersion = null;
const version = createVersionNumber(options.format);
const writeStream = createWriteStream(outfile, { encoding: 'utf8' });
@@ -294,12 +250,12 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
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);
const groups = (found.groups ?? {});
const replace = found[0].replace(
(groups.build ?? groups.version ?? found[1] ?? found[0]),
version
);
line = line.replace(found[0], replace);
}
writeStream.write(line + "\n");
@@ -317,88 +273,6 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
}
}
/**
* 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.
*

66
build/utils/config.js Normal file
View File

@@ -0,0 +1,66 @@
/**
* @file Provides simple user configuration options.
*/
import loconfig from '../../loconfig.json' assert { type: 'json' };
let usrconfig;
try {
usrconfig = await import('../../loconfig.local.json', {
assert: { type: 'json' }
});
usrconfig = usrconfig.default;
merge(loconfig, usrconfig);
} catch (err) {
console.error(err);
}
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');
}

95
build/utils/glob.js Normal file
View File

@@ -0,0 +1,95 @@
/**
* @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);
});
};
}

View File

@@ -1,115 +0,0 @@
/**
* @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,
};

View File

@@ -11,7 +11,7 @@ import kleur from 'kleur';
* @param {string} [type] - The type of message.
* @param {string} [timerID] - The console time label to output.
*/
function message(text, type, timerID) {
export default function message(text, type, timerID) {
switch (type) {
case 'success':
console.log('✅ ', kleur.bgGreen().black(text));
@@ -52,10 +52,4 @@ function message(text, type, timerID) {
}
console.log('');
}
export default message;
export {
message,
};

View File

@@ -16,7 +16,7 @@ import notifier from 'node-notifier';
* @param {function} callback - The notification callback.
* @return {void}
*/
function notification(options, callback) {
export default function notification(options, callback) {
if (typeof options === 'string') {
options = {
message: options
@@ -42,10 +42,4 @@ function notification(options, callback) {
}
notifier.notify(options, callback);
}
export default notification;
export {
notification,
};

27
build/utils/postcss.js Normal file
View File

@@ -0,0 +1,27 @@
/**
* @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
};

View File

@@ -3,10 +3,6 @@
*/
import loconfig from './config.js';
import {
escapeRegExp,
flatten
} from '../utils/index.js';
const templateData = flatten({
paths: loconfig.paths
@@ -26,7 +22,7 @@ const templateData = flatten({
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
* @return {*} Returns the transformed value.
*/
function resolve(input, data = templateData) {
export default function resolve(input, data = templateData) {
switch (typeof input) {
case 'string': {
return resolveValue(input, data);
@@ -60,7 +56,7 @@ function resolve(input, data = templateData) {
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
* @return {string} Returns the translated string.
*/
function resolveValue(input, data = templateData) {
export function resolveValue(input, data = templateData) {
const tags = [];
if (data !== templateData) {
@@ -97,9 +93,55 @@ 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);
export {
resolve,
resolveValue,
};
if (typeof input[key] === 'object') {
flatten(input[key], field, target);
} else {
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, '\\$&');
}

View File

@@ -2,11 +2,10 @@ import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js';
import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
import loconfig from './helpers/config.js';
import message from './helpers/message.js';
import notification from './helpers/notification.js';
import resolve from './helpers/template.js';
import { merge } from './utils/index.js';
import loconfig, { merge } from './utils/config.js';
import message from './utils/message.js';
import notification from './utils/notification.js';
import resolve from './utils/template.js';
import browserSync from 'browser-sync';
import { join } from 'node:path';
@@ -70,18 +69,16 @@ function configureServer(server, { paths, tasks }) {
});
// Watch source concats
if (tasks.concats?.length) {
server.watch(
resolve(
tasks.concats.reduce(
(patterns, { includes }) => patterns.concat(includes),
[]
)
server.watch(
resolve(
tasks.concats.reduce(
(patterns, { includes }) => patterns.concat(includes),
[]
)
).on('change', () => {
concatFiles(...developmentConcatFilesArgs);
});
}
)
).on('change', () => {
concatFiles(...developmentConcatFilesArgs);
});
// Watch source styles
server.watch(

View File

@@ -338,8 +338,8 @@ See [`svgs.js`](../build/tasks/svgs.js) for details.
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.
Can generate a hexadecimal value (using random bytes) or
use the current timestamp.
Example:
@@ -355,11 +355,6 @@ Example:
"format": "hex:8",
"key": "hex",
"outfile": "./assets.json"
},
{
"format": "inc:semver",
"key": "inc",
"outfile": "./assets.json"
}
]
}
@@ -368,8 +363,7 @@ Example:
```json
{
"now": 1665071717350,
"hex": "6ef54181c4ba",
"hex": "1.0.2"
"hex": "6ef54181c4ba"
}
```

View File

@@ -70,8 +70,8 @@ The first step is to set intial SCSS values in the following files :
grid-template-columns: repeat(4, 1fr);
}
&.-col-#{$base-column-nb}\@from-md {
@media (min-width: $from-md) {
&.-col-#{$base-column-nb}\@from-medium {
@media (min-width: $from-medium) {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
}

View File

@@ -80,10 +80,10 @@ Learn about [namespacing](https://csswizardry.com/2015/03/more-transparent-ui-co
}
.c-block_heading {
@media (max-width: $to-md) {
@media (max-width: $to-medium) {
.c-block.-large & {
margin-bottom: rem(40px);
}
}
}
}
```
@@ -181,7 +181,10 @@ detection and smooth scrolling with parallax.
```js
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].

View File

@@ -15,7 +15,7 @@
"dest": "./www/assets/scripts"
},
"svgs": {
"src": "./assets/svgs",
"src": "./assets/images/sprite",
"dest": "./www/assets/images"
},
"views": {

2445
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,36 +6,36 @@
"author": "Locomotive <info@locomotive.ca>",
"type": "module",
"engines": {
"node": "20.x",
"node": ">=18.13",
"npm": ">=8.0"
},
"scripts": {
"start": "node --experimental-json-modules --no-warnings build/watch.js",
"build": "node --experimental-json-modules --no-warnings build/build.js"
"build": "node --experimental-json-modules --no-warnings build/build.js",
"precommit": "prettier --write 'assets/**/*.{js,scss,json}'"
},
"dependencies": {
"locomotive-scroll": "^5.0.0-beta.11",
"locomotive-scroll": "^4.1.4",
"modujs": "^1.4.2",
"modularload": "^1.2.6",
"normalize.css": "^8.0.1",
"sass": "^1.57.1",
"svg4everybody": "^2.1.9"
},
"devDependencies": {
"autoprefixer": "^10.4.17",
"browser-sync": "^3.0.2",
"autoprefixer": "^10.4.13",
"browser-sync": "^2.27.11",
"concat": "^1.0.3",
"esbuild": "^0.20.0",
"esbuild": "^0.16.17",
"kleur": "^4.1.5",
"node-notifier": "^10.0.1",
"postcss": "^8.4.21",
"prettier": "^2.8.3",
"purgecss": "^5.0.0",
"sass": "^1.70.0",
"svg-mixer": "~2.3.14",
"tiny-glob": "^0.2.9"
},
"overrides": {
"browser-sync": {
"ua-parser-js": "~1.0.33"
},
"svg-mixer": {
"postcss": "^8.4.20"
}

19
www/assets/scripts/app.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"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

View File

@@ -18,8 +18,8 @@
</head>
<body data-module-load>
<div data-load-container>
<div data-module-scroll="main">
<header>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/"><h1>Locomotive Boilerplate</h1></a>
<nav>
<ul>
@@ -30,7 +30,7 @@
</nav>
</header>
<main>
<main data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Page</h1>
@@ -90,7 +90,7 @@
</div>
</main>
<footer>
<footer data-scroll-section>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>
</div>

View File

@@ -34,8 +34,8 @@
<body data-module-load>
<div data-load-container>
<div data-module-scroll="main">
<header>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/">
<h1>Locomotive Boilerplate</h1>
</a>
@@ -48,7 +48,7 @@
</nav>
</header>
<main>
<main data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
@@ -76,7 +76,7 @@
</div>
</main>
<footer>
<footer data-scroll-section>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>

View File

@@ -18,8 +18,8 @@
</head>
<body data-module-load>
<div data-load-container>
<div data-module-scroll="main">
<header>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/"><h1>Locomotive Boilerplate</h1></a>
<nav>
<ul>
@@ -30,7 +30,7 @@
</nav>
</header>
<main>
<main data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Images</h1>
@@ -116,7 +116,7 @@
</div>
</main>
<footer>
<footer data-scroll-section>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>
</div>

View File

@@ -42,8 +42,8 @@
<body data-module-load>
<div data-load-container>
<div data-module-scroll="main">
<header>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/">
<h1>Locomotive Boilerplate</h1>
</a>
@@ -56,13 +56,13 @@
</nav>
</header>
<main data-module-example>
<main data-module-example data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
</div>
</main>
<footer>
<footer data-scroll-section>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>