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
79 changed files with 1669 additions and 6427 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v17.9
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.

View File

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

View File

@@ -1,65 +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,
});
})
window.onload = (event) => {
const $style = document.getElementById('main-css');
const $style = document.getElementById('main-css')
if ($style) {
if ($style.isLoaded) {
init();
init()
} else {
$style.addEventListener('load', (event) => {
init();
});
init()
})
}
} else {
console.warn('The "main-css" stylesheet not found');
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() {
globals();
globals()
app.init(app);
app.init(app)
$html.classList.add(CSS_CLASS.LOADED);
$html.classList.add(CSS_CLASS.READY);
$html.classList.remove(CSS_CLASS.LOADING);
// Bind window resize event with default vars
const resizeEndEvent = new CustomEvent(CUSTOM_EVENT.RESIZE_END)
window.addEventListener('resize', () => {
$html.style.setProperty('--vw', `${document.documentElement.clientWidth * 0.01}px`)
debounce(() => {
window.dispatchEvent(resizeEndEvent)
}, 200, false)
})
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')
if (ENV.IS_DEV) {
console.group('Eager fonts loaded!', eagerFonts.length, '/', document.fonts.size);
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*/))
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*/))
document.fonts.forEach((font) =>
console.log(
font.family,
font.style,
font.weight,
font.status /*, font*/
)
)
console.groupEnd()
}
});
})
}
}

View File

@@ -7,51 +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_DESKTOP = typeof window.orientation === 'undefined'
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_DESKTOP,
IS_MOBILE: !IS_DESKTOP,
// 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',
IMAGE: "c-image",
IMAGE_LAZY_LOADED: "-lazy-loaded",
IMAGE_LAZY_LOADING: "-lazy-loading",
IMAGE_LAZY_ERROR: "-lazy-error",
})
// Custom js events
const CUSTOM_EVENT = Object.freeze({
RESIZE_END: 'loco.resizeEnd',
// ...
})
// Fonts parameters
const FONT = Object.freeze({
EAGER: [
{ family: 'Source Sans', style: 'normal', weight: 400 },
{ family: 'Source Sans', style: 'normal', weight: 700 },
],
})
export {
ENV,
CSS_CLASS,
CUSTOM_EVENT,
FONT,
}

View File

@@ -1,29 +1,23 @@
import svg4everybody from 'svg4everybody';
import { ENV } from './config';
import { triggerLazyloadCallbacks } from './utils/image';
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?.();
/**
* Trigger lazyload
*/
triggerLazyloadCallbacks();
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,35 +1,22 @@
import { module } from 'modujs';
import modularLoad from 'modularload';
import { resetLazyloadCallbacks, triggerLazyloadCallbacks } from "../utils/image";
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() {
this.load = new modularLoad({
const load = new modularLoad({
enterDelay: 0,
transitions: {
customTransition: {}
}
});
customTransition: {},
},
})
this.load.on('loaded', (transition, oldContainer, newContainer) => {
this.call('destroy', oldContainer, 'app');
this.call('update', newContainer, 'app');
/**
* Trigger lazyload
*/
triggerLazyloadCallbacks();
});
this.load.on("loading", () => {
/**
* Remove previous lazyload callbacks
*/
resetLazyloadCallbacks();
});
load.on('loaded', (transition, oldContainer, newContainer) => {
this.call('destroy', oldContainer, 'app')
this.call('update', newContainer, 'app')
})
}
}

View File

@@ -1,53 +1,52 @@
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])
})
scrollTo(params) {
let { target, ...options } = params
options = Object.assign({
// Defaults
duration: 1,
}, options)
this.scroll?.scrollTo(target, options)
this.scroll.on('scroll', (args) => {
// console.log(args.scroll);
})
}
/**
* Observe new scroll elements
*
* @param $newContainer (HTMLElement)
*/
addScrollElements($newContainer) {
this.scroll?.addScrollElements($newContainer)
}
/**
* Unobserve scroll elements
*
* @param $oldContainer (HTMLElement)
*/
removeScrollElements($oldContainer) {
this.scroll?.removeScrollElements($oldContainer)
* Lazy load the related image.
*
* @see ../utils/image.js
*
* It is recommended to wrap your `<img>` into an element with the
* CSS class name `.c-lazy`. The CSS class name modifier `.-lazy-loaded`
* will be applied on both the image and the parent wrapper.
*
* ```html
* <div class="c-lazy o-ratio u-4:3">
* <img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
* </div>
* ```
*
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
*/
lazyLoad(args) {
lazyLoadImage(args.obj.el, null, () => {
//callback
})
}
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

@@ -9,13 +9,13 @@ const escapeHtml = (str) =>
/[&<>'"]/g,
(tag) =>
({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"'": "&#39;",
'"': "&quot;",
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
}[tag])
);
)
/**
* Unescape HTML string
@@ -25,11 +25,11 @@ const escapeHtml = (str) =>
const unescapeHtml = (str) =>
str
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&#39;", "'")
.replace("&quot;", '"');
.replace('&amp;', '&')
.replace('&lt;', '<')
.replace('&gt;', '>')
.replace('&#39;', "'")
.replace('&quot;', '"')
/**
* Get element data attributes
@@ -39,39 +39,39 @@ const unescapeHtml = (str) =>
const getNodeData = (node) => {
// All attributes
const attributes = node.attributes;
const attributes = node.attributes
// Regex Pattern
const pattern = /^data\-(.+)$/;
const pattern = /^data\-(.+)$/
// Output
const data = {};
const data = {}
for (let i in attributes) {
if (!attributes[i]) {
continue;
continue
}
// Attributes name (ex: data-module)
let name = attributes[i].name;
let name = attributes[i].name
// This happens.
if (!name) {
continue;
continue
}
let match = name.match(pattern);
let match = name.match(pattern)
if (!match) {
continue;
continue
}
// If this throws an error, you have some
// serious problems in your HTML.
data[match[1]] = getData(node.getAttribute(name));
data[match[1]] = getData(node.getAttribute(name))
}
return data;
};
return data
}
/**
* Parse value to data type.
@@ -81,31 +81,31 @@ const getNodeData = (node) => {
* @return {mixed} value in its natural data type
*/
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
const getData = (data) => {
if (data === "true") {
return true;
if (data === 'true') {
return true
}
if (data === "false") {
return false;
if (data === 'false') {
return false
}
if (data === "null") {
return null;
if (data === 'null') {
return null
}
// Only convert to a number if it doesn't change the string
if (data === +data + "") {
return +data;
if (data === +data + '') {
return +data
}
if (rbrace.test(data)) {
return JSON.parse(data);
return JSON.parse(data)
}
return data;
};
return data
}
/**
* Returns an array containing all the parent nodes of the given node
@@ -115,49 +115,15 @@ const getData = (data) => {
const getParents = ($el) => {
// Set up a parent array
let parents = [];
let parents = []
// Push each parent element to the array
for (; $el && $el !== document; $el = $el.parentNode) {
parents.push($el);
parents.push($el)
}
// Return our parent array
return parents;
};
return parents
}
// https://gomakethings.com/how-to-get-the-closest-parent-element-with-a-matching-selector-using-vanilla-javascript/
const queryClosestParent = ($el, selector) => {
// Element.matches() polyfill
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function (s) {
var matches = (
this.document || this.ownerDocument
).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// Get the closest matching element
for (; $el && $el !== document; $el = $el.parentNode) {
if ($el.matches(selector)) return $el;
}
return null;
};
export {
escapeHtml,
unescapeHtml,
getNodeData,
getData,
getParents,
queryClosestParent,
};
export { escapeHtml, unescapeHtml, getNodeData, getData, getParents }

View File

@@ -1,6 +1,3 @@
import { CSS_CLASS } from '../config'
import { queryClosestParent } from './html'
/**
* Get an image meta data
*
@@ -8,14 +5,13 @@ import { queryClosestParent } from './html'
* @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.
*
@@ -39,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) => {
@@ -54,7 +52,6 @@ const loadImage = (url, options = {}) => {
})
}
/**
* Lazy load the given image.
*
@@ -69,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)
@@ -81,7 +78,7 @@ const lazyLoadImage = async ($el, url, callback) => {
LAZY_LOADED_IMAGES.push(loadedImage)
}
if($el.src === src) {
if ($el.src === src) {
return
}
@@ -92,111 +89,17 @@ const lazyLoadImage = async ($el, url, callback) => {
}
requestAnimationFrame(() => {
let lazyParent = $el.closest(`.${CSS_CLASS.IMAGE}`)
let lazyParent = $el.closest('.c-lazy')
if(lazyParent) {
lazyParent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
if (lazyParent) {
lazyParent.classList.add('-lazy-loaded')
lazyParent.style.backgroundImage = ''
}
$el.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
$el.classList.add('-lazy-loaded')
callback?.()
})
}
/**
* Lazyload Callbacks
*
*/
const lazyImageLoad = (e) => {
const $img = e.currentTarget;
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
requestAnimationFrame(() => {
if ($parent) {
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
}
$img.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
});
};
const lazyImageError = (e) => {
const $img = e.currentTarget;
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
requestAnimationFrame(() => {
if ($parent) {
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_ERROR);
}
});
};
/* Trigger Lazyload Callbacks */
const triggerLazyloadCallbacks = ($lazyImagesArgs) => {
const $lazyImages = $lazyImagesArgs
? $lazyImagesArgs
: document.querySelectorAll('[loading="lazy"]');
if ("loading" in HTMLImageElement.prototype) {
for (const $img of $lazyImages) {
const $parent = queryClosestParent(
$img,
`.${CSS_CLASS.IMAGE}`
);
if (!$img.complete) {
if($parent) {
$parent.classList.add(
CSS_CLASS.IMAGE_LAZY_LOADING
);
}
$img.addEventListener("load", lazyImageLoad, { once: true });
$img.addEventListener("error", lazyImageError, { once: true });
} else {
if (!$img.complete) {
$parent.classList.add(
CSS_CLASS.IMAGE_LAZY_LOADED
);
}
}
}
} else {
// if 'loading' supported
for (const $img of $lazyImages) {
const $parent = queryClosestParent(
$img,
`.${CSS_CLASS.IMAGE}`
);
if($parent) {
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
}
}
}
};
/* Reset Lazyload Callbacks */
const resetLazyloadCallbacks = () => {
if ("loading" in HTMLImageElement.prototype) {
const $lazyImages = document.querySelectorAll('[loading="lazy"]');
for (const $img of $lazyImages) {
$img.removeEventListener("load", lazyImageLoad, { once: true });
$img.removeEventListener("error", lazyImageError, { once: true });
}
}
};
export {
getImageMetadata,
loadImage,
lazyLoadImage,
triggerLazyloadCallbacks,
resetLazyloadCallbacks
}
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((v - x)/(a - 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((v - x)/(a - 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

@@ -3,29 +3,30 @@
// ==========================================================================
.c-heading {
line-height: $line-height-h;
margin-bottom: rem(30px);
&.-h1 {
font-size: var(--font-size-h1);
font-size: rem($font-size-h1);
}
&.-h2 {
font-size: var(--font-size-h2);
font-size: rem($font-size-h2);
}
&.-h3 {
font-size: var(--font-size-h3);
font-size: rem($font-size-h3);
}
&.-h4 {
font-size: var(--font-size-h4);
font-size: rem($font-size-h4);
}
&.-h5 {
font-size: var(--font-size-h5);
font-size: rem($font-size-h5);
}
&.-h6 {
font-size: var(--font-size-h6);
font-size: rem($font-size-h6);
}
}

View File

@@ -1,20 +0,0 @@
// ==========================================================================
// Components / Image
// ==========================================================================
.c-image {
}
.c-image_img {
// Lazy loading styles
.c-image.-lazy-load & {
transition: opacity $speed $easing;
opacity: 0;
}
.c-image.-lazy-loaded & {
opacity: 1;
}
}

View File

@@ -0,0 +1,40 @@
// ==========================================================================
// Components / Scrollbar
// ==========================================================================
.c-scrollbar {
position: absolute;
right: 0;
top: 0;
width: 11px;
height: 100vh;
transform-origin: center right;
transition: transform 0.3s, opacity 0.3s;
opacity: 0;
&:hover {
transform: scaleX(1.45);
}
&:hover,
.has-scroll-scrolling &,
.has-scroll-dragging & {
opacity: 1;
}
}
.c-scrollbar_thumb {
position: absolute;
top: 0;
right: 0;
background-color: $color-darkest;
opacity: 0.5;
width: 7px;
border-radius: 10px;
margin: 2px;
cursor: grab;
.has-scroll-dragging & {
cursor: grabbing;
}
}

View File

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

View File

@@ -2,6 +2,20 @@
// 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.
//
@@ -15,7 +29,7 @@
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;
@@ -51,14 +65,30 @@ html {
&.is-loading {
cursor: wait;
}
&.has-scroll-smooth {
overflow: hidden;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
&.has-scroll-dragging {
user-select: none;
}
}
body {
.has-scroll-smooth & {
overflow: hidden;
}
}
::selection {
background-color: $color-selection-background;
color: $color-selection-text;
background-color: $selection-background-color;
color: $selection-text-color;
text-shadow: none;
}

View File

@@ -57,13 +57,25 @@ figure {
padding: 0;
}
h1, h2, h3, h4, h5, h6 {
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] {
a,
area,
button,
input,
label,
select,
textarea,
[tabindex] {
-ms-touch-action: manipulation; // [1]
touch-action: manipulation;
}
@@ -83,5 +95,5 @@ hr {
padding: 0;
height: 1px;
border: 0;
border-top: 1px solid #CCCCCC;
border-top: 1px solid #cccccc;
}

View File

@@ -1,69 +1,69 @@
// ==========================================================================
// Main
// ==========================================================================
@use "sass:math";
@use 'sass:math';
// Settings
// ==========================================================================
@import 'settings/config.eases';
@import 'settings/config.colors';
@import 'settings/config';
// ==========================================================================
// Tools
// ==========================================================================
@import "tools/maths";
@import "tools/functions";
@import "tools/mixins";
@import "tools/fonts";
@import 'tools/maths';
@import 'tools/functions';
@import 'tools/mixins';
@import 'tools/fonts';
// @import "tools/layout";
// @import "tools/widths";
// @import "tools/family";
// Settings
// ==========================================================================
@import "settings/config.eases";
@import "settings/config.colors";
@import "settings/config";
@import "settings/config.variables";
// Generic
// ==========================================================================
@import "node_modules/normalize.css/normalize";
@import "generic/generic";
@import "generic/media";
@import "generic/form";
@import "generic/button";
// Vendors
// ==========================================================================
@import "node_modules/locomotive-scroll/dist/locomotive-scroll";
@import 'node_modules/normalize.css/normalize';
@import 'generic/generic';
@import 'generic/media';
@import 'generic/form';
@import 'generic/button';
// Elements
// ==========================================================================
@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/button";
@import "components/form";
@import "components/image";
@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";

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);
}
@@ -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,7 +40,6 @@
}
}
// SVG sizes
// ==========================================================================
@@ -55,4 +53,3 @@
// --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

@@ -5,27 +5,25 @@
// Palette
// =============================================================================
$colors: (
primary: #3297FD,
lightest: #FFFFFF,
darkest: #000000,
);
$color-lightest: #ffffff;
$color-darkest: #000000;
// 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(darkest);
$color-selection-background: color(lightest);
$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

@@ -3,46 +3,46 @@
// ==========================================================================
// Power 1
$ease-power1-in: cubic-bezier(0.550, 0.085, 0.680, 0.530);
$ease-power1-out: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$ease-power1-in-out: cubic-bezier(0.455, 0.030, 0.515, 0.955);
$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);
// Power 2
$ease-power2-in: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$ease-power2-out: cubic-bezier(0.215, 0.610, 0.355, 1.000);
$ease-power2-in-out: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$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 3
$ease-power3-in: cubic-bezier(0.895, 0.030, 0.685, 0.220);
$ease-power3-out: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$ease-power3-in-out: cubic-bezier(0.770, 0.000, 0.175, 1.000);
$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
$ease-power4-in: cubic-bezier(0.755, 0.050, 0.855, 0.060);
$ease-power4-out: cubic-bezier(0.230, 1.000, 0.320, 1.000);
$ease-power4-in-out: cubic-bezier(0.860, 0.000, 0.070, 1.000);
$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);
// Expo
$ease-expo-in: cubic-bezier(0.950, 0.050, 0.795, 0.035);
$ease-expo-out: cubic-bezier(0.190, 1.000, 0.220, 1.000);
$ease-expo-in-out: cubic-bezier(1.000, 0.000, 0.000, 1.000);
$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);
// Back
$ease-back-in: cubic-bezier(0.600, -0.280, 0.735, 0.045);
$ease-back-out: cubic-bezier(0.175, 00.885, 0.320, 1.275);
$ease-back-in-out: cubic-bezier(0.680, -0.550, 0.265, 1.550);
$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);
// Sine
$ease-sine-in: cubic-bezier(0.470, 0.000, 0.745, 0.715);
$ease-sine-out: cubic-bezier(0.390, 0.575, 0.565, 1.000);
$ease-sine-in-out: cubic-bezier(0.445, 0.050, 0.550, 0.950);
$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);
// Circ
$ease-circ-in: cubic-bezier(0.600, 0.040, 0.980, 0.335);
$ease-circ-out: cubic-bezier(0.075, 0.820, 0.165, 1.000);
$ease-circ-in-out: cubic-bezier(0.785, 0.135, 0.150, 0.860);
$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);
// Misc
$ease-bounce: cubic-bezier(0.17, 0.67, 0.3, 1.33);
$ease-slow-out: cubic-bezier(.04,1.15,0.4,.99);
$ease-smooth: cubic-bezier(0.380, 0.005, 0.215, 1);
$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,21 +6,26 @@
// =============================================================================
// 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-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;
$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.
//
@@ -28,7 +33,7 @@ $font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console
// <font-id>: (<font-name>, <font-fallbacks>)
// ```
$font-families: (
sans: join("Source Sans", $font-fallback-sans, $separator: comma),
sans: join('Source Sans', $font-fallback-sans, $separator: comma),
);
// List of custom font faces as tuples.
@@ -37,10 +42,10 @@ $font-families: (
// <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),
('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
@@ -48,61 +53,71 @@ $font-faces: (
// Base
$font-size: 16px;
$line-height: math.div(24px, $font-size);
$font-color: color(darkest);
$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;
// Transitions
// =============================================================================
$speed: 0.3s;
$easing: $ease-power2-out;
$speed: 0.3s;
$easing: $ease-power2-out;
// Spacing Units
// =============================================================================
$unit: 60px;
$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;
$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
'header': 200,
'above': 1,
'below': -1,
);

View File

@@ -1,34 +0,0 @@
// ==========================================================================
// Settings / Config / CSS VARS
// ==========================================================================
:root {
// Grid
--grid-columns: 4;
--grid-gutter: #{rem(10px)};
--grid-gutter-half: calc(0.5 * var(--grid-gutter));
--grid-margin: #{rem(10px)};
// Container
--container-width: calc(100% - 2 * var(--grid-margin));
// Font sizes
--font-size-h1: #{responsive-type(36px, 72px, 1400px)};
--font-size-h2: #{rem(28px)};
--font-size-h3: #{rem(24px)};
--font-size-h4: #{rem(20px)};
--font-size-h5: #{rem(18px)};
--font-size-h6: #{rem(16px)};
// // Colors
// @each $color, $value in $colors {
// --color-#{"" + $color}: #{$value};
// }
@media (min-width: $from-small) {
--grid-columns: #{$base-column-nb};
--grid-gutter: #{rem(16px)};
--grid-margin: #{rem(20px)};
}
}

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

@@ -15,8 +15,8 @@
@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)};
}
@@ -33,7 +33,7 @@
@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.";
}
@@ -92,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;
}
}
@@ -139,89 +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} $number - The percentage spacer
// @param {number} $inset - The grid gutter inset
// @return {function<number>}
@function grid-space($percentage, $inset: 0) {
@return calc(#{$percentage} * (100vw - 2 * var(--grid-margin, 0px)) - (1 - #{$percentage}) * var(--grid-gutter, 0px) + #{$inset} * var(--grid-gutter, 0px));
}
// Returns calculation of a percentage of the viewport height.
//
// ```scss
// .c-box {
// height: vh(100);
// }
// ```
//
// @param {number} $number - The percentage number
// @return {function<number>} in vh
@function vh($number) {
@return calc(#{$number} * var(--vh, 1vh));
}
// Returns calculation of a percentage of the viewport width.
//
// ```scss
// .c-box {
// width: vw(100);
// }
// ```
//
// @param {number} $number - The percentage number
// @return {function<number>} in vw
@function vw($number) {
@return calc(#{$number} * var(--vw, 1vw));
}
// Returns clamp of calculated preferred responsive font size
// within a font size and breakpoint range.
//
// ```scss
// .c-heading.-h1 {
// font-size: responsive-type(30px, 60px, 1800);
// }
//
// .c-heading.-h2 {
// font-size: responsive-type(20px, 40px, $from-big);
// }
// ```
//
// @param {number} $min-size - Minimum font size in pixels.
// @param {number} $max-size - Maximum font size in pixels.
// @param {number} $breakpoint - Maximum breakpoint.
// @return {function<number, function<number>, number>}
@function responsive-type($min-size, $max-size, $breakpoint) {
$delta: math.div($max-size, $breakpoint);
@return clamp($min-size, calc(#{strip-unit($delta)} * #{vw(100)}), $max-size);
}
// Returns color code.
//
// ```scss
// .c-box {
// width: color(primary);
// }
// ```
//
// @param {string} $key - The color key in $colors.
// @return {color}
@function color($key) {
@if not map-has-key($colors, $key) {
@error "Unknown '#{$key}' in $colors.";
}
@return map-get($colors, $key);
}

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,5 +198,5 @@
@mixin u-shown($display: block, $important: true) {
$important: important($important);
display: $display $important;
visibility: visible $important;
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

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

@@ -21,25 +21,25 @@
/* stylelint-disable string-quotes */
$spacing-directions: (
null: null,
'-top': '-top',
'-right': '-right',
'-bottom': '-bottom',
'-left': '-left',
null: null,
'-top': '-top',
'-right': '-right',
'-bottom': '-bottom',
'-left': '-left',
'-horizontal': '-left' '-right',
'-vertical': '-top' '-bottom',
'-vertical': '-top' '-bottom',
) !default;
$spacing-properties: (
'padding': 'padding',
'margin': 'margin',
'margin': 'margin',
) !default;
$spacing-sizes: (
null: $unit,
null: $unit,
'-double': $unit * 2,
'-small': $unit-small,
'-none': 0px
'-none': 0px,
) !default;
@each $property-namespace, $property in $spacing-properties {

View File

@@ -4,7 +4,7 @@
// ARIA roles display visual cursor hints
[aria-busy="true"] {
[aria-busy='true'] {
cursor: progress;
}

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,18 +82,9 @@ 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 ({
includes,
outfile,
@@ -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,20 +50,9 @@ 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 ({
includes,
outdir = '',
@@ -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,13 +1,8 @@
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';
@@ -87,10 +82,10 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
sassOptions !== developmentSassOptions &&
sassOptions !== productionSassOptions
) {
sassOptions = merge({}, defaultSassOptions, sassOptions);
sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
}
if (supportsPostCSS) {
if (postcss) {
if (postcssOptions == null) {
postcssOptions = productionPostCSSOptions;
} else if (
@@ -98,19 +93,10 @@ 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 ({
infile,
outfile,
@@ -130,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
);
@@ -205,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.
*
@@ -217,23 +232,22 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
*/
async function purgeUnusedCSS(outfile, label) {
label = label ?? basename(outfile);
const timeLabel = `${label} purged in`;
console.time(timeLabel);
const purgeCSSContentFiles = Array.from(loconfig.tasks.purgeCSS.content);
const purgeCSSResults = await (new PurgeCSS()).purge({
const purgeCSSResults = await new PurgeCSS().purge({
content: purgeCSSContentFiles,
css: [ outfile ],
rejected: true,
defaultExtractor: (content) => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
safelist: {
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,18 +44,9 @@ 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 ({
includes,
outfile,
@@ -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 {
@@ -95,22 +94,11 @@ 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(({
outfile,
label = null,

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';

View File

@@ -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].

1440
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,37 +6,36 @@
"author": "Locomotive <info@locomotive.ca>",
"type": "module",
"engines": {
"node": ">=17.9",
"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.8",
"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.13",
"browser-sync": "^2.27.11",
"concat": "^1.0.3",
"esbuild": "^0.17.6",
"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.57.1",
"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"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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>
@@ -39,24 +39,84 @@
<h3 class="c-heading -h3">Basic</h3>
<img src="http://picsum.photos/800/600?v=1" alt="" loading="lazy" class="c-image_img" width="800" height="600"/>
<div style="width: 640px; max-width: 100%;">
<div class="c-image"><img src="http://picsum.photos/800/600?v=2" alt="" loading="lazy" class="c-image_img" width="800" height="600"/></div>
<div class="c-image"><img src="http://picsum.photos/800/600?v=3" alt="" loading="lazy" class="c-image_img" width="800" height="600"/></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
</div>
<h4 class="c-heading -h3">Using o-ratio</h3>
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
<div style="width: 480px; max-width: 100%;">
<div class="o-ratio u-4:3"><div class="c-image || o-ratio_content"><img src="http://picsum.photos/800/600?v=4" alt="" loading="lazy" class="c-image_img"/></div></div>
<div class="o-ratio u-4:3"><div class="c-image || o-ratio_content"><img src="http://picsum.photos/800/600?v=5" alt="" loading="lazy" class="c-image_img"/></div></div>
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=1);"></div>
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=2);"></div>
</div>
</section>
<section>
<h3 class="c-heading -h3">Relative to scroll</h3>
<h4 class="c-heading -h3">Using o-ratio & img</h3>
<div style="width: 640px; max-width: 100%;">
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
</div>
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
<div style="width: 480px; max-width: 100%;">
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=1"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=2"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=3"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=4"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=5"></div>
</div>
<h4 class="c-heading -h3">Using SVG viewport for ratio</h3>
<div style="width: 480px; max-width: 100%;">
<img
data-scroll
data-scroll-call="lazyLoad, Scroll, main"
data-src="http://picsum.photos/640/480?v=6"
alt=""
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
/>
<img
data-scroll
data-scroll-call="lazyLoad, Scroll, main"
data-src="http://picsum.photos/640/480?v=7"
alt=""
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
/>
<img
data-scroll
data-scroll-call="lazyLoad, Scroll, main"
data-src="http://picsum.photos/640/480?v=8"
alt=""
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
/>
</div>
</section>
</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>