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

88 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
7c1b61eda9 Add build config dynamic import assertation type 2023-02-02 10:00:20 -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
e4ae03a94c Remove unused sass env function 2023-01-31 15:37:40 -05:00
Deven Caron
3cde7d40ee Update readme 2023-01-31 14:15:56 -05:00
Deven Caron
d1d4fb5fe5 Bump node-version & upgrade node-sass to sass 2023-01-31 13:57:51 -05:00
Deven Caron
2f6b353616 Add prettier & precommit command 2023-01-31 11:44:00 -05:00
Chauncey McAskill
3cd81bdb3e Improve asset versioning task
Improved logic for replacing the value to allow for simpler regular expression patterns. Lookbehinds and lookaheads are no longer required.
2023-01-13 16:42:47 -05:00
Chauncey McAskill
b6970832a3 Improve asset versioning task
Added support for replacing a string in a file using a regular expression.

The routine uses Node's Readline and Stream modules.

The `outfile` will be renamed with a `~` suffix, then stream each line, writing to a new `outfile`.

On success, the backup file (with `~`) is deleted.

On error, any new file is deleted, then the backup file is renamed without a `~` suffix.

Usage:

```json
"versions": [
    {
        "format": "timestamp",
        "key": "regexp:(?<=\bdefine\('ASSETS_VERSION', )[^\)]+(?=\);)",
        "outfile": "src/bootstrap.php"
    }
]
```

```php
<?php

define( 'ASSETS_VERSION', 1665071717350 );
```
2023-01-13 15:08:50 -05:00
Chauncey McAskill
b8f0a24cdc Update NPM dependencies
Updated:
- esbuild v0.16.13 → v0.16.17
- postcss v8.4.20 → v8.4.21
2023-01-13 14:55:01 -05:00
Chauncey McAskill
0c718a2644 Update Development documentation
Changed:
- Bumped NPM requirement.
- Added note about benefits of using NVM.
- Added note about support for PurgeCSS to Styles task.
- Added details about Versions task.
2023-01-05 10:37:52 -05:00
Chauncey McAskill
56d255eac8 Fix NPM dependency constraints on svg-mixer
Changed:
- Fixed svg-mixer constraint from `^2.3.14` to `~2.3.14` to stay within `2.3` range since `2.4.0` appears to be an anomaly.
- Overrode postcss constraint in svg-mixer from `^6.0.21` to `^8.4.20` to fix security notice.
2023-01-04 11:01:40 -05:00
Chauncey McAskill
e7e343e62c Bump NPM requirement to 8+
To take advantage of NPM dependency overrides.
2023-01-04 11:01:40 -05:00
Chauncey McAskill
590e06fc03 Update NPM dependencies
Updated:
- autoprefixer v10.4.12 → v10.4.13
- browser-sync v2.27.10 → v2.27.11
- esbuild v0.14.54 → v0.16.13
- node-sass v7.0.3 → v8.0.0
- postcss v8.4.17 → v8.4.20
2023-01-04 11:01:38 -05:00
Lucas Vallenet
4fd7968b86 Fix SCSS syntax error and duplicate 2023-01-04 11:26:56 +01:00
Lucas Bigot
20b167da33 Invert grid-helper visibility condition 2022-11-22 14:51:37 -05:00
Deven Caron
f7ca837782 Disable browsersync ghostMode 2022-11-02 11:15:49 -04:00
Deven Caron
0c8ed9595f Merge pull request #112 from locomotivemtl/feature/grid-helper 2022-10-31 14:34:13 -04:00
arnvvd
eead1d27cd Move grid helper call to global.js 2022-10-31 14:27:18 -04:00
arnvvd
2e3db21ec8 Update dynamic imports condition 2022-10-31 14:25:22 -04:00
arnvvd
9c478f5f7d grid helper refactoring + prepare JS dynamic import + add app-env function for Sass conditions 2022-10-31 14:25:20 -04:00
Jérémy Minié
ebcbb6dc84 Only use GridHelper's margin setting for lateral grid spacing 2022-10-31 14:22:31 -04:00
Lucas Vallenet
b7c49086c9 Add custom grid helper based on CSS custom properties 2022-10-31 14:20:59 -04:00
Chauncey McAskill
9219a4cc0a Rename default function in versions.js task
Renamed from `bumpVersion` to `bumpVersions` for consistency with the default functions of other tasks.
2022-10-13 12:53:32 -04:00
Chauncey McAskill
14afe2295a Improve asset versioning task
Added:
- Support for writing multiple times to the same file.
- Support for random hexadecimal value instead of timestamp.

Usage:

```json
"versions": [
    {
        "format": "timestamp",
        "key": "now",
        "outfile": "./assets.json"
    },
    {
        "format": "hex:8",
        "key": "hex",
        "outfile": "./assets.json"
    }
]
```

```json
{
    "now": 1665071717350,
    "hex": "6ef54181c4ba"
}
```
2022-10-12 12:07:24 -04:00
Deven Caron
1bdd2def8d Compile assets 2022-10-06 14:00:32 -04:00
Deven Caron
84ce496df7 Tweak images size to match block ratio 2022-10-06 14:00:26 -04:00
Deven Caron
05e631dbca Fixed locomotive-scroll rendering glitch 2022-10-06 13:59:42 -04:00
Chauncey McAskill
f8f0a7779c Implement simple asset versioning task
A task to allow one to define zero or more JSON files and keys to create or update with the current timestamp.

By default, the boilerplate will maintain a './assets.json' file which should be imported by the Web framework and applied to compiled assets.
2022-10-06 11:32:04 -04:00
Arnaud Pinot
8e320f2cd0 Merge pull request #120 from locomotivemtl/feature/es6-updates 2022-10-05 16:56:16 -04:00
Arnaud Pinot
a8314d064f Update assets/scripts/utils/maths.js
Co-authored-by: Chauncey McAskill <chauncey@locomotive.ca>
2022-10-05 16:54:36 -04:00
arnvvd
76614e8126 Merge branch 'master' into feature/es6-updates 2022-10-05 16:43:57 -04:00
arnvvd
bed84ce392 Merge branch 'feature/es6-updates' of github.com:locomotivemtl/locomotive-boilerplate into feature/es6-updates 2022-10-05 16:39:30 -04:00
arnvvd
feb2241164 Add documentation for esbuild process.env 2022-10-05 16:39:25 -04:00
arnvvd
b1f5a00b8c Delete transform utils function 2022-10-05 16:38:55 -04:00
Arnaud Pinot
2b30d9ac5c Update assets/scripts/utils/is.js
Co-authored-by: Chauncey McAskill <chauncey@locomotive.ca>
2022-10-05 16:36:20 -04:00
Arnaud Pinot
9e5704238e Update assets/scripts/utils/tickers.js
Co-authored-by: Chauncey McAskill <chauncey@locomotive.ca>
2022-10-05 16:35:23 -04:00
Arnaud Pinot
1ede84e1b1 Update assets/scripts/utils/tickers.js
Co-authored-by: Chauncey McAskill <chauncey@locomotive.ca>
2022-10-05 16:34:49 -04:00
Arnaud Pinot
3a83a3209b Merge pull request #129 from locomotivemtl/feature/eager-fonts
Add support for the CSS Font Loading API
2022-10-05 14:42:00 -04:00
arnvvd
98957eb6c4 Merge branch 'master' of github.com:locomotivemtl/locomotive-boilerplate into feature/eager-fonts 2022-10-05 14:38:55 -04:00
arnvvd
6712d2d24d Remove unused constant 2022-10-05 14:27:36 -04:00
Chauncey McAskill
139a6739f6 Update NPM dependencies
Updated:
- autoprefixer v10.4.4 → v10.4.12
- browser-sync v2.27.9 → v2.27.10
- esbuild v0.14.27 → v0.14.54
- kleur v4.1.4 → v4.1.5
- postcss v8.4.12 → v8.4.17
- purgecss v4.1.3 → v4.1.3
2022-10-05 12:54:05 -04:00
Chauncey McAskill
3272521dba Add constant to collect all eager fonts 2022-10-04 16:56:31 -04:00
Chauncey McAskill
e7f0455ce4 Testing CSS Font Loading API
The code in this commit is not intended for production environments;
it requires further testing.

Added:
- Multiple "Source Sans 3" fonts to test many `FontFace` entries.

Changed:
- Removed quotes from font family name to avoid them being included in `FontFace.family` value.

