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

Merge branch 'master' into feature/es6-updates

This commit is contained in:
arnvvd
2022-10-05 16:43:57 -04:00
40 changed files with 2465 additions and 827 deletions

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

View File

View File

@@ -3,6 +3,7 @@ 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
@@ -24,6 +25,11 @@ window.onload = (event) => {
}
};
export const EAGER_FONTS = [
{ family: 'Source Sans', style: 'normal', weight: 400 },
{ family: 'Source Sans', style: 'normal', weight: 700 },
];
function init() {
globals();
@@ -32,5 +38,21 @@ function init() {
html.classList.add(config.CSS_CLASS.LOADED);
html.classList.add(config.CSS_CLASS.READY);
html.classList.remove(config.CSS_CLASS.LOADING);
/**
* Eagerly load the following fonts.
*/
if (isFontLoadingAPIAvailable) {
loadFonts(EAGER_FONTS).then((eagerFonts) => {
html.classList.add('fonts-loaded');
// 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()
});
}
}

View File

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

View File

@@ -1,4 +1,6 @@
import { module } from 'modujs';
import { EAGER_FONTS } from '../app';
import { whenReady } from '../utils/fonts';
export default class extends module {
constructor(m) {
@@ -6,5 +8,10 @@ export default class extends module {
}
init() {
whenReady(EAGER_FONTS).then((fonts) => this.onFontsLoaded(fonts));
}
onFontsLoaded(fonts) {
console.log('Example: Eager Fonts Loaded!', fonts)
}
}

View File

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

View File

@@ -1,129 +1,118 @@
import { isFunction } from './is';
import { arrayContains, findByKeyValue, removeFromArray } from './array';
import { $document, $window, $html, $body } from './environment';
const CALLBACKS = {
hidden: [],
visible: []
};
const ACTIONS = [
'addCallback',
'removeCallback'
];
const STATES = [
'visible',
'hidden'
];
const PREFIX = 'v-';
let UUID = 0;
// Main event
$document.on('visibilitychange', function(event) {
if (document.hidden) {
onDocumentChange('hidden');
} else {
onDocumentChange('visible');
}
});
/**
* Add a callback
* @param {string} state
* @param {function} callback
* @return {string} ident
* The `PageVisibility` interface provides support for dispatching
* a custom event derived from the value of {@see document.visibilityState}
* when the "visibilitychange" event is fired.
*
* The custom events are:
*
* - "visibilityhidden" representing the "hidden" visibility state.
* - "visibilityvisible" representing the "visibile" visibility state.
*
* Example:
*
* ```js
* import pageVisibility from './utils/visibility.js';
*
* pageVisibility.enableCustomEvents();
*
* document.addEventListener('visibilityhidden', () => videoElement.pause());
* ```
*
* The dispatched event object is the same from "visibilitychange"
* and renamed according to the visibility state.
*
* The `PageVisibility` interface does not manage the attachment/detachment
* of event listeners on the custom event types.
*
* Further reading:
*
* - {@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}
*/
function addCallback (state, options) {
let callback = options.callback || '';
export default new class PageVisibility {
/**
* Checks if the "visibilitychange" event listener has been registered.
*
* @return {boolean} Returns `false` if the event listener is not registered,
* otherwise returns `true`.
*/
get isEnabled() {
return isVisibilityChangeObserved;
}
/**
* Removes the "visibilitychange" event listener.
*
* @return {boolean} Returns `false` if the event listener was already unregistered,
* otherwise returns `true`.
*/
disableCustomEvents() {
if (isVisibilityChangeObserved) {
isVisibilityChangeObserved = false;
document.removeEventListener('visibilitychange', handleCustomVisibilityChange);
return true;
}
if (!isFunction(callback)) {
console.warn('Callback is not a function');
return false;
}
let ident = PREFIX + UUID++;
/**
* Registers the "visibilitychange" event listener.
*
* @return {boolean} Returns `false` if the event listener was already registered,
* otherwise returns `true`.
*/
enableCustomEvents() {
if (!isVisibilityChangeObserved) {
isVisibilityChangeObserved = true;
document.addEventListener('visibilitychange', handleCustomVisibilityChange);
return true;
}
CALLBACKS[state].push({
ident: ident,
callback: callback
});
return ident;
}
/**
* Remove a callback
* @param {string} state Visible or hidden
* @param {string} ident Unique identifier
* @return {boolean} If operation was a success
*/
function removeCallback (state, options) {
let ident = options.ident || '';
if (typeof(ident) === 'undefined' || ident === '') {
console.warn('Need ident to remove callback');
return false;
}
let index = findByKeyValue(CALLBACKS[state], 'ident', ident)[0];
// console.log(ident)
// console.log(CALLBACKS[state])
if (typeof(index) !== 'undefined') {
removeFromArray(CALLBACKS[state], index);
return true;
} else {
console.warn('Callback could not be found');
return false;
}
}
/**
* When document state changes, trigger callbacks
* @param {string} state Visible or hidden
* Tracks whether custom visibility event types
* are available (`true`) or not (`false`).
*
* @type {boolean}
*/
function onDocumentChange (state) {
let callbackArray = CALLBACKS[state];
let i = 0;
let len = callbackArray.length;
let isVisibilityChangeObserved = false;
for (; i < len; i++) {
callbackArray[i].callback();
}
/**
* Dispatches a custom visibility event at the document derived
* from the value of {@see document.visibilityState}.
*
* @listens document#visibilitychange
*
* @fires PageVisibility#visibilityhidden
* @fires PageVisibility#visibilityvisible
*
* @param {Event} event
* @return {void}
*/
function handleCustomVisibilityChange(event) {
document.dispatchEvent(new CustomEvent(`visibility${document.visibilityState}`, {
detail: {
cause: event
}
}));
}
/**
* Public facing API for adding and removing callbacks
* @param {object} options Options
* @return {boolean|integer} Unique identifier for the callback or boolean indicating success or failure
* The "visibilityhidden" eveent is fired at the document when the contents
* of its tab have become hidden.
*
* @event PageVisibility#visibilityhidden
* @type {Event}
*/
function visibilityApi (options) {
let action = options.action || '';
let state = options.state || '';
let ret;
// Type and value checking
if (!arrayContains(ACTIONS, action)) {
console.warn('Action does not exist');
return false;
}
if (!arrayContains(STATES, state)) {
console.warn('State does not exist');
return false;
}
// @todo Magic call function pls
if (action === 'addCallback') {
ret = addCallback(state, options);
} else if (action === 'removeCallback') {
ret = removeCallback(state, options);
}
return ret;
}
export { visibilityApi };
/**
* The "visibilityvisible" eveent is fired at the document when the contents
* of its tab have become visible.
*
* @event PageVisibility#visibilityvisible
* @type {Event}
*/

View File

@@ -27,7 +27,7 @@ $input-icon-color: 424242; // No #
.c-form_input {
padding: rem(10px);
border: 1px solid lightgray;
background-color: white;
background-color: $color-lightest;
&:hover {
border-color: darkgray;
@@ -71,7 +71,7 @@ $checkbox-icon-color: $input-icon-color;
}
&::before {
background-color: $white;
background-color: $color-lightest;
border: 1px solid lightgray;
}

View File

@@ -25,7 +25,7 @@
position: absolute;
top: 0;
right: 0;
background-color: black;
background-color: $color-darkest;
opacity: 0.5;
width: 7px;
border-radius: 10px;

View File

@@ -15,7 +15,7 @@ html {
min-height: 100%; // [2]
line-height: $line-height;
font-family: ff("sans");
color: $color;
color: $font-color;
line-height: $line-height; // [3]
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -74,9 +74,9 @@ body {
}
a {
color: $link-color;
color: $color-link;
@include u-hocus {
color: $link-hover-color;
color: $color-link-hover;
}
}

View File

@@ -33,7 +33,7 @@ i {
b,
strong {
font-weight: $bold;
font-weight: $font-weight-bold;
}
a {

View File

@@ -13,11 +13,12 @@
// Tools
// ==========================================================================
@import "tools/maths";
@import "tools/functions";
@import "tools/mixins";
@import "tools/fonts";
@import "tools/layout";
@import "tools/widths";
// @import "tools/layout";
// @import "tools/widths";
// @import "tools/family";
// Generic
@@ -40,7 +41,9 @@
@import "objects/scroll";
@import "objects/container";
@import "objects/ratio";
@import "objects/layout";
@import "objects/icons";
@import "objects/grid";
// @import "objects/layout";
// @import "objects/crop";
// @import "objects/table";
@@ -56,15 +59,12 @@
@import "components/button";
@import "components/form";
// Templates
// ==========================================================================
// @import "templates/template";
// Utilities
// ==========================================================================
@import "utilities/ratio";
@import "utilities/widths";
@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

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

@@ -0,0 +1,58 @@
// ==========================================================================
// Objects / SVG Icons
// ==========================================================================
// Markup
//
// 1. If icon is accessible and has a title
// 2. If icon is decorative
//
// <i class="o-icon ${modifier}">
// <svg
// class="svg-${icon-name}"
// xmlns="http://www.w3.org/2000/svg"
// role="img" [1]
// aria-hidden="true" [2]
// focusable="false" [2]
// aria-labelledby="${id}" [1]
// >
// <title id="${id}"> [1]
// Locomotive
// </title>
// <use xlink:href="assets/images/sprite.svg#${icon-name}" xmlns:xlink="http://www.w3.org/1999/xlink"/>
// </svg>
// </i>
// Global styles for icones
// ==========================================================================
.o-icon {
display: inline-block;
vertical-align: middle;
svg {
--icon-height: calc(var(--icon-width) * (1 / (var(--icon-ratio))));
display: block;
width: var(--icon-width);
height: var(--icon-height);
fill: currentColor;
}
}
// SVG sizes
// ==========================================================================
// // Logo
// .svg-logo {
// --icon-width: #{rem(100px)};
// --icon-ratio: 20/30; // width/height based on svg viewBox
// // Sizes
// .o-icon.-big & {
// --icon-width: #{rem(200px)};
// }
// }

View File

@@ -5,25 +5,25 @@
// Palette
// =============================================================================
$white: #FFFFFF;
$black: #000000;
$color-lightest: #FFFFFF;
$color-darkest: #000000;
// Specific
// =============================================================================
// Link
$link-color: #1A0DAB;
$link-focus-color: #1A0DAB;
$link-hover-color: 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
// =============================================================================
$facebook-color: #3B5998;
$instagram-color: #E1306C;
$youtube-color: #CD201F;
$twitter-color: #1DA1F2;
$color-facebook: #3B5998;
$color-instagram: #E1306C;
$color-youtube: #CD201F;
$color-twitter: #1DA1F2;

View File

@@ -2,22 +2,47 @@
// Settings / Config / Eases
// ==========================================================================
$Power1EaseOut: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$Power2EaseOut: cubic-bezier(0.215, 0.610, 0.355, 1.000);
$Power3EaseOut: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$Power4EaseOut: cubic-bezier(0.230, 1.000, 0.320, 1.000);
$Power1EaseIn: cubic-bezier(0.550, 0.085, 0.680, 0.530) ;
$Power2EaseIn: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$Power3EaseIn: cubic-bezier(0.895, 0.030, 0.685, 0.220);
$Power4EaseIn: cubic-bezier(0.755, 0.050, 0.855, 0.060);
$ExpoEaseOut: cubic-bezier(0.190, 1.000, 0.220, 1.000);
$ExpoEaseIn: cubic-bezier(0.950, 0.050, 0.795, 0.035);
$ExpoEaseInOut: cubic-bezier(1.000, 0.000, 0.000, 1.000);
$SineEaseOut: cubic-bezier(0.390, 0.575, 0.565, 1.000);
$SineEaseIn: cubic-bezier(0.470, 0.000, 0.745, 0.715);
$Power1EaseInOut: cubic-bezier(0.455, 0.030, 0.515, 0.955);
$Power2EaseInOut: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$Power3EaseInOut: cubic-bezier(0.770, 0.000, 0.175, 1.000);
$Power4EaseInOut: cubic-bezier(0.860, 0.000, 0.070, 1.000);
$SlowEaseOut: cubic-bezier(.04,1.15,0.4,.99);
$bounce: cubic-bezier(0.17, 0.67, 0.3, 1.33);
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);

View File

@@ -14,26 +14,42 @@ $assets-path: "../" !default;
// Typefaces
// =============================================================================
// Font directory
$font-dir: "../fonts/";
// Font fallbacks (retrieved from systemfontstack.com on 2022-05-31)
$font-fallback-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
$font-fallback-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
$font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
// Map of font families.
//
// ```
// <font-id>: (<font-name>, <font-fallbacks>)
// ```
$font-families: (
"sans": ("Webfont Sans", "Helvetica Neue", Arial, sans-serif),
// "serif": ("Webfont Serif", Georgia, serif)
sans: join(Source Sans, $font-fallback-sans, $separator: comma),
);
// List of custom font faces as tuples.
//
// ```
// <font-name> <font-file-basename> <font-weight> <font-style>
// ```
$font-faces: (
// "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
// =============================================================================
// Base
$font-size: 16px;
$line-height: 24px / $font-size;
$color: #222222;
$font-size: 16px;
$line-height: 24px / $font-size;
$font-color: $color-darkest;
// Headings
$font-size-h1: 36px !default;
@@ -45,29 +61,31 @@ $font-size-h6: 16px !default;
$line-height-h: $line-height;
// Weights
$light: 300;
$normal: 400;
$medium: 500;
$bold: 700;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 700;
// Transitions
// =============================================================================
$speed: 0.3s;
$easing: $Power2EaseOut;
$speed: 0.3s;
$easing: $ease-power2-out;
// Spacing Units
// =============================================================================
$unit: 60px;
$unit-small: 30px;
$unit-small: 20px;
// Container
// =============================================================================
$container-width: 2000px;
// ==========================================================================
$padding: $unit;
// Grid
// ==========================================================================
$base-column-nb: 12;
$base-column-gap: $unit-small;
// Breakpoints
// =============================================================================
@@ -89,3 +107,12 @@ $from-gigantic: 2000px !default;
$to-gigantic: $from-gigantic - 1 !default;
$from-colossal: 2400px !default;
$to-colossal: $from-colossal - 1 !default;
// Master z-indexe
// =============================================================================
$z-indexes: (
"header": 200,
"above": 1,
"below": -1
);

View File

@@ -2,11 +2,14 @@
// Tools / Font Faces
// ==========================================================================
// Import the webfont with font-face as woff and woff2
// Imports the custom font.
//
// @param {List} $webfont (font name, filename, font-weight, font-style) - Each webfont to import.
// @param {String} $dir - The webfont directory path
// @output void
// The mixin expects font files to be woff and woff2.
//
// @param {List} $webfont - A custom font to import, as a tuple:
// `<font-name> <font-file-basename> <font-weight> <font-style>`.
// @param {String} $dir - The webfont directory path.
// @output The `@font-face` at-rule specifying the custom font.
@mixin font-face($webfont, $dir) {
@font-face {
@@ -19,27 +22,39 @@
}
}
// Loops through a list of local fonts and import each font-face as woff and woff2
// Imports the list of custom fonts.
//
// @param {List} $webfonts [(font name, filename, font-weight, font-style)] - Each webfont to import.
// @param {String} $dir - The webfont directory path
// @output void
// @require {mixin} font-face
//
// @param {List<List>} $webfonts - List of custom fonts to import.
// See `font-face` mixin for details.
// @param {String} $dir - The webfont directory path.
// @output The `@font-face` at-rules specifying the custom fonts.
@mixin font-faces($webfonts, $dir) {
@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);
}
}
}
// Map the font-family requires with the existing imported font-families
// Retrieves the font family stack for the given font ID.
//
// @param {String} $font-family - The name of the webfont.
// @return {String} The webfont and it's fallbacks.
// @require {variable} $font-families - See settings directory.
//
// @param {String} $font-family - The custom font ID.
// @throws Error if the $font-family does not exist.
// @return {List} The font stack.
@function ff($font-family) {
@if not map-has-key($font-families, $font-family) {
@error "No font-family found in $font-families map for `#{$font-family}`. Property omitted.";
}
@error "No font-family found in $font-families map for `#{$font-family}`.";
}
$value: map-get($font-families, $font-family);
@return $value;

View File

@@ -2,6 +2,15 @@
// Tools / Functions
// ==========================================================================
// Check if the given value is a number in pixel
//
// @param {Number} $number - The value to check
// @return {Boolean}
@function is-pixel-number($number) {
@return type-of($number) == number and unit($number) == "px";
}
// Converts the given pixel value to its EM quivalent.
//
// @param {Number} $size - The pixel value to convert.
@@ -9,20 +18,12 @@
// @return {Number} Scalable pixel value in EMs.
@function em($size, $base: $font-size) {
@if (type-of($size) == number) {
@if (unit($size) != "px") {
@error "`#{$size}` needs to be a pixel value.";
}
} @else {
@error "`#{$size}` needs to be a number.";
@if not is-pixel-number($size) {
@error "`#{$size}` needs to be a number in pixel.";
}
@if (type-of($base) == number) {
@if (unit($base) != "px") {
@error "`#{$base}` needs to be a pixel value.";
}
} @else {
@error "`#{$base}` needs to be a number.";
@if not is-pixel-number($base) {
@error "`#{$base}` needs to be a number in pixel.";
}
@return ($size / $base) * 1em;
@@ -35,25 +36,43 @@
// @return {Number} Scalable pixel value in REMs.
@function rem($size, $base: $font-size) {
@if (type-of($size) == number) {
@if (unit($size) != "px") {
@error "`#{$size}` needs to be a pixel value.";
}
} @else {
@error "`#{$size}` needs to be a number.";
@if not is-pixel-number($size) {
@error "`#{$size}` needs to be a number in pixel.";
}
@if (type-of($base) == number) {
@if (unit($base) != "px") {
@error "`#{$base}` needs to be a pixel value.";
}
} @else {
@error "`#{$base}` needs to be a number.";
@if not is-pixel-number($base) {
@error "`#{$base}` needs to be a number in pixel.";
}
@return ($size / $base) * 1rem;
}
// Retrieves the z-index from the {@see $layers master list}.
//
// @link on http://css-tricks.com/handling-z-index/
//
// @param {string} $layer The name of the z-index.
// @param {number} $modifier A positive or negative modifier to apply
// to the returned z-index value.
// @throw Error if the $layer does not exist.
// @throw Warning if the $modifier might overlap another master z-index.
// @return {number} The computed z-index of $layer and $modifier.
@function z($layer, $modifier: 0) {
@if not map-has-key($z-indexes, $layer) {
@error "Unknown master z-index layer: #{$layer}";
}
@if ($modifier >= 50 or $modifier <= -50) {
@warn "Modifier may overlap the another master z-index layer: #{$modifier}";
}
$index: map-get($z-indexes, $layer);
@return $index + $modifier;
}
// Converts a number to a percentage.
//
// @alias percentage()
@@ -94,7 +113,7 @@
@function important($flag: false) {
@if ($flag == true) {
@return !important;
} @elseif ($important == false) {
} @else if ($important == false) {
@return null;
} @else {
@error "`#{$flag}` needs to be `true` or `false`.";

View File

@@ -0,0 +1,136 @@
// ==========================================================================
// Tools / Maths
// ==========================================================================
// Removes the unit from the given number.
//
// @param {number} $number The number to strip.
// @return {number}
@function strip-units($number) {
@return $number / ($number * 0 + 1);
}
// Returns the square root of the given number.
//
// @param {number} $number The number to calculate.
// @return {number}
@function sqrt($number) {
$x: 1;
$value: $x;
@for $i from 1 through 10 {
$value: $x - ($x * $x - abs($number)) / (2 * $x);
$x: $value;
}
@return $value;
}
// Returns a number raised to the power of an exponent.
//
// @param {number} $number The base number.
// @param {number} $exp The exponent.
// @return {number}
@function pow($number, $exp) {
$value: 1;
@if $exp > 0 {
@for $i from 1 through $exp {
$value: $value * $number;
}
} @else if $exp < 0 {
@for $i from 1 through -$exp {
$value: $value / $number;
}
}
@return $value;
}
// Returns the factorial of the given number.
//
// @param {number} $number The number to calculate.
// @return {number}
@function fact($number) {
$value: 1;
@if $number > 0 {
@for $i from 1 through $number {
$value: $value * $i;
}
}
@return $value;
}
// Returns an approximation of pi, with 11 decimals.
//
// @return {number}
@function pi() {
@return 3.14159265359;
}
// Converts the number in degrees to the radian equivalent .
//
// @param {number} $angle The angular value to calculate.
// @return {number} If $angle has the `deg` unit,
// the radian equivalent is returned.
// Otherwise, the unitless value of $angle is returned.
@function rad($angle) {
$unit: unit($angle);
$angle: strip-units($angle);
// If the angle has `deg` as unit, convert to radians.
@if ($unit == deg) {
@return $angle / 180 * pi();
}
@return $angle;
}
// Returns the sine of the given number.
//
// @param {number} $angle The angle to calculate.
// @return {number}
@function sin($angle) {
$sin: 0;
$angle: rad($angle);
@for $i from 0 through 10 {
$sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
}
@return $sin;
}
// Returns the cosine of the given number.
//
// @param {string} $angle The angle to calculate.
// @return {number}
@function cos($angle) {
$cos: 0;
$angle: rad($angle);
@for $i from 0 through 10 {
$cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 * $i);
}
@return $cos;
}
// Returns the tangent of the given number.
//
// @param {string} $angle The angle to calculate.
// @return {number}
@function tan($angle) {
@return sin($angle) / cos($angle);
}

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

@@ -7,6 +7,7 @@ import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import sass from 'node-sass';
import { PurgeCSS } from 'purgecss';
const sassRender = promisify(sass.render);
@@ -142,7 +143,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 +218,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);
}
}

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;

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,12 @@
],
"outfile": "{% paths.svgs.dest %}/sprite.svg"
}
]
],
"purgeCSS": {
"content": [
"./www/**/*.html",
"./assets/scripts/**/*"
]
}
}
}

1102
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,14 +21,15 @@
"svg4everybody": "^2.1.9"
},
"devDependencies": {
"autoprefixer": "^10.4.4",
"browser-sync": "^2.27.9",
"autoprefixer": "^10.4.12",
"browser-sync": "^2.27.10",
"concat": "^1.0.3",
"esbuild": "^0.14.27",
"kleur": "^4.1.4",
"esbuild": "^0.14.54",
"kleur": "^4.1.5",
"node-notifier": "^10.0.1",
"node-sass": "^7.0.1",
"postcss": "^8.4.12",
"postcss": "^8.4.17",
"purgecss": "^5.0.0",
"svg-mixer": "^2.3.14",
"tiny-glob": "^0.2.9"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -609,10 +609,141 @@
// assets/scripts/modules.js
var modules_exports = {};
__export(modules_exports, {
Example: () => Example_default,
Load: () => Load_default,
Scroll: () => Scroll_default
});
// assets/scripts/utils/fonts.js
var isFontLoadingAPIAvailable = "fonts" in document;
function conformsToReference(font, criterion2) {
for (const [key, value] of Object.entries(criterion2)) {
switch (key) {
case "family": {
if (trim(font[key]) !== value) {
return false;
}
break;
}
case "weight": {
if (font[key] != value) {
return false;
}
break;
}
default: {
if (font[key] !== value) {
return false;
}
break;
}
}
}
return true;
}
function conformsToShorthand(font, criterion2) {
const family = trim(font.family);
if (trim(family) === criterion2) {
return true;
}
if (criterion2.endsWith(trim(family)) && (criterion2.match(font.weight) || criterion2.match(font.style))) {
return true;
}
return true;
}
function findManyByReference(search) {
const found = [];
for (const font of document.fonts) {
if (conformsToReference(font, search)) {
found.push(font);
}
}
return found;
}
function findManyByShorthand(search) {
const found = [];
for (const font of document.fonts) {
if (conformsToShorthand(font, search)) {
found.push(font);
}
}
return found;
}
function getMany(queries) {
if (!Array.isArray(queries)) {
queries = [queries];
}
const found = /* @__PURE__ */ 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];
}
function loadFonts(fontsToLoad, debug = false) {
return __async(this, null, function* () {
var _a;
if (((_a = fontsToLoad.size) != null ? _a : fontsToLoad.length) === 0) {
throw new TypeError("Expected at least one font");
}
return yield loadFontsWithAPI([...fontsToLoad], debug);
});
}
function loadFontFaceWithAPI(font) {
return __async(this, null, function* () {
return yield (font.status === "unloaded" ? font.load() : font.loaded).then((font2) => font2, (err) => font);
});
}
function loadFontsWithAPI(fontsToLoad, debug = false) {
return __async(this, null, function* () {
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 yield Promise.all(fontsToBeLoaded);
});
}
function trim(value) {
return value.replace(/['"]+/g, "");
}
function whenReady(queries) {
return __async(this, null, function* () {
const fonts = getMany(queries);
return yield Promise.all(fonts.map((font) => font.loaded));
});
}
// assets/scripts/modules/Example.js
var Example_default = class extends _default {
constructor(m) {
super(m);
}
init() {
whenReady(EAGER_FONTS).then((fonts) => this.onFontsLoaded(fonts));
}
onFontsLoaded(fonts) {
console.log("Example: Eager Fonts Loaded!", fonts);
}
};
// node_modules/modularload/dist/main.esm.js
function _classCallCheck2(instance, Constructor) {
if (!(instance instanceof Constructor)) {
@@ -3694,12 +3825,21 @@
console.warn('The "main-css" stylesheet not found');
}
};
var EAGER_FONTS = [
{ family: "Source Sans", style: "normal", weight: 400 },
{ family: "Source Sans", style: "normal", weight: 700 }
];
function init() {
globals_default();
app.init(app);
html.classList.add(config_default.CSS_CLASS.LOADED);
html.classList.add(config_default.CSS_CLASS.READY);
html.classList.remove(config_default.CSS_CLASS.LOADING);
if (isFontLoadingAPIAvailable) {
loadFonts(EAGER_FONTS).then((eagerFonts) => {
html.classList.add("fonts-loaded");
});
}
}
})();
/*

File diff suppressed because one or more lines are too long

View File

@@ -516,11 +516,43 @@ button:focus, button:hover,
text-decoration: none;
}
@font-face {
font-display: swap;
font-family: Source Sans;
src: url("../fonts/SourceSans3-Bold.woff2") format("woff2"), url("../fonts/SourceSans3-Bold.woff") format("woff");
font-weight: 700;
font-style: normal;
}
@font-face {
font-display: swap;
font-family: Source Sans;
src: url("../fonts/SourceSans3-BoldIt.woff2") format("woff2"), url("../fonts/SourceSans3-BoldIt.woff") format("woff");
font-weight: 700;
font-style: italic;
}
@font-face {
font-display: swap;
font-family: Source Sans;
src: url("../fonts/SourceSans3-Regular.woff2") format("woff2"), url("../fonts/SourceSans3-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-display: swap;
font-family: Source Sans;
src: url("../fonts/SourceSans3-RegularIt.woff2") format("woff2"), url("../fonts/SourceSans3-RegularIt.woff") format("woff");
font-weight: 400;
font-style: italic;
}
html {
min-height: 100%;
line-height: 1.5;
font-family: "Webfont Sans", "Helvetica Neue", Arial, sans-serif;
color: #222222;
font-family: Source, Sans, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
color: #000000;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -614,9 +646,8 @@ a:focus, a:hover {
.o-container {
margin-right: auto;
margin-left: auto;
padding-right: 3.75rem;
padding-left: 3.75rem;
max-width: 132.5rem;
padding-right: 20px;
padding-left: 20px;
}
.o-ratio {
@@ -644,90 +675,182 @@ a:focus, a:hover {
width: 100%;
}
.o-layout {
margin: 0;
padding: 0;
list-style: none;
font-size: 0;
margin-left: 0;
}
.o-layout.-gutter {
margin-left: -3.75rem;
}
.o-layout.-gutter-small {
margin-left: -1.875rem;
}
.o-layout.-center {
text-align: center;
}
.o-layout.-right {
text-align: right;
}
.o-layout.-reverse {
direction: rtl;
}
.o-layout.-reverse.-flex {
flex-direction: row-reverse;
}
.o-layout.-flex {
display: flex;
}
.o-layout.-flex.-top {
align-items: flex-start;
}
.o-layout.-flex.-middle {
align-items: center;
}
.o-layout.-flex.-bottom {
align-items: flex-end;
}
.o-layout.-stretch {
align-items: stretch;
}
.o-layout_item {
.o-icon {
display: inline-block;
width: 100%;
vertical-align: top;
font-size: 1rem;
padding-left: 0;
}
.o-layout.-gutter > .o-layout_item {
padding-left: 3.75rem;
}
.o-layout.-gutter-small > .o-layout_item {
padding-left: 1.875rem;
}
.o-layout.-middle > .o-layout_item {
vertical-align: middle;
}
.o-layout.-bottom > .o-layout_item {
vertical-align: bottom;
.o-icon svg {
--icon-height: calc(var(--icon-width) * (1 / (var(--icon-ratio))));
display: block;
width: var(--icon-width);
height: var(--icon-height);
fill: currentColor;
}
.o-layout.-center > .o-layout_item,
.o-layout.-right > .o-layout_item,
.o-layout.-reverse > .o-layout_item {
text-align: left;
/**
* 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%;
}
.o-layout.-reverse > .o-layout_item {
direction: ltr;
.o-grid:is(ul,
ol) {
margin: 0;
padding: 0;
list-style: none;
}
.o-grid.-col-12 {
grid-template-columns: repeat(12, 1fr);
}
.o-grid.-col-4 {
grid-template-columns: repeat(4, 1fr);
}
@media (min-width: 1000px) {
.o-grid.-col-12\@from-medium {
grid-template-columns: repeat(12, 1fr);
}
}
.o-grid.-gutters {
gap: 20px;
-moz-column-gap: 20px;
column-gap: 20px;
}
.o-grid.-full-height {
height: 100%;
}
.o-grid.-top-items {
align-items: start;
}
.o-grid.-right-items {
justify-items: end;
}
.o-grid.-bottom-items {
align-items: end;
}
.o-grid.-left-items {
justify-items: start;
}
.o-grid.-center-items {
align-items: center;
justify-items: center;
}
.o-grid.-center-items-x {
justify-items: center;
}
.o-grid.-center-items-y {
align-items: center;
}
.o-grid.-stretch-items {
align-items: stretch;
justify-items: stretch;
}
.o-grid.-top-cells {
align-content: start;
}
.o-grid.-right-cells {
justify-content: end;
}
.o-grid.-bottom-cells {
align-content: end;
}
.o-grid.-left-cells {
justify-content: start;
}
.o-grid.-center-cells {
align-content: center;
justify-content: center;
}
.o-grid.-center-cells-x {
justify-content: center;
}
.o-grid.-center-cells-y {
align-content: center;
}
.o-grid.-stretch-cells {
align-content: stretch;
justify-content: stretch;
}
.o-grid.-space-around-cells {
align-content: space-around;
justify-content: space-around;
}
.o-grid.-space-around-cells-x {
justify-content: space-around;
}
.o-grid.-space-around-cells-y {
align-content: space-around;
}
.o-grid.-space-between-cells {
justify-content: space-between;
align-content: space-between;
}
.o-grid.-space-between-cells-x {
justify-content: space-between;
}
.o-grid.-space-between-cells-y {
align-content: space-between;
}
.o-grid.-space-evenly-cells {
justify-content: space-evenly;
align-content: space-evenly;
}
.o-grid.-space-evenly-cells-x {
justify-content: space-evenly;
}
.o-grid.-space-evenly-cells-y {
align-content: space-evenly;
}
.o-grid_item {
grid-column: 1 / -1;
}
.o-grid_item.-align-end {
align-self: end;
}
.c-scrollbar {
@@ -753,7 +876,7 @@ a:focus, a:hover {
position: absolute;
top: 0;
right: 0;
background-color: black;
background-color: #000000;
opacity: 0.5;
width: 7px;
border-radius: 10px;
@@ -818,7 +941,7 @@ a:focus, a:hover {
.c-form_input, .c-form_select_input, .c-form_textarea {
padding: 0.625rem;
border: 1px solid lightgray;
background-color: white;
background-color: #FFFFFF;
}
.c-form_input:hover, .c-form_select_input:hover, .c-form_textarea:hover {
@@ -949,68 +1072,43 @@ a:focus, a:hover {
}
/* stylelint-enable */
.u-1\/1 {
width: 100% !important;
.u-gc-1\/3 {
grid-column-start: 1;
grid-column-end: 3;
}
.u-1\/2 {
width: 50% !important;
}
.u-2\/2 {
width: 100% !important;
}
.u-1\/3 {
width: 33.33333% !important;
}
.u-2\/3 {
width: 66.66667% !important;
}
.u-3\/3 {
width: 100% !important;
}
.u-1\/4 {
width: 25% !important;
}
.u-2\/4 {
width: 50% !important;
}
.u-3\/4 {
width: 75% !important;
}
.u-4\/4 {
width: 100% !important;
}
.u-1\/5 {
width: 20% !important;
}
.u-2\/5 {
width: 40% !important;
}
.u-3\/5 {
width: 60% !important;
}
.u-4\/5 {
width: 80% !important;
}
.u-5\/5 {
width: 100% !important;
}
@media (min-width: 700px) {
.u-1\/2\@from-small {
width: 50%;
@media (min-width: 1000px) {
.u-gc-1\/5\@from-medium {
grid-column-start: 1;
grid-column-end: 5;
}
}
@media (min-width: 1000px) {
.u-gc-1\/8\@from-medium {
grid-column-start: 1;
grid-column-end: 8;
}
}
@media (min-width: 1000px) {
.u-gc-5\/9\@from-medium {
grid-column-start: 5;
grid-column-end: 9;
}
}
@media (min-width: 1000px) {
.u-gc-5\/13\@from-medium {
grid-column-start: 5;
grid-column-end: 13;
}
}
@media (min-width: 1000px) {
.u-gc-9\/13\@from-medium {
grid-column-start: 9;
grid-column-end: 13;
}
}

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>

View File

@@ -29,11 +29,20 @@
-->
<!-- <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>
<!-- <p aria-hidden="true" class="u-screen-reader-text" style="font-family: 'Webfont';">&nbsp;</p> -->
<div data-load-container>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
@@ -44,11 +53,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>