this repo has no description
1<script lang="ts">
2 import type { Artwork as JetArtworkType } from '@jet-app/app-store/api/models';
3 import { intersectionObserver } from '@amp/web-app-components/src/actions/intersection-observer';
4 import { buildSrc } from '@amp/web-app-components/src/components/Artwork/utils/srcset';
5 import ResizeDetector from '@amp/web-app-components/src/components/helpers/ResizeDetector.svelte';
6 import { colorAsString } from '~/utils/color';
7
8 export let artwork: JetArtworkType;
9 export let active: boolean = false;
10
11 $: isBackgroundImageLoaded = false;
12 $: backgroundImage = artwork
13 ? buildSrc(
14 artwork.template,
15 {
16 crop: 'sr',
17 width: 400,
18 height: Math.floor(400 / 1.6667),
19 fileType: 'webp',
20 },
21 {},
22 )
23 : undefined;
24
25 $: if (backgroundImage) {
26 const img = new Image();
27 img.onload = () => (isBackgroundImageLoaded = true);
28 img.src = backgroundImage;
29 }
30
31 let resizing = false;
32 const handleResizeUpdate = (e: CustomEvent<{ isResizing: boolean }>) =>
33 (resizing = e.detail.isResizing);
34
35 let isOutOfView = true;
36 const handleIntersectionOberserverUpdate = (
37 isIntersectingViewport: boolean,
38 ) => (isOutOfView = !isIntersectingViewport);
39</script>
40
41{#if backgroundImage}
42 <ResizeDetector on:resizeUpdate={handleResizeUpdate} />
43
44 <div
45 class="container"
46 class:active
47 class:resizing
48 class:loaded={isBackgroundImageLoaded}
49 class:out-of-view={isOutOfView}
50 style:--background-image={`url(${backgroundImage})`}
51 style:--background-color={artwork.backgroundColor &&
52 colorAsString(artwork.backgroundColor)}
53 use:intersectionObserver={{
54 callback: handleIntersectionOberserverUpdate,
55 threshold: 0,
56 }}
57 >
58 <div class="overlay" />
59 </div>
60{/if}
61
62<style>
63 .container {
64 --veil: rgb(240, 240, 240, 0.65);
65 --speed: 0.66s;
66 --aspect-ratio: 16/9;
67 --scale: 1.2;
68 position: absolute;
69 top: 0;
70 left: 0;
71 width: 100%;
72 aspect-ratio: var(--aspect-ratio);
73 max-height: 900px;
74 opacity: 0;
75
76 /*
77 This stack of background images represents the following three layers, listed front-to-back:
78
79 1) A gradient from transparent to white that acts as a mask for the entire container.
80 `mask-image` caused too much thrashing and CPU usage when animating and resizing,
81 so we are mimicking its functionality with this top-layer background image.
82 2) A semi-transparent veil to evenly fade out the bg. Note that this is not technically
83 a gradient, but we are using `linear-gradient` because a regular `rgb` value can't be
84 used in `background-image`.
85 3) The joe color of the background image that will eventualy be loaded.
86 */
87 background-image: linear-gradient(
88 180deg,
89 rgba(255, 255, 255, 0) 50%,
90 var(--pageBg) 80%
91 ),
92 linear-gradient(0deg, var(--veil) 0%, var(--veil) 80%),
93 linear-gradient(
94 0deg,
95 var(--background-color) 0%,
96 var(--background-color) 80%
97 );
98 background-position: center;
99 background-size: 120%;
100
101 /*
102 Blurring via the CSS filter does not extend edge-to-edge of the contents width, but we
103 can mitigate that by ever-so-slightly bumping up the `scale` of content so it bleeds off
104 the page cleanly.
105 */
106 filter: blur(20px) saturate(1.3);
107 transform: scale(var(--scale));
108 transition: opacity calc(var(--speed) * 2) ease-out,
109 background-size var(--speed) ease-in;
110
111 @media (prefers-color-scheme: dark) {
112 --veil: rgba(0, 0, 0, 0.5);
113 }
114 }
115
116 .container.loaded {
117 /*
118 This stack of background images represents the following three layers, listed front-to-back:
119
120 1) A gradient from transparent to white that acts as a mask for the entire container.
121 `mask-image` caused too much thrashing and CPU usage when animating and resizing,
122 so we are mimicking its functionality with this top-layer background image.
123 2) A semi-transparent veil to evenly fade out the image. Note that this is not technically
124 a gradient, but we are using `linear-gradient` because a regular `rgb` value can't be
125 used in `background-image`.
126 3) The actual background image.
127 */
128 background-image: linear-gradient(
129 180deg,
130 rgba(255, 255, 255, 0) 50%,
131 var(--pageBg) 80%
132 ),
133 linear-gradient(0deg, var(--veil) 0%, var(--veil) 80%),
134 var(--background-image);
135 }
136
137 .container.active {
138 opacity: 1;
139 transition: opacity calc(var(--speed) / 2) ease-in;
140 background-size: 100%;
141 }
142
143 .overlay {
144 position: absolute;
145 z-index: 2;
146 top: 0;
147 left: 0;
148 width: 100%;
149 aspect-ratio: var(--aspect-ratio);
150 max-height: 900px;
151 opacity: 0;
152 background-image: var(--background-image);
153 background-position: 100% 100%;
154 background-size: 250%;
155 filter: brightness(1.3) saturate(0);
156 mix-blend-mode: overlay;
157 will-change: opacity, background-position;
158 animation: shift-background 60s infinite linear alternate;
159 animation-play-state: paused;
160 transition: opacity var(--speed) ease-in;
161 }
162
163 .active .overlay {
164 opacity: 0.3;
165 animation-play-state: running;
166 transition: opacity calc(var(--speed) * 2) ease-in
167 calc(var(--speed) * 2);
168 }
169
170 .active.out-of-view .overlay,
171 .active.resizing .overlay {
172 animation-play-state: paused;
173 opacity: 0;
174 }
175
176 @keyframes shift-background {
177 0% {
178 background-position: 0% 50%;
179 background-size: 250%;
180 }
181
182 25% {
183 background-position: 60% 20%;
184 background-size: 300%;
185 }
186
187 50% {
188 background-position: 100% 50%;
189 background-size: 320%;
190 }
191
192 75% {
193 background-position: 40% 100%;
194 background-size: 220%;
195 }
196
197 100% {
198 background-position: 20% 50%;
199 background-size: 300%;
200 }
201 }
202</style>