Files
housesof/src/routes/about/+page.svelte

349 lines
12 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<style lang="scss">
@import "../../style/pages/about";
</style>
<script lang="ts">
import { navigating, page } from '$app/stores'
import { onMount, afterUpdate } from 'svelte'
import type { PageData } from './$types'
import { scroll, animate, inView, timeline } from 'motion'
import { map } from '$utils/functions'
import { getAssetUrlKey } from '$utils/api'
import { DELAY } from '$utils/contants'
import { quartOut } from '$animations/easings'
// Components
import Metas from '$components/Metas.svelte'
import PageTransition from '$components/PageTransition.svelte'
import Image from '$components/atoms/Image.svelte'
import Button from '$components/atoms/Button.svelte'
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
import Heading from '$components/molecules/Heading.svelte'
import ProcessStep from '$components/molecules/ProcessStep.svelte'
import InteractiveGlobe2 from '$components/organisms/InteractiveGlobe2.svelte'
import ShopModule from '$components/organisms/ShopModule.svelte'
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
export let data: PageData
const { about, photos } = data
let scrollY: number, innerWidth: number, innerHeight: number
let purposeEl: HTMLElement, photosGridEl: HTMLElement
let currentStep: number = 0
let photoFirstEl: HTMLElement, photoUsEl: HTMLElement
let photosGridOffset: number = photosGridEl && photosGridEl.offsetTop
$: currentStep = $page.url.hash ? Number($page.url.hash.split('#step-')[1]) - 1 : 0
$: parallaxPhotos = photosGridEl && map(scrollY, photosGridOffset - innerHeight, photosGridOffset + innerHeight / 1.5, 0, innerHeight * 0.15, true)
$: fadedPhotosIndexes = innerWidth > 768
? [0, 2, 5, 7, 9, 12, 17, 20, 22, 26, 30, 32, 34]
: [1, 4, 5, 7, 11, 14, 17, 20, 24, 27, 30, 33, 34, 36, 40, 43]
const introText = about.intro_text
.replace('<strong>',
`<a href="/${about.intro_firstlocation.country.slug}/${about.intro_firstlocation.slug}" data-sveltekit-noscroll data-sveltekit-prefetch>
<img src="${getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')}" width="32" height="32" alt="${about.intro_firstlocation.country.flag.title}">
<strong>
`)
.replace('</strong>', '</strong></a>')
onMount(() => {
/**
* Animations
*/
const animation = timeline([
// Heading
['.heading .text', {
y: [24, 0],
opacity: [0, 1],
z: 0,
}, {
at: 0.5,
}],
// First photo
[photoFirstEl, {
y: ['10%', 0],
rotate: [0, getComputedStyle(photoFirstEl).getPropertyValue('--rotate')],
opacity: [0, 1],
z: 0,
}, {
at: 0.75,
opacity: {
duration: 1
}
}],
// Portrait photo
[photoUsEl, {
y: ['10%', 0],
x: [0, '5%'],
rotate: [0, 5],
opacity: [0, 1],
z: 0,
}, {
at: 1,
opacity: {
duration: 1
}
}],
// Text
['.about__introduction .text', {
y: [32, 0],
opacity: [0, 1],
z: 0,
}, {
at: 1.2,
}],
], {
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
defaultOptions: {
duration: 1.6,
easing: quartOut,
},
})
animation.stop()
// Run animation
requestAnimationFrame(animation.play)
/**
* Intro parallax
*/
// First photo
scroll(animate(photoFirstEl.querySelector('figure'), {
y: ['-3%', '6%'],
x: [0, '-4%'],
rotate: [-1.5, 0],
z: 0,
}), {
target: photoFirstEl,
offset: ["-200%", "150%"]
})
// Portrait photo
scroll(animate(photoUsEl.querySelector('figure'), {
y: [0, '-6%'],
x: [0, '-3%'],
rotate: [-2, 0],
z: 0,
}), {
target: photoUsEl,
offset: ["-250%", "150%"]
})
/**
* Purpose reveal
*/
inView(purposeEl, ({ target, isIntersecting }) => {
target.classList.toggle('is-visible', isIntersecting)
}, { amount: 0.6 })
// Parallax
scroll(animate(purposeEl.querySelector('picture img'), {
y: [0, '40%'],
}))
/**
* Steps scroll animation
*/
const cards = stepsEl.querySelectorAll('.step')
const cardsAmount = about.process_steps.length
cards.forEach((card: HTMLElement, i: number) => {
const index = i + 1
const reverseIndex = cardsAmount - index
const overlay = card.querySelector('.overlay')
const scrollOptions: ScrollOptions = {
target: stepsEl,
offset: [`${i / cardsAmount * 100}%`, `${index / cardsAmount * 100}%`],
}
// Card scale
scroll(animate(card, {
scale: [1, 1 - (0.02 * reverseIndex)]
}), scrollOptions)
// Overlay opacity
scroll(animate(overlay, {
opacity: [0, 0.2 + (0.05 * reverseIndex)]
}), scrollOptions)
})
/**
* Play videos only when visible
*/
const videos = stepsEl.querySelectorAll('video')
if (videos) {
videosObserver = new IntersectionObserver(entries => {
entries.forEach(({ target, isIntersecting }) => {
// @ts-ignore
isIntersecting ? target.play() : target.pause()
})
}, {
threshold: 0,
})
videos.forEach(video => videosObserver.observe(video))
}
// Destroy
return () => {
videosObserver.disconnect()
}
})
afterUpdate(() => {
// Update photos grid top offset
photosGridOffset = photosGridEl.offsetTop
})
</script>
<svelte:window bind:scrollY bind:innerWidth bind:innerHeight />
<Metas
title="About the project Houses Of"
description={about.description}
image=""
/>
<PageTransition name="about">
<Heading
text={about.description}
/>
<section class="about__introduction">
<div class="container grid">
<div class="photo-first" bind:this={photoFirstEl}>
<figure>
<Image
class="shadow-box-dark"
id={about.intro_firstphoto.id}
alt={about.intro_firstphoto.title}
sizeKey="photo-list"
sizes={{
small: { width: 400 },
medium: { width: 600 },
large: { width: 800 },
}}
ratio={1.5}
/>
<figcaption class="text-info">{about.intro_firstphoto_caption}</figcaption>
</figure>
</div>
<div class="photo-us" bind:this={photoUsEl}>
<figure>
<Image
class="shadow-box-dark"
id={about.intro_portraits.id}
alt={about.intro_portraits.title}
sizeKey="square"
sizes={{
small: { width: 250 }
}}
ratio={1}
/>
</figure>
</div>
<div class="text text-normal">
{@html introText}
</div>
</div>
</section>
<section class="about__purpose grid" bind:this={purposeEl}>
<div class="container">
<div class="text title-xl">
{@html about.purpose_text}
</div>
<div class="background">
<picture class="background__illustration">
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(about.intro_firstlocation.illustration_desktop_2x.id, 'illustration-desktop-2x')}>
<source media="(min-width: 768px)" srcset={getAssetUrlKey(about.intro_firstlocation.illustration_desktop.id, 'illustration-desktop-1x')}>
<img
src={getAssetUrlKey(about.intro_firstlocation.illustration_mobile.id, 'illustration-mobile')}
width={320}
height={824}
alt="Illustration for {about.intro_firstlocation.name}"
decoding="async"
/>
</picture>
</div>
</div>
</section>
<section class="about__process">
<div class="container grid">
<aside>
<div class="heading">
<h2 class="title-medium">{about.process_title}</h2>
<p class="text-normal">{about.process_subtitle}</p>
</div>
<ol>
{#each about.process_steps as { title }, index}
<li class:is-active={index === currentStep}>
<a href="#step-{index + 1}" class="title-big">
<span>{title}</span>
</a>
</li>
{/each}
</ol>
</aside>
<div class="steps">
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
<ProcessStep
{index} {text}
image={image ?? undefined}
video={video_mp4 && video_webm ? {
mp4: video_mp4.id,
webm: video_webm.id
} : undefined}
visible={index === currentStep}
/>
{/each}
</div>
</div>
</section>
<section class="about__photos" bind:this={photosGridEl}>
<div class="container-wide">
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px">
{#each photos as { image: { id }, title }, index}
<AboutGridPhoto class="grid-photo"
{id}
alt={title}
disabled={fadedPhotosIndexes.includes(index)}
/>
{/each}
</div>
</div>
</section>
<section class="about__interest grid">
<div class="container grid">
<h2 class="title-xl">{about.contact_title}</h2>
<div class="blocks">
{#each about.contact_blocks as { title, text, link, button }}
<div class="block">
<h3 class="text-label">{title}</h3>
<p class="text-normal">{text}</p>
{#if link}
<Button size="small" url={link} text={button} />
{/if}
</div>
{/each}
</div>
</div>
</section>
</PageTransition>