Compare commits
146 Commits
quentinhoc
...
mcaskill/j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f400ca9622 | ||
|
|
1898a94373 | ||
|
|
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
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"ie": "11"
|
||||
},
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
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
|
||||
|
||||
303
README.md
@@ -1,158 +1,233 @@
|
||||
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 Locomotive.</p>
|
||||
|
||||
Front-end boilerplate for projects by [Locomotive][locomtl].
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
| ---------- | -------- |
|
||||
| [Node] | >= 14.17 |
|
||||
| [NPM] | >= 6.0 |
|
||||
|
||||
[Node]: https://nodejs.org/
|
||||
[NPM]: https://npmjs.com/
|
||||
|
||||
You can use [nvm](https://github.com/nvm-sh/nvm) to install the node version in `.nvmrc`.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
# install mbp and gulp
|
||||
npm install mbp gulp@next -g
|
||||
npm i
|
||||
```
|
||||
|
||||
## Usage
|
||||
```sh
|
||||
# init your project
|
||||
mbp init locomotivemtl/locomotive-boilerplate <directory>
|
||||
|
||||
# run default watch task
|
||||
gulp
|
||||
```sh
|
||||
# start it
|
||||
npm start
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Change the mentions of `boilerplate` for your project's name in
|
||||
- `mconfig.json`
|
||||
- `assets/scripts/utils/environment.js`
|
||||
|
||||
## CSS
|
||||
There are a few occurrences that should be renamed for your project:
|
||||
|
||||
- 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)
|
||||
* [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):
|
||||
* Browser Sync proxy URL: `locomotive-boilerplate.test`
|
||||
Remove `paths.url` to use Browser Sync'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`
|
||||
|
||||
### Sass import order
|
||||
## Build
|
||||
|
||||
* **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 {}`).
|
||||
#### Tasks
|
||||
```sh
|
||||
# watch
|
||||
npm start
|
||||
|
||||
### 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**
|
||||
# build
|
||||
npm run build
|
||||
```
|
||||
<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>
|
||||
|
||||
## Styles
|
||||
|
||||
[Sass](https://github.com/sass/node-sass) is our CSS preprocessor. [Autoprefixer](https://github.com/postcss/autoprefixer) is also included.
|
||||
|
||||
#### Architecture
|
||||
|
||||
[ITCSS](https://github.com/itcss) is our CSS architecture.
|
||||
|
||||
* `settings`: Global variables, site-wide settings, config switches, etc.
|
||||
* `tools`: Site-wide mixins and functions.
|
||||
* `generic`: Low-specificity, far-reaching rulesets (e.g. resets).
|
||||
* `elements`: Unclassed HTML elements (e.g. `a {}`, `blockquote {}`, `address {}`).
|
||||
* `objects`: Objects, abstractions, and design patterns (e.g. `.o-layout {}`).
|
||||
* `components`: Discrete, complete chunks of UI (e.g. `.c-carousel {}`).
|
||||
* `utilities`: High-specificity, very explicit selectors. Overrides and helper
|
||||
classes (e.g. `.u-hidden {}`)
|
||||
|
||||
[_source_](https://github.com/inuitcss/inuitcss#css-directory-structure)
|
||||
|
||||
#### Naming
|
||||
|
||||
We use a simplified [BEM](https://github.com/bem) syntax.
|
||||
|
||||
```
|
||||
.block .block_element -modifier
|
||||
```
|
||||
|
||||
#### Namespaces
|
||||
|
||||
We namespace our classes for more [transparency](https://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/).
|
||||
|
||||
* `o-`: Object that it may be used in any number of unrelated contexts to the one you can currently see it in. Making modifications to these types of class could potentially have knock-on effects in a lot of other unrelated places.
|
||||
* `c-`: Component is a concrete, implementation-specific piece of UI. All of the changes you make to its styles should be detectable in the context you’re currently looking at. Modifying these styles should be safe and have no side effects.
|
||||
* `u-`: Utility has a very specific role (often providing only one declaration) and should not be bound onto or changed. It can be reused and is not tied to any specific piece of UI.
|
||||
* `s-`: Scope creates a new styling context. Similar to a Theme, but not necessarily cosmetic, these should be used sparingly—they can be open to abuse and lead to poor CSS if not used wisely.
|
||||
* `is-`, `has-`: Is currently styled a certain way because of a state or condition. It tells us that the DOM currently has a temporary, optional, or short-lived style applied to it due to a certain state being invoked.
|
||||
|
||||
[_source_](https://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/#the-namespaces)
|
||||
|
||||
#### Example
|
||||
|
||||
```html
|
||||
<div class="c-block -large">
|
||||
<div class="c-block_layout o-layout">
|
||||
<div class="o-layout_item u-1/2@from-medium">
|
||||
<div class="c-block_heading o-h -medium">Heading</div>
|
||||
</div>
|
||||
<div class="o-layout_item u-1/2@from-medium">
|
||||
<a class="c-block_button o-button -outline" href="#">Button</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
```scss
|
||||
.c-block {
|
||||
&.-large {
|
||||
padding: rem(60px);
|
||||
}
|
||||
}
|
||||
|
||||
**CSS** (`/tools/_widths.scss`)
|
||||
```
|
||||
.u-1\/2\@from-medium {
|
||||
@media (min-width: $from-medium) {
|
||||
width: span(1/2);
|
||||
.c-block_heading {
|
||||
@media (max-width: $to-medium) {
|
||||
.c-block.-large & {
|
||||
margin-bottom: rem(40px);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
### Form
|
||||
[modularJS](https://github.com/modularorg/modularjs) is a small framework we use on top of ES modules. It compiles with [Rollup](https://github.com/rollup/rollup) and [Babel](https://github.com/babel/babel).
|
||||
|
||||
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.
|
||||
#### Why
|
||||
|
||||
*[Demo][demo-form]*
|
||||
* Automatically init visible modules.
|
||||
* Easily call other modules methods.
|
||||
* Quickly set scoped events with delegation.
|
||||
* Simply select DOM elements scoped in their module.
|
||||
|
||||
## JavaScript
|
||||
[_source_](https://github.com/modularorg/modularjs#why)
|
||||
|
||||
- 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
|
||||
#### Example
|
||||
|
||||
[locomtl]: https://locomotive.ca
|
||||
[demo-grid]: https://codepen.io/AntoineBoulanger/pen/EaLNxe
|
||||
[demo-form]: https://codepen.io/AntoineBoulanger/pen/uBJmi
|
||||
```html
|
||||
<div data-module-example>
|
||||
<div data-example="main">
|
||||
<h2>Example</h2>
|
||||
</div>
|
||||
<button data-example="load">More</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
import { module } from 'modujs';
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
|
||||
this.events = {
|
||||
click: {
|
||||
load: 'loadMore'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
this.$('main')[0].classList.add('is-loading');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more](https://github.com/modularorg/modularjs)
|
||||
|
||||
## 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.
|
||||
[modularLoad](https://github.com/modularorg/modularload) is used for page transitions and lazy loading.
|
||||
|
||||
2. Main settings are set inside `assets/scripts/transitions/TransitionManager.js`
|
||||
#### Example
|
||||
|
||||
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
|
||||
```html
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/page" data-load="transitionName">Page</a>
|
||||
</nav>
|
||||
<div data-load-container>
|
||||
<img data-load-src="assets/images/hello.jpg">
|
||||
</div>
|
||||
```
|
||||
```js
|
||||
import modularLoad from 'modularload';
|
||||
|
||||
### Schema
|
||||
this.load = new modularLoad({
|
||||
enterDelay: 300,
|
||||
transitions: {
|
||||
transitionName: {
|
||||
enterDelay: 450
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Legend
|
||||
- `[ ]` : listener
|
||||
- `*` : trigger event
|
||||
[Learn more](https://github.com/modularorg/modularload)
|
||||
|
||||
`[pjax:send]` -> (transition) launch()
|
||||
## Scroll detection
|
||||
|
||||
`[pjax:switch]` (= new view is loaded) -> (BaseTransition) `hideView()` -> hide animations & `*readyToRemove`
|
||||
[Locomotive Scroll](https://github.com/locomotivemtl/locomotive-scroll) is used for elements in viewport detection and smooth scrolling with parallax.
|
||||
|
||||
`[readyToRemove]` -> `remove()` -> delete modules, remove oldView from the DOM, innerHTML newView, init modules, `display()`
|
||||
#### Example
|
||||
|
||||
`display()` -> (BaseTransition) `displayView()` -> display animations & `*readyToDestroy`
|
||||
-> init new modules
|
||||
```html
|
||||
<div data-module-scroll>
|
||||
<div data-scroll>Trigger</div>
|
||||
<div data-scroll data-scroll-speed="1">Parallax</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
`[readyToRemove]` -> reinit()
|
||||
```js
|
||||
import LocomotiveScroll from 'locomotive-scroll';
|
||||
|
||||
## 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)
|
||||
this.scroll = new LocomotiveScroll({
|
||||
el: this.el,
|
||||
smooth: true
|
||||
});
|
||||
````
|
||||
|
||||
[Learn more](https://github.com/locomotivemtl/locomotive-scroll)
|
||||
|
||||
@@ -1,161 +1,35 @@
|
||||
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 { html } from './utils/environment';
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
$document.on(EVENT.DELETE_SCOPED_MODULES, (event) => {
|
||||
this.deleteModules(event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all existing modules or a specific scope of modules
|
||||
* @param {Object} event The event being triggered.
|
||||
* @return {Object} Self (allows chaining)
|
||||
*/
|
||||
deleteModules(event) {
|
||||
let destroyAll = true;
|
||||
let moduleIds = [];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
html.classList.add('is-loaded');
|
||||
html.classList.add('is-ready');
|
||||
html.classList.remove('is-loading');
|
||||
}
|
||||
|
||||
// IIFE for loading the application
|
||||
// ==========================================================================
|
||||
(function() {
|
||||
new App();
|
||||
$document.triggerHandler({
|
||||
type: EVENT.INIT_MODULES,
|
||||
firstBlood: true
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import TransitionManager from './transitions/TransitionManager';
|
||||
import svg4everybody from 'svg4everybody';
|
||||
|
||||
export default function(firstBlood) {
|
||||
export default function() {
|
||||
svg4everybody();
|
||||
|
||||
if (firstBlood) {
|
||||
const transitionManager = new TransitionManager();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export {default as Example} from './modules/Example';
|
||||
export {default as Load} from './modules/Load';
|
||||
export {default as Modal} from './modules/Modal';
|
||||
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,10 @@
|
||||
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';
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
init() {
|
||||
// Set events and such
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.log('❌ [module]:destroy - Example');
|
||||
super.destroy();
|
||||
this.$el.off(`.${EVENT_NAMESPACE}`);
|
||||
}
|
||||
}
|
||||
|
||||
26
assets/scripts/modules/Load.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { module } from 'modujs';
|
||||
import modularLoad from 'modularload';
|
||||
|
||||
export default class extends module {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
init() {
|
||||
const load = new modularLoad({
|
||||
enterDelay: 0,
|
||||
transitions: {
|
||||
customTransition: {}
|
||||
}
|
||||
});
|
||||
|
||||
this.load.on('loading', (transition, oldContainer) => {
|
||||
this.call('hide', null, 'Modal');
|
||||
});
|
||||
|
||||
load.on('loaded', (transition, oldContainer, newContainer) => {
|
||||
this.call('destroy', oldContainer, 'app');
|
||||
this.call('update', newContainer, 'app');
|
||||
});
|
||||
}
|
||||
}
|
||||
515
assets/scripts/modules/Modal.js
Normal file
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* Module to display a modal dialog.
|
||||
*
|
||||
* Uses {@link https://github.com/KittyGiraudel/a11y-dialog A11y Dialog}.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* ```html
|
||||
* <!-- 1. The dialog container -->
|
||||
* <div
|
||||
* id="your-dialog-id"
|
||||
* data-module-modal="your-dialog-id"
|
||||
* aria-labelledby="your-dialog-title-id"
|
||||
* aria-hidden="true"
|
||||
* >
|
||||
* <!-- 2. The dialog overlay -->
|
||||
* <div data-a11y-dialog-hide></div>
|
||||
* <!-- 3. The actual dialog -->
|
||||
* <div role="document">
|
||||
* <!-- 4. The close button -->
|
||||
* <button type="button" data-a11y-dialog-hide aria-label="Close dialog">
|
||||
* ×
|
||||
* </button>
|
||||
* <!-- 5. The dialog title -->
|
||||
* <h1 id="your-dialog-title-id">Your dialog title</h1>
|
||||
* <!-- 6. Dialog content -->
|
||||
* </div>
|
||||
* </div>
|
||||
*
|
||||
* <!-- 7. The a11y-dialog modal trigger -->
|
||||
* <button type="button" data-a11y-dialog-show="your-dialog-id">
|
||||
* Open the dialog
|
||||
* </button>
|
||||
* ```
|
||||
*
|
||||
* The dialog container must have a unique name for the `data-module-modal`
|
||||
* attribute for cross-component interaction.
|
||||
*
|
||||
* The `id` attribute is recommended but optional if `data-module-modal`
|
||||
* is present with a unique name.
|
||||
*
|
||||
* If the dialog container uses the `data-a11y-dialog` attribute for automatic
|
||||
* instantiation through HTML, this component will not initialize.
|
||||
*
|
||||
* ### Features
|
||||
*
|
||||
* #### Automatically showing dialog
|
||||
*
|
||||
* The modal supports being shown on page load via the
|
||||
* `data-modal-autoshow` attribute:
|
||||
*
|
||||
* ```html
|
||||
* <div data-module-modal="your-dialog-id"
|
||||
* data-modal-autoshow
|
||||
* aria-labelledby="your-dialog-title-id"
|
||||
* aria-hidden="true"
|
||||
* >
|
||||
* ```
|
||||
*
|
||||
* The modal will be shown during the module's `init()` process.
|
||||
*
|
||||
* #### Showing dialog once
|
||||
*
|
||||
* The modal supports being shown at most once via the
|
||||
* `data-modal-show-once` attribute:
|
||||
*
|
||||
* ```html
|
||||
* <div data-module-modal="your-dialog-id"
|
||||
* data-modal-show-once
|
||||
* aria-labelledby="your-dialog-title-id"
|
||||
* aria-hidden="true"
|
||||
* >
|
||||
* ```
|
||||
*
|
||||
* By default, it uses {@see window.localStorage} to persist dismissal.
|
||||
* With a "session" value (`data-modal-show-once="session"`), the component
|
||||
* will use {@see window.sessionStorage}.
|
||||
*/
|
||||
|
||||
import A11yDialog from 'a11y-dialog'
|
||||
import { module } from 'modujs';
|
||||
import { html, isDebug } from '../utils/environment';
|
||||
|
||||
/**
|
||||
* Component to display a modal dialog.
|
||||
*
|
||||
* @property {?A11yDialog} dialog - The {@see A11yDialog} instance.
|
||||
* @property {string} moduleName - The module class name.
|
||||
* @property {string} moduleID - The module class instance ID.
|
||||
*/
|
||||
export default class extends module
|
||||
{
|
||||
/**
|
||||
* Whether the dialog should be shown on page load.
|
||||
*
|
||||
* @var {boolean}
|
||||
*/
|
||||
autoShow = false;
|
||||
|
||||
/**
|
||||
* The element to add `contextShowClass` to. Defaults to `<html>`.
|
||||
*
|
||||
* @var {Element}
|
||||
*/
|
||||
contextElement = html;
|
||||
|
||||
/**
|
||||
* The CSS class name to apply to `contextElement` to mark the dialog as shown.
|
||||
*
|
||||
* @var {string}
|
||||
*/
|
||||
contextShowClass = 'has-modal-open';
|
||||
|
||||
/**
|
||||
* Whether to log information about the modal.
|
||||
*
|
||||
* @var {boolean}
|
||||
*/
|
||||
debug = isDebug;
|
||||
|
||||
/**
|
||||
* The storage key to remember the dialog was dismissed.
|
||||
*
|
||||
* @var {?string}
|
||||
*/
|
||||
dismissedStoreKey;
|
||||
|
||||
/**
|
||||
* Whether the dialog can be shown at most once.
|
||||
*
|
||||
* @var {boolean}
|
||||
*/
|
||||
showOnce = false;
|
||||
|
||||
/**
|
||||
* The storage object; either `localStorage` or `sessionStorage`.
|
||||
*
|
||||
* @var {?Storage}
|
||||
*/
|
||||
showOnceStore = window.localStorage;
|
||||
|
||||
/**
|
||||
* Whether the dialog was shown.
|
||||
*
|
||||
* @var {boolean}
|
||||
*/
|
||||
wasShown = false;
|
||||
|
||||
/**
|
||||
* Creates a new Modal component.
|
||||
*
|
||||
* @param {object} options - The module options.
|
||||
* @param {string} options.name - The module class name.
|
||||
* @param {string} options.dataName - The module data attribute name.
|
||||
* @throws {TypeError} If the module ID or module class is missing.
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.events = {
|
||||
click: {
|
||||
dismiss: 'hide',
|
||||
hide: 'hide',
|
||||
show: 'show',
|
||||
toggle: 'toggle',
|
||||
},
|
||||
submit: 'onSubmit',
|
||||
submitend: 'hide',
|
||||
};
|
||||
|
||||
const moduleAttr = `data-module-${options.dataName}`;
|
||||
|
||||
this.moduleName = options.name;
|
||||
this.moduleID = this.el.getAttribute(moduleAttr);
|
||||
|
||||
if (!this.moduleID) {
|
||||
throw new TypeError(
|
||||
`${this.moduleName} must have an ID on attribute ${moduleAttr}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.el.hasAttribute('id')) {
|
||||
this.el.setAttribute('id', this.moduleID);
|
||||
}
|
||||
|
||||
this._onHide = this.onHide.bind(this);
|
||||
this._onShow = this.onShow.bind(this);
|
||||
this._onSubmit = this.onSubmit.bind(this);
|
||||
|
||||
this.resolveShowOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the A11y Dialog instance and initializes the Modal component.
|
||||
*
|
||||
* If the `data-a11y-dialog` attribute is defined on the
|
||||
* {@see this.el dialog element}, the Modal component will
|
||||
* be ignored.
|
||||
*
|
||||
* If the Modal component was previously dismissed,
|
||||
* the component will be ignored.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
init() {
|
||||
if (this.el.hasAttribute('data-a11y-dialog')) {
|
||||
const dialogID = (this.el.getAttribute('data-a11y-dialog') || this.moduleID);
|
||||
this.debug && console.warn(`${this.moduleName} [${dialogID}] not initialized because of automatic instantiation through HTML`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.showOnce && this.wasShown) {
|
||||
this.debug && console.log(`${this.moduleName} [${this.moduleID}] not initialized because the dialog was previously dismissed`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog = this.createDialog();
|
||||
|
||||
/**
|
||||
* Assigning the this class and the dialog to the dialog container
|
||||
* element for easy access from the web console.
|
||||
*/
|
||||
this.el.appModal = this;
|
||||
this.el.a11yDialog = this.dialog;
|
||||
|
||||
this.addDialogEventListeners();
|
||||
|
||||
if (this.el.hasAttribute(`${this.mAttr}-autoshow`)) {
|
||||
this.dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mUpdate(modules) {
|
||||
super.mUpdate(modules);
|
||||
|
||||
this.refreshDialogOpeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new A11y Dialog instance.
|
||||
*
|
||||
* @protected
|
||||
* @return {A11yDialog}
|
||||
*/
|
||||
createDialog() {
|
||||
return new A11yDialog(this.el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the A11y Dialog instance and Modal component.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
destroy() {
|
||||
this.destroyDialog();
|
||||
|
||||
delete this.el.appModal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys only the A11y Dialog instance.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
destroyDialog() {
|
||||
if (this.dialog) {
|
||||
this.dialog.destroy();
|
||||
}
|
||||
|
||||
delete this.el.a11yDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the modal.
|
||||
*
|
||||
* Marks the modal as dismissed which means it
|
||||
* should not be shown for the foreseeable future.
|
||||
*
|
||||
* @protected
|
||||
* @param {boolean} [destroy=false] - Whether to destroy the modal or not.
|
||||
* @return {void}
|
||||
*/
|
||||
dismissDialog(destroy = false) {
|
||||
this.wasShown = true;
|
||||
|
||||
if (this.showOnceStore && this.dismissedStoreKey) {
|
||||
this.showOnceStore.setItem(this.dismissedStoreKey, this.getDismissedStoreValue());
|
||||
} else {
|
||||
this.debug && console.warn(`${this.moduleName} [${dialogID}] does not have a 'showOnceStore' or a 'dismissedStoreKey' for persisting dismissal`);
|
||||
}
|
||||
|
||||
if (destroy) {
|
||||
this.destroyDialog();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers event listeners on the A11y Dialog.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
addDialogEventListeners() {
|
||||
if (this.dialog) {
|
||||
this.dialog.on('hide', this._onHide);
|
||||
this.dialog.on('show', this._onShow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters event listeners on the A11y Dialog.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
removeDialogEventListeners() {
|
||||
if (this.dialog) {
|
||||
this.dialog.off('hide', this._onHide);
|
||||
this.dialog.off('show', this._onShow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {self}
|
||||
*/
|
||||
hide() {
|
||||
if (this.dialog) {
|
||||
this.dialog.hide();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {self}
|
||||
*/
|
||||
show() {
|
||||
if (this.dialog) {
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {self}
|
||||
*/
|
||||
toggle() {
|
||||
if (this.dialog) {
|
||||
if (this.dialog.shown) {
|
||||
this.dialog.hide();
|
||||
} else {
|
||||
this.dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage key to remember the dialog was dismissed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getDismissedStoreKey() {
|
||||
return `${this.moduleName}.${this.moduleID}.dismissed`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage value to remember the dialog was dismissed.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getDismissedStoreValue() {
|
||||
return (new Date()).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of kebab-case form module names.
|
||||
*
|
||||
* @return {string[]}
|
||||
*/
|
||||
getFormModuleNames() {
|
||||
return [
|
||||
'form',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the dialog has finished being hidden from the user.
|
||||
*
|
||||
* This method will trigger dismissal of the dialog if to be
|
||||
* {@see this.showOnce shown at most once}.
|
||||
*
|
||||
* This method will remove the {@see this.contextShowClass CSS context show class}.
|
||||
*
|
||||
* @listens A11yDialog#hide
|
||||
*
|
||||
* @protected
|
||||
* @param {Element} dialogEl - The dialog container element.
|
||||
* @param {Event} event - The dialog hide event.
|
||||
* @return {void}
|
||||
*/
|
||||
onHide(dialogEl, event) {
|
||||
if (this.showOnce && !this.wasShown) {
|
||||
this.dismissDialog(true);
|
||||
}
|
||||
|
||||
if (this.contextElement && this.contextShowClass) {
|
||||
this.contextElement.classList.remove(this.contextShowClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the dialog has been made visible to the user.
|
||||
*
|
||||
* This method will add the {@see this.contextShowClass CSS context show class}.
|
||||
*
|
||||
* @listens A11yDialog#show
|
||||
*
|
||||
* @protected
|
||||
* @param {Element} dialogEl - The dialog container element.
|
||||
* @param {Event} event - The dialog show event.
|
||||
* @return {void}
|
||||
*/
|
||||
onShow(dialogEl, event) {
|
||||
if (this.contextElement && this.contextShowClass) {
|
||||
this.contextElement.classList.add(this.contextShowClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles form submission inside the modal.
|
||||
*
|
||||
* This event listener is used to handle forms that do not use a custom
|
||||
* form module where the modal will dispatch a custom "submitend" event.
|
||||
*
|
||||
* @listens form#submit
|
||||
* @fires form#submitend
|
||||
*
|
||||
* @protected
|
||||
* @param {Event} event - The submit event.
|
||||
* @return {void}
|
||||
*/
|
||||
onSubmit(event) {
|
||||
const selectors = this.getFormModuleNames()
|
||||
.map((name) => `:not([data-module-${name}])`)
|
||||
.join('');
|
||||
|
||||
if (event.target.matches(selectors)) {
|
||||
this.debug && console.log('Modal.onSubmit')
|
||||
const submitEndEvent = new CustomEvent('submitend', {
|
||||
bubbles: true
|
||||
});
|
||||
event.target.dispatchEvent(submitEndEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes all opener event listeners of the A11y Dialog instance.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
refreshDialogOpeners() {
|
||||
if (this.dialog) {
|
||||
// Remove the click event listener from all dialog openers
|
||||
this.dialog._openers.forEach((opener) => {
|
||||
opener.removeEventListener('click', this.dialog._show);
|
||||
});
|
||||
|
||||
// Keep a collection of dialog openers, each of which will be bound a click
|
||||
// event listener to open the dialog
|
||||
this.dialog._openers = document.querySelectorAll('[data-a11y-dialog-show="' + this.dialog._id + '"]');
|
||||
this.dialog._openers.forEach((opener) => {
|
||||
opener.addEventListener('click', this.dialog._show);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the "show at most once" feature.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
resolveShowOnce() {
|
||||
const showOnceAttr = `${this.mAttr}-show-once`;
|
||||
|
||||
this.showOnce = this.el.hasAttribute(showOnceAttr);
|
||||
|
||||
if (!this.showOnce) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.el.getAttribute(showOnceAttr)) {
|
||||
case 'session':
|
||||
this.showOnceStore = window.sessionStorage;
|
||||
break;
|
||||
|
||||
case 'local':
|
||||
this.showOnceStore = window.localStorage;
|
||||
break;
|
||||
}
|
||||
|
||||
this.dismissedStoreKey = this.getDismissedStoreKey();
|
||||
|
||||
if (this.showOnceStore) {
|
||||
this.wasShown = this.showOnceStore.getItem(this.dismissedStoreKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,52 @@
|
||||
import { APP_NAME, $document } from '../utils/environment';
|
||||
import AbstractModule from './AbstractModule';
|
||||
import ScrollManager from '../scroll/vendors/ScrollManager';
|
||||
import { module } from 'modujs';
|
||||
import { lazyLoadImage } from '../utils/image';
|
||||
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({
|
||||
el: this.el,
|
||||
smooth: true
|
||||
});
|
||||
|
||||
this.scroll.on('call', (func, way, obj, id) => {
|
||||
// Using modularJS
|
||||
this.call(func[0], { way, obj }, func[1], func[2]);
|
||||
});
|
||||
|
||||
this.scroll.on('scroll', (args) => {
|
||||
// console.log(args.scroll);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load the related image.
|
||||
*
|
||||
* @see ../utils/image.js
|
||||
*
|
||||
* It is recommended to wrap your `<img>` into an element with the
|
||||
* CSS class name `.c-lazy`. The CSS class name modifier `.-lazy-loaded`
|
||||
* will be applied on both the image and the parent wrapper.
|
||||
*
|
||||
* ```html
|
||||
* <div class="c-lazy o-ratio u-4:3">
|
||||
* <img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* @param {LocomotiveScroll} args - The Locomotive Scroll instance.
|
||||
*/
|
||||
lazyLoad(args) {
|
||||
lazyLoadImage(args.obj.el, null, () => {
|
||||
//callback
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
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';
|
||||
@@ -86,3 +86,16 @@ export function findByKeyValue( array, key, value ) {
|
||||
export function cloneArray( array ) {
|
||||
return JSON.parse(JSON.stringify(array));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles array in place. ES6 version
|
||||
* @param {Array} a items An array containing the items.
|
||||
*/
|
||||
export function shuffle(a) {
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
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 html = document.documentElement;
|
||||
const body = document.body;
|
||||
const isDebug = html.hasAttribute('data-debug');
|
||||
|
||||
const isDebug = !!$html.data('debug');
|
||||
|
||||
export { APP_NAME, DATA_API_KEY, $document, $window, $html, $body, isDebug, $pjaxWrapper };
|
||||
export { APP_NAME, DATA_API_KEY, html, body, isDebug };
|
||||
|
||||
@@ -94,3 +94,48 @@ export function getData(data) {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all the parent nodes of the given node
|
||||
* @param {object} node
|
||||
* @return {array} parent nodes
|
||||
*/
|
||||
export function getParents(elem) {
|
||||
// Set up a parent array
|
||||
let parents = [];
|
||||
|
||||
// Push each parent element to the array
|
||||
for ( ; elem && elem !== document; elem = elem.parentNode ) {
|
||||
parents.push(elem);
|
||||
}
|
||||
|
||||
// Return our parent array
|
||||
return parents;
|
||||
}
|
||||
|
||||
// https://gomakethings.com/how-to-get-the-closest-parent-element-with-a-matching-selector-using-vanilla-javascript/
|
||||
export function queryClosestParent(elem, selector) {
|
||||
|
||||
// Element.matches() polyfill
|
||||
if (!Element.prototype.matches) {
|
||||
Element.prototype.matches =
|
||||
Element.prototype.matchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector ||
|
||||
function(s) {
|
||||
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
|
||||
i = matches.length;
|
||||
while (--i >= 0 && matches.item(i) !== this) {}
|
||||
return i > -1;
|
||||
};
|
||||
}
|
||||
|
||||
// Get the closest matching element
|
||||
for ( ; elem && elem !== document; elem = elem.parentNode ) {
|
||||
if ( elem.matches( selector ) ) return elem;
|
||||
}
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
87
assets/scripts/utils/image.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const LAZY_LOADED_IMAGES = []
|
||||
|
||||
export function 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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getImageMetadata($img) {
|
||||
return {
|
||||
url: $img.src,
|
||||
width: $img.naturalWidth,
|
||||
height: $img.naturalHeight,
|
||||
ratio: $img.naturalWidth / $img.naturalHeight,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load the given image.
|
||||
*
|
||||
* @param {HTMLImageElement} $el - The image element.
|
||||
* @param {?string} url - The URI to lazy load into $el.
|
||||
* 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.
|
||||
*/
|
||||
export async function lazyLoadImage($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('.c-lazy');
|
||||
|
||||
if(lazyParent) {
|
||||
lazyParent.classList.add('-lazy-loaded')
|
||||
lazyParent.style.backgroundImage = ''
|
||||
}
|
||||
|
||||
$el.classList.add('-lazy-loaded')
|
||||
|
||||
callback?.()
|
||||
})
|
||||
}
|
||||
3
assets/scripts/utils/maths.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function lerp(start, end, amt){
|
||||
return (1 - amt) * start + amt * end
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
21
assets/scripts/utils/transform.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export function transform(el, transformValue){
|
||||
el.style.webkitTransform = transformValue;
|
||||
el.style.msTransform = transformValue;
|
||||
el.style.transform = transformValue;
|
||||
}
|
||||
|
||||
export function getTranslate(el){
|
||||
const translate = {}
|
||||
if(!window.getComputedStyle) return;
|
||||
|
||||
const style = getComputedStyle(el);
|
||||
const transform = style.transform || style.webkitTransform || style.mozTransform;
|
||||
|
||||
let mat = transform.match(/^matrix3d\((.+)\)$/);
|
||||
if(mat) return parseFloat(mat[1].split(', ')[13]);
|
||||
mat = transform.match(/^matrix\((.+)\)$/);
|
||||
translate.x = mat ? parseFloat(mat[1].split(', ')[4]) : 0;
|
||||
translate.y = mat ? parseFloat(mat[1].split(', ')[5]) : 0;
|
||||
|
||||
return translate;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
8
assets/styles/components/_button.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.c-button {
|
||||
padding: rem(15px) rem(20px);
|
||||
background-color: lightgray;
|
||||
|
||||
@include u-hocus {
|
||||
background-color: darkgray;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,37 @@
|
||||
// ==========================================================================
|
||||
// Objects / Buttons
|
||||
// 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;
|
||||
border: 1px solid lightgray;
|
||||
background-color: white;
|
||||
|
||||
&:hover {
|
||||
border-color: darkgray;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: gray;
|
||||
border-color: dimgray;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@@ -30,42 +40,21 @@ $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;
|
||||
@@ -79,6 +68,7 @@ $checkbox-icon-color: $input-icon-color;
|
||||
|
||||
&::before {
|
||||
background-color: $white;
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@@ -86,22 +76,42 @@ $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 +119,22 @@ $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 +145,26 @@ $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;
|
||||
.c-form_textarea {
|
||||
@extend .c-form_input;
|
||||
|
||||
min-height: rem(100px);
|
||||
min-height: rem(200px);
|
||||
}
|
||||
28
assets/styles/components/_heading.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.c-heading {
|
||||
line-height: $line-height-h;
|
||||
margin-bottom: rem(30px);
|
||||
|
||||
&.-h1 {
|
||||
font-size: rem($font-size-h1);
|
||||
}
|
||||
|
||||
&.-h2 {
|
||||
font-size: rem($font-size-h2);
|
||||
}
|
||||
|
||||
&.-h3 {
|
||||
font-size: rem($font-size-h3);
|
||||
}
|
||||
|
||||
&.-h4 {
|
||||
font-size: rem($font-size-h4);
|
||||
}
|
||||
|
||||
&.-h5 {
|
||||
font-size: rem($font-size-h5);
|
||||
}
|
||||
|
||||
&.-h6 {
|
||||
font-size: rem($font-size-h6);
|
||||
}
|
||||
}
|
||||
83
assets/styles/components/_modal.scss
Normal file
@@ -0,0 +1,83 @@
|
||||
// =============================================================================
|
||||
// Modal Dialog
|
||||
// =============================================================================
|
||||
// Adapted from https://codesandbox.io/s/a11y-dialog-pnwqu?file=/src/styles.css
|
||||
// =============================================================================
|
||||
|
||||
// Component settings
|
||||
// =============================================================================
|
||||
|
||||
$dialog-container-zindex: overlay-zindex(modal) !default;
|
||||
$dialog-backdrop-background-color: rgba(0, 0, 0, 0.9) !default;
|
||||
$dialog-content-background-color: rgb(255, 255, 255) !default;
|
||||
|
||||
// Basic component styles
|
||||
// =============================================================================
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
}
|
||||
|
||||
.has-modal-open {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.c-dialog_container {
|
||||
display: flex;
|
||||
overflow: scroll;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: $dialog-container-zindex;
|
||||
|
||||
&[aria-hidden='true'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-dialog_overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: $dialog-backdrop-background-color;
|
||||
animation: fade-in 200ms both;
|
||||
}
|
||||
|
||||
.c-dialog_content {
|
||||
background-color: $dialog-content-background-color;
|
||||
margin: auto;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
animation: fade-in 400ms 200ms both, slide-up 400ms 200ms both;
|
||||
padding: 1em;
|
||||
max-width: 90%;
|
||||
width: 600px;
|
||||
border-radius: 2px;
|
||||
|
||||
@media screen and (min-width: $from-small) {
|
||||
padding: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-dialog_close {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
|
||||
@media screen and (min-width: $from-small) {
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
34
assets/styles/components/_scrollbar.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
.c-scrollbar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 11px;
|
||||
height: 100vh;
|
||||
transform-origin: center right;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scaleX(1.45);
|
||||
}
|
||||
|
||||
&:hover, .has-scroll-scrolling &, .has-scroll-dragging & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.c-scrollbar_thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: black;
|
||||
opacity: 0.5;
|
||||
width: 7px;
|
||||
border-radius: 10px;
|
||||
margin: 2px;
|
||||
cursor: grab;
|
||||
|
||||
.has-scroll-dragging & {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
1
assets/styles/critical.scss
Normal file
@@ -0,0 +1 @@
|
||||
$assets-path: "assets/";
|
||||
@@ -7,28 +7,26 @@
|
||||
//
|
||||
// 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.
|
||||
// 2. Ensure the page always fills at least the entire height of the viewport.
|
||||
//
|
||||
html {
|
||||
overflow-y: scroll; /* [2] */
|
||||
min-height: 100%; /* [3] */
|
||||
min-height: 100%; /* [2] */
|
||||
color: $color;
|
||||
font-family: $font-family;
|
||||
line-height: $line-height; /* [1] */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: $to-small) {
|
||||
font-size: 12px;
|
||||
font-size: $font-size - 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-small) and (max-width: $to-medium) {
|
||||
font-size: 13px;
|
||||
font-size: $font-size - 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-medium) and (max-width: $to-large) {
|
||||
font-size: 14px;
|
||||
font-size: $font-size - 1px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-large) and (max-width: $to-huge) {
|
||||
@@ -36,15 +34,33 @@ html {
|
||||
}
|
||||
|
||||
@media (min-width: $from-huge) and (max-width: $to-gigantic) {
|
||||
font-size: 18px;
|
||||
font-size: $font-size + 1px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-gigantic) and (max-width: $to-colossal) {
|
||||
font-size: 21px;
|
||||
font-size: $font-size + 2px;
|
||||
}
|
||||
|
||||
@media (min-width: $from-colossal) {
|
||||
font-size: 24px;
|
||||
font-size: $font-size + 4px;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
&.has-scroll-smooth {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.has-scroll-dragging {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
.has-scroll-smooth & {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +71,9 @@ html {
|
||||
}
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
|
||||
@include u-hocus {
|
||||
color: $link-hover-color;
|
||||
}
|
||||
|
||||
color: $link-color;
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
// 6. Force all button-styled elements to appear clickable.
|
||||
//
|
||||
button,
|
||||
.o-button {
|
||||
.c-button {
|
||||
@include u-hocus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,23 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
// Settings
|
||||
// ==========================================================================
|
||||
@import "settings/config.eases";
|
||||
@import "settings/config.colors";
|
||||
@import "settings/config";
|
||||
|
||||
@@ -14,9 +15,8 @@
|
||||
@import "tools/mixins";
|
||||
@import "tools/fonts";
|
||||
@import "tools/layout";
|
||||
// @import "tools/ratio";
|
||||
// @import "tools/widths";
|
||||
// @import "tools/familly";
|
||||
@import "tools/widths";
|
||||
// @import "tools/family";
|
||||
|
||||
// Generic
|
||||
// ==========================================================================
|
||||
@@ -26,23 +26,19 @@
|
||||
@import "generic/form";
|
||||
@import "generic/button";
|
||||
|
||||
// Base
|
||||
// Elements
|
||||
// ==========================================================================
|
||||
@import "base/fonts";
|
||||
@import "base/page";
|
||||
@import "base/headings";
|
||||
@import "elements/fonts";
|
||||
@import "elements/page";
|
||||
|
||||
// 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";
|
||||
@import "objects/container";
|
||||
@import "objects/ratio";
|
||||
@import "objects/layout";
|
||||
// @import "objects/crop";
|
||||
// @import "objects/table";
|
||||
|
||||
// Vendors
|
||||
// ==========================================================================
|
||||
@@ -50,7 +46,11 @@
|
||||
|
||||
// Components
|
||||
// ==========================================================================
|
||||
// @import "components/component";
|
||||
@import "components/scrollbar";
|
||||
@import "components/heading";
|
||||
@import "components/button";
|
||||
@import "components/form";
|
||||
@import "components/modal";
|
||||
|
||||
// Templates
|
||||
// ==========================================================================
|
||||
@@ -58,10 +58,10 @@
|
||||
|
||||
// Utilities
|
||||
// ==========================================================================
|
||||
@import "utilities/ratio";
|
||||
@import "utilities/widths";
|
||||
// @import "utilities/align";
|
||||
// @import "utilities/helpers";
|
||||
// @import "utilities/states";
|
||||
// @import "utilities/headings";
|
||||
// @import "utilities/spacing";
|
||||
// @import "utilities/widths";
|
||||
// @import "utilities/print";
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Objects / Buttons
|
||||
// ==========================================================================
|
||||
.o-button {
|
||||
@include u-hocus {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
padding: rem(10px);
|
||||
background-color: lightgray;
|
||||
}
|
||||
@@ -12,16 +12,10 @@
|
||||
// @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-right: rem($padding);
|
||||
padding-left: rem($padding);
|
||||
max-width: rem($container-width + ($padding * 2));
|
||||
}
|
||||
|
||||
@@ -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,20 +2,6 @@
|
||||
// Objects / 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;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create ratio-bound content blocks, to keep media (e.g. images, videos) in
|
||||
* their correct aspect ratios.
|
||||
@@ -38,6 +24,7 @@ $aspect-ratios: (
|
||||
}
|
||||
|
||||
.o-ratio_content,
|
||||
.o-ratio > img,
|
||||
.o-ratio > iframe,
|
||||
.o-ratio > embed,
|
||||
.o-ratio > object {
|
||||
@@ -46,34 +33,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 +1,3 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
.o-scroll {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
19
assets/styles/settings/_config.eases.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
$Power1EaseOut: cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
||||
$Power2EaseOut: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
$Power3EaseOut: cubic-bezier(0.165, 0.840, 0.440, 1.000);
|
||||
$Power4EaseOut: cubic-bezier(0.230, 1.000, 0.320, 1.000);
|
||||
$Power1EaseIn: cubic-bezier(0.550, 0.085, 0.680, 0.530) ;
|
||||
$Power2EaseIn: cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||
$Power3EaseIn: cubic-bezier(0.895, 0.030, 0.685, 0.220);
|
||||
$Power4EaseIn: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
$ExpoEaseOut: cubic-bezier(0.190, 1.000, 0.220, 1.000);
|
||||
$ExpoEaseIn: cubic-bezier(0.950, 0.050, 0.795, 0.035);
|
||||
$ExpoEaseInOut: cubic-bezier(1.000, 0.000, 0.000, 1.000);
|
||||
$SineEaseOut: cubic-bezier(0.390, 0.575, 0.565, 1.000);
|
||||
$SineEaseIn: cubic-bezier(0.470, 0.000, 0.745, 0.715);
|
||||
$Power1EaseInOut: cubic-bezier(0.455, 0.030, 0.515, 0.955);
|
||||
$Power2EaseInOut: cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
$Power3EaseInOut: cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||
$Power4EaseInOut: cubic-bezier(0.860, 0.000, 0.070, 1.000);
|
||||
$SlowEaseOut: cubic-bezier(.04,1.15,0.4,.99);
|
||||
$bounce: cubic-bezier(0.17, 0.67, 0.3, 1.33);
|
||||
@@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// =============================================================================
|
||||
// Settings / Config
|
||||
// ==========================================================================
|
||||
// =============================================================================
|
||||
|
||||
// Context
|
||||
// =============================================================================
|
||||
@@ -38,7 +38,7 @@ $bold: 700;
|
||||
// Transitions
|
||||
// =============================================================================
|
||||
$speed: 0.3s;
|
||||
$easing: linear;
|
||||
$easing: $Power2EaseOut;
|
||||
|
||||
// Spacing Units
|
||||
// =============================================================================
|
||||
@@ -46,10 +46,25 @@ $unit: 60px;
|
||||
$unit-small: 30px;
|
||||
|
||||
// Container
|
||||
// ==========================================================================
|
||||
// =============================================================================
|
||||
$container-width: 2000px;
|
||||
$padding: $unit;
|
||||
|
||||
// Z-Indexes
|
||||
// =============================================================================
|
||||
$overlay-zindexes: (
|
||||
dropdown: 100,
|
||||
sticky: 120,
|
||||
fixed: 130,
|
||||
offcanvas-backdrop: 140,
|
||||
offcanvas: 145,
|
||||
modal-backdrop: 150,
|
||||
modal: 155,
|
||||
popover: 170,
|
||||
tooltip: 180,
|
||||
transition: 190,
|
||||
) !default;
|
||||
|
||||
// Breakpoints
|
||||
// =============================================================================
|
||||
$from-tiny: 500px !default;
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
/// @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;
|
||||
@if $num == 1 {
|
||||
&:first-child {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
&:nth-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
&:nth-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children from the last to `$num`.
|
||||
@@ -24,9 +24,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-last-child(-n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children after the first to `$num`.
|
||||
@@ -34,9 +34,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-child(n + #{$num + 1}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all children before `$num` from the last.
|
||||
@@ -44,45 +44,45 @@
|
||||
/// @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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&:nth-child(#{$num}n):nth-child(n + #{$first}):nth-child(-n + #{$last}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:not(:nth-child(#{$num})) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select children each `$num`.
|
||||
@@ -102,9 +102,9 @@
|
||||
/// @param {number} $num - id of the child
|
||||
/// @alias every
|
||||
@mixin each($num) {
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select children each `$num`.
|
||||
@@ -112,9 +112,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-child(#{$num}n) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the `$num` child from the start and the `$num` child from the last.
|
||||
@@ -122,10 +122,10 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-child(#{$num}),
|
||||
&:nth-last-child(#{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,9 +135,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-child(#{round($num / 2)}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,9 +146,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-child(n + #{$num}):nth-last-child(n + #{$num}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,9 +158,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:nth-last-child(#{$limit}):first-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// This quantity-query mixin will only select the last of `$limit` items. It will not
|
||||
@@ -169,9 +169,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&: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
|
||||
@@ -180,13 +180,13 @@
|
||||
/// @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);
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(n + #{$num}),
|
||||
&:nth-last-child(n + #{$num}) ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
&: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
|
||||
@@ -195,13 +195,13 @@
|
||||
/// @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);
|
||||
$selector: &;
|
||||
$child: nth(nth($selector, -1), -1);
|
||||
|
||||
&:nth-last-child(-n + #{$num}):first-child,
|
||||
&:nth-last-child(-n + #{$num}):first-child ~ #{$child} {
|
||||
@content;
|
||||
}
|
||||
&: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.
|
||||
@@ -209,59 +209,59 @@
|
||||
/// @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);
|
||||
$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;
|
||||
}
|
||||
&: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
|
||||
}
|
||||
&: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
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only select the child if it’s unique.
|
||||
@@ -269,18 +269,18 @@
|
||||
/// @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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
&:only-child {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/// Will only select children if they are not unique. Meaning if there is at
|
||||
@@ -288,9 +288,9 @@
|
||||
/// @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;
|
||||
}
|
||||
&:not(:only-child) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,19 +302,19 @@
|
||||
/// @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;
|
||||
}
|
||||
@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
|
||||
@@ -323,5 +323,5 @@
|
||||
/// @param {number} $num - Number of children
|
||||
/// @param {number} $index - Index of the sorting
|
||||
@function order-index($i, $index) {
|
||||
@return ($index + $i);
|
||||
@return ($index + $i);
|
||||
}
|
||||
@@ -89,6 +89,54 @@
|
||||
@return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieves the overlay component z-index.
|
||||
//
|
||||
// Adapted from OZMap.
|
||||
//
|
||||
// 1. The elements of the global z-index map may contain `null`
|
||||
// (to indicate that the z-index value should be set automatically).
|
||||
// 2. The elements of the global z-index map may contain
|
||||
// a hardcoded number, to be used as the z-index for that element.
|
||||
// - Note that the value must be greater than any preceding element,
|
||||
// to maintain the order.
|
||||
//
|
||||
// @link https://rafistrauss.com/blog/ordered_z_index_maps/ OZMap
|
||||
//
|
||||
// @param {String} $list-key - The global z-index map key.
|
||||
// @param {Number} $modifier - A positive or negative modifier to apply
|
||||
// to the returned z-index value.
|
||||
// @throw Error if the $list-key does not exist or value is unacceptable.
|
||||
// @return {Number} The next available z-index value.
|
||||
//
|
||||
@function overlay-zindex($list-key, $modifier: 0) {
|
||||
@if map-has-key($overlay-zindexes, $key: $list-key) {
|
||||
$accumulator: 0;
|
||||
|
||||
@each $key, $val in $overlay-zindexes {
|
||||
@if ($val == null) {
|
||||
$accumulator: $accumulator + $overlay-zindex-increment;
|
||||
$val: $accumulator;
|
||||
} @else {
|
||||
@if ($val <= $accumulator) {
|
||||
//* If the z-index is not greater than the elements preceding it,
|
||||
//* the whole element-order paradigm is invalidated
|
||||
@error "z-index for #{$key} must be greater than the preceding value!";
|
||||
}
|
||||
$accumulator: $val;
|
||||
}
|
||||
|
||||
@if ($key == $list-key) {
|
||||
@return ($accumulator + $modifier);
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
@error "#{$list-key} doesn't exist in the $overlay-zindexes map";
|
||||
}
|
||||
}
|
||||
|
||||
$overlay-zindex-increment: 1 !default;
|
||||
|
||||
//
|
||||
// Resolve whether a rule is important or not.
|
||||
//
|
||||
@@ -124,5 +172,3 @@
|
||||
@function is-frontend() {
|
||||
@return ('frontend' == $context);
|
||||
}
|
||||
|
||||
$context: 'frontend' !default;
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
// @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;
|
||||
|
||||
@@ -121,17 +121,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.
|
||||
//
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
42
assets/styles/utilities/_ratio.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
// ==========================================================================
|
||||
// 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: ($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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,3 +18,9 @@
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
9
build/build.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import concatFiles from './tasks/concats.js';
|
||||
import compileScripts from './tasks/scripts.js';
|
||||
import compileStyles from './tasks/styles.js' ;
|
||||
import compileSVGs from './tasks/svgs.js' ;
|
||||
|
||||
concatFiles();
|
||||
compileScripts();
|
||||
compileStyles();
|
||||
compileSVGs();
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
48
build/tasks/concats.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import loconfig from '../../loconfig.json';
|
||||
import glob from '../utils/glob.js';
|
||||
import message from '../utils/message.js';
|
||||
import notification from '../utils/notification.js';
|
||||
import template from '../utils/template.js';
|
||||
import concat from 'concat';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
/**
|
||||
* Concatenates groups of files.
|
||||
*
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function concatFiles() {
|
||||
loconfig.tasks.concats.forEach(async ({
|
||||
includes,
|
||||
outfile
|
||||
}) => {
|
||||
const filename = basename(outfile || 'undefined');
|
||||
|
||||
const timeLabel = `${filename} concatenated in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
includes = includes.map((path) => template(path));
|
||||
outfile = template(outfile);
|
||||
|
||||
const files = await glob(includes);
|
||||
|
||||
await concat(files, outfile);
|
||||
|
||||
if (files.length) {
|
||||
message(`${filename} concatenated`, 'success', timeLabel);
|
||||
} else {
|
||||
message(`${filename} is empty`, 'notice', timeLabel);
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error concatenating ${filename}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${filename} concatenation failed 🚨`,
|
||||
message: `${err.name}: ${err.message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
61
build/tasks/scripts.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import loconfig from '../../loconfig.json';
|
||||
import message from '../utils/message.js';
|
||||
import notification from '../utils/notification.js';
|
||||
import template from '../utils/template.js';
|
||||
import esbuild from 'esbuild';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
/**
|
||||
* Bundles and minifies main JavaScript files.
|
||||
*
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileScripts() {
|
||||
loconfig.tasks.scripts.forEach(async ({
|
||||
includes,
|
||||
outdir = '',
|
||||
outfile = ''
|
||||
}) => {
|
||||
const filename = basename(outdir || outfile || 'undefined');
|
||||
|
||||
const timeLabel = `${filename} compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
includes = includes.map((path) => template(path));
|
||||
|
||||
if (outdir) {
|
||||
outdir = template(outdir);
|
||||
} else if (outfile) {
|
||||
outfile = template(outfile);
|
||||
} else {
|
||||
throw new TypeError(
|
||||
'Expected \'outdir\' or \'outfile\''
|
||||
);
|
||||
}
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: includes,
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
color: true,
|
||||
logLevel: 'error',
|
||||
target: [
|
||||
'es2015',
|
||||
],
|
||||
outdir,
|
||||
outfile
|
||||
});
|
||||
|
||||
message(`${filename} compiled`, 'success', timeLabel);
|
||||
} catch (err) {
|
||||
// errors managments (already done in esbuild)
|
||||
notification({
|
||||
title: `${filename} compilation failed 🚨`,
|
||||
message: `${err.errors[0].text} in ${err.errors[0].location.file} line ${err.errors[0].location.line}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
105
build/tasks/styles.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import loconfig from '../../loconfig.json';
|
||||
import message from '../utils/message.js';
|
||||
import notification from '../utils/notification.js';
|
||||
import postcss from '../utils/postcss.js';
|
||||
import template from '../utils/template.js';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { basename } from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
import sass from 'node-sass';
|
||||
|
||||
const sassRender = promisify(sass.render);
|
||||
|
||||
/**
|
||||
* Compiles and minifies main Sass files to CSS.
|
||||
*
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileStyles() {
|
||||
loconfig.tasks.styles.forEach(async ({
|
||||
infile,
|
||||
outfile
|
||||
}) => {
|
||||
const name = basename((outfile || 'undefined'), '.css');
|
||||
|
||||
const timeLabel = `${name}.css compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
infile = template(infile);
|
||||
outfile = template(outfile);
|
||||
|
||||
let result = await sassRender({
|
||||
file: infile,
|
||||
omitSourceMapUrl: true,
|
||||
outFile: outfile,
|
||||
outputStyle: 'compressed',
|
||||
sourceMap: true,
|
||||
sourceMapContents: true
|
||||
});
|
||||
|
||||
if (postcss) {
|
||||
result = await postcss.process(result.css, {
|
||||
from: outfile,
|
||||
to: outfile,
|
||||
map: {
|
||||
annotation: false,
|
||||
inline: false,
|
||||
sourcesContent: true
|
||||
}
|
||||
});
|
||||
|
||||
if (result.warnings) {
|
||||
const warnings = result.warnings();
|
||||
if (warnings.length) {
|
||||
message(`Error processing ${name}.css`, 'warning');
|
||||
warnings.forEach((warn) => {
|
||||
message(warn.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await writeFile(outfile, result.css);
|
||||
|
||||
if (result.css) {
|
||||
message(`${name}.css compiled`, 'success', timeLabel);
|
||||
} else {
|
||||
message(`${name}.css is empty`, 'notice', timeLabel);
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error compiling ${name}.css`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${name}.css save failed 🚨`,
|
||||
message: `Could not save stylesheet to ${name}.css`
|
||||
});
|
||||
}
|
||||
|
||||
if (result.map) {
|
||||
try {
|
||||
await writeFile(outfile + '.map', result.map.toString());
|
||||
} catch (err) {
|
||||
message(`Error compiling ${name}.css.map`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${name}.css.map save failed 🚨`,
|
||||
message: `Could not save sourcemap to ${name}.css.map`
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
message(`Error compiling ${name}.scss`, 'error');
|
||||
message(err.formatted || err);
|
||||
|
||||
notification({
|
||||
title: `${name}.scss compilation failed 🚨`,
|
||||
message: (err.formatted || `${err.name}: ${err.message}`)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
47
build/tasks/svgs.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import loconfig from '../../loconfig.json';
|
||||
import message from '../utils/message.js';
|
||||
import notification from '../utils/notification.js';
|
||||
import template from '../utils/template.js';
|
||||
import { basename } from 'node:path';
|
||||
import mixer from 'svg-mixer';
|
||||
|
||||
/**
|
||||
* Generates and transforms SVG spritesheets.
|
||||
*
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
export default async function compileSVGs() {
|
||||
loconfig.tasks.svgs.forEach(async ({
|
||||
includes,
|
||||
outfile
|
||||
}) => {
|
||||
const filename = basename(outfile || 'undefined');
|
||||
|
||||
const timeLabel = `${filename} compiled in`;
|
||||
console.time(timeLabel);
|
||||
|
||||
try {
|
||||
includes = includes.map((path) => template(path));
|
||||
outfile = template(outfile);
|
||||
|
||||
const result = await mixer(includes, {
|
||||
spriteConfig: {
|
||||
usages: false
|
||||
}
|
||||
});
|
||||
|
||||
await result.write(outfile);
|
||||
|
||||
message(`${filename} compiled`, 'success', timeLabel);
|
||||
} catch (err) {
|
||||
message(`Error compiling ${filename}`, 'error');
|
||||
message(err);
|
||||
|
||||
notification({
|
||||
title: `${filename} compilation failed 🚨`,
|
||||
message: `${err.name}: ${err.message}`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
86
build/utils/glob.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @file Retrieve the first available glob library.
|
||||
*
|
||||
* Note that options vary between libraries.
|
||||
*
|
||||
* Candidates:
|
||||
* - {@link https://npmjs.com/package/tiny-glob tiny-glob}
|
||||
* - {@link https://npmjs.com/package/globby}
|
||||
* - {@link https://npmjs.com/package/fast-glob fast-glob}
|
||||
* - {@link https://npmjs.com/package/glob glob}
|
||||
*/
|
||||
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
/**
|
||||
* @type {string[]} A list of packages to attempt import.
|
||||
*/
|
||||
const candidates = [
|
||||
'tiny-glob',
|
||||
'globby',
|
||||
'fast-glob',
|
||||
'glob',
|
||||
];
|
||||
|
||||
export default await importGlob();
|
||||
|
||||
/**
|
||||
* Imports the first available glob function.
|
||||
*
|
||||
* @throws {TypeError} If no glob library was found.
|
||||
* @return {function}
|
||||
*/
|
||||
async function importGlob() {
|
||||
let glob, module;
|
||||
|
||||
for (let name of candidates) {
|
||||
try {
|
||||
module = await import(name);
|
||||
|
||||
if (typeof module.default !== 'function') {
|
||||
throw new TypeError(`Expected ${name} to be a function`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the function to ensure
|
||||
* a common pattern.
|
||||
*/
|
||||
switch (name) {
|
||||
case 'tiny-glob':
|
||||
return createArrayableGlob(module.default, {
|
||||
filesOnly: true
|
||||
});
|
||||
|
||||
case 'glob':
|
||||
return promisify(module.default);
|
||||
|
||||
default:
|
||||
return module.default;
|
||||
}
|
||||
} catch (err) {
|
||||
// swallow this error; skip to the next candidate.
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
`No glob library was found, expected one of: ${modules.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper function for the glob function
|
||||
* to provide support for arrays of patterns.
|
||||
*
|
||||
* @param {function} glob - The glob function.
|
||||
* @param {object} options - The glob options.
|
||||
* @return {function}
|
||||
*/
|
||||
function createArrayableGlob(glob, options) {
|
||||
return (patterns, options) => {
|
||||
const globs = patterns.map((pattern) => glob(pattern, options));
|
||||
|
||||
return Promise.all(globs).then((files) => {
|
||||
return [].concat.apply([], files);
|
||||
});
|
||||
};
|
||||
}
|
||||
51
build/utils/message.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
export default function message(text, type, timerID) {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
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('');
|
||||
};
|
||||
41
build/utils/notification.js
Normal file
@@ -0,0 +1,41 @@
|
||||
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}
|
||||
*/
|
||||
export default 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);
|
||||
};
|
||||
14
build/utils/postcss.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @file If available, returns the PostCSS processor with any plugins.
|
||||
*/
|
||||
|
||||
try {
|
||||
var { default: postcss } = await import('postcss');
|
||||
let { default: autoprefixer } = await import('autoprefixer');
|
||||
|
||||
postcss = postcss([ autoprefixer ]);
|
||||
} catch (err) {
|
||||
postcss = null;
|
||||
}
|
||||
|
||||
export default postcss;
|
||||
111
build/utils/template.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @file Provides simple template tags.
|
||||
*/
|
||||
|
||||
import loconfig from '../../loconfig.json';
|
||||
|
||||
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 {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.
|
||||
*/
|
||||
export default function template(input, data) {
|
||||
const tags = [];
|
||||
|
||||
if (data) {
|
||||
data = flatten(data);
|
||||
} else {
|
||||
data = templateData;
|
||||
}
|
||||
|
||||
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 '';
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new object with all nested object properties
|
||||
* concatenated into it recursively.
|
||||
*
|
||||
* Nested keys are flattened into a property path:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* a: {
|
||||
* b: {
|
||||
* c: 1
|
||||
* }
|
||||
* },
|
||||
* d: 1
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* "a.b.c": 1,
|
||||
* "d": 1
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {object} input - The object to flatten.
|
||||
* @param {string} prefix - The parent key prefix.
|
||||
* @param {object} target - The object that will receive the flattened properties.
|
||||
* @return {object} Returns the `target` object.
|
||||
*/
|
||||
function flatten(input, prefix, target = {}) {
|
||||
for (let key in input) {
|
||||
let field = (prefix ? prefix + '.' + key : key);
|
||||
|
||||
if (typeof input[key] === 'object') {
|
||||
flatten(input[key], field, target);
|
||||
} else {
|
||||
target[field] = input[key];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes regular expression characters.
|
||||
*
|
||||
* @param {string} str - The input string.
|
||||
* @return {string} Returns the quoted (escaped) string.
|
||||
*/
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g, '\\$&');
|
||||
}
|
||||
@@ -1,22 +1,82 @@
|
||||
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 loconfig from '../loconfig.json';
|
||||
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 template from './utils/template.js';
|
||||
import server 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));
|
||||
const { paths, tasks } = loconfig;
|
||||
|
||||
const serverConfig = {
|
||||
open: false,
|
||||
notify: false
|
||||
};
|
||||
|
||||
if (typeof paths.url === 'string' && paths.url.length > 0) {
|
||||
// Use proxy
|
||||
serverConfig.proxy = paths.url;
|
||||
} else {
|
||||
// Use base directory
|
||||
serverConfig.server = {
|
||||
baseDir: paths.dest
|
||||
};
|
||||
}
|
||||
|
||||
function reload(done) {
|
||||
server.reload();
|
||||
done();
|
||||
}
|
||||
// Start the Browsersync server
|
||||
server.init(serverConfig);
|
||||
|
||||
export default watch;
|
||||
// Build scripts, compile styles, concat files,
|
||||
// and generate spritesheets on first hit
|
||||
concatFiles();
|
||||
compileScripts();
|
||||
compileStyles();
|
||||
compileSVGs();
|
||||
|
||||
// and call any methods on it.
|
||||
server.watch(
|
||||
[
|
||||
paths.views.src,
|
||||
join(paths.scripts.dest, '*.js'),
|
||||
join(paths.styles.dest, '*.css'),
|
||||
join(paths.svgs.dest, '*.svg'),
|
||||
]
|
||||
).on('change', server.reload);
|
||||
|
||||
// Watch scripts
|
||||
server.watch(
|
||||
[
|
||||
join(paths.scripts.src, '**/*.js'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileScripts();
|
||||
});
|
||||
|
||||
// Watch concats
|
||||
server.watch(
|
||||
tasks.concats.reduce(
|
||||
(patterns, { includes }) => patterns.concat(includes),
|
||||
[]
|
||||
).map((path) => template(path))
|
||||
).on('change', () => {
|
||||
concatFiles();
|
||||
});
|
||||
|
||||
// Watch styles
|
||||
server.watch(
|
||||
[
|
||||
join(paths.styles.src, '**/*.scss'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileStyles();
|
||||
});
|
||||
|
||||
// Watch svgs
|
||||
server.watch(
|
||||
[
|
||||
join(paths.svgs.src, '*.svg'),
|
||||
]
|
||||
).on('change', () => {
|
||||
compileSVGs();
|
||||
});
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import gulp from 'gulp';
|
||||
import styles from './build/styles.js';
|
||||
import scripts from './build/scripts.js';
|
||||
import concat from './build/concat.js';
|
||||
import svgs from './build/svgs.js';
|
||||
import serve from './build/serve.js';
|
||||
import watch from './build/watch.js';
|
||||
import copy from './build/copy.js';
|
||||
import { buildStyles, buildScripts } from './build/build.js';
|
||||
|
||||
const compile = gulp.series(styles, scripts, svgs, concat);
|
||||
const main = gulp.series(copy, compile, serve, watch);
|
||||
const build = gulp.series(copy, compile, buildStyles, buildScripts);
|
||||
|
||||
gulp.task('default', main);
|
||||
gulp.task('compile', compile);
|
||||
gulp.task('build', build);
|
||||
gulp.task('copy', copy);
|
||||
61
loconfig.json
Executable file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"paths": {
|
||||
"url": "locomotive-boilerplate.test",
|
||||
"src": "./assets",
|
||||
"dest": "./www",
|
||||
"images": {
|
||||
"src": "./assets/images"
|
||||
},
|
||||
"styles": {
|
||||
"src": "./assets/styles",
|
||||
"dest": "./www/assets/styles"
|
||||
},
|
||||
"scripts": {
|
||||
"src": "./assets/scripts",
|
||||
"dest": "./www/assets/scripts"
|
||||
},
|
||||
"svgs": {
|
||||
"src": "./assets/images/sprite",
|
||||
"dest": "./www/assets/images"
|
||||
},
|
||||
"views": {
|
||||
"src": "./views/boilerplate/template"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"concats": [
|
||||
{
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/vendors/*.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/vendors.js"
|
||||
}
|
||||
],
|
||||
"scripts": [
|
||||
{
|
||||
"includes": [
|
||||
"{% paths.scripts.src %}/app.js"
|
||||
],
|
||||
"outfile": "{% paths.scripts.dest %}/app.js"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"infile": "{% paths.styles.src %}/critical.scss",
|
||||
"outfile": "{% paths.styles.dest %}/critical.css"
|
||||
},
|
||||
{
|
||||
"infile": "{% paths.styles.src %}/main.scss",
|
||||
"outfile": "{% paths.styles.dest %}/main.css"
|
||||
}
|
||||
],
|
||||
"svgs": [
|
||||
{
|
||||
"includes": [
|
||||
"{% paths.svgs.src %}/*.svg"
|
||||
],
|
||||
"outfile": "{% paths.svgs.dest %}/sprite.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
33
mconfig.json
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"url": "boilerplate.test",
|
||||
"src": "./assets/",
|
||||
"dest": "./www/",
|
||||
"build": "./build/",
|
||||
"styles": {
|
||||
"src": "./assets/styles/",
|
||||
"dest": "./www/assets/styles/",
|
||||
"main": "main"
|
||||
},
|
||||
"scripts": {
|
||||
"src": "./assets/scripts/",
|
||||
"dest": "./www/assets/scripts/",
|
||||
"main": "app",
|
||||
"vendors": {
|
||||
"src": "./assets/scripts/vendors/",
|
||||
"main": "vendors"
|
||||
}
|
||||
},
|
||||
"svgs": {
|
||||
"src": "./assets/images/sprite/",
|
||||
"dest": "./www/assets/images/"
|
||||
},
|
||||
"views": {
|
||||
"src": "./views/boilerplate/template/"
|
||||
},
|
||||
"modules": {
|
||||
"build": "gulp",
|
||||
"style": "sass",
|
||||
"script": "js",
|
||||
"view": false
|
||||
}
|
||||
}
|
||||
12388
package-lock.json
generated
Normal file
32
package.json
@@ -4,15 +4,33 @@
|
||||
"title": "Locomotive Boilerplate",
|
||||
"version": "1.0.0",
|
||||
"author": "Locomotive <info@locomotive.ca>",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=14.17",
|
||||
"npm": ">=6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node --experimental-json-modules --no-warnings build/watch.js",
|
||||
"build": "node --experimental-json-modules --no-warnings build/build.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"locomotive-scroll": "git+https://git@github.com/locomotivemtl/locomotive-scroll.git",
|
||||
"normalize.css": "*",
|
||||
"pjax": "*",
|
||||
"smooth-scrollbar": "git+https://git@github.com/locomotivemtl/smooth-scrollbar.git#develop",
|
||||
"svg4everybody": "*"
|
||||
"a11y-dialog": "^7.3.0",
|
||||
"locomotive-scroll": "^4.1.3",
|
||||
"modujs": "^1.4.2",
|
||||
"modularload": "^1.2.6",
|
||||
"normalize.css": "^8.0.1",
|
||||
"svg4everybody": "^2.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/polyfill": "*",
|
||||
"gulp-concat": "*"
|
||||
"autoprefixer": "^10.3.7",
|
||||
"browser-sync": "^2.26.13",
|
||||
"concat": "^1.0.3",
|
||||
"esbuild": "^0.13.4",
|
||||
"kleur": "^4.1.4",
|
||||
"node-notifier": "^10.0.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"postcss": "^8.3.9",
|
||||
"svg-mixer": "^2.3.14",
|
||||
"tiny-glob": "^0.2.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Boilerplate</title>
|
||||
<title>Email | Locomotive Boilerplate</title>
|
||||
</head>
|
||||
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
|
||||
<center>
|
||||
|
||||
BIN
www/assets/images/favicons/android-chrome-144x144.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 795 B After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -2,28 +2,56 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
width="160.000000pt" height="160.000000pt" viewBox="0 0 160.000000 160.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
<g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M900 3500 l0 -3501 1513 4 c831 3 1530 7 1552 10 22 3 67 8 100 12
|
||||
33 3 76 8 95 11 19 3 53 7 75 10 52 6 96 14 115 19 8 2 33 7 56 9 104 13 399
|
||||
99 549 161 437 179 770 482 952 865 62 130 121 311 148 451 31 159 43 316 40
|
||||
519 -1 113 -5 221 -9 240 -3 19 -8 49 -10 65 -44 307 -196 607 -420 830 -195
|
||||
194 -362 287 -696 392 -9 3 -2 10 20 20 153 68 254 126 365 208 246 183 446
|
||||
475 530 775 23 83 29 109 41 179 3 18 8 43 11 55 13 52 11 444 -2 541 -68 478
|
||||
-242 804 -580 1088 -120 100 -300 206 -515 300 -120 53 -479 150 -635 172 -23
|
||||
3 -50 7 -60 9 -11 2 -45 7 -76 10 -31 4 -66 9 -78 11 -11 2 -62 7 -113 11 -51
|
||||
3 -104 9 -118 11 -14 3 -661 7 -1437 10 l-1413 4 0 -3501z m2765 2315 c149
|
||||
-22 167 -26 240 -46 120 -34 233 -85 304 -137 71 -51 148 -141 189 -219 69
|
||||
-133 93 -240 97 -438 7 -258 -44 -433 -170 -585 -132 -158 -387 -275 -662
|
||||
-304 -29 -3 -77 -9 -105 -12 -40 -4 -1026 -8 -1189 -5 l-26 1 0 880 -1 881
|
||||
617 -2 c447 -1 641 -5 706 -14z m210 -2779 c469 -79 716 -328 773 -781 8 -68
|
||||
8 -266 -1 -341 -13 -115 -72 -271 -135 -359 -167 -234 -437 -367 -794 -391
|
||||
-71 -4 -1246 -9 -1354 -5 l-21 1 -1 938 c0 515 2 941 5 945 6 9 1470 3 1528
|
||||
-7z"/>
|
||||
<path d="M540 1593 c-29 -12 -65 -61 -61 -85 0 -5 -7 -8 -16 -8 -10 0 -29 -11
|
||||
-43 -25 -14 -14 -40 -38 -59 -54 -31 -25 -35 -34 -35 -77 -1 -27 -7 -55 -13
|
||||
-62 -7 -7 -13 -20 -13 -28 0 -8 -10 -14 -24 -14 -28 0 -36 -19 -31 -70 2 -19
|
||||
-1 -42 -5 -50 -5 -9 -4 -21 4 -30 21 -24 86 -208 83 -232 -3 -14 -12 -23 -28
|
||||
-25 -40 -6 -49 -11 -49 -33 0 -11 -11 -43 -25 -70 -14 -27 -25 -57 -25 -66 0
|
||||
-10 -6 -25 -14 -33 -13 -14 -12 -21 4 -56 11 -22 19 -47 20 -55 0 -8 8 -28 18
|
||||
-45 27 -46 26 -79 -3 -95 -14 -7 -25 -20 -25 -27 0 -16 -48 -83 -155 -215 -25
|
||||
-32 -42 -59 -38 -59 29 -4 220 -5 231 -1 10 3 9 10 -3 28 -14 22 -14 24 2 24
|
||||
10 0 26 -14 35 -30 36 -63 121 -66 161 -5 22 34 57 47 57 22 0 -20 43 -57 76
|
||||
-66 30 -8 72 7 93 33 8 10 30 13 75 11 56 -3 65 -1 69 16 8 31 56 21 104 -23
|
||||
40 -35 56 -42 108 -41 38 1 42 3 88 43 53 47 68 49 96 14 29 -37 84 -62 126
|
||||
-58 37 3 85 32 108 64 10 14 29 20 73 24 57 4 59 5 59 32 0 24 -5 30 -27 32
|
||||
-16 2 -28 9 -28 15 0 7 -3 22 -6 33 -4 15 0 23 13 26 16 4 19 14 17 54 -2 65
|
||||
-1 62 -23 69 -18 5 -19 18 -17 263 1 288 5 303 69 320 31 8 32 9 32 66 1 31
|
||||
-4 60 -10 64 -19 12 -617 8 -630 -4 -6 -7 -10 -36 -9 -65 2 -70 2 -69 17 -69
|
||||
9 0 12 -22 11 -78 0 -43 -3 -83 -7 -89 -8 -13 -53 -9 -73 8 -11 9 -18 9 -32
|
||||
-4 -16 -15 -45 -10 -43 7 1 3 1 7 0 9 0 1 -2 13 -5 27 -13 72 -115 64 -129
|
||||
-10 -2 -12 -10 -28 -16 -36 -10 -12 -15 -12 -33 -1 -14 10 -29 11 -44 6 -16
|
||||
-6 -22 -5 -22 6 0 16 0 16 -50 18 -32 2 -35 5 -34 32 1 34 61 202 81 226 7 9
|
||||
10 19 7 22 -4 4 -8 36 -9 71 -2 64 -2 65 19 50 11 -7 30 -14 41 -14 13 0 25
|
||||
-11 33 -29 17 -38 58 -53 99 -36 17 7 34 22 37 33 4 11 15 27 26 37 25 23 26
|
||||
61 1 76 -24 16 -16 47 15 55 13 3 24 10 24 14 0 19 34 40 65 40 26 0 39 6 51
|
||||
25 13 21 24 25 51 23 41 -3 59 16 50 53 -7 26 -41 36 -63 18 -9 -7 -15 -5 -21
|
||||
6 -5 8 -23 19 -40 24 -26 8 -34 5 -51 -12 -54 -60 -76 -63 -107 -15 -21 34
|
||||
-58 49 -90 38 -12 -4 -31 0 -50 12 -29 17 -84 23 -115 11z m806 -705 c11 -11
|
||||
15 -39 16 -104 0 -49 0 -95 -1 -101 -1 -10 -38 -13 -151 -13 l-149 0 2 110 c2
|
||||
74 6 113 14 118 7 5 67 7 133 6 93 -1 125 -5 136 -16z m-1139 -726 c-1 -57 -3
|
||||
-68 -18 -69 -14 -1 -16 3 -8 30 5 18 11 48 13 67 5 42 5 40 9 40 2 0 4 -31 4
|
||||
-68z m-108 -4 c-25 -47 -41 -68 -50 -62 -5 3 -2 12 7 21 8 9 22 26 31 39 19
|
||||
30 27 31 12 2z m39 -25 c-16 -39 -32 -54 -18 -19 12 33 19 46 25 46 3 0 -1
|
||||
-12 -7 -27z m-30 -8 c-6 -14 -15 -25 -20 -25 -5 0 -1 11 8 25 9 14 18 25 20
|
||||
25 2 0 -2 -11 -8 -25z m55 -5 c-3 -11 -10 -23 -16 -27 -5 -3 -8 -2 -5 3 3 5 8
|
||||
17 11 27 9 24 16 22 10 -3z m152 0 c-3 -5 -13 -10 -21 -10 -8 0 -14 5 -14 10
|
||||
0 6 9 10 21 10 11 0 17 -4 14 -10z m105 -5 c-12 -15 -30 -12 -30 6 0 5 10 9
|
||||
21 9 18 0 19 -2 9 -15z m115 5 c-3 -5 -10 -10 -16 -10 -5 0 -9 5 -9 10 0 6 7
|
||||
10 16 10 8 0 12 -4 9 -10z m458 -3 c-2 -4 -3 -14 -3 -23 0 -11 -6 -14 -22 -8
|
||||
-30 9 -32 10 -25 29 4 9 15 14 29 11 13 -1 22 -6 21 -9z m68 -3 c14 -18 10
|
||||
-23 -21 -26 -19 -2 -25 2 -26 17 -1 25 29 31 47 9z m245 -6 c-1 -25 -23 -33
|
||||
-45 -19 -12 8 -12 12 -2 25 20 24 48 20 47 -6z m64 12 c13 -9 12 -12 -4 -25
|
||||
-29 -22 -42 -18 -42 10 0 27 19 33 46 15z m-1027 -36 c-2 -7 -11 -10 -19 -7
|
||||
-17 6 -18 15 -2 31 14 14 29 -3 21 -24z m39 -5 c-19 -7 -27 4 -18 28 6 14 8
|
||||
14 21 -3 12 -17 11 -20 -3 -25z m188 10 c0 -17 -3 -18 -20 -9 -16 9 -18 13 -8
|
||||
24 16 17 28 11 28 -15z m45 -11 c-19 -11 -30 -5 -21 11 4 6 14 7 22 4 15 -5
|
||||
14 -7 -1 -15z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.7 KiB |
1
www/assets/images/sprite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs></defs></svg>
|
||||
|
After Width: | Height: | Size: 123 B |
7
www/assets/scripts/app.js.map
Normal file
2
www/assets/scripts/jquery-3.3.1.min.js
vendored
1
www/assets/styles/critical.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"critical.css"}
|
||||
1
www/assets/styles/main.css.map
Normal file
BIN
www/favicon.ico
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
105
www/form.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<!doctype html>
|
||||
<html class="is-loading" lang="en" data-page="page">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Form | Locomotive Boilerplate</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
|
||||
|
||||
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print" onload="this.media='all'; this.onload=null; this.isLoaded=true">
|
||||
</head>
|
||||
<body data-module-load>
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="images.html">Images</a></li>
|
||||
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||
<li><a href="modal.html">Modal</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-scroll-section>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Page</h1>
|
||||
|
||||
<div class="o-layout">
|
||||
<div class="o-layout_item u-1/2@from-small">
|
||||
|
||||
<form class="c-form">
|
||||
<div class="c-form_item">
|
||||
<label class="c-form_label" for="input">Input</label>
|
||||
<input class="c-form_input" type="text" id="input">
|
||||
</div>
|
||||
<div class="c-form_item">
|
||||
<div class="o-layout -gutter">
|
||||
<div class="o-layout_item u-1/2">
|
||||
<input class="c-form_checkbox" type="checkbox" id="checkbox">
|
||||
<label class="c-form_checkboxLabel" for="checkbox">Checkbox</label>
|
||||
</div>
|
||||
<div class="o-layout_item u-1/2">
|
||||
<input class="c-form_checkbox" type="checkbox" id="checkbox2">
|
||||
<label class="c-form_checkboxLabel" for="checkbox2">Checkbox 2</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-form_item">
|
||||
<div class="o-layout -gutter">
|
||||
<div class="o-layout_item u-1/2">
|
||||
<input class="c-form_radio" type="radio" name="radio" id="radio">
|
||||
<label class="c-form_radioLabel" for="radio">Radio</label>
|
||||
</div>
|
||||
<div class="o-layout_item u-1/2">
|
||||
<input class="c-form_radio" type="radio" name="radio" id="radio2">
|
||||
<label class="c-form_radioLabel" for="radio2">Radio 2</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-form_item">
|
||||
<label class="c-form_label" for="select">Select</label>
|
||||
<div class="c-form_select">
|
||||
<select class="c-form_select_input" id="select">
|
||||
<option value="option">Option</option>
|
||||
<option value="option2">Option 2</option>
|
||||
<option value="option3">Option 3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-form_item">
|
||||
<label class="c-form_label" for="textarea">Textarea</label>
|
||||
<textarea class="c-form_textarea" id="textarea"></textarea>
|
||||
</div>
|
||||
<button class="c-button" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js" crossorigin="anonymous"></script>
|
||||
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="assets/scripts/vendors.js" defer></script>
|
||||
<script src="assets/scripts/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
131
www/images.html
Normal file
@@ -0,0 +1,131 @@
|
||||
<!doctype html>
|
||||
<html class="is-loading" lang="en" data-page="images">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Images | Locomotive Boilerplate</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
|
||||
|
||||
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print" onload="this.media='all'; this.onload=null; this.isLoaded=true">
|
||||
</head>
|
||||
<body data-module-load>
|
||||
<div data-load-container>
|
||||
<div class="o-scroll" data-module-scroll="main">
|
||||
<header data-scroll-section>
|
||||
<a href="/"><h1>Locomotive Boilerplate</h1></a>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="images.html">Images</a></li>
|
||||
<li><a href="form.html" data-load="customTransition">Form</a></li>
|
||||
<li><a href="modal.html">Modal</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main data-scroll-section>
|
||||
<div class="o-container">
|
||||
<h1 class="c-heading -h1">Images</h1>
|
||||
|
||||
<section>
|
||||
<h2 class="c-heading -h2">Lazy load demo</h2>
|
||||
|
||||
<h3 class="c-heading -h3">Basic</h3>
|
||||
|
||||
<div style="width: 640px; max-width: 100%;">
|
||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
|
||||
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/640/480?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=1);"></div>
|
||||
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=2);"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="c-heading -h3">Relative to scroll</h3>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & img</h3>
|
||||
|
||||
<div style="width: 640px; max-width: 100%;">
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=3" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=4" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
<div class="o-ratio u-4:3">
|
||||
<img data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=5" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=1"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=2"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=3"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=4"></div>
|
||||
<div style="background-size: cover; background-position: center;" class="o-ratio u-16:9" data-scroll data-scroll-call="lazyLoad, Scroll, main" data-src="http://picsum.photos/640/480?v=5"></div>
|
||||
</div>
|
||||
|
||||
<h4 class="c-heading -h3">Using SVG viewport for ratio</h3>
|
||||
|
||||
<div style="width: 480px; max-width: 100%;">
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=6"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=7"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
|
||||
<img
|
||||
data-scroll
|
||||
data-scroll-call="lazyLoad, Scroll, main"
|
||||
data-src="http://picsum.photos/640/480?v=8"
|
||||
alt=""
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 480'%3E%3C/svg%3E"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer data-scroll-section>
|
||||
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js" crossorigin="anonymous"></script>
|
||||
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="assets/scripts/vendors.js" defer></script>
|
||||
<script src="assets/scripts/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||