mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Compare commits
340 Commits
quentinhoc
...
feature/la
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e12fc31f0 | ||
|
|
70b36052e6 | ||
|
|
f1e2e2270f | ||
|
|
13735c64f9 | ||
|
|
659ef3767b | ||
|
|
e162af17dd | ||
|
|
e16ba2ca16 | ||
|
|
43a5eb1ad3 | ||
|
|
217a1adba7 | ||
|
|
a11e98e31e | ||
|
|
6ef90dbe11 | ||
|
|
6726d665f2 | ||
|
|
dca6c5de1d | ||
|
|
05a00c4258 | ||
|
|
7517be0e76 | ||
|
|
dcec21adf4 | ||
|
|
297e0b4ec8 | ||
|
|
9a2083d894 | ||
|
|
1a81c865ae | ||
|
|
d6b5784cdd | ||
|
|
8894664743 | ||
|
|
8aac2ffea6 | ||
|
|
349d110dee | ||
|
|
7be5e48f22 | ||
|
|
1ee315663e | ||
|
|
89bb00790f | ||
|
|
9db0c71a82 | ||
|
|
7742bbb9d0 | ||
|
|
c9a9209b4b | ||
|
|
9d758f3b2c | ||
|
|
0738dd6491 | ||
|
|
a4656f59ed | ||
|
|
7ff6094e40 | ||
|
|
3fee6f4888 | ||
|
|
f774482255 | ||
|
|
b7d9311ac6 | ||
|
|
c22a006079 | ||
|
|
af57ebd9cb | ||
|
|
c82e9916d0 | ||
|
|
943324220a | ||
|
|
477cec7763 | ||
|
|
5d38685460 | ||
|
|
9079d735bc | ||
|
|
87238fcdd5 | ||
|
|
2f75d8f3d2 | ||
|
|
0346a15b57 | ||
|
|
a2d658bc13 | ||
|
|
7c1b61eda9 | ||
|
|
1fe30a9837 | ||
|
|
e4ae03a94c | ||
|
|
3cde7d40ee | ||
|
|
d1d4fb5fe5 | ||
|
|
3cd81bdb3e | ||
|
|
b6970832a3 | ||
|
|
b8f0a24cdc | ||
|
|
0c718a2644 | ||
|
|
be71474633 | ||
|
|
56d255eac8 | ||
|
|
e7e343e62c | ||
|
|
590e06fc03 | ||
|
|
810df92a61 | ||
|
|
4fd7968b86 | ||
|
|
aba77ea2d9 | ||
|
|
bf28fe21a3 | ||
|
|
7b3cefd8df | ||
|
|
20b167da33 | ||
|
|
f7ca837782 | ||
|
|
0c8ed9595f | ||
|
|
eead1d27cd | ||
|
|
2e3db21ec8 | ||
|
|
9c478f5f7d | ||
|
|
ebcbb6dc84 | ||
|
|
b7c49086c9 | ||
|
|
9219a4cc0a | ||
|
|
14afe2295a | ||
|
|
1bdd2def8d | ||
|
|
84ce496df7 | ||
|
|
05e631dbca | ||
|
|
f8f0a7779c | ||
|
|
8e320f2cd0 | ||
|
|
a8314d064f | ||
|
|
76614e8126 | ||
|
|
bed84ce392 | ||
|
|
feb2241164 | ||
|
|
b1f5a00b8c | ||
|
|
2b30d9ac5c | ||
|
|
9e5704238e | ||
|
|
1ede84e1b1 | ||
|
|
3a83a3209b | ||
|
|
98957eb6c4 | ||
|
|
6712d2d24d | ||
|
|
139a6739f6 | ||
|
|
3272521dba | ||
|
|
e7f0455ce4 | ||
|
|
bf425521c4 | ||
|
|
4bdaa5d085 | ||
|
|
a385f6ed11 | ||
|
|
4079752fe0 | ||
|
|
14d7e09b2b | ||
|
|
e70bf33409 | ||
|
|
8b0926269a | ||
|
|
8d1b548ad0 | ||
|
|
7ca7486913 | ||
|
|
520b75185f | ||
|
|
a056a87855 | ||
|
|
9b99a1958b | ||
|
|
d6193a41fa | ||
|
|
4fafcb8e1d | ||
|
|
aadc410e44 | ||
|
|
0439b165cf | ||
|
|
f98eebc9e1 | ||
|
|
cb80a2ed13 | ||
|
|
07c3155c29 | ||
|
|
c264cb7905 | ||
|
|
1050b83326 | ||
|
|
d0a075ff24 | ||
|
|
ad4a1c7d47 | ||
|
|
de6b3d73a1 | ||
|
|
e8af22009c | ||
|
|
de1a5904a8 | ||
|
|
f527488464 | ||
|
|
cf3f40c956 | ||
|
|
ebb55769f9 | ||
|
|
c8d4e7c154 | ||
|
|
bce37afb6e | ||
|
|
7a23abff92 | ||
|
|
27d8fdee22 | ||
|
|
9154deb036 | ||
|
|
17e8004515 | ||
|
|
9a461ab4c0 | ||
|
|
6b3edefa48 | ||
|
|
fd5efe3531 | ||
|
|
2783fb5138 | ||
|
|
f1e4cd2c55 | ||
|
|
b7d25c5865 | ||
|
|
0a199afe01 | ||
|
|
8ca570b37a | ||
|
|
e5417ff6ab | ||
|
|
3a94c6aba9 | ||
|
|
648109fc9b | ||
|
|
eddc0ee156 | ||
|
|
202e4064f8 | ||
|
|
e20111fd2e | ||
|
|
de0c4993cb | ||
|
|
b162c62930 | ||
|
|
cb27975087 | ||
|
|
0b667542f5 | ||
|
|
d7de1b2566 | ||
|
|
34bca7d68a | ||
|
|
f44093ec19 | ||
|
|
6a4043408b | ||
|
|
92500f908f | ||
|
|
d97fa82c77 | ||
|
|
ccf813f6be | ||
|
|
141a8ffa97 | ||
|
|
e875495928 | ||
|
|
c2db2e1922 | ||
|
|
32ba1c0eeb | ||
|
|
5199ee2025 | ||
|
|
d6c8fdac23 | ||
|
|
15a4f2e64f | ||
|
|
9e35894ef1 | ||
|
|
cabeba55c0 | ||
|
|
47974a77a9 | ||
|
|
e8b2a86798 | ||
|
|
da66f89d7f | ||
|
|
53beae26b0 | ||
|
|
9a01c0f17f | ||
|
|
97d9f1ec00 | ||
|
|
8b8b267e9d | ||
|
|
822cf7daa8 | ||
|
|
7f452f1fcc | ||
|
|
5010560ee3 | ||
|
|
0cfb3fbc7d | ||
|
|
86f88c3f14 | ||
|
|
48bd911804 | ||
|
|
d49d3eabb2 | ||
|
|
28aa6c7de6 | ||
|
|
38dd28832e | ||
|
|
757c26c772 | ||
|
|
5e07473396 | ||
|
|
c9056b27d8 | ||
|
|
38a6c73d2f | ||
|
|
9d18205b0f | ||
|
|
1e7e90c8aa | ||
|
|
47007cddaf | ||
|
|
5dd3fa843f | ||
|
|
a5623d3122 | ||
|
|
67e1fae8f4 | ||
|
|
a95fe4523c | ||
|
|
eadc414329 | ||
|
|
b19c18b18c | ||
|
|
db85740a18 | ||
|
|
5c24fabaa2 | ||
|
|
9e3d304654 | ||
|
|
6ded72bc79 | ||
|
|
2a97183d39 | ||
|
|
2316219201 | ||
|
|
d99a69f212 | ||
|
|
8bc79f715e | ||
|
|
62601f22ed | ||
|
|
082f3b5827 | ||
|
|
df567220d5 | ||
|
|
7d35dcbf28 | ||
|
|
8b40b1a92e | ||
|
|
f2b657568a | ||
|
|
a28848e8aa | ||
|
|
b881003705 | ||
|
|
b55e625457 | ||
|
|
25ef6675af | ||
|
|
7df0481d05 | ||
|
|
e53efd6ebc | ||
|
|
14ec69f26d | ||
|
|
fa8aa98595 | ||
|
|
b24b4e10c4 | ||
|
|
21f6acf4a6 | ||
|
|
bbbb49f30b | ||
|
|
99e1b3fa93 | ||
|
|
548b2c604b | ||
|
|
ec9228a337 | ||
|
|
8dec2c69fe | ||
|
|
3a65683fd8 | ||
|
|
1a2cc7b6ac | ||
|
|
e7935211cd | ||
|
|
7e8a21f698 | ||
|
|
7e30939b14 | ||
|
|
655031cd1b | ||
|
|
d4ded2a64e | ||
|
|
589ec99135 | ||
|
|
84286fef66 | ||
|
|
6e50bc202d | ||
|
|
91109c5221 | ||
|
|
834c6165b0 | ||
|
|
ffece71aac | ||
|
|
454ae64d07 | ||
|
|
7479444572 | ||
|
|
b46fb31dfe | ||
|
|
23e55d6340 | ||
|
|
70827b0a7d | ||
|
|
8cff91aa68 | ||
|
|
b24014d8b1 | ||
|
|
e9e0e5784e | ||
|
|
8b4d758443 | ||
|
|
e51c717a3c | ||
|
|
0267bc6ebd | ||
|
|
4a5d821965 | ||
|
|
5cc8a75866 | ||
|
|
1203e54277 | ||
|
|
14d9f47fe0 | ||
|
|
72eaf582a5 | ||
|
|
e4d1c0058a | ||
|
|
bcb7525019 | ||
|
|
03b3d211c8 | ||
|
|
0d42f65ff6 | ||
|
|
b62385c4e0 | ||
|
|
53653b2111 | ||
|
|
26821492a7 | ||
|
|
5d1c5a17f5 | ||
|
|
0071b9d790 | ||
|
|
ebda397455 | ||
|
|
c94f7ca88e | ||
|
|
f52b073263 | ||
|
|
b8b056dbe0 | ||
|
|
fbe2a6badf | ||
|
|
e687e52cd2 | ||
|
|
3cf62e80f7 | ||
|
|
a6efa6bcb1 | ||
|
|
85a2784a11 | ||
|
|
65a2e64474 | ||
|
|
2cfe06ea1d | ||
|
|
b1d90327a3 | ||
|
|
41b8030d9e | ||
|
|
d2d294e145 | ||
|
|
f39a5f0a75 | ||
|
|
6a6f2cfa21 | ||
|
|
0e8134629a | ||
|
|
5ab2d41525 | ||
|
|
2d095ef973 | ||
|
|
89b7d9523b | ||
|
|
ad81a8e97f | ||
|
|
bddbaaed1c | ||
|
|
5f20fe0e43 | ||
|
|
718feed2c7 | ||
|
|
11b9fa7a7d | ||
|
|
ad01d00751 | ||
|
|
31c803d14d | ||
|
|
6027f8d927 | ||
|
|
7cc8cee0fd | ||
|
|
65d1ead6e7 | ||
|
|
f7a2f8b219 | ||
|
|
5bd1ca268f | ||
|
|
f67d4c0c41 | ||
|
|
f15ec08784 | ||
|
|
6ab44c64a9 | ||
|
|
8e5c4e5c83 | ||
|
|
2e7bb3b482 | ||
|
|
e686689b52 | ||
|
|
46797cbd6d | ||
|
|
20d08c3d4b | ||
|
|
57c3e72a06 | ||
|
|
43fc62950a | ||
|
|
80e1614508 | ||
|
|
55dc6c029c | ||
|
|
a3001fe3b1 | ||
|
|
c24ba3fdc1 | ||
|
|
59243317a7 | ||
|
|
6ae89d6621 | ||
|
|
e910afc384 | ||
|
|
a110cc7ae2 | ||
|
|
a967b864e3 | ||
|
|
cc181ed26c | ||
|
|
47b667bd95 | ||
|
|
61c9c0ac6f | ||
|
|
680d6af675 | ||
|
|
3ed1175aaf | ||
|
|
9058945b1a | ||
|
|
1f589add29 | ||
|
|
d2db947fd1 | ||
|
|
88dd4dde3b | ||
|
|
41c3fe4b49 | ||
|
|
9836c462b2 | ||
|
|
cb49c03cca | ||
|
|
6324f7ee82 | ||
|
|
883e4d202e | ||
|
|
5da3bcd961 | ||
|
|
26cccc7d92 | ||
|
|
ce8582c0a4 | ||
|
|
ee258f5d5f | ||
|
|
cf9a0a2705 | ||
|
|
e6dec31198 | ||
|
|
7d47ae0d82 | ||
|
|
206ced5b10 | ||
|
|
d89a242e60 | ||
|
|
d166261d0a | ||
|
|
ea5c63744e | ||
|
|
52db1cc63e | ||
|
|
6d8954563a | ||
|
|
d9eb8364dc | ||
|
|
88aa667090 | ||
|
|
2b176132d3 |
13
.babelrc
13
.babelrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"ie": "11"
|
||||
},
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
1
.browserslistrc
Normal file
1
.browserslistrc
Normal file
@@ -0,0 +1 @@
|
||||
defaults
|
||||
@@ -9,9 +9,8 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
[*.{md,markdown}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{*.yml,*.json}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
[*.{ms,mustache}]
|
||||
insert_final_newline = false
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
loconfig.*.json
|
||||
!loconfig.example.json
|
||||
.prettierrc
|
||||
232
README.md
232
README.md
@@ -1,158 +1,100 @@
|
||||
Locomotive's Front-end Boilerplate
|
||||
==================================
|
||||
<p align="center">
|
||||
<a href="https://github.com/locomotivemtl/locomotive-boilerplate">
|
||||
<img src="https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png" height="140">
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">Locomotive Boilerplate</h1>
|
||||
<p align="center">Front-end boilerplate for projects by <a href="https://locomotive.ca/">Locomotive</a>.</p>
|
||||
|
||||
Front-end boilerplate for projects by [Locomotive][locomtl].
|
||||
## Features
|
||||
|
||||
* Uses a custom [task runner](docs/development.md) for handling assets.
|
||||
* Uses [BrowserSync] for fast development and testing in browsers.
|
||||
* Uses [Sass] for a feature rich superset of CSS.
|
||||
* Uses [ESBuild] for extremely fast processing of JS/ES modules.
|
||||
* 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).
|
||||
|
||||
## Getting started
|
||||
|
||||
Make sure you have the following installed:
|
||||
|
||||
* [Node] — at least 17.9, 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
|
||||
# Clone the repository.
|
||||
git clone https://github.com/locomotivemtl/locomotive-boilerplate.git my-new-project
|
||||
|
||||
# Enter the newly-cloned directory.
|
||||
cd my-new-project
|
||||
```
|
||||
|
||||
Then replace the original remote repository with your project's repository.
|
||||
|
||||
Then update the following files to suit your project:
|
||||
|
||||
* [`README.md`](README.md):
|
||||
The file you are currently reading.
|
||||
* [`package.json`](package.json):
|
||||
* Package name: `@locomotivemtl/boilerplate`
|
||||
* Package title: `Locomotive Boilerplate`
|
||||
* [`package-lock.json`](package-lock.json):
|
||||
* Package name: `@locomotivemtl/boilerplate`
|
||||
* [`loconfig.json`](loconfig.json):
|
||||
* BrowserSync proxy URL: `locomotive-boilerplate.test`
|
||||
Remove `paths.url` to use BrowserSync's built-in server which uses `paths.dest`.
|
||||
* View path: `./views/boilerplate/template`
|
||||
* [`environment.js`](assets/scripts/utils/environment.js):
|
||||
* Application name: `Boilerplate`
|
||||
* [`site.webmanifest`](www/site.webmanifest):
|
||||
* Manifest name: `Locomotive Boilerplate`
|
||||
* Manifest short name: `Boilerplate`
|
||||
* HTML files:
|
||||
* Page title: `Locomotive Boilerplate`
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
# install mbp and gulp
|
||||
npm install mbp gulp@next -g
|
||||
# Switch to recommended Node version from .nvmrc
|
||||
nvm use
|
||||
|
||||
# Install dependencies from package.json
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Development
|
||||
|
||||
```sh
|
||||
# init your project
|
||||
mbp init locomotivemtl/locomotive-boilerplate <directory>
|
||||
# Start development server, watch for changes, and compile assets
|
||||
npm start
|
||||
|
||||
# run default watch task
|
||||
gulp
|
||||
# Compile and minify assets
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Change the mentions of `boilerplate` for your project's name in
|
||||
- `mconfig.json`
|
||||
- `assets/scripts/utils/environment.js`
|
||||
Learn more about [development and building](docs/development.md).
|
||||
|
||||
## CSS
|
||||
## Documentation
|
||||
|
||||
- We use [Sass](http://sass-lang.com) for our CSS Preprocessor
|
||||
- [itcss](http://itcss.io) CSS architecture
|
||||
- More Minimal BEM like CSS Syntax: `.block_element -modifier`
|
||||
- [More Transparent UI Code with Namespaces](http://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces)
|
||||
|
||||
### Sass import order
|
||||
|
||||
* **Settings:** Global variables, site-wide settings, config switches, etc.
|
||||
* **Tools:** Site-wide mixins and functions.
|
||||
* **Generic:** Low-specificity, far-reaching rulesets (e.g. resets).
|
||||
* **Base:** Unclassed HTML elements (e.g. `a {}`, `blockquote {}`, `address {}`).
|
||||
* **Objects:** Objects, abstractions, and design patterns (e.g. `.o-media {}`).
|
||||
* **Components:** Discrete, complete chunks of UI (e.g. `.c-carousel {}`).
|
||||
* **Utilities:** High-specificity, very explicit selectors. Overrides and helper
|
||||
classes (e.g. `.u-hidden {}`).
|
||||
|
||||
### Grid
|
||||
We use [inuitcss](https://github.com/inuitcss/inuitcss/tree/6eb574fa604481ffa36272e6034e77467334ec50) layout and width system. We are using a inline-block grid system.
|
||||
|
||||
Insert a `.o-layout` block and add `.o-layout_item` elements inside it. By default `o-layout_item` made 100%.
|
||||
You can define different fractions in `/tools/_widths.scss` (`$widths-fractions`)
|
||||
|
||||
If you want a 2 columns grid, just add `.u-1/2` on your 2 `.o-layout_item`
|
||||
|
||||
If you want to adapt columns by media queries, by example a 2 columns grid for 1000px + resolutions, and one columns in block under 1000px :
|
||||
|
||||
**HTML**
|
||||
```
|
||||
<div class="o-layout">
|
||||
<div class="o-layout_item u-1/2@from-medium">
|
||||
first colum
|
||||
</div>
|
||||
<div class="o-layout_item u-1/2@from-medium">
|
||||
second colum
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**CSS** (`/tools/_widths.scss`)
|
||||
```
|
||||
.u-1\/2\@from-medium {
|
||||
@media (min-width: $from-medium) {
|
||||
width: span(1/2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Form
|
||||
|
||||
We included some basic CSS styles and resets to the form elements so we can easily have custom style form elements that work on every browsers.
|
||||
|
||||
*[Demo][demo-form]*
|
||||
|
||||
## JavaScript
|
||||
|
||||
- We use HTML data attributes to init our JavaScript modules: `data-module`
|
||||
- All DOM related JavaScript is hooked to `js-` prefixed HTML classes
|
||||
- [jQuery](https://jquery.com) is globally included
|
||||
|
||||
[locomtl]: https://locomotive.ca
|
||||
[demo-grid]: https://codepen.io/AntoineBoulanger/pen/EaLNxe
|
||||
[demo-form]: https://codepen.io/AntoineBoulanger/pen/uBJmi
|
||||
|
||||
## Page transitions
|
||||
We use [Pjax](https://github.com/MoOx/pjax) by MoOx.
|
||||
|
||||
### Setup
|
||||
1. Create a wrapper : `.js-pjax-wrapper` and a container `.js-pjax-container` inside. When a transition is launched, the new container is put inside the wrapper, and the old one is remove.
|
||||
|
||||
2. Main settings are set inside `assets/scripts/transitions/TransitionManager.js`
|
||||
|
||||
3. `BaseTransition` is launched by default, to set a new transition (like `CustomTransition`) :
|
||||
- create a new class `TestTransition.js` witch extends `BaseTransition` in `assets/scripts/transitions/`
|
||||
- add a line in `assets/scripts/transitions/transitions.js` to add your transition
|
||||
- use it like : `<a href="/yourUrl" data-transition="TestTransition">My page</a>`
|
||||
- Enjoy and made everything you want in your transition, check `BaseTransition.js` or `CustomTransition.js` like example
|
||||
|
||||
### Schema
|
||||
|
||||
Legend
|
||||
- `[ ]` : listener
|
||||
- `*` : trigger event
|
||||
|
||||
`[pjax:send]` -> (transition) launch()
|
||||
|
||||
`[pjax:switch]` (= new view is loaded) -> (BaseTransition) `hideView()` -> hide animations & `*readyToRemove`
|
||||
|
||||
`[readyToRemove]` -> `remove()` -> delete modules, remove oldView from the DOM, innerHTML newView, init modules, `display()`
|
||||
|
||||
`display()` -> (BaseTransition) `displayView()` -> display animations & `*readyToDestroy`
|
||||
-> init new modules
|
||||
|
||||
`[readyToRemove]` -> reinit()
|
||||
|
||||
## Locomotive Scroll
|
||||
|
||||

|
||||
- [locomotive-scroll](https://github.com/locomotivemtl/locomotive-scroll)
|
||||
|
||||
### Configuration
|
||||
- Create a `.o-scroll` container with `data-module="Scroll"`
|
||||
- in the module `Scroll.js` you have a basic initialisation
|
||||
|
||||
### Options
|
||||
|
||||
Options | Type | Description
|
||||
--- | --- | ---
|
||||
container | $element | Scroll container (with the smooth scroll, this container will be transform)
|
||||
selector | String | Every elements will be check by the scroll, can be affect by a followed data attributes
|
||||
smooth | Boolean | If you want a smooth scroll
|
||||
smoothMobile | Boolean | If you want a smooth scroll on mobile
|
||||
mobileContainer | $element | Scroll container on mobile, document by default
|
||||
getWay | Boolean | if true, the animate will determine if you scroll down or scroll up
|
||||
getSpeed | Boolean | if true, the animate will calcul the velocity of your scroll. Access with `this.scroll.y`
|
||||
|
||||
### Data attributes
|
||||
|
||||
Data | Value | Description
|
||||
--- | --- | ---
|
||||
data-speed | number | Speed of transform for parallax elements
|
||||
data-repeat | false | Determine if the "In View" class is added one or each times
|
||||
data-inview-class | is-show | CSS Class when the element is in view.
|
||||
data-position | top/bottom | Trigger from top/bottom of the window instead of the default from bottom to top
|
||||
data-target | #id, .class | Trigger from another element
|
||||
data-horizontal | false | Use transformX instead of transformY
|
||||
data-sticky | false | Set $element sticky when it's in viewport
|
||||
data-sticky-target | #id | Stop the element stick when the target is in viewport
|
||||
data-callback | `test.Scroll(test:0)` | trigger event, with options way wich return "leave" or "enter" when $element is in viewport
|
||||
data-viewport-offset | i,j | value between 0 to 1 (0.3 to start at 30% of the bottom of the viewport), useful to trigger a sequence of callbacks. (i : value wich start at the bottom, j : start at the top, j is optional)
|
||||
* [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
|
||||
[ITCSS]: https://itcss.io/
|
||||
[Locomotive Scroll]: https://npmjs.com/package/locomotive-scroll
|
||||
[modularJS]: https://npmjs.com/package/modujs
|
||||
[modularLoad]: https://npmjs.com/package/modularload
|
||||
[Sass]: https://sass-lang.com/
|
||||
[SVG Mixer]: https://npmjs.com/package/svg-mixer
|
||||
[Node]: https://nodejs.org/
|
||||
[NPM]: https://npmjs.com/
|
||||
[NVM]: https://github.com/nvm-sh/nvm
|
||||
|
||||
3
assets.json
Normal file
3
assets.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 1691082391092
|
||||
}
|
||||
@@ -1,161 +1,65 @@
|
||||
import { APP_NAME, $document, $pjaxWrapper } from './utils/environment';
|
||||
|
||||
import globals from './globals';
|
||||
|
||||
import { arrayContains, removeFromArray } from './utils/array';
|
||||
import { getNodeData } from './utils/html';
|
||||
import { isFunction } from './utils/is';
|
||||
|
||||
// Basic modules
|
||||
import modular from 'modujs';
|
||||
import * as modules from './modules';
|
||||
import globals from './globals';
|
||||
import { debounce } from './utils/tickers'
|
||||
import { $html } from './utils/dom';
|
||||
import { ENV, FONT, CUSTOM_EVENT, CSS_CLASS } from './config'
|
||||
import { isFontLoadingAPIAvailable, loadFonts } from './utils/fonts';
|
||||
|
||||
const MODULE_NAME = 'App';
|
||||
const EVENT_NAMESPACE = `${APP_NAME}.${MODULE_NAME}`;
|
||||
const app = new modular({
|
||||
modules: modules,
|
||||
});
|
||||
|
||||
export const EVENT = {
|
||||
INIT_MODULES: `initModules.${EVENT_NAMESPACE}`,
|
||||
INIT_SCOPED_MODULES: `initScopedModules.${EVENT_NAMESPACE}`,
|
||||
DELETE_SCOPED_MODULES: `deleteScopedModules.${EVENT_NAMESPACE}`
|
||||
window.onload = (event) => {
|
||||
const $style = document.getElementById('main-css');
|
||||
|
||||
if ($style) {
|
||||
if ($style.isLoaded) {
|
||||
init();
|
||||
} else {
|
||||
$style.addEventListener('load', (event) => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn('The "main-css" stylesheet not found');
|
||||
}
|
||||
};
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
this.modules = modules;
|
||||
this.currentModules = [];
|
||||
function init() {
|
||||
globals();
|
||||
|
||||
$document.on(EVENT.INIT_MODULES, (event) => {
|
||||
this.initGlobals(event.firstBlood)
|
||||
.deleteModules(event)
|
||||
.initModules(event);
|
||||
});
|
||||
app.init(app);
|
||||
|
||||
$document.on(EVENT.INIT_SCOPED_MODULES, (event) => {
|
||||
this.initModules(event);
|
||||
});
|
||||
$html.classList.add(CSS_CLASS.LOADED);
|
||||
$html.classList.add(CSS_CLASS.READY);
|
||||
$html.classList.remove(CSS_CLASS.LOADING);
|
||||
|
||||
$document.on(EVENT.DELETE_SCOPED_MODULES, (event) => {
|
||||
this.deleteModules(event);
|
||||
});
|
||||
}
|
||||
// Bind window resize event with default vars
|
||||
const resizeEndEvent = new CustomEvent(CUSTOM_EVENT.RESIZE_END)
|
||||
window.addEventListener('resize', () => {
|
||||
$html.style.setProperty('--vw', `${document.documentElement.clientWidth * 0.01}px`)
|
||||
debounce(() => {
|
||||
window.dispatchEvent(resizeEndEvent)
|
||||
}, 200, false)
|
||||
})
|
||||
|
||||
/**
|
||||
* Destroy all existing modules or a specific scope of modules
|
||||
* @param {Object} event The event being triggered.
|
||||
* @return {Object} Self (allows chaining)
|
||||
* Eagerly load the following fonts.
|
||||
*/
|
||||
deleteModules(event) {
|
||||
let destroyAll = true;
|
||||
let moduleIds = [];
|
||||
if (isFontLoadingAPIAvailable) {
|
||||
loadFonts(FONT.EAGER, ENV.IS_DEV).then((eagerFonts) => {
|
||||
$html.classList.add(CSS_CLASS.FONTS_LOADED);
|
||||
|
||||
// Check for scope first
|
||||
if (event.$scope instanceof jQuery && event.$scope.length > 0) {
|
||||
// Modules within scope
|
||||
const $modules = event.$scope.find('[data-module]');
|
||||
|
||||
// Determine their uids
|
||||
moduleIds = $.makeArray($modules.map(function(index) {
|
||||
return $modules.eq(index).data('uid');
|
||||
}));
|
||||
|
||||
if (moduleIds.length > 0) {
|
||||
destroyAll = false;
|
||||
} else {
|
||||
return this;
|
||||
if (ENV.IS_DEV) {
|
||||
console.group('Eager fonts loaded!', eagerFonts.length, '/', document.fonts.size);
|
||||
console.group('State of eager fonts:')
|
||||
eagerFonts.forEach((font) => console.log(font.family, font.style, font.weight, font.status/*, 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()
|
||||
}
|
||||
}
|
||||
|
||||
// Loop modules and destroying all of them, or specific ones
|
||||
let i = this.currentModules.length;
|
||||
|
||||
while (i--) {
|
||||
if (destroyAll || arrayContains(moduleIds, this.currentModules[i].uid)) {
|
||||
removeFromArray(moduleIds, this.currentModules[i].uid);
|
||||
this.currentModules[i].destroy();
|
||||
this.currentModules.splice(i);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute global functions and settings
|
||||
* Allows you to initialize global modules only once if you need
|
||||
* (ex.: when using Barba.js or SmoothState.js)
|
||||
* @return {Object} Self (allows chaining)
|
||||
*/
|
||||
initGlobals(firstBlood) {
|
||||
globals(firstBlood);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find modules and initialize them
|
||||
* @param {Object} event The event being triggered.
|
||||
* @return {Object} Self (allows chaining)
|
||||
*/
|
||||
initModules(event) {
|
||||
// Elements with module
|
||||
let $moduleEls = [];
|
||||
|
||||
// If first blood, load all modules in the DOM
|
||||
// If scoped, render elements with modules
|
||||
// If Barba, load modules contained in Barba container
|
||||
if (event.firstBlood) {
|
||||
$moduleEls = $document.find('[data-module]');
|
||||
} else if (event.$scope instanceof jQuery && event.$scope.length > 0) {
|
||||
$moduleEls = event.$scope.find('[data-module]');
|
||||
} else if (event.isPjax) {
|
||||
$moduleEls = $pjaxWrapper.find('[data-module]');
|
||||
}
|
||||
|
||||
// Loop through elements
|
||||
let i = 0;
|
||||
const elsLen = $moduleEls.length;
|
||||
|
||||
for (; i < elsLen; i++) {
|
||||
|
||||
// Current element
|
||||
let el = $moduleEls[i];
|
||||
|
||||
// All data- attributes considered as options
|
||||
let options = getNodeData(el);
|
||||
|
||||
// Add current DOM element and jQuery element
|
||||
options.el = el;
|
||||
options.$el = $moduleEls.eq(i);
|
||||
|
||||
// Module does exist at this point
|
||||
let attr = options.module;
|
||||
|
||||
// Splitting modules found in the data-attribute
|
||||
let moduleIdents = attr.split(/[,\s]+/g);
|
||||
|
||||
// Loop modules
|
||||
let j = 0;
|
||||
let modulesLen = moduleIdents.length;
|
||||
|
||||
for (; j < modulesLen; j++) {
|
||||
let moduleAttr = moduleIdents[j];
|
||||
|
||||
if (typeof this.modules[moduleAttr] === 'function') {
|
||||
let module = new this.modules[moduleAttr](options);
|
||||
this.currentModules.push(module);
|
||||
module.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// IIFE for loading the application
|
||||
// ==========================================================================
|
||||
(function() {
|
||||
new App();
|
||||
$document.triggerHandler({
|
||||
type: EVENT.INIT_MODULES,
|
||||
firstBlood: true
|
||||
});
|
||||
})();
|
||||
|
||||
57
assets/scripts/config.js
Normal file
57
assets/scripts/config.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* > When using the esBuild API, all `process.env.NODE_ENV` expressions
|
||||
* > are automatically defined to `"production"` if all minification
|
||||
* > options are enabled and `"development"` otherwise. This only happens
|
||||
* > if `process`, `process.env`, and `process.env.NODE_ENV` are not already
|
||||
* > defined. This substitution is necessary to avoid code crashing instantly
|
||||
* > (since `process` is a Node API, not a web API).
|
||||
* > — https://esbuild.github.io/api/#platform
|
||||
*/
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV
|
||||
const IS_DESKTOP = typeof window.orientation === 'undefined'
|
||||
|
||||
// Main environment variables
|
||||
const ENV = Object.freeze({
|
||||
// Node environment
|
||||
NAME: NODE_ENV,
|
||||
IS_PROD: NODE_ENV === 'production',
|
||||
IS_DEV: NODE_ENV === 'development',
|
||||
|
||||
// Device
|
||||
IS_DESKTOP,
|
||||
IS_MOBILE: !IS_DESKTOP,
|
||||
})
|
||||
|
||||
// Main CSS classes used within the project
|
||||
const CSS_CLASS = Object.freeze({
|
||||
LOADING: 'is-loading',
|
||||
LOADED: 'is-loaded',
|
||||
READY: 'is-ready',
|
||||
FONTS_LOADED: 'fonts-loaded',
|
||||
IMAGE: "c-image",
|
||||
IMAGE_LAZY_LOADED: "-lazy-loaded",
|
||||
IMAGE_LAZY_LOADING: "-lazy-loading",
|
||||
IMAGE_LAZY_ERROR: "-lazy-error",
|
||||
})
|
||||
|
||||
// Custom js events
|
||||
const CUSTOM_EVENT = Object.freeze({
|
||||
RESIZE_END: 'loco.resizeEnd',
|
||||
// ...
|
||||
})
|
||||
|
||||
// Fonts parameters
|
||||
const FONT = Object.freeze({
|
||||
EAGER: [
|
||||
{ family: 'Source Sans', style: 'normal', weight: 400 },
|
||||
{ family: 'Source Sans', style: 'normal', weight: 700 },
|
||||
],
|
||||
})
|
||||
|
||||
export {
|
||||
ENV,
|
||||
CSS_CLASS,
|
||||
CUSTOM_EVENT,
|
||||
FONT,
|
||||
}
|
||||
@@ -1,10 +1,29 @@
|
||||
import TransitionManager from './transitions/TransitionManager';
|
||||
import svg4everybody from 'svg4everybody';
|
||||
import { ENV } from './config';
|
||||
import { triggerLazyloadCallbacks } from './utils/image';
|
||||
|
||||
export default function(firstBlood) {
|
||||
// Dynamic imports for development mode only
|
||||
let gridHelper;
|
||||
(async () => {
|
||||
if (ENV.IS_DEV) {
|
||||
const gridHelperModule = await import('./utils/grid-helper');
|
||||
gridHelper = gridHelperModule?.gridHelper;
|
||||
}
|
||||
})();
|
||||
|
||||
export default function () {
|
||||
/**
|
||||
* Use external SVG spritemaps
|
||||
*/
|
||||
svg4everybody();
|
||||
|
||||
if (firstBlood) {
|
||||
const transitionManager = new TransitionManager();
|
||||
}
|
||||
/**
|
||||
* Add grid helper
|
||||
*/
|
||||
gridHelper?.();
|
||||
|
||||
/**
|
||||
* Trigger lazyload
|
||||
*/
|
||||
triggerLazyloadCallbacks();
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
let uid = 0;
|
||||
|
||||
/**
|
||||
* Abstract Module
|
||||
*/
|
||||
export default class {
|
||||
constructor(options) {
|
||||
this.$el = options.$el || null;
|
||||
this.el = options.el || null;
|
||||
|
||||
// Generate a unique module identifier
|
||||
this.uid = 'm-' + uid++;
|
||||
// Use jQuery's data API to "store it in the DOM"
|
||||
this.$el.data('uid', this.uid);
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
destroy() {
|
||||
if (this.$el) {
|
||||
this.$el.removeData('uid')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,17 @@
|
||||
import { APP_NAME } from '../utils/environment';
|
||||
import AbstractModule from './AbstractModule';
|
||||
|
||||
const MODULE_NAME = 'Example';
|
||||
const EVENT_NAMESPACE = `${APP_NAME}.${MODULE_NAME}`;
|
||||
|
||||
const EVENT = {
|
||||
CLICK: `click.${EVENT_NAMESPACE}`
|
||||
};
|
||||
|
||||
export default class extends AbstractModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
// Declaration of properties
|
||||
console.log('🔨 [module]:constructor - Example');
|
||||
import { module } from 'modujs';
|
||||
import { FONT } from '../config';
|
||||
import { whenReady } from '../utils/fonts';
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
init() {
|
||||
// Set events and such
|
||||
|
||||
whenReady(FONT.EAGER).then((fonts) => this.onFontsLoaded(fonts));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.log('❌ [module]:destroy - Example');
|
||||
super.destroy();
|
||||
this.$el.off(`.${EVENT_NAMESPACE}`);
|
||||
onFontsLoaded(fonts) {
|
||||
console.log('Example: Eager Fonts Loaded!', fonts)
|
||||
}
|
||||
}
|
||||
|
||||
35
assets/scripts/modules/Load.js
Normal file
35
assets/scripts/modules/Load.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { module } from 'modujs';
|
||||
import modularLoad from 'modularload';
|
||||
import { resetLazyloadCallbacks, triggerLazyloadCallbacks } from "../utils/image";
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.load = new modularLoad({
|
||||
enterDelay: 0,
|
||||
transitions: {
|
||||
customTransition: {}
|
||||
}
|
||||
});
|
||||
|
||||
this.load.on('loaded', (transition, oldContainer, newContainer) => {
|
||||
this.call('destroy', oldContainer, 'app');
|
||||
this.call('update', newContainer, 'app');
|
||||
|
||||
/**
|
||||
* Trigger lazyload
|
||||
*/
|
||||
triggerLazyloadCallbacks();
|
||||
});
|
||||
|
||||
this.load.on("loading", () => {
|
||||
/**
|
||||
* Remove previous lazyload callbacks
|
||||
*/
|
||||
resetLazyloadCallbacks();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,53 @@
|
||||
import { APP_NAME, $document } from '../utils/environment';
|
||||
import AbstractModule from './AbstractModule';
|
||||
import ScrollManager from '../scroll/vendors/ScrollManager';
|
||||
import { module } from 'modujs'
|
||||
import LocomotiveScroll from 'locomotive-scroll'
|
||||
|
||||
const MODULE_NAME = 'Scroll';
|
||||
const EVENT_NAMESPACE = `${APP_NAME}.${MODULE_NAME}`;
|
||||
|
||||
export default class extends AbstractModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
init() {
|
||||
setTimeout(() => {
|
||||
this.scrollManager = new ScrollManager({
|
||||
container: this.$el,
|
||||
selector: '.js-animate',
|
||||
smooth: false,
|
||||
smoothMobile: false,
|
||||
mobileContainer: $document,
|
||||
getWay: false,
|
||||
getSpeed: false
|
||||
});
|
||||
}, 500);
|
||||
this.scroll = new LocomotiveScroll({
|
||||
modularInstance: this,
|
||||
})
|
||||
|
||||
// // Force scroll to top
|
||||
// if (history.scrollRestoration) {
|
||||
// history.scrollRestoration = 'manual'
|
||||
// window.scrollTo(0, 0)
|
||||
// }
|
||||
}
|
||||
|
||||
scrollTo(params) {
|
||||
let { target, ...options } = params
|
||||
|
||||
options = Object.assign({
|
||||
// Defaults
|
||||
duration: 1,
|
||||
}, options)
|
||||
|
||||
this.scroll?.scrollTo(target, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe new scroll elements
|
||||
*
|
||||
* @param $newContainer (HTMLElement)
|
||||
*/
|
||||
addScrollElements($newContainer) {
|
||||
this.scroll?.addScrollElements($newContainer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unobserve scroll elements
|
||||
*
|
||||
* @param $oldContainer (HTMLElement)
|
||||
*/
|
||||
removeScrollElements($oldContainer) {
|
||||
this.scroll?.removeScrollElements($oldContainer)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.scrollManager.destroy();
|
||||
this.scroll.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Extended Locomotive Scroll
|
||||
// ==========================================================================
|
||||
/* jshint esnext: true */
|
||||
import Scroll, { EVENT_KEY as VENDOR_EVENT_KEY, EVENT as VENDOR_EVENTS, DEFAULTS as VENDOR_DEFAULTS } from './vendors/Scroll'
|
||||
|
||||
/**
|
||||
* UNCOMMENT ONLY THE LINES YOU NEED
|
||||
*/
|
||||
// import { $window, $document } from '../../utils/environment';
|
||||
// import debounce from '../../utils/debounce';
|
||||
// import { isNumeric } from '../../utils/is';
|
||||
|
||||
export const EVENT_KEY = VENDOR_EVENT_KEY;
|
||||
|
||||
export const EVENT = Object.assign(VENDOR_EVENTS, {
|
||||
// TEST: `test.${EVENT_KEY}`
|
||||
});
|
||||
|
||||
export const DEFAULTS = Object.assign(VENDOR_DEFAULTS, { });
|
||||
|
||||
export default class extends Scroll {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Extended Locomotive Smooth Scroll
|
||||
// ==========================================================================
|
||||
/* jshint esnext: true */
|
||||
import SmoothScroll from './vendors/SmoothScroll'
|
||||
|
||||
/**
|
||||
* UNCOMMENT ONLY THE LINES YOU NEED
|
||||
*/
|
||||
// import { $window, $document, $html } from '../utils/environment';
|
||||
// import Scroll, { DEFAULTS, EVENT } from './Scroll';
|
||||
|
||||
// import debounce from '../utils/debounce';
|
||||
// import Scrollbar from 'smooth-scrollbar';
|
||||
// import { isNumeric } from '../utils/is';
|
||||
|
||||
export default class extends SmoothScroll {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { APP_NAME, $document, $html, $body, isDebug, $pjaxWrapper } from '../utils/environment';
|
||||
|
||||
import { EVENT as TransitionEvent } from './TransitionManager'
|
||||
|
||||
export default class {
|
||||
constructor(options) {
|
||||
|
||||
this.options = options;
|
||||
this.wrapper = options.wrapper;
|
||||
this.overrideClass = options.overrideClass ? options.overrideClass : '';
|
||||
this.clickedLink = options.clickedLink;
|
||||
|
||||
}
|
||||
|
||||
launch() {
|
||||
if(isDebug) {
|
||||
console.log("---- Launch transition 👊 -----");
|
||||
}
|
||||
|
||||
$html
|
||||
.removeClass('has-dom-loaded has-dom-animated ')
|
||||
.addClass(`has-dom-loading ${this.overrideClass}`);
|
||||
|
||||
}
|
||||
|
||||
hideView(oldView, newView) {
|
||||
if(isDebug) {
|
||||
console.log('----- ❌ [VIEW]:hide - ', oldView.getAttribute('data-template'));
|
||||
}
|
||||
|
||||
// launch it at the end (animations...)
|
||||
$document.triggerHandler({
|
||||
type:TransitionEvent.READYTOAPPEND,
|
||||
oldView: oldView,
|
||||
newView: newView
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
displayView(view) {
|
||||
|
||||
if(isDebug) {
|
||||
console.log('----- ✅ [VIEW]:display :', view.getAttribute('data-template'));
|
||||
}
|
||||
|
||||
$html.attr('data-template', view.getAttribute('data-template'));
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
$html
|
||||
.addClass('has-dom-loaded')
|
||||
.removeClass('has-dom-loading');
|
||||
|
||||
setTimeout(() => {
|
||||
$html
|
||||
.removeClass(this.overrideClass)
|
||||
.addClass('has-dom-animated');
|
||||
}, 1000);
|
||||
|
||||
// launch it at the end (animations...)
|
||||
$document.triggerHandler({
|
||||
type:TransitionEvent.READYTODESTROY
|
||||
});
|
||||
|
||||
},1000);
|
||||
}
|
||||
|
||||
|
||||
destroy() {
|
||||
if(isDebug) {
|
||||
console.log("---- ❌ [transition]:destroy -----");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { APP_NAME, $document, $html, isDebug, $pjaxWrapper } from '../utils/environment';
|
||||
import BaseTransition from './BaseTransition';
|
||||
|
||||
import { EVENT as TransitionEvent } from './TransitionManager'
|
||||
|
||||
export default class extends BaseTransition{
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.overrideClass = '-custom-transition';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
import Pjax from 'pjax';
|
||||
import { APP_NAME, $document, $html, isDebug, $pjaxWrapper, $window } from '../utils/environment';
|
||||
import { EVENT as APP_EVENT } from '../app';
|
||||
|
||||
//List here all of your transitions
|
||||
import * as transitions from './transitions';
|
||||
|
||||
const MODULE_NAME = 'Transition';
|
||||
const EVENT_NAMESPACE = `${APP_NAME}.${MODULE_NAME}`;
|
||||
|
||||
export const EVENT = {
|
||||
CLICK: `click.${EVENT_NAMESPACE}`,
|
||||
READYTOAPPEND: `readyToAppend.${EVENT_NAMESPACE}`,
|
||||
READYTODESTROY: `readyToDestroy.${EVENT_NAMESPACE}`,
|
||||
GOTO: `goto.${EVENT_NAMESPACE}`
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@todo :
|
||||
|
||||
- ✅ get data-transition on clicked link -> launch() and add switch(){}
|
||||
- ✅ add goto listener
|
||||
- ✅ add overrideClass system for all transitions
|
||||
- ✅ add base class manager like old DefaultTransition (has-dom-loaded, has-dom-loading etc..)
|
||||
|
||||
|
||||
======= SCHEMA =======
|
||||
|
||||
[] : listener
|
||||
* : trigger event
|
||||
|
||||
[pjax:send] -> (transition) launch()
|
||||
|
||||
[pjax:switch] (= new view is loaded) -> (transition) hideView()-> hide animations & *readyToAppend
|
||||
|
||||
[readyToAppend] -> append() -> delete modules
|
||||
-> remove oldView from the DOM, and innerHTMl newView
|
||||
-> change()
|
||||
|
||||
display() -> (transition) displayView() -> display animations & *readyToDestroy
|
||||
-> init new modules
|
||||
|
||||
[readyToAppend] -> reinit()
|
||||
|
||||
*/
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
|
||||
|
||||
// jQuery ondomready
|
||||
$window.on('load',() => {
|
||||
this.load();
|
||||
});
|
||||
|
||||
this.transition = new transitions['BaseTransition']({
|
||||
wrapper: this.wrapper
|
||||
});
|
||||
|
||||
/*
|
||||
===== PJAX CONFIGURATION =====
|
||||
*/
|
||||
|
||||
this.containerClass = '.js-pjax-container';
|
||||
this.wrapperId = 'js-pjax-wrapper';
|
||||
this.noPjaxRequestClass = 'no-transition';
|
||||
this.wrapper = document.getElementById(this.wrapperId);
|
||||
|
||||
this.options = {
|
||||
debug: false,
|
||||
cacheBust: false,
|
||||
elements: [`a:not(.${this.noPjaxRequestClass})`,'form[action]'],
|
||||
selectors: ['title',`${this.containerClass}`],
|
||||
switches: {},
|
||||
requestOptions: {
|
||||
timeout: 2000
|
||||
}
|
||||
};
|
||||
this.options.switches[this.containerClass] = (oldEl, newEl, options) => this.switch(oldEl, newEl, options)
|
||||
this.pjax = new Pjax(this.options);
|
||||
|
||||
/*
|
||||
===== LISTENERS =====
|
||||
*/
|
||||
|
||||
document.addEventListener('pjax:send',(e) => this.send(e));
|
||||
|
||||
|
||||
$document.on(EVENT.READYTOAPPEND,(event) => {
|
||||
this.append(event.oldView, event.newView);
|
||||
});
|
||||
$document.on(EVENT.READYTODESTROY,(event) => {
|
||||
this.reinit();
|
||||
});
|
||||
|
||||
|
||||
/** goto exampe
|
||||
$document.triggerHandler({
|
||||
type: 'goto.Transition',
|
||||
options : {
|
||||
el: {{element clicked?}},
|
||||
link: {{url}}
|
||||
}
|
||||
});
|
||||
*/
|
||||
$document.on(EVENT.GOTO, (e) => {
|
||||
if(e.options.el != undefined) {
|
||||
this.autoEl = e.options.el.get(0);
|
||||
}
|
||||
this.pjax.loadUrl(e.options.link, $.extend({}, this.pjax.options));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (PJAX) Launch when pjax receive a request
|
||||
* get & manage data-transition,init and launch it
|
||||
* @param {event}
|
||||
* @return void
|
||||
*/
|
||||
send(e) {
|
||||
if(isDebug) {
|
||||
console.log("---- Launch request 🙌 -----");
|
||||
}
|
||||
|
||||
let el,transition;
|
||||
|
||||
if(e.triggerElement != undefined) {
|
||||
|
||||
el = e.triggerElement;
|
||||
|
||||
transition = el.getAttribute('data-transition') ? el.getAttribute('data-transition') : 'BaseTransition';
|
||||
$html.attr('data-transition',transition);
|
||||
|
||||
} else {
|
||||
|
||||
if (this.autoEl != undefined) {
|
||||
el = this.autoEl;
|
||||
} else {
|
||||
el = document;
|
||||
}
|
||||
|
||||
transition = 'BaseTransition';
|
||||
}
|
||||
|
||||
// options available : wrapper, overrideClass
|
||||
this.transition = new transitions[transition]({
|
||||
wrapper: this.wrapper,
|
||||
clickedLink: el
|
||||
});
|
||||
|
||||
this.transition.launch();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* (PJAX) Launch when new page is loaded
|
||||
* @param {js dom element},
|
||||
* @param {js dom element}
|
||||
* @param {options : pjax options}
|
||||
* @return void
|
||||
*/
|
||||
switch(oldView, newView, options) {
|
||||
if(isDebug) {
|
||||
console.log('---- Next view loaded 👌 -----');
|
||||
}
|
||||
this.transition.hideView(oldView, newView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch when you trigger EVENT.READYTOAPPEND in your transition
|
||||
* after newView append, launch this.change()
|
||||
* @param {js dom element},
|
||||
* @param {js dom element}
|
||||
* @return void
|
||||
*/
|
||||
append(oldView, newView) {
|
||||
|
||||
newView.style.opacity = 0;
|
||||
this.wrapper.appendChild(newView);
|
||||
|
||||
// Add these 2 rAF if you want to have the containers overlapped
|
||||
// Useful with a image transition, to prevent flickering
|
||||
// requestAnimationFrame(() => {
|
||||
// requestAnimationFrame(() => {
|
||||
newView.style.opacity = 1;
|
||||
this.change(oldView, newView);
|
||||
// });
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* launch after this.append(), remove modules, remove oldView and set the newView
|
||||
* @param {js dom element},
|
||||
* @return void
|
||||
*/
|
||||
change(oldView, newView) {
|
||||
|
||||
$document.triggerHandler({
|
||||
type: APP_EVENT.DELETE_SCOPED_MODULES,
|
||||
$scope: $pjaxWrapper
|
||||
});
|
||||
|
||||
this.wrapper.innerHTML = newView.outerHTML;
|
||||
|
||||
oldView.remove();
|
||||
|
||||
// Fetch any inline script elements.
|
||||
const scripts = newView.querySelectorAll('script.js-inline');
|
||||
|
||||
if (scripts instanceof window.NodeList) {
|
||||
let i = 0;
|
||||
let len = scripts.length;
|
||||
for (; i < len; i++) {
|
||||
eval(scripts[i].innerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
$document.triggerHandler({
|
||||
type: APP_EVENT.INIT_SCOPED_MODULES,
|
||||
isPjax: true
|
||||
});
|
||||
|
||||
this.pjax.onSwitch();
|
||||
|
||||
this.transition.displayView(newView);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch when you trigger EVENT.READYTODESTROY in your transition -> displayView(), at the end
|
||||
* @return void
|
||||
*/
|
||||
reinit() {
|
||||
this.transition.destroy();
|
||||
$html.attr('data-transition','');
|
||||
this.transition = new transitions['BaseTransition']({
|
||||
wrapper: this.wrapper
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM is loaded
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
load() {
|
||||
$html.addClass('has-dom-loaded');
|
||||
$html.removeClass('has-dom-loading');
|
||||
setTimeout(() => {
|
||||
$html.addClass('has-dom-animated');
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export {default as BaseTransition} from './BaseTransition';
|
||||
export {default as CustomTransition} from './CustomTransition';
|
||||
@@ -1,88 +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));
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export default function(func, wait, immediate) {
|
||||
let timeout;
|
||||
return function() {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
const later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
const callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
7
assets/scripts/utils/dom.js
Normal file
7
assets/scripts/utils/dom.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const $html = document.documentElement
|
||||
const $body = document.body
|
||||
|
||||
export {
|
||||
$html,
|
||||
$body,
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
const APP_NAME = 'Boilerplate';
|
||||
const DATA_API_KEY = '.data-api';
|
||||
|
||||
const $document = $(document);
|
||||
const $window = $(window);
|
||||
const $html = $(document.documentElement).removeClass('has-no-js').addClass('has-js');
|
||||
const $body = $(document.body);
|
||||
const $pjaxWrapper = $('#js-pjax-wrapper');
|
||||
|
||||
const isDebug = !!$html.data('debug');
|
||||
|
||||
export { APP_NAME, DATA_API_KEY, $document, $window, $html, $body, isDebug, $pjaxWrapper };
|
||||
402
assets/scripts/utils/fonts.js
Normal file
402
assets/scripts/utils/fonts.js
Normal file
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Font Faces
|
||||
*
|
||||
* Provides utilities to facilitate interactions with the CSS Font Loading API.
|
||||
*
|
||||
* Features functions to:
|
||||
*
|
||||
* - Retrieve one or more `FontFace` instances based on a font search query.
|
||||
* - Check if a `FontFace` instance matches a font search query.
|
||||
* - Eagerly load fonts that match a font search query.
|
||||
* - Wait until fonts that match a font search query are loaded.
|
||||
*
|
||||
* References:
|
||||
*
|
||||
* - {@link https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FontFaceReference
|
||||
*
|
||||
* @property {string} family - The name used to identify the font in our CSS.
|
||||
* @property {string} [style] - The style used by the font in our CSS.
|
||||
* @property {string} [weight] - The weight used by the font in our CSS.
|
||||
*/
|
||||
|
||||
const isFontLoadingAPIAvailable = ('fonts' in document);
|
||||
|
||||
/**
|
||||
* Determines if the given font matches the given `FontFaceReference`.
|
||||
*
|
||||
* @param {FontFace} font - The font to inspect.
|
||||
* @param {FontFaceReference} criterion - The object of property values to match.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function conformsToReference(font, criterion)
|
||||
{
|
||||
for (const [ key, value ] of Object.entries(criterion)) {
|
||||
switch (key) {
|
||||
case 'family': {
|
||||
if (trim(font[key]) !== value) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'weight': {
|
||||
/**
|
||||
* Note concerning font weights:
|
||||
* Loose equality (`==`) is used to compare numeric weights,
|
||||
* a number (`400`) and a numeric string (`"400"`).
|
||||
* Comparison between numeric and keyword values is neglected.
|
||||
*
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping
|
||||
*/
|
||||
if (font[key] != value) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (font[key] !== value) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given font matches the given font shorthand.
|
||||
*
|
||||
* @param {FontFace} font - The font to inspect.
|
||||
* @param {string} criterion - The font shorthand to match.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function conformsToShorthand(font, criterion)
|
||||
{
|
||||
const family = trim(font.family);
|
||||
|
||||
if (trim(family) === criterion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
criterion.endsWith(trim(family)) && (
|
||||
criterion.match(font.weight) ||
|
||||
criterion.match(font.style)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given font matches any of the given criteria.
|
||||
*
|
||||
* @param {FontFace} font - The font to inspect.
|
||||
* @param {FontFaceReference[]} criteria - A list of objects with property values to match.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function conformsToAnyReference(font, criteria)
|
||||
{
|
||||
for (const criterion of criteria) {
|
||||
if (conformsToReference(font, criterion)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
|
||||
* the provided `FontFaceReference`.
|
||||
*
|
||||
* @param {FontFaceReference} font
|
||||
*
|
||||
* @returns {FontFace[]}
|
||||
*/
|
||||
function findManyByReference(search)
|
||||
{
|
||||
const found = [];
|
||||
|
||||
for (const font of document.fonts) {
|
||||
if (conformsToReference(font, search)) {
|
||||
found.push(font);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
|
||||
* the provided font shorthand.
|
||||
*
|
||||
* @param {string} font
|
||||
*
|
||||
* @returns {FontFace[]}
|
||||
*/
|
||||
function findManyByShorthand(search)
|
||||
{
|
||||
const found = [];
|
||||
|
||||
for (const font of document.fonts) {
|
||||
if (conformsToShorthand(font, search)) {
|
||||
found.push(font);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first `FontFace` from `document.fonts` that satisfies
|
||||
* the provided `FontFaceReference`.
|
||||
*
|
||||
* @param {FontFaceReference} font
|
||||
*
|
||||
* @returns {?FontFace}
|
||||
*/
|
||||
function findOneByReference(search)
|
||||
{
|
||||
for (const font of document.fonts) {
|
||||
if (conformsToReference(font, criterion)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first `FontFace` from `document.fonts` that satisfies
|
||||
* the provided font shorthand.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - "Roboto"
|
||||
* - "italic bold 16px Roboto"
|
||||
*
|
||||
* @param {string} font
|
||||
*
|
||||
* @returns {?FontFace}
|
||||
*/
|
||||
function findOneByShorthand(search)
|
||||
{
|
||||
for (const font of document.fonts) {
|
||||
if (conformsToShorthand(font, search)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `FontFace` from `document.fonts` that satisfies
|
||||
* the provided query.
|
||||
*
|
||||
* @param {FontFaceReference|string} font - Either:
|
||||
* - a `FontFaceReference` object
|
||||
* - a font family name
|
||||
* - a font specification, for example "italic bold 16px Roboto"
|
||||
*
|
||||
* @returns {?FontFace}
|
||||
*
|
||||
* @throws {TypeError}
|
||||
*/
|
||||
function getAny(search) {
|
||||
if (search) {
|
||||
switch (typeof search) {
|
||||
case 'string':
|
||||
return findOneByShorthand(search);
|
||||
|
||||
case 'object':
|
||||
return findOneByReference(search);
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
'Expected font query to be font shorthand or font reference'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of all `FontFace` from `document.fonts` that satisfy
|
||||
* the provided queries.
|
||||
*
|
||||
* @param {FontFaceReference|string|(FontFaceReference|string)[]} queries
|
||||
*
|
||||
* @returns {FontFace[]}
|
||||
*
|
||||
* @throws {TypeError}
|
||||
*/
|
||||
function getMany(queries) {
|
||||
if (!Array.isArray(queries)) {
|
||||
queries = [ queries ];
|
||||
}
|
||||
|
||||
const found = new Set();
|
||||
|
||||
queries.forEach((search) => {
|
||||
if (search) {
|
||||
switch (typeof search) {
|
||||
case 'string':
|
||||
found.add(...findManyByShorthand(search));
|
||||
return;
|
||||
|
||||
case 'object':
|
||||
found.add(...findManyByReference(search));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
'Expected font query to be font shorthand or font reference'
|
||||
);
|
||||
})
|
||||
|
||||
return [ ...found ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a font face is registered.
|
||||
*
|
||||
* @param {FontFace|FontFaceReference|string} search - Either:
|
||||
* - a `FontFace` instance
|
||||
* - a `FontFaceReference` object
|
||||
* - a font family name
|
||||
* - a font specification, for example "italic bold 16px Roboto"
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasAny(search) {
|
||||
if (search instanceof FontFace) {
|
||||
return document.fonts.has(search);
|
||||
}
|
||||
|
||||
return getAny(search) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly load fonts.
|
||||
*
|
||||
* Most user agents only fetch and load fonts when they are first needed
|
||||
* ("lazy loaded"), which can result in a perceptible delay.
|
||||
*
|
||||
* This function will "eager load" the fonts.
|
||||
*
|
||||
* @param {(FontFace|FontFaceReference)[]} fontsToLoad - List of fonts to load.
|
||||
* @param {boolean} [debug] - If TRUE, log details to the console.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function loadFonts(fontsToLoad, debug = false)
|
||||
{
|
||||
if ((fontsToLoad.size ?? fontsToLoad.length) === 0) {
|
||||
throw new TypeError(
|
||||
'Expected at least one font'
|
||||
);
|
||||
}
|
||||
|
||||
return await loadFontsWithAPI([ ...fontsToLoad ], debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly load a font using `FontFaceSet` API.
|
||||
*
|
||||
* @param {FontFace} font
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function loadFontFaceWithAPI(font)
|
||||
{
|
||||
return await (font.status === 'unloaded'
|
||||
? font.load()
|
||||
: font.loaded
|
||||
).then((font) => font, (err) => font)
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly load fonts using `FontFaceSet` API.
|
||||
*
|
||||
* @param {FontFaceReference[]} fontsToLoad
|
||||
* @param {boolean} [debug]
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function loadFontsWithAPI(fontsToLoad, debug = false)
|
||||
{
|
||||
debug && console.group('[loadFonts:API]', fontsToLoad.length, '/', document.fonts.size);
|
||||
|
||||
const fontsToBeLoaded = [];
|
||||
|
||||
for (const fontToLoad of fontsToLoad) {
|
||||
if (fontToLoad instanceof FontFace) {
|
||||
if (!document.fonts.has(fontToLoad)) {
|
||||
document.fonts.add(fontToLoad);
|
||||
}
|
||||
|
||||
fontsToBeLoaded.push(
|
||||
loadFontFaceWithAPI(fontToLoad)
|
||||
);
|
||||
} else {
|
||||
fontsToBeLoaded.push(
|
||||
...getMany(fontToLoad).map((font) => loadFontFaceWithAPI(font))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
debug && console.groupEnd();
|
||||
|
||||
return await Promise.all(fontsToBeLoaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes quotes from the the string.
|
||||
*
|
||||
* When a `@font-face` is declared, the font family is sometimes
|
||||
* defined in quotes which end up included in the `FontFace` instance.
|
||||
*
|
||||
* @param {string} value
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function trim(value) {
|
||||
return value.replace(/['"]+/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves with the specified fonts
|
||||
* when they are done loading or failed.
|
||||
*
|
||||
* @param {FontFaceReference|string|(FontFaceReference|string)[]} queries
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function whenReady(queries)
|
||||
{
|
||||
const fonts = getMany(queries);
|
||||
|
||||
return await Promise.all(fonts.map((font) => font.loaded));
|
||||
}
|
||||
|
||||
export {
|
||||
getAny,
|
||||
getMany,
|
||||
hasAny,
|
||||
isFontLoadingAPIAvailable,
|
||||
loadFonts,
|
||||
whenReady,
|
||||
}
|
||||
138
assets/scripts/utils/grid-helper.js
Normal file
138
assets/scripts/utils/grid-helper.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Grid Helper
|
||||
*
|
||||
* Provides a grid based on the design guidelines and is helpful for web integration.
|
||||
*
|
||||
* - `Control + g` to toggle the grid
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GridHelperReference
|
||||
*
|
||||
* @property {string} [gutterCssVar=GRID_HELPER_GUTTER_CSS_VAR] - CSS variable used to define grid gutters.
|
||||
* @property {string} [marginCssVar=GRID_HELPER_MARGIN_CSS_VAR] - CSS variable used to define grid margins.
|
||||
* @property {string} [rgbaColor=GRID_HELPER_RGBA_COLOR] - RGBA color for the grid appearence.
|
||||
*/
|
||||
|
||||
const GRID_HELPER_GUTTER_CSS_VAR = '--grid-gutter';
|
||||
const GRID_HELPER_MARGIN_CSS_VAR = '--grid-margin';
|
||||
const GRID_HELPER_RGBA_COLOR = 'rgba(255, 0, 0, .1)';
|
||||
|
||||
/**
|
||||
* Create a grid helper
|
||||
*
|
||||
* @param {GridHelperReference}
|
||||
*
|
||||
*/
|
||||
function gridHelper({
|
||||
gutterCssVar = GRID_HELPER_GUTTER_CSS_VAR,
|
||||
marginCssVar = GRID_HELPER_MARGIN_CSS_VAR,
|
||||
rgbaColor = GRID_HELPER_RGBA_COLOR,
|
||||
} = {}) {
|
||||
// Set grid container
|
||||
const $gridContainer = document.createElement('div');
|
||||
document.body.append($gridContainer);
|
||||
|
||||
// Set grid appearence
|
||||
setGridHelperColumns($gridContainer, rgbaColor);
|
||||
setGridHelperStyles($gridContainer, gutterCssVar, marginCssVar);
|
||||
|
||||
// Set grid interactivity
|
||||
setGridEvents($gridContainer, rgbaColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grid container styles
|
||||
*
|
||||
* @param {HTMLElement} $container - DOM Element that contains a list of generated columns
|
||||
* @param {string} gutterCssVar - CSS variable used to define grid gutters.
|
||||
* @param {string} marginCssVar - CSS variable used to define grid margins.
|
||||
*
|
||||
*/
|
||||
function setGridHelperStyles($container, gutterCssVar, marginCssVar) {
|
||||
const elStyles = $container.style;
|
||||
elStyles.zIndex = '10000';
|
||||
elStyles.position = 'fixed';
|
||||
elStyles.top = '0';
|
||||
elStyles.left = '0';
|
||||
elStyles.display = 'flex';
|
||||
elStyles.width = '100%';
|
||||
elStyles.height = '100%';
|
||||
elStyles.columnGap = `var(${gutterCssVar}, 0)`;
|
||||
elStyles.paddingLeft = `var(${marginCssVar}, 0)`;
|
||||
elStyles.paddingRight = `var(${marginCssVar}, 0)`;
|
||||
elStyles.pointerEvents = 'none';
|
||||
elStyles.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grid columns
|
||||
*
|
||||
* @param {HTMLElement} $container - DOM Element that will contain a list of generated columns
|
||||
* @param {string} rgbaColor - RGBA color to stylize the generated columns
|
||||
*
|
||||
*/
|
||||
function setGridHelperColumns($container, rgbaColor) {
|
||||
// Clear columns
|
||||
$container.innerHTML = '';
|
||||
|
||||
// Loop through columns
|
||||
const columns = Number(
|
||||
window.getComputedStyle($container).getPropertyValue('--grid-columns')
|
||||
);
|
||||
|
||||
let $col;
|
||||
for (var i = 0; i < columns; i++) {
|
||||
$col = document.createElement('div');
|
||||
$col.style.flex = '1 1 0';
|
||||
$col.style.backgroundColor = rgbaColor;
|
||||
$container.appendChild($col);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grid events
|
||||
*
|
||||
* Resize to rebuild columns
|
||||
* Keydown/Keyup to toggle the grid display
|
||||
*
|
||||
* @param {HTMLElement} $container - DOM Element that contains a list of generated columns
|
||||
* @param {string} rgbaColor - RGBA color to stylize the generated columns
|
||||
*
|
||||
*/
|
||||
function setGridEvents($container, rgbaColor) {
|
||||
// Handle resize
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
setGridHelperColumns($container, rgbaColor)
|
||||
);
|
||||
|
||||
// Toggle grid
|
||||
let ctrlDown = false;
|
||||
let isActive = false;
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key == 'Control') {
|
||||
ctrlDown = true;
|
||||
} else {
|
||||
if (ctrlDown && e.key == 'g') {
|
||||
if (isActive) {
|
||||
$container.style.visibility = 'hidden';
|
||||
} else {
|
||||
$container.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
isActive = !isActive;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key == 'Control') {
|
||||
ctrlDown = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { gridHelper };
|
||||
@@ -1,31 +1,43 @@
|
||||
/**
|
||||
* @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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
const escapeHtml = (str) =>
|
||||
str.replace(
|
||||
/[&<>'"]/g,
|
||||
(tag) =>
|
||||
({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"'": "'",
|
||||
'"': """,
|
||||
}[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(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
}
|
||||
|
||||
const unescapeHtml = (str) =>
|
||||
str
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace(""", '"');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -59,38 +71,93 @@ export function getNodeData(node) {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (data === 'true') {
|
||||
|
||||
const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
|
||||
const getData = (data) => {
|
||||
if (data === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data === 'false') {
|
||||
if (data === "false") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data === 'null') {
|
||||
if (data === "null") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only convert to a number if it doesn't change the string
|
||||
if (data === +data+'') {
|
||||
if (data === +data + "") {
|
||||
return +data;
|
||||
}
|
||||
|
||||
if (rbrace.test( data )) {
|
||||
return JSON.parse( data );
|
||||
if (rbrace.test(data)) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array containing all the parent nodes of the given node
|
||||
* @param {HTMLElement} $el - DOM Element
|
||||
* @return {array} parent nodes
|
||||
*/
|
||||
|
||||
const getParents = ($el) => {
|
||||
// Set up a parent array
|
||||
let parents = [];
|
||||
|
||||
// Push each parent element to the array
|
||||
for (; $el && $el !== document; $el = $el.parentNode) {
|
||||
parents.push($el);
|
||||
}
|
||||
|
||||
// Return our parent array
|
||||
return parents;
|
||||
};
|
||||
|
||||
// https://gomakethings.com/how-to-get-the-closest-parent-element-with-a-matching-selector-using-vanilla-javascript/
|
||||
const queryClosestParent = ($el, selector) => {
|
||||
// Element.matches() polyfill
|
||||
if (!Element.prototype.matches) {
|
||||
Element.prototype.matches =
|
||||
Element.prototype.matchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector ||
|
||||
function (s) {
|
||||
var matches = (
|
||||
this.document || this.ownerDocument
|
||||
).querySelectorAll(s),
|
||||
i = matches.length;
|
||||
while (--i >= 0 && matches.item(i) !== this) {}
|
||||
return i > -1;
|
||||
};
|
||||
}
|
||||
|
||||
// Get the closest matching element
|
||||
for (; $el && $el !== document; $el = $el.parentNode) {
|
||||
if ($el.matches(selector)) return $el;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export {
|
||||
escapeHtml,
|
||||
unescapeHtml,
|
||||
getNodeData,
|
||||
getData,
|
||||
getParents,
|
||||
queryClosestParent,
|
||||
};
|
||||
|
||||
202
assets/scripts/utils/image.js
Normal file
202
assets/scripts/utils/image.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import { CSS_CLASS } from '../config'
|
||||
import { queryClosestParent } from './html'
|
||||
|
||||
/**
|
||||
* Get an image meta data
|
||||
*
|
||||
* @param {HTMLImageElement} $img - The image element.
|
||||
* @return {object} The given image meta data
|
||||
*/
|
||||
|
||||
const getImageMetadata = $img => ({
|
||||
url: $img.src,
|
||||
width: $img.naturalWidth,
|
||||
height: $img.naturalHeight,
|
||||
ratio: $img.naturalWidth / $img.naturalHeight,
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Load the given image.
|
||||
*
|
||||
* @param {string} url - The URI to lazy load into $el.
|
||||
* @param {object} options - An object of options
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
const loadImage = (url, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const $img = new Image()
|
||||
|
||||
if (options.crossOrigin) {
|
||||
$img.crossOrigin = options.crossOrigin
|
||||
}
|
||||
|
||||
const loadCallback = () => {
|
||||
resolve({
|
||||
element: $img,
|
||||
...getImageMetadata($img),
|
||||
})
|
||||
}
|
||||
|
||||
if($img.decode) {
|
||||
$img.src = url
|
||||
$img.decode().then(loadCallback).catch(e => {
|
||||
reject(e)
|
||||
})
|
||||
} else {
|
||||
$img.onload = loadCallback
|
||||
$img.onerror = (e) => {
|
||||
reject(e)
|
||||
}
|
||||
$img.src = url
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lazy load the given image.
|
||||
*
|
||||
* @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.
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
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)
|
||||
|
||||
if (!loadedImage) {
|
||||
loadedImage = await loadImage(src)
|
||||
|
||||
if (!loadedImage.url) {
|
||||
return
|
||||
}
|
||||
|
||||
LAZY_LOADED_IMAGES.push(loadedImage)
|
||||
}
|
||||
|
||||
if($el.src === src) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($el.tagName === 'IMG') {
|
||||
$el.src = loadedImage.url
|
||||
} else {
|
||||
$el.style.backgroundImage = `url(${loadedImage.url})`
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
let lazyParent = $el.closest(`.${CSS_CLASS.IMAGE}`)
|
||||
|
||||
if(lazyParent) {
|
||||
lazyParent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
|
||||
lazyParent.style.backgroundImage = ''
|
||||
}
|
||||
|
||||
$el.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED)
|
||||
|
||||
callback?.()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazyload Callbacks
|
||||
*
|
||||
*/
|
||||
const lazyImageLoad = (e) => {
|
||||
const $img = e.currentTarget;
|
||||
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if ($parent) {
|
||||
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
}
|
||||
|
||||
$img.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
});
|
||||
};
|
||||
|
||||
const lazyImageError = (e) => {
|
||||
const $img = e.currentTarget;
|
||||
const $parent = queryClosestParent($img, `.${CSS_CLASS.IMAGE}`);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if ($parent) {
|
||||
$parent.classList.remove(CSS_CLASS.IMAGE_LAZY_LOADING);
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_ERROR);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* Trigger Lazyload Callbacks */
|
||||
const triggerLazyloadCallbacks = ($lazyImagesArgs) => {
|
||||
const $lazyImages = $lazyImagesArgs
|
||||
? $lazyImagesArgs
|
||||
: document.querySelectorAll('[loading="lazy"]');
|
||||
|
||||
if ("loading" in HTMLImageElement.prototype) {
|
||||
for (const $img of $lazyImages) {
|
||||
const $parent = queryClosestParent(
|
||||
$img,
|
||||
`.${CSS_CLASS.IMAGE}`
|
||||
);
|
||||
|
||||
|
||||
if (!$img.complete) {
|
||||
if($parent) {
|
||||
$parent.classList.add(
|
||||
CSS_CLASS.IMAGE_LAZY_LOADING
|
||||
);
|
||||
}
|
||||
|
||||
$img.addEventListener("load", lazyImageLoad, { once: true });
|
||||
$img.addEventListener("error", lazyImageError, { once: true });
|
||||
} else {
|
||||
if (!$img.complete) {
|
||||
$parent.classList.add(
|
||||
CSS_CLASS.IMAGE_LAZY_LOADED
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if 'loading' supported
|
||||
for (const $img of $lazyImages) {
|
||||
const $parent = queryClosestParent(
|
||||
$img,
|
||||
`.${CSS_CLASS.IMAGE}`
|
||||
);
|
||||
|
||||
if($parent) {
|
||||
$parent.classList.add(CSS_CLASS.IMAGE_LAZY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Reset Lazyload Callbacks */
|
||||
const resetLazyloadCallbacks = () => {
|
||||
if ("loading" in HTMLImageElement.prototype) {
|
||||
const $lazyImages = document.querySelectorAll('[loading="lazy"]');
|
||||
for (const $img of $lazyImages) {
|
||||
$img.removeEventListener("load", lazyImageLoad, { once: true });
|
||||
$img.removeEventListener("error", lazyImageError, { once: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
getImageMetadata,
|
||||
loadImage,
|
||||
lazyLoadImage,
|
||||
triggerLazyloadCallbacks,
|
||||
resetLazyloadCallbacks
|
||||
}
|
||||
@@ -1,37 +1,25 @@
|
||||
const toString = Object.prototype.toString;
|
||||
const arrayLikePattern = /^\[object (?:Array|FileList)\]$/;
|
||||
/**
|
||||
* Determines if the argument is object-like.
|
||||
*
|
||||
* A value is object-like if it's not `null` and has a `typeof` result of "object".
|
||||
*
|
||||
* @param {*} x - The value to be checked.
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
|
||||
export function isArray ( thing ) {
|
||||
return toString.call( thing ) === '[object Array]';
|
||||
}
|
||||
|
||||
export function isArrayLike ( obj ) {
|
||||
return arrayLikePattern.test( toString.call( obj ) );
|
||||
}
|
||||
|
||||
export function isEqual ( a, b ) {
|
||||
if ( a === null && b === null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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]';
|
||||
const isObject = x => (x && typeof x === 'object')
|
||||
|
||||
/**
|
||||
* Determines if the argument is a function.
|
||||
*
|
||||
* @param {*} x - The value to be checked.
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
const isFunction = x => typeof x === 'function'
|
||||
|
||||
|
||||
export {
|
||||
isObject,
|
||||
isFunction
|
||||
}
|
||||
|
||||
54
assets/scripts/utils/maths.js
Normal file
54
assets/scripts/utils/maths.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { isNumeric } from './is'
|
||||
|
||||
let isAnimating = false;
|
||||
|
||||
const defaults = {
|
||||
easing: 'swing',
|
||||
headerOffset: 60,
|
||||
speed: 300
|
||||
};
|
||||
|
||||
/**
|
||||
* scrollTo is a function that scrolls a container to an element's position within that controller
|
||||
* Uses jQuery's $.Deferred to allow using a callback on animation completion
|
||||
* @param {object} $element A jQuery node
|
||||
* @param {object} options
|
||||
*/
|
||||
export function scrollTo($element, options) {
|
||||
const deferred = $.Deferred();
|
||||
|
||||
// Drop everything if this ain't a jQuery object
|
||||
if ($element instanceof jQuery && $element.length > 0) {
|
||||
|
||||
// Merging options
|
||||
options = $.extend({}, defaults, (typeof options !== 'undefined' ? options : {}));
|
||||
|
||||
// Prevents accumulation of animations
|
||||
if (isAnimating === false) {
|
||||
isAnimating = true;
|
||||
|
||||
// Default container that we'll be scrolling
|
||||
let $container = $('html, body');
|
||||
let elementOffset = 0;
|
||||
|
||||
// Testing container in options for jQuery-ness
|
||||
// If we're not using a custom container, we take the top document offset
|
||||
// If we are, we use the elements position relative to the container
|
||||
if (typeof options.$container !== 'undefined' && options.$container instanceof jQuery && options.$container.length > 0) {
|
||||
$container = options.$container;
|
||||
|
||||
if (typeof options.scrollTop !== 'undefined' && isNumeric(options.scrollTop) && options.scrollTop !== 0) {
|
||||
scrollTop = options.scrollTop;
|
||||
} else {
|
||||
scrollTop = $element.position().top - options.headerOffset;
|
||||
}
|
||||
} else {
|
||||
if (typeof options.scrollTop !== 'undefined' && isNumeric(options.scrollTop) && options.scrollTop !== 0) {
|
||||
scrollTop = options.scrollTop;
|
||||
} else {
|
||||
scrollTop = $element.offset().top - options.headerOffset;
|
||||
}
|
||||
}
|
||||
|
||||
$container.animate({
|
||||
scrollTop: scrollTop
|
||||
}, options.speed, options.easing, function() {
|
||||
isAnimating = false;
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
78
assets/scripts/utils/tickers.js
Normal file
78
assets/scripts/utils/tickers.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Creates a debounced function.
|
||||
*
|
||||
* A debounced function delays invoking `callback` until after
|
||||
* `delay` milliseconds have elapsed since the last time the
|
||||
* debounced function was invoked.
|
||||
*
|
||||
* Useful for behaviour that should only happen _before_ or
|
||||
* _after_ an event has stopped occurring.
|
||||
*
|
||||
* @template {function} T
|
||||
*
|
||||
* @param {T} callback - The function to debounce.
|
||||
* @param {number} delay - The number of milliseconds to wait.
|
||||
* @param {boolean} [immediate] -
|
||||
* If `true`, `callback` is invoked before `delay`.
|
||||
* If `false`, `callback` is invoked after `delay`.
|
||||
* @return {function<T>} The new debounced function.
|
||||
*/
|
||||
|
||||
const debounce = (callback, delay, immediate = false) => {
|
||||
let timeout = null
|
||||
|
||||
return (...args) => {
|
||||
clearTimeout(timeout)
|
||||
|
||||
const later = () => {
|
||||
timeout = null
|
||||
if (!immediate) {
|
||||
callback(...args)
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate && !timeout) {
|
||||
callback(...args)
|
||||
}
|
||||
|
||||
timeout = setTimeout(later, delay)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a throttled function.
|
||||
*
|
||||
* A throttled function invokes `callback` at most once per every
|
||||
* `delay` milliseconds.
|
||||
*
|
||||
* Useful for rate-limiting an event that occurs in quick succession.
|
||||
*
|
||||
* @template {function} T
|
||||
*
|
||||
* @param {T} callback - The function to throttle.
|
||||
* @param {number} delay - The number of milliseconds to wait.
|
||||
* @return {function<T>} The new throttled function.
|
||||
*/
|
||||
|
||||
const throttle = (callback, delay) => {
|
||||
let timeout = false
|
||||
|
||||
return (...args) => {
|
||||
if (!timeout) {
|
||||
timeout = true
|
||||
|
||||
callback(...args)
|
||||
|
||||
setTimeout(() => {
|
||||
timeout = false
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
debounce,
|
||||
throttle
|
||||
}
|
||||
35
assets/scripts/utils/transform.js
Normal file
35
assets/scripts/utils/transform.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 {
|
||||
transform,
|
||||
getTranslate
|
||||
}
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Base / Fonts
|
||||
// ==========================================================================
|
||||
|
||||
// @include font-face(
|
||||
// $font-foobar,
|
||||
// "fonts/Foobar/Regular"
|
||||
// ) {
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// }
|
||||
|
||||
// @include font-face(
|
||||
// $font-foobar,
|
||||
// "fonts/Foobar/Medium"
|
||||
// ) {
|
||||
// font-style: normal;
|
||||
// font-weight: 500;
|
||||
// }
|
||||
|
||||
// @include font-face(
|
||||
// $font-foobar,
|
||||
// "fonts/Foobar/Semibold"
|
||||
// ) {
|
||||
// font-style: normal;
|
||||
// font-weight: 600;
|
||||
// }
|
||||
|
||||
// @include font-face(
|
||||
// $font-bazqux,
|
||||
// "fonts/Bazqux/Regular",
|
||||
// ("eot", "woff2", "woff", "ttf")
|
||||
// ) {
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// }
|
||||
@@ -1,64 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Base / Headings
|
||||
// ==========================================================================
|
||||
|
||||
@mixin h {
|
||||
margin-top: 0;
|
||||
line-height: $line-height-h;
|
||||
}
|
||||
|
||||
//
|
||||
// Provide a generic class to apply common heading styles.
|
||||
//
|
||||
// @example
|
||||
// <p class="u-h"></p>
|
||||
//
|
||||
//
|
||||
.o-h {
|
||||
@include h;
|
||||
}
|
||||
|
||||
//
|
||||
// Styles for headings 1 through 6 with classes to provide
|
||||
// a double stranded heading hierarchy, e.g. we semantically
|
||||
// need an H2, but we want it to be sized like an H1:
|
||||
//
|
||||
// @example
|
||||
// <h2 class="o-h1"></h2>
|
||||
//
|
||||
//
|
||||
h1, .o-h1 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h1);
|
||||
}
|
||||
|
||||
h2, .o-h2 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h2);
|
||||
}
|
||||
|
||||
h3, .o-h3 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h3);
|
||||
}
|
||||
|
||||
h4, .o-h4 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h4);
|
||||
}
|
||||
|
||||
h5, .o-h5 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h5);
|
||||
}
|
||||
|
||||
h6, .o-h6 {
|
||||
@extend .o-h;
|
||||
|
||||
font-size: rem($font-size-h6);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Base / Page
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Simple page-level setup.
|
||||
//
|
||||
// 1. Set the default `font-size` and `line-height` for the entire project,
|
||||
// sourced from our default variables.
|
||||
// 2. Force scrollbars to always be visible to prevent awkward ‘jumps’ when
|
||||
// navigating between pages that do/do not have enough content to produce
|
||||
// scrollbars naturally.
|
||||
// 3. Ensure the page always fills at least the entire height of the viewport.
|
||||
//
|
||||
html {
|
||||
overflow-y: scroll; /* [2] */
|
||||
min-height: 100%; /* [3] */
|
||||
color: $color;
|
||||
font-family: $font-family;
|
||||
line-height: $line-height; /* [1] */
|
||||
|
||||
@media (max-width: $to-small) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-small) and (max-width: $to-medium) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-medium) and (max-width: $to-large) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-large) and (max-width: $to-huge) {
|
||||
font-size: $font-size; /* [1] */
|
||||
}
|
||||
|
||||
@media (min-width: $from-huge) and (max-width: $to-gigantic) {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-gigantic) and (max-width: $to-colossal) {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-colossal) {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: $selection-background-color;
|
||||
color: $selection-text-color;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
@include u-hocus {
|
||||
color: $link-hover-color;
|
||||
}
|
||||
|
||||
color: $link-color;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
// ==========================================================================
|
||||
// Objects / Buttons
|
||||
// Components / Buttons
|
||||
// ==========================================================================
|
||||
.o-button {
|
||||
@include u-hocus {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
padding: rem(10px);
|
||||
.c-button {
|
||||
padding: rem(15px) rem(20px);
|
||||
background-color: lightgray;
|
||||
|
||||
@include u-hocus {
|
||||
background-color: darkgray;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,40 @@
|
||||
// ==========================================================================
|
||||
// Objects / Buttons
|
||||
// Components / Form
|
||||
// ==========================================================================
|
||||
|
||||
.c-form {
|
||||
|
||||
}
|
||||
|
||||
.c-form_item {
|
||||
position: relative;
|
||||
margin-bottom: rem(30px);
|
||||
}
|
||||
|
||||
// Label
|
||||
// =============================================================================
|
||||
.o-label {
|
||||
// ==========================================================================
|
||||
|
||||
.c-form_label {
|
||||
display: block;
|
||||
margin-bottom: rem(15px);
|
||||
margin-bottom: rem(10px);
|
||||
}
|
||||
|
||||
// Input
|
||||
// =============================================================================
|
||||
// ==========================================================================
|
||||
|
||||
$input-icon-color: 424242; // No #
|
||||
|
||||
.o-input {
|
||||
.c-form_input {
|
||||
padding: rem(10px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: lightgray;
|
||||
background-color: white;
|
||||
border: 1px solid lightgray;
|
||||
background-color: color(lightest);
|
||||
|
||||
&:hover {
|
||||
border-color: darkgray;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: gray;
|
||||
border-color: dimgray;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@@ -30,47 +43,27 @@ $input-icon-color: 424242; // No #
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
// =============================================================================
|
||||
// ==========================================================================
|
||||
|
||||
$checkbox: rem(18px);
|
||||
$checkbox-icon-color: $input-icon-color;
|
||||
|
||||
.o-checkbox {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
|
||||
&:focus {
|
||||
+ .o-checkbox-label {
|
||||
&::before {
|
||||
border-color: gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
+ .o-checkbox-label {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o-checkbox-label {
|
||||
@extend .o-label;
|
||||
.c-form_checkboxLabel {
|
||||
@extend .c-form_label;
|
||||
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
margin-right: rem(10px);
|
||||
margin-bottom: 0;
|
||||
padding-left: ($checkbox + rem(10px));
|
||||
cursor: pointer;
|
||||
|
||||
&::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;
|
||||
@@ -78,7 +71,8 @@ $checkbox-icon-color: $input-icon-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: $white;
|
||||
background-color: color(lightest);
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@@ -86,22 +80,43 @@ $checkbox-icon-color: $input-icon-color;
|
||||
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-position: center;
|
||||
background-size: rem(13px);
|
||||
background-size: rem(12px);
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
border-color: darkgray;
|
||||
}
|
||||
}
|
||||
|
||||
.c-form_checkbox:focus + & {
|
||||
&::before {
|
||||
border-color: dimgray;
|
||||
}
|
||||
}
|
||||
|
||||
.c-form_checkbox:checked + & {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-form_checkbox {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Radio
|
||||
// =============================================================================
|
||||
// ==========================================================================
|
||||
|
||||
$radio-icon-color: $input-icon-color;
|
||||
|
||||
.o-radio {
|
||||
@extend .o-checkbox;
|
||||
}
|
||||
|
||||
.o-radio-label {
|
||||
@extend .o-checkbox-label;
|
||||
.c-form_radioLabel {
|
||||
@extend .c-form_checkboxLabel;
|
||||
|
||||
&::before, &::after {
|
||||
border-radius: 50%;
|
||||
@@ -109,25 +124,23 @@ $radio-icon-color: $input-icon-color;
|
||||
|
||||
&::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-size: rem(8px);
|
||||
background-size: rem(6px);
|
||||
}
|
||||
}
|
||||
|
||||
.c-form_radio {
|
||||
@extend .c-form_checkbox;
|
||||
}
|
||||
|
||||
// Select
|
||||
// =============================================================================
|
||||
|
||||
$select-icon: rem(40px);
|
||||
$select-icon-color: $input-icon-color;
|
||||
|
||||
.o-select {
|
||||
@extend .o-input;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: $select-icon;
|
||||
}
|
||||
|
||||
.o-select-wrap {
|
||||
.c-form_select {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
@@ -138,17 +151,27 @@ $select-icon-color: $input-icon-color;
|
||||
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-position: center;
|
||||
background-size: rem(10px);
|
||||
background-size: rem(8px);
|
||||
background-repeat: no-repeat;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-form_select_input {
|
||||
@extend .c-form_input;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: $select-icon;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Textarea
|
||||
// =============================================================================
|
||||
.o-textarea {
|
||||
@extend .o-input;
|
||||
|
||||
min-height: rem(100px);
|
||||
.c-form_textarea {
|
||||
@extend .c-form_input;
|
||||
|
||||
min-height: rem(200px);
|
||||
}
|
||||
31
assets/styles/components/_heading.scss
Normal file
31
assets/styles/components/_heading.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
// ==========================================================================
|
||||
// Components / Headings
|
||||
// ==========================================================================
|
||||
|
||||
.c-heading {
|
||||
margin-bottom: rem(30px);
|
||||
|
||||
&.-h1 {
|
||||
font-size: var(--font-size-h1);
|
||||
}
|
||||
|
||||
&.-h2 {
|
||||
font-size: var(--font-size-h2);
|
||||
}
|
||||
|
||||
&.-h3 {
|
||||
font-size: var(--font-size-h3);
|
||||
}
|
||||
|
||||
&.-h4 {
|
||||
font-size: var(--font-size-h4);
|
||||
}
|
||||
|
||||
&.-h5 {
|
||||
font-size: var(--font-size-h5);
|
||||
}
|
||||
|
||||
&.-h6 {
|
||||
font-size: var(--font-size-h6);
|
||||
}
|
||||
}
|
||||
20
assets/styles/components/_image.scss
Normal file
20
assets/styles/components/_image.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
// ==========================================================================
|
||||
// Components / Image
|
||||
// ==========================================================================
|
||||
|
||||
.c-image {
|
||||
|
||||
}
|
||||
|
||||
.c-image_img {
|
||||
|
||||
// Lazy loading styles
|
||||
.c-image.-lazy-load & {
|
||||
transition: opacity $speed $easing;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.c-image.-lazy-loaded & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
5
assets/styles/critical.scss
Normal file
5
assets/styles/critical.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
// ==========================================================================
|
||||
// Critical CSS
|
||||
// ==========================================================================
|
||||
|
||||
$assets-path: "assets/";
|
||||
71
assets/styles/elements/_document.scss
Normal file
71
assets/styles/elements/_document.scss
Normal file
@@ -0,0 +1,71 @@
|
||||
// ==========================================================================
|
||||
// Elements / Document
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Simple page-level setup.
|
||||
//
|
||||
// 1. Include web fonts
|
||||
// 2. Ensure the page always fills at least the entire height of the viewport.
|
||||
// 3. Set the default `font-size` and `line-height` for the entire project,
|
||||
// sourced from our default variables.
|
||||
|
||||
@include font-faces($font-faces, $font-dir); // [1]
|
||||
|
||||
html {
|
||||
min-height: 100%; // [2]
|
||||
line-height: $line-height; // [3]
|
||||
font-family: ff("sans");
|
||||
color: $font-color;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: $to-small) {
|
||||
font-size: $font-size - 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-small) and (max-width: $to-medium) {
|
||||
font-size: $font-size - 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-medium) and (max-width: $to-large) {
|
||||
font-size: $font-size - 1px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-large) and (max-width: $to-huge) {
|
||||
font-size: $font-size; // [1]
|
||||
}
|
||||
|
||||
@media (min-width: $from-huge) and (max-width: $to-gigantic) {
|
||||
font-size: $font-size + 1px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-gigantic) and (max-width: $to-colossal) {
|
||||
font-size: $font-size + 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-colossal) {
|
||||
font-size: $font-size + 4px;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
cursor: wait;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: $color-selection-background;
|
||||
color: $color-selection-text;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-link;
|
||||
|
||||
@include u-hocus {
|
||||
color: $color-link-hover;
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,33 @@
|
||||
// Generic / Buttons
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// 1. Allow us to style box model properties.
|
||||
// 2. Fixes odd inner spacing in IE7.
|
||||
// 3. Reset/normalize some styles.
|
||||
// 4. Line different sized buttons up a little nicer.
|
||||
// 5. Make buttons inherit font styles (often necessary when styling `input`s as buttons).
|
||||
// 6. Force all button-styled elements to appear clickable.
|
||||
//
|
||||
|
||||
button,
|
||||
.o-button {
|
||||
.c-button {
|
||||
@include u-hocus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
display: inline-block; /* [1] */
|
||||
overflow: visible; /* [2] */
|
||||
margin: 0; /* [3] */
|
||||
display: inline-block; // [1]
|
||||
overflow: visible; // [2]
|
||||
margin: 0; // [3]
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
background: none transparent;
|
||||
color: inherit;
|
||||
vertical-align: middle; /* [4] */
|
||||
text-align: center; /* [3] */
|
||||
vertical-align: middle; // [4]
|
||||
text-align: center; // [3]
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
font: inherit; /* [5] */
|
||||
font: inherit; // [5]
|
||||
line-height: normal;
|
||||
cursor: pointer; /* [6] */
|
||||
cursor: pointer; // [6]
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ select {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// Remove Firefox :focus dotted outline, breaks color inherit
|
||||
// &:-moz-focusring {
|
||||
// color: transparent;
|
||||
// text-shadow: 0 0 0 #000000; // Text :focus color
|
||||
// }
|
||||
// // Remove Firefox :focus dotted outline, breaks color inherit
|
||||
// // &:-moz-focusring {
|
||||
// // color: transparent;
|
||||
// // text-shadow: 0 0 0 #000000; // Text :focus color
|
||||
// // }
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
||||
@@ -6,11 +6,10 @@ html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
//
|
||||
// Add the correct display in IE 10-.
|
||||
// 1. Add the correct display in IE.
|
||||
//
|
||||
template, /* [1] */
|
||||
|
||||
template, // [1]
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -34,7 +33,7 @@ i {
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: $bold;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -45,11 +44,27 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Single taps should be dispatched immediately on clickable elements
|
||||
*/
|
||||
ul,
|
||||
ol {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
p,
|
||||
figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 1. Single taps should be dispatched immediately on clickable elements
|
||||
|
||||
a, area, button, input, label, select, textarea, [tabindex] {
|
||||
-ms-touch-action: manipulation; /* [1] */
|
||||
-ms-touch-action: manipulation; // [1]
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,57 +2,51 @@
|
||||
// Generic / Media
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// 1. Setting `vertical-align` removes the whitespace that appears under `img`
|
||||
// elements when they are dropped into a page as-is. Safer alternative to
|
||||
// using `display: block;`.
|
||||
//
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
iframe,
|
||||
img,
|
||||
svg,
|
||||
video {
|
||||
vertical-align: middle; /* [1] */
|
||||
vertical-align: middle; // [1]
|
||||
}
|
||||
|
||||
//
|
||||
// Add the correct display in iOS 4-7.
|
||||
//
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
//
|
||||
// 2. Fluid media for responsive purposes.
|
||||
//
|
||||
|
||||
img,
|
||||
svg {
|
||||
max-width: 100%; /* [2] */
|
||||
max-width: 100%; // [2]
|
||||
height: auto;
|
||||
|
||||
//
|
||||
// 4. If a `width` and/or `height` attribute have been explicitly defined, let’s
|
||||
// not make the image fluid.
|
||||
//
|
||||
&[width], /* [4] */
|
||||
// 4. If a `width` and/or `height` attribute have been explicitly defined,
|
||||
// let’s not make the image fluid.
|
||||
|
||||
&[width], // [4]
|
||||
&[height] {
|
||||
/* [4] */
|
||||
// [4]
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 4. Offset `alt` text from surrounding copy.
|
||||
//
|
||||
|
||||
img {
|
||||
font-style: italic; /* [4] */
|
||||
font-style: italic; // [4]
|
||||
}
|
||||
|
||||
//
|
||||
// 5. SVG elements should fallback to their surrounding text color.
|
||||
//
|
||||
|
||||
svg {
|
||||
fill: currentColor; /* [5] */
|
||||
fill: currentColor; // [5]
|
||||
}
|
||||
|
||||
@@ -1,67 +1,72 @@
|
||||
// ==========================================================================
|
||||
// Main
|
||||
// ==========================================================================
|
||||
|
||||
// Settings
|
||||
// ==========================================================================
|
||||
@import "settings/config.colors";
|
||||
@import "settings/config";
|
||||
@use "sass:math";
|
||||
|
||||
// ==========================================================================
|
||||
// Tools
|
||||
// ==========================================================================
|
||||
|
||||
@import "tools/maths";
|
||||
@import "tools/functions";
|
||||
@import "tools/mixins";
|
||||
@import "tools/fonts";
|
||||
@import "tools/layout";
|
||||
// @import "tools/ratio";
|
||||
// @import "tools/layout";
|
||||
// @import "tools/widths";
|
||||
// @import "tools/familly";
|
||||
// @import "tools/family";
|
||||
|
||||
// Settings
|
||||
// ==========================================================================
|
||||
|
||||
@import "settings/config.eases";
|
||||
@import "settings/config.colors";
|
||||
@import "settings/config";
|
||||
@import "settings/config.variables";
|
||||
|
||||
// Generic
|
||||
// ==========================================================================
|
||||
|
||||
@import "node_modules/normalize.css/normalize";
|
||||
@import "generic/generic";
|
||||
@import "generic/media";
|
||||
@import "generic/form";
|
||||
@import "generic/button";
|
||||
|
||||
// Base
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
@import "base/fonts";
|
||||
@import "base/page";
|
||||
@import "base/headings";
|
||||
@import "node_modules/locomotive-scroll/dist/locomotive-scroll";
|
||||
|
||||
// Elements
|
||||
// ==========================================================================
|
||||
|
||||
@import "elements/document";
|
||||
|
||||
// Objects
|
||||
// ==========================================================================
|
||||
@import "objects/container";
|
||||
// @import "objects/crop";
|
||||
// @import "objects/ratio";
|
||||
// @import "objects/table";
|
||||
@import "objects/layout";
|
||||
@import "objects/form";
|
||||
@import "objects/button";
|
||||
@import "objects/pjax";
|
||||
@import "objects/scroll";
|
||||
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
// @import "vendors/vendor";
|
||||
@import "objects/container";
|
||||
@import "objects/ratio";
|
||||
@import "objects/icons";
|
||||
@import "objects/grid";
|
||||
// @import "objects/layout";
|
||||
// @import "objects/table";
|
||||
|
||||
// Components
|
||||
// ==========================================================================
|
||||
// @import "components/component";
|
||||
|
||||
// Templates
|
||||
// ==========================================================================
|
||||
// @import "templates/template";
|
||||
@import "components/heading";
|
||||
@import "components/button";
|
||||
@import "components/form";
|
||||
@import "components/image";
|
||||
|
||||
// Utilities
|
||||
// ==========================================================================
|
||||
|
||||
@import "utilities/ratio";
|
||||
@import "utilities/grid-column";
|
||||
// @import "utilities/widths";
|
||||
// @import "utilities/align";
|
||||
// @import "utilities/helpers";
|
||||
// @import "utilities/states";
|
||||
// @import "utilities/headings";
|
||||
// @import "utilities/spacing";
|
||||
// @import "utilities/widths";
|
||||
// @import "utilities/print";
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Objects / Container
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Page-level constraining and wrapping elements.
|
||||
//
|
||||
// > In programming languages the word *container* is generally used for structures
|
||||
@@ -10,18 +9,10 @@
|
||||
// > A *wrapper* instead is something that wraps around a single object to provide
|
||||
// more functionalities and interfaces to it.
|
||||
// @link http://stackoverflow.com/a/13202141/140357
|
||||
//
|
||||
|
||||
/* stylelint-disable */
|
||||
@if (type-of($container-width) != number) {
|
||||
@error "`#{$container-width}` needs to be a number."
|
||||
}
|
||||
/* stylelint-enable */
|
||||
|
||||
.o-container {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: $padding;
|
||||
padding-left: $padding;
|
||||
max-width: $container-width;
|
||||
padding-left: var(--grid-margin);
|
||||
padding-right: var(--grid-margin);
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Objects / Crop
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.crop.scss
|
||||
//
|
||||
|
||||
// A list of cropping ratios that get generated as modifier classes.
|
||||
$crop-ratios: (
|
||||
(2:1),
|
||||
(4:3),
|
||||
(16:9),
|
||||
) !default;
|
||||
|
||||
/**
|
||||
* Provide a cropping container in order to display media (usually images)
|
||||
* cropped to certain ratios.
|
||||
*
|
||||
* 1. Set up a positioning context in which the image can sit.
|
||||
* 2. This is the crucial part: where the cropping happens.
|
||||
*/
|
||||
.o-crop {
|
||||
position: relative; /* [1] */
|
||||
display: block;
|
||||
overflow: hidden; /* [2] */
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this class to the content (usually `img`) that needs cropping.
|
||||
*
|
||||
* 1. Image’s default positioning is top-left in the cropping box.
|
||||
* 2. Make sure the media doesn’t stop itself too soon.
|
||||
*/
|
||||
.o-crop_content {
|
||||
position: absolute;
|
||||
top: 0; /* [1] */
|
||||
left: 0; /* [1] */
|
||||
max-width: none; /* [2] */
|
||||
|
||||
/**
|
||||
* We can position the media in different locations within the cropping area.
|
||||
*/
|
||||
&.-right {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
&.-bottom {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&.-center {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable */
|
||||
|
||||
//
|
||||
// Generate a series of crop classes to be used like so:
|
||||
//
|
||||
// @example
|
||||
// <div class="o-crop -16:9">
|
||||
//
|
||||
//
|
||||
.o-crop {
|
||||
@each $crop in $crop-ratios {
|
||||
@each $antecedent, $consequent in $crop {
|
||||
@if (type-of($antecedent) != number) {
|
||||
@error "`#{$antecedent}` needs to be a number."
|
||||
}
|
||||
|
||||
@if (type-of($consequent) != number) {
|
||||
@error "`#{$consequent}` needs to be a number."
|
||||
}
|
||||
|
||||
&.-#{$antecedent}\:#{$consequent} {
|
||||
padding-bottom: ($consequent/$antecedent) * 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-enable */
|
||||
178
assets/styles/objects/_grid.scss
Normal file
178
assets/styles/objects/_grid.scss
Normal file
@@ -0,0 +1,178 @@
|
||||
// ==========================================================================
|
||||
// Grid helper
|
||||
// ==========================================================================
|
||||
// Help: https://css-tricks.com/snippets/css/complete-guide-grid/
|
||||
//
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* ```html
|
||||
* <div class="o-grid -col-4 -col-12@from-medium -gutters">
|
||||
* <div class="o-grid_item u-gc-1/2 u-gc-3/9@from-medium">
|
||||
* <p>Hello</p>
|
||||
* </div>
|
||||
* <div class="o-grid_item u-gc-3/4 u-gc-9/13@from-medium">
|
||||
* <p>Hello</p>
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
|
||||
.o-grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
&:is(ul, ol) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Cols
|
||||
// ==========================================================================
|
||||
|
||||
// Responsive grid columns based on `--grid-columns`
|
||||
&.-cols {
|
||||
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
||||
}
|
||||
|
||||
&.-col-#{$base-column-nb} {
|
||||
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||
}
|
||||
|
||||
&.-col-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
&.-col-#{$base-column-nb}\@from-medium {
|
||||
@media (min-width: $from-medium) {
|
||||
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Gutters
|
||||
// ==========================================================================
|
||||
|
||||
// Gutters rows and columns
|
||||
&.-gutters {
|
||||
gap: var(--grid-gutter);
|
||||
column-gap: var(--grid-gutter);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Modifiers
|
||||
// ==========================================================================
|
||||
&.-full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Aligns
|
||||
// ==========================================================================
|
||||
|
||||
// ==========================================================================
|
||||
// Items inside cells
|
||||
//
|
||||
&.-top-items {
|
||||
align-items: start;
|
||||
}
|
||||
&.-right-items {
|
||||
justify-items: end;
|
||||
}
|
||||
&.-bottom-items {
|
||||
align-items: end;
|
||||
}
|
||||
&.-left-items {
|
||||
justify-items: start;
|
||||
}
|
||||
&.-center-items {
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
}
|
||||
&.-center-items-x {
|
||||
justify-items: center;
|
||||
}
|
||||
&.-center-items-y {
|
||||
align-items: center;
|
||||
}
|
||||
&.-stretch-items {
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Cells
|
||||
//
|
||||
&.-top-cells {
|
||||
align-content: start;
|
||||
}
|
||||
&.-right-cells {
|
||||
justify-content: end;
|
||||
}
|
||||
&.-bottom-cells {
|
||||
align-content: end;
|
||||
}
|
||||
&.-left-cells {
|
||||
justify-content: start;
|
||||
}
|
||||
&.-center-cells {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
&.-center-cells-x {
|
||||
justify-content: center;
|
||||
}
|
||||
&.-center-cells-y {
|
||||
align-content: center;
|
||||
}
|
||||
&.-stretch-cells {
|
||||
align-content: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
&.-space-around-cells {
|
||||
align-content: space-around;
|
||||
justify-content: space-around;
|
||||
}
|
||||
&.-space-around-cells-x {
|
||||
justify-content: space-around;
|
||||
}
|
||||
&.-space-around-cells-y {
|
||||
align-content: space-around;
|
||||
}
|
||||
&.-space-between-cells {
|
||||
justify-content: space-between;
|
||||
align-content: space-between;
|
||||
}
|
||||
&.-space-between-cells-x {
|
||||
justify-content: space-between;
|
||||
}
|
||||
&.-space-between-cells-y {
|
||||
align-content: space-between;
|
||||
}
|
||||
&.-space-evenly-cells {
|
||||
justify-content: space-evenly;
|
||||
align-content: space-evenly;
|
||||
}
|
||||
&.-space-evenly-cells-x {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
&.-space-evenly-cells-y {
|
||||
align-content: space-evenly;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Grid item
|
||||
// ==========================================================================
|
||||
// By default, a grid item takes full width of its parent.
|
||||
//
|
||||
.o-grid_item {
|
||||
grid-column-start: var(--gc-start, 1);
|
||||
grid-column-end: var(--gc-end, -1);
|
||||
|
||||
&.-align-end {
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
58
assets/styles/objects/_icons.scss
Normal file
58
assets/styles/objects/_icons.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
// ==========================================================================
|
||||
// Objects / SVG Icons
|
||||
// ==========================================================================
|
||||
|
||||
|
||||
// Markup
|
||||
//
|
||||
// 1. If icon is accessible and has a title
|
||||
// 2. If icon is decorative
|
||||
//
|
||||
// <i class="o-icon ${modifier}">
|
||||
// <svg
|
||||
// class="svg-${icon-name}"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// role="img" [1]
|
||||
// aria-hidden="true" [2]
|
||||
// focusable="false" [2]
|
||||
// aria-labelledby="${id}" [1]
|
||||
// >
|
||||
// <title id="${id}"> [1]
|
||||
// Locomotive
|
||||
// </title>
|
||||
// <use xlink:href="assets/images/sprite.svg#${icon-name}" xmlns:xlink="http://www.w3.org/1999/xlink"/>
|
||||
// </svg>
|
||||
// </i>
|
||||
|
||||
// Global styles for icones
|
||||
// ==========================================================================
|
||||
|
||||
.o-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
svg {
|
||||
--icon-height: calc(var(--icon-width) * math.div(1, (var(--icon-ratio))));
|
||||
|
||||
display: block;
|
||||
width: var(--icon-width);
|
||||
height: var(--icon-height);
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SVG sizes
|
||||
// ==========================================================================
|
||||
|
||||
// // Logo
|
||||
// .svg-logo {
|
||||
// --icon-width: #{rem(100px)};
|
||||
// --icon-ratio: 20/30; // width/height based on svg viewBox
|
||||
|
||||
// // Sizes
|
||||
// .o-icon.-big & {
|
||||
// --icon-width: #{rem(200px)};
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -2,41 +2,41 @@
|
||||
// Objects / Layout
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Grid-like layout system.
|
||||
//
|
||||
// The layout object provides us with a column-style layout system. This file
|
||||
// contains the basic structural elements, but classes should be complemented
|
||||
// with width utilities, for example:
|
||||
//
|
||||
// @example
|
||||
// <div class="o-layout">
|
||||
// <div class="o-layout_item u-1/1 u-1/3@medium">
|
||||
// </div>
|
||||
// <div class="o-layout_item u-1/2 u-1/3@medium">
|
||||
// </div>
|
||||
// <div class="o-layout_item u-1/2 u-1/3@medium">
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
// We can also manipulate entire layout systems by adding a series of modifiers
|
||||
// to the `.o-layout` block. For example:
|
||||
//
|
||||
// @example
|
||||
// <div class="o-layout -reverse">
|
||||
//
|
||||
// This will reverse the displayed order of the system so that it runs in the
|
||||
// opposite order to our source, effectively flipping the system over.
|
||||
//
|
||||
// @example
|
||||
// <div class="o-layout -[right|center]">
|
||||
//
|
||||
// This will cause the system to fill up from either the centre or the right
|
||||
// hand side. Default behaviour is to fill up the layout system from the left.
|
||||
//
|
||||
// @requires tools/layout
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/0420ba8/objects/_objects.layout.scss
|
||||
//
|
||||
////
|
||||
/// Grid-like layout system.
|
||||
///
|
||||
/// The layout object provides us with a column-style layout system. This file
|
||||
/// contains the basic structural elements, but classes should be complemented
|
||||
/// with width utilities, for example:
|
||||
///
|
||||
/// @example
|
||||
/// <div class="o-layout">
|
||||
/// <div class="o-layout_item u-1/1 u-1/3@medium">
|
||||
/// </div>
|
||||
/// <div class="o-layout_item u-1/2 u-1/3@medium">
|
||||
/// </div>
|
||||
/// <div class="o-layout_item u-1/2 u-1/3@medium">
|
||||
/// </div>
|
||||
/// </div>
|
||||
///
|
||||
/// We can also manipulate entire layout systems by adding a series of modifiers
|
||||
/// to the `.o-layout` block. For example:
|
||||
///
|
||||
/// @example
|
||||
/// <div class="o-layout -reverse">
|
||||
///
|
||||
/// This will reverse the displayed order of the system so that it runs in the
|
||||
/// opposite order to our source, effectively flipping the system over.
|
||||
///
|
||||
/// @example
|
||||
/// <div class="o-layout -[right|center]">
|
||||
///
|
||||
/// This will cause the system to fill up from either the centre or the right
|
||||
/// hand side. Default behaviour is to fill up the layout system from the left.
|
||||
///
|
||||
/// @requires tools/layout
|
||||
/// @link https://github.com/inuitcss/inuitcss/blob/0420ba8/objects/_objects.layout.scss
|
||||
////
|
||||
|
||||
.o-layout {
|
||||
@include o-layout;
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
|
||||
&.-gutter-small {
|
||||
margin-left: rem(-$unit/2);
|
||||
margin-left: rem(-$unit-small);
|
||||
}
|
||||
|
||||
// Horizontal aligment modifiers
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
|
||||
.o-layout.-gutter-small > & {
|
||||
padding-left: rem($unit/2);
|
||||
padding-left: rem($unit-small);
|
||||
}
|
||||
|
||||
// Vertical alignment modifiers
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
.o-pjax_wrapper {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.o-pjax_container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -2,28 +2,13 @@
|
||||
// Objects / Ratio
|
||||
// ==========================================================================
|
||||
|
||||
// Create ratio-bound content blocks, to keep media (e.g. images, videos) in
|
||||
// their correct aspect ratios.
|
||||
//
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.ratio.scss
|
||||
// http://alistapart.com/article/creating-intrinsic-ratios-for-video
|
||||
//
|
||||
// 1. Default cropping is a 1:1 ratio (i.e. a perfect square).
|
||||
|
||||
// A list of aspect ratios that get generated as modifier classes.
|
||||
|
||||
$aspect-ratios: (
|
||||
(2:1),
|
||||
(4:3),
|
||||
(16:9),
|
||||
) !default;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create ratio-bound content blocks, to keep media (e.g. images, videos) in
|
||||
* their correct aspect ratios.
|
||||
*
|
||||
* http://alistapart.com/article/creating-intrinsic-ratios-for-video
|
||||
*
|
||||
* 1. Default cropping is a 1:1 ratio (i.e. a perfect square).
|
||||
*/
|
||||
.o-ratio {
|
||||
position: relative;
|
||||
display: block;
|
||||
@@ -31,13 +16,14 @@ $aspect-ratios: (
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
padding-bottom: 100%; /* [1] */
|
||||
padding-bottom: 100%; // [1]
|
||||
width: 100%;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.o-ratio_content,
|
||||
.o-ratio > img,
|
||||
.o-ratio > iframe,
|
||||
.o-ratio > embed,
|
||||
.o-ratio > object {
|
||||
@@ -46,34 +32,5 @@ $aspect-ratios: (
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
|
||||
/* stylelint-disable */
|
||||
|
||||
//
|
||||
// Generate a series of ratio classes to be used like so:
|
||||
//
|
||||
// @example
|
||||
// <div class="o-ratio -16:9">
|
||||
//
|
||||
//
|
||||
.o-ratio {
|
||||
@each $ratio in $aspect-ratios {
|
||||
@each $antecedent, $consequent in $ratio {
|
||||
@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}::before {
|
||||
padding-bottom: ($consequent/$antecedent) * 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-enable */
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
html.has-smooth-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.o-scroll{
|
||||
html.has-smooth-scroll & {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
transform: translate3d(0,0,0);
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Scrollbar
|
||||
// ==========================================================================
|
||||
[data-scrollbar],[scrollbar],scrollbar{display:block;position:relative}[data-scrollbar] .scroll-content,[scrollbar] .scroll-content,scrollbar .scroll-content{-webkit-transform:translateZ(0);transform:translateZ(0);will-change:transform}[data-scrollbar].sticky .scrollbar-track,[scrollbar].sticky .scrollbar-track,scrollbar.sticky .scrollbar-track{background:hsla(0,0%,87%,.75)}[data-scrollbar] .scrollbar-track,[scrollbar] .scrollbar-track,scrollbar .scrollbar-track{position:absolute;opacity:0;z-index:1;-webkit-transition:opacity .5s ease-out,background .5s ease-out;transition:opacity .5s ease-out,background .5s ease-out;background:none}[data-scrollbar] .scrollbar-track.show,[data-scrollbar] .scrollbar-track:hover,[scrollbar] .scrollbar-track.show,[scrollbar] .scrollbar-track:hover,scrollbar .scrollbar-track.show,scrollbar .scrollbar-track:hover{opacity:1}[data-scrollbar] .scrollbar-track:hover,[scrollbar] .scrollbar-track:hover,scrollbar .scrollbar-track:hover{background:hsla(0,0%,87%,.75)}[data-scrollbar] .scrollbar-track-x,[scrollbar] .scrollbar-track-x,scrollbar .scrollbar-track-x{bottom:0;left:0;width:100%;height:8px}[data-scrollbar] .scrollbar-track-y,[scrollbar] .scrollbar-track-y,scrollbar .scrollbar-track-y{top:0;right:0;width:8px;height:100%}[data-scrollbar] .scrollbar-thumb,[scrollbar] .scrollbar-thumb,scrollbar .scrollbar-thumb{position:absolute;top:0;left:0;width:8px;height:8px;background:rgba(0,0,0,.5);border-radius:4px}[data-scrollbar] .overscroll-glow,[scrollbar] .overscroll-glow,scrollbar .overscroll-glow{position:absolute;top:0;left:0;width:100%;height:100%}
|
||||
|
||||
.scrollbar-track {
|
||||
user-select: none;
|
||||
background-color: transparent !important;
|
||||
width: 14px !important;
|
||||
opacity: 0 !important;
|
||||
z-index: 99999 !important;
|
||||
|
||||
.scrolling & {
|
||||
opacity: 0.75 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1 !important;
|
||||
background-color: #fafafa !important;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-thumb {
|
||||
position: relative;
|
||||
width: 14px !important;
|
||||
background-color: transparent !important;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
left: 3px;
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
transition: background-color $speed $easing;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background-color: #7d7d7d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
// ==========================================================================
|
||||
// Objects / Tables
|
||||
// ==========================================================================
|
||||
|
||||
.o-table {
|
||||
width: 100%;
|
||||
|
||||
/**
|
||||
* Force all cells within a table to occupy the same width as each other.
|
||||
*
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout#Values
|
||||
*/
|
||||
// Force all cells within a table to occupy the same width as each other.
|
||||
//
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout#Values
|
||||
|
||||
&.-fixed {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
@@ -4,22 +4,28 @@
|
||||
|
||||
// Palette
|
||||
// =============================================================================
|
||||
$white: #FFFFFF;
|
||||
$black: #000000;
|
||||
|
||||
// Specific
|
||||
$colors: (
|
||||
primary: #3297FD,
|
||||
lightest: #FFFFFF,
|
||||
darkest: #000000,
|
||||
);
|
||||
|
||||
|
||||
// Specifics
|
||||
// =============================================================================
|
||||
|
||||
// Link
|
||||
$link-color: #1A0DAB;
|
||||
$link-focus-color: #1A0DAB;
|
||||
$link-hover-color: darken(#1A0DAB, 10%);
|
||||
// Selection
|
||||
$selection-text-color: #3297FD;
|
||||
$selection-background-color: #FFFFFF;
|
||||
$color-link: color(primary);
|
||||
$color-link-focus: color(primary);
|
||||
$color-link-hover: darken(color(primary), 10%);
|
||||
|
||||
// Social Colors
|
||||
// =============================================================================
|
||||
$facebook-color: #3B5998;
|
||||
$instagram-color: #E1306C;
|
||||
$youtube-color: #CD201F;
|
||||
$twitter-color: #1DA1F2;
|
||||
// Selection
|
||||
$color-selection-text: color(darkest);
|
||||
$color-selection-background: color(lightest);
|
||||
|
||||
// Socials
|
||||
$color-facebook: #3B5998;
|
||||
$color-instagram: #E1306C;
|
||||
$color-youtube: #CD201F;
|
||||
$color-twitter: #1DA1F2;
|
||||
|
||||
48
assets/styles/settings/_config.eases.scss
Normal file
48
assets/styles/settings/_config.eases.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
// ==========================================================================
|
||||
// Settings / Config / Eases
|
||||
// ==========================================================================
|
||||
|
||||
// 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);
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
// Context
|
||||
// =============================================================================
|
||||
|
||||
// The current stylesheet context. Available values: frontend, editor.
|
||||
$context: frontend !default;
|
||||
|
||||
@@ -12,46 +13,72 @@ $assets-path: "../" !default;
|
||||
|
||||
// Typefaces
|
||||
// =============================================================================
|
||||
$font-sans-serif: sans-serif;
|
||||
|
||||
// Font directory
|
||||
$font-dir: "../fonts/";
|
||||
|
||||
// Font fallbacks (retrieved from systemfontstack.com on 2022-05-31)
|
||||
$font-fallback-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
|
||||
$font-fallback-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
||||
$font-fallback-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
|
||||
|
||||
// Map of font families.
|
||||
//
|
||||
// ```
|
||||
// <font-id>: (<font-name>, <font-fallbacks>)
|
||||
// ```
|
||||
$font-families: (
|
||||
sans: join("Source Sans", $font-fallback-sans, $separator: comma),
|
||||
);
|
||||
|
||||
// List of custom font faces as tuples.
|
||||
//
|
||||
// ```
|
||||
// <font-name> <font-file-basename> <font-weight> <font-style>
|
||||
// ```
|
||||
$font-faces: (
|
||||
("Source Sans", "SourceSans3-Bold", 700, normal),
|
||||
("Source Sans", "SourceSans3-BoldIt", 700, italic),
|
||||
("Source Sans", "SourceSans3-Regular", 400, normal),
|
||||
("Source Sans", "SourceSans3-RegularIt", 400, italic),
|
||||
);
|
||||
|
||||
// Typography
|
||||
// =============================================================================
|
||||
|
||||
// Base
|
||||
$font-size: 16px;
|
||||
$line-height: 24px / $font-size;
|
||||
$font-family: $font-sans-serif;
|
||||
$color: #222222;
|
||||
// Headings
|
||||
$font-size-h1: 36px !default;
|
||||
$font-size-h2: 28px !default;
|
||||
$font-size-h3: 24px !default;
|
||||
$font-size-h4: 20px !default;
|
||||
$font-size-h5: 18px !default;
|
||||
$font-size-h6: 16px !default;
|
||||
$line-height-h: $line-height;
|
||||
$font-size: 16px;
|
||||
$line-height: math.div(24px, $font-size);
|
||||
$font-color: color(darkest);
|
||||
|
||||
// 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: linear;
|
||||
|
||||
$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;
|
||||
|
||||
// Breakpoints
|
||||
// =============================================================================
|
||||
|
||||
$from-tiny: 500px !default;
|
||||
$to-tiny: $from-tiny - 1 !default;
|
||||
$from-small: 700px !default;
|
||||
@@ -70,3 +97,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
|
||||
);
|
||||
|
||||
34
assets/styles/settings/_config.variables.scss
Normal file
34
assets/styles/settings/_config.variables.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
// ==========================================================================
|
||||
// Settings / Config / CSS VARS
|
||||
// ==========================================================================
|
||||
|
||||
:root {
|
||||
|
||||
// Grid
|
||||
--grid-columns: 4;
|
||||
--grid-gutter: #{rem(10px)};
|
||||
--grid-gutter-half: calc(0.5 * var(--grid-gutter));
|
||||
--grid-margin: #{rem(10px)};
|
||||
|
||||
// Container
|
||||
--container-width: calc(100% - 2 * var(--grid-margin));
|
||||
|
||||
// Font sizes
|
||||
--font-size-h1: #{responsive-type(36px, 72px, 1400px)};
|
||||
--font-size-h2: #{rem(28px)};
|
||||
--font-size-h3: #{rem(24px)};
|
||||
--font-size-h4: #{rem(20px)};
|
||||
--font-size-h5: #{rem(18px)};
|
||||
--font-size-h6: #{rem(16px)};
|
||||
|
||||
// // Colors
|
||||
// @each $color, $value in $colors {
|
||||
// --color-#{"" + $color}: #{$value};
|
||||
// }
|
||||
|
||||
@media (min-width: $from-small) {
|
||||
--grid-columns: #{$base-column-nb};
|
||||
--grid-gutter: #{rem(16px)};
|
||||
--grid-margin: #{rem(20px)};
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
//
|
||||
//
|
||||
// DOCS : https://lukyvj.github.io/family.scss/
|
||||
//
|
||||
//
|
||||
/// Select all children from the first to `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin first($num) {
|
||||
@if $num == 1 {
|
||||
&:first-child {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
&:nth-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children from the last to `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin last($num) {
|
||||
&:nth-last-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children after the first to `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin after-first($num) {
|
||||
&:nth-child(n + #{$num + 1}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children before `$num` from the last.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin from-end($num) {
|
||||
&:nth-last-child(#{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children between `$first` and `$last`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin between($first, $last) {
|
||||
&:nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all even children between `$first` and `$last`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin even-between($first, $last) {
|
||||
&:nth-child(even):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all odd children between `$first` and `$last`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin odd-between($first, $last) {
|
||||
&:nth-child(odd):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all `$num` children between `$first` and `$last`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin n-between($num, $first, $last) {
|
||||
&:nth-child(#{$num}n):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Select all children but `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin all-but($num) {
|
||||
&:not(:nth-child(#{$num})) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select children each `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
/// @alias every
|
||||
@mixin each($num) {
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select children each `$num`.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin every($num) {
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the `$num` child from the start and the `$num` child from the last.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin from-first-last($num) {
|
||||
&:nth-child(#{$num}),
|
||||
&:nth-last-child(#{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Select the item in the middle of `$num` child. Only works with odd number
|
||||
/// chain.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin middle($num) {
|
||||
&:nth-child(#{round($num / 2)}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Select all children between the `$num` first and the `$num` last.
|
||||
/// @group with-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - id of the child
|
||||
@mixin all-but-first-last($num) {
|
||||
&:nth-child(n + #{$num}):nth-last-child(n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This quantity-query mixin will only select the first of `$limit` items. It will not
|
||||
/// work if there is not as much as item as you set in `$limit`.
|
||||
/// @group Quantity queries
|
||||
/// @param {number} $limit
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin first-of($limit) {
|
||||
&:nth-last-child(#{$limit}):first-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// This quantity-query mixin will only select the last of `$limit` items. It will not
|
||||
/// if there is not as much as item as you set in `$limit`.
|
||||
/// @group Quantity queries
|
||||
/// @param {number} $limit
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin last-of($limit) {
|
||||
&:nth-of-type(#{$limit}):nth-last-of-type(1) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// This quantity-query mixin will select every items if there is at least `$num` items. It will not
|
||||
/// if there is not as much as item as you set in `$num`.
|
||||
/// @group Quantity queries
|
||||
/// @param {number} $limit
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin at-least($num) {
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(n + #{$num}),
|
||||
&:nth-last-child(n + #{$num}) ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// This quantity-query mixin will select every items if there is at most `$num` items. It will not
|
||||
/// if there is not as much as item as you set in `$num`.
|
||||
/// @group Quantity queries
|
||||
/// @param {number} $limit
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin at-most($num) {
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(-n + #{$num}):first-child,
|
||||
&:nth-last-child(-n + #{$num}):first-child ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// This quantity-query mixin will select every items only if there is between `$min` and `$max` items.
|
||||
/// @group Quantity queries
|
||||
/// @param {number} $limit
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin in-between($min, $max) {
|
||||
$selector: &;
|
||||
$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} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the first exact child
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin first-child() {
|
||||
&:first-of-type {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the last exact child
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin last-child() {
|
||||
&:last-of-type {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all even children.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin even() {
|
||||
&:nth-child(even) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all odd children.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin odd() {
|
||||
&:nth-child(odd) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select only the first and last child.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin first-last() {
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only select the child if it’s unique.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @alias only
|
||||
@mixin unique() {
|
||||
&:only-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only select the child if it’s unique.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin only() {
|
||||
&:only-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only select children if they are not unique. Meaning if there is at
|
||||
/// least 2 children, the style is applied.
|
||||
/// @group no-arguments
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
@mixin not-unique() {
|
||||
&:not(:only-child) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This mixin is used to automatically sort z-index in numerical order. But it
|
||||
/// can also sort them in anti-numerical order, depending the parameters you use.
|
||||
/// @group using functions
|
||||
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
/// @param {number} $num - Number of children
|
||||
/// @param {string} $direction [forward] - Direction of the sort
|
||||
/// @param {number} $index [0] - Index of the sorting
|
||||
@mixin child-index($num, $direction: 'forward', $index: 0) {
|
||||
@for $i from 1 through $num {
|
||||
@if ($direction == 'forward') {
|
||||
&:nth-child(#{$i}) {
|
||||
z-index: order-index($i, $index);
|
||||
@content;
|
||||
}
|
||||
} @else if ($direction == 'backward') {
|
||||
&:nth-last-child(#{$i}) {
|
||||
z-index: order-index($i, $index);
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by the child-index mixin. It will returned the proper sorted numbers
|
||||
/// depending on the `$index` value.
|
||||
/// @access private
|
||||
/// @param {number} $num - Number of children
|
||||
/// @param {number} $index - Index of the sorting
|
||||
@function order-index($i, $index) {
|
||||
@return ($index + $i);
|
||||
}
|
||||
352
assets/styles/tools/_family.scss
Normal file
352
assets/styles/tools/_family.scss
Normal file
@@ -0,0 +1,352 @@
|
||||
// ==========================================================================
|
||||
// Tools / Family
|
||||
// ==========================================================================
|
||||
|
||||
// DOCS : https://lukyvj.github.io/family.scss/
|
||||
//
|
||||
// Select all children from the first to `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin first($num) {
|
||||
@if $num == 1 {
|
||||
&:first-child {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
&:nth-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children from the last to `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin last($num) {
|
||||
&:nth-last-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children after the first to `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin after-first($num) {
|
||||
&:nth-child(n + #{$num + 1}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children before `$num` from the last.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin from-end($num) {
|
||||
&:nth-last-child(#{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children between `$first` and `$last`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin between($first, $last) {
|
||||
&:nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all even children between `$first` and `$last`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin even-between($first, $last) {
|
||||
&:nth-child(even):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all odd children between `$first` and `$last`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin odd-between($first, $last) {
|
||||
&:nth-child(odd):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all `$num` children between `$first` and `$last`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin n-between($num, $first, $last) {
|
||||
&:nth-child(#{$num}n):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children but `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin all-but($num) {
|
||||
&:not(:nth-child(#{$num})) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select children each `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
// @alias every
|
||||
|
||||
@mixin each($num) {
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select children each `$num`.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin every($num) {
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select the `$num` child from the start and the `$num` child from the last.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin from-first-last($num) {
|
||||
&:nth-child(#{$num}),
|
||||
&:nth-last-child(#{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select the item in the middle of `$num` child. Only works with odd number
|
||||
// chain.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin middle($num) {
|
||||
&:nth-child(#{round(math.div($num, 2))}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all children between the `$num` first and the `$num` last.
|
||||
// @group with-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - id of the child
|
||||
|
||||
@mixin all-but-first-last($num) {
|
||||
&:nth-child(n + #{$num}):nth-last-child(n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This quantity-query mixin will only select the first of `$limit` items. It will not
|
||||
// work if there is not as much as item as you set in `$limit`.
|
||||
// @group Quantity queries
|
||||
// @param {number} $limit
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin first-of($limit) {
|
||||
&:nth-last-child(#{$limit}):first-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This quantity-query mixin will only select the last of `$limit` items. It will not
|
||||
// if there is not as much as item as you set in `$limit`.
|
||||
// @group Quantity queries
|
||||
// @param {number} $limit
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin last-of($limit) {
|
||||
&:nth-of-type(#{$limit}):nth-last-of-type(1) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This quantity-query mixin will select every items if there is at least `$num` items. It will not
|
||||
// if there is not as much as item as you set in `$num`.
|
||||
// @group Quantity queries
|
||||
// @param {number} $limit
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin at-least($num) {
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(n + #{$num}),
|
||||
&:nth-last-child(n + #{$num}) ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This quantity-query mixin will select every items if there is at most `$num` items. It will not
|
||||
// if there is not as much as item as you set in `$num`.
|
||||
// @group Quantity queries
|
||||
// @param {number} $limit
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin at-most($num) {
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(-n + #{$num}):first-child,
|
||||
&:nth-last-child(-n + #{$num}):first-child ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This quantity-query mixin will select every items only if there is between `$min` and `$max` items.
|
||||
// @group Quantity queries
|
||||
// @param {number} $limit
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin in-between($min, $max) {
|
||||
$selector: &;
|
||||
$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} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select the first exact child
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin first-child() {
|
||||
&:first-of-type {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Select the last exact child
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin last-child() {
|
||||
&:last-of-type {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
// Select all even children.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin even() {
|
||||
&:nth-child(even) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all odd children.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin odd() {
|
||||
&:nth-child(odd) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Select only the first and last child.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin first-last() {
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Will only select the child if it’s unique.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @alias only
|
||||
|
||||
@mixin unique() {
|
||||
&:only-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Will only select the child if it’s unique.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin only() {
|
||||
&:only-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Will only select children if they are not unique. Meaning if there is at
|
||||
// least 2 children, the style is applied.
|
||||
// @group no-arguments
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
|
||||
@mixin not-unique() {
|
||||
&:not(:only-child) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// This mixin is used to automatically sort z-index in numerical order. But it
|
||||
// can also sort them in anti-numerical order, depending the parameters you use.
|
||||
// @group using functions
|
||||
// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
|
||||
// @param {number} $num - Number of children
|
||||
// @param {string} $direction [forward] - Direction of the sort
|
||||
// @param {number} $index [0] - Index of the sorting
|
||||
|
||||
@mixin child-index($num, $direction: 'forward', $index: 0) {
|
||||
@for $i from 1 through $num {
|
||||
@if ($direction == 'forward') {
|
||||
&:nth-child(#{$i}) {
|
||||
z-index: order-index($i, $index);
|
||||
@content;
|
||||
}
|
||||
} @else if ($direction == 'backward') {
|
||||
&:nth-last-child(#{$i}) {
|
||||
z-index: order-index($i, $index);
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the child-index mixin. It will returned the proper sorted numbers
|
||||
// depending on the `$index` value.
|
||||
// @access private
|
||||
// @param {number} $num - Number of children
|
||||
// @param {number} $index - Index of the sorting
|
||||
|
||||
@function order-index($i, $index) {
|
||||
@return ($index + $i);
|
||||
}
|
||||
@@ -2,97 +2,60 @@
|
||||
// Tools / Font Faces
|
||||
// ==========================================================================
|
||||
|
||||
$global-font-file-formats: "woff2", "woff" !default;
|
||||
// Imports the custom font.
|
||||
//
|
||||
// The mixin expects font files to be woff and woff2.
|
||||
//
|
||||
// @param {List} $webfont - A custom font to import, as a tuple:
|
||||
// `<font-name> <font-file-basename> <font-weight> <font-style>`.
|
||||
// @param {String} $dir - The webfont directory path.
|
||||
// @output The `@font-face` at-rule specifying the custom font.
|
||||
|
||||
//
|
||||
// Builds the `src` list for an `@font-face` declaration.
|
||||
//
|
||||
// @link https://github.com/thoughtbot/bourbon/blob/master/core/bourbon/utilities/_font-source-declaration.scss
|
||||
// @link http://goo.gl/Ru1bKP
|
||||
//
|
||||
// @param {String} $font-family - The font family name.
|
||||
// @param {String} $file-path - The path to the font family.
|
||||
// @param {List} $file-formats - The file formats to request.
|
||||
// @return {List}
|
||||
//
|
||||
// @require {function} list-contains
|
||||
//
|
||||
// @access private
|
||||
//
|
||||
@function font-source-declaration(
|
||||
$font-family,
|
||||
$file-path,
|
||||
$file-formats
|
||||
) {
|
||||
$src: ();
|
||||
$formats-map: (
|
||||
eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"),
|
||||
woff2: "#{$file-path}.woff2" format("woff2"),
|
||||
woff: "#{$file-path}.woff" format("woff"),
|
||||
ttf: "#{$file-path}.ttf" format("truetype"),
|
||||
svg: "#{$file-path}.svg##{$font-family}" format("svg"),
|
||||
);
|
||||
@mixin font-face($webfont, $dir) {
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: nth($webfont, 1);
|
||||
src: url("#{$dir}#{nth($webfont, 2)}.woff2") format("woff2"),
|
||||
url("#{$dir}#{nth($webfont, 2)}.woff") format("woff");
|
||||
font-weight: #{nth($webfont, 3)};
|
||||
font-style: #{nth($webfont, 4)};
|
||||
}
|
||||
}
|
||||
|
||||
@each $key, $values in $formats-map {
|
||||
@if list-contains($file-formats, $key) {
|
||||
$file-path: nth($values, 1);
|
||||
$font-format: nth($values, 2);
|
||||
$src: append($src, url("#{$assets-path}#{$file-path}") $font-format, comma);
|
||||
// Imports the list of custom fonts.
|
||||
//
|
||||
// @require {mixin} font-face
|
||||
//
|
||||
// @param {List<List>} $webfonts - List of custom fonts to import.
|
||||
// See `font-face` mixin for details.
|
||||
// @param {String} $dir - The webfont directory path.
|
||||
// @output The `@font-face` at-rules specifying the custom fonts.
|
||||
|
||||
@mixin font-faces($webfonts, $dir) {
|
||||
@if (length($webfonts) > 0) {
|
||||
@if (type-of(nth($webfonts, 1)) == "list") {
|
||||
@each $webfont in $webfonts {
|
||||
@include font-face($webfont, $dir);
|
||||
}
|
||||
} @else {
|
||||
@include font-face($webfonts, $dir);
|
||||
}
|
||||
}
|
||||
|
||||
@return $src;
|
||||
}
|
||||
|
||||
// Retrieves the font family stack for the given font ID.
|
||||
//
|
||||
// Generates an `@font-face` declaration.
|
||||
// @require {variable} $font-families - See settings directory.
|
||||
//
|
||||
// You can choose the specific file formats you need to output; the mixin supports
|
||||
// `eot`, `ttf`, `svg`, `woff2` and `woff`.
|
||||
//
|
||||
// @link https://github.com/thoughtbot/bourbon/blob/master/core/bourbon/library/_font-face.scss
|
||||
//
|
||||
// @param {String} $font-family - The font family name.
|
||||
// @param {String} $file-path - The path to the font family.
|
||||
// @param {String|List} $file-formats [("ttf", "woff2", "woff")]
|
||||
// A list of file formats to support,
|
||||
// for example ("eot", "ttf", "svg", "woff2", "woff").
|
||||
//
|
||||
// @content
|
||||
// Any additional CSS properties that are included in the `@include`
|
||||
// directive will be output within the `@font-face` declaration, e.g.
|
||||
// you can pass in `font-weight`, `font-style` and/or `unicode-range`.
|
||||
//
|
||||
// @example scss
|
||||
// @include font-face(
|
||||
// "source-sans-pro",
|
||||
// "fonts/source-sans-pro-regular",
|
||||
// ("woff2", "woff")
|
||||
// ) {
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// }
|
||||
//
|
||||
// // CSS Output
|
||||
// @font-face {
|
||||
// font-family: "source-sans-pro";
|
||||
// src: url("fonts/source-sans-pro-regular.woff2") format("woff2"),
|
||||
// url("fonts/source-sans-pro-regular.woff") format("woff");
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// }
|
||||
//
|
||||
// @require {function} _font-source-declaration
|
||||
// @require {function} _retrieve-bourbon-setting
|
||||
//
|
||||
@mixin font-face(
|
||||
$font-family,
|
||||
$file-path,
|
||||
$file-formats: $global-font-file-formats
|
||||
) {
|
||||
@font-face {
|
||||
font-family: $font-family;
|
||||
src: font-source-declaration( $font-family, $file-path, $file-formats);
|
||||
@content;
|
||||
// @param {String} $font-family - The custom font ID.
|
||||
// @throws Error if the $font-family does not exist.
|
||||
// @return {List} The font stack.
|
||||
|
||||
@function ff($font-family) {
|
||||
@if not map-has-key($font-families, $font-family) {
|
||||
@error "No font-family found in $font-families map for `#{$font-family}`.";
|
||||
}
|
||||
|
||||
$value: map-get($font-families, $font-family);
|
||||
@return $value;
|
||||
}
|
||||
|
||||
@@ -1,73 +1,89 @@
|
||||
// ==========================================================================
|
||||
// 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.
|
||||
// @param {Number} $base [$font-size] - The assumed base font size.
|
||||
// @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;
|
||||
@return math.div($size, $base) * 1em;
|
||||
}
|
||||
|
||||
//
|
||||
// Converts the given pixel value to its REM quivalent.
|
||||
//
|
||||
// @param {Number} $size - The pixel value to convert.
|
||||
// @param {Number} $base [$font-size] - The assumed base font size.
|
||||
// @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;
|
||||
@return math.div($size, $base) * 1rem;
|
||||
}
|
||||
|
||||
// Retrieves the z-index from the {@see $layers master list}.
|
||||
//
|
||||
// @link on http://css-tricks.com/handling-z-index/
|
||||
//
|
||||
// @param {string} $layer The name of the z-index.
|
||||
// @param {number} $modifier A positive or negative modifier to apply
|
||||
// to the returned z-index value.
|
||||
// @throw Error if the $layer does not exist.
|
||||
// @throw Warning if the $modifier might overlap another master z-index.
|
||||
// @return {number} The computed z-index of $layer and $modifier.
|
||||
|
||||
@function z($layer, $modifier: 0) {
|
||||
@if not map-has-key($z-indexes, $layer) {
|
||||
@error "Unknown master z-index layer: #{$layer}";
|
||||
}
|
||||
|
||||
@if ($modifier >= 50 or $modifier <= -50) {
|
||||
@warn "Modifier may overlap the another master z-index layer: #{$modifier}";
|
||||
}
|
||||
|
||||
$index: map-get($z-indexes, $layer);
|
||||
|
||||
@return $index + $modifier;
|
||||
}
|
||||
|
||||
// Converts a number to a percentage.
|
||||
//
|
||||
// @alias percentage()
|
||||
// @link http://sassdoc.com/annotations/#alias
|
||||
// @param {Number} $number - The value to convert.
|
||||
// @return {Number} A percentage.
|
||||
//
|
||||
|
||||
@function span($number) {
|
||||
@return percentage($number);
|
||||
}
|
||||
|
||||
//
|
||||
// Checks if a list contains a value(s).
|
||||
//
|
||||
// @link https://github.com/thoughtbot/bourbon/blob/master/core/bourbon/validators/_contains.scss
|
||||
@@ -75,7 +91,7 @@
|
||||
// @param {List} $values - A single value or list of values to check for.
|
||||
// @return {Boolean}
|
||||
// @access private
|
||||
//
|
||||
|
||||
@function list-contains(
|
||||
$list,
|
||||
$values...
|
||||
@@ -89,40 +105,123 @@
|
||||
@return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Resolve whether a rule is important or not.
|
||||
//
|
||||
// @param {Boolean} $flag - Whether a rule is important (TRUE) or not (FALSE).
|
||||
// @return {String|Null} Returns `!important` or NULL.
|
||||
//
|
||||
|
||||
@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`."
|
||||
@error "`#{$flag}` needs to be `true` or `false`.";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Determine if the current context is for a WYSIWYG editor.
|
||||
//
|
||||
// @requires {String} $context - The global context of the stylesheet.
|
||||
// @return {Boolean} If the $context is set to "editor".
|
||||
//
|
||||
|
||||
@function is-editor() {
|
||||
@return ('editor' == $context);
|
||||
}
|
||||
|
||||
//
|
||||
// Determine if the current context is for the front-end.
|
||||
//
|
||||
// @requires {String} $context - The global context of the stylesheet.
|
||||
// @return {Boolean} If the $context is set to "frontend".
|
||||
//
|
||||
|
||||
@function is-frontend() {
|
||||
@return ('frontend' == $context);
|
||||
}
|
||||
|
||||
$context: 'frontend' !default;
|
||||
|
||||
// Returns calculation of a percentage of the grid cell width
|
||||
// with optional inset of grid gutter.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: grid-space(6/12);
|
||||
// margin-left: grid-space(1/12, 1);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage spacer
|
||||
// @param {number} $inset - The grid gutter inset
|
||||
// @return {function<number>}
|
||||
@function grid-space($percentage, $inset: 0) {
|
||||
@return calc(#{$percentage} * (100vw - 2 * var(--grid-margin, 0px)) - (1 - #{$percentage}) * var(--grid-gutter, 0px) + #{$inset} * var(--grid-gutter, 0px));
|
||||
}
|
||||
|
||||
// Returns calculation of a percentage of the viewport height.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// height: vh(100);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage number
|
||||
// @return {function<number>} in vh
|
||||
@function vh($number) {
|
||||
@return calc(#{$number} * var(--vh, 1vh));
|
||||
}
|
||||
|
||||
// Returns calculation of a percentage of the viewport width.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: vw(100);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $number - The percentage number
|
||||
// @return {function<number>} in vw
|
||||
|
||||
@function vw($number) {
|
||||
@return calc(#{$number} * var(--vw, 1vw));
|
||||
}
|
||||
|
||||
// Returns clamp of calculated preferred responsive font size
|
||||
// within a font size and breakpoint range.
|
||||
//
|
||||
// ```scss
|
||||
// .c-heading.-h1 {
|
||||
// font-size: responsive-type(30px, 60px, 1800);
|
||||
// }
|
||||
//
|
||||
// .c-heading.-h2 {
|
||||
// font-size: responsive-type(20px, 40px, $from-big);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {number} $min-size - Minimum font size in pixels.
|
||||
// @param {number} $max-size - Maximum font size in pixels.
|
||||
// @param {number} $breakpoint - Maximum breakpoint.
|
||||
// @return {function<number, function<number>, number>}
|
||||
@function responsive-type($min-size, $max-size, $breakpoint) {
|
||||
$delta: math.div($max-size, $breakpoint);
|
||||
@return clamp($min-size, calc(#{strip-unit($delta)} * #{vw(100)}), $max-size);
|
||||
}
|
||||
|
||||
// Returns color code.
|
||||
//
|
||||
// ```scss
|
||||
// .c-box {
|
||||
// width: color(primary);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// @param {string} $key - The color key in $colors.
|
||||
// @return {color}
|
||||
|
||||
@function color($key) {
|
||||
@if not map-has-key($colors, $key) {
|
||||
@error "Unknown '#{$key}' in $colors.";
|
||||
}
|
||||
@return map-get($colors, $key);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Tools / Layout
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Grid-like layout system.
|
||||
//
|
||||
// The layout tools provide a column-style layout system. This file contains
|
||||
@@ -10,7 +9,6 @@
|
||||
//
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/0420ba8/objects/_objects.layout.scss
|
||||
//
|
||||
|
||||
//
|
||||
// Generate the layout container.
|
||||
//
|
||||
@@ -19,9 +17,11 @@
|
||||
//
|
||||
// @requires {function} u-list-reset
|
||||
// @output `font-size`, `margin`, `padding`, `list-style`
|
||||
//
|
||||
|
||||
@mixin o-layout($gutter: 0, $fix-whitespace: true) {
|
||||
@include u-list-reset;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
@if ($fix-whitespace) {
|
||||
font-size: 0;
|
||||
@@ -32,7 +32,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate the layout item.
|
||||
//
|
||||
// 1. Required in order to combine fluid widths with fixed gutters.
|
||||
@@ -43,7 +42,7 @@
|
||||
// 4. By default, all layout items are full-width (mobile first).
|
||||
// 5. Gutters provided by left padding:
|
||||
// http://csswizardry.com/2011/08/building-better-grid-systems/
|
||||
//
|
||||
|
||||
@mixin o-layout_item($gutter: 0, $fix-whitespace: true) {
|
||||
display: inline-block; // [2]
|
||||
width: 100%; // [4]
|
||||
|
||||
141
assets/styles/tools/_maths.scss
Normal file
141
assets/styles/tools/_maths.scss
Normal file
@@ -0,0 +1,141 @@
|
||||
// ==========================================================================
|
||||
// Tools / Maths
|
||||
// ==========================================================================
|
||||
|
||||
// Remove the unit of a length
|
||||
//
|
||||
// @param {Number} $number Number to remove unit from
|
||||
// @return {function<number>}
|
||||
@function strip-unit($value) {
|
||||
@if type-of($value) != "number" {
|
||||
@error "Invalid `#{type-of($value)}` type. Choose a number type instead.";
|
||||
} @else if type-of($value) == "number" and not is-unitless($value) {
|
||||
@return math.div($value, $value * 0 + 1);
|
||||
}
|
||||
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// Returns the square root of the given number.
|
||||
//
|
||||
// @param {number} $number The number to calculate.
|
||||
// @return {number}
|
||||
|
||||
@function sqrt($number) {
|
||||
$x: 1;
|
||||
$value: $x;
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
$value: $x - math.div(($x * $x - abs($number)), (2 * $x));
|
||||
$x: $value;
|
||||
}
|
||||
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// Returns a number raised to the power of an exponent.
|
||||
//
|
||||
// @param {number} $number The base number.
|
||||
// @param {number} $exp The exponent.
|
||||
// @return {number}
|
||||
|
||||
@function pow($number, $exp) {
|
||||
$value: 1;
|
||||
|
||||
@if $exp > 0 {
|
||||
@for $i from 1 through $exp {
|
||||
$value: $value * $number;
|
||||
}
|
||||
} @else if $exp < 0 {
|
||||
@for $i from 1 through -$exp {
|
||||
$value: math.div($value, $number);
|
||||
}
|
||||
}
|
||||
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// Returns the factorial of the given number.
|
||||
//
|
||||
// @param {number} $number The number to calculate.
|
||||
// @return {number}
|
||||
|
||||
@function fact($number) {
|
||||
$value: 1;
|
||||
|
||||
@if $number > 0 {
|
||||
@for $i from 1 through $number {
|
||||
$value: $value * $i;
|
||||
}
|
||||
}
|
||||
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// Returns an approximation of pi, with 11 decimals.
|
||||
//
|
||||
// @return {number}
|
||||
|
||||
@function pi() {
|
||||
@return 3.14159265359;
|
||||
}
|
||||
|
||||
// Converts the number in degrees to the radian equivalent .
|
||||
//
|
||||
// @param {number} $angle The angular value to calculate.
|
||||
// @return {number} If $angle has the `deg` unit,
|
||||
// the radian equivalent is returned.
|
||||
// Otherwise, the unitless value of $angle is returned.
|
||||
|
||||
@function rad($angle) {
|
||||
$unit: unit($angle);
|
||||
$angle: strip-units($angle);
|
||||
|
||||
// If the angle has `deg` as unit, convert to radians.
|
||||
@if ($unit == deg) {
|
||||
@return math.div($angle, 180) * pi();
|
||||
}
|
||||
|
||||
@return $angle;
|
||||
}
|
||||
|
||||
// Returns the sine of the given number.
|
||||
//
|
||||
// @param {number} $angle The angle to calculate.
|
||||
// @return {number}
|
||||
|
||||
@function sin($angle) {
|
||||
$sin: 0;
|
||||
$angle: rad($angle);
|
||||
|
||||
@for $i from 0 through 10 {
|
||||
$sin: $sin + pow(-1, $i) * math.div(pow($angle, (2 * $i + 1)), fact(2 * $i + 1));
|
||||
}
|
||||
|
||||
@return $sin;
|
||||
}
|
||||
|
||||
// Returns the cosine of the given number.
|
||||
//
|
||||
// @param {string} $angle The angle to calculate.
|
||||
// @return {number}
|
||||
|
||||
@function cos($angle) {
|
||||
$cos: 0;
|
||||
$angle: rad($angle);
|
||||
|
||||
@for $i from 0 through 10 {
|
||||
$cos: $cos + pow(-1, $i) * math.div(pow($angle, 2 * $i), fact(2 * $i));
|
||||
}
|
||||
|
||||
@return $cos;
|
||||
}
|
||||
|
||||
// Returns the tangent of the given number.
|
||||
//
|
||||
// @param {string} $angle The angle to calculate.
|
||||
// @return {number}
|
||||
|
||||
@function tan($angle) {
|
||||
@return math.div(sin($angle), cos($angle));
|
||||
}
|
||||
@@ -2,37 +2,34 @@
|
||||
// Tools / Mixins
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// Set the color of the highlight that appears over a link while it's being tapped.
|
||||
//
|
||||
// By default, the highlight is suppressed.
|
||||
//
|
||||
// @param {Color} $value [rgba(0, 0, 0, 0)] - The value of the highlight.
|
||||
// @output `-webkit-tap-highlight-color`
|
||||
//
|
||||
|
||||
@mixin tap-highlight-color($value: rgba(0, 0, 0, 0)) {
|
||||
-webkit-tap-highlight-color: $value;
|
||||
}
|
||||
|
||||
//
|
||||
// Set whether or not touch devices use momentum-based scrolling for the given element.
|
||||
//
|
||||
// By default, applies momentum-based scrolling for the current element.
|
||||
//
|
||||
// @param {String} $value [rgba(0, 0, 0, 0)] - The type of scrolling.
|
||||
// @output `-webkit-overflow-scrolling`
|
||||
//
|
||||
|
||||
@mixin overflow-scrolling($value: touch) {
|
||||
-webkit-overflow-scrolling: $value;
|
||||
}
|
||||
|
||||
//
|
||||
// Micro clearfix rules for containing floats.
|
||||
//
|
||||
// @link http://www.cssmojo.com/the-very-latest-clearfix-reloaded/
|
||||
// @param {List} $supports The type of clearfix to generate.
|
||||
// @output Injects `:::after` pseudo-element.
|
||||
//
|
||||
|
||||
@mixin u-clearfix($supports...) {
|
||||
&::after {
|
||||
display: if(list-contains($supports, table), table, block);
|
||||
@@ -41,7 +38,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate a font-size and baseline-compatible line-height.
|
||||
//
|
||||
// @link https://github.com/inuitcss/inuitcss/c14029c/tools/_tools.font-size.scss
|
||||
@@ -49,25 +45,24 @@
|
||||
// @param {Number} $line-height [auto] - The line box height.
|
||||
// @param {Boolean} $important [false] - Whether the font-size is important.
|
||||
// @output `font-size`, `line-height`
|
||||
//
|
||||
|
||||
@mixin font-size($font-size, $line-height: auto, $important: false) {
|
||||
$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;
|
||||
line-height: ceil(math.div($font-size, $line-height)) * math.div($line-height, $font-size) $important;
|
||||
}
|
||||
@else {
|
||||
@if (type-of($line-height) == number or $line-height == "inherit" or $line-height == "normal") {
|
||||
line-height: $line-height $important;
|
||||
}
|
||||
@elseif ($line-height != "none" and $line-height != false) {
|
||||
@error "D’oh! `#{$line-height}` is not a valid value for `$line-height`."
|
||||
@else if ($line-height != "none" and $line-height != false) {
|
||||
@error "D’oh! `#{$line-height}` is not a valid value for `$line-height`.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Vertically-center the direct descendants of the current element.
|
||||
//
|
||||
// Centering is achieved by displaying children as inline-blocks. Any whitespace
|
||||
@@ -75,7 +70,7 @@
|
||||
// and its children.
|
||||
//
|
||||
// @output `font-size`, `display`, `vertical-align`
|
||||
//
|
||||
|
||||
@mixin o-vertical-center {
|
||||
font-size: 0;
|
||||
|
||||
@@ -93,13 +88,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate `:hover` and `:focus` styles in one go.
|
||||
//
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/master/tools/_tools.mixins.scss
|
||||
// @content Wrapped in `:focus` and `:hover` pseudo-classes.
|
||||
// @output Wraps the given content in `:focus` and `:hover` pseudo-classes.
|
||||
//
|
||||
|
||||
@mixin u-hocus {
|
||||
&:focus,
|
||||
&:hover {
|
||||
@@ -107,13 +101,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate `:active` and `:focus` styles in one go.
|
||||
//
|
||||
// @see {Mixin} u-hocus
|
||||
// @content Wrapped in `:focus` and `:active` pseudo-classes.
|
||||
// @output Wraps the given content in `:focus` and `:hover` pseudo-classes.
|
||||
//
|
||||
|
||||
@mixin u-actus {
|
||||
&:focus,
|
||||
&:active {
|
||||
@@ -121,18 +114,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Injects generic rules for disabling UL/OL/LI styles.
|
||||
//
|
||||
// @output `list-style`, `margin`, `padding`
|
||||
//
|
||||
@mixin u-list-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
//
|
||||
// Prevent text from wrapping onto multiple lines for the current element.
|
||||
//
|
||||
// An ellipsis is appended to the end of the line.
|
||||
@@ -142,7 +123,7 @@
|
||||
//
|
||||
// @param {Number} $width [100%] - The maximum width of element.
|
||||
// @output `max-width`, `word-wrap`, `white-space`, `overflow`, `text-overflow`
|
||||
//
|
||||
|
||||
@mixin u-truncate($width: 100%) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -153,12 +134,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Applies accessible hiding to the current element.
|
||||
//
|
||||
// @param {Boolean} $important [true] - Whether the visibility is important.
|
||||
// @output Properties for removing the element from the document flow.
|
||||
//
|
||||
|
||||
@mixin u-accessibly-hidden($important: true) {
|
||||
$important: important($important);
|
||||
position: absolute $important;
|
||||
@@ -171,12 +151,11 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Allows an accessibly hidden element to be focusable via keyboard navigation.
|
||||
//
|
||||
// @content For styling the now visible element.
|
||||
// @output Injects `:focus`, `:active` pseudo-classes.
|
||||
//
|
||||
|
||||
@mixin u-accessibly-focusable {
|
||||
@include u-actus {
|
||||
clip: auto;
|
||||
@@ -187,7 +166,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Hide the current element from all.
|
||||
//
|
||||
// The element will be hidden from screen readers and removed from the document flow.
|
||||
@@ -195,14 +173,13 @@
|
||||
// @link http://juicystudio.com/article/screen-readers-display-none.php
|
||||
// @param {Boolean} $important [true] - Whether the visibility is important.
|
||||
// @output `display`, `visibility`
|
||||
//
|
||||
|
||||
@mixin u-hidden($important: true) {
|
||||
$important: important($important);
|
||||
display: none $important;
|
||||
visibility: hidden $important;
|
||||
}
|
||||
|
||||
//
|
||||
// Show the current element for all.
|
||||
//
|
||||
// The element will be accessible from screen readers and visible in the document flow.
|
||||
@@ -210,7 +187,7 @@
|
||||
// @param {String} $display [block] - The rendering box used for the element.
|
||||
// @param {Boolean} $important [true] - Whether the visibility is important.
|
||||
// @output `display`, `visibility`
|
||||
//
|
||||
|
||||
@mixin u-shown($display: block, $important: true) {
|
||||
$important: important($important);
|
||||
display: $display $important;
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Tools / Ratio Constraint
|
||||
// ==========================================================================
|
||||
|
||||
//
|
||||
// A tool to restrain a container to a unitary or fractional proportion.
|
||||
//
|
||||
|
||||
$data-ratios: "1/2" "0.5" 50%,
|
||||
"11/20" "0.55" 55%,
|
||||
"3/5" "0.6" 60%,
|
||||
"13/20" "0.65" 65%,
|
||||
"7/10" "0.7" 70%,
|
||||
"3/4" "0.75" 75%,
|
||||
"4/5" "0.8" 80%,
|
||||
"17/20" "0.85" 85%,
|
||||
"9/10" "0.9" 90%,
|
||||
"19/20" "0.95" 95%,
|
||||
"1/1" "1" 100%,
|
||||
"21/20" "1.05" 105%,
|
||||
"11/10" "1.1" 110%,
|
||||
"23/20" "1.15" 115%,
|
||||
"6/5" "1.2" 120%,
|
||||
"5/4" "1.25" 125% !default;
|
||||
$data-ratio-crops: "top" "bottom" "both" !default;
|
||||
|
||||
@mixin crop($crop) {
|
||||
@if $crop == "top" {
|
||||
bottom: 0;
|
||||
} @else if $crop == "bottom" {
|
||||
top: 0;
|
||||
} @else if $crop == "both" {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.u-ratio {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
width: 100%;
|
||||
content: "";
|
||||
}
|
||||
|
||||
@each $ratio in $data-ratios {
|
||||
$ratio-1: nth($ratio, 1);
|
||||
$ratio-2: nth($ratio, 2);
|
||||
&[data-ratio="#{$ratio-1}"]::before,
|
||||
&[data-ratio="#{$ratio-2}"]::before {
|
||||
padding-top: nth($ratio, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-ratio_content_container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-ratio_content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@each $crop in $data-ratio-crops {
|
||||
&[data-ratio-crop="#{$crop}"] {
|
||||
@include crop($crop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,21 @@
|
||||
// .u-pull-2/4
|
||||
// .u-pull-1/5
|
||||
// .u-push-2/3
|
||||
|
||||
$widths-offsets: false !default;
|
||||
|
||||
// By default, the boilerplate uses fractions-like classes like `<div class="u-1/4">`.
|
||||
// You can change the `/` to whatever you fancy with this variable.
|
||||
|
||||
$fractions-delimiter: \/ !default;
|
||||
|
||||
// When using Sass-MQ, this defines the separator for the breakpoints suffix
|
||||
// in the class name. By default, we are generating the responsive suffixes
|
||||
// for the classes with a `@` symbol so you get classes like:
|
||||
// <div class="u-3/12@mobile">
|
||||
|
||||
$breakpoint-delimiter: \@ !default;
|
||||
|
||||
//
|
||||
// Generate a series of width helper classes
|
||||
//
|
||||
// @example scss
|
||||
@@ -45,7 +47,7 @@ $breakpoint-delimiter: \@ !default;
|
||||
// @param {List} $colums - The columns we want the widths to have.
|
||||
// @param {String} $breakpoint - Optional suffix for responsive widths.
|
||||
// @output `width`, `position`, `right`, `left`
|
||||
//
|
||||
|
||||
@mixin widths($columns, $breakpoint: null, $important: true) {
|
||||
$important: important($important);
|
||||
|
||||
@@ -56,7 +58,7 @@ $breakpoint-delimiter: \@ !default;
|
||||
@for $numerator from 1 through $denominator {
|
||||
// Build a class in the format `.u-3/4[@<breakpoint>]`.
|
||||
.u-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
width: ($numerator / $denominator) * 100% $important;
|
||||
width: math.div($numerator, $denominator) * 100% $important;
|
||||
}
|
||||
|
||||
@if ($widths-offsets == true) {
|
||||
@@ -64,13 +66,13 @@ $breakpoint-delimiter: \@ !default;
|
||||
.u-push-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
position: relative $important;
|
||||
right: auto $important;
|
||||
left: ($numerator / $denominator) * 100% $important;
|
||||
left: math.div($numerator, $denominator) * 100% $important;
|
||||
}
|
||||
|
||||
// Build a class in the format `.u-pull-5/6[@<breakpoint>]`.
|
||||
.u-pull-#{$numerator}#{$fractions-delimiter}#{$denominator}#{$breakpoint} {
|
||||
position: relative $important;
|
||||
right: ($numerator / $denominator) * 100% $important;
|
||||
right: math.div($numerator, $denominator) * 100% $important;
|
||||
left: auto $important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
// Floats
|
||||
// ==========================================================================
|
||||
|
||||
.u-float-left {
|
||||
float: left !important;
|
||||
}
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
// Horizontal Text
|
||||
// ==========================================================================
|
||||
|
||||
.u-text-center {
|
||||
text-align: center !important;
|
||||
}
|
||||
@@ -28,6 +30,7 @@
|
||||
|
||||
// Vertical Text
|
||||
// ==========================================================================
|
||||
|
||||
.u-align-baseline {
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
|
||||
41
assets/styles/utilities/_grid-column.scss
Normal file
41
assets/styles/utilities/_grid-column.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
// ==========================================================================
|
||||
// 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, $mediaquery in $breakpoints {
|
||||
@for $fromIndex from 1 through $colsMax {
|
||||
@for $toIndex from 1 through $colsMax {
|
||||
@if $mediaquery == null {
|
||||
.u-gc-#{$fromIndex}\/#{$toIndex} {
|
||||
--gc-start: #{$fromIndex};
|
||||
--gc-end: #{$toIndex};
|
||||
}
|
||||
} @else {
|
||||
.u-gc-#{$fromIndex}\/#{$toIndex}\@#{$breakpoint} {
|
||||
@media (min-width: #{$mediaquery}) {
|
||||
--gc-start: #{$fromIndex};
|
||||
--gc-end: #{$toIndex};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Utilities / Headings
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* Redefine all of our basic heading styles against utility classes so as to
|
||||
* provide larger (or smaller) generic font sizes. Anything more opinionated
|
||||
* than simple font-size changes should likely be applied via "o-" classes
|
||||
*
|
||||
* @example
|
||||
* <p class="u-h1"></p>
|
||||
*
|
||||
* @requires base/headings
|
||||
* @link http://csswizardry.com/2016/02/managing-typography-on-large-apps/
|
||||
* @link https://github.com/inuitcss/inuitcss/blob/develop/utilities/_utilities.headings.scss
|
||||
*/
|
||||
|
||||
.u-h1 {
|
||||
font-size: rem($font-size-h1) !important;
|
||||
}
|
||||
|
||||
.u-h2 {
|
||||
font-size: rem($font-size-h2) !important;
|
||||
}
|
||||
|
||||
.u-h3 {
|
||||
font-size: rem($font-size-h3) !important;
|
||||
}
|
||||
|
||||
.u-h4 {
|
||||
font-size: rem($font-size-h4) !important;
|
||||
}
|
||||
|
||||
.u-h5 {
|
||||
font-size: rem($font-size-h5) !important;
|
||||
}
|
||||
|
||||
.u-h6 {
|
||||
font-size: rem($font-size-h6) !important;
|
||||
}
|
||||
@@ -4,18 +4,21 @@
|
||||
|
||||
// Layout
|
||||
// ==========================================================================
|
||||
|
||||
.u-clearfix {
|
||||
@include u-clearfix;
|
||||
}
|
||||
|
||||
// Decorative
|
||||
// =============================================================================
|
||||
|
||||
.u-truncate {
|
||||
@include u-truncate;
|
||||
}
|
||||
|
||||
// Visibility / Display
|
||||
// ==========================================================================
|
||||
|
||||
[hidden][aria-hidden="false"] {
|
||||
position: absolute;
|
||||
display: inherit;
|
||||
@@ -30,35 +33,33 @@
|
||||
// display: block;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 1. Fix for Firefox bug: an image styled `max-width:100%` within an
|
||||
// * inline-block will display at its default size, and not limit its width to
|
||||
// * 100% of an ancestral container.
|
||||
// */
|
||||
// // 1. Fix for Firefox bug: an image styled `max-width:100%` within an
|
||||
// // inline-block will display at its default size, and not limit its width to
|
||||
// // 100% of an ancestral container.
|
||||
//
|
||||
// .u-inline-block {
|
||||
// display: inline-block !important;
|
||||
// max-width: 100%; /* 1 */
|
||||
// }
|
||||
|
||||
//
|
||||
// .u-inline {
|
||||
// display: inline !important;
|
||||
// }
|
||||
|
||||
//
|
||||
// .u-table {
|
||||
// display: table !important;
|
||||
// }
|
||||
|
||||
//
|
||||
// .u-tableCell {
|
||||
// display: table-cell !important;
|
||||
// }
|
||||
|
||||
//
|
||||
// .u-tableRow {
|
||||
// display: table-row !important;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Completely remove from the flow but leave available to screen readers.
|
||||
*/
|
||||
// Completely remove from the flow but leave available to screen readers.
|
||||
|
||||
.u-screen-reader-text {
|
||||
@include u-accessibly-hidden;
|
||||
}
|
||||
@@ -69,13 +70,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Extends the `.screen-reader-text` class to allow the element
|
||||
* to be focusable when navigated to via the keyboard.
|
||||
*
|
||||
* @link https://www.drupal.org/node/897638
|
||||
* @todo Define styles when focused.
|
||||
*/
|
||||
// Extends the `.screen-reader-text` class to allow the element
|
||||
// to be focusable when navigated to via the keyboard.
|
||||
//
|
||||
// @link https://www.drupal.org/node/897638
|
||||
// @todo Define styles when focused.
|
||||
|
||||
.u-screen-reader-text.-focusable {
|
||||
@include u-accessibly-focusable;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
////
|
||||
|
||||
@media print {
|
||||
/**
|
||||
* 1. Black prints faster: http://www.sanbeiji.com/archives/953
|
||||
*/
|
||||
|
||||
// 1. Black prints faster: http://www.sanbeiji.com/archives/953
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after,
|
||||
@@ -21,7 +21,7 @@
|
||||
*:first-line {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
color: #000000 !important; /* [1] */
|
||||
color: #000000 !important; // [1]
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
@@ -38,10 +38,9 @@
|
||||
content: " (" attr(title) ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't show links that are fragment identifiers, or use the `javascript:`
|
||||
* pseudo protocol.
|
||||
*/
|
||||
// Don't show links that are fragment identifiers, or use the `javascript:`
|
||||
// pseudo protocol.
|
||||
|
||||
a[href^="#"]:after,
|
||||
a[href^="javascript:"]:after {
|
||||
content: "";
|
||||
@@ -53,9 +52,8 @@
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Printing Tables: http://css-discuss.incutio.com/wiki/Printing_Tables
|
||||
*/
|
||||
// Printing Tables: http://css-discuss.incutio.com/wiki/Printing_Tables
|
||||
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
37
assets/styles/utilities/_ratio.scss
Normal file
37
assets/styles/utilities/_ratio.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
// ==========================================================================
|
||||
// Utilities / Ratio
|
||||
// ==========================================================================
|
||||
|
||||
// @link https://github.com/inuitcss/inuitcss/blob/19d0c7e/objects/_objects.ratio.scss
|
||||
|
||||
// A list of aspect ratios that get generated as modifier classes.
|
||||
$aspect-ratios: (
|
||||
(2:1),
|
||||
(4:3),
|
||||
(16:9),
|
||||
) !default;
|
||||
|
||||
/* stylelint-disable */
|
||||
|
||||
// Generate a series of ratio classes to be used like so:
|
||||
//
|
||||
// @example
|
||||
// <div class="o-ratio u-16:9">
|
||||
|
||||
@each $ratio in $aspect-ratios {
|
||||
@each $antecedent, $consequent in $ratio {
|
||||
@if (type-of($antecedent) != number) {
|
||||
@error "`#{$antecedent}` needs to be a number."
|
||||
}
|
||||
|
||||
@if (type-of($consequent) != number) {
|
||||
@error "`#{$consequent}` needs to be a number."
|
||||
}
|
||||
|
||||
.u-#{$antecedent}\:#{$consequent}::before {
|
||||
padding-bottom: math.div($consequent, $antecedent) * 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-enable */
|
||||
@@ -37,8 +37,9 @@ $spacing-properties: (
|
||||
|
||||
$spacing-sizes: (
|
||||
null: $unit,
|
||||
'-double': $unit * 2,
|
||||
'-small': $unit-small,
|
||||
'-none': 0
|
||||
'-none': 0px
|
||||
) !default;
|
||||
|
||||
@each $property-namespace, $property in $spacing-properties {
|
||||
@@ -46,7 +47,7 @@ $spacing-sizes: (
|
||||
@each $size-namespace, $size in $spacing-sizes {
|
||||
.u-#{$property-namespace}#{$direction-namespace}#{$size-namespace} {
|
||||
@each $direction in $direction-rules {
|
||||
#{$property}#{$direction}: $size !important;
|
||||
#{$property}#{$direction}: rem($size) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// Utilities / States
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* ARIA roles display visual cursor hints
|
||||
*/
|
||||
// ARIA roles display visual cursor hints
|
||||
|
||||
[aria-busy="true"] {
|
||||
cursor: progress;
|
||||
}
|
||||
@@ -17,9 +16,7 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control visibility without affecting flow.
|
||||
*/
|
||||
// Control visibility without affecting flow.
|
||||
|
||||
.is-visible {
|
||||
visibility: visible !important;
|
||||
@@ -31,9 +28,7 @@
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely remove from the flow and screen readers.
|
||||
*/
|
||||
// Completely remove from the flow and screen readers.
|
||||
|
||||
.is-hidden {
|
||||
@include u-hidden;
|
||||
@@ -56,29 +51,27 @@
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
|
||||
//
|
||||
// .is-hidden\@from-large {
|
||||
// @media (min-width: $from-large) {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Display a hidden-by-default element.
|
||||
// */
|
||||
|
||||
// // Display a hidden-by-default element.
|
||||
//
|
||||
// .is-shown {
|
||||
// @include u-shown;
|
||||
// }
|
||||
|
||||
//
|
||||
// table.is-shown {
|
||||
// display: table !important;
|
||||
// }
|
||||
|
||||
//
|
||||
// tr.is-shown {
|
||||
// display: table-row !important;
|
||||
// }
|
||||
|
||||
//
|
||||
// td.is-shown,
|
||||
// th.is-shown {
|
||||
// display: table-cell !important;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
////
|
||||
/// @link https://github.com/inuitcss/inuitcss/blob/6eb574f/utilities/_utilities.widths.scss
|
||||
////
|
||||
|
||||
///
|
||||
///
|
||||
/// Which fractions would you like in your grid system(s)?
|
||||
/// By default, the boilerplate provides fractions of one whole, halves, thirds,
|
||||
/// quarters, and fifths, e.g.:
|
||||
@@ -15,6 +15,14 @@
|
||||
/// .u-2/5
|
||||
/// .u-3/4
|
||||
/// .u-2/3
|
||||
////
|
||||
|
||||
$widths-fractions: 1 2 3 4 5 !default;
|
||||
|
||||
@include widths($widths-fractions);
|
||||
|
||||
.u-1\/2\@from-small {
|
||||
@media (min-width: $from-small) {
|
||||
width: span(1/2);
|
||||
}
|
||||
}
|
||||
|
||||
11
build/build.js
Normal file
11
build/build.js
Normal file
@@ -0,0 +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 bumpVersions from './tasks/versions.js';
|
||||
|
||||
concatFiles();
|
||||
compileScripts();
|
||||
compileStyles();
|
||||
compileSVGs();
|
||||
bumpVersions();
|
||||
@@ -1,14 +0,0 @@
|
||||
import gulp from 'gulp';
|
||||
import gulpConcat from 'gulp-concat';
|
||||
import paths from '../mconfig.json';
|
||||
|
||||
function concat() {
|
||||
return gulp
|
||||
.src([
|
||||
`${paths.scripts.vendors.src}*.js`
|
||||
])
|
||||
.pipe(gulpConcat(`${paths.scripts.vendors.main}.js`))
|
||||
.pipe(gulp.dest(paths.scripts.dest));
|
||||
}
|
||||
|
||||
export default concat;
|
||||
@@ -1,14 +0,0 @@
|
||||
import gulp from 'gulp';
|
||||
import paths from '../mconfig.json';
|
||||
import error from './error.js';
|
||||
|
||||
function copy() {
|
||||
return gulp
|
||||
.src([`./node_modules/locomotive-scroll/assets/scripts/scroll/vendors/*`])
|
||||
.on('error', function(err) {
|
||||
error(this, err);
|
||||
})
|
||||
.pipe(gulp.dest(`${paths.scripts.src}/scroll/vendors`));
|
||||
}
|
||||
|
||||
export default copy;
|
||||
25
build/helpers/config.js
Normal file
25
build/helpers/config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file Provides simple user configuration options.
|
||||
*/
|
||||
|
||||
import loconfig from '../../loconfig.json' assert { type: 'json' };
|
||||
import { merge } from '../utils/index.js';
|
||||
|
||||
let usrconfig;
|
||||
|
||||
try {
|
||||
usrconfig = await import('../../loconfig.local.json', {
|
||||
assert: { type: 'json' },
|
||||
});
|
||||
usrconfig = usrconfig.default;
|
||||
|
||||
merge(loconfig, usrconfig);
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
export default loconfig;
|
||||
|
||||
export {
|
||||
loconfig,
|
||||
};
|
||||
162
build/helpers/glob.js
Normal file
162
build/helpers/glob.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @file Retrieve the first available glob library.
|
||||
*
|
||||
* Note that options vary between libraries.
|
||||
*
|
||||
* Candidates:
|
||||
*
|
||||
* - {@link https://npmjs.com/package/tiny-glob tiny-glob} [1][5][6]
|
||||
* - {@link https://npmjs.com/package/globby globby} [2][5]
|
||||
* - {@link https://npmjs.com/package/fast-glob fast-glob} [3]
|
||||
* - {@link https://npmjs.com/package/glob glob} [1][4][5]
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - [1] The library's function accepts only a single pattern.
|
||||
* - [2] The library's function accepts only an array of patterns.
|
||||
* - [3] The library's function accepts either a single pattern
|
||||
* or an array of patterns.
|
||||
* - [4] The library's function does not return a Promise but will be
|
||||
* wrapped in a function that does return a Promise.
|
||||
* - [5] The library's function will be wrapped in a function that
|
||||
* supports a single pattern and an array of patterns.
|
||||
* - [6] The library's function returns files and directories but will be
|
||||
* preconfigured to return only files.
|
||||
*/
|
||||
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
/**
|
||||
* @callback GlobFn
|
||||
*
|
||||
* @param {string|string[]} patterns - A string pattern
|
||||
* or an array of string patterns.
|
||||
* @param {object} options
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} GlobOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {GlobFn|undefined} The discovered glob function.
|
||||
*/
|
||||
let glob;
|
||||
|
||||
/**
|
||||
* @type {string[]} A list of packages to attempt import.
|
||||
*/
|
||||
const candidates = [
|
||||
'tiny-glob',
|
||||
'globby',
|
||||
'fast-glob',
|
||||
'glob',
|
||||
];
|
||||
|
||||
try {
|
||||
glob = await importGlob();
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} Whether a glob function was discovered (TRUE) or not (FALSE).
|
||||
*/
|
||||
const supportsGlob = (typeof glob === 'function');
|
||||
|
||||
/**
|
||||
* Imports the first available glob function.
|
||||
*
|
||||
* @throws {TypeError} If no glob library was found.
|
||||
*
|
||||
* @returns {GlobFn}
|
||||
*/
|
||||
async function importGlob() {
|
||||
for (let name of candidates) {
|
||||
try {
|
||||
let globModule = await import(name);
|
||||
|
||||
if (typeof globModule.default !== 'function') {
|
||||
throw new TypeError(`Expected ${name} to be a function`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the function to ensure
|
||||
* a common pattern.
|
||||
*/
|
||||
switch (name) {
|
||||
case 'tiny-glob':
|
||||
/** [1][5] */
|
||||
return createArrayableGlob(
|
||||
/** [6] */
|
||||
createPresetGlob(globModule.default, {
|
||||
filesOnly: true
|
||||
})
|
||||
);
|
||||
|
||||
case 'globby':
|
||||
/** [2][5] - If `patterns` is a string, wraps into an array. */
|
||||
return (patterns, options) => globModule.default([].concat(patterns), options);
|
||||
|
||||
case 'glob':
|
||||
/** [1][5] */
|
||||
return createArrayableGlob(
|
||||
/** [4] */
|
||||
promisify(globModule.default)
|
||||
);
|
||||
|
||||
default:
|
||||
return globModule.default;
|
||||
}
|
||||
} catch (err) {
|
||||
// swallow this error; skip to the next candidate.
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
`No glob library was found, expected one of: ${candidates.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper function for the glob function
|
||||
* to provide support for arrays of patterns.
|
||||
*
|
||||
* @param {function} globFn - The glob function.
|
||||
*
|
||||
* @returns {GlobFn}
|
||||
*/
|
||||
function createArrayableGlob(globFn) {
|
||||
return (patterns, options) => {
|
||||
/** [2] If `patterns` is a string, wraps into an array. */
|
||||
patterns = [].concat(patterns);
|
||||
|
||||
const globs = patterns.map((pattern) => globFn(pattern, options));
|
||||
|
||||
return Promise.all(globs).then((files) => {
|
||||
return [].concat.apply([], files);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper function for the glob function
|
||||
* to define new default options.
|
||||
*
|
||||
* @param {function} globFn - The glob function.
|
||||
* @param {GlobOptions} presets - The glob function options to preset.
|
||||
*
|
||||
* @returns {GlobFn}
|
||||
*/
|
||||
function createPresetGlob(globFn, presets) {
|
||||
return (patterns, options) => globFn(patterns, Object.assign({}, presets, options));
|
||||
}
|
||||
|
||||
export default glob;
|
||||
|
||||
export {
|
||||
glob,
|
||||
supportsGlob,
|
||||
};
|
||||
61
build/helpers/message.js
Normal file
61
build/helpers/message.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file Provides a decorator for console messages.
|
||||
*/
|
||||
|
||||
import kleur from 'kleur';
|
||||
|
||||
/**
|
||||
* Outputs a message to the console.
|
||||
*
|
||||
* @param {string} text - The message to output.
|
||||
* @param {string} [type] - The type of message.
|
||||
* @param {string} [timerID] - The console time label to output.
|
||||
*/
|
||||
function message(text, type, timerID) {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
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;
|
||||
|
||||
case 'error':
|
||||
console.log('❌ ', kleur.bgRed().black(text));
|
||||
break;
|
||||
|
||||
case 'warning':
|
||||
console.log('⚠️ ', kleur.bgYellow().black(text));
|
||||
break;
|
||||
|
||||
case 'waiting':
|
||||
console.log('⏱ ', kleur.blue().italic(text));
|
||||
|
||||
if (timerID != null) {
|
||||
console.timeLog(timerID);
|
||||
timerID = null;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(text);
|
||||
break;
|
||||
}
|
||||
|
||||
if (timerID != null) {
|
||||
console.timeEnd(timerID);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
export default message;
|
||||
|
||||
export {
|
||||
message,
|
||||
};
|
||||
51
build/helpers/notification.js
Normal file
51
build/helpers/notification.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file Provides a decorator for cross-platform notification.
|
||||
*/
|
||||
|
||||
import notifier from 'node-notifier';
|
||||
|
||||
/**
|
||||
* Sends a cross-platform native notification.
|
||||
*
|
||||
* Wraps around node-notifier to assign default values.
|
||||
*
|
||||
* @param {string|object} options - The notification options or a message.
|
||||
* @param {string} options.title - The notification title.
|
||||
* @param {string} options.message - The notification message.
|
||||
* @param {string} options.icon - The notification icon.
|
||||
* @param {function} callback - The notification callback.
|
||||
* @return {void}
|
||||
*/
|
||||
function notification(options, callback) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
message: options
|
||||
};
|
||||
} else if (!options.title && !options.message) {
|
||||
throw new TypeError(
|
||||
'Notification expects at least a \'message\' parameter'
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof options.icon === 'undefined') {
|
||||
options.icon = 'https://user-images.githubusercontent.com/4596862/54868065-c2aea200-4d5e-11e9-9ce3-e0013c15f48c.png';
|
||||
}
|
||||
|
||||
// If notification does not use a callback,
|
||||
// shorten the wait before timing out.
|
||||
if (typeof callback === 'undefined') {
|
||||
if (typeof options.wait === 'undefined') {
|
||||
if (typeof options.timeout === 'undefined') {
|
||||
options.timeout = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifier.notify(options, callback);
|
||||
}
|
||||
|
||||
export default notification;
|
||||
|
||||
export {
|
||||
notification,
|
||||
};
|
||||
139
build/helpers/postcss.js
Normal file
139
build/helpers/postcss.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @file If available, returns the PostCSS Processor creator and
|
||||
* any the Autoprefixer PostCSS plugin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('autoprefixer').autoprefixer.Options} AutoprefixerOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('postcss').AcceptedPlugin} AcceptedPlugin
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('postcss').Postcss} Postcss
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('postcss').ProcessOptions} ProcessOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('postcss').Processor} Processor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {AcceptedPlugin[]} PluginList
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object<string, AcceptedPlugin>} PluginMap
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {PluginList|PluginMap} PluginCollection
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} PostCSSOptions
|
||||
*
|
||||
* @property {ProcessOptions} processor - The `Processor#process()` options.
|
||||
* @property {AutoprefixerOptions} autoprefixer - The `autoprefixer()` options.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Postcss|undefined} postcss - The discovered PostCSS function.
|
||||
* @type {AcceptedPlugin|undefined} autoprefixer - The discovered Autoprefixer function.
|
||||
*/
|
||||
let postcss, autoprefixer;
|
||||
|
||||
try {
|
||||
postcss = await import('postcss');
|
||||
postcss = postcss.default;
|
||||
|
||||
autoprefixer = await import('autoprefixer');
|
||||
autoprefixer = autoprefixer.default;
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} Whether PostCSS was discovered (TRUE) or not (FALSE).
|
||||
*/
|
||||
const supportsPostCSS = (typeof postcss === 'function');
|
||||
|
||||
/**
|
||||
* @type {PluginList} A list of supported plugins.
|
||||
*/
|
||||
const pluginsList = [
|
||||
autoprefixer,
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {PluginMap} A map of supported plugins.
|
||||
*/
|
||||
const pluginsMap = {
|
||||
'autoprefixer': autoprefixer,
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to create a PostCSS Processor with the given plugins and options.
|
||||
*
|
||||
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
|
||||
* If a map of plugins, the plugin name looks up `options`.
|
||||
* @param {PostCSSOptions} options - The PostCSS wrapper options.
|
||||
*
|
||||
* @returns {Processor|null}
|
||||
*/
|
||||
function createProcessor(pluginsListOrMap, options)
|
||||
{
|
||||
if (!postcss) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const plugins = parsePlugins(pluginsListOrMap, options);
|
||||
|
||||
return postcss(plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the PostCSS plugins and options.
|
||||
*
|
||||
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
|
||||
* If a map of plugins, the plugin name looks up `options`.
|
||||
* @param {PostCSSOptions} options - The PostCSS wrapper options.
|
||||
*
|
||||
* @returns {PluginList}
|
||||
*/
|
||||
function parsePlugins(pluginsListOrMap, options)
|
||||
{
|
||||
if (Array.isArray(pluginsListOrMap)) {
|
||||
return pluginsListOrMap;
|
||||
}
|
||||
|
||||
/** @type {PluginList} */
|
||||
const plugins = [];
|
||||
|
||||
for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
|
||||
if (name in options) {
|
||||
plugin = plugin[name](options[name]);
|
||||
}
|
||||
|
||||
plugins.push(plugin);
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
export default postcss;
|
||||
|
||||
export {
|
||||
autoprefixer,
|
||||
createProcessor,
|
||||
parsePlugins,
|
||||
pluginsList,
|
||||
pluginsMap,
|
||||
postcss,
|
||||
supportsPostCSS,
|
||||
};
|
||||
105
build/helpers/template.js
Normal file
105
build/helpers/template.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @file Provides simple template tags.
|
||||
*/
|
||||
|
||||
import loconfig from './config.js';
|
||||
import {
|
||||
escapeRegExp,
|
||||
flatten
|
||||
} from '../utils/index.js';
|
||||
|
||||
const templateData = flatten({
|
||||
paths: loconfig.paths
|
||||
});
|
||||
|
||||
/**
|
||||
* Replaces all template tags from a map of keys and values.
|
||||
*
|
||||
* If replacement pairs contain a mix of substrings, regular expressions,
|
||||
* and functions, regular expressions are executed last.
|
||||
*
|
||||
* @param {*} input - The value being searched and replaced on.
|
||||
* If input is, or contains, a string, tags will be resolved.
|
||||
* If input is, or contains, an object, it is mutated directly.
|
||||
* If input is, or contains, an array, a shallow copy is returned.
|
||||
* Otherwise, the value is left intact.
|
||||
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
||||
* @return {*} Returns the transformed value.
|
||||
*/
|
||||
function resolve(input, data = templateData) {
|
||||
switch (typeof input) {
|
||||
case 'string': {
|
||||
return resolveValue(input, data);
|
||||
}
|
||||
|
||||
case 'object': {
|
||||
if (input == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((value) => resolve(value, data));
|
||||
} else {
|
||||
for (const key in input) {
|
||||
input[key] = resolve(input[key], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all template tags in a string from a map of keys and values.
|
||||
*
|
||||
* If replacement pairs contain a mix of substrings, regular expressions,
|
||||
* and functions, regular expressions are executed last.
|
||||
*
|
||||
* @param {string} input - The string being searched and replaced on.
|
||||
* @param {object} [data] - An object in the form `{ 'from': 'to', … }`.
|
||||
* @return {string} Returns the translated string.
|
||||
*/
|
||||
function resolveValue(input, data = templateData) {
|
||||
const tags = [];
|
||||
|
||||
if (data !== templateData) {
|
||||
data = flatten(data);
|
||||
}
|
||||
|
||||
for (let tag in data) {
|
||||
tags.push(escapeRegExp(tag));
|
||||
}
|
||||
|
||||
if (tags.length === 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
const search = new RegExp('\\{%\\s*(' + tags.join('|') + ')\\s*%\\}', 'g');
|
||||
return input.replace(search, (match, key) => {
|
||||
let value = data[key];
|
||||
|
||||
switch (typeof value) {
|
||||
case 'function':
|
||||
/**
|
||||
* Retrieve the offset of the matched substring `args[0]`
|
||||
* and the whole string being examined `args[1]`.
|
||||
*/
|
||||
let args = Array.prototype.slice.call(arguments, -2);
|
||||
return value.call(data, match, args[0], args[1]);
|
||||
|
||||
case 'string':
|
||||
case 'number':
|
||||
return value;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
export default resolve;
|
||||
|
||||
export {
|
||||
resolve,
|
||||
resolveValue,
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import browserSync from 'browser-sync';
|
||||
import paths from '../mconfig.json';
|
||||
|
||||
export const server = browserSync.create();
|
||||
|
||||
function serve(done) {
|
||||
server.init({
|
||||
notify: false,
|
||||
proxy: paths.url,
|
||||
host: paths.url,
|
||||
open: 'external'
|
||||
});
|
||||
done();
|
||||
}
|
||||
|
||||
export default serve;
|
||||
144
build/tasks/concats.js
Normal file
144
build/tasks/concats.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import loconfig from '../helpers/config.js';
|
||||
import glob, { supportsGlob } from '../helpers/glob.js';
|
||||
import message from '../helpers/message.js';
|
||||
import notification from '../helpers/notification.js';
|
||||
import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import concat from 'concat';
|
||||
import {
|
||||
basename,
|
||||
normalize,
|
||||
} from 'node:path';
|
||||
|
||||
/**
|
||||
* @const {object} defaultGlobOptions - The default shared glob options.
|
||||
* @const {object} developmentGlobOptions - The predefined glob options for development.
|
||||
* @const {object} productionGlobOptions - The predefined glob options for production.
|
||||
*/
|
||||
export const defaultGlobOptions = {
|
||||
};
|
||||
export const developmentGlobOptions = Object.assign({}, defaultGlobOptions);
|
||||
export const productionGlobOptions = Object.assign({}, defaultGlobOptions);
|
||||
|
||||
/**
|
||||
* @typedef {object} ConcatOptions
|
||||
* @property {boolean} removeDuplicates - Removes duplicate paths from
|
||||
* the array of matching files and folders.
|
||||
* Only the first occurrence of each path is kept.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @const {ConcatOptions} defaultConcatOptions - The default shared concatenation options.
|
||||
* @const {ConcatOptions} developmentConcatOptions - The predefined concatenation options for development.
|
||||
* @const {ConcatOptions} productionConcatOptions - The predefined concatenation options for production.
|
||||
*/
|
||||
export const defaultConcatOptions = {
|
||||
removeDuplicates: true,
|
||||
};
|
||||
export const developmentConcatOptions = Object.assign({}, defaultConcatOptions);
|
||||
export const productionConcatOptions = Object.assign({}, defaultConcatOptions);
|
||||
|
||||
/**
|
||||
* @const {object} developmentConcatFilesArgs - The predefined `concatFiles()` options for development.
|
||||
* @const {object} productionConcatFilesArgs - The predefined `concatFiles()` options for production.
|
||||
*/
|
||||
export const developmentConcatFilesArgs = [
|
||||
developmentGlobOptions,
|
||||
developmentConcatOptions,
|
||||
];
|
||||
export const productionConcatFilesArgs = [
|
||||
productionGlobOptions,
|
||||
productionConcatOptions,
|
||||
];
|
||||
|
||||
/**
|
||||
* Concatenates groups of files.
|
||||
*
|
||||
* @todo Add support for minification.
|
||||
*
|
||||
* @async
|
||||
* @param {object|boolean} [globOptions=null] - Customize the glob options.
|
||||
* If `null`, default production options are used.
|
||||
* If `false`, the glob function will be ignored.
|
||||
* @param {object} [concatOptions=null] - Customize the concatenation options.
|
||||
* If `null`, default production options are used.
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function concatFiles(globOptions = null, concatOptions = null) {
|
||||
if (supportsGlob) {
|
||||
if (globOptions == null) {
|
||||
globOptions = productionGlobOptions;
|
||||
} else if (
|
||||
globOptions !== false &&
|
||||
globOptions !== developmentGlobOptions &&
|
||||
globOptions !== productionGlobOptions
|
||||
) {
|
||||
globOptions = merge({}, defaultGlobOptions, globOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (concatOptions == null) {
|
||||
concatOptions = productionConcatOptions;
|
||||
} else if (
|
||||
concatOptions !== developmentConcatOptions &&
|
||||
concatOptions !== productionConcatOptions
|
||||
) {
|
||||
concatOptions = merge({}, defaultConcatOptions, concatOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {object} entry - The entrypoint to process.
|
||||
* @param {string[]} entry.includes - One or more paths to process.
|
||||
* @param {string} entry.outfile - The file to write to.
|
||||
* @param {?string} [entry.label] - The task label.
|
||||
* Defaults to the outfile name.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loconfig.tasks.concats.forEach(async ({
|
||||
includes,
|
||||
outfile,
|
||||
label = null
|
||||
}) => {
|
||||
if (!label) {
|
||||
label = basename(outfile || 'undefined');
|
||||
}
|
||||
|
||||
const timeLabel = `${label} concatenated in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
if (!Array.isArray(includes)) {
|
||||
includes = [ includes ];
|
||||
}
|
||||
|
||||
includes = resolve(includes);
|
||||
outfile = resolve(outfile);
|
||||
|
||||
if (supportsGlob && globOptions) {
|
||||
includes = await glob(includes, globOptions);
|
||||
}
|
||||
|
||||
if (concatOptions.removeDuplicates) {
|
||||
includes = includes.map((path) => normalize(path));
|
||||
includes = [ ...new Set(includes) ];
|
||||
}
|
||||
|
||||
await concat(includes, outfile);
|
||||
|
||||
if (includes.length) {
|
||||
message(`${label} concatenated`, 'success', timeLabel);
|
||||
} else {
|
||||
message(`${label} is empty`, 'notice', timeLabel);
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error concatenating ${label}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${label} concatenation failed 🚨`,
|
||||
message: `${err.name}: ${err.message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
113
build/tasks/scripts.js
Normal file
113
build/tasks/scripts.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import loconfig from '../helpers/config.js';
|
||||
import message from '../helpers/message.js';
|
||||
import notification from '../helpers/notification.js';
|
||||
import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import esbuild from 'esbuild';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
/**
|
||||
* @const {object} defaultESBuildOptions - The default shared ESBuild options.
|
||||
* @const {object} developmentESBuildOptions - The predefined ESBuild options for development.
|
||||
* @const {object} productionESBuildOptions - The predefined ESBuild options for production.
|
||||
*/
|
||||
export const defaultESBuildOptions = {
|
||||
bundle: true,
|
||||
color: true,
|
||||
sourcemap: true,
|
||||
target: [
|
||||
'es2015',
|
||||
],
|
||||
};
|
||||
export const developmentESBuildOptions = Object.assign({}, defaultESBuildOptions);
|
||||
export const productionESBuildOptions = Object.assign({}, defaultESBuildOptions, {
|
||||
logLevel: 'warning',
|
||||
minify: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* @const {object} developmentScriptsArgs - The predefined `compileScripts()` options for development.
|
||||
* @const {object} productionScriptsArgs - The predefined `compileScripts()` options for production.
|
||||
*/
|
||||
export const developmentScriptsArgs = [
|
||||
developmentESBuildOptions,
|
||||
];
|
||||
export const productionScriptsArgs = [
|
||||
productionESBuildOptions,
|
||||
];
|
||||
|
||||
/**
|
||||
* Bundles and minifies main JavaScript files.
|
||||
*
|
||||
* @async
|
||||
* @param {object} [esBuildOptions=null] - Customize the ESBuild build API options.
|
||||
* If `null`, default production options are used.
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileScripts(esBuildOptions = null) {
|
||||
if (esBuildOptions == null) {
|
||||
esBuildOptions = productionESBuildOptions;
|
||||
} else if (
|
||||
esBuildOptions !== developmentESBuildOptions &&
|
||||
esBuildOptions !== productionESBuildOptions
|
||||
) {
|
||||
esBuildOptions = merge({}, defaultESBuildOptions, esBuildOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {object} entry - The entrypoint to process.
|
||||
* @param {string[]} entry.includes - One or more paths to process.
|
||||
* @param {string} [entry.outdir] - The directory to write to.
|
||||
* @param {string} [entry.outfile] - The file to write to.
|
||||
* @param {?string} [entry.label] - The task label.
|
||||
* Defaults to the outdir or outfile name.
|
||||
* @throws {TypeError} If outdir and outfile are missing.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loconfig.tasks.scripts.forEach(async ({
|
||||
includes,
|
||||
outdir = '',
|
||||
outfile = '',
|
||||
label = null
|
||||
}) => {
|
||||
if (!label) {
|
||||
label = basename(outdir || outfile || 'undefined');
|
||||
}
|
||||
|
||||
const timeLabel = `${label} compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
if (!Array.isArray(includes)) {
|
||||
includes = [ includes ];
|
||||
}
|
||||
|
||||
includes = resolve(includes);
|
||||
|
||||
if (outdir) {
|
||||
outdir = resolve(outdir);
|
||||
} else if (outfile) {
|
||||
outfile = resolve(outfile);
|
||||
} else {
|
||||
throw new TypeError(
|
||||
'Expected \'outdir\' or \'outfile\''
|
||||
);
|
||||
}
|
||||
|
||||
await esbuild.build(Object.assign({}, esBuildOptions, {
|
||||
entryPoints: includes,
|
||||
outdir,
|
||||
outfile,
|
||||
}));
|
||||
|
||||
message(`${label} compiled`, 'success', timeLabel);
|
||||
} catch (err) {
|
||||
// errors managments (already done in esbuild)
|
||||
notification({
|
||||
title: `${label} compilation failed 🚨`,
|
||||
message: `${err.errors[0].text} in ${err.errors[0].location.file} line ${err.errors[0].location.line}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
241
build/tasks/styles.js
Normal file
241
build/tasks/styles.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import loconfig from '../helpers/config.js';
|
||||
import message from '../helpers/message.js';
|
||||
import notification from '../helpers/notification.js';
|
||||
import {
|
||||
createProcessor,
|
||||
pluginsMap as postcssPluginsMap,
|
||||
supportsPostCSS
|
||||
} from '../helpers/postcss.js';
|
||||
import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { basename } from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
import sass from 'sass';
|
||||
import { PurgeCSS } from 'purgecss';
|
||||
|
||||
const sassRender = promisify(sass.render);
|
||||
|
||||
let postcssProcessor;
|
||||
|
||||
/**
|
||||
* @const {object} defaultSassOptions - The default shared Sass options.
|
||||
* @const {object} developmentSassOptions - The predefined Sass options for development.
|
||||
* @const {object} productionSassOptions - The predefined Sass options for production.
|
||||
*/
|
||||
export const defaultSassOptions = {
|
||||
omitSourceMapUrl: true,
|
||||
sourceMap: true,
|
||||
sourceMapContents: true,
|
||||
};
|
||||
|
||||
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
|
||||
outputStyle: 'expanded',
|
||||
});
|
||||
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
|
||||
outputStyle: 'compressed',
|
||||
});
|
||||
|
||||
/**
|
||||
* @const {object} defaultPostCSSOptions - The default shared PostCSS options.
|
||||
* @const {object} developmentPostCSSOptions - The predefined PostCSS options for development.
|
||||
* @const {object} productionPostCSSOptions - The predefined PostCSS options for production.
|
||||
*/
|
||||
export const defaultPostCSSOptions = {
|
||||
processor: {
|
||||
map: {
|
||||
annotation: false,
|
||||
inline: false,
|
||||
sourcesContent: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const developmentPostCSSOptions = Object.assign({}, defaultPostCSSOptions);
|
||||
export const productionPostCSSOptions = Object.assign({}, defaultPostCSSOptions);
|
||||
|
||||
/**
|
||||
* @const {object|boolean} developmentStylesArgs - The predefined `compileStyles()` options for development.
|
||||
* @const {object|boolean} productionStylesArgs - The predefined `compileStyles()` options for production.
|
||||
*/
|
||||
export const developmentStylesArgs = [
|
||||
developmentSassOptions,
|
||||
developmentPostCSSOptions,
|
||||
];
|
||||
export const productionStylesArgs = [
|
||||
productionSassOptions,
|
||||
productionPostCSSOptions,
|
||||
];
|
||||
|
||||
/**
|
||||
* Compiles and minifies main Sass files to CSS.
|
||||
*
|
||||
* @todo Add deep merge of `postcssOptions` to better support customization
|
||||
* of default processor options.
|
||||
*
|
||||
* @async
|
||||
* @param {object} [sassOptions=null] - Customize the Sass render API options.
|
||||
* If `null`, default production options are used.
|
||||
* @param {object|boolean} [postcssOptions=null] - Customize the PostCSS processor API options.
|
||||
* If `null`, default production options are used.
|
||||
* If `false`, PostCSS processing will be ignored.
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileStyles(sassOptions = null, postcssOptions = null) {
|
||||
if (sassOptions == null) {
|
||||
sassOptions = productionSassOptions;
|
||||
} else if (
|
||||
sassOptions !== developmentSassOptions &&
|
||||
sassOptions !== productionSassOptions
|
||||
) {
|
||||
sassOptions = merge({}, defaultSassOptions, sassOptions);
|
||||
}
|
||||
|
||||
if (supportsPostCSS) {
|
||||
if (postcssOptions == null) {
|
||||
postcssOptions = productionPostCSSOptions;
|
||||
} else if (
|
||||
postcssOptions !== false &&
|
||||
postcssOptions !== developmentPostCSSOptions &&
|
||||
postcssOptions !== productionPostCSSOptions
|
||||
) {
|
||||
postcssOptions = merge({}, defaultPostCSSOptions, postcssOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {object} entry - The entrypoint to process.
|
||||
* @param {string[]} entry.infile - The file to process.
|
||||
* @param {string} entry.outfile - The file to write to.
|
||||
* @param {?string} [entry.label] - The task label.
|
||||
* Defaults to the outfile name.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loconfig.tasks.styles.forEach(async ({
|
||||
infile,
|
||||
outfile,
|
||||
label = null
|
||||
}) => {
|
||||
const filestem = basename((outfile || 'undefined'), '.css');
|
||||
|
||||
const timeLabel = `${label || `${filestem}.css`} compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
infile = resolve(infile);
|
||||
outfile = resolve(outfile);
|
||||
|
||||
let result = await sassRender(Object.assign({}, sassOptions, {
|
||||
file: infile,
|
||||
outFile: outfile,
|
||||
}));
|
||||
|
||||
if (supportsPostCSS && postcssOptions) {
|
||||
if (typeof postcssProcessor === 'undefined') {
|
||||
postcssProcessor = createProcessor(
|
||||
postcssPluginsMap,
|
||||
postcssOptions
|
||||
);
|
||||
}
|
||||
|
||||
result = await postcssProcessor.process(
|
||||
result.css,
|
||||
Object.assign({}, postcssOptions.processor, {
|
||||
from: outfile,
|
||||
to: outfile,
|
||||
})
|
||||
);
|
||||
|
||||
if (result.warnings) {
|
||||
const warnings = result.warnings();
|
||||
if (warnings.length) {
|
||||
message(`Error processing ${label || `${filestem}.css`}`, 'warning');
|
||||
warnings.forEach((warn) => {
|
||||
message(warn.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
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);
|
||||
} else {
|
||||
message(`${label || `${filestem}.css`} is empty`, 'notice', timeLabel);
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error compiling ${label || `${filestem}.css`}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${label || `${filestem}.css`} save failed 🚨`,
|
||||
message: `Could not save stylesheet to ${label || `${filestem}.css`}`
|
||||
});
|
||||
}
|
||||
|
||||
if (result.map) {
|
||||
try {
|
||||
await writeFile(outfile + '.map', result.map.toString());
|
||||
} catch (err) {
|
||||
message(`Error compiling ${label || `${filestem}.css.map`}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${label || `${filestem}.css.map`} save failed 🚨`,
|
||||
message: `Could not save sourcemap to ${label || `${filestem}.css.map`}`
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error compiling ${label || `${filestem}.scss`}`, 'error');
|
||||
message(err.formatted || err);
|
||||
|
||||
notification({
|
||||
title: `${label || `${filestem}.scss`} compilation failed 🚨`,
|
||||
message: (err.formatted || `${err.name}: ${err.message}`)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
95
build/tasks/svgs.js
Normal file
95
build/tasks/svgs.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import loconfig from '../helpers/config.js';
|
||||
import message from '../helpers/message.js';
|
||||
import notification from '../helpers/notification.js';
|
||||
import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import { basename } from 'node:path';
|
||||
import mixer from 'svg-mixer';
|
||||
|
||||
/**
|
||||
* @const {object} defaultMixerOptions - The default shared Mixer options.
|
||||
* @const {object} developmentMixerOptions - The predefined Mixer options for development.
|
||||
* @const {object} productionMixerOptions - The predefined Mixer options for production.
|
||||
*/
|
||||
export const defaultMixerOptions = {
|
||||
spriteConfig: {
|
||||
usages: false,
|
||||
},
|
||||
};
|
||||
export const developmentMixerOptions = Object.assign({}, defaultMixerOptions);
|
||||
export const productionMixerOptions = Object.assign({}, defaultMixerOptions);
|
||||
|
||||
/**
|
||||
* @const {object} developmentSVGsArgs - The predefined `compileSVGs()` options for development.
|
||||
* @const {object} productionSVGsArgs - The predefined `compileSVGs()` options for production.
|
||||
*/
|
||||
export const developmentSVGsArgs = [
|
||||
developmentMixerOptions,
|
||||
];
|
||||
export const productionSVGsArgs = [
|
||||
productionMixerOptions,
|
||||
];
|
||||
|
||||
/**
|
||||
* Generates and transforms SVG spritesheets.
|
||||
*
|
||||
* @async
|
||||
* @param {object} [mixerOptions=null] - Customize the Mixer API options.
|
||||
* If `null`, default production options are used.
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileSVGs(mixerOptions = null) {
|
||||
if (mixerOptions == null) {
|
||||
mixerOptions = productionMixerOptions;
|
||||
} else if (
|
||||
mixerOptions !== developmentMixerOptions &&
|
||||
mixerOptions !== productionMixerOptions
|
||||
) {
|
||||
mixerOptions = merge({}, defaultMixerOptions, mixerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {object} entry - The entrypoint to process.
|
||||
* @param {string[]} entry.includes - One or more paths to process.
|
||||
* @param {string} entry.outfile - The file to write to.
|
||||
* @param {?string} [entry.label] - The task label.
|
||||
* Defaults to the outfile name.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loconfig.tasks.svgs.forEach(async ({
|
||||
includes,
|
||||
outfile,
|
||||
label = null
|
||||
}) => {
|
||||
if (!label) {
|
||||
label = basename(outfile || 'undefined');
|
||||
}
|
||||
|
||||
const timeLabel = `${label} compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
if (!Array.isArray(includes)) {
|
||||
includes = [ includes ];
|
||||
}
|
||||
|
||||
includes = resolve(includes);
|
||||
outfile = resolve(outfile);
|
||||
|
||||
const result = await mixer(includes, mixerOptions);
|
||||
|
||||
await result.write(outfile);
|
||||
|
||||
message(`${label} compiled`, 'success', timeLabel);
|
||||
} catch (err) {
|
||||
message(`Error compiling ${label}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${label} compilation failed 🚨`,
|
||||
message: `${err.name}: ${err.message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
327
build/tasks/versions.js
Normal file
327
build/tasks/versions.js
Normal file
@@ -0,0 +1,327 @@
|
||||
import loconfig from '../helpers/config.js';
|
||||
import message from '../helpers/message.js';
|
||||
import resolve from '../helpers/template.js';
|
||||
import { merge } from '../utils/index.js';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import events from 'node:events';
|
||||
import {
|
||||
createReadStream,
|
||||
createWriteStream,
|
||||
} from 'node:fs';
|
||||
import {
|
||||
mkdir,
|
||||
rename,
|
||||
rm,
|
||||
readFile,
|
||||
writeFile,
|
||||
} from 'node:fs/promises';
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
} from 'node:path';
|
||||
import readline from 'node:readline';
|
||||
|
||||
/**
|
||||
* @typedef {object} VersionOptions
|
||||
* @property {string|number|null} prettyPrint - A string or number to insert
|
||||
* white space into the output JSON string for readability purposes.
|
||||
* @property {string} versionFormat - The version number format.
|
||||
* @property {string|RegExp} versionKey - Either:
|
||||
* - A string representing the JSON field name assign the version number to.
|
||||
*
|
||||
* Explicit:
|
||||
*
|
||||
* ```json
|
||||
* "key": "json:version"
|
||||
* ```
|
||||
*
|
||||
* Implicit:
|
||||
*
|
||||
* ```json
|
||||
* "key": "version"
|
||||
* ```
|
||||
*
|
||||
* - A `RegExp` object or regular expression string prefixed with `regexp:`.
|
||||
*
|
||||
* ```json
|
||||
* "key": "regexp:(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)"
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* key: new RegExp('(?<=^const ASSETS_VERSION = ')(?<version>\\d+)(?=';$)')
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* key: /(?<=^const ASSETS_VERSION = ')(?<version>\d+)(?=';$)/
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* @const {VersionOptions} defaultVersionOptions - The default shared version options.
|
||||
* @const {VersionOptions} developmentVersionOptions - The predefined version options for development.
|
||||
* @const {VersionOptions} productionVersionOptions - The predefined version options for production.
|
||||
*/
|
||||
export const defaultVersionOptions = {
|
||||
prettyPrint: 4,
|
||||
versionFormat: 'timestamp',
|
||||
versionKey: 'version',
|
||||
};
|
||||
export const developmentVersionOptions = Object.assign({}, defaultVersionOptions);
|
||||
export const productionVersionOptions = Object.assign({}, defaultVersionOptions);
|
||||
|
||||
/**
|
||||
* @const {object} developmentVersionFilesArgs - The predefined `bumpVersion()` options for development.
|
||||
* @const {object} productionVersionFilesArgs - The predefined `bumpVersion()` options for production.
|
||||
*/
|
||||
export const developmentVersionFilesArgs = [
|
||||
developmentVersionOptions,
|
||||
];
|
||||
export const productionVersionFilesArgs = [
|
||||
productionVersionOptions,
|
||||
];
|
||||
|
||||
/**
|
||||
* Bumps version numbers in file.
|
||||
*
|
||||
* @async
|
||||
* @param {object} [versionOptions=null] - Customize the version options.
|
||||
* If `null`, default production options are used.
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function bumpVersions(versionOptions = null) {
|
||||
if (versionOptions == null) {
|
||||
versionOptions = productionVersionOptions;
|
||||
} else if (
|
||||
versionOptions !== developmentVersionOptions &&
|
||||
versionOptions !== productionVersionOptions
|
||||
) {
|
||||
versionOptions = merge({}, defaultVersionOptions, versionOptions);
|
||||
}
|
||||
|
||||
const queue = new Map();
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {object} entry - The entrypoint to process.
|
||||
* @param {string} entry.outfile - The file to write to.
|
||||
* @param {?string} [entry.label] - The task label.
|
||||
* Defaults to the outfile name.
|
||||
* @param {?string} [entry.format] - The version number format.
|
||||
* @param {?string} [entry.key] - The JSON field name assign the version number to.
|
||||
* @param {?string|number} [entry.pretty] - The white space to use to format the JSON file.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loconfig.tasks.versions.forEach(({
|
||||
outfile,
|
||||
label = null,
|
||||
...options
|
||||
}) => {
|
||||
if (!label) {
|
||||
label = basename(outfile || 'undefined');
|
||||
}
|
||||
|
||||
options.pretty = (options.pretty ?? versionOptions.prettyPrint);
|
||||
options.format = (options.format ?? versionOptions.versionFormat);
|
||||
options.key = (options.key ?? versionOptions.versionKey);
|
||||
|
||||
if (queue.has(outfile)) {
|
||||
queue.get(outfile).then(() => handleBumpVersion(outfile, label, options));
|
||||
} else {
|
||||
queue.set(outfile, handleBumpVersion(outfile, label, options));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a formatted version number or string.
|
||||
*
|
||||
* @param {string} format - The version format.
|
||||
* @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"'
|
||||
);
|
||||
}
|
||||
115
build/utils/index.js
Normal file
115
build/utils/index.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @file Provides generic functions and constants.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {RegExp} - Match all special characters.
|
||||
*/
|
||||
const regexUnescaped = /[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g;
|
||||
|
||||
/**
|
||||
* Quotes regular expression characters.
|
||||
*
|
||||
* @param {string} str - The input string.
|
||||
* @return {string} Returns the quoted (escaped) string.
|
||||
*/
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(regexUnescaped, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object with all nested object properties
|
||||
* concatenated into it recursively.
|
||||
*
|
||||
* Nested keys are flattened into a property path:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* a: {
|
||||
* b: {
|
||||
* c: 1
|
||||
* }
|
||||
* },
|
||||
* d: 1
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* "a.b.c": 1,
|
||||
* "d": 1
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {object} input - The object to flatten.
|
||||
* @param {string} prefix - The parent key prefix.
|
||||
* @param {object} target - The object that will receive the flattened properties.
|
||||
* @return {object} Returns the `target` object.
|
||||
*/
|
||||
function flatten(input, prefix, target = {}) {
|
||||
for (const key in input) {
|
||||
const field = (prefix ? prefix + '.' + key : key);
|
||||
|
||||
if (isObjectLike(input[key])) {
|
||||
flatten(input[key], field, target);
|
||||
} else {
|
||||
target[field] = input[key];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the passed value is an `Object`.
|
||||
*
|
||||
* @param {*} value - The value to be checked.
|
||||
* @return {boolean} Returns `true` if the value is an `Object`,
|
||||
* otherwise `false`.
|
||||
*/
|
||||
function isObjectLike(value) {
|
||||
return (value != null && typeof value === 'object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object with all nested object properties
|
||||
* merged into it recursively.
|
||||
*
|
||||
* @param {object} target - The target object.
|
||||
* @param {object[]} ...sources - The source object(s).
|
||||
* @throws {TypeError} If the target and source are the same.
|
||||
* @return {object} Returns the `target` object.
|
||||
*/
|
||||
function merge(target, ...sources) {
|
||||
for (const source of sources) {
|
||||
if (target === source) {
|
||||
throw new TypeError(
|
||||
'Cannot merge, target and source are the same'
|
||||
);
|
||||
}
|
||||
|
||||
for (const key in source) {
|
||||
if (source[key] != null) {
|
||||
if (isObjectLike(source[key]) && isObjectLike(target[key])) {
|
||||
merge(target[key], source[key]);
|
||||
continue;
|
||||
} else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
|
||||
target[key] = target[key].concat(source[key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export {
|
||||
escapeRegExp,
|
||||
flatten,
|
||||
isObjectLike,
|
||||
merge,
|
||||
regexUnescaped,
|
||||
};
|
||||
209
build/watch.js
209
build/watch.js
@@ -1,22 +1,197 @@
|
||||
import gulp from 'gulp';
|
||||
import paths from '../mconfig.json';
|
||||
import styles from './styles.js';
|
||||
import scripts from './scripts.js';
|
||||
import svgs from './svgs.js';
|
||||
import concat from './concat.js';
|
||||
import { server } from './serve.js';
|
||||
import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js';
|
||||
import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
|
||||
import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
|
||||
import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
|
||||
import loconfig from './helpers/config.js';
|
||||
import message from './helpers/message.js';
|
||||
import notification from './helpers/notification.js';
|
||||
import resolve from './helpers/template.js';
|
||||
import { merge } from './utils/index.js';
|
||||
import browserSync from 'browser-sync';
|
||||
import { join } from 'node:path';
|
||||
|
||||
function watch() {
|
||||
gulp.watch(paths.styles.src, styles);
|
||||
gulp.watch(paths.scripts.src, gulp.series(scripts, reload));
|
||||
gulp.watch(paths.scripts.vendors.src, concat);
|
||||
gulp.watch(paths.views.src, reload);
|
||||
gulp.watch(paths.svgs.src, gulp.series(svgs, reload));
|
||||
// Match a URL protocol.
|
||||
const regexUrlStartsWithProtocol = /^[a-z0-9\-]:\/\//i;
|
||||
|
||||
// Build scripts, compile styles, concat files,
|
||||
// and generate spritesheets on first hit
|
||||
concatFiles(...developmentConcatFilesArgs);
|
||||
compileScripts(...developmentScriptsArgs);
|
||||
compileStyles(...developmentStylesArgs);
|
||||
compileSVGs(...developmentSVGsArgs);
|
||||
|
||||
// Create a new BrowserSync instance
|
||||
const server = browserSync.create();
|
||||
|
||||
// Start the BrowserSync server
|
||||
server.init(createServerOptions(loconfig), (err) => {
|
||||
if (err) {
|
||||
message('Error starting development server', 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: 'Development server failed',
|
||||
message: `${err.name}: ${err.message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
configureServer(server, loconfig);
|
||||
|
||||
/**
|
||||
* Configures the BrowserSync options.
|
||||
*
|
||||
* @param {BrowserSync} server - The BrowserSync API.
|
||||
* @param {object} loconfig - The project configset.
|
||||
* @param {object} loconfig.paths - The paths options.
|
||||
* @param {object} loconfig.tasks - The tasks options.
|
||||
* @return {void}
|
||||
*/
|
||||
function configureServer(server, { paths, tasks }) {
|
||||
const views = createViewsArray(paths.views);
|
||||
|
||||
// Reload on any changes to views or processed files
|
||||
server.watch(
|
||||
[
|
||||
...views,
|
||||
join(paths.scripts.dest, '*.js'),
|
||||
join(paths.styles.dest, '*.css'),
|
||||
join(paths.svgs.dest, '*.svg'),
|
||||
]
|
||||
).on('change', server.reload);
|
||||
|
||||
// Watch source scripts
|
||||
server.watch(
|
||||
[
|
||||
join(paths.scripts.src, '**/*.js'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileScripts(...developmentScriptsArgs);
|
||||
});
|
||||
|
||||
// Watch source concats
|
||||
server.watch(
|
||||
resolve(
|
||||
tasks.concats.reduce(
|
||||
(patterns, { includes }) => patterns.concat(includes),
|
||||
[]
|
||||
)
|
||||
)
|
||||
).on('change', () => {
|
||||
concatFiles(...developmentConcatFilesArgs);
|
||||
});
|
||||
|
||||
// Watch source styles
|
||||
server.watch(
|
||||
[
|
||||
join(paths.styles.src, '**/*.scss'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileStyles(...developmentStylesArgs);
|
||||
});
|
||||
|
||||
// Watch source SVGs
|
||||
server.watch(
|
||||
[
|
||||
join(paths.svgs.src, '*.svg'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileSVGs(...developmentSVGsArgs);
|
||||
});
|
||||
}
|
||||
|
||||
function reload(done) {
|
||||
server.reload();
|
||||
done();
|
||||
/**
|
||||
* Creates a new object with all the BrowserSync options.
|
||||
*
|
||||
* @param {object} loconfig - The project configset.
|
||||
* @param {object} loconfig.paths - The paths options.
|
||||
* @param {object} loconfig.server - The server options.
|
||||
* @return {object} Returns the server options.
|
||||
*/
|
||||
function createServerOptions({
|
||||
paths,
|
||||
server: options
|
||||
}) {
|
||||
const config = {
|
||||
open: false,
|
||||
notify: false,
|
||||
ghostMode: false
|
||||
};
|
||||
|
||||
// Resolve the URL for the BrowserSync server
|
||||
if (isNonEmptyString(paths.url)) {
|
||||
// Use proxy
|
||||
config.proxy = paths.url;
|
||||
} else if (isNonEmptyString(paths.dest)) {
|
||||
// Use base directory
|
||||
config.server = {
|
||||
baseDir: paths.dest
|
||||
};
|
||||
}
|
||||
|
||||
merge(config, resolve(options));
|
||||
|
||||
// If HTTPS is enabled, prepend `https://` to proxy URL
|
||||
if (options?.https) {
|
||||
if (isNonEmptyString(config.proxy?.target)) {
|
||||
config.proxy.target = prependSchemeToUrl(config.proxy.target, 'https');
|
||||
} else if (isNonEmptyString(config.proxy)) {
|
||||
config.proxy = prependSchemeToUrl(config.proxy, 'https');
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export default watch;
|
||||
/**
|
||||
* Creates a new array (shallow-copied) from the views configset.
|
||||
*
|
||||
* @param {*} views - The views configset.
|
||||
* @throws {TypeError} If views is invalid.
|
||||
* @return {array} Returns the views array.
|
||||
*/
|
||||
function createViewsArray(views) {
|
||||
if (Array.isArray(views)) {
|
||||
return Array.from(views);
|
||||
}
|
||||
|
||||
switch (typeof views) {
|
||||
case 'string':
|
||||
return [ views ];
|
||||
|
||||
case 'object':
|
||||
if (views != null) {
|
||||
return Object.values(views);
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
'Expected \'views\' to be a string, array, or object'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the scheme to the URL.
|
||||
*
|
||||
* @param {string} url - The URL to mutate.
|
||||
* @param {string} [scheme] - The URL scheme to prepend.
|
||||
* @return {string} Returns the mutated URL.
|
||||
*/
|
||||
function prependSchemeToUrl(url, scheme = 'http') {
|
||||
if (regexUrlStartsWithProtocol.test(url)) {
|
||||
return url.replace(regexUrlStartsWithProtocol, `${scheme}://`);
|
||||
}
|
||||
|
||||
return `${scheme}://${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the passed value is a string with at least one character.
|
||||
*
|
||||
* @param {*} value - The value to be checked.
|
||||
* @return {boolean} Returns `true` if the value is a non-empty string,
|
||||
* otherwise `false`.
|
||||
*/
|
||||
function isNonEmptyString(value) {
|
||||
return (typeof value === 'string' && value.length > 0);
|
||||
}
|
||||
|
||||
426
docs/development.md
Normal file
426
docs/development.md
Normal file
@@ -0,0 +1,426 @@
|
||||
# Development
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Configuration](#configuration)
|
||||
* [Environment Configuration](#environment-configuration)
|
||||
* [Development Configuration](#development-configuration)
|
||||
* [`paths` option](#paths-option)
|
||||
* [`paths.url` option](#pathsurl-option)
|
||||
* [`paths.dest` option](#pathsdest-option)
|
||||
* [`tasks` option](#tasks-option)
|
||||
* [`server` option](#server-option)
|
||||
* [Tasks](#tasks)
|
||||
* [`concats`](#concats)
|
||||
* [`scripts`](#scripts)
|
||||
* [`styles`](#styles)
|
||||
* [`svgs`](#svgs)
|
||||
* [`versions`](#versions)
|
||||
|
||||
---
|
||||
|
||||
The boilerplate provides a custom, easily configured, and very simple,
|
||||
task runner for [Node] to process assets and test quickly in browsers.
|
||||
|
||||
Learn more about the boilerplate's [tasks](#tasks) below.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have the following installed:
|
||||
|
||||
* [Node] — at least 14.17, 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
|
||||
nvm use
|
||||
|
||||
# Install dependencies from package.json
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# Start development server, watch for changes, and compile assets
|
||||
npm start
|
||||
|
||||
# Compile and minify assets
|
||||
npm run build
|
||||
```
|
||||
|
||||
See [`build.js`](../build/build.js) and [`watch.js`](../build/watch.js)
|
||||
for details.
|
||||
|
||||
## Configuration
|
||||
|
||||
For development, most configuration values for processing front-end assets
|
||||
are defined in the [`loconfig.json`](../loconfig.json) file that exists at
|
||||
the root directory of your project.
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
If any configuration options vary depending on whether your project is
|
||||
running on your computer, a collaborator's computer, or on a web server,
|
||||
these values should be stored in a `loconfig.local.json` file.
|
||||
|
||||
In fresh copy of the boilerplate, the root directory of your project
|
||||
will contain a [`loconfig.example.json`](../loconfig.example.json) file.
|
||||
|
||||
> 💡 The boilerplate's default example customizes the development server
|
||||
> to use a custom SSL certificate.
|
||||
|
||||
That file can be copied to `loconfig.local.json` and customized to suit
|
||||
your local environment.
|
||||
|
||||
Your `loconfig.local.json` _should not_ be committed to your project's
|
||||
source control.
|
||||
|
||||
> 💡 If you are developing with a team, you may wish to continue
|
||||
> including a `loconfig.example.json` file with your project.
|
||||
|
||||
### Development Configuration
|
||||
|
||||
The boilerplate provides a few configuration settings to control the
|
||||
behaviour for processing front-end assets.
|
||||
|
||||
#### `paths` option
|
||||
|
||||
The `paths` option defines URIs and file paths.
|
||||
|
||||
It is primarily used for template tags to reference any configuration
|
||||
properties to reduce repetition.
|
||||
|
||||
Template tags are specified using `{% %}` delimiters. They will be
|
||||
automatically expanded when tasks process paths.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"paths": {
|
||||
"styles": {
|
||||
"src": "./assets/styles",
|
||||
"dest": "./www/assets/styles"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"styles": [
|
||||
{
|
||||
"infile": "{% paths.styles.src %}/main.scss", // → ./assets/styles/main.scss
|
||||
"outfile": "{% paths.styles.dest %}/main.css" // → ./www/assets/styles/main.css
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `paths.url` option
|
||||
|
||||
The `paths.url` option defines the base URI of the project.
|
||||
|
||||
By default, it is used by the development server as a proxy
|
||||
for an existing virtual host.
|
||||
|
||||
```json
|
||||
{
|
||||
"paths": {
|
||||
"url": "locomotive-boilerplate.test"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `paths.dest` option
|
||||
|
||||
The `paths.dest` option defines the public web directory of the project.
|
||||
|
||||
By default, it is used by the development server as the base directory
|
||||
to serve the website from if a proxy URI is not provided.
|
||||
|
||||
```json
|
||||
{
|
||||
"paths": {
|
||||
"dest": "./www"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `tasks` option
|
||||
|
||||
Which assets and how they should be processed can be configured via
|
||||
the `tasks` option:
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"scripts": [
|
||||
{
|
||||
"includes": [
|
||||
"./assets/scripts/app.js"
|
||||
],
|
||||
"outfile": "./www/assets/scripts/app.js"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"infile": "./assets/styles/main.scss",
|
||||
"outfile": "./www/assets/styles/main.css"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [tasks](#tasks) section, below, for details.
|
||||
|
||||
#### `server` option
|
||||
|
||||
The development server (BrowserSync) can be configured via
|
||||
the `server` option:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"open": true,
|
||||
"https": {
|
||||
"key": "~/.config/valet/Certificates/{% paths.url %}.key",
|
||||
"cert": "~/.config/valet/Certificates/{% paths.url %}.crt"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Visit [BrowserSync's documentation](https://browsersync.io/docs/options)
|
||||
for all options.
|
||||
|
||||
## Tasks
|
||||
|
||||
The boilerplate provides a handful of tasks for handling
|
||||
the most commonly processed assets.
|
||||
|
||||
### `concats`
|
||||
|
||||
A wrapper around [concat] (with optional support for globbing) for concatenating multiple files.
|
||||
|
||||
By default, [tiny-glob] is installed with the boilerplate.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"concats": [
|
||||
{
|
||||
"label": "Application Vendors",
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/vendors/*.js",
|
||||
"node_modules/focus-visible/dist/focus-visible.min.js",
|
||||
"node_modules/vue/dist/vue.min.js",
|
||||
"node_modules/vuelidate/dist/vuelidate.min.js",
|
||||
"node_modules/vuelidate/dist/validators.min.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/app/vendors.js"
|
||||
},
|
||||
{
|
||||
"label": "Public Site Vendors",
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/vendors/*.js",
|
||||
"node_modules/focus-visible/dist/focus-visible.min.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/site/vendors.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
See [`concats.js`](../build/tasks/concats.js) for details.
|
||||
|
||||
### `scripts`
|
||||
|
||||
A wrapper around [esbuild] for bundling and minifying modern JS/ES modules.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": [
|
||||
{
|
||||
"label": "Application Dashboard JS",
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/app/dashboard.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/app/dashboard.js"
|
||||
},
|
||||
{
|
||||
"label": "Public Site JS",
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/site/main.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/site/main.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
See [`scripts.js`](../build/tasks/scripts.js) for details.
|
||||
|
||||
### `styles`
|
||||
|
||||
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.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"styles": [
|
||||
{
|
||||
"label": "Text Editor CSS",
|
||||
"infile": "{% paths.styles.src %}/app/editor.scss",
|
||||
"outfile": "{% paths.styles.dest %}/app/editor.css"
|
||||
},
|
||||
{
|
||||
"label": "Application Dashboard CSS",
|
||||
"infile": "{% paths.styles.src %}/app/dashboard.scss",
|
||||
"outfile": "{% paths.styles.dest %}/app/dashboard.css"
|
||||
},
|
||||
{
|
||||
"label": "Public Site Critical CSS",
|
||||
"infile": "{% paths.styles.src %}/site/critical.scss",
|
||||
"outfile": "{% paths.styles.dest %}/site/critical.css"
|
||||
},
|
||||
{
|
||||
"label": "Public Site CSS",
|
||||
"infile": "{% paths.styles.src %}/site/main.scss",
|
||||
"outfile": "{% paths.styles.dest %}/site/main.css"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
and generating spritesheets.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"svgs": [
|
||||
{
|
||||
"label": "Application Spritesheet",
|
||||
"includes": [
|
||||
"{% paths.images.src %}/app/*.svg"
|
||||
],
|
||||
"outfile": "{% paths.svgs.dest %}/app/sprite.svg"
|
||||
},
|
||||
{
|
||||
"label": "Public Site Spritesheet",
|
||||
"includes": [
|
||||
"{% paths.images.src %}/site/*.svg"
|
||||
],
|
||||
"outfile": "{% paths.svgs.dest %}/site/sprite.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
[esbuild]: https://npmjs.com/package/esbuild
|
||||
[fast-glob]: https://npmjs.com/package/fast-glob
|
||||
[glob]: https://npmjs.com/package/glob
|
||||
[globby]: https://npmjs.com/package/globby
|
||||
[Node]: https://nodejs.org/
|
||||
[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
109
docs/grid.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Grid system
|
||||
|
||||
* [Architectures](#architecture)
|
||||
* [Build tasks](#build-tasks)
|
||||
* [Configuration](#configuration)
|
||||
* [Usage](#usage)
|
||||
* [Example](#example)
|
||||
|
||||
## Architecture
|
||||
|
||||
The boilerplate's grid system is meant to be simple and easy to use. The goal is to create a light, flexible, and reusable way to build layouts.
|
||||
The following styles are needed to work properly:
|
||||
|
||||
* [`o-grid`](../assets/styles/objects/_grid.scss) — Object file where the default grid styles are set such as column numbers, modifiers, and options.
|
||||
* [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) — Utility file that generates the styles for every possible column based on an array of media queries and column numbers.
|
||||
|
||||
### Build tasks
|
||||
|
||||
The columns generated by [`u-grid-columns`](../assets/styles/utilities/_grid-column.scss) adds a lot of styles to the compiled CSS file. To mitigate that, [PurgeCSS] is integrated into the `styles` build task to purge unused CSS.
|
||||
|
||||
#### Configuration
|
||||
|
||||
Depending on your project, you will need to specify all the files that include CSS classes from the grid system. These files will be scanned by [PurgeCSS] to your compiled CSS files.
|
||||
|
||||
Example of a Charcoal project:
|
||||
|
||||
```jsonc
|
||||
"purgeCSS": {
|
||||
"content": [
|
||||
"./views/app/template/**/*.mustache",
|
||||
"./src/App/Template/*.php",
|
||||
"./assets/scripts/**/*" // use case: `el.classList.add('u-gc-1/2')`
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The first step is to set intial SCSS values in the following files :
|
||||
|
||||
- [`settings/_config.scss`](../assets/styles/settings/_config.scss)
|
||||
|
||||
```scss
|
||||
// Grid
|
||||
// ==========================================================================
|
||||
$base-column-nb: 12;
|
||||
$base-column-gap: $unit-small;
|
||||
```
|
||||
|
||||
You can create multiple column layouts depending on media queries.
|
||||
|
||||
- [`objects/_grid.scss`](../assets/styles/objects/_grid.scss)
|
||||
|
||||
```scss
|
||||
.o-grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
// ==========================================================================
|
||||
// Cols
|
||||
// ==========================================================================
|
||||
&.-col-#{$base-column-nb} {
|
||||
grid-template-columns: repeat(#{$base-column-nb}, 1fr);
|
||||
}
|
||||
|
||||
&.-col-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
&.-col-#{$base-column-nb}\@from-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/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user