this repo has no description
at main 202 lines 6.7 kB view raw
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>