Notes:
- Replaces hidden `<span>` elements with `FaceFace.load()` and `FaceFace.loaded` to eagerly load fonts.
- Fonts are eagerly using custom `loadFonts()` (see 'app.js').
- Acting upon loaded fonts is done using `whenReady()` (see 'Example.js').
2022-09-27 11:11:31 -04:00
Deven Caron
bf425521c4 Fix "z()" sass function typo 2022-08-25 14:45:24 -04:00
Chauncey McAskill
4bdaa5d085 Fix typo in styles.js
Resolves #125
2022-08-12 14:03:55 -04:00
Deven Caron
a385f6ed11 Merge pull request #108 from locomotivemtl/feature/grid-css
Add grid CSS layout system
2022-06-07 13:13:31 -04:00
Deven Caron
4079752fe0 Add ul,ol condition grid reset styles 2022-06-07 10:46:44 -04:00
Deven Caron
14d7e09b2b Compile assets 2022-06-06 16:43:51 -04:00
Deven Caron
e70bf33409 Update variable names to kebab-case 2022-06-06 16:43:13 -04:00
Deven Caron
8b0926269a Apply suggestions from code review
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 16:30:59 -04:00
Deven Caron
8d1b548ad0 Update PurgeCSS import order 2022-06-06 16:28:07 -04:00
Deven Caron
7ca7486913 Fix $container-width conflict 2022-06-06 14:42:25 -04:00
Jérémy Minié
520b75185f Remove unused $container-width SCSS variable 2022-06-06 13:26:47 -04:00
Jérémy Minié
a056a87855 Fix grid.md code sample closure + Link grid doc in README 2022-06-06 13:26:07 -04:00
Deven Caron
9b99a1958b Manually apply some #108 suggestions 2022-06-06 13:26:07 -04:00
Deven Caron
d6193a41fa Apply suggestions from code review
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 13:26:07 -04:00
Deven Caron
4fafcb8e1d Update docs/grid.md
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 13:26:07 -04:00
Deven Caron
aadc410e44 Change PurgeCSS task message label
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 13:26:07 -04:00
Deven Caron
0439b165cf Apply suggestions from code review
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 13:26:07 -04:00
Deven Caron
f98eebc9e1 Add global readme grid reference 2022-06-06 13:26:07 -04:00
Deven Caron
cb80a2ed13 Add missing grid readme instructions 2022-06-06 13:26:07 -04:00
Deven Caron
07c3155c29 Compile styles 2022-06-06 13:15:56 -04:00
Deven Caron
c264cb7905 [WIP] Start grid doc 2022-06-06 13:07:57 -04:00
Deven Caron
1050b83326 Add template links 2022-06-06 13:07:57 -04:00
Deven Caron
d0a075ff24 Add css grid system base styles 2022-06-06 13:07:12 -04:00
Deven Caron
ad4a1c7d47 Add PurgeCSS u-gc* tasks 2022-06-06 13:03:06 -04:00
Chauncey McAskill
de6b3d73a1 Merge pull request #124 from GregoireCiles-fix/scss-build-font-faces
* GregoireCiles-fix/scss-build-font-faces:
  Support for building a single font family
  Fix alignment and mixin in _fonts.scss
2022-06-06 11:37:45 -04:00
Grégoire Ciles
e8af22009c Support for building a single font family
Co-authored-by: Chauncey McAskill <chauncey@mcaskill.ca>
2022-06-06 11:36:37 -04:00
Grégoire Ciles
de1a5904a8 Fix alignment and mixin in _fonts.scss 2022-06-06 11:36:31 -04:00
Lucas Vallenet
f527488464 Delete queryClosestParent function 2022-06-02 17:23:06 +02:00
Lucas Vallenet
cf3f40c956 Update functions comments 2022-06-02 16:45:31 +02:00
Lucas Vallenet
ebb55769f9 Remove isEqual and isNumeric functions 2022-06-02 11:09:32 +02:00
Lucas Vallenet
9a461ab4c0 Remove events.js 2022-06-01 14:21:14 +02:00
Lucas Vallenet
eddc0ee156 Edit logs 2022-05-26 13:42:49 +02:00
Lucas Vallenet
202e4064f8 Remove removeCustomEvent (no use). Add logs 2022-05-26 13:42:38 +02:00
Lucas Vallenet
e20111fd2e Add removeCustomEvent util 2022-05-26 13:32:52 +02:00
Lucas Vallenet
de0c4993cb Add config.js / Add tickers utils / Add events utils 2022-05-26 13:06:27 +02:00
Lucas Vallenet
6a4043408b Remove export comment 2022-05-25 13:28:15 +02:00
Lucas Vallenet
92500f908f Add roundNumber util 2022-05-25 13:27:40 +02:00
Lucas Vallenet
d97fa82c77 Remove array util 2022-05-25 13:27:30 +02:00
Lucas Vallenet
ccf813f6be Update functions / ES6 syntax / Add functions comments 2022-05-20 16:51:24 +02:00
72 changed files with 3560 additions and 10619 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
Thumbs.db
loconfig.*.json
!loconfig.example.json
.prettierrc

2
.nvmrc
View File

@@ -1 +1 @@
v14.17
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

@@ -15,6 +15,7 @@
* Uses [SVG Mixer] for processing SVG files and generating spritesheets.
* Uses [ITCSS] for a sane and scalable CSS architecture.
* Uses [Locomotive Scroll] for smooth scrolling with parallax effects.
* Uses a custom [grid system](docs/grid.md) for layout creation.
Learn more about [languages and technologies](docs/technologies.md).
@@ -22,8 +23,8 @@ Learn more about [languages and technologies](docs/technologies.md).
Make sure you have the following installed:
* [Node] — at least 14.17, the latest LTS is recommended.
* [NPM] — at least 6.0, 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.
@@ -84,6 +85,7 @@ Learn more about [development and building](docs/development.md).
* [Development and building](docs/development.md)
* [Languages and technologies](docs/technologies.md)
* [Grid system](docs/grid.md)
[BrowserSync]: https://npmjs.com/package/browser-sync
[ESBuild]: https://npmjs.com/package/esbuild

3
assets.json Normal file
View File

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

View File

@@ -1,35 +1,79 @@
import modular from 'modujs';
import * as modules from './modules';
import globals from './globals';
import { html } from './utils/environment';
import modular from 'modujs'
import * as modules from './modules'
import globals from './globals'
import { html } from './utils/environment'
import config from './config'
import { isFontLoadingAPIAvailable, loadFonts } from './utils/fonts'
const app = new modular({
modules: modules
});
modules: 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')
}
};
function init() {
globals();
app.init(app);
html.classList.add('is-loaded');
html.classList.add('is-ready');
html.classList.remove('is-loading');
}
export const EAGER_FONTS = [
{ family: 'Source Sans', style: 'normal', weight: 400 },
{ family: 'Source Sans', style: 'normal', weight: 700 },
]
function init() {
globals()
app.init(app)
html.classList.add('is-loaded')
html.classList.add('is-ready')
html.classList.remove('is-loading')
/**
* Eagerly load the following fonts.
*/
if (isFontLoadingAPIAvailable) {
loadFonts(EAGER_FONTS, config.IS_DEV).then((eagerFonts) => {
html.classList.add('fonts-loaded')
if (config.IS_DEV) {
console.group(
'Eager fonts loaded!',
eagerFonts.length,
'/',
document.fonts.size
)
console.group('State of eager fonts:')
eagerFonts.forEach((font) =>
console.log(
font.family,
font.style,
font.weight,
font.status /*, font*/
)
)
console.groupEnd()
console.group('State of all fonts:')
document.fonts.forEach((font) =>
console.log(
font.family,
font.style,
font.weight,
font.status /*, font*/
)
)
console.groupEnd()
}
})
}
}

24
assets/scripts/config.js Normal file
View File

@@ -0,0 +1,24 @@
/**
* > When using the esBuild API, all `process.env.NODE_ENV` expressions
* > are automatically defined to `"production"` if all minification
* > options are enabled and `"development"` otherwise. This only happens
* > if `process`, `process.env`, and `process.env.NODE_ENV` are not already
* > defined. This substitution is necessary to avoid code crashing instantly
* > (since `process` is a Node API, not a web API).
* > — https://esbuild.github.io/api/#platform
*/
const env = process.env.NODE_ENV
export default config = Object.freeze({
// Environments
ENV: env,
IS_PROD: env === 'production',
IS_DEV: env === 'development',
// CSS class names
CSS_CLASS: {
LOADING: 'is-loading',
READY: 'is-ready',
LOADED: 'is-loaded',
},
})

View File

@@ -1,5 +1,23 @@
import svg4everybody from 'svg4everybody';
import svg4everybody from 'svg4everybody'
import config from './config'
export default function() {
svg4everybody();
// Dynamic imports for development mode only
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()
/**
* Add grid helper
*/
gridHelper?.()
}

View File

@@ -1,2 +1,3 @@
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,10 +1,17 @@
import { module } from 'modujs';
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(EAGER_FONTS).then((fonts) => this.onFontsLoaded(fonts))
}
onFontsLoaded(fonts) {
console.log('Example: Eager Fonts Loaded!', fonts)
}
}

View File

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

View File

@@ -1,22 +1,22 @@
import { module } from 'modujs';
import { lazyLoadImage } from '../utils/image';
import LocomotiveScroll from 'locomotive-scroll';
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({
el: this.el,
smooth: true
});
smooth: true,
})
this.scroll.on('call', (func, way, obj, id) => {
// Using modularJS
this.call(func[0], { way, obj }, func[1], func[2]);
});
this.call(func[0], { way, obj }, func[1], func[2])
})
this.scroll.on('scroll', (args) => {
// console.log(args.scroll);
@@ -47,6 +47,6 @@ export default class extends module {
}
destroy() {
this.scroll.destroy();
this.scroll.destroy()
}
}

View File

@@ -1,101 +0,0 @@
import { isArray } from './is';
export function addToArray ( array, value ) {
const index = array.indexOf( value );
if ( index === -1 ) {
array.push( value );
}
}
export function arrayContains ( array, value ) {
for ( let i = 0, c = array.length; i < c; i++ ) {
if ( array[i] == value ) {
return true;
}
}
return false;
}
export function arrayContentsMatch ( a, b ) {
let i;
if ( !isArray( a ) || !isArray( b ) ) {
return false;
}
if ( a.length !== b.length ) {
return false;
}
i = a.length;
while ( i-- ) {
if ( a[i] !== b[i] ) {
return false;
}
}
return true;
}
export function ensureArray ( x ) {
if ( typeof x === 'string' ) {
return [ x ];
}
if ( x === undefined ) {
return [];
}
return x;
}
export function lastItem ( array ) {
return array[ array.length - 1 ];
}
export function removeFromArray ( array, member ) {
if ( !array ) {
return;
}
const index = array.indexOf( member );
if ( index !== -1 ) {
array.splice( index, 1 );
}
}
export function toArray ( arrayLike ) {
const array = [];
let i = arrayLike.length;
while ( i-- ) {
array[i] = arrayLike[i];
}
return array;
}
export function findByKeyValue( array, key, value ) {
return array.filter(function( obj ) {
return obj[key] === value;
});
}
export function cloneArray( array ) {
return JSON.parse(JSON.stringify(array));
}
/**
* Shuffles array in place. ES6 version
* @param {Array} a items An array containing the items.
*/
export function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}

