├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .jshintrc
├── .stylelintrc
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── css
│ ├── _abstracts
│ │ ├── mixins.scss
│ │ └── variables.scss
│ ├── _base
│ │ ├── custom-reset.scss
│ │ └── global.scss
│ ├── _critical
│ │ └── init.scss
│ ├── _fonts.css
│ ├── _global-components
│ │ ├── footer.scss
│ │ ├── header.scss
│ │ └── mini-cart.scss
│ ├── _pages
│ │ ├── 404.scss
│ │ ├── about.scss
│ │ └── home.scss
│ └── main.scss
├── fonts
│ └── .gitkeep
├── images
│ ├── .gitkeep
│ ├── meta
│ │ ├── facebook-share.png
│ │ └── twitter-share.png
│ └── temp
│ │ ├── shift-image-2.jpg
│ │ ├── shift-image-3.jpg
│ │ └── shift-image.jpg
├── js
│ ├── _classes
│ │ └── ScrollBasedAnims.js
│ ├── _global
│ │ ├── _renderer.js
│ │ ├── anims.js
│ │ ├── helpers.js
│ │ └── storage.js
│ ├── _initialize.js
│ ├── _transitions
│ │ └── basicFade.js
│ ├── _worker
│ │ ├── _init.js
│ │ └── workers
│ │ │ └── image-load.js
│ ├── about
│ │ ├── _renderer.js
│ │ └── anims.js
│ ├── home
│ │ ├── _renderer.js
│ │ └── anims.js
│ ├── main.js
│ └── routing.js
└── templates
│ ├── 404.php
│ ├── about.php
│ ├── footer.php
│ ├── header.php
│ ├── index.php
│ └── parts
│ ├── footer-content.php
│ ├── global-scope.php
│ ├── site
│ ├── site-meta.php
│ └── site-prefetch.php
│ └── svgs
│ ├── social-facebook.php
│ ├── social-instagram.php
│ └── social-twitter.php
├── web
└── site
│ ├── .htaccess
│ ├── index.php
│ └── robots.txt
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = tab
7 | indent_size = 4
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.php]
14 | indent_size = 4
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "parserOptions": {
4 | "ecmaVersion": 2017,
5 | "sourceType": "module"
6 | },
7 | "globals": {
8 | "angular": false,
9 | "module": false,
10 | "inject": false,
11 | "document": false
12 | },
13 | "env": {
14 | "browser": true,
15 | "amd": true,
16 | "es6": true
17 | },
18 | "rules": {
19 | "no-unused-vars": "off",
20 | "no-case-declarations": "off",
21 | "no-console": "off"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 |
3 | # Application
4 | web/config.php
5 | !web/config.*.php
6 | web/cache/*
7 | web/app/views/*
8 | web/site/*
9 | !web/site/server
10 | !web/site/index.php
11 | !web/site/.htaccess
12 | !web/site/robots.txt
13 |
14 | # Dotenv
15 | !.env
16 | .env.*
17 | !.env.example
18 |
19 | # Vendor (e.g. Composer)
20 | web/vendor/*
21 |
22 | # Compiled
23 | compiled
24 |
25 | # Node Package Manager
26 | node_modules
27 |
28 | # Vagrant
29 | bin
30 | .vagrant
31 |
32 | .idea/
33 | composer.phar
34 | composer.lock
35 | package.lock
36 | npm-debug.log
37 |
38 | # Stray Files
39 | .DS_Store
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6
3 | }
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 |
4 | }
5 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Localhost Setup
2 | 1. From the project root in terminal, run NPM install
3 | 2. To start the project now, setup your watch: `npm run watch-dev` or `npm run watch` to minimize for prod builds
4 | 3. In the terminal, navigate to `web/site` and then type `php -S localhost:8000` to start your virtual server,
5 | or point MAMP to the `web/site` directory
6 |
7 | ## Core performance principles
8 |
9 | 1. Avoid network requests during animations whenever possible. Load images as blobs with web workers, prefetch priority pages on doc ready when the thread is idle, etc
10 | 2. Never autoplay videos. Use the scroll anims library to play/pause/restart them when in/out of view. See `playPauseVideos()` in the scroll based anims class.
11 | 3. Taking scroll measurements should be done only once (and on resize). Notice the order of image loading & function calls in `/js/_global/_renderer` `globalEvents` function.
12 | There is a reason the preload-critical image workers are fired, and when those + the lead-in transition (either initial or PJAX transition) is complete, we take the scroll
13 | measurements. It's a balance of waiting for unavoidable network requests to be finished, for animations to be finished, etc.. and then instantiating the scroll based anims.
14 | The actual class call and all calculations are very fast, but can cause dropped frames if you fire it too soon during your transitions.
15 | 4. Take all animation measurements once (lots of BCR calls) when the class is instantiated, never on the fly in the animation loop. Avoid anything else that might cause a document reflow
16 | such as adding/removing class names, querying an element's offsetHeight, etc. [What causes layout/reflow](https://gist.github.com/paulirish/5d52fb081b3570c81e3a).
17 | 5. Hyper optimize anything that fires in RAF. Use for loops with cached length values instead of .forEach (~8x faster), never check or set anything that doesn't need to be
18 | checked/set (use flags).
19 | 6. All media should have it's an intrinsic height set, except for elements whose height do not affect document flow. With the goal of only taking our animation measurements once,
20 | plus not waiting for all images to do so, it's critical that all images somehow have a height in the CSS. Consider using an `.ar-wrapper` container to set the aspect ratio with padding bottom
21 | and positioning the media absolutely within.
22 |
23 | ## PJAX specifics
24 |
25 | 1. Pacing is everything. It's not about what is actually happening, it's about what the user perceives to be happening. With highway we have total freedom during transitions & page renderers to pace things as needed.
26 | 2. Store important elements, timeline references, measurements etc in globalStorage objects (see `/js/_global/storage.js`). When moving between pages, you will often need to reverse a global timeline,
27 | update the namespace, check measurement values, etc.
28 | 3. Know when certain aspects of your transitions are complete by modifying the above mentioned globalStorage values. This helps greatly with the pacing. Notice the setInterval
29 | in the critical images callback in the global renderer.
30 |
31 | ### Advanced uses
32 | 1. By adding contextual transitions when instantiating highway in `/js/routing.js`, you can place `data-transition="transitionName"` to an anchor tag to use custom transitions on the fly.
33 | The contextual transitions can also be used when calling a redirect programmatically, ie, `H.redirect(fullHref, transitionName)`
34 | 2. Often times contextual transitions are used in conjunction with a strategy called "overlapping transitions", whereby you strategically call `from.remove()` in the transition when you know
35 | the next page's elements are in position and loaded. In this way, you can create transitions that make certain elements appear to persist across views. For example, from the footer of one page
36 | to the header of the next might have identical elements. By using a contextual transition to take you from the footer to the next page hero, and animating/timing your transition correctly,
37 | you can make those identical elements appear to have never changed when in reality they were replaced by the new elements.
38 |
39 | ## Scroll based anims class specifics
40 |
41 | #### Shout out to [Jesper Landberg](https://twitter.com/Jesper_Landberg) whose original scroll class logic guided and inspired this development
42 |
43 | ### (See comments in file)
44 | 1. The scroll based animations file can be set with a flag to use either virtual scroll or the native window scrollY value. Regardless, mobile and Firefox use the native scrollY value.
45 | 2. There are more advanced usages, but basically the pattern will always be the same. Create a data collection function that gets bounding data, call it in `getCache()`, then create an
46 | animation function that animates against that data and call it in the `run()` function.
47 | 3. If you want to use native scroll, you'll have to set some body styles accordingly. See `/css/_critical/init.scss`. the `data-smooth` attributes on the page sections won't do anything if
48 | `this.isVS` is set to false.
49 |
50 | ### Advanced uses
51 | With the data available regardless of device, there's really nothing you can't do with the `ScrollBasedAnims` class.
52 | 1. As you'll see with the hero and footer element animations, I like to use what I refer to as a measure elements (`.measure-el`). When nested inside of a section we want to animate
53 | and given a certain height & top positioning, these can be used to pan through more complicated timelines (what I sometimes refer to as scenes). The scene might be more complicated and the timeline created
54 | within the class instead of being extracted from the markup, but the core strategy remains the same. You collect the measure element bounding data so you can track it's progress, and you pan through
55 | a timeline based on that. Typically this involves animations that take place over several viewport heights.
56 | 2. Fixed positioning isn't as straight forward when using virtual scroll, but overall virtual scroll produces a better fixed position experience because we're animating the fixed element on every frame
57 | and never swapping css position value from fixed to unfixed which can cause jumps. Basically the strategy is to set the css position to fixed, and at the bottom of the section that it remains fixed through, you
58 | place a measure element (remember `.measure-el`) and as that element enters the viewport, transform the fixed position element up and out of view. See [this page](https://plenaire.co/products/remedy/), the `.measure-el`
59 | at the bottom of the `.right-content` in the first part of that page. I actually had to nudge that with JS because the left side collapses, but the point is as the measure element enters, the fixed element will scroll up
60 | out of view and back down when scrolling up.
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zero-shopify-prismic-php",
3 | "author": "Zero Studios",
4 | "license": "UNLICENSED",
5 | "private": true,
6 | "version": "0.9.9",
7 | "description": "A PHP based solution for centralizing Shopify and Prismic.",
8 | "devDependencies": {
9 | "@babel/core": "^7.8.6",
10 | "@babel/plugin-transform-runtime": "^7.8.3",
11 | "@babel/preset-env": "^7.8.6",
12 | "@babel/runtime": "^7.8.4",
13 | "ajv": "^6.12.0",
14 | "assets-loader": "^0.5",
15 | "babel-cli": "^6.26.0",
16 | "babel-loader": "^8.0.6",
17 | "babel-preset-env": "^1.7.0",
18 | "babel-preset-es2015": "^6.24.1",
19 | "copy-webpack-plugin": "^4.5.1",
20 | "core-js": "^3.6.4",
21 | "core-js-compat": "^3.6.4",
22 | "css-loader": "^0.26.4",
23 | "eslint": "^5.16.0",
24 | "eslint-loader": "^2.2.1",
25 | "file-loader": "^0.10.1",
26 | "friendly-errors-webpack-plugin": "^1.7.0",
27 | "generate-json-webpack-plugin": "^0.3.1",
28 | "glob": "^7.1.6",
29 | "mini-css-extract-plugin": "^0.4.0",
30 | "node-sass": "^4.13.1",
31 | "optimize-css-assets-webpack-plugin": "^4.0.2",
32 | "postcss-import": "^11.1.0",
33 | "postcss-loader": "^2.1.5",
34 | "postcss-scss": "^1.0.5",
35 | "sass-loader": "^7.3.1",
36 | "style-loader": "^0.13.2",
37 | "stylelint": "^8.0.0",
38 | "stylelint-scss": "^3.14.2",
39 | "stylelint-webpack-plugin": "^0.10.5",
40 | "uglifyjs-webpack-plugin": "^1.2.5",
41 | "url-loader": "^1.1.2",
42 | "webpack": "^4.41.6",
43 | "webpack-cli": "^3.3.11",
44 | "webpack-glob-entry": "^2.1.1",
45 | "webpack-google-cloud-storage-plugin": "^0.9.0"
46 | },
47 | "dependencies": {
48 | "@dogstudio/highway": "^2.1.3",
49 | "flatpickr": "^2.4.2",
50 | "flickity": "^2.2.1",
51 | "form-serialize": "^0.7.2",
52 | "gsap": "^3.2.4",
53 | "intersection-observer": "^0.7.0",
54 | "lodash.throttle": "^4.1.1",
55 | "moment": "^2.24.0",
56 | "quicklink": "^1.0.1",
57 | "virtual-scroll": "^1.5.2"
58 | },
59 | "optionalDependencies": {
60 | "ismobilejs": "^0.4.1",
61 | "js-cookie": "^2.2.0"
62 | },
63 | "scripts": {
64 | "build": "webpack",
65 | "watch": "webpack --watch",
66 | "watch-dev": "webpack --watch --mode=development"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | syntax: "postcss-scss",
3 | plugins: {
4 | "postcss-import": {},
5 | "autoprefixer": {
6 | browsers: ["last 2 versions","ie 11"]
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/src/css/_abstracts/mixins.scss:
--------------------------------------------------------------------------------
1 | /* ------------------------------------------------------------------ */
2 | /* MIXINS
3 |
4 | - Global mixins used across the site
5 | /* ------------------------------------------------------------------ */
6 |
7 | /* ----- MEDIA QUERIES ----- */
8 | @mixin bp-320 {
9 | @media screen and (max-width: 320px) {
10 | @content;
11 | }
12 | }
13 | @mixin bp-370 {
14 | @media screen and (max-width: 370px) {
15 | @content;
16 | }
17 | }
18 | @mixin bp-480 {
19 | @media screen and (max-width: 480px) {
20 | @content;
21 | }
22 | }
23 | @mixin bp-767 {
24 | @media screen and (max-width: 767px) {
25 | @content;
26 | }
27 | }
28 | @mixin bp-959 {
29 | @media screen and (max-width: 959px) {
30 | @content;
31 | }
32 | }
33 | @mixin bp-1024 {
34 | @media screen and (max-width: 1024px) {
35 | @content;
36 | }
37 | }
38 | @mixin bp-1200 {
39 | @media screen and (max-width: 1200px) {
40 | @content;
41 | }
42 | }
43 | @mixin bp-1440 {
44 | @media screen and (max-width: 1440px) {
45 | @content;
46 | }
47 | }
48 | @mixin bp-1680 {
49 | @media screen and (max-width: 1680px) {
50 | @content;
51 | }
52 | }
53 | @mixin bp-1920 {
54 | @media screen and (max-width: 1920px) {
55 | @content;
56 | }
57 | }
58 |
59 | /* ----- SIZE ----- */
60 | @function get-vw($target) {
61 | $vw-context: (1440 * 0.01) * 1px;
62 | @return ($target / $vw-context) * 1vw;
63 | }
64 |
--------------------------------------------------------------------------------
/src/css/_abstracts/variables.scss:
--------------------------------------------------------------------------------
1 | /* ------------------------------------------------------------------ */
2 | /* VARIABLES
3 |
4 | - Global variables used across the site
5 | - Colors, breakpoints, easings, global sizes etc
6 | /* ------------------------------------------------------------------ */
7 |
8 | /* ----- COLORS ----- */
9 | $white: #FFFFFF;
10 | $black: #2c2c2c;
11 |
12 | /* ----- FONTS ----- */
13 | //$plaidXL: "Plaid-XL", sans-serif;
14 | //$moderatMd: "Moderat-Medium", sans-serif;
15 | //$moderatRg: "Moderat-Regular", sans-serif;
16 |
17 | /* ----- DEFAULT IMAGE PATH ----- */
18 | $imgpath: "https://project.cdn.prismic.io/project/";
19 |
20 | /* ----- EASINGS ----- */
21 |
22 | /* Default Equations */
23 | $linear: cubic-bezier(0.25, 0.25, 0.75, 0.75);
24 | $ease: cubic-bezier(0.25, 0.1, 0.25, 1);
25 | $ease-in: cubic-bezier(0.42, 0, 1, 1);
26 | $ease-out: cubic-bezier(0, 0, 0.58, 1);
27 | $ease-in-out: cubic-bezier(0.42, 0, 0.58, 1);
28 |
29 | /* Penner Equations (approximated) originally created by @robpenner */
30 | $ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
31 | $ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
32 | $ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
33 | $ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
34 | $ease-in-sine: cubic-bezier(0.47, 0, 0.745, 0.715);
35 | $ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
36 | $ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
37 | $ease-in-back: cubic-bezier(0.6, -0.28, 0.735, 0.045);
38 |
39 | $ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
40 | $ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
41 | $ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
42 | $ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
43 | $ease-out-sine: cubic-bezier(0.39, 0.575, 0.565, 1);
44 | $ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
45 | $ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
46 | $ease-out-back: cubic-bezier(0.175, 0.885, 0.32, 1.275);
47 |
48 | $ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
49 | $ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
50 | $ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
51 | $ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
52 | $ease-in-out-sine: cubic-bezier(0.445, 0.05, 0.55, 0.95);
53 | $ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
54 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
55 | $ease-in-out-back: cubic-bezier(0.68, -0.55, 0.265, 1.55);
56 |
--------------------------------------------------------------------------------
/src/css/_base/custom-reset.scss:
--------------------------------------------------------------------------------
1 | /* Main Scrollbar */
2 | ::-webkit-scrollbar {
3 | width: 0;
4 | }
5 |
6 | ::-webkit-scrollbar-button{
7 |
8 | }
9 |
10 | ::-webkit-scrollbar-track{
11 |
12 | }
13 |
14 | ::-webkit-scrollbar-track-piece{
15 |
16 | }
17 |
18 | ::-webkit-scrollbar-thumb{
19 |
20 | }
21 |
22 | ::-webkit-scrollbar-corner{
23 |
24 | }
25 |
26 | ::-webkit-resizer{
27 |
28 | }
29 |
30 | h1,h2,h3,h4,h5,h5 {
31 | margin: 0;
32 | }
33 | ul {
34 | margin: 0;
35 | padding: 0;
36 | }
37 | li {
38 | list-style-type: none;
39 | }
40 | button,
41 | input,
42 | textarea,
43 | select {
44 | -webkit-appearance: none;
45 | outline: none;
46 | border-radius: 0;
47 | background-color: transparent;
48 | }
49 | input, textarea {
50 | background: transparent;
51 | border: none;
52 |
53 | }
54 | body {
55 | margin: 0;
56 | }
57 |
58 | a {
59 | text-decoration: none;
60 | color: inherit;
61 | }
62 |
63 | p {
64 | margin: 0;
65 | a {
66 | text-decoration: underline;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/css/_base/global.scss:
--------------------------------------------------------------------------------
1 | svg, img {
2 | height: 100%;
3 | display: block;
4 | }
5 | video {
6 | object-fit: cover;
7 | object-position: center;
8 | }
9 | .ar-wrapper {
10 | width: 100%;
11 | position: relative;
12 | height: 0;
13 | padding-bottom: 100%;
14 | }
15 | .bg {
16 | background-size: cover;
17 | background-position: center;
18 | }
19 | .btn {
20 | cursor: pointer;
21 | width: 225px;
22 | height: 50px;
23 | border-radius: 5px;
24 | font-size: 11px;
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | text-transform: uppercase;
29 | }
30 | .measure-el {
31 | position: absolute;
32 | width: 1px;
33 | right: 0;
34 | }
35 | .container-120 {
36 | width: 100%;
37 | max-width: 1440px;
38 | margin: 0 auto;
39 | padding: 0 120px;
40 | display: flex;
41 | align-items: center;
42 | @include bp-1200 {
43 | padding: 0 80px;
44 | }
45 | @include bp-959 {
46 | padding: 0 40px;
47 | }
48 | @include bp-767 {
49 | padding: 0 30px;
50 | .left, .right {
51 | width: 100%;
52 | }
53 | }
54 | @include bp-370 {
55 | padding: 0 20px;
56 | }
57 | }
58 | .container-90 {
59 | width: 100%;
60 | max-width: 1440px;
61 | margin: 0 auto;
62 | padding: 0 90px;
63 | display: flex;
64 | align-items: center;
65 | @include bp-1200 {
66 | padding: 0 70px;
67 | }
68 | @include bp-959 {
69 | padding: 0 40px;
70 | }
71 | @include bp-767 {
72 | padding: 0 30px;
73 | .left, .right {
74 | width: 100%;
75 | }
76 | }
77 | @include bp-370 {
78 | padding: 0 20px;
79 | }
80 | }
81 | .cml-120 {
82 | margin-left: 120px;
83 | @include bp-1200 {
84 | margin-left: 80px;
85 | }
86 | @include bp-959 {
87 | margin-left: 40px;
88 | }
89 | @include bp-767 {
90 | margin-left: 30px;
91 | }
92 | @include bp-370 {
93 | margin-left: 20px;
94 | }
95 | }
96 | .hidden {
97 | display: none !important;
98 | visibility: hidden;
99 | }
100 | main {
101 | display: block;
102 | }
103 | .view {
104 | width: 100%;
105 | }
106 | .hero {
107 | .measure-el {
108 | height: 55%;
109 | top: 0;
110 | @include bp-767 {
111 | height: 80%;
112 | }
113 | }
114 | }
115 | section {
116 | position: relative;
117 | z-index: 2;
118 | }
119 |
120 | // TOUCH DEVICE STYLES...
121 | // THINGS ARE GONNA GET PRETTY SPECIFIC HERE
122 | html {
123 | &.touch {
124 | #footer {
125 | h5 {
126 | display: none;
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/css/_critical/init.scss:
--------------------------------------------------------------------------------
1 | /*
2 | This file is to store the minimum css needed to
3 | render the page upon first load.
4 |
5 | The DOM file load order will look like:
6 |
7 | _fonts.css
8 | _init.css
9 | main.js
10 | main.css
11 |
12 | -------------------------------------------------- */
13 | @import "../_abstracts/variables";
14 |
15 | /* Init CSS
16 | -------------------------------------------------- */
17 |
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | html,
23 | html a {
24 | -webkit-font-smoothing: antialiased !important;
25 | -moz-osx-font-smoothing: grayscale;
26 | }
27 |
28 | // NATIVE SCROLL BODY STYLES
29 | //html,
30 | //body {
31 | // width: 100%;
32 | // overflow-x: hidden;
33 | // overflow-y: auto;
34 | // &.touch {
35 | //
36 | // }
37 | // &.locked {
38 | // overflow-y: hidden;
39 | // }
40 | //}
41 |
42 | // VS STYLES.. IF USING NATIVE SCROLL COMMENT THIS BLOCK OUT AND UNCOMMENT THE ABOVE BLOCK
43 | body {
44 | width: 100%;
45 | height: 100%;
46 | min-height: 100%;
47 | max-height: 100%;
48 | overflow: hidden;
49 | &.locked {
50 | overflow-y: hidden;
51 | }
52 | }
53 | html {
54 | width: 100%;
55 | &.touch {
56 | overflow-x: hidden;
57 | overflow-y: auto;
58 | body {
59 | height: auto;
60 | max-height: none;
61 | min-height: 0;
62 | overflow-x: hidden;
63 | overflow-y: auto;
64 | }
65 | }
66 | }
67 | #container {
68 | overflow: hidden;
69 | }
70 | #main {
71 |
72 | }
73 | #global-mask {
74 | position: fixed;
75 | top: 0;
76 | left: 0;
77 | width: 100%;
78 | height: 100%;
79 | background-color: $white;
80 | z-index: 100;
81 | }
82 |
--------------------------------------------------------------------------------
/src/css/_fonts.css:
--------------------------------------------------------------------------------
1 | /*
2 | Store your @font-face in here
3 | -------------------------------------------------- */
4 | @import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");
--------------------------------------------------------------------------------
/src/css/_global-components/footer.scss:
--------------------------------------------------------------------------------
1 | #footer {
2 | background-color: #3f7647;
3 | height: 400px;
4 | width: 100%;
5 | position: relative;
6 | z-index: 2;
7 | h5 {
8 | font-size: 100px;
9 | font-family: sans-serif;
10 | color: #000000;
11 | text-transform: uppercase;
12 | white-space: nowrap;
13 | z-index: 3;
14 | margin-top: 60px;
15 | pointer-events: none;
16 | }
17 | .measure-el {
18 | height: 100%;
19 | top: 0;
20 | }
21 | }
22 | .footer-inner {
23 | display: flex;
24 | justify-content: center;
25 | }
26 |
--------------------------------------------------------------------------------
/src/css/_global-components/header.scss:
--------------------------------------------------------------------------------
1 | #header {
2 | position: fixed;
3 | top: 15px;
4 | left: 0;
5 | width: 100%;
6 | height: 70px;
7 | z-index: 9;
8 | .container-90 {
9 | height: 100%;
10 | justify-content: space-between;
11 | align-items: flex-end;
12 | max-width: none;
13 | }
14 | a {
15 | text-transform: uppercase;
16 | font-weight: bold;
17 | font-family: sans-serif;
18 | letter-spacing: .02px;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/css/_global-components/mini-cart.scss:
--------------------------------------------------------------------------------
1 | #mini-cart {
2 | position: fixed;
3 | background: #FFFFFF;
4 | width: 40%;
5 | height: calc(var(--vh, 1vh) * 100);
6 | opacity: 0;
7 | visibility: hidden;
8 | pointer-events: none;
9 | display: none;
10 | }
11 |
--------------------------------------------------------------------------------
/src/css/_pages/404.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/css/_pages/404.scss
--------------------------------------------------------------------------------
/src/css/_pages/about.scss:
--------------------------------------------------------------------------------
1 | .view-about {
2 | section {
3 | height: calc(var(--vh, 1vh) * 100);
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | position: relative;
8 | overflow: hidden;
9 | h1 {
10 | font-size: 180px;
11 | font-family: sans-serif;
12 | color: #000000;
13 | text-transform: uppercase;
14 | white-space: nowrap;
15 | position: absolute;
16 | left: 0;
17 | z-index: 3;
18 | pointer-events: none;
19 | @include bp-767 {
20 | color: #ffffff;
21 | font-size: 50px;
22 | }
23 | }
24 | .image-wrapper {
25 | width: 35%;
26 | position: relative;
27 | .ar-wrapper {
28 | padding-bottom: 60%;
29 | }
30 | @include bp-767 {
31 | width: 75%;
32 | }
33 | }
34 | img {
35 | width: 100%;
36 | height: 100%;
37 | z-index: 2;
38 | position: absolute;
39 | top: 0;
40 | left: 0;
41 | }
42 | }
43 | .scale-bg {
44 | position: absolute;
45 | width: 100%;
46 | height: 100%;
47 | top: 0;
48 | left: 0;
49 | transform-origin: 50% 100%;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/css/_pages/home.scss:
--------------------------------------------------------------------------------
1 | /*
2 | HOME PAGE STYLES
3 |
4 | - Import all home page specific styles here
5 | -------------------------------------------------- */
6 | #scroll-progress {
7 | position: fixed;
8 | left: 0;
9 | top: 0;
10 | height: 15px;
11 | width: 100%;
12 | z-index: 99;
13 | transform: translateZ(0);
14 | .track {
15 | width: 100%;
16 | height: 100%;
17 | background-color: #EBEBEB;
18 | }
19 | .indicator {
20 | width: 100%;
21 | height: 100%;
22 | background-color: #3f7647;
23 | position: absolute;
24 | top: 0;
25 | transform-origin: 0 50%;
26 | transform: scaleX(0);
27 | }
28 | }
29 |
30 | .view-home {
31 | section {
32 | height: calc(var(--vh, 1vh) * 100);
33 | display: flex;
34 | justify-content: center;
35 | align-items: center;
36 | position: relative;
37 | overflow: hidden;
38 | h1 {
39 | font-size: 180px;
40 | font-family: sans-serif;
41 | color: #000000;
42 | text-transform: uppercase;
43 | white-space: nowrap;
44 | position: absolute;
45 | left: 0;
46 | z-index: 3;
47 | pointer-events: none;
48 | @include bp-767 {
49 | color: #ffffff;
50 | font-size: 50px;
51 | }
52 | }
53 | .image-wrapper {
54 | width: 35%;
55 | position: relative;
56 | .ar-wrapper {
57 | padding-bottom: 60%;
58 | }
59 | @include bp-767 {
60 | width: 75%;
61 | }
62 | }
63 | img {
64 | width: 100%;
65 | height: 100%;
66 | z-index: 2;
67 | position: absolute;
68 | top: 0;
69 | left: 0;
70 | }
71 | }
72 | .scale-bg {
73 | position: absolute;
74 | width: 100%;
75 | height: 100%;
76 | top: 0;
77 | left: 0;
78 | transform-origin: 50% 100%;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/css/main.scss:
--------------------------------------------------------------------------------
1 | /*
2 | ABSTRACTS
3 |
4 | -------------------------------------------------- */
5 | @import "/_abstracts/variables";
6 | @import "/_abstracts/mixins";
7 |
8 | /*
9 | BASE
10 |
11 | -------------------------------------------------- */
12 | @import "/_base/custom-reset";
13 | @import "/_base/global";
14 |
15 |
16 | /*
17 | PAGES / LOCAL TEMPLATES
18 |
19 | -------------------------------------------------- */
20 | @import "/_pages/home";
21 | @import "/_pages/about";
22 | @import "/_pages/404";
23 |
24 |
25 | /*
26 | GLOBAL COMPONENTS
27 |
28 | -------------------------------------------------- */
29 | @import "_global-components/header";
30 | @import "_global-components/footer";
31 | @import "_global-components/mini-cart";
32 |
33 | /*
34 | PLUGINS
35 |
36 | - Third party plugins
37 | -------------------------------------------------- */
38 |
--------------------------------------------------------------------------------
/src/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/fonts/.gitkeep
--------------------------------------------------------------------------------
/src/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/.gitkeep
--------------------------------------------------------------------------------
/src/images/meta/facebook-share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/meta/facebook-share.png
--------------------------------------------------------------------------------
/src/images/meta/twitter-share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/meta/twitter-share.png
--------------------------------------------------------------------------------
/src/images/temp/shift-image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/temp/shift-image-2.jpg
--------------------------------------------------------------------------------
/src/images/temp/shift-image-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/temp/shift-image-3.jpg
--------------------------------------------------------------------------------
/src/images/temp/shift-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshkirk/highway-scroll/9c955fd64b2b494a65379976301723a248bf0fea/src/images/temp/shift-image.jpg
--------------------------------------------------------------------------------
/src/js/_classes/ScrollBasedAnims.js:
--------------------------------------------------------------------------------
1 | import VirtualScroll from "virtual-scroll";
2 | import { gsap, Expo, Linear, Sine } from "gsap";
3 | import { globalStorage, domStorage } from "../_global/storage";
4 |
5 | export class ScrollBasedAnims {
6 | constructor(options = {}) {
7 | this.isVS = true; // Whether to use VS or native scroll for all animations
8 | this.isMobile = globalStorage.isMobile;
9 |
10 | if (this.isMobile || globalStorage.isFirefox) { // Both mobile and FF perform much better without VS
11 | this.isVS = false;
12 | }
13 |
14 | this.bindMethods();
15 |
16 | gsap.defaults({
17 | ease: "Linear.easeNone" // You want the ease of animations to be 1:1 with the scroll of the user's mouse if native (or the VS ease)
18 | });
19 |
20 | this.el = this.isVS ? domStorage.containerEl : document.body;
21 |
22 | this.thisPagesTLs = []; // Array that will hold all the [data-entrance] timelines
23 | this.offsetVal = 0; // Value used to check against so we don't play [data-entrance] timelines more than once
24 | this.body = document.body;
25 | this.direction = 'untouched'; // Updated to either "up" or "down" in the run function
26 | this.transitioning = false;
27 |
28 | const {
29 | sections = !this.isVS ? false : this.el.querySelectorAll('[data-smooth]'), // Only collect [data-smooth] sectoins if this.isVS
30 | dataFromElems = this.el.querySelectorAll('[data-from]'), // Elements that transition between the values placed on the element in the markup
31 | dataHeroFromElems = document.querySelectorAll('[data-h-from]'), // [data-h-from] math differs a little
32 | heroMeasureEl = this.el.querySelector('.hero .measure-el'), // Use css to make the .measure-el within the hero whatever height you want. When it's gone, the animation is complete
33 | footerMeasureEl = document.querySelector('#footer .measure-el'), // Same goes for the footer measure element
34 | scrollBasedElems = this.el.querySelectorAll('[data-entrance]'), // Elements that have a certain entrance once, when scrolled into view
35 | threshold = 200, // Default pixel distance for this.isVisible to say yes this element is visible. We only animate against elements that are visible
36 | ease = 0.18, // VS ease
37 | // PC users deserve some consideration too, generally scroll devices can vary greatly on PC and the scroll events emitted will be far less frequent/reliable for easing purposes,
38 | // so we just jack it up since we'll be easing it anyways. Only used if VS is turned on
39 | mouseMultiplier = (["Win32", "Win64", "Windows", "WinCE"].indexOf(window.navigator.platform) !== -1 ? 0.6 : 0.25),
40 | preventTouch = true,
41 | passive = true,
42 | scrollIndicator = document.getElementById('scroll-progress') // The progress bar on top. This is just an example of data collection and animation
43 | } = options;
44 |
45 | this.dom = { // Save values to this.dom so we can just destroy that on leave
46 | el: this.el,
47 | // namespace: this.namespace,
48 | sections: sections,
49 | dataFromElems: dataFromElems,
50 | dataHeroFromElems: dataHeroFromElems,
51 | scrollBasedElems: scrollBasedElems,
52 | heroMeasureEl: heroMeasureEl,
53 | footerMeasureEl: footerMeasureEl,
54 | scrollIndicator: scrollIndicator
55 | };
56 |
57 | this.sections = null;
58 | this.dataFromElems = null;
59 | this.dataHeroFromElems = null;
60 | this.scrollBasedElems = null;
61 | // this.namespace = null;
62 | this.raf = null;
63 |
64 | this.state = {
65 | resizing: false
66 | };
67 |
68 | this.data = {
69 | threshold: threshold,
70 | ease: ease,
71 | current: 0,
72 | currentScrollY: 0,
73 | last: 0,
74 | target: 0,
75 | height: 0,
76 | width: globalStorage.windowWidth,
77 | max: 0
78 | };
79 |
80 | if (this.isVS) { // If VS, instantiate the VS and bind inputs/textarea so you can press space without scrolling down while focused
81 | this.vs = new VirtualScroll({
82 | el: this.el,
83 | mouseMultiplier: mouseMultiplier,
84 | preventTouch: preventTouch,
85 | passive: passive
86 | });
87 |
88 | const inputs = document.querySelectorAll('input:not(.bound), textarea:not(.bound)');
89 | for (let i = 0; i < inputs.length; i++) {
90 | let input = inputs[i];
91 | input.classList.add('bound');
92 | input.addEventListener('focus', () => {
93 | document.removeEventListener('keydown', this.vs._onKeyDown)
94 | });
95 | input.addEventListener('blur', () => {
96 | document.addEventListener('keydown', this.vs._onKeyDown)
97 | });
98 | }
99 | }
100 |
101 | this.init();
102 |
103 | if (globalStorage.windowWidth > 767) { // I turn [data-entrance] animations off on mobile because I prefer a more simple mobile experience with just a few key animations, instead of many elements fading in, etc.
104 | let length = this.dom.scrollBasedElems.length;
105 | for (let i = 0; i < length; i++) {
106 | const entranceEl = this.dom.scrollBasedElems[i];
107 | const entranceType = entranceEl.dataset.entrance;
108 | const entranceTL = new gsap.timeline({ paused: true });
109 |
110 | switch (entranceType) {
111 | case 'scale-bg':
112 | const bg = entranceEl.querySelector('.scale-bg');
113 | entranceTL
114 | .fromTo(bg, 1.2, { scaleY: 0 }, { scaleY: 1, ease: Expo.easeOut, force3D: true })
115 |
116 | this.thisPagesTLs.push(entranceTL);
117 | break;
118 |
119 | case "stagger-fade":
120 | let staggerEls = entranceEl.querySelectorAll('.stagger-el');
121 |
122 | entranceTL
123 | .fromTo(staggerEls, 0.7, { autoAlpha:0 }, { autoAlpha: 1, stagger: .12, force3D: true });
124 |
125 | this.thisPagesTLs.push(entranceTL);
126 | break;
127 |
128 | default:
129 |
130 | break;
131 | }
132 | }
133 | }
134 | }
135 |
136 | bindMethods() {
137 | ['run', 'event', 'resize']
138 | .forEach(fn => this[fn] = this[fn].bind(this));
139 | }
140 |
141 | init() {
142 | this.on();
143 | }
144 |
145 | on() { // Note how we take all measurements (getBounding & getCache) whether it's VS or not. Using native scroll we're still going to animate everything exactly the same in run()
146 | if (this.isVS) {
147 | this.dom.el.classList.add('is-vs');
148 | this.setStyles();
149 | this.vs.on(this.event);
150 | }
151 | this.getBounding();
152 | this.getCache();
153 | this.requestAnimationFrame();
154 | }
155 |
156 | setStyles() { // Only called if isVS
157 | this.dom.el.style.position = 'fixed';
158 | this.dom.el.style.top = 0;
159 | this.dom.el.style.left = 0;
160 | this.dom.el.style.width = '100%';
161 | }
162 |
163 | event(e) {
164 | this.data.target += Math.round(e.deltaY * -1);
165 | this.clamp();
166 | }
167 |
168 | clamp() {
169 | this.data.target = Math.round(Math.min(Math.max(this.data.target, 0), this.data.max));
170 | }
171 |
172 | run() { // The RAF engine
173 | if (this.state.resizing || this.transitioning) return;
174 |
175 | if (this.isVS) { // If isVS, use those the simulated pageY values
176 |
177 | this.data.current += (this.data.target - this.data.current) * this.data.ease;
178 | this.transformSections(); // If isVS, transform the [data-smooth] sections
179 | // Notice in both cases, this.getDirection() is called in between setting this.data.current and this.data.last, so we can measure deltas and know if we're going up or down
180 | this.getDirection();
181 | this.data.last = this.data.current;
182 |
183 | } else { // else if native scroll do everything the same except we're just setting this.data.current to be the literal window.scrollY
184 | // checking the window.scrollY causes a reflow, but the performance of everything is so tight, that is okay :D
185 | this.data.current = window.scrollY;
186 | this.data.currentScrollY = this.data.current;
187 | if (this.data.current === this.data.last) { // Don't run the animation cycle if they haven't scrolled
188 | this.requestAnimationFrame();
189 | return;
190 | }
191 | // Notice in both cases, this.getDirection() is called in between setting this.data.current and this.data.last, so we can measure deltas and know if we're going up or down
192 | this.getDirection();
193 | this.data.last = this.data.current;
194 | }
195 |
196 | this.scrollProgress(); // Example animation function that animates a progress bar
197 |
198 | if (!globalStorage.reducedMotion) { // You need to make sure all elements have proper positioning if not animated, or remove this check
199 | this.checkScrollBasedLoadins();
200 | this.animateDataHeroFromElems();
201 | this.animateDataFromElems();
202 | this.animateFooterReveal();
203 | }
204 | this.playPauseVideos();
205 | // Do it all over again. Note that even for native scroll, we're running a RAF and not just relying on the scroll event to fire the run() function. It's much more reliable and smooth.
206 | this.requestAnimationFrame();
207 | }
208 |
209 | getDirection() {
210 | if (this.data.last - this.data.current < 0) {
211 | // DOWN
212 | if (this.direction === 'down' || this.data.current <= 0) { return; }
213 | this.direction = 'down';
214 | } else if (this.data.last - this.data.current > 0) {
215 | // UP
216 | if (this.direction === 'up') { return; }
217 | this.direction = 'up';
218 | }
219 | }
220 |
221 | playPauseVideos() {
222 | if (this.direction === "untouched") return;
223 | for (let i = 0; i < this.videosDataLength; i++) {
224 | let data = this.videosData[i];
225 | let { isVisible } = this.isVisible(data, 50)
226 | if (isVisible) {
227 | if (!data.playing) {
228 | data.el.play();
229 | data.playing = true;
230 | }
231 | } else if (!isVisible && data.playing) {
232 | data.el.pause();
233 | data.el.currentTime = 0;
234 | data.playing = false;
235 | }
236 | }
237 | }
238 |
239 | scrollProgress() {
240 | if (this.direction === "untouched" || !this.dom.scrollIndicator) { return }
241 |
242 | let scrollProgress = this.data.current / this.data.max;
243 |
244 | gsap.set(this.scrollProgressData.scrollBar, { scaleX: scrollProgress })
245 | }
246 |
247 | getSections() { // [data-smooth] sections, which are only present if isVS
248 | if (!this.dom.sections) return;
249 | this.sections = [];
250 | let length = this.dom.sections.length;
251 | for (let i = 0; i < length; i++) {
252 | let el = this.dom.sections[i]
253 | el.style.transform = '';
254 | const bounds = el.getBoundingClientRect();
255 | this.sections.push({
256 | top: (bounds.top + this.data.currentScrollY),
257 | bottom: (bounds.bottom + this.data.currentScrollY)
258 | });
259 | }
260 | }
261 |
262 | getVideos() {
263 | let playPauseVideos = document.querySelectorAll('.play-in-view')
264 | this.videosData = []
265 |
266 | for (let i = 0; i < playPauseVideos.length; i++) {
267 | let bounds = playPauseVideos[i].getBoundingClientRect()
268 | this.videosData.push({
269 | el: playPauseVideos[i],
270 | playing: false,
271 | top: (bounds.top + this.data.currentScrollY) > this.data.height ? (bounds.top + this.data.currentScrollY) : this.data.height,
272 | bottom: (bounds.bottom + this.data.currentScrollY),
273 | })
274 | }
275 | this.videosDataLength = this.videosData.length
276 | }
277 |
278 | getScrollBasedSections() { // [data-entrance] sections that animate once when they hit a certain threshold in the viewport
279 | if (!this.dom.scrollBasedElems || this.isMobile) return
280 | this.scrollBasedElems = []
281 | let length = this.dom.scrollBasedElems.length;
282 | for (let i = 0; i < length; i++) {
283 | if (i < this.offsetVal) { continue }
284 | let el = this.dom.scrollBasedElems[i]
285 | const bounds = el.getBoundingClientRect()
286 | this.scrollBasedElems.push({
287 | el: el,
288 | played: false,
289 | top: (bounds.top + this.data.currentScrollY) > this.data.height ? (bounds.top + this.data.currentScrollY) : this.data.height,
290 | bottom: (bounds.bottom + this.data.currentScrollY),
291 | height: (bounds.bottom - bounds.top),
292 | offset: globalStorage.windowWidth < 768 ? (el.dataset.offsetMobile * globalStorage.windowHeight) : (el.dataset.offset * globalStorage.windowHeight)
293 | })
294 | }
295 |
296 | }
297 |
298 | getDataFromElems() { // [data-from] elements that animate forward and backward as you scroll up and down. Note you can optionally have [data-mobile-from] & [data-mobile-to] on your elements
299 | if (!this.dom.dataFromElems) return;
300 |
301 | this.dataFromElems = [];
302 |
303 | let useMobile = globalStorage.windowWidth < 768;
304 |
305 | let length = this.dom.dataFromElems.length
306 | for (let i = 0; i < length; i++) {
307 | let el = this.dom.dataFromElems[i]
308 |
309 | let from, to, dur;
310 | const bounds = el.getBoundingClientRect()
311 | const tl = new gsap.timeline({ paused: true })
312 |
313 | if (useMobile) {
314 | from = el.dataset.mobileFrom ? JSON.parse(el.dataset.mobileFrom) : JSON.parse(el.dataset.from);
315 | to = el.dataset.mobileTo ? JSON.parse(el.dataset.mobileTo) : JSON.parse(el.dataset.to);
316 | if (el.dataset.mobileDur) {
317 | dur = el.dataset.mobileDur;
318 | } else {
319 | dur = el.dataset.dur ? el.dataset.dur : 1;
320 | }
321 | } else {
322 | from = JSON.parse(el.dataset.from);
323 | to = JSON.parse(el.dataset.to);
324 | dur = el.dataset.dur ? el.dataset.dur : 1;
325 | }
326 |
327 | tl.fromTo(el, 1, from, to)
328 |
329 | this.dataFromElems.push({
330 | el: el,
331 | tl: tl,
332 | top: (bounds.top + this.data.currentScrollY) > this.data.height ? (bounds.top + this.data.currentScrollY - (this.isMobile ? 40 : 0)) : this.data.height,
333 | bottom: (bounds.bottom + this.data.currentScrollY - (this.isMobile ? 40 : 0)),
334 | height: bounds.bottom - bounds.top,
335 | from: from,
336 | duration: dur,
337 | progress: {
338 | current: 0
339 | }
340 | })
341 | }
342 |
343 | }
344 |
345 | getHeroMeasureEl() { // Measure the .measure-el within .hero so we know when the [data-h-from] element animations should be complete
346 | if (!this.dom.heroMeasureEl) return;
347 | const el = this.dom.heroMeasureEl;
348 | const bounds = el.getBoundingClientRect();
349 | this.heroMeasureData = {
350 | top: (bounds.top + this.data.currentScrollY) > this.data.height ? (bounds.top + this.data.currentScrollY) : this.data.height,
351 | bottom: (bounds.bottom + this.data.currentScrollY),
352 | height: bounds.bottom - bounds.top,
353 | progress: {
354 | current: 0
355 | }
356 | };
357 | }
358 |
359 | getFooterMeasureEl() {
360 | // Same as the getHeroMeasureEl() but the math is again slightly different. Here we're determining when a bottom-of-the-screen element is 100% in.
361 | // In the previous function we're gathering data so we can determine when an already-visible element is 100% out.
362 | // In the next function we'll get the elements that use a 0-1 intersectRatio() to animate forward and backward as they pass through the viewport
363 | if (!this.dom.footerMeasureEl || this.isMobile) return;
364 | const el = this.dom.footerMeasureEl;
365 | const bounds = el.getBoundingClientRect();
366 | this.footerRevealTL = new gsap.timeline({ paused: true })
367 | this.footerRevealTL
368 | .fromTo("#footer h5", 1, { y: 200, rotation: 540, opacity: 0 }, { y: 0, rotation: 0, opacity: 1 });
369 |
370 | this.footerMeasureData = {
371 | top: (bounds.top + this.data.currentScrollY) > this.data.height ? (bounds.top + this.data.currentScrollY) : this.data.height,
372 | bottom: (bounds.bottom + this.data.currentScrollY),
373 | height: bounds.bottom - bounds.top,
374 | duration: (this.data.height / (bounds.bottom - bounds.top)).toFixed(2)
375 | };
376 | }
377 |
378 | getDataHeroFromElems() {
379 | if (!this.dom.dataHeroFromElems) return;
380 |
381 | this.dataHeroFromElems = [];
382 | const useMobile = globalStorage.windowWidth < 768;
383 | for (let i = 0; i < this.dom.dataHeroFromElems.length; i++) {
384 | let el = this.dom.dataHeroFromElems[i]
385 | let from, to;
386 | const tl = new gsap.timeline({ paused: true });
387 |
388 | if (useMobile) {
389 | from = el.dataset.hMobileFrom ? JSON.parse(el.dataset.hMobileFrom) : JSON.parse(el.dataset.hFrom);
390 | to = el.dataset.mobileTo ? JSON.parse(el.dataset.mobileTo) : JSON.parse(el.dataset.to);
391 | } else {
392 | from = JSON.parse(el.dataset.hFrom);
393 | to = JSON.parse(el.dataset.to);
394 | }
395 |
396 | tl.fromTo(el, 1, from, to);
397 |
398 | this.dataHeroFromElems.push({
399 | el: el,
400 | tl: tl,
401 | progress: {
402 | current: 0
403 | }
404 | })
405 | }
406 | }
407 |
408 | transformSections() {
409 | if (!this.sections) return;
410 |
411 | const translate = this.data.current.toFixed(2);
412 |
413 | let length = this.sections.length;
414 | for (let i = 0; i < length; i++) {
415 | let data = this.sections[i]
416 | const { isVisible } = this.isVisible(data);
417 |
418 | if (isVisible || this.state.resizing) gsap.set(this.dom.sections[i], { y: -translate, force3D: true, ease: Linear.easeNone });
419 | }
420 | }
421 |
422 | animateDataHeroFromElems() {
423 | if (this.direction === "untouched" || !this.heroMeasureData) return;
424 | const { isVisible } = ( this.isVisible(this.heroMeasureData) );
425 | if (!isVisible) return;
426 | let percentageThrough = (this.data.current / this.heroMeasureData.height).toFixed(3);
427 |
428 | if (percentageThrough <= 0) {
429 | percentageThrough = 0;
430 | } else if (percentageThrough >= 1) {
431 | percentageThrough = 1;
432 | }
433 | // console.log(percentageThrough)
434 | let length = this.dataHeroFromElems.length;
435 | for (let i = 0; i < length; i++) {
436 | let data = this.dataHeroFromElems[i]
437 | data.tl.progress(percentageThrough)
438 | }
439 | }
440 |
441 | animateFooterReveal() {
442 | if (this.direction === "untouched" || !this.footerMeasureData) return;
443 | const { isVisible, start, end } = ( this.isVisible(this.footerMeasureData, 0.01) );
444 | if (!isVisible) {
445 | this.footerRevealTL.progress(0);
446 | return;
447 | }
448 | let percentageThrough = ((((start).toFixed(2) / this.data.height).toFixed(3) - 1) * -1) * this.footerMeasureData.duration;
449 | if (percentageThrough <= 0) {
450 | percentageThrough = 0;
451 | } else if (percentageThrough >= 1) {
452 | percentageThrough = 1;
453 | }
454 | console.log(percentageThrough);
455 | this.footerRevealTL.progress(percentageThrough);
456 | }
457 |
458 | animateDataFromElems() {
459 | if (this.direction === "untouched" || !this.dataFromElems) return
460 |
461 | let length = this.dataFromElems.length;
462 | for (let i = 0; i < length; i++) {
463 | let data = this.dataFromElems[i]
464 |
465 | const { isVisible, start, end } = this.isVisible(data, 0.01);
466 |
467 | if (isVisible) {
468 |
469 | this.intersectRatio(data, start, end)
470 |
471 | data.tl.progress(data.progress.current)
472 | }
473 | }
474 | }
475 |
476 | checkScrollBasedLoadins() {
477 | if (this.direction === "untouched" || !this.scrollBasedElems) { return }
478 | if (this.thisPagesTLs.length !== this.offsetVal) {
479 | let length = this.scrollBasedElems.length;
480 | for (let i = 0; i < length; i++) {
481 | let data = this.scrollBasedElems[i];
482 |
483 | if (data.played) { continue }
484 |
485 | if ((this.data.current + data.offset) > data.top) {
486 | this.thisPagesTLs[this.offsetVal].play();
487 | this.offsetVal++;
488 | data.played = true;
489 | }
490 | }
491 | }
492 | }
493 |
494 | intersectRatio(data, top, bottom) {
495 | const start = top - this.data.height;
496 |
497 | if (start > 0) { return }
498 | const end = (this.data.height + bottom + data.height) * data.duration;
499 | data.progress.current = Math.abs(start / end);
500 | data.progress.current = Math.max(0, Math.min(1, data.progress.current));
501 | }
502 |
503 | isVisible(bounds, offset) {
504 | const threshold = !offset ? this.data.threshold : offset;
505 | const start = bounds.top - this.data.current;
506 | const end = bounds.bottom - this.data.current;
507 | const isVisible = start < (threshold + this.data.height) && end > -threshold;
508 | return {
509 | isVisible,
510 | start,
511 | end
512 | };
513 | }
514 |
515 | requestAnimationFrame() {
516 | this.raf = requestAnimationFrame(this.run);
517 | }
518 |
519 | cancelAnimationFrame() {
520 | cancelAnimationFrame(this.raf);
521 | }
522 |
523 | getCache() {
524 | if (this.isVS) {
525 | this.getSections();
526 | }
527 | this.getVideos();
528 | if (globalStorage.reducedMotion) { return; }
529 | this.getScrollBasedSections();
530 | this.getDataHeroFromElems();
531 | this.getDataFromElems();
532 | this.getHeroMeasureEl();
533 | this.getFooterMeasureEl();
534 | this.getScrollProgressData();
535 | }
536 |
537 | getScrollProgressData() {
538 | if (!this.dom.scrollIndicator) return
539 | const el = this.dom.scrollIndicator;
540 | const scrollBar = el.querySelector('.indicator')
541 | const indicators = el.querySelectorAll('.indicator');
542 | const trackHeight = Math.round(el.offsetHeight)
543 | const indicatorHeight = Math.round(indicators[0].offsetHeight);
544 |
545 | gsap.set(el, { height: trackHeight })
546 | gsap.set(indicators, { height: indicatorHeight })
547 |
548 | const scrollDist = trackHeight - scrollBar.offsetHeight;
549 |
550 | this.scrollProgressData = {
551 | scrollDist: scrollDist,
552 | scrollBar: scrollBar
553 | };
554 | }
555 |
556 | getBounding() {
557 | this.data.height = globalStorage.windowHeight + (this.isMobile ? 100 : 0);
558 | this.data.max = (this.dom.el.getBoundingClientRect().height - this.data.height) + (this.isMobile ? 100 : 0);
559 | }
560 |
561 | resize(omnibar = false) {
562 | // Okay so this is some shit. We either lock the body height and overflow so the omnibar can't collapse, or we do the below..
563 | // Basically if we can assume it's just the omnibar collapsing, don't re-measure things and just keep measuring off the un-collapsed omnibar height. This allows for
564 | // scroll based animations to continue smoothly during omnibar collapse.
565 |
566 | // I haven't been able to animate the footer reveal properly without locking the body so you'll notice the footer reveal is currently only being done on desktop :(
567 | // If you lock the body overflow so the body can't collapse you can remove the omnibar stuff from this function and animate an awesome footer reveal too, I wouldn't blame you.
568 |
569 | this.state.resizing = true;
570 | if (!omnibar) {
571 | this.getCache();
572 | if (this.isVS) {
573 | this.transformSections();
574 | }
575 | this.getBounding();
576 | }
577 | this.state.resizing = false;
578 | }
579 |
580 |
581 | scrollTo(val) {
582 | this.state.scrollingTo = true;
583 | if (!this.isVS) {
584 | gsap.to(this.el, 1, { scrollTop: val, ease: Sine.easeInOut, onComplete: () => { this.state.scrolling = false } })
585 | } else {
586 | gsap.to(this.data, 0.7, { target: val, ease: Sine.easeInOut, onComplete: () => { this.state.scrolling = false } })
587 | }
588 | }
589 |
590 | destroy() {
591 | this.transitioning = true; // We destroy the class instance in the global onLeave function
592 |
593 | if (this.isVS) {
594 | this.vs.off(this.event);
595 | this.dom.el.classList.remove('is-vs');
596 | this.vs.destroy();
597 | }
598 | this.state.rafCancelled = true;
599 | this.cancelAnimationFrame();
600 |
601 | this.dom = null;
602 | this.data = null;
603 | this.raf = null;
604 | }
605 | }
606 |
--------------------------------------------------------------------------------
/src/js/_global/_renderer.js:
--------------------------------------------------------------------------------
1 | /*
2 | Load Plugins / Functions
3 | -------------------------------------------------- */
4 | import { LazyLoadWorker } from "../_worker/_init";
5 | import { hasClass, getViewport, tracking } from "../_global/helpers";
6 | import { globalStorage } from "../_global/storage";
7 | import { globalEntrance, pageEntrance } from "../_global/anims";
8 | import { ScrollBasedAnims } from "../_classes/ScrollBasedAnims";
9 | import 'intersection-observer';
10 | import quicklink from "quicklink/dist/quicklink.mjs";
11 |
12 | /* --- Scroll variable --- */
13 | export let $scroll;
14 |
15 | /* --- Setup our Image workers --- */
16 | let ImageLoad = new LazyLoadWorker("image-load");
17 |
18 | /* --- Global Events - Fire on Every Page --- */
19 | const globalEvents = (namespace = null)=>{
20 |
21 | globalStorage.namespace = namespace;
22 |
23 | /* --- Set our preload callback images sizes --- */
24 | let type = "mobile";
25 |
26 | if(globalStorage.windowWidth > 767 && !hasClass(document.body, "force-mobile")){
27 | type = "desktop";
28 | }
29 |
30 | /*
31 | * Load Critical Images
32 | *
33 | * The callback is meant to fire DOM Critical related
34 | * global functions. Everything page specific needs to
35 | * go within it's respective file.
36 | */
37 | ImageLoad.loadImages("preload-critical", type, ()=>{
38 |
39 | /* --- Critical Done --- */
40 | globalEntrance(()=>{
41 |
42 | /* --- Global Entrance is done --- */
43 |
44 | });
45 |
46 | let transitionFinished = setInterval(()=>{
47 | if(globalStorage.transitionFinished === true){
48 | clearInterval(transitionFinished);
49 | $scroll = new ScrollBasedAnims({});
50 | pageEntrance(namespace);
51 | }
52 | }, 20);
53 |
54 | ImageLoad.loadImages("preload", type, ()=>{
55 |
56 | /* --- All Images Done --- */
57 |
58 | });
59 | });
60 | };
61 |
62 | /* --- DOMContentLoaded Function --- */
63 | export const onReady = ()=>{
64 | let namespace = document.querySelector('[data-router-view]').dataset.routerView;
65 | let vh = globalStorage.windowHeight * 0.01;
66 |
67 | document.body.style.setProperty('--vh', `${vh}px`);
68 | document.body.style.setProperty('--vhu', `${vh}px`); // viewport height updated
69 |
70 | globalEvents(namespace);
71 |
72 | if (namespace === "home") {
73 | quicklink({urls:['/about']});
74 | } else if (namespace === "about") {
75 | quicklink({urls:['/']});
76 | }
77 | };
78 |
79 | /* --- window.onload Function --- */
80 | export const onLoad = ()=>{
81 |
82 | };
83 |
84 | /* --- window.onresize Function --- */
85 | export const onResize = ()=>{
86 | let newWidth = getViewport().width;
87 | let omnibar = false;
88 | if (globalStorage.windowWidth === newWidth && globalStorage.isMobile) {
89 | omnibar = true;
90 | }
91 |
92 | globalStorage.windowHeight = getViewport().height;
93 | globalStorage.windowWidth = newWidth;
94 |
95 | let vh = globalStorage.windowHeight * 0.01;
96 | if (!omnibar) {
97 | document.body.style.setProperty('--vh', `${vh}px`);
98 | }
99 | document.body.style.setProperty('--vhu', `${vh}px`);
100 |
101 | $scroll.resize(omnibar);
102 | };
103 |
104 | /*
105 | * Highway NAVIGATE_OUT callback
106 | *
107 | * onLeave is fired when a highway transition has been
108 | * initiated. This callback is primarily used to unbind
109 | * events, or modify global settings that might be called
110 | * in onEnter/onEnterCompleted functions.
111 | */
112 | export const onLeave = (from, trigger, location)=>{
113 |
114 | /* --- Remove our scroll measurements --- */
115 | $scroll.destroy();
116 |
117 | /* --- Flag transition for load in animations --- */
118 | globalStorage.transitionFinished = false;
119 | };
120 |
121 | /*
122 | * Highway NAVIGATE_IN callback
123 | *
124 | * onEnter should only contain event bindings and non-
125 | * DOM related event measurements. Both view containers
126 | * are still loaded into the DOM during this callback.
127 | */
128 | export const onEnter = (to, trigger, location)=>{
129 | globalEvents(to.view.dataset.routerView);
130 | };
131 |
132 | /*
133 | * Highway NAVIGATE_END callback
134 | *
135 | * onEnterCompleted should be your primary event callback.
136 | * The previous view's DOM node has been removed when this
137 | * event fires.
138 | */
139 | export const onEnterCompleted = (from, to, trigger, location)=>{
140 |
141 | /* --- Track Page Views through Ajax --- */
142 | tracking("google", "set", "page", location.pathname);
143 | tracking("google", "send", {
144 | hitType: "pageview",
145 | page: location.pathname,
146 | title: to.page.title
147 | });
148 | };
149 |
--------------------------------------------------------------------------------
/src/js/_global/anims.js:
--------------------------------------------------------------------------------
1 | import { gsap, Sine } from "gsap";
2 | import {domStorage, globalStorage} from "./storage";
3 | import { $scroll } from "../_global/_renderer"
4 |
5 | /*
6 | Page specific animations
7 | -------------------------------------------------- */
8 | export const pageEntrance = (namespace)=>{
9 |
10 | /* ----- Establish our timeline ----- */
11 | let timeline = new gsap.timeline({ paused: true });
12 |
13 | timeline.to(domStorage.globalMask, 1, { autoAlpha: 0, ease: Sine.easeInOut });
14 |
15 | /* ----- Setup cases for specific load-ins ----- */
16 | switch(namespace){
17 |
18 | case "home":
19 |
20 | timeline.add(()=>{ console.log("home anims in go here"); });
21 | timeline.play();
22 |
23 | break;
24 |
25 | case "about":
26 |
27 | timeline.add(()=>{ console.log("about anims in go here"); });
28 | timeline.play();
29 |
30 | break;
31 |
32 | /* ----- Our default page entrance ----- */
33 | default:
34 |
35 | timeline.add(()=>{ console.log("default anims in go here"); });
36 | timeline.play();
37 |
38 | break;
39 | }
40 |
41 | if (globalStorage.firstLoad) {
42 | globalStorage.firstLoad = false;
43 | }
44 | };
45 |
46 | /*
47 | Global element animations
48 | -------------------------------------------------- */
49 | export const globalEntrance = ()=>{
50 |
51 | if(globalStorage.firstLoad === false){
52 | return;
53 | }
54 |
55 | /* ----- Establish our timeline ----- */
56 | let timeline = new gsap.timeline({ paused: true });
57 |
58 | // timeline.to(".loader-icon", 0.5, { autoAlpha: 0, ease: Sine.easeInOut, onComplete: ()=>{ globalStorage.transitionFinished = true; }});
59 | globalStorage.transitionFinished = true; ////// delete this if waiting for loader animations ^^^
60 |
61 | timeline.play();
62 | };
63 |
--------------------------------------------------------------------------------
/src/js/_global/helpers.js:
--------------------------------------------------------------------------------
1 | /*
2 | Has Class
3 |
4 | Returns true/false if element has classname.
5 | -------------------------------------------------- */
6 | export const hasClass = (element, cls)=>{
7 | return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
8 | };
9 |
10 | /*
11 | Vanilla .eq()
12 |
13 | Returns element by index.
14 |
15 | Usage: eq.call(arrayOfElements, index);
16 | -------------------------------------------------- */
17 | export const eq = (index)=>{
18 | if(index >= 0 && index < this.length){
19 | return this[index];
20 | } else {
21 | return -1;
22 | }
23 | };
24 |
25 | /*
26 | Vanilla array remove
27 | -------------------------------------------------- */
28 | export const remove = (array, element)=>{
29 | return array.filter(e => e !== element);
30 | };
31 |
32 |
33 | /*
34 | Get Viewport
35 |
36 | returns the native height and width of the
37 | browser viewport.
38 | -------------------------------------------------- */
39 | export const getViewport = function(){
40 | let e = window, a = 'inner';
41 | if(!('innerWidth' in window)){
42 | a = 'client';
43 | e = document.documentElement || document.body;
44 | }
45 | return { width : e[ a+'Width' ] , height : e[ a+'Height' ] };
46 | };
47 |
--------------------------------------------------------------------------------
/src/js/_global/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | Global Storage Object
3 | -------------------------------------------------- */
4 | export const globalStorage = {
5 | "assetPath": (document.getElementById("site-data") && window.location.host.indexOf("localhost") < 0) ? document.getElementById("site-data").getAttribute("data-asset-path") : "/assets/code/",
6 | "firstLoad": true,
7 | "isMobile": false,
8 | "isSafari": false,
9 | "isChrome": false,
10 | "isFirefox": (navigator.userAgent.indexOf("Firefox") > -1),
11 | "windowHeight": "",
12 | "windowWidth": "",
13 | "transitionFinished": false,
14 | "queryParams": {},
15 | "referrer": "",
16 | "reducedMotion": window.matchMedia("(prefers-reduced-motion: reduce)").matches,
17 | "headerShowing": true
18 | };
19 |
20 | export const domStorage = {
21 | // "header": document.getElementById('header'),
22 | "mainEl": document.getElementById('main'),
23 | "containerEl": document.getElementById('container'),
24 | "globalMask": document.getElementById('global-mask'),
25 | "openMobileMenu": () => {},
26 | "closeMobileMenu": () => {},
27 | "resetMobileMenu": () => {}
28 | }
29 |
30 | export const ecommStorage = {
31 | "products": null,
32 | "cart": null,
33 | "checkout": "",
34 | "cartCountEls": "",
35 | "removingLastItem": false,
36 | "openMiniCart": () => {},
37 | "closeMiniCart": () => {},
38 | }
39 |
40 |
41 | // adjust storage values based on things we can determine right away
42 | let is_chrome = navigator.userAgent.indexOf('Chrome') > -1;
43 | let is_safari = navigator.userAgent.indexOf("Safari") > -1;
44 | let is_opera = navigator.userAgent.toLowerCase().indexOf("op") > -1;
45 |
46 | if ((is_chrome)&&(is_safari)) { is_safari = false; }
47 | if ((is_chrome)&&(is_opera)) { is_chrome = false; }
48 |
49 | if (is_safari) { globalStorage.isSafari = true }
50 | if (is_chrome) { globalStorage.isChrome = true }
51 |
52 | const motionMediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
53 | globalStorage.reducedMotion = motionMediaQuery.matches;
54 |
55 | if (globalStorage.reducedMotion) {
56 | motionMediaQuery.addEventListener('change', () => {
57 | globalStorage.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
58 | console.log('Prefers reduced motion: '+globalStorage.reducedMotion)
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/src/js/_initialize.js:
--------------------------------------------------------------------------------
1 | if ('scrollRestoration' in history) {
2 | history.scrollRestoration = 'manual';
3 | }
4 |
5 | /*
6 | Init Routing
7 | -------------------------------------------------- */
8 | import "./routing";
9 |
10 | /*
11 | Load Plugins / Functions
12 | -------------------------------------------------- */
13 | import { getViewport } from "./_global/helpers";
14 | import { onReady, onLoad, onResize } from "./_global/_renderer";
15 | import { globalStorage } from "./_global/storage";
16 |
17 |
18 | /*
19 | Constants
20 | -------------------------------------------------- */
21 | const isMobile = require("ismobilejs");
22 |
23 | globalStorage.isMobile = isMobile.any;
24 |
25 | if (globalStorage.isMobile) {
26 | document.documentElement.classList.add('touch');
27 | }
28 |
29 | /*
30 | Check for Reduced Motion changes
31 | -------------------------------------------------- */
32 | if(globalStorage.reducedMotion){
33 | window.matchMedia("(prefers-reduced-motion: reduce)").addEventListener("change", ()=>{
34 | globalStorage.reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
35 | });
36 | }
37 |
38 | /*
39 | Doc ready
40 | -------------------------------------------------- */
41 | document.addEventListener("DOMContentLoaded", () => {
42 |
43 | /* --- Keep these up to date --- */
44 | globalStorage.windowWidth = getViewport().width;
45 | globalStorage.windowHeight = getViewport().height;
46 |
47 | /* --- Fire onReady --- */
48 | onReady();
49 | }, false);
50 |
51 | /*
52 | Window onload
53 | -------------------------------------------------- */
54 | window.onload = function(){
55 |
56 | /* --- Keep these up to date --- */
57 | globalStorage.windowWidth = getViewport().width;
58 | globalStorage.windowHeight = getViewport().height;
59 |
60 | /* --- Fire onLoad --- */
61 | onLoad();
62 | };
63 |
64 | /*
65 | Window resize
66 | -------------------------------------------------- */
67 | let resizeTimeout = setTimeout(()=>{},0);
68 |
69 | window.onresize = function(){
70 |
71 | /* --- Clear the timeout if actively resizing --- */
72 | clearTimeout(resizeTimeout);
73 |
74 | /* --- Delay resize event --- */
75 | resizeTimeout = setTimeout(()=>{
76 |
77 | /* --- Keep these up to date --- */
78 | globalStorage.windowWidth = getViewport().width;
79 | globalStorage.windowHeight = getViewport().height;
80 |
81 | /* --- Fire onResize --- */
82 | onResize();
83 | }, 250);
84 | };
--------------------------------------------------------------------------------
/src/js/_transitions/basicFade.js:
--------------------------------------------------------------------------------
1 | import Highway from "@dogstudio/highway";
2 | import { gsap, Sine } from "gsap";
3 | import { globalStorage } from "../_global/storage";
4 | import {$scroll} from "../_global/_renderer";
5 |
6 | let globalMask = document.getElementById("global-mask");
7 |
8 | /*
9 | Default Highway Transition
10 | -------------------------------------------------- */
11 | class BasicFade extends Highway.Transition{
12 |
13 | out({from, trigger, done}){
14 |
15 | gsap.fromTo(globalMask, 0.5, { autoAlpha: 0 }, { autoAlpha: 1, ease: Sine.easeInOut, onComplete: () => { done() } });
16 | }
17 |
18 | in({from, to, trigger, done}){
19 |
20 | globalStorage.namespace = to.dataset.routerView;
21 |
22 | // Remove old view
23 | from.remove();
24 |
25 | if (globalStorage.isMobile) {
26 | // Move to top of page
27 | document.body.scrollTop = 0;
28 | window.scroll(0,0);
29 | }
30 |
31 | globalStorage.transitionFinished = true;
32 |
33 | done();
34 | }
35 | }
36 |
37 | export default BasicFade;
38 |
--------------------------------------------------------------------------------
/src/js/_worker/_init.js:
--------------------------------------------------------------------------------
1 | import { hasClass, getViewport } from "../_global/helpers";
2 | import { globalStorage } from "../_global/storage";
3 | /*
4 | * Worker pool initialization file
5 | *
6 | * - Sets up our worker pool to give our
7 | * javaScript an array of worker threads
8 | * to work with
9 | * - Workers are used to run long-running
10 | * or heavy scripts, don't inject every
11 | * function into this.
12 | */
13 | export class LazyLoadWorker {
14 |
15 | constructor(worker){
16 | this.worker = new Worker("/assets/js/workers/image-load.js");
17 |
18 | this.running = false;
19 | this.array = [];
20 | this.count = 0;
21 | this.total = 0;
22 | this.type = "";
23 |
24 | /* --- Set our preload callback images sizes --- */
25 | this.size = "mobile";
26 |
27 | if(getViewport().width > 767 && !hasClass(document.body, "force-mobile")){
28 | this.size = "desktop";
29 | }
30 |
31 | this.bindEvents();
32 | }
33 |
34 | bindEvents(){
35 |
36 | this.worker.addEventListener("message", (event)=>{
37 |
38 | const data = event.data;
39 |
40 | /* --- Make sure we get elements that might have duplicate images --- */
41 | let images = document.querySelectorAll(`[data-preload-${this.size}="${data.url}"]`);
42 |
43 | for(let i = 0; i < images.length; i++){
44 |
45 | let url = data.url;
46 | let rmv = (i === images.length - 1);
47 |
48 | this.preload(images[i], data.blob, rmv);
49 | }
50 | });
51 | }
52 |
53 | loadImages(type, size, callback){
54 | let images = document.querySelectorAll(`.${type}`);
55 | let protocol = window.location.protocol + "//";
56 |
57 | this.array = [];
58 | this.count = 0;
59 |
60 | this.total = images.length;
61 | this.type = type;
62 |
63 | /* --- Loop through our images --- */
64 | for(let i = 0; i < images.length; i++){
65 |
66 | let url = images[i].getAttribute("data-preload-" + size);
67 | let tag = images[i].tagName.toLowerCase();
68 |
69 | /* --- Fallback for local images --- */
70 | if(url.indexOf("http") < 0){
71 | url = protocol + window.location.host + url;
72 | }
73 |
74 | if(url !== "" && this.array.indexOf(url) < 0){
75 |
76 | if(globalStorage.popstate || tag === "iframe" || tag === "video"){
77 | /* --- Hot swap our src --- */
78 | this.hotSwap(images[i], this.size);
79 | this.total--;
80 | } else {
81 | /* --- Send the url over to the worker --- */
82 | this.array.push(url);
83 | this.message(url);
84 | }
85 |
86 | } else {
87 |
88 | /* --- Reduce total if there is no image --- */
89 | this.total--;
90 | }
91 | }
92 |
93 | /* --- Set our callback interval --- */
94 | let interval = setInterval(()=>{
95 |
96 | if(this.count >= this.total){
97 | clearInterval(interval);
98 | callback(type + " done!");
99 | }
100 |
101 | }, 10);
102 | }
103 |
104 | hotSwap(element, size){
105 | this.preloadSwap(element, size);
106 | }
107 |
108 | removeAttrs(element) {
109 | element.classList.remove('preload');
110 | element.classList.remove('preload-critical');
111 | element.removeAttribute('data-preload-desktop');
112 | element.removeAttribute('data-preload-mobile');
113 | }
114 |
115 | preload(element, blob, remove){
116 |
117 | /* ----- If there is no URL defined, return clean ----- */
118 | if(!blob || blob === ""){
119 |
120 | this.removeAttrs(element);
121 |
122 | return;
123 | }
124 |
125 | let tag = element.tagName.toLowerCase();
126 | let url = URL.createObjectURL(blob);
127 |
128 | switch(tag){
129 |
130 | /* ----- Video Tags ----- */
131 | case "video":
132 |
133 | let source = document.createElement("source");
134 |
135 | source.setAttribute("src", url);
136 |
137 | element.appendChild(source);
138 | element.load();
139 | element.play();
140 |
141 | let checkVideoLoad = setInterval(()=>{
142 |
143 | if(element.readyState > 3){
144 |
145 | /* --- Video has loaded --- */
146 | this.removeAttrs(element);
147 | clearInterval(checkVideoLoad);
148 |
149 | if(remove === true){
150 | URL.revokeObjectURL(url);
151 | }
152 |
153 | this.count++;
154 | }
155 |
156 | }, 25);
157 |
158 | break;
159 |
160 | /* ----- Img Tags ----- */
161 | case "img":
162 |
163 | /* --- Wait for Img tag to load --- */
164 | element.onload = ()=>{
165 |
166 | this.removeAttrs(element);
167 |
168 | if(remove === true){
169 | URL.revokeObjectURL(url);
170 | }
171 |
172 | this.count++;
173 | };
174 |
175 | element.onerror = ()=>{
176 | console.log("LOAD FAILURE: " + url);
177 | this.count++;
178 | };
179 |
180 | element.setAttribute("src", url);
181 |
182 | break;
183 |
184 | /* ----- Everything else, assumes background-image ----- */
185 | default:
186 |
187 | /* --- Use Img load to measure for background-image --- */
188 | element.style.backgroundImage = "url(" + url + ")";
189 | this.removeAttrs(element);
190 |
191 | if(remove === true){
192 | // URL.revokeObjectURL(url);
193 | }
194 |
195 | this.count++;
196 |
197 | break;
198 | }
199 | }
200 |
201 | preloadSwap(element, size){
202 |
203 | let tag = element.tagName.toLowerCase();
204 | let url = element.getAttribute("data-preload-" + size);
205 |
206 | switch(tag){
207 |
208 | /* ----- Video Tags ----- */
209 | case "video":
210 |
211 | let source = document.createElement("source");
212 |
213 | source.setAttribute("src", url);
214 |
215 | element.appendChild(source);
216 | element.load();
217 | element.play();
218 |
219 | break;
220 |
221 | /* ----- Img Tags ----- */
222 | case "img":
223 | case "iframe":
224 |
225 | element.setAttribute("src", url);
226 |
227 |
228 | break;
229 |
230 | /* ----- Everything else, assumes background-image ----- */
231 | default:
232 |
233 | element.style.backgroundImage = "url(" + url + ")";
234 |
235 | break;
236 | }
237 |
238 | this.removeAttrs(element);
239 | }
240 |
241 | /* --- Post our message to the worker --- */
242 | message(message){
243 | this.worker.postMessage(message);
244 | }
245 | }
--------------------------------------------------------------------------------
/src/js/_worker/workers/image-load.js:
--------------------------------------------------------------------------------
1 | /* jshint ignore: start */
2 | self.addEventListener("message", async event => {
3 | const url = event.data;
4 |
5 | const response = await fetch(url);
6 | const blob = await response.blob();
7 |
8 | self.postMessage({
9 | url: url,
10 | blob: blob
11 | });
12 | });
--------------------------------------------------------------------------------
/src/js/about/_renderer.js:
--------------------------------------------------------------------------------
1 | import Highway from "@dogstudio/highway";
2 | import { aboutSpecificFunc } from "./anims";
3 |
4 | /*
5 | View Events for Highway
6 |
7 | - About Page
8 | - Events are listed in their execution order
9 | -------------------------------------------------- */
10 | class AboutRenderer extends Highway.Renderer {
11 |
12 | onEnter() {
13 | console.log("onEnter");
14 | aboutSpecificFunc();
15 | }
16 |
17 | onEnterCompleted() {
18 | console.log("onEnterCompleted");
19 | }
20 |
21 | onLeave() {
22 | console.log("onLeave");
23 | }
24 |
25 | onLeaveCompleted() {
26 | console.log("onLeaveCompleted");
27 | }
28 | }
29 |
30 | export default AboutRenderer;
31 |
--------------------------------------------------------------------------------
/src/js/about/anims.js:
--------------------------------------------------------------------------------
1 | export const aboutSpecificFunc = () => {
2 | console.log('Hello from the about page');
3 | };
4 |
--------------------------------------------------------------------------------
/src/js/home/_renderer.js:
--------------------------------------------------------------------------------
1 | import Highway from "@dogstudio/highway";
2 | import { homeSpecificFunc } from "./anims";
3 |
4 | /*
5 | View Events for Highway
6 |
7 | - Home Page
8 | - Events are listed in their execution order
9 | -------------------------------------------------- */
10 | class HomeRenderer extends Highway.Renderer {
11 |
12 | onEnter() {
13 | console.log("onEnter");
14 | homeSpecificFunc()
15 | }
16 |
17 | onEnterCompleted() {
18 | console.log("onEnterCompleted");
19 | }
20 |
21 | onLeave() {
22 | console.log("onLeave");
23 | }
24 |
25 | onLeaveCompleted() {
26 | console.log("onLeaveCompleted");
27 | }
28 | }
29 |
30 | export default HomeRenderer;
31 |
--------------------------------------------------------------------------------
/src/js/home/anims.js:
--------------------------------------------------------------------------------
1 | export const homeSpecificFunc = () => {
2 | console.log('Hello from the home page');
3 | };
4 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | Import and run any polyfills here
3 | -------------------------------------------------- */
4 | const ua = navigator.userAgent || navigator.vendor || window.opera;
5 | const isInstagram = (ua.indexOf("Instagram") > -1) ? true : false;
6 |
7 | if(isInstagram){
8 | document.body.className += " instagram-browser force-mobile";
9 | }
10 |
11 | /*
12 | Run your initilize script, make sure this is last
13 | -------------------------------------------------- */
14 | import "./_initialize";
--------------------------------------------------------------------------------
/src/js/routing.js:
--------------------------------------------------------------------------------
1 | /*
2 | Load Plugins / Functions
3 | -------------------------------------------------- */
4 | import Highway from "@dogstudio/highway";
5 | import { onLeave, onEnter, onEnterCompleted } from "./_global/_renderer";
6 | /* --- Highway Renderers --- */
7 | import HomeRenderer from "./home/_renderer";
8 | import AboutRenderer from "./about/_renderer";
9 | /* --- Highway Transitions --- */
10 | import BasicFade from "./_transitions/basicFade";
11 |
12 | /*
13 | Setup Core
14 | -------------------------------------------------- */
15 | export const H = new Highway.Core({
16 | renderers: {
17 | home: HomeRenderer,
18 | about: AboutRenderer
19 | },
20 | transitions: {
21 | default: BasicFade,
22 | contextual: { // Add transitions here that you can use by placing data-transition on an anchor, or in an H.redirect(href, transition)
23 | // footerToHero: FooterToHero
24 | }
25 | }
26 | });
27 |
28 | /*
29 | Global Events
30 | -------------------------------------------------- */
31 | H.on("NAVIGATE_OUT", ({ from, trigger, location })=>{
32 | onLeave(from, trigger, location);
33 | });
34 |
35 | H.on("NAVIGATE_IN", ({ to, trigger, location })=>{
36 | onEnter(to, trigger, location);
37 | });
38 |
39 | H.on("NAVIGATE_END", ({ from, to, trigger, location })=>{
40 | onEnterCompleted(from, to, trigger, location);
41 | });
42 |
--------------------------------------------------------------------------------
/src/templates/404.php:
--------------------------------------------------------------------------------
1 |
";
5 | print_r($document);
6 | echo "
";
7 | /* --------------- */
8 | ?>
--------------------------------------------------------------------------------
/src/templates/about.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Gone when 55% of about hero
8 |
9 |
10 |
![]()
14 |
15 |
16 |
17 |
18 |
19 |
20 | I'm going past your screen
23 |
24 |
25 |
26 |
![]()
31 |
32 |
33 |
34 |
35 | I'm going past your screen
38 |
39 |
40 |
![]()
45 |
46 |
47 |
48 |
49 |
50 | I'm going past your screen
53 |
54 |
55 |
56 |
![]()
61 |
62 |
63 |
64 |
65 | I'm going past your screen
68 |
69 |
70 |
![]()
75 |
76 |
77 |
78 |
79 |
80 |
81 | I'm going past your screen
84 |
85 |
86 |
87 |
![]()
92 |
93 |
94 |
95 |
96 |
100 |
101 |
--------------------------------------------------------------------------------
/src/templates/footer.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
12 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/templates/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Gone when 55% of home hero
8 |
9 |
10 |
![]()
14 |
15 |
16 |
17 |
18 |
19 |
20 | I'm going past your screen
23 |
24 |
25 |
26 |
![]()
31 |
32 |
33 |
34 |
35 | I'm going past your screen
38 |
39 |
40 |
![]()
45 |
46 |
47 |
48 |
49 |
50 | I'm going past your screen
53 |
54 |
55 |
56 |
![]()
61 |
62 |
63 |
64 |
65 | I'm going past your screen
68 |
69 |
70 |
![]()
75 |
76 |
77 |
78 |
79 |
80 |
81 | I'm going past your screen
84 |
85 |
86 |
87 |
![]()
92 |
93 |
94 |
95 |
96 |
100 |
101 |
--------------------------------------------------------------------------------
/src/templates/parts/footer-content.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/parts/global-scope.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/parts/site/site-meta.php:
--------------------------------------------------------------------------------
1 |
";
9 | print_r($settings);
10 | echo "
";
11 | /* --------------- */
12 | if(isset($WPGLOBAL["document"]) && isset($WPGLOBAL["document"]->data->meta_image->url)){
13 | $fb_img = $WPGLOBAL["document"]->data->meta_image->url;
14 | $tw_img = $WPGLOBAL["document"]->data->meta_image->url;
15 | }
16 | ?>
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | data)){ ?>
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | data) && isset($settings->data->favicon->url)){ ?>
36 |
37 |
url; ?>" />
38 |
url; ?>" />
39 |
url; ?>" />
40 |
url; ?>" />
41 |
url; ?>" />
42 |
url; ?>" />
43 |
44 |
url; ?>" />
45 |
46 |
url; ?>" />
47 |
url; ?>" />
48 |
url; ?>" />
49 |
50 |
51 |
52 |
url; ?>" />
53 |
url; ?>" />
54 |
url; ?>" />
55 |
url; ?>" />
56 |
url; ?>" />
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/templates/parts/site/site-prefetch.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/templates/parts/svgs/social-facebook.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/parts/svgs/social-instagram.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/parts/svgs/social-twitter.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/site/.htaccess:
--------------------------------------------------------------------------------
1 | # Turn off all directory indexes
2 | Options All -indexes
3 |
4 |
5 | RewriteEngine On
6 | RewriteCond %{REQUEST_FILENAME} !-f
7 | RewriteRule ^([^\.]+)$ $1.php [NC,L]
8 |
9 |
10 | ##################################
11 | ##################################
12 | ##################################
13 | ##################################
14 | ##################################
15 | ########## ##########
16 | ########## HIYA BUDDY ##########
17 | ########## ##########
18 | ##################################
19 | ##################################
20 | ##################################
21 | ##################################
22 | ##################################
23 |
24 | # ----------------------------------------------------------------------
25 | # | Cross-origin images |
26 | # ----------------------------------------------------------------------
27 |
28 | # Send the CORS header for images when browsers request it.
29 | #
30 | # https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
31 | # https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
32 |
33 |
34 |
35 |
36 | SetEnvIf Origin ":" IS_CORS
37 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
38 |
39 |
40 |
41 |
42 |
43 | # ----------------------------------------------------------------------
44 | # | Cross-origin web fonts |
45 | # ----------------------------------------------------------------------
46 |
47 | # Allow cross-origin access to web fonts.
48 |
49 |
50 |
51 | Header set Access-Control-Allow-Origin "*"
52 |
53 |
54 |
55 |
56 | # ----------------------------------------------------------------------
57 | # | Media types |
58 | # ----------------------------------------------------------------------
59 |
60 | # Serve resources with the proper media types (f.k.a. MIME types).
61 | #
62 | # https://www.iana.org/assignments/media-types/media-types.xhtml
63 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
64 |
65 |
66 |
67 | # Data interchange
68 |
69 | AddType application/atom+xml atom
70 | AddType application/json json map topojson
71 | AddType application/ld+json jsonld
72 | AddType application/rss+xml rss
73 | AddType application/vnd.geo+json geojson
74 | AddType application/xml rdf xml
75 |
76 |
77 | # JavaScript
78 |
79 | # Servers should use text/javascript for JavaScript resources.
80 | # https://html.spec.whatwg.org/multipage/scripting.html#scriptingLanguages
81 |
82 | AddType text/javascript js mjs
83 |
84 |
85 | # Manifest files
86 |
87 | AddType application/manifest+json webmanifest
88 | AddType application/x-web-app-manifest+json webapp
89 | AddType text/cache-manifest appcache
90 |
91 |
92 | # Media files
93 |
94 | AddType audio/mp4 f4a f4b m4a
95 | AddType audio/ogg oga ogg opus
96 | AddType image/bmp bmp
97 | AddType image/svg+xml svg svgz
98 | AddType image/webp webp
99 | AddType video/mp4 f4v f4p m4v mp4
100 | AddType video/ogg ogv
101 | AddType video/webm webm
102 | AddType video/x-flv flv
103 |
104 | # Serving `.ico` image files with a different media type
105 | # prevents Internet Explorer from displaying them as images:
106 | # https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
107 |
108 | AddType image/x-icon cur ico
109 |
110 |
111 | # WebAssembly
112 |
113 | AddType application/wasm wasm
114 |
115 |
116 | # Web fonts
117 |
118 | AddType font/woff woff
119 | AddType font/woff2 woff2
120 | AddType application/vnd.ms-fontobject eot
121 | AddType font/ttf ttf
122 | AddType font/collection ttc
123 | AddType font/otf otf
124 |
125 |
126 | # Other
127 |
128 | AddType application/octet-stream safariextz
129 | AddType application/x-bb-appworld bbaw
130 | AddType application/x-chrome-extension crx
131 | AddType application/x-opera-extension oex
132 | AddType application/x-xpinstall xpi
133 | AddType text/calendar ics
134 | AddType text/markdown markdown md
135 | AddType text/vcard vcard vcf
136 | AddType text/vnd.rim.location.xloc xloc
137 | AddType text/vtt vtt
138 | AddType text/x-component htc
139 |
140 |
141 |
142 |
143 | # ----------------------------------------------------------------------
144 | # | Character encodings |
145 | # ----------------------------------------------------------------------
146 |
147 | # Serve all resources labeled as `text/html` or `text/plain`
148 | # with the media type `charset` parameter set to `UTF-8`.
149 | #
150 | # https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
151 |
152 | AddDefaultCharset utf-8
153 |
154 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
155 |
156 | # Serve the following file types with the media type `charset`
157 | # parameter set to `UTF-8`.
158 | #
159 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
160 |
161 |
162 | AddCharset utf-8 .atom \
163 | .bbaw \
164 | .css \
165 | .geojson \
166 | .ics \
167 | .js \
168 | .json \
169 | .jsonld \
170 | .manifest \
171 | .markdown \
172 | .md \
173 | .mjs \
174 | .rdf \
175 | .rss \
176 | .topojson \
177 | .vtt \
178 | .webapp \
179 | .webmanifest \
180 | .xloc \
181 | .xml
182 |
183 |
184 |
185 | # ----------------------------------------------------------------------
186 | # | Compression |
187 | # ----------------------------------------------------------------------
188 |
189 |
190 |
191 | # Force compression for mangled `Accept-Encoding` request headers
192 | # https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
193 |
194 |
195 |
196 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
197 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
198 |
199 |
200 |
201 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
202 |
203 | # Compress all output labeled with one of the following media types.
204 | # https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype
205 |
206 |
207 | AddOutputFilterByType DEFLATE "application/atom+xml" \
208 | "application/javascript" \
209 | "application/json" \
210 | "application/ld+json" \
211 | "application/manifest+json" \
212 | "application/rdf+xml" \
213 | "application/rss+xml" \
214 | "application/schema+json" \
215 | "application/vnd.geo+json" \
216 | "application/vnd.ms-fontobject" \
217 | "application/wasm" \
218 | "application/x-font-ttf" \
219 | "application/x-javascript" \
220 | "application/x-web-app-manifest+json" \
221 | "application/xhtml+xml" \
222 | "application/xml" \
223 | "font/collection" \
224 | "font/eot" \
225 | "font/opentype" \
226 | "font/otf" \
227 | "font/ttf" \
228 | "image/bmp" \
229 | "image/svg+xml" \
230 | "image/vnd.microsoft.icon" \
231 | "image/x-icon" \
232 | "text/cache-manifest" \
233 | "text/calendar" \
234 | "text/css" \
235 | "text/html" \
236 | "text/javascript" \
237 | "text/plain" \
238 | "text/markdown" \
239 | "text/vcard" \
240 | "text/vnd.rim.location.xloc" \
241 | "text/vtt" \
242 | "text/x-component" \
243 | "text/x-cross-domain-policy" \
244 | "text/xml"
245 |
246 |
247 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
248 |
249 | # Map the following filename extensions to the specified
250 | # encoding type in order to make Apache serve the file types
251 | # with the appropriate `Content-Encoding` response header
252 | # (do note that this will NOT make Apache compress them!).
253 | #
254 | # If these files types would be served without an appropriate
255 | # `Content-Enable` response header, client applications (e.g.:
256 | # browsers) wouldn't know that they first need to uncompress
257 | # the response, and thus, wouldn't be able to understand the
258 | # content.
259 | #
260 | # https://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
261 |
262 |
263 | AddEncoding gzip svgz
264 |
265 |
266 |
267 |
268 |
269 |
270 | # ----------------------------------------------------------------------
271 | # | ETags |
272 | # ----------------------------------------------------------------------
273 |
274 | # Remove `ETags` as resources are sent with far-future expires headers.
275 | #
276 | # https://developer.yahoo.com/performance/rules.html#etags
277 | # https://tools.ietf.org/html/rfc7232#section-2.3
278 |
279 | # `FileETag None` doesn't work in all cases.
280 |
281 | Header unset ETag
282 |
283 |
284 | FileETag None
285 |
286 |
287 | # ----------------------------------------------------------------------
288 | # | Expires headers |
289 | # ----------------------------------------------------------------------
290 |
291 | # Serve resources with far-future expires headers.
292 | #
293 | # (!) If you don't control versioning with filename-based
294 | # cache busting, you should consider lowering the cache times
295 | # to something like one week.
296 | #
297 | # https://httpd.apache.org/docs/current/mod/mod_expires.html
298 |
299 |
300 |
301 | ExpiresActive on
302 | ExpiresDefault "access plus 1 month"
303 |
304 | # CSS
305 |
306 | ExpiresByType text/css "access plus 1 year"
307 |
308 |
309 | # Data interchange
310 |
311 | ExpiresByType application/atom+xml "access plus 1 hour"
312 | ExpiresByType application/rdf+xml "access plus 1 hour"
313 | ExpiresByType application/rss+xml "access plus 1 hour"
314 |
315 | ExpiresByType application/json "access plus 0 seconds"
316 | ExpiresByType application/ld+json "access plus 0 seconds"
317 | ExpiresByType application/schema+json "access plus 0 seconds"
318 | ExpiresByType application/vnd.geo+json "access plus 0 seconds"
319 | ExpiresByType application/xml "access plus 0 seconds"
320 | ExpiresByType text/calendar "access plus 0 seconds"
321 | ExpiresByType text/xml "access plus 0 seconds"
322 |
323 |
324 | # Favicon (cannot be renamed!) and cursor images
325 |
326 | ExpiresByType image/vnd.microsoft.icon "access plus 1 week"
327 | ExpiresByType image/x-icon "access plus 1 week"
328 |
329 | # HTML
330 |
331 | ExpiresByType text/html "access plus 0 seconds"
332 |
333 |
334 | # JavaScript
335 |
336 | ExpiresByType application/javascript "access plus 1 year"
337 | ExpiresByType application/x-javascript "access plus 1 year"
338 | ExpiresByType text/javascript "access plus 1 year"
339 |
340 |
341 | # Manifest files
342 |
343 | ExpiresByType application/manifest+json "access plus 1 week"
344 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
345 | ExpiresByType text/cache-manifest "access plus 0 seconds"
346 |
347 |
348 | # Markdown
349 |
350 | ExpiresByType text/markdown "access plus 0 seconds"
351 |
352 |
353 | # Media files
354 |
355 | ExpiresByType audio/ogg "access plus 1 month"
356 | ExpiresByType image/bmp "access plus 1 month"
357 | ExpiresByType image/gif "access plus 1 month"
358 | ExpiresByType image/jpeg "access plus 1 month"
359 | ExpiresByType image/png "access plus 1 month"
360 | ExpiresByType image/svg+xml "access plus 1 month"
361 | ExpiresByType image/webp "access plus 1 month"
362 | ExpiresByType video/mp4 "access plus 1 month"
363 | ExpiresByType video/ogg "access plus 1 month"
364 | ExpiresByType video/webm "access plus 1 month"
365 |
366 |
367 | # WebAssembly
368 |
369 | ExpiresByType application/wasm "access plus 1 year"
370 |
371 |
372 | # Web fonts
373 |
374 | # Collection
375 | ExpiresByType font/collection "access plus 1 month"
376 |
377 | # Embedded OpenType (EOT)
378 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
379 | ExpiresByType font/eot "access plus 1 month"
380 |
381 | # OpenType
382 | ExpiresByType font/opentype "access plus 1 month"
383 | ExpiresByType font/otf "access plus 1 month"
384 |
385 | # TrueType
386 | ExpiresByType application/x-font-ttf "access plus 1 month"
387 | ExpiresByType font/ttf "access plus 1 month"
388 |
389 | # Web Open Font Format (WOFF) 1.0
390 | ExpiresByType application/font-woff "access plus 1 month"
391 | ExpiresByType application/x-font-woff "access plus 1 month"
392 | ExpiresByType font/woff "access plus 1 month"
393 |
394 | # Web Open Font Format (WOFF) 2.0
395 | ExpiresByType application/font-woff2 "access plus 1 month"
396 | ExpiresByType font/woff2 "access plus 1 month"
397 |
398 |
399 | # Other
400 |
401 | ExpiresByType text/x-cross-domain-policy "access plus 1 week"
402 |
403 |
--------------------------------------------------------------------------------
/web/site/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Gone when 55% of home hero
8 |
9 |
10 |
![]()
14 |
15 |
16 |
17 |
18 |
19 |
20 | I'm going past your screen
23 |
24 |
25 |
26 |
![]()
31 |
32 |
33 |
34 |
35 | I'm going past your screen
38 |
39 |
40 |
![]()
45 |
46 |
47 |
48 |
49 |
50 | I'm going past your screen
53 |
54 |
55 |
56 |
![]()
61 |
62 |
63 |
64 |
65 | I'm going past your screen
68 |
69 |
70 |
![]()
75 |
76 |
77 |
78 |
79 |
80 |
81 | I'm going past your screen
84 |
85 |
86 |
87 |
![]()
92 |
93 |
94 |
95 |
96 |
100 |
101 |
--------------------------------------------------------------------------------
/web/site/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /server
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* --- CDN Configuration ---
2 | *
3 | * Make sure to fill these out with the folder/bucket IDs from the CDN
4 | * Having this enabled will REQUIRE a cdnkey.json file
5 | */
6 |
7 | /* global __dirname */
8 | const webpack = require("webpack");
9 | const path = require("path");
10 | const entry = require("webpack-glob-entry");
11 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
12 | const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
13 | const CopyWebpackPlugin = require("copy-webpack-plugin");
14 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
15 | const FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin");
16 | const StyleLintPlugin = require("stylelint-webpack-plugin");
17 | const GenerateJSONPlugin = require("generate-json-webpack-plugin");
18 | const WebpackGoogleCloudStoragePlugin = require("webpack-google-cloud-storage-plugin");
19 |
20 | function recursiveIssuer(m) {
21 | if (m.issuer) {
22 | return recursiveIssuer(m.issuer);
23 | } else if (m.name) {
24 | return m.name;
25 | } else {
26 | return false;
27 | }
28 | }
29 |
30 | let compiledPath = "../../../../../compiled/";
31 |
32 | module.exports = {
33 | mode: "production",
34 | entry: {
35 | "js/main.js": [
36 | "./src/js/main.js"
37 | ],
38 | "js/workers/image-load.js": [
39 | "./src/js/_worker/workers/image-load.js"
40 | ],
41 | "../../../../../compiled/init": [
42 | "./src/css/_critical/init.scss"
43 | ],
44 | "../../../../../compiled/fonts": [
45 | "./src/css/_fonts.css"
46 | ],
47 | "../../../../../compiled/main": [
48 | "./src/css/main.scss"
49 | ]
50 | },
51 | output: {
52 | path: path.resolve(__dirname, "./web/site/assets/"),
53 | filename: "[name]",
54 | chunkFilename: compiledPath + "[id].chunk"
55 | },
56 | resolve: {
57 | extensions: [".webpack.js", ".web.js", ".js", ".jsx"]
58 | },
59 | optimization: {
60 | splitChunks: {
61 | cacheGroups: {
62 | init: {
63 | name: "css/_init.css",
64 | test: (m,c,entry = "../../../../../compiled/init") => m.constructor.name === "CssModule" && recursiveIssuer(m) === entry,
65 | chunks: "initial",
66 | enforce: true
67 | },
68 | fonts: {
69 | name: "css/_fonts.css",
70 | test: (m,c,entry = "../../../../../compiled/fonts") => m.constructor.name === "CssModule" && recursiveIssuer(m) === entry,
71 | chunks: "initial",
72 | enforce: true
73 | },
74 | main: {
75 | name: "css/main.css",
76 | test: (m,c,entry = "../../../../../compiled/main") => m.constructor.name === "CssModule" && recursiveIssuer(m) === entry,
77 | chunks: "initial",
78 | enforce: true
79 | },
80 | default: false
81 | }
82 | },
83 | minimizer: [
84 | new UglifyJsPlugin({
85 | test: [
86 | /\.js$/i
87 | ],
88 | sourceMap: false,
89 | uglifyOptions: {
90 | ecma: 5,
91 | compress: {
92 | warnings: false
93 | }
94 | }
95 | }),
96 | new OptimizeCssAssetsPlugin({
97 | assetNameRegExp: /\.css$/i,
98 | cssProcessorOptions: {
99 | zindex: false
100 | }
101 | })
102 | ]
103 | },
104 | module: {
105 | rules: [
106 | {
107 | test: /\.js$/,
108 | exclude: /node_modules/,
109 | use: [
110 | {
111 | loader: "babel-loader",
112 | options: {
113 | presets: [
114 | [
115 | "@babel/preset-env", {
116 | "targets": {
117 | "node": "current",
118 | "browsers": [ "last 2 versions", "ie 11" ]
119 | }
120 | }
121 | ]
122 | ],
123 | plugins: [
124 | [
125 | "@babel/plugin-transform-runtime", {
126 | "regenerator": true
127 | }
128 | ]
129 | ]
130 | }
131 | },
132 | {
133 | loader: "eslint-loader"
134 | }
135 | ]
136 | },
137 | {
138 | test: /\.(png|woff|woff2|eot|ttf|svg)$/,
139 | loader: "url-loader?limit=100000"
140 | },
141 | {
142 | test: /\.(sa|sc|c)ss$/,
143 | use: [
144 | MiniCssExtractPlugin.loader,
145 | {
146 | loader: "css-loader",
147 | options: {
148 | importLoaders: 1,
149 | minimize: true
150 | }
151 | },
152 | {
153 | loader: "postcss-loader",
154 | options: {
155 | config: {
156 | path: path.resolve(__dirname, "./postcss.config.js")
157 | }
158 | }
159 | },
160 | {
161 | loader: "sass-loader"
162 | }
163 | ]
164 | }
165 | ]
166 | },
167 | plugins: [
168 | new MiniCssExtractPlugin({
169 | filename: "[name]"
170 | }),
171 | new CopyWebpackPlugin([
172 | {
173 | from: "./src/images",
174 | to: "./images",
175 | force: true,
176 | ignore: [
177 | ".gitkeep"
178 | ]
179 | },
180 | {
181 | from: "./src/fonts",
182 | to: "./fonts",
183 | force: true,
184 | ignore: [
185 | ".gitkeep"
186 | ]
187 | },
188 | // {
189 | // from: "./src/css/_fonts.css",
190 | // to: "./fonts",
191 | // force: true,
192 | // ignore: [
193 | // ".gitkeep"
194 | // ]
195 | // },
196 | {
197 | from: "./src/templates",
198 | to: "../../site",
199 | force: true,
200 | ignore: [
201 | ".gitkeep"
202 | ]
203 | }
204 | ]),
205 | new StyleLintPlugin(),
206 | new webpack.NoEmitOnErrorsPlugin(),
207 | new FriendlyErrorsPlugin()
208 | ]
209 | };
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
/src/templates/header.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |