floter.design/src/routes/work/[slug]/+page.svelte
2023-10-12 23:45:07 +02:00

320 lines
No EOL
7.2 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger';
import ScrollToPlugin from 'gsap/dist/ScrollToPlugin';
import { CldImage, CldVideoPlayer } from 'svelte-cloudinary';
export let data;
gsap.registerPlugin(ScrollTrigger);
let visible = false;
onMount(() => {
visible = true;
if (document.querySelector('.workclone')) {
document.querySelector('.workclone')?.remove();
}
gsap.to('.logo-wrapper', {
opacity: 0,
zIndex: -1,
duration: 1,
ease: 'power2.out',
})
gsap.from('.work', {
y: '100vh',
duration: 1,
ease: 'power4.out',
})
gsap.from('.gallery-wrapper', {
y: '-100vh',
duration: 1,
ease: 'power4.out',
})
const gallery = document.querySelector('.gallery') as HTMLElement;
let startX: number;
let scrollLeft: number;
let isDown = false;
if (gallery) {
gallery.addEventListener('mousedown', (e) => {
isDown = true;
startX = e.pageX - gallery.offsetLeft;
scrollLeft = gallery.scrollLeft;
});
gallery.addEventListener('mouseleave', () => {
isDown = false;
});
gallery.addEventListener('mouseup', () => {
isDown = false;
});
gallery.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - gallery.offsetLeft;
const walk = (x - startX) * 4; //scroll-fast
gallery.scrollLeft = scrollLeft - walk;
});
}
return () => {
gsap.killTweensOf('.logo-wrapper, .work, .gallery-wrapper');
}
})
</script>
<div class="logo-wrapper">
<div class="svg-logo">{@html data.svg}</div>
</div>
<div class="subnav">
<a href="/work" class="subnav-item button">← Back</a>
</div>
<div class="gallery-wrapper">
<div class="gallery">
{#if visible}
{#each data.images as image}
<figure>
{#if image.includes('/video/')}
<video src={image} width="1400" height="840" autoplay muted loop></video>
{:else}
<CldImage
src={image}
alt="{data.title}"
sizes="(min-width: 768px) 67vw, 90vw"
width={1400}
height={840}
placeholder="blur"
loading="eager"
objectFit="cover"
/>
{/if}
</figure>
{/each}
{/if}
</div>
</div>
<article class="work">
{#if visible}
<h1>{data.title}</h1>
<div class="description">
{data.description}
</div>
<div class="work-content">
<div class="infobox">
<div class="tasks">
<div class="tasks-title">What I did:</div>
<ul>
{#each data.tags as tag }
<li>{tag}</li>
{ /each }
</ul>
</div>
<div class="reference">
<div class="reference-title">Reference:</div>
<div><a href={data.reference}>{data.referenceName}</a></div>
</div>
{#if data.agency}
<div class="agency">
<div class="agency-title">Agency:</div>
<div><a href={data.agency}>{data.agencyName}</a></div>
</div>
{/if}
</div>
<div class="work-content-text">
{@html data.Content.html}
</div>
</div>
{/if}
</article>
<style lang="scss">
.subnav {
@media screen and (min-width: 768px) {
position: fixed;
top: 0;
left: 0;
display: inline-block;
z-index: 22;
}
}
.subnav-item {
margin: var(--spacing-nav);
}
.logo-wrapper {
position: fixed;
text-align: center;
width: 100vw;
height: 100vh;
z-index: 1;
}
.svg-logo {
color: var(--color-highlight);
padding: 3em;
width: 100%;
height: 100%;
margin: auto;
display: flex;
flex-direction: column;
justify-content: center;
@media screen and (min-width: 768px) {
width: 60%;
}
}
:global(.svg-logo svg) {
object-fit: fill;
width: 100%;
height: 100%;
}
.work {
overflow: hidden;
padding: 0 var(--spacing-outer) 100px var(--spacing-outer);
@media screen and (min-width: 768px) {
margin: 0 var(--spacing-outer) 0 150px;
padding: 0 var(--spacing-outer) 100px var(--spacing-outer);
width: calc(100% - 150px - var(--spacing-outer));
}
}
h1 {
margin: .5em 0 0.25em 0;
font-size: 2.5rem;
@media screen and (min-width: 768px) {
font-size: 5rem;
}
}
.description {
margin-bottom: 1.5em;
line-height: 1.3;
letter-spacing: -0.0075em;
font-size: 1.25rem;
@media screen and (min-width: 768px) {
font-size: 2.5rem;
}
}
.infobox {
font-size: 1.25rem;
padding: 0 0 2em 0;
@media screen and (min-width: 768px) {
flex: 0 0 25%;
}
}
.reference-title, .agency-title, .tasks-title {
font-weight: bold;
font-style: italic;
margin: 0 0 .125em 0;
}
.reference-title, .agency-title {
margin-top: 1em;
margin-bottom: 0;
}
.tasks ul {
margin: 0;
display: flex;
flex-wrap: wrap;
gap: .125em;
}
.tasks li {
font-size: 1rem;
list-style: none;
border: none;
text-transform: uppercase;
border-radius: 3px;
margin: 0;
padding: 0.25em 0.33em;
background-color: var(--color-highlight);
color: var(--color-bg);
}
.work-content {
position: relative;
z-index: 2;
@media screen and (min-width: 768px) {
display: flex;
gap: 2em;
}
}
.work-content-text :global(h2) {
font-size: 2rem;
@media screen and (min-width: 768px) {
font-size: 2.5rem;
}
}
.work-content-text > :global(:first-child) {
margin-top: 0;
}
.work-content-text :global(p) {
font-size: 1rem;
@media screen and (min-width: 768px) {
font-size: 1em;
}
}
.work-content-text > :global(h1):first-child {
margin-top: 0;
}
.gallery-wrapper {
position: relative;
width: 100vw;
overflow: hidden;
margin-bottom: -80px;
top: -80px;
padding: 0 0 1em 0;
z-index: 1;
opacity: 0.75;
perspective: 250px;
perspective-origin: center bottom;
@media screen and (min-width: 768px) {
padding: 2em 0 4em 0;
margin-bottom: -120px;
perspective: 500px;
}
}
.gallery {
transform-origin: 50% 50%;
position: relative;
display: flex;
scroll-behavior: smooth;
gap: 1px;
overflow-x: scroll;
scroll-snap-type: x mandatory;
width: 115vw;
left: -4vw;
cursor: grab;
transform: rotate3d(-2, 0, 1, -10deg);
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
@media screen and (min-width: 768px) {
width: 110vw;
left: -3vw;
transform: rotate3d(-2, 0, 1, -10deg);
}
}
figure {
display: flex;
flex-direction: column;
overflow: hidden;
justify-content: center;
scroll-snap-align: start;
flex: 1 0 85%;
background-color: var(--color-highlight);
height: 100%;
aspect-ratio: 140/84;
width: auto;
margin: 0;
@media screen and (min-width: 768px) {
flex: 1 0 66.666%;
}
& :global(img), video {
width: 100%;
height: 100%;
object-position: left top;
}
video {
object-fit: cover;
}
}
</style>