View File

@@ -1,15 +0,0 @@
export default function(func, wait, immediate) {
let timeout;
return function() {
const context = this;
const args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}

View File

@@ -1,8 +1,8 @@
const APP_NAME = 'Boilerplate';
const DATA_API_KEY = '.data-api';
const APP_NAME = 'Boilerplate'
const DATA_API_KEY = '.data-api'
const html = document.documentElement;
const body = document.body;
const isDebug = html.hasAttribute('data-debug');
const html = document.documentElement
const body = document.body
const isDebug = html.hasAttribute('data-debug')
export { APP_NAME, DATA_API_KEY, html, body, isDebug };
export { APP_NAME, DATA_API_KEY, html, body, isDebug }

View File

@@ -0,0 +1,391 @@
/**
* Font Faces
*
* Provides utilities to facilitate interactions with the CSS Font Loading API.
*
* Features functions to:
*
* - Retrieve one or more `FontFace` instances based on a font search query.
* - Check if a `FontFace` instance matches a font search query.
* - Eagerly load fonts that match a font search query.
* - Wait until fonts that match a font search query are loaded.
*
* References:
*
* - {@link https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API}
*/
/**
* @typedef {Object} FontFaceReference
*
* @property {string} family - The name used to identify the font in our CSS.
* @property {string} [style] - The style used by the font in our CSS.
* @property {string} [weight] - The weight used by the font in our CSS.
*/
const isFontLoadingAPIAvailable = 'fonts' in document
/**
* Determines if the given font matches the given `FontFaceReference`.
*
* @param {FontFace} font - The font to inspect.
* @param {FontFaceReference} criterion - The object of property values to match.
*
* @returns {boolean}
*/
function conformsToReference(font, criterion) {
for (const [key, value] of Object.entries(criterion)) {
switch (key) {
case 'family': {
if (trim(font[key]) !== value) {
return false
}
break
}
case 'weight': {
/**
* Note concerning font weights:
* Loose equality (`==`) is used to compare numeric weights,
* a number (`400`) and a numeric string (`"400"`).
* Comparison between numeric and keyword values is neglected.
*
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping
*/
if (font[key] != value) {
return false
}
break
}
default: {
if (font[key] !== value) {
return false
}
break
}
}
}
return true
}
/**
* Determines if the given font matches the given font shorthand.
*
* @param {FontFace} font - The font to inspect.
* @param {string} criterion - The font shorthand to match.
*
* @returns {boolean}
*/
function conformsToShorthand(font, criterion) {
const family = trim(font.family)
if (trim(family) === criterion) {
return true
}
if (
criterion.endsWith(trim(family)) &&
(criterion.match(font.weight) || criterion.match(font.style))
) {
return true
}
return true
}
/**
* Determines if the given font matches any of the given criteria.
*
* @param {FontFace} font - The font to inspect.
* @param {FontFaceReference[]} criteria - A list of objects with property values to match.
*
* @returns {boolean}
*/
function conformsToAnyReference(font, criteria) {
for (const criterion of criteria) {
if (conformsToReference(font, criterion)) {
return true
}
}
return false
}
/**
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
* the provided `FontFaceReference`.
*
* @param {FontFaceReference} font
*
* @returns {FontFace[]}
*/
function findManyByReference(search) {
const found = []
for (const font of document.fonts) {
if (conformsToReference(font, search)) {
found.push(font)
}
}
return found
}
/**
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
* the provided font shorthand.
*
* @param {string} font
*
* @returns {FontFace[]}
*/
function findManyByShorthand(search) {
const found = []
for (const font of document.fonts) {
if (conformsToShorthand(font, search)) {
found.push(font)
}
}
return found
}
/**
* Returns the first `FontFace` from `document.fonts` that satisfies
* the provided `FontFaceReference`.
*
* @param {FontFaceReference} font
*
* @returns {?FontFace}
*/
function findOneByReference(search) {
for (const font of document.fonts) {
if (conformsToReference(font, criterion)) {
return font
}
}
return null
}
/**
* Returns the first `FontFace` from `document.fonts` that satisfies
* the provided font shorthand.
*
* Examples:
*
* - "Roboto"
* - "italic bold 16px Roboto"
*
* @param {string} font
*
* @returns {?FontFace}
*/
function findOneByShorthand(search) {
for (const font of document.fonts) {
if (conformsToShorthand(font, search)) {
return font
}
}
return null
}
/**
* Returns a `FontFace` from `document.fonts` that satisfies
* the provided query.
*
* @param {FontFaceReference|string} font - Either:
* - a `FontFaceReference` object
* - a font family name
* - a font specification, for example "italic bold 16px Roboto"
*
* @returns {?FontFace}
*
* @throws {TypeError}
*/
function getAny(search) {
if (search) {
switch (typeof search) {
case 'string':
return findOneByShorthand(search)
case 'object':
return findOneByReference(search)
}
}
throw new TypeError(
'Expected font query to be font shorthand or font reference'
)
}
/**
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
* the provided queries.
*
* @param {FontFaceReference|string|(FontFaceReference|string)[]} queries
*
* @returns {FontFace[]}
*
* @throws {TypeError}
*/
function getMany(queries) {
if (!Array.isArray(queries)) {
queries = [queries]
}
const found = new Set()
queries.forEach((search) => {
if (search) {
switch (typeof search) {
case 'string':
found.add(...findManyByShorthand(search))
return
case 'object':
found.add(...findManyByReference(search))
return
}
}
throw new TypeError(
'Expected font query to be font shorthand or font reference'
)
})
return [...found]
}
/**
* Determines if a font face is registered.
*
* @param {FontFace|FontFaceReference|string} search - Either:
* - a `FontFace` instance
* - a `FontFaceReference` object
* - a font family name
* - a font specification, for example "italic bold 16px Roboto"
*
* @returns {boolean}
*/
function hasAny(search) {
if (search instanceof FontFace) {
return document.fonts.has(search)
}
return getAny(search) != null
}
/**
* Eagerly load fonts.
*
* Most user agents only fetch and load fonts when they are first needed
* ("lazy loaded"), which can result in a perceptible delay.
*
* This function will "eager load" the fonts.
*
* @param {(FontFace|FontFaceReference)[]} fontsToLoad - List of fonts to load.
* @param {boolean} [debug] - If TRUE, log details to the console.
*
* @returns {Promise}
*/
async function loadFonts(fontsToLoad, debug = false) {
if ((fontsToLoad.size ?? fontsToLoad.length) === 0) {
throw new TypeError('Expected at least one font')
}
return await loadFontsWithAPI([...fontsToLoad], debug)
}
/**
* Eagerly load a font using `FontFaceSet` API.
*
* @param {FontFace} font
*
* @returns {Promise}
*/
async function loadFontFaceWithAPI(font) {
return await (font.status === 'unloaded' ? font.load() : font.loaded).then(
(font) => font,
(err) => font
)
}
/**
* Eagerly load fonts using `FontFaceSet` API.
*
* @param {FontFaceReference[]} fontsToLoad
* @param {boolean} [debug]
*
* @returns {Promise}
*/
async function loadFontsWithAPI(fontsToLoad, debug = false) {
debug &&
console.group(
'[loadFonts:API]',
fontsToLoad.length,
'/',
document.fonts.size
)
const fontsToBeLoaded = []
for (const fontToLoad of fontsToLoad) {
if (fontToLoad instanceof FontFace) {
if (!document.fonts.has(fontToLoad)) {
document.fonts.add(fontToLoad)
}
fontsToBeLoaded.push(loadFontFaceWithAPI(fontToLoad))
} else {
fontsToBeLoaded.push(
...getMany(fontToLoad).map((font) => loadFontFaceWithAPI(font))
)
}
}
debug && console.groupEnd()
return await Promise.all(fontsToBeLoaded)
}
/**
* Removes quotes from the the string.
*
* When a `@font-face` is declared, the font family is sometimes
* defined in quotes which end up included in the `FontFace` instance.
*
* @param {string} value
*
* @returns {string}
*/
function trim(value) {
return value.replace(/['"]+/g, '')
}
/**
* Returns a Promise that resolves with the specified fonts
* when they are done loading or failed.
*
* @param {FontFaceReference|string|(FontFaceReference|string)[]} queries
*
* @returns {Promise}
*/
async function whenReady(queries) {
const fonts = getMany(queries)
return await Promise.all(fonts.map((font) => font.loaded))
}
export {
getAny,
getMany,
hasAny,
isFontLoadingAPIAvailable,
loadFonts,
whenReady,
}

View File

@@ -0,0 +1,138 @@
/**
* Grid Helper
*
* Provides a grid based on the design guidelines and is helpful for web integration.
*
* - `Control + g` to toggle the grid
*
*/
/**
* @typedef {Object} GridHelperReference
*
* @property {string} [gutterCssVar=GRID_HELPER_GUTTER_CSS_VAR] - CSS variable used to define grid gutters.
* @property {string} [marginCssVar=GRID_HELPER_MARGIN_CSS_VAR] - CSS variable used to define grid margins.
* @property {string} [rgbaColor=GRID_HELPER_RGBA_COLOR] - RGBA color for the grid appearence.
*/
const GRID_HELPER_GUTTER_CSS_VAR = '--grid-gutter'
const GRID_HELPER_MARGIN_CSS_VAR = '--grid-margin'
const GRID_HELPER_RGBA_COLOR = 'rgba(255, 0, 0, .1)'
/**
* Create a grid helper
*
* @param {GridHelperReference}
*
*/
function gridHelper({
gutterCssVar = GRID_HELPER_GUTTER_CSS_VAR,
marginCssVar = GRID_HELPER_MARGIN_CSS_VAR,
rgbaColor = GRID_HELPER_RGBA_COLOR,
} = {}) {
// Set grid container
const $gridContainer = document.createElement('div')
document.body.append($gridContainer)
// Set grid appearence
setGridHelperColumns($gridContainer, rgbaColor)
setGridHelperStyles($gridContainer, gutterCssVar, marginCssVar)
// Set grid interactivity
setGridEvents($gridContainer, rgbaColor)
}
/**
* Set grid container styles
*
* @param {HTMLElement} $container - DOM Element that contains a list of generated columns
* @param {string} gutterCssVar - CSS variable used to define grid gutters.
* @param {string} marginCssVar - CSS variable used to define grid margins.
*
*/
function setGridHelperStyles($container, gutterCssVar, marginCssVar) {
const elStyles = $container.style
elStyles.zIndex = '10000'
elStyles.position = 'fixed'
elStyles.top = '0'
elStyles.left = '0'
elStyles.display = 'flex'
elStyles.width = '100%'
elStyles.height = '100%'
elStyles.columnGap = `var(${gutterCssVar}, 0)`
elStyles.paddingLeft = `var(${marginCssVar}, 0)`
elStyles.paddingRight = `var(${marginCssVar}, 0)`
elStyles.pointerEvents = 'none'
elStyles.visibility = 'hidden'
}
/**
* Set grid columns
*
* @param {HTMLElement} $container - DOM Element that will contain a list of generated columns
* @param {string} rgbaColor - RGBA color to stylize the generated columns
*
*/
function setGridHelperColumns($container, rgbaColor) {
// Clear columns
$container.innerHTML = ''
// Loop through columns
const columns = Number(
window.getComputedStyle($container).getPropertyValue('--grid-columns')
)
let $col
for (var i = 0; i < columns; i++) {
$col = document.createElement('div')
$col.style.flex = '1 1 0'
$col.style.backgroundColor = rgbaColor
$container.appendChild($col)
}
}
/**
* Set grid events
*
* Resize to rebuild columns
* Keydown/Keyup to toggle the grid display
*
* @param {HTMLElement} $container - DOM Element that contains a list of generated columns
* @param {string} rgbaColor - RGBA color to stylize the generated columns
*
*/
function setGridEvents($container, rgbaColor) {
// Handle resize
window.addEventListener(
'resize',
setGridHelperColumns($container, rgbaColor)
)
// Toggle grid
let ctrlDown = false
let isActive = false
document.addEventListener('keydown', (e) => {
if (e.key == 'Control') {
ctrlDown = true
} else {
if (ctrlDown && e.key == 'g') {
if (isActive) {
$container.style.visibility = 'hidden'
} else {
$container.style.visibility = 'visible'
}
isActive = !isActive
}
}
})
document.addEventListener('keyup', (e) => {
if (e.key == 'Control') {
ctrlDown = false
}
})
}
export { gridHelper }

View File

@@ -1,141 +1,129 @@
/**
* @see https://github.com/ractivejs/ractive/blob/dev/src/utils/html.js
* Escape HTML string
* @param {string} str - string to escape
* @return {string} escaped string
*/
export function escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
const escapeHtml = (str) =>
str.replace(
/[&<>'"]/g,
(tag) =>
({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
}[tag])
)
/**
* Prepare HTML content that contains mustache characters for use with Ractive
* @param {string} str
* @return {string}
* Unescape HTML string
* @param {string} str - string to unescape
* @return {string} unescaped string
*/
export function unescapeHtml(str) {
return str
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
}
const unescapeHtml = (str) =>
str
.replace('&amp;', '&')
.replace('&lt;', '<')
.replace('&gt;', '>')
.replace('&#39;', "'")
.replace('&quot;', '"')
/**
* Get element data attributes
* @param {DOMElement} node
* @return {Array} data
* @param {HTMLElement} node - node element
* @return {array} node data
*/
export function getNodeData(node) {
const getNodeData = (node) => {
// All attributes
const attributes = node.attributes;
const attributes = node.attributes
// Regex Pattern
const pattern = /^data\-(.+)$/;
const pattern = /^data\-(.+)$/
// Output
const data = {};
const data = {}
for (let i in attributes) {
if (!attributes[i]) {
continue;
continue
}
// Attributes name (ex: data-module)
let name = attributes[i].name;
let name = attributes[i].name
// This happens.
if (!name) {
continue;
continue
}
let match = name.match(pattern);
let match = name.match(pattern)
if (!match) {
continue;
continue
}
// If this throws an error, you have some
// serious problems in your HTML.
data[match[1]] = getData(node.getAttribute(name));
data[match[1]] = getData(node.getAttribute(name))
}
return data;
return data
}
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
/**
* Parse value to data type.
*
* @link https://github.com/jquery/jquery/blob/3.1.1/src/data.js
* @param {string} data - A value to convert.
* @return {mixed} Returns the value in its natural data type.
* @param {string} data - value to convert
* @return {mixed} value in its natural data type
*/
export function getData(data) {
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/
const getData = (data) => {
if (data === 'true') {
return true;
return true
}
if (data === 'false') {
return false;
return false
}
if (data === 'null') {
return 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 );
if (rbrace.test(data)) {
return JSON.parse(data)
}
return data;
return data
}
/**
* Returns an array containing all the parent nodes of the given node
* @param {object} node
* @return {array} parent nodes
* @param {HTMLElement} $el - DOM Element
* @return {array} parent nodes
*/
export function getParents(elem) {
const getParents = ($el) => {
// Set up a parent array
let parents = [];
let parents = []
// Push each parent element to the array
for ( ; elem && elem !== document; elem = elem.parentNode ) {
parents.push(elem);
for (; $el && $el !== document; $el = $el.parentNode) {
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/
export function queryClosestParent(elem, 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 ( ; elem && elem !== document; elem = elem.parentNode ) {
if ( elem.matches( selector ) ) return elem;
}
return null;
};
export { escapeHtml, unescapeHtml, getNodeData, getData, getParents }

View File

@@ -1,81 +1,97 @@
const LAZY_LOADED_IMAGES = []
/**
* Get an image meta data
*
* @param {HTMLImageElement} $img - The image element.
* @return {object} The given image meta data
*/
export function loadImage(url, options = {}) {
const getImageMetadata = ($img) => ({
url: $img.src,
width: $img.naturalWidth,
height: $img.naturalHeight,
ratio: $img.naturalWidth / $img.naturalHeight,
})
/**
* Load the given image.
*
* @param {string} url - The URI to lazy load into $el.
* @param {object} options - An object of options
* @return {void}
*/
const loadImage = (url, options = {}) => {
return new Promise((resolve, reject) => {
const $img = new Image();
const $img = new Image()
if (options.crossOrigin) {
$img.crossOrigin = options.crossOrigin;
$img.crossOrigin = options.crossOrigin
}
const loadCallback = () => {
resolve({
element: $img,
...getImageMetadata($img),
});
})
}
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) => {
reject(e);
};
reject(e)
}
$img.src = url
}
});
}
export function getImageMetadata($img) {
return {
url: $img.src,
width: $img.naturalWidth,
height: $img.naturalHeight,
ratio: $img.naturalWidth / $img.naturalHeight,
};
})
}
/**
* Lazy load the given image.
*
* @param {HTMLImageElement} $el - The image element.
* @param {?string} url - The URI to lazy load into $el.
* @param {HTMLImageElement} $el - The image element.
* @param {?string} url - The URI to lazy load into $el.
* If falsey, the value of the `data-src` attribute on $el will be used as the URI.
* @param {?function} callback - A function to call when the image is loaded.
* @param {?function} callback - A function to call when the image is loaded.
* @return {void}
*/
export async function lazyLoadImage($el, url, callback) {
const LAZY_LOADED_IMAGES = []
const lazyLoadImage = async ($el, url, callback) => {
let src = url ? url : $el.dataset.src
let 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)
if (!loadedImage.url) {
return;
return
}
LAZY_LOADED_IMAGES.push(loadedImage)
}
if($el.src === src) {
if ($el.src === src) {
return
}
if ($el.tagName === 'IMG') {
$el.src = loadedImage.url;
$el.src = loadedImage.url
} else {
$el.style.backgroundImage = `url(${loadedImage.url})`;
$el.style.backgroundImage = `url(${loadedImage.url})`
}
requestAnimationFrame(() => {
let lazyParent = $el.closest('.c-lazy');
let lazyParent = $el.closest('.c-lazy')
if(lazyParent) {
if (lazyParent) {
lazyParent.classList.add('-lazy-loaded')
lazyParent.style.backgroundImage = ''
}
@@ -85,3 +101,5 @@ export async function lazyLoadImage($el, url, callback) {
callback?.()
})
}
export { getImageMetadata, loadImage, lazyLoadImage }

View File

@@ -1,37 +1,21 @@
const toString = Object.prototype.toString;
const arrayLikePattern = /^\[object (?:Array|FileList)\]$/;
/**
* Determines if the argument is object-like.
*
* A value is object-like if it's not `null` and has a `typeof` result of "object".
*
* @param {*} x - The value to be checked.
* @return {boolean}
*/
// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
export function isArray ( thing ) {
return toString.call( thing ) === '[object Array]';
}
const isObject = (x) => x && typeof x === 'object'
export function isArrayLike ( obj ) {
return arrayLikePattern.test( toString.call( obj ) );
}
/**
* Determines if the argument is a function.
*
* @param {*} x - The value to be checked.
* @return {boolean}
*/
export function isEqual ( a, b ) {
if ( a === null && b === null ) {
return true;
}
const isFunction = (x) => typeof x === 'function'
if ( typeof a === 'object' || typeof b === 'object' ) {
return false;
}
return a === b;
}
// http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
export function isNumeric ( thing ) {
return !isNaN( parseFloat( thing ) ) && isFinite( thing );
}
export function isObject ( thing ) {
return ( thing && toString.call( thing ) === '[object Object]' );
}
export function isFunction( thing ) {
const getType = {};
return thing && getType.toString.call(thing) === '[object Function]';
}
export { isObject, isFunction }

View File

@@ -1,3 +1,45 @@
export function lerp(start, end, amt){
return (1 - amt) * start + amt * end
/**
* Clamp value
* @param {number} min - start value
* @param {number} max - end value
* @param {number} a - alpha value
* @return {number} clamped value
*/
const clamp = (min = 0, max = 1, a) => Math.min(max, Math.max(min, a))
/**
* Calculate lerp
* @param {number} x - start value
* @param {number} y - end value
* @param {number} a - alpha value
* @return {number} lerp value
*/
const lerp = (x, y, a) => x * (1 - a) + y * a
/**
* Calculate inverted lerp
* @param {number} x - start value
* @param {number} y - end value
* @param {number} a - alpha value
* @return {number} inverted lerp value
*/
const invlerp = (x, y, a) => clamp((v - x) / (a - x))
/**
* Round number to the specified precision.
*
* This function is necessary because `Number.prototype.toPrecision()`
* and `Number.prototype.toFixed()`
*
* @param {number} number - The floating point number to round.
* @param {number} [precision] - The number of digits to appear after the decimal point.
* @return {number} The rounded number.
*/
const roundNumber = (number, precision = 2) => {
return Number.parseFloat(number.toPrecision(precision))
}
export { clamp, lerp, invlerp, roundNumber }

View File

@@ -0,0 +1,73 @@
/**
* Creates a debounced function.
*
* A debounced function delays invoking `callback` until after
* `delay` milliseconds have elapsed since the last time the
* debounced function was invoked.
*
* Useful for behaviour that should only happen _before_ or
* _after_ an event has stopped occurring.
*
* @template {function} T
*
* @param {T} callback - The function to debounce.
* @param {number} delay - The number of milliseconds to wait.
* @param {boolean} [immediate] -
* If `true`, `callback` is invoked before `delay`.
* If `false`, `callback` is invoked after `delay`.
* @return {function<T>} The new debounced function.
*/
const debounce = (callback, delay, immediate = false) => {
let timeout = null
return (...args) => {
clearTimeout(timeout)
const later = () => {
timeout = null
if (!immediate) {
callback(...args)
}
}
if (immediate && !timeout) {
callback(...args)
}
timeout = setTimeout(later, delay)
}
}
/**
* Creates a throttled function.
*
* A throttled function invokes `callback` at most once per every
* `delay` milliseconds.
*
* Useful for rate-limiting an event that occurs in quick succession.
*
* @template {function} T
*
* @param {T} callback - The function to throttle.
* @param {number} delay - The number of milliseconds to wait.
* @return {function<T>} The new throttled function.
*/
const throttle = (callback, delay) => {
let timeout = false
return (...args) => {
if (!timeout) {
timeout = true
callback(...args)
setTimeout(() => {
timeout = false
}, delay)
}
}
}
export { debounce, throttle }

View File

@@ -1,21 +1,35 @@
export function transform(el, transformValue){
el.style.webkitTransform = transformValue;
el.style.msTransform = transformValue;
el.style.transform = transformValue;
/**
* Get translate function
* @param {HTMLElement} $el - DOM Element
* @return {number|object} translate value
*/
const getTranslate = ($el) => {
if (!window.getComputedStyle) {
return
}
let translate
const style = getComputedStyle($el)
const transform =
style.msTransform ||
style.webkitTransform ||
style.MozTransform ||
style.OTransform ||
style.transform
const matrix3D = transform.match(/^matrix3d\((.+)\)$/)
if (matrix3D) {
translate = parseFloat(matrix3D[1].split(', ')[13])
} else {
const matrix = transform.match(/^matrix\((.+)\)$/)
translate = {
x: matrix ? parseFloat(matrix[1].split(', ')[4]) : 0,
y: matrix ? parseFloat(matrix[1].split(', ')[5]) : 0,
}
}
return translate
}
export function getTranslate(el){
const translate = {}
if(!window.getComputedStyle) return;
const style = getComputedStyle(el);
const transform = style.transform || style.webkitTransform || style.mozTransform;
let mat = transform.match(/^matrix3d\((.+)\)$/);
if(mat) return parseFloat(mat[1].split(', ')[13]);
mat = transform.match(/^matrix\((.+)\)$/);
translate.x = mat ? parseFloat(mat[1].split(', ')[4]) : 0;
translate.y = mat ? parseFloat(mat[1].split(', ')[5]) : 0;
return translate;
}
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 {
@@ -58,16 +57,17 @@ $checkbox-icon-color: $input-icon-color;
padding-left: ($checkbox + rem(10px));
cursor: pointer;
&::before, &::after {
&::before,
&::after {
position: absolute;
top: 50%;
left: 0;
display: inline-block;
margin-top: (-$checkbox / 2);
margin-top: math.div(-$checkbox, 2);
padding: 0;
width: $checkbox;
height: $checkbox;
content: "";
content: '';
}
&::before {
@@ -78,7 +78,7 @@ $checkbox-icon-color: $input-icon-color;
&::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

@@ -16,7 +16,9 @@
transform: scaleX(1.45);
}
&:hover, .has-scroll-scrolling &, .has-scroll-dragging & {
&:hover,
.has-scroll-scrolling &,
.has-scroll-dragging & {
opacity: 1;
}
}

View File

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

View File

@@ -2,6 +2,21 @@
// Elements / Document
// ==========================================================================
:root {
// Grid
--grid-columns: 4;
--grid-gutter: #{rem(10px)};
--grid-gutter-half: calc(0.5 * var(--grid-gutter));
--grid-margin: 0px;
@media (min-width: $from-small) {
--grid-columns: 12;
--grid-gutter: #{rem(16px)};
--grid-margin: #{rem(20px)};
}
}
//
// Simple page-level setup.
//
// 1. Include web fonts
@@ -13,10 +28,9 @@
html {
min-height: 100%; // [2]
line-height: $line-height;
font-family: ff("sans");
color: $font-color;
line-height: $line-height; // [3]
font-family: ff('sans');
color: $font-color;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -54,6 +68,11 @@ html {
&.has-scroll-smooth {
overflow: hidden;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
&.has-scroll-dragging {

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,49 +1,50 @@
// ==========================================================================
// Main
// ==========================================================================
@use 'sass:math';
// Settings
// ==========================================================================
@import "settings/config.eases";
@import "settings/config.colors";
@import "settings/config";
@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/layout";
@import "tools/widths";
@import 'tools/maths';
@import 'tools/functions';
@import 'tools/mixins';
@import 'tools/fonts';
// @import "tools/layout";
// @import "tools/widths";
// @import "tools/family";
// Generic
// ==========================================================================
@import "node_modules/normalize.css/normalize";
@import "generic/generic";
@import "generic/media";
@import "generic/form";
@import "generic/button";
@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/scroll";
@import "objects/container";
@import "objects/ratio";
@import "objects/icons";
@import "objects/layout";
// @import "objects/crop";
@import 'objects/scroll';
@import 'objects/container';
@import 'objects/ratio';
@import 'objects/icons';
@import 'objects/grid';
// @import "objects/layout";
// @import "objects/table";
// Vendors
@@ -53,16 +54,17 @@
// Components
// ==========================================================================
@import "components/scrollbar";
@import "components/heading";
@import "components/button";
@import "components/form";
@import 'components/scrollbar';
@import 'components/heading';
@import 'components/button';
@import 'components/form';
// Utilities
// ==========================================================================
@import "utilities/ratio";
@import "utilities/widths";
@import 'utilities/ratio';
@import 'utilities/grid-column';
// @import "utilities/widths";
// @import "utilities/align";
// @import "utilities/helpers";
// @import "utilities/states";

View File

@@ -13,7 +13,6 @@
.o-container {
margin-right: auto;
margin-left: auto;
padding-right: rem($padding);
padding-left: rem($padding);
max-width: rem($container-width + ($padding * 2));
padding-right: $base-column-gap;
padding-left: $base-column-gap;
}

View File

@@ -1,83 +0,0 @@
// ==========================================================================
// Objects / Crop
// ==========================================================================
// @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.crop.scss
// A list of cropping ratios that get generated as modifier classes.
$crop-ratios: (
(2:1),
(4:3),
(16:9),
) !default;
// Provide a cropping container in order to display media (usually images)
// cropped to certain ratios.
//
// 1. Set up a positioning context in which the image can sit.
// 2. This is the crucial part: where the cropping happens.
.o-crop {
position: relative; // [1]
display: block;
overflow: hidden; // [2]
}
// Apply this class to the content (usually `img`) that needs cropping.
//
// 1. Images default positioning is top-left in the cropping box.
// 2. Make sure the media doesnt stop itself too soon.
.o-crop_content {
position: absolute;
top: 0; // [1]
left: 0; // [1]
max-width: none; // [2]
// We can position the media in different locations within the cropping area.
&.-right {
right: 0;
left: auto;
}
&.-bottom {
top: auto;
bottom: 0;
}
&.-center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
/* stylelint-disable */
// Generate a series of crop classes to be used like so:
//
// @example
// <div class="o-crop -16:9">
.o-crop {
@each $crop in $crop-ratios {
@each $antecedent, $consequent in $crop {
@if (type-of($antecedent) != number) {
@error "`#{$antecedent}` needs to be a number.";
}
@if (type-of($consequent) != number) {
@error "`#{$consequent}` needs to be a number.";
}
&.-#{$antecedent}\:#{$consequent} {
padding-bottom: ($consequent/$antecedent) * 100%;
}
}
}
}
/* stylelint-enable */

View File

@@ -0,0 +1,171 @@
// ==========================================================================
// Grid helper
// ==========================================================================
// Help: https://css-tricks.com/snippets/css/complete-guide-grid/
//
/**
* Usage:
*
* ```html
* <div class="o-grid -col-4 -col-12@from-medium -gutters">
* <div class="o-grid_item u-gc-1/2 u-gc-3/9@from-medium">
* <p>Hello</p>
* </div>
* <div class="o-grid_item u-gc-3/4 u-gc-9/13@from-medium">
* <p>Hello</p>
* </div>
* </div>
* ```
*/
.o-grid {
display: grid;
width: 100%;
&:is(ul, ol) {
margin: 0;
padding: 0;
list-style: none;
}
// ==========================================================================
// Cols
// ==========================================================================
&.-col-#{$base-column-nb} {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
&.-col-4 {
grid-template-columns: repeat(4, 1fr);
}
&.-col-#{$base-column-nb}\@from-medium {
@media (min-width: $from-medium) {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
}
// ==========================================================================
// Gutters
// ==========================================================================
// Gutters rows and columns
&.-gutters {
gap: $base-column-gap;
column-gap: $base-column-gap;
}
// ==========================================================================
// Modifiers
// ==========================================================================
&.-full-height {
height: 100%;
}
// ==========================================================================
// Aligns
// ==========================================================================
// ==========================================================================
// Items inside cells
//
&.-top-items {
align-items: start;
}
&.-right-items {
justify-items: end;
}
&.-bottom-items {
align-items: end;
}
&.-left-items {
justify-items: start;
}
&.-center-items {
align-items: center;
justify-items: center;
}
&.-center-items-x {
justify-items: center;
}
&.-center-items-y {
align-items: center;
}
&.-stretch-items {
align-items: stretch;
justify-items: stretch;
}
// ==========================================================================
// Cells
//
&.-top-cells {
align-content: start;
}
&.-right-cells {
justify-content: end;
}
&.-bottom-cells {
align-content: end;
}
&.-left-cells {
justify-content: start;
}
&.-center-cells {
align-content: center;
justify-content: center;
}
&.-center-cells-x {
justify-content: center;
}
&.-center-cells-y {
align-content: center;
}
&.-stretch-cells {
align-content: stretch;
justify-content: stretch;
}
&.-space-around-cells {
align-content: space-around;
justify-content: space-around;
}
&.-space-around-cells-x {
justify-content: space-around;
}
&.-space-around-cells-y {
align-content: space-around;
}
&.-space-between-cells {
justify-content: space-between;
align-content: space-between;
}
&.-space-between-cells-x {
justify-content: space-between;
}
&.-space-between-cells-y {
align-content: space-between;
}
&.-space-evenly-cells {
justify-content: space-evenly;
align-content: space-evenly;
}
&.-space-evenly-cells-x {
justify-content: space-evenly;
}
&.-space-evenly-cells-y {
align-content: space-evenly;
}
}
// ==========================================================================
// Grid item
// ==========================================================================
// By default, a grid item takes full width of its parent.
//
.o-grid_item {
grid-column: 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
@@ -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

@@ -5,25 +5,25 @@
// Palette
// =============================================================================
$color-lightest: #FFFFFF;
$color-darkest: #000000;
$color-lightest: #ffffff;
$color-darkest: #000000;
// Specific
// =============================================================================
// Link
$color-link: #1A0DAB;
$color-link-focus: #1A0DAB;
$color-link-hover: darken(#1A0DAB, 10%);
$color-link: #1a0dab;
$color-link-focus: #1a0dab;
$color-link-hover: darken(#1a0dab, 10%);
// Selection
$selection-text-color: #3297FD;
$selection-background-color: #FFFFFF;
$selection-text-color: #3297fd;
$selection-background-color: #ffffff;
// Social Colors
// =============================================================================
$color-facebook: #3B5998;
$color-instagram: #E1306C;
$color-youtube: #CD201F;
$color-twitter: #1DA1F2;
$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,9 +33,7 @@ $font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console
// <font-id>: (<font-name>, <font-fallbacks>)
// ```
$font-families: (
"sans": ("Webfont Sans", $font-fallback-sans),
// "serif": ("Webfont Serif", $font-fallback-serif),
// "mono": ("Webfont Mono", $font-fallback-mono)
sans: join('Source Sans', $font-fallback-sans, $separator: comma),
);
// List of custom font faces as tuples.
@@ -39,9 +42,10 @@ $font-families: (
// <font-name> <font-file-basename> <font-weight> <font-style>
// ```
$font-faces: (
// "Webfont Sans" "webfont-sans_regular" 400 normal,
// "Webfont Sans" "webfont-sans_regular-italic" 400 italic,
// "Webfont Serif" "webfont-sans_bold" 700 normal,
('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
@@ -49,77 +53,71 @@ $font-faces: (
// Base
$font-size: 16px;
$line-height: 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;
$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-small: 30px;
$unit: 60px;
$unit-small: 20px;
// Container
// =============================================================================
// ==========================================================================
$padding: $unit;
$container-width: 2000px;
$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: (
"goku": 9000,
"transition": 500,
"toast": 400,
"popover": 300,
"modal": 250,
"sheet": 200,
"fixed": 150,
"sticky": 100,
"dropdown": 50,
"default": 1,
"limbo": -999
'header': 200,
'above': 1,
'below': -1,
);

View File

@@ -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)};
}
@@ -32,8 +32,14 @@
// @output The `@font-face` at-rules specifying the custom fonts.
@mixin font-faces($webfonts, $dir) {
@each $webfont in $webfonts {
@include font-face($webfont, $dir);
@if (length($webfonts) > 0) {
@if (type-of(nth($webfonts, 1)) == 'list') {
@each $webfont in $webfonts {
@include font-face($webfont, $dir);
}
} @else {
@include font-face($webfonts, $dir);
}
}
}
@@ -47,8 +53,8 @@
@function ff($font-family) {
@if not map-has-key($font-families, $font-family) {
@error "No font-family found in $font-families map for `#{$font-family}`.";
}
@error "No font-family found in $font-families map for `#{$font-family}`.";
}
$value: map-get($font-families, $font-family);
@return $value;

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.
@@ -26,7 +26,7 @@
@error "`#{$base}` needs to be a number in pixel.";
}
@return ($size / $base) * 1em;
@return math.div($size, $base) * 1em;
}
// Converts the given pixel value to its REM 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.";
}
@@ -45,7 +44,7 @@
@error "`#{$base}` needs to be a number in pixel.";
}
@return ($size / $base) * 1rem;
@return math.div($size, $base) * 1rem;
}
// Retrieves the z-index from the {@see $layers master list}.
@@ -60,7 +59,7 @@
// @return {number} The computed z-index of $layer and $modifier.
@function z($layer, $modifier: 0) {
@if not map-has-key($layers, $layer) {
@if not map-has-key($z-indexes, $layer) {
@error "Unknown master z-index layer: #{$layer}";
}
@@ -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;
}
}

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($font-size / $line-height) * ($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;
}
@elseif ($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

@@ -0,0 +1,63 @@
// ==========================================================================
// Tools / Grid Columns
// ==========================================================================
//
// Grid layout system.
//
// This tool generates columns for all needed media queries.
// Unused classes will be purge by the css post-processor.
//
$colsMax: $base-column-nb + 1;
$breakpoints: (
'null' null,
'from-tiny' 'from-tiny',
'from-small' 'from-small',
'from-medium' 'from-medium',
'from-large' 'from-large',
'from-big' 'from-big'
) !default;
@each $breakpoint-namespace, $breakpoint in $breakpoints {
@for $fromIndex from 1 through $colsMax {
@for $toIndex from 1 through $colsMax {
@if $breakpoint == null {
.u-gc-#{$fromIndex}\/#{$toIndex} {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else {
.u-gc-#{$fromIndex}\/#{$toIndex}\@#{$breakpoint} {
@if $breakpoint-namespace == 'from-tiny' {
@media (min-width: $from-tiny) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-small' {
@media (min-width: $from-small) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-medium' {
@media (min-width: $from-medium) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-large' {
@media (min-width: $from-large) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
} @else if $breakpoint-namespace == 'from-big' {
@media (min-width: $from-big) {
grid-column-start: #{$fromIndex};
grid-column-end: #{$toIndex};
}
}
}
}
}
}
}

View File

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

View File

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

View File

@@ -6,9 +6,15 @@
// A list of aspect ratios that get generated as modifier classes.
$aspect-ratios: (
(2:1),
(4:3),
(16:9),
(
2: 1,
),
(
4: 3,
),
(
16: 9,
)
) !default;
/* stylelint-disable */
@@ -21,15 +27,15 @@ $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 {
padding-bottom: ($consequent/$antecedent) * 100%;
.u-#{$antecedent}\:#{$consequent}::before {
padding-bottom: math.div($consequent, $antecedent) * 100%;
}
}
}

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,9 +1,11 @@
import concatFiles from './tasks/concats.js';
import compileScripts from './tasks/scripts.js';
import compileStyles from './tasks/styles.js' ;
import compileSVGs from './tasks/svgs.js' ;
import compileStyles from './tasks/styles.js';
import compileSVGs from './tasks/svgs.js';
import bumpVersions from './tasks/versions.js';
concatFiles();
compileScripts();
compileStyles();
compileSVGs();
bumpVersions();

View File

@@ -6,7 +6,8 @@ import resolve from '../utils/template.js';
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import sass from 'node-sass';
import sass from 'sass';
import { PurgeCSS } from 'purgecss';
const sassRender = promisify(sass.render);
@@ -22,6 +23,7 @@ export const defaultSassOptions = {
sourceMap: true,
sourceMapContents: true,
};
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
outputStyle: 'expanded',
});
@@ -142,7 +144,12 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
}
try {
await writeFile(outfile, result.css);
await writeFile(outfile, result.css).then(() => {
// Purge CSS once file exists.
if (outfile) {
purgeUnusedCSS(outfile, `${label || `${filestem}.css`}`);
}
});
if (result.css) {
message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel);
@@ -212,3 +219,37 @@ function createPostCSSProcessor(pluginsListOrMap, options)
return postcss(plugins);
}
/**
* Purge unused styles from CSS files.
*
* @async
*
* @param {string} outfile - The path of a css file
* If missing the function stops.
* @param {string} label - The CSS file label or name.
* @return {Promise}
*/
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({
content: purgeCSSContentFiles,
css: [ outfile ],
rejected: true,
defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
safelist: {
standard: [ /^((?!\bu-gc-).)*$/ ]
}
})
for(let result of purgeCSSResults) {
await writeFile(outfile, result.css)
message(`${label} purged`, 'chore', timeLabel);
}
}

315
build/tasks/versions.js Normal file
View File

@@ -0,0 +1,315 @@
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 {
createReadStream,
createWriteStream,
} from 'node:fs';
import {
mkdir,
rename,
rm,
readFile,
writeFile,
} from 'node:fs/promises';
import {
basename,
dirname,
} from 'node:path';
import readline from 'node:readline';
/**
* @typedef {object} VersionOptions
* @property {string|number|null} prettyPrint - A string or number to insert
* white space into the output JSON string for readability purposes.
* @property {string} versionFormat - The version number format.
* @property {string|RegExp} versionKey - Either:
* - A string representing the JSON field name assign the version number to.
*
* Explicit:
*
* ```json
* "key": "json:version"
* ```
*
* Implicit:
*
* ```json
* "key": "version"
* ```
*
* - A `RegExp` object or regular expression string prefixed with `regexp:`.
*
* ```json
* "key": "regexp:(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)"
* ```
*
* ```js
* key: new RegExp('(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)')
* ```
*
* ```js
* key: /(?<=^const ASSETS_VERSION = ')(?<version>\d+)(?=';$)/
* ```
*/
/**
* @const {VersionOptions} defaultVersionOptions - The default shared version options.
* @const {VersionOptions} developmentVersionOptions - The predefined version options for development.
* @const {VersionOptions} productionVersionOptions - The predefined version options for production.
*/
export const defaultVersionOptions = {
prettyPrint: 4,
versionFormat: 'timestamp',
versionKey: 'version',
};
export const developmentVersionOptions = Object.assign({}, defaultVersionOptions);
export const productionVersionOptions = Object.assign({}, defaultVersionOptions);
/**
* @const {object} developmentVersionFilesArgs - The predefined `bumpVersion()` options for development.
* @const {object} productionVersionFilesArgs - The predefined `bumpVersion()` options for production.
*/
export const developmentVersionFilesArgs = [
developmentVersionOptions,
];
export const productionVersionFilesArgs = [
productionVersionOptions,
];
/**
* Bumps version numbers in file.
*
* @async
* @param {object} [versionOptions=null] - Customize the version options.
* If `null`, default production options are used.
* @return {Promise}
*/
export default async function bumpVersions(versionOptions = null) {
if (versionOptions == null) {
versionOptions = productionVersionOptions;
} else if (
versionOptions !== developmentVersionOptions &&
versionOptions !== productionVersionOptions
) {
versionOptions = Object.assign({}, defaultVersionOptions, versionOptions);
}
const queue = new Map();
loconfig.tasks.versions.forEach(({
outfile,
label = null,
...options
}) => {
if (!label) {
label = basename(outfile || 'undefined');
}
options.pretty = (options.pretty ?? versionOptions.prettyPrint);
options.format = (options.format ?? versionOptions.versionFormat);
options.key = (options.key ?? versionOptions.versionKey);
if (queue.has(outfile)) {
queue.get(outfile).then(() => handleBumpVersion(outfile, label, options));
} else {
queue.set(outfile, handleBumpVersion(outfile, label, options));
}
});
};
/**
* Creates a formatted version number or string.
*
* @param {string} format - The version format.
* @return {string|number}
*/
function createVersionNumber(format) {
let [ type, modifier ] = format.split(':', 2);
switch (type) {
case 'hex':
case 'hexadecimal':
modifier = Number.parseInt(modifier);
if (Number.isNaN(modifier)) {
modifier = 6;
}
return randomBytes(modifier).toString('hex');
case 'regex':
return new RegExp(modifier);
case 'timestamp':
return Date.now().valueOf();
}
throw new TypeError(
'Expected \'format\' to be either "timestamp" or "hexadecimal"'
);
}
/**
* @async
* @param {string} outfile
* @param {string} label
* @param {object} options
* @return {Promise}
*/
async function handleBumpVersion(outfile, label, options) {
const timeLabel = `${label} bumped in`;
console.time(timeLabel);
try {
options.key = parseVersionKey(options.key);
if (options.key instanceof RegExp) {
await handleBumpVersionWithRegExp(outfile, label, options);
} else {
await handleBumpVersionInJson(outfile, label, options);
}
message(`${label} bumped`, 'success', timeLabel);
} catch (err) {
message(`Error bumping ${label}`, 'error');
message(err);
notification({
title: `${label} bumping failed 🚨`,
message: `${err.name}: ${err.message}`
});
}
}
/**
* Changes the version number for the provided JSON key in file.
*
* @async
* @param {string} outfile
* @param {string} label
* @param {object} options
* @param {string} options.key
* @return {Promise}
*/
async function handleBumpVersionInJson(outfile, label, options) {
outfile = resolve(outfile);
let json;
try {
json = JSON.parse(await readFile(outfile, { encoding: 'utf8' }));
} catch (err) {
json = {};
message(`${label} is a new file`, 'notice');
await mkdir(dirname(outfile), { recursive: true });
}
const version = createVersionNumber(options.format);
json[options.key] = version;
return await writeFile(
outfile,
JSON.stringify(json, null, options.pretty),
{ encoding: 'utf8' }
);
}
/**
* Changes the version number for the provided RegExp in file.
*
* @async
* @param {string} outfile
* @param {string} label
* @param {object} options
* @param {RegExp} options.key
* @return {Promise}
*/
async function handleBumpVersionWithRegExp(outfile, label, options) {
outfile = resolve(outfile);
const bckfile = `${outfile}~`;
await rename(outfile, bckfile);
try {
const rl = readline.createInterface({
input: createReadStream(bckfile),
});
const version = createVersionNumber(options.format);
const writeStream = createWriteStream(outfile, { encoding: 'utf8' });
rl.on('line', (line) => {
const found = line.match(options.key);
if (found) {
const groups = (found.groups ?? {});
const replace = found[0].replace(
(groups.build ?? groups.version ?? found[1] ?? found[0]),
version
);
line = line.replace(found[0], replace);
}
writeStream.write(line + "\n");
});
await events.once(rl, 'close');
await rm(bckfile);
} catch (err) {
await rm(outfile, { force: true });
await rename(bckfile, outfile);
throw err;
}
}
/**
* Parses the version key.
*
* @param {*} key - The version key.
* @return {string|RegExp}
*/
function parseVersionKey(key) {
if (key instanceof RegExp) {
return key;
}
if (typeof key !== 'string') {
throw new TypeError(
'Expected \'key\' to be either a string or a RegExp'
);
}
const delimiter = key.indexOf(':');
if (delimiter === -1) {
// Assumes its a JSON key
return key;
}
const type = key.slice(0, delimiter);
const value = key.slice(delimiter + 1);
switch (type) {
case 'json':
return value;
case 'regex':
case 'regexp':
return new RegExp(value);
}
throw new TypeError(
'Expected \'key\' type to be either "json" or "regexp"'
);
}

View File

@@ -2,17 +2,19 @@
* @file Provides simple user configuration options.
*/
import loconfig from '../../loconfig.json';
import loconfig from '../../loconfig.json' assert { type: 'json' };
let usrconfig;
try {
usrconfig = await import('../../loconfig.local.json');
usrconfig = await import('../../loconfig.local.json', {
assert: { type: 'json' }
});
usrconfig = usrconfig.default;
merge(loconfig, usrconfig);
} catch (err) {
// do nothing
console.error(err);
}
export default loconfig;

View File

@@ -17,6 +17,10 @@ export default function message(text, type, timerID) {
console.log('✅ ', kleur.bgGreen().black(text));
break;
case 'chore':
console.log('🧹 ', kleur.bgGreen().black(text));
break;
case 'notice':
console.log(' ', kleur.bgBlue().black(text));
break;

View File

@@ -113,7 +113,8 @@ function createServerOptions({
}) {
const config = {
open: false,
notify: false
notify: false,
ghostMode: false
};
// Resolve the URL for the BrowserSync server

View File

@@ -15,6 +15,7 @@
* [`scripts`](#scripts)
* [`styles`](#styles)
* [`svgs`](#svgs)
* [`versions`](#versions)
---
@@ -28,7 +29,9 @@ Learn more about the boilerplate's [tasks](#tasks) below.
Make sure you have the following installed:
* [Node] — at least 14.17, the latest LTS is recommended.
* [NPM] — at least 6.0, the latest LTS is recommended.
* [NPM] — at least 8.0, the latest LTS is recommended.
> 💡 You can use [NVM] to install and use different versions of Node via the command-line.
```sh
# Switch to recommended Node version from .nvmrc
@@ -262,8 +265,8 @@ See [`scripts.js`](../build/tasks/scripts.js) for details.
### `styles`
A wrapper around [node-sass] (and optionally [Autoprefixer] via [PostCSS])
for compiling and minifying Sass into CSS.
A wrapper around [sass] (with optional support for [Autoprefixer]
via [PostCSS]) for compiling and minifying Sass into CSS.
By default, [PostCSS] and [Autoprefixer] are installed with the boilerplate.
@@ -298,6 +301,9 @@ Example:
See [`styles.js`](../build/tasks/styles.js) for details.
The task also supports [PurgeCSS] to remove unused CSS.
See the [documentation on our Grid System](grid.md#build-tasks) for details.
### `svgs`
A wrapper around [SVG Mixer] for transforming and minifying SVG files
@@ -328,6 +334,80 @@ Example:
See [`svgs.js`](../build/tasks/svgs.js) for details.
### `versions`
A task to create and update values for use in versioning assets.
Can generate a hexadecimal value (using random bytes) or
use the current timestamp.
Example:
```json
{
"versions": [
{
"format": "timestamp",
"key": "now",
"outfile": "./assets.json"
},
{
"format": "hex:8",
"key": "hex",
"outfile": "./assets.json"
}
]
}
```
```json
{
"now": 1665071717350,
"hex": "6ef54181c4ba"
}
```
The task supports replacing the value of a data key in a JSON file or replacing
a string in a file using a [regular expression](RegExp).
* Explicit JSON field name:
```json
{
"key": "json:version"
}
```
* Implicit JSON field name:
```json
{
"key": "version"
}
```
The regular expression can be a `RegExp` object or a pattern prefixed with `regexp:`.
* ```json
{
"key": "regexp:(?<=^const ASSETS_VERSION = ')(?<build>\\d+)(?=';$)"
}
```
* ```js
{
key: new RegExp('(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)')
}
```
* ```js
{
key: /^ \* Version: +(?:.+?)\+(.+?)$/
}
```
The regular expression pattern will match the first occurrence and replace
the first match in the following order: `build` (named capture), `version`
(named capture), `1` (first capture), or `0` (whole match).
See [`versions.js`](../build/tasks/versions.js) for details.
[Autoprefixer]: https://npmjs.com/package/autoprefixer
[BrowserSync]: https://npmjs.com/package/browser-sync
[concat]: https://npmjs.com/package/concat
@@ -336,9 +416,11 @@ See [`svgs.js`](../build/tasks/svgs.js) for details.
[glob]: https://npmjs.com/package/glob
[globby]: https://npmjs.com/package/globby
[Node]: https://nodejs.org/
[node-sass]: https://npmjs.com/package/node-sass
[sass]: https://npmjs.com/package/sass
[NPM]: https://npmjs.com/
[NVM]: https://github.com/nvm-sh/nvm
[PostCSS]: https://npmjs.com/package/postcss
[PurgeCSS]: https://purgecss.com/
[RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
[SVG Mixer]: https://npmjs.com/package/svg-mixer
[tiny-glob]: https://npmjs.com/package/tiny-glob

109
docs/grid.md Normal file
View File

@@ -0,0 +1,109 @@
# Grid system
* [Architectures](#architecture)
* [Build tasks](#build-tasks)
* [Configuration](#configuration)
* [Usage](#usage)
* [Example](#example)
## Architecture
The boilerplate's grid system is meant to be simple and easy to use. The goal is to create a light, flexible, and reusable way to build layouts.
The following styles are needed to work properly:
* [`o-grid`](../assets/styles/objects/_grid.scss) — Object file where the default grid styles are set such as column numbers, modifiers, and options.
* [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) — Utility file that generates the styles for every possible column based on an array of media queries and column numbers.
### Build tasks
The columns generated by [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) adds a lot of styles to the compiled CSS file. To mitigate that, [PurgeCSS] is integrated into the `styles` build task to purge unused CSS.
#### Configuration
Depending on your project, you will need to specify all the files that include CSS classes from the grid system. These files will be scanned by [PurgeCSS] to your compiled CSS files.
Example of a Charcoal project:
```jsonc
"purgeCSS": {
"content": [
"./views/app/template/**/*.mustache",
"./src/App/Template/*.php",
"./assets/scripts/**/*" // use case: `el.classList.add('u-gc-1/2')`
]
}
```
## Usage
The first step is to set intial SCSS values in the following files :
- [`settings/_config.scss`](../assets/styles/settings/_config.scss)
```scss
// Grid
// ==========================================================================
$base-column-nb: 12;
$base-column-gap: $unit-small;
```
You can create multiple column layouts depending on media queries.
- [`objects/_grid.scss`](../assets/styles/objects/_grid.scss)
```scss
.o-grid {
display: grid;
width: 100%;
margin: 0;
padding: 0;
list-style: none;
// ==========================================================================
// Cols
// ==========================================================================
&.-col-#{$base-column-nb} {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
&.-col-4 {
grid-template-columns: repeat(4, 1fr);
}
&.-col-#{$base-column-nb}\@from-medium {
@media (min-width: $from-medium) {
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
}
}
// …
```
### Example
The following layout has 4 columns at `>=999px` and 12 columns at `<1000px`.
```html
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
<div class="o-grid -col-4 -col-12@from-medium -gutters">
<div class="o-grid_item u-gc-1/8@from-medium">
<h2 class="c-heading -h2">This grid has 4 columns and 12 columns from `medium` MQ</h2>
</div>
<div class="o-grid_item u-gc-1/5@from-medium">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
<div class="o-grid_item u-gc-5/9@from-medium">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
<div class="o-grid_item u-gc-9/13@from-medium">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
</div>
</div>
```
[PurgeCSS]: https://purgecss.com/

View File

@@ -19,7 +19,7 @@
"dest": "./www/assets/images"
},
"views": {
"src": "./views/boilerplate/template"
"src": "./www/"
}
},
"tasks": {
@@ -56,6 +56,17 @@
],
"outfile": "{% paths.svgs.dest %}/sprite.svg"
}
],
"purgeCSS": {
"content": [
"./www/**/*.html",
"./assets/scripts/**/*"
]
},
"versions": [
{
"outfile": "./assets.json"
}
]
}
}

6525
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,30 +6,38 @@
"author": "Locomotive <info@locomotive.ca>",
"type": "module",
"engines": {
"node": ">=14.17",
"npm": ">=6.0"
"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": "^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.4",
"browser-sync": "^2.27.9",
"autoprefixer": "^10.4.13",
"browser-sync": "^2.27.11",
"concat": "^1.0.3",
"esbuild": "^0.14.27",
"kleur": "^4.1.4",
"esbuild": "^0.16.17",
"kleur": "^4.1.5",
"node-notifier": "^10.0.1",
"node-sass": "^7.0.1",
"postcss": "^8.4.12",
"svg-mixer": "^2.3.14",
"postcss": "^8.4.21",
"prettier": "^2.8.3",
"purgecss": "^5.0.0",
"svg-mixer": "~2.3.14",
"tiny-glob": "^0.2.9"
},
"overrides": {
"svg-mixer": {
"postcss": "^8.4.20"
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,7 @@
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="grid.html">Grid</a></li>
</ul>
</nav>
</header>

96
www/grid.html Normal file
View File

@@ -0,0 +1,96 @@
<!doctype html>
<html class="is-loading" lang="en" data-page="grid">
<head>
<meta charset="utf-8">
<title>Locomotive Boilerplate</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<meta name="msapplication-TileColor" content="#ffffff">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
<!-- For a dark mode managment and svg favicon add this in your favicon.svg -->
<!--
<style>
path {
fill: #000;
}
@media (prefers-color-scheme: dark) {
path {
fill: #fff;
}
}
</style>
-->
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
onload="this.media='all'; this.onload=null; this.isLoaded=true">
</head>
<body data-module-load>
<div data-load-container>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/">
<h1>Locomotive Boilerplate</h1>
</a>
<nav>
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="grid.html">Grid</a></li>
</ul>
</nav>
</header>
<main data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
<div class="o-grid -col-4 -col-12@from-medium -gutters">
<div class="o-grid_item u-gc-1/8@from-medium" style="background-color: gray;">
<h2 class="c-heading -h2">This grid has 4 columns and 12 columns from `medium` MQ</h2>
</div>
<div class="o-grid_item u-gc-1/5@from-medium" style="background-color: yellow;">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
<div class="o-grid_item u-gc-5/9@from-medium" style="background-color: red;">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
<div class="o-grid_item u-gc-9/13@from-medium" style="background-color: blue;">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
<div class="o-grid_item u-gc-1/3 u-gc-5/13@from-medium" style="background-color: pink;">
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Expedita provident distinctio deleniti eaque cumque doloremque aut quo dicta porro commodi, temporibus totam dolor autem tempore quasi ullam sed suscipit vero?</p>
</div>
</div>
</div>
</main>
<footer 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>
</div>
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js"
crossorigin="anonymous"></script>
<script nomodule
src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController"
crossorigin="anonymous"></script>
<script src="assets/scripts/vendors.js" defer></script>
<script src="assets/scripts/app.js" defer></script>
</body>
</html>

View File

@@ -25,6 +25,7 @@
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="grid.html">Grid</a></li>
</ul>
</nav>
</header>
@@ -39,8 +40,8 @@
<h3 class="c-heading -h3">Basic</h3>
<div style="width: 640px; max-width: 100%;">
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
</div>
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
@@ -58,30 +59,30 @@
<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/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
<div class="o-ratio u-4:3">
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/800/600?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
</div>
</div>
<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/640/480?v=1"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=2"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=3"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=4"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=5"></div>
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/1280/720?v=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>

View File

@@ -29,11 +29,18 @@
-->
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
<!-- Preload Fonts -->
<link rel="preload" href="assets/fonts/SourceSans3-Bold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-BoldIt.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-RegularIt.woff2" as="font" type="font/woff2" crossorigin>
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
onload="this.media='all'; this.onload=null; this.isLoaded=true">
</head>
<body data-module-load>
<div data-load-container>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
@@ -44,11 +51,12 @@
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="grid.html">Grid</a></li>
</ul>
</nav>
</header>
<main data-scroll-section>
<main data-module-example data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
</div>