├── .gitignore
├── .gitattributes
├── src
├── favicon.ico
├── img
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ └── 4.jpg
├── js
│ ├── utils.js
│ ├── cursor.js
│ ├── index.js
│ └── thumbnailAnimation.js
├── index.html
└── css
│ └── base.css
├── dist
├── 1.dc197a9a.jpg
├── 2.3ca6bb44.jpg
├── 3.a433b89d.jpg
├── 4.c6d96be5.jpg
├── favicon.26242483.ico
├── index.html
└── base.98fd6c19.css
├── package.json
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.cache
3 | package-lock.json
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/src/img/1.jpg
--------------------------------------------------------------------------------
/src/img/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/src/img/2.jpg
--------------------------------------------------------------------------------
/src/img/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/src/img/3.jpg
--------------------------------------------------------------------------------
/src/img/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/src/img/4.jpg
--------------------------------------------------------------------------------
/dist/1.dc197a9a.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/dist/1.dc197a9a.jpg
--------------------------------------------------------------------------------
/dist/2.3ca6bb44.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/dist/2.3ca6bb44.jpg
--------------------------------------------------------------------------------
/dist/3.a433b89d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/dist/3.a433b89d.jpg
--------------------------------------------------------------------------------
/dist/4.c6d96be5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/dist/4.c6d96be5.jpg
--------------------------------------------------------------------------------
/dist/favicon.26242483.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/ThumbFullTransition/HEAD/dist/favicon.26242483.ico
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ThumbFullTransition",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "parcel src/index.html --open",
8 | "clean": "rm -rf dist/*",
9 | "build:parcel": "parcel build src/index.html --no-content-hash --no-minify --no-source-maps --public-url ./",
10 | "build": "npm run clean && npm run build:parcel"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/codrops/ThumbFullTransition.git"
15 | },
16 | "keywords": [],
17 | "author": "Codrops",
18 | "license": "MIT",
19 | "homepage": "[HOMEPAGE]",
20 | "bugs": {
21 | "url": "https://github.com/codrops/ThumbFullTransition/issues"
22 | },
23 | "dependencies": {
24 | "gsap": "^3.5.0",
25 | "imagesloaded": "^4.1.4",
26 | "locomotive-scroll": "^3.5.4",
27 | "parcel-bundler": "^1.12.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | const imagesLoaded = require('imagesloaded');
2 |
3 | // Linear interpolation
4 | const lerp = (a, b, n) => (1 - n) * a + n * b;
5 |
6 | // Gets the mouse position
7 | const getMousePos = e => {
8 | return {
9 | x : e.clientX,
10 | y : e.clientY
11 | };
12 | };
13 |
14 | const calcWinsize = () => {
15 | return {width: window.innerWidth, height: window.innerHeight};
16 | };
17 |
18 | // Preload images
19 | const preloadImages = (selector = 'img') => {
20 | return new Promise((resolve) => {
21 | imagesLoaded(document.querySelectorAll(selector), resolve);
22 | });
23 | };
24 |
25 | // Preload images
26 | const preloadFonts = (id) => {
27 | return new Promise((resolve) => {
28 | WebFont.load({
29 | typekit: {
30 | id: id
31 | },
32 | active: resolve
33 | });
34 | });
35 | };
36 |
37 | export {
38 | lerp,
39 | getMousePos,
40 | calcWinsize,
41 | preloadImages,
42 | preloadFonts
43 | };
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2009 - 2020 [Codrops](https://tympanus.net/codrops)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Thumbnail to Full Width Image Animation
2 |
3 | An experimental animation where an image goes from thumbnail to full width, allowing for a classy entry to an article view. Based on this [animation](https://twitter.com/wrongakram/status/1279204900661071875) by [Akram Khalid](https://twitter.com/wrongakram).
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/?p=50733)
8 |
9 | [Demo](http://tympanus.net/Development/ThumbFullTransition/)
10 |
11 |
12 | ## Installation
13 |
14 | Install dependencies:
15 |
16 | ```
17 | npm install
18 | ```
19 |
20 | Compile the code for development and start a local server:
21 |
22 | ```
23 | npm start
24 | ```
25 |
26 | Create the build:
27 |
28 | ```
29 | npm run build
30 | ```
31 |
32 | ## Credits
33 |
34 | - Images by [DeMorris Byrd](https://unsplash.com/@byrdman85)
35 |
36 | ## Misc
37 |
38 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/)
39 |
40 | ## License
41 | [MIT](LICENSE)
42 |
43 | Made with :blue_heart: by [Codrops](http://www.codrops.com)
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/js/cursor.js:
--------------------------------------------------------------------------------
1 | import { gsap } from 'gsap';
2 | import { lerp, getMousePos } from './utils';
3 |
4 | // Track the mouse position
5 | let mouse = {x: 0, y: 0};
6 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev));
7 |
8 | export default class Cursor {
9 | constructor(el) {
10 | this.DOM = {el: el};
11 | this.DOM.el.style.opacity = 0;
12 |
13 | this.bounds = this.DOM.el.getBoundingClientRect();
14 |
15 | this.renderedStyles = {
16 | tx: {previous: 0, current: 0, amt: 0.2},
17 | ty: {previous: 0, current: 0, amt: 0.2},
18 | scale: {previous: 1, current: 1, amt: 0.15},
19 | //opacity: {previous: 1, current: 1, amt: 0.1}
20 | };
21 |
22 | this.onMouseMoveEv = () => {
23 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2;
24 | this.renderedStyles.ty.previous = this.renderedStyles.ty.previous = mouse.y - this.bounds.height/2;
25 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1});
26 | requestAnimationFrame(() => this.render());
27 | window.removeEventListener('mousemove', this.onMouseMoveEv);
28 | };
29 | window.addEventListener('mousemove', this.onMouseMoveEv);
30 | }
31 | enter() {
32 | this.renderedStyles['scale'].current = 4.5;
33 | //this.renderedStyles['opacity'].current = 0.5;
34 | }
35 | leave() {
36 | this.renderedStyles['scale'].current = 1;
37 | //this.renderedStyles['opacity'].current = 1;
38 | }
39 | render() {
40 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2;
41 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2;
42 |
43 | for (const key in this.renderedStyles ) {
44 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt);
45 | }
46 |
47 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px) scale(${this.renderedStyles['scale'].previous})`;
48 | //this.DOM.el.style.opacity = this.renderedStyles['opacity'].previous;
49 |
50 | requestAnimationFrame(() => this.render());
51 | }
52 | }
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | import { preloadImages, preloadFonts } from './utils';
2 | import { gsap } from 'gsap';
3 | import Cursor from './cursor';
4 | import LocomotiveScroll from 'locomotive-scroll';
5 | import ThumbnailAnimation from './thumbnailAnimation';
6 |
7 | // Preload images and fonts
8 | Promise.all([preloadImages(), preloadFonts('lne5uqk')]).then(() => {
9 | // Remove loader (loading class)
10 | document.body.classList.remove('loading');
11 |
12 | // Initialize custom cursor
13 | const cursor = new Cursor(document.querySelector('.cursor'));
14 |
15 | // Initialize the thumb animation obj
16 | const thumbAnimation = new ThumbnailAnimation();
17 |
18 | // In a real case scenario the visible thumbs would be part of some sort of slideshow
19 | // We are just showing how to do the click/expand animation, so for now let's just animate out the two thumbs (left and right) once we click the thumb in the middle.
20 | const sideThumbs = document.querySelectorAll('.thumbs > .thumb');
21 |
22 | // Mouse entering the middle thumb
23 | thumbAnimation.on('enter', () => {
24 | cursor.enter();
25 |
26 | // Slightly move the side thumbs away
27 | gsap.to(sideThumbs, {
28 | duration: 1,
29 | ease: 'expo',
30 | opacity: 0.8,
31 | x: i => i ? '25%' : '-25%',
32 | //scale: 1.1,
33 | rotateY: i => i ? -15 : 15,
34 | z: 40
35 | });
36 | });
37 |
38 | // Mouse leaving the middle thumb
39 | thumbAnimation.on('leave', () => {
40 | cursor.leave();
41 |
42 | // Move the side thumbs back into its original position
43 | gsap.to(sideThumbs, {
44 | duration: 1,
45 | ease: 'expo',
46 | opacity: 1,
47 | x: '0%',
48 | //scale: 1
49 | rotateY: 0,
50 | z: 0
51 | });
52 | });
53 |
54 | // Middle thumb click and start animation
55 | thumbAnimation.on('start', () => {
56 | cursor.leave();
57 |
58 | // Move the side thumbs away
59 | gsap.to(sideThumbs, {
60 | duration: 0.8,
61 | ease: 'power3.inOut',
62 | opacity: 0,
63 | x: i => i ? '150%' : '-150%',
64 | //scale: 0.6
65 | rotateY: i => i ? -45 : 45,
66 | z: 200
67 | });
68 | });
69 |
70 | // Middle thumb image got fully expanded
71 | thumbAnimation.on('scrollready', () => {
72 | // Initialize the Locomotive scroll
73 | new LocomotiveScroll({
74 | el: document.querySelector('[data-scroll-container]'),
75 | smooth: true
76 | });
77 | });
78 |
79 | [...document.querySelectorAll('a')].forEach(link => {
80 | link.addEventListener('mouseenter', () => cursor.enter());
81 | link.addEventListener('mouseleave', () => cursor.leave());
82 | });
83 | });
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Thumbnail to Full Width Image Animation | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 |
49 |
50 |
Then they opened the firmaments and the Light descended below to the lower regions and to those who were without form, having no true likeness. It was thus that they got the likeness of the Light for themselves. Some rejoiced because the Light had come to them, and that they had been made rich thereby. Others mourned because they were made poor and that which they thought they had was taken away from them. Thus came He, who went forth full of grace, and was taken captive with a captivity. A light of glory was given to the æons who had received the Spark, and guardian spirits were sent to them who are Gamanêl, Etrempsouchos, and Agramas, and those who are with them. They bring help to those who have believed in the Spark of Light.
51 |
The Crown that the Deathless pray for
52 |
55 |
Now in the Space of the Indivisible Atom are twelve Founts, above which are the twelve Paternities who surround the Indivisible [Queen] like Deeps or like Skies and make for Her a crown in which is every kind of life: all modes of Triple-powered life, of Uncontainable life, of Infinite life, of Ineffable life, of Silent life, of Unknown life, of Solitary life, of Unshakable life, of First-manifested life, of Self-born life, of True life. All is therein. Every species is in it, all Gnôses and every power which has received the Light, yea, all Mind manifests itself therein. This is the Crown which the Father of the Universe has placed upon the Indivisible [Queen] with three hundred and sixty-five kinds in it, brilliant and filling the Universe with an incorruptible and unfailing light.
56 |
57 |
63 |
64 |
65 |
66 |
67 |
07
68 |
69 |
Into Light
70 |
71 |
72 |
02
73 |
74 |
Surrender
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Thumbnail to Full Width Image Animation | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 |
25 |
26 |
38 |
39 |
40 |
57 |
58 |
Then they opened the firmaments and the Light descended below to the lower regions and to those who were without form, having no true likeness. It was thus that they got the likeness of the Light for themselves. Some rejoiced because the Light had come to them, and that they had been made rich thereby. Others mourned because they were made poor and that which they thought they had was taken away from them. Thus came He, who went forth full of grace, and was taken captive with a captivity. A light of glory was given to the æons who had received the Spark, and guardian spirits were sent to them who are Gamanêl, Etrempsouchos, and Agramas, and those who are with them. They bring help to those who have believed in the Spark of Light.
59 |
The Crown that the Deathless pray for
60 |
63 |
Now in the Space of the Indivisible Atom are twelve Founts, above which are the twelve Paternities who surround the Indivisible [Queen] like Deeps or like Skies and make for Her a crown in which is every kind of life: all modes of Triple-powered life, of Uncontainable life, of Infinite life, of Ineffable life, of Silent life, of Unknown life, of Solitary life, of Unshakable life, of First-manifested life, of Self-born life, of True life. All is therein. Every species is in it, all Gnôses and every power which has received the Light, yea, all Mind manifests itself therein. This is the Crown which the Father of the Universe has placed upon the Indivisible [Queen] with three hundred and sixty-five kinds in it, brilliant and filling the Universe with an incorruptible and unfailing light.
64 |
65 |
71 |
72 |
73 |
74 |
75 |
07
76 |
77 |
Into Light
78 |
79 |
80 |
02
81 |
82 |
Surrender
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/js/thumbnailAnimation.js:
--------------------------------------------------------------------------------
1 | import { gsap } from 'gsap';
2 | import { EventEmitter } from 'events';
3 | import { calcWinsize } from './utils';
4 |
5 | // Calculate the viewport size
6 | let winsize = calcWinsize();
7 | window.addEventListener('resize', () => winsize = calcWinsize());
8 |
9 | export default class ThumbnailAnimation extends EventEmitter {
10 | constructor(el) {
11 | super();
12 | // Cache some DOM elements
13 | this.getDOMElems();
14 | this.config = {
15 | // True if not yet expanded
16 | isThumbView: true
17 | };
18 | // Calculate the necessary transforms to adjust the middle image size and position
19 | this.layout();
20 | this.initEvents();
21 | }
22 | getDOMElems() {
23 | this.DOM = {contentEl : document.querySelector('.content')};
24 | this.DOM.intro = this.DOM.contentEl.querySelector('.content__intro');
25 | this.DOM.introImg = this.DOM.contentEl.querySelector('.content__intro-img');
26 | this.DOM.introImgWrap = this.DOM.contentEl.querySelector('.content__intro-imgWrap');
27 | this.DOM.meta = this.DOM.contentEl.querySelector('.content__meta');
28 | this.DOM.metaTitle = this.DOM.contentEl.querySelector('.content__meta-title');
29 | this.DOM.titleBlock = this.DOM.contentEl.querySelector('.content__title > .anim-block');
30 | this.DOM.subtitleBlock = this.DOM.contentEl.querySelector('.content__subtitle > .anim-block');
31 | }
32 | layout() {
33 | // Hide main title and subtitle
34 | gsap.set(this.DOM.titleBlock, {y: '100%'});
35 | gsap.set(this.DOM.subtitleBlock, {y: '100%'});
36 |
37 | const bodyComputedStyle = getComputedStyle(document.body);
38 |
39 | // Get the image settings from the variables set in the stylesheet
40 | let imageSettings = {
41 | imageWidthStart: winsize.width,
42 | imageHeightStart: parseFloat(bodyComputedStyle.getPropertyValue('--image-height-start')),
43 | imageWidthEnd: parseFloat(bodyComputedStyle.getPropertyValue('--image-width-end')),
44 | imageHeightEnd: parseFloat(bodyComputedStyle.getPropertyValue('--image-height-end')),
45 | metaOffset: parseFloat(bodyComputedStyle.getPropertyValue('--meta-offset'))
46 | };
47 |
48 | // Set the scale value from the initial and the target dimensions
49 | // and move the intro image wrap to the center of the screen.
50 | // We want to set the full width image to be the size of a thumbnail.
51 | // For that we scale down the outer wrapper and set a counter scale to
52 | // the inner image, followed by another scale so that the inner image
53 | // becomes the size of the thumbnail, too.
54 | let introRect = this.DOM.intro.getBoundingClientRect();
55 | let introTransform = {
56 | scaleX: imageSettings.imageWidthEnd / imageSettings.imageWidthStart,
57 | scaleY: imageSettings.imageHeightEnd / imageSettings.imageHeightStart,
58 | y: (winsize.height/2 - introRect.top) - introRect.height/2
59 | };
60 | gsap.set(this.DOM.intro, {
61 | y: introTransform.y,
62 | scaleX: introTransform.scaleX,
63 | scaleY: introTransform.scaleY
64 | });
65 |
66 | // Apply the counter scale and again the target dimension scale to the inner image
67 | gsap.set(this.DOM.introImg, {
68 | scaleX: 1/introTransform.scaleX * imageSettings.imageWidthEnd / this.DOM.introImg.clientWidth,
69 | scaleY: 1/introTransform.scaleY * imageSettings.imageHeightEnd / this.DOM.introImg.clientHeight
70 | });
71 |
72 | // Calculate new sizes/positions (after transform)
73 | introRect = this.DOM.intro.getBoundingClientRect();
74 | // content__meta size/position
75 | const metaRect = this.DOM.meta.getBoundingClientRect();
76 | let metaTransform = {
77 | x: introRect.left - metaRect.left,
78 | y: introRect.top - metaRect.top
79 | };
80 | gsap.set(this.DOM.meta, {
81 | x: metaTransform.x,
82 | y: metaTransform.y - imageSettings.metaOffset
83 | });
84 |
85 | // Show the meta title element
86 | gsap.set(this.DOM.metaTitle, {opacity: 1});
87 | }
88 | initEvents() {
89 | this.onClickIntroFn = () => this.onClickIntro();
90 | this.onMouseenterIntroFn = () => this.onMouseenterIntro();
91 | this.onMouseleaveIntroFn = () => this.onMouseleaveIntro();
92 | this.onResizeFn = () => this.onResize();
93 | this.DOM.intro.addEventListener('click', this.onClickIntroFn);
94 | this.DOM.intro.addEventListener('mouseenter', this.onMouseenterIntroFn);
95 | this.DOM.intro.addEventListener('mouseleave', this.onMouseleaveIntroFn);
96 | window.addEventListener('resize', this.onResizeFn);
97 | }
98 | onResize() {
99 | if ( !this.config.isThumbView ) return false;
100 | // Reset transforms
101 | this.DOM.intro.style.transform = 'none';
102 | this.DOM.introImg.style.transform ='none';
103 | this.DOM.meta.style.transform = 'none';
104 | // Re layout
105 | this.layout();
106 | }
107 | onClickIntro() {
108 | // For demo purposes, remove the click event once clicked
109 | // We are just showcasing the effect
110 | // In a real case scenario there would be a back to slideshow button and the click event wouldn't need to be removed
111 | this.DOM.intro.removeEventListener('click', this.onClickIntroFn);
112 | this.config.isThumbView = false;
113 |
114 | // Animate all the elements (image, title, subtitle, meta)
115 | gsap
116 | .timeline({
117 | onStart: () => this.emit('start'),
118 | onComplete: () => this.emit('end')
119 | })
120 | .to(this.DOM.intro, {
121 | duration: 1,
122 | ease: 'expo.inOut',
123 | y: 0,
124 | scaleX: 1,
125 | scaleY: 1,
126 | onComplete: () => {
127 | this.DOM.contentEl.classList.add('content--scroll');
128 | this.emit('scrollready');
129 | }
130 | })
131 | .to(this.DOM.introImg, {
132 | duration: 1,
133 | ease: 'expo.inOut',
134 | scaleX: 1,
135 | scaleY: 1
136 | }, 0)
137 | .to(this.DOM.introImgWrap, {
138 | duration: 1,
139 | ease: 'expo.inOut',
140 | scale: 1
141 | }, 0)
142 | .to(this.DOM.metaTitle, {
143 | duration: 0.6,
144 | ease: 'expo.inOut',
145 | opacity: 0
146 | }, 0)
147 | .to(this.DOM.meta, {
148 | duration: 1,
149 | ease: 'expo.inOut',
150 | x: 0,
151 | y: 0
152 | }, 0)
153 | .to([this.DOM.titleBlock, this.DOM.subtitleBlock], {
154 | duration: 0.8,
155 | ease: 'expo',
156 | startAt: {rotation: 3},
157 | y: '0%',
158 | rotation: 0,
159 | stagger: 0.3
160 | }, 0.7);
161 | }
162 | // On mouseenter animate the scale of the intro image wrapper
163 | // This wrapper is necessary so we don't have to play with the image itself (which has already a transform applied)
164 | onMouseenterIntro() {
165 | if ( !this.config.isThumbView ) return false;
166 | this.emit('enter');
167 |
168 | gsap
169 | .to(this.DOM.introImgWrap, {
170 | duration: 1,
171 | ease: 'expo',
172 | scale: 1.2
173 | });
174 | }
175 | // On mouseleave reset/animate the scale of the intro image wrapper
176 | onMouseleaveIntro() {
177 | if ( !this.config.isThumbView ) return false;
178 | this.emit('leave');
179 |
180 | gsap
181 | .to(this.DOM.introImgWrap, {
182 | duration: 1,
183 | ease: 'expo',
184 | scale: 1
185 | });
186 | }
187 | }
--------------------------------------------------------------------------------
/src/css/base.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 15px;
9 | }
10 |
11 | body {
12 | margin: 0;
13 | --color-text: #111110;
14 | --color-bg: #E9E6E2;
15 | --color-link: #000;
16 | --color-link-hover: #111110;
17 | --image-height-start: 555px;
18 | --image-width-end: 260px;
19 | --image-height-end: 320px;
20 | --image-offset: 50px;
21 | --meta-offset: 40px;
22 | --color-footer-bg: #dad6cf;
23 | --content-padding: 50px;
24 | --content-width: 850px;
25 | color: var(--color-text);
26 | background-color: var(--color-bg);
27 | font-family: mentone, sans-serif;
28 | overflow: hidden;
29 | overflow-y: scroll;
30 | --cursor-stroke: none;
31 | --cursor-fill: #c5b4b2;
32 | --cursor-stroke-width: 1px;
33 | transform: translate3d(0,0,0);
34 | }
35 |
36 | /* Page Loader */
37 | .js .loading::before,
38 | .js .loading::after {
39 | content: '';
40 | position: fixed;
41 | z-index: 1000;
42 | }
43 |
44 | .js .loading::before {
45 | top: 0;
46 | left: 0;
47 | width: 100%;
48 | height: 100%;
49 | background: var(--color-bg);
50 | }
51 |
52 | .js .loading::after {
53 | top: 50%;
54 | left: 50%;
55 | width: 60px;
56 | height: 60px;
57 | margin: -30px 0 0 -30px;
58 | border-radius: 50%;
59 | opacity: 0.4;
60 | background: var(--color-link);
61 | animation: loaderAnim 0.7s linear infinite alternate forwards;
62 |
63 | }
64 |
65 | @keyframes loaderAnim {
66 | to {
67 | opacity: 1;
68 | transform: scale3d(0.5,0.5,1);
69 | }
70 | }
71 |
72 | a {
73 | text-decoration: underline;
74 | color: var(--color-link);
75 | outline: none;
76 | }
77 |
78 | a:hover,
79 | a:focus {
80 | text-decoration: none;
81 | color: var(--color-link-hover);
82 | outline: none;
83 | }
84 |
85 | .frame {
86 | padding: 1.25rem;
87 | width: 100%;
88 | position: absolute;
89 | z-index: 1000;
90 | text-transform: uppercase;
91 | font-size: 0.85rem;
92 | }
93 |
94 | .frame__title {
95 | font-size: 0.85rem;
96 | font-weight: 600;
97 | margin: 0 0 0.25rem;
98 | }
99 |
100 | .frame__tagline {
101 | margin: 0.5rem 0;
102 | }
103 |
104 | .frame__links {
105 | margin-bottom: 1rem;
106 | }
107 |
108 | .frame__links a:not(:last-child) {
109 | margin-right: 1rem;
110 | }
111 |
112 | .frame__button {
113 | cursor: not-allowed;
114 | font-weight: 600;
115 | text-decoration: none;
116 | }
117 |
118 | .content {
119 | position: relative;
120 | }
121 |
122 | .js .content {
123 | overflow: hidden;
124 | height: 100vh;
125 | pointer-events: none;
126 | }
127 |
128 | .js .content--scroll {
129 | overflow: visible;
130 | height: auto;
131 | pointer-events: auto;
132 | }
133 |
134 | .inner {
135 | max-width: var(--content-width);
136 | padding: 0 var(--content-padding);
137 | margin: 0 auto;
138 | }
139 |
140 | .content__header {
141 | display: flex;
142 | flex-direction: column;
143 | padding-top: 10rem;
144 | }
145 |
146 | .content__meta {
147 | position: relative;
148 | font-size: 0.85rem;
149 | width: var(--image-width-end);
150 | }
151 |
152 | .content__meta-number {
153 | font-weight: bold;
154 | }
155 |
156 | .content__meta-title {
157 | position: absolute;
158 | top: 0;
159 | left: 0;
160 | text-transform: uppercase;
161 | transform: translateY(calc(var(--meta-offset) * 2 + var(--image-height-end))) translateY(-100%);
162 | }
163 |
164 | .js .content__meta-title {
165 | opacity: 0;
166 | }
167 |
168 | .content__title {
169 | letter-spacing: -0.15rem;
170 | margin: 1rem 0;
171 | line-height: 1;
172 | font-family: nocturne-serif, serif;
173 | font-weight: 600;
174 | font-size: clamp(1.5rem, 10vw, 8rem);
175 | }
176 |
177 | .js .anim-block-wrap {
178 | position: relative;
179 | overflow: hidden;
180 | }
181 |
182 | .js .anim-block {
183 | display: block;
184 | }
185 |
186 | .content__subtitle {
187 | font-family: nocturne-serif, serif;
188 | font-weight: 600;
189 | font-size: 1.25rem;
190 | margin: 0.5rem 0 3rem 0;
191 | line-height: 1.2;
192 | }
193 |
194 | .content__breakout {
195 | width: 100vw;
196 | left: 50%;
197 | right: 50%;
198 | margin: auto -50vw 0;
199 | position: relative;
200 | }
201 |
202 | .content__intro {
203 | height: var(--image-height-start);
204 | overflow: hidden;
205 | display: flex;
206 | align-items: center;
207 | pointer-events: auto;
208 | cursor: pointer;
209 | will-change: transform;
210 | }
211 |
212 | .content--scroll .content__intro {
213 | cursor: default;
214 | }
215 |
216 | .content__intro-imgWrap {
217 | will-change: transform;
218 | }
219 |
220 | .content__intro-img {
221 | width: 100vw;
222 | height: auto;
223 | position: relative;
224 | }
225 |
226 | .content__body {
227 | padding: 0 0 10rem 0;
228 | font-weight: 400;
229 | line-height: 1.6;
230 | }
231 |
232 | .js .content__body {
233 | opacity: 0;
234 | }
235 |
236 | .js .content--scroll .content__body {
237 | opacity: 1;
238 | }
239 |
240 | .content__body-para {
241 | margin: 4rem 0;
242 | padding: 0 1rem;
243 | }
244 |
245 | .anim-show {
246 | transform: translateY(100%);
247 | opacity: 0;
248 | transition: all .8s cubic-bezier(0.2,1,0.3,1);
249 | }
250 |
251 | .anim-show.is-inview {
252 | transform: translateY(0%);
253 | opacity: 1;
254 | }
255 |
256 | .cell--1-3 {
257 | grid-column: 1 / span 3;
258 | }
259 |
260 | .cell--2-3 {
261 | grid-column: 2 / span 2;
262 | }
263 |
264 | .cell--3 {
265 | grid-column: 3;
266 | }
267 |
268 | .cell--1-3 {
269 | grid-column: 1 / span 3;
270 | }
271 |
272 | .content__body-imgwrap {
273 | width: 100%;
274 | height: calc(((var(--content-width) - var(--content-padding)) * 828/1000) - 100px);
275 | overflow: hidden;
276 | position: relative;
277 | display: flex;
278 | align-items: center;
279 | justify-content: center;
280 | }
281 |
282 | .content__body-image {
283 | width: 100%;
284 | height: calc((var(--content-width) - var(--content-padding)) * 828/1000);
285 | background-size: cover;
286 | background-position: 50% 50%;
287 | }
288 |
289 | .content__footer {
290 | padding: 3rem 4rem 3.5rem;
291 | background: var(--color-footer-bg);
292 | }
293 |
294 | .inner--grid {
295 | text-align: left;
296 | }
297 |
298 | .content__footer-copyright {
299 | text-decoration: none;
300 | }
301 |
302 | .content__footer-next {
303 | display: block;
304 | font-family: nocturne-serif, serif;
305 | font-weight: 600;
306 | font-size: 3rem;
307 | cursor: not-allowed;
308 | }
309 |
310 | .cursor {
311 | display: none;
312 | }
313 |
314 | .thumbs {
315 | opacity: 0;
316 | pointer-events: none;
317 | position: fixed;
318 | top: 0;
319 | left: 0;
320 | width: 100%;
321 | height: 100vh;
322 | padding: 45px;
323 | display: flex;
324 | align-items: center;
325 | justify-content: space-between;
326 | perspective: 1000px;
327 | }
328 |
329 | .thumb {
330 | position: relative;
331 | display: flex;
332 | flex-direction: column;
333 | justify-content: center;
334 | font-size: 0.85rem;
335 | width: var(--image-width-end);
336 | will-change: transform;
337 | }
338 |
339 | .thumb__img {
340 | cursor: not-allowed;
341 | width: 100%;
342 | height: var(--image-height-end);
343 | background-size: cover;
344 | background-position: 50% 50%;
345 | }
346 |
347 | .thumb__number {
348 | position: absolute;
349 | font-weight: bold;
350 | bottom: 100%;
351 | transform: translateY(calc(-1 * var(--meta-offset) + 100%));
352 | }
353 |
354 | .thumb__title {
355 | position: absolute;
356 | top: 100%;
357 | left: 0;
358 | text-transform: uppercase;
359 | transform: translateY(calc(var(--meta-offset) - 100%));
360 | }
361 |
362 | /*! locomotive-scroll v3.5.4 | MIT License | https://github.com/locomotivemtl/locomotive-scroll */
363 | html.has-scroll-smooth {
364 | overflow: hidden;
365 | }
366 |
367 | html.has-scroll-dragging {
368 | -webkit-user-select: none;
369 | -moz-user-select: none;
370 | -ms-user-select: none;
371 | user-select: none;
372 | }
373 |
374 | .has-scroll-smooth body {
375 | overflow: hidden;
376 | }
377 |
378 | .has-scroll-smooth [data-scroll-container] {
379 | min-height: 100vh;
380 | }
381 |
382 | .c-scrollbar {
383 | position: absolute;
384 | right: 0;
385 | top: 0;
386 | width: 11px;
387 | height: 100vh;
388 | transform-origin: center right;
389 | transition: transform 0.3s, opacity 0.3s;
390 | opacity: 0;
391 | }
392 | .c-scrollbar:hover {
393 | transform: scaleX(1.45);
394 | }
395 | .c-scrollbar:hover, .has-scroll-scrolling .c-scrollbar, .has-scroll-dragging .c-scrollbar {
396 | opacity: 1;
397 | }
398 |
399 | .c-scrollbar_thumb {
400 | position: absolute;
401 | top: 0;
402 | right: 0;
403 | background-color: black;
404 | opacity: 0.5;
405 | width: 7px;
406 | border-radius: 10px;
407 | margin: 2px;
408 | cursor: -webkit-grab;
409 | cursor: grab;
410 | }
411 | .has-scroll-dragging .c-scrollbar_thumb {
412 | cursor: -webkit-grabbing;
413 | cursor: grabbing;
414 | }
415 |
416 | @media screen and (min-width: 53em) {
417 | body {
418 | --meta-offset: 100px;
419 | }
420 | .frame {
421 | padding: 1.25rem 3rem;
422 | text-align: left;
423 | display: grid;
424 | align-content: space-between;
425 | max-width: none;
426 | pointer-events: none;
427 | grid-template-columns: 1fr 1fr 8rem;
428 | grid-template-rows: auto;
429 | grid-template-areas: 'title links menu';
430 | }
431 | .frame__title-wrap {
432 | grid-area: title;
433 | }
434 | .frame__button {
435 | justify-self: end;
436 | grid-area: menu;
437 | }
438 | .frame__links {
439 | grid-area: links;
440 | margin: 0;
441 | justify-self: end;
442 | }
443 | .frame a {
444 | pointer-events: auto;
445 | }
446 | .content__body {
447 | display: grid;
448 | grid-template-columns: 50% 25% 25%;
449 | align-items: start;
450 | }
451 | .text-right {
452 | text-align: right;
453 | }
454 | .inner--grid {
455 | display: grid;
456 | grid-template-columns: 50% 50%;
457 | align-items: center;
458 | }
459 | .thumbs {
460 | opacity: 1;
461 | }
462 | .thumb__img {
463 | pointer-events: auto;
464 | }
465 | }
466 |
467 | @media screen and (max-height: 700px) {
468 | body {
469 | --meta-offset: 40px;
470 | }
471 | }
472 |
473 | @media (any-pointer: fine) {
474 | .cursor {
475 | position: fixed;
476 | top: 0;
477 | left: 0;
478 | display: block;
479 | pointer-events: none;
480 | mix-blend-mode: color-burn;
481 | }
482 | .cursor__inner {
483 | fill: var(--cursor-fill);
484 | stroke: var(--cursor-stroke);
485 | stroke-width: var(--cursor-stroke-width);
486 | opacity: 0.7;
487 | }
488 | .credits {
489 | padding-left: 25vw;
490 | }
491 | }
--------------------------------------------------------------------------------
/dist/base.98fd6c19.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 15px;
9 | }
10 |
11 | body {
12 | margin: 0;
13 | --color-text: #111110;
14 | --color-bg: #E9E6E2;
15 | --color-link: #000;
16 | --color-link-hover: #111110;
17 | --image-height-start: 555px;
18 | --image-width-end: 260px;
19 | --image-height-end: 320px;
20 | --image-offset: 50px;
21 | --meta-offset: 40px;
22 | --color-footer-bg: #dad6cf;
23 | --content-padding: 50px;
24 | --content-width: 850px;
25 | color: var(--color-text);
26 | background-color: var(--color-bg);
27 | font-family: mentone, sans-serif;
28 | overflow: hidden;
29 | overflow-y: scroll;
30 | --cursor-stroke: none;
31 | --cursor-fill: #c5b4b2;
32 | --cursor-stroke-width: 1px;
33 | transform: translate3d(0,0,0);
34 | }
35 |
36 | /* Page Loader */
37 | .js .loading::before,
38 | .js .loading::after {
39 | content: '';
40 | position: fixed;
41 | z-index: 1000;
42 | }
43 |
44 | .js .loading::before {
45 | top: 0;
46 | left: 0;
47 | width: 100%;
48 | height: 100%;
49 | background: var(--color-bg);
50 | }
51 |
52 | .js .loading::after {
53 | top: 50%;
54 | left: 50%;
55 | width: 60px;
56 | height: 60px;
57 | margin: -30px 0 0 -30px;
58 | border-radius: 50%;
59 | opacity: 0.4;
60 | background: var(--color-link);
61 | animation: loaderAnim 0.7s linear infinite alternate forwards;
62 |
63 | }
64 |
65 | @keyframes loaderAnim {
66 | to {
67 | opacity: 1;
68 | transform: scale3d(0.5,0.5,1);
69 | }
70 | }
71 |
72 | a {
73 | text-decoration: underline;
74 | color: var(--color-link);
75 | outline: none;
76 | }
77 |
78 | a:hover,
79 | a:focus {
80 | text-decoration: none;
81 | color: var(--color-link-hover);
82 | outline: none;
83 | }
84 |
85 | .frame {
86 | padding: 1.25rem;
87 | width: 100%;
88 | position: absolute;
89 | z-index: 1000;
90 | text-transform: uppercase;
91 | font-size: 0.85rem;
92 | }
93 |
94 | .frame__title {
95 | font-size: 0.85rem;
96 | font-weight: 600;
97 | margin: 0 0 0.25rem;
98 | }
99 |
100 | .frame__tagline {
101 | margin: 0.5rem 0;
102 | }
103 |
104 | .frame__links {
105 | margin-bottom: 1rem;
106 | }
107 |
108 | .frame__links a:not(:last-child) {
109 | margin-right: 1rem;
110 | }
111 |
112 | .frame__button {
113 | cursor: not-allowed;
114 | font-weight: 600;
115 | text-decoration: none;
116 | }
117 |
118 | .content {
119 | position: relative;
120 | }
121 |
122 | .js .content {
123 | overflow: hidden;
124 | height: 100vh;
125 | pointer-events: none;
126 | }
127 |
128 | .js .content--scroll {
129 | overflow: visible;
130 | height: auto;
131 | pointer-events: auto;
132 | }
133 |
134 | .inner {
135 | max-width: var(--content-width);
136 | padding: 0 var(--content-padding);
137 | margin: 0 auto;
138 | }
139 |
140 | .content__header {
141 | display: flex;
142 | flex-direction: column;
143 | padding-top: 10rem;
144 | }
145 |
146 | .content__meta {
147 | position: relative;
148 | font-size: 0.85rem;
149 | width: var(--image-width-end);
150 | }
151 |
152 | .content__meta-number {
153 | font-weight: bold;
154 | }
155 |
156 | .content__meta-title {
157 | position: absolute;
158 | top: 0;
159 | left: 0;
160 | text-transform: uppercase;
161 | transform: translateY(calc(var(--meta-offset) * 2 + var(--image-height-end))) translateY(-100%);
162 | }
163 |
164 | .js .content__meta-title {
165 | opacity: 0;
166 | }
167 |
168 | .content__title {
169 | letter-spacing: -0.15rem;
170 | margin: 1rem 0;
171 | line-height: 1;
172 | font-family: nocturne-serif, serif;
173 | font-weight: 600;
174 | font-size: clamp(1.5rem, 10vw, 8rem);
175 | }
176 |
177 | .js .anim-block-wrap {
178 | position: relative;
179 | overflow: hidden;
180 | }
181 |
182 | .js .anim-block {
183 | display: block;
184 | }
185 |
186 | .content__subtitle {
187 | font-family: nocturne-serif, serif;
188 | font-weight: 600;
189 | font-size: 1.25rem;
190 | margin: 0.5rem 0 3rem 0;
191 | line-height: 1.2;
192 | }
193 |
194 | .content__breakout {
195 | width: 100vw;
196 | left: 50%;
197 | right: 50%;
198 | margin: auto -50vw 0;
199 | position: relative;
200 | }
201 |
202 | .content__intro {
203 | height: var(--image-height-start);
204 | overflow: hidden;
205 | display: flex;
206 | align-items: center;
207 | pointer-events: auto;
208 | cursor: pointer;
209 | will-change: transform;
210 | }
211 |
212 | .content--scroll .content__intro {
213 | cursor: default;
214 | }
215 |
216 | .content__intro-imgWrap {
217 | will-change: transform;
218 | }
219 |
220 | .content__intro-img {
221 | width: 100vw;
222 | height: auto;
223 | position: relative;
224 | }
225 |
226 | .content__body {
227 | padding: 0 0 10rem 0;
228 | font-weight: 400;
229 | line-height: 1.6;
230 | }
231 |
232 | .js .content__body {
233 | opacity: 0;
234 | }
235 |
236 | .js .content--scroll .content__body {
237 | opacity: 1;
238 | }
239 |
240 | .content__body-para {
241 | margin: 4rem 0;
242 | padding: 0 1rem;
243 | }
244 |
245 | .anim-show {
246 | transform: translateY(100%);
247 | opacity: 0;
248 | transition: all .8s cubic-bezier(0.2,1,0.3,1);
249 | }
250 |
251 | .anim-show.is-inview {
252 | transform: translateY(0%);
253 | opacity: 1;
254 | }
255 |
256 | .cell--1-3 {
257 | grid-column: 1 / span 3;
258 | }
259 |
260 | .cell--2-3 {
261 | grid-column: 2 / span 2;
262 | }
263 |
264 | .cell--3 {
265 | grid-column: 3;
266 | }
267 |
268 | .cell--1-3 {
269 | grid-column: 1 / span 3;
270 | }
271 |
272 | .content__body-imgwrap {
273 | width: 100%;
274 | height: calc(((var(--content-width) - var(--content-padding)) * 828/1000) - 100px);
275 | overflow: hidden;
276 | position: relative;
277 | display: flex;
278 | align-items: center;
279 | justify-content: center;
280 | }
281 |
282 | .content__body-image {
283 | width: 100%;
284 | height: calc((var(--content-width) - var(--content-padding)) * 828/1000);
285 | background-size: cover;
286 | background-position: 50% 50%;
287 | }
288 |
289 | .content__footer {
290 | padding: 3rem 4rem 3.5rem;
291 | background: var(--color-footer-bg);
292 | }
293 |
294 | .inner--grid {
295 | text-align: left;
296 | }
297 |
298 | .content__footer-copyright {
299 | text-decoration: none;
300 | }
301 |
302 | .content__footer-next {
303 | display: block;
304 | font-family: nocturne-serif, serif;
305 | font-weight: 600;
306 | font-size: 3rem;
307 | cursor: not-allowed;
308 | }
309 |
310 | .cursor {
311 | display: none;
312 | }
313 |
314 | .thumbs {
315 | opacity: 0;
316 | pointer-events: none;
317 | position: fixed;
318 | top: 0;
319 | left: 0;
320 | width: 100%;
321 | height: 100vh;
322 | padding: 45px;
323 | display: flex;
324 | align-items: center;
325 | justify-content: space-between;
326 | perspective: 1000px;
327 | }
328 |
329 | .thumb {
330 | position: relative;
331 | display: flex;
332 | flex-direction: column;
333 | justify-content: center;
334 | font-size: 0.85rem;
335 | width: var(--image-width-end);
336 | will-change: transform;
337 | }
338 |
339 | .thumb__img {
340 | cursor: not-allowed;
341 | width: 100%;
342 | height: var(--image-height-end);
343 | background-size: cover;
344 | background-position: 50% 50%;
345 | }
346 |
347 | .thumb__number {
348 | position: absolute;
349 | font-weight: bold;
350 | bottom: 100%;
351 | transform: translateY(calc(-1 * var(--meta-offset) + 100%));
352 | }
353 |
354 | .thumb__title {
355 | position: absolute;
356 | top: 100%;
357 | left: 0;
358 | text-transform: uppercase;
359 | transform: translateY(calc(var(--meta-offset) - 100%));
360 | }
361 |
362 | /*! locomotive-scroll v3.5.4 | MIT License | https://github.com/locomotivemtl/locomotive-scroll */
363 | html.has-scroll-smooth {
364 | overflow: hidden;
365 | }
366 |
367 | html.has-scroll-dragging {
368 | -webkit-user-select: none;
369 | -moz-user-select: none;
370 | -ms-user-select: none;
371 | user-select: none;
372 | }
373 |
374 | .has-scroll-smooth body {
375 | overflow: hidden;
376 | }
377 |
378 | .has-scroll-smooth [data-scroll-container] {
379 | min-height: 100vh;
380 | }
381 |
382 | .c-scrollbar {
383 | position: absolute;
384 | right: 0;
385 | top: 0;
386 | width: 11px;
387 | height: 100vh;
388 | transform-origin: center right;
389 | transition: transform 0.3s, opacity 0.3s;
390 | opacity: 0;
391 | }
392 | .c-scrollbar:hover {
393 | transform: scaleX(1.45);
394 | }
395 | .c-scrollbar:hover, .has-scroll-scrolling .c-scrollbar, .has-scroll-dragging .c-scrollbar {
396 | opacity: 1;
397 | }
398 |
399 | .c-scrollbar_thumb {
400 | position: absolute;
401 | top: 0;
402 | right: 0;
403 | background-color: black;
404 | opacity: 0.5;
405 | width: 7px;
406 | border-radius: 10px;
407 | margin: 2px;
408 | cursor: -webkit-grab;
409 | cursor: grab;
410 | }
411 | .has-scroll-dragging .c-scrollbar_thumb {
412 | cursor: -webkit-grabbing;
413 | cursor: grabbing;
414 | }
415 |
416 | @media screen and (min-width: 53em) {
417 | body {
418 | --meta-offset: 100px;
419 | }
420 | .frame {
421 | padding: 1.25rem 3rem;
422 | text-align: left;
423 | display: grid;
424 | align-content: space-between;
425 | max-width: none;
426 | pointer-events: none;
427 | grid-template-columns: 1fr 1fr 8rem;
428 | grid-template-rows: auto;
429 | grid-template-areas: 'title links menu';
430 | }
431 | .frame__title-wrap {
432 | grid-area: title;
433 | }
434 | .frame__button {
435 | justify-self: end;
436 | grid-area: menu;
437 | }
438 | .frame__links {
439 | grid-area: links;
440 | margin: 0;
441 | justify-self: end;
442 | }
443 | .frame a {
444 | pointer-events: auto;
445 | }
446 | .content__body {
447 | display: grid;
448 | grid-template-columns: 50% 25% 25%;
449 | align-items: start;
450 | }
451 | .text-right {
452 | text-align: right;
453 | }
454 | .inner--grid {
455 | display: grid;
456 | grid-template-columns: 50% 50%;
457 | align-items: center;
458 | }
459 | .thumbs {
460 | opacity: 1;
461 | }
462 | .thumb__img {
463 | pointer-events: auto;
464 | }
465 | }
466 |
467 | @media screen and (max-height: 700px) {
468 | body {
469 | --meta-offset: 40px;
470 | }
471 | }
472 |
473 | @media (any-pointer: fine) {
474 | .cursor {
475 | position: fixed;
476 | top: 0;
477 | left: 0;
478 | display: block;
479 | pointer-events: none;
480 | mix-blend-mode: color-burn;
481 | }
482 | .cursor__inner {
483 | fill: var(--cursor-fill);
484 | stroke: var(--cursor-stroke);
485 | stroke-width: var(--cursor-stroke-width);
486 | opacity: 0.7;
487 | }
488 | .credits {
489 | padding-left: 25vw;
490 | }
491 | }
--------------------------------------------------------------------------------