Merge branch 'dev'

This commit is contained in:
2024-08-05 16:28:18 +02:00
12 changed files with 99 additions and 103 deletions

View File

@@ -9,24 +9,25 @@ import { quartOut } from './easings'
export const scaleFade = (node: HTMLElement, { export const scaleFade = (node: HTMLElement, {
scale = [0.7, 1], scale = [0.7, 1],
opacity = [1, 0], opacity = [1, 0],
x = null, x = [0, 0],
delay = 0, delay = 0,
duration = 1, duration = 0.75,
}): TransitionConfig => { }): TransitionConfig => {
return { const anim = animate(node, {
css: () => { scale,
animate(node, { opacity,
scale, x,
opacity, z: 0,
x, }, {
z: 0, easing: quartOut,
}, { duration,
easing: quartOut, delay,
duration, })
delay,
})
return null return {
duration: anim.duration * 1000,
tick: (t) => {
anim.currentTime = t
} }
} }
} }
@@ -42,19 +43,20 @@ export const revealSplit = (node: HTMLElement, {
duration = 1, duration = 1,
delay = 0, delay = 0,
}): TransitionConfig => { }): TransitionConfig => {
return { const anim = animate(node.querySelectorAll(children), {
css: () => { opacity,
animate(node.querySelectorAll(children), { y,
opacity, z: 0,
y, }, {
z: 0, easing: quartOut,
}, { duration,
easing: quartOut, delay: stagger(0.04, { start: delay }),
duration, })
delay: stagger(0.04, { start: delay }),
})
return null return {
duration: anim.duration * 1000,
tick: (t) => {
anim.currentTime = t
} }
} }
} }

View File

@@ -35,7 +35,7 @@
let scrollY = $state<number>() let scrollY = $state<number>()
let innerWidth = $state<number>() let innerWidth = $state<number>()
let innerHeight = $state<number>() let innerHeight = $state<number>()
let titleEl = $state<HTMLElement>() let titleEl: HTMLElement
// Check if title is larger than viewport to translate it // Check if title is larger than viewport to translate it
const isLarger = $derived<boolean>(titleEl && titleEl.offsetWidth >= innerWidth) const isLarger = $derived<boolean>(titleEl && titleEl.offsetWidth >= innerWidth)

View File

@@ -24,7 +24,7 @@
const { settings }: any = getContext('global') const { settings }: any = getContext('global')
let locationEl = $state<HTMLElement>() let locationEl: HTMLElement
let photoIndex = $state(0) let photoIndex = $state(0)
// Location date limit // Location date limit

View File

@@ -3,18 +3,18 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { scale } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
import { scaleFade } from '$animations/transitions' import { scaleFade } from '$animations/transitions'
// Components // Components
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
import { getAssetUrlKey } from '$utils/api' import { getAssetUrlKey } from '$utils/api'
let { let {
index,
text, text,
image, image,
video, video,
}: { }: {
index: number
text: string text: string
image?: any image?: any
video?: any video?: any
@@ -25,9 +25,8 @@
<div <div
class="step grid" class="step grid"
style:--index={index}
in:scaleFade|local={{ scale: [1.1, 1], opacity: [0, 1], x: [20, 0], delay: 0.2 }} in:scaleFade|local={{ scale: [1.1, 1], opacity: [0, 1], x: [20, 0], delay: 0.2 }}
out:scaleFade|local={{ scale: [1, 0.9], opacity: [1, 0], x: [0, -20] }} out:scale={{ start: 0.9, duration: 750, easing: quartOut }}
> >
{#if image || video} {#if image || video}
<div class="media"> <div class="media">

View File

@@ -12,7 +12,7 @@
const { settings: { switcher_links } }: any = getContext('global') const { settings: { switcher_links } }: any = getContext('global')
let switcherEl = $state<HTMLElement>() let switcherEl: HTMLElement
let isOpen = $state(false) let isOpen = $state(false)

View File

@@ -33,8 +33,8 @@
} = $props() } = $props()
let innerWidth = $state<number>() let innerWidth = $state<number>()
let globeParentEl = $state<HTMLElement>() let globeParentEl: HTMLElement
let globeEl = $state<HTMLElement>() let globeEl: HTMLElement
let globe = $state<any>() let globe = $state<any>()
let observer: IntersectionObserver let observer: IntersectionObserver
let animation = $state<number>() let animation = $state<number>()

View File

@@ -21,8 +21,8 @@
let innerWidth = $state<number>() let innerWidth = $state<number>()
let navObserver: IntersectionObserver let navObserver: IntersectionObserver
let introEl = $state<HTMLElement>() let introEl: HTMLElement
let navChooseEl = $state<HTMLElement>() let navChooseEl: HTMLElement
let scrolledPastIntro = $state(false) let scrolledPastIntro = $state(false)

View File

@@ -5,7 +5,7 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_LIST_INCREMENT } from '$env/static/public' import { PUBLIC_LIST_INCREMENT } from '$env/static/public'
import { page, navigating } from '$app/stores' import { page, navigating } from '$app/stores'
import { stagger, timeline } from 'motion' import { scroll, stagger, timeline } from 'motion'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { quartOut } from '$animations/easings' import { quartOut } from '$animations/easings'
@@ -13,6 +13,7 @@
import { DELAY } from '$utils/constants' import { DELAY } from '$utils/constants'
import { seenLocations } from '$utils/stores' import { seenLocations } from '$utils/stores'
import { photoFields } from '$utils/api' import { photoFields } from '$utils/api'
import { lerp } from 'utils/math'
import { padZero } from 'utils/string' import { padZero } from 'utils/string'
// Components // Components
import Metas from '$components/Metas.svelte' import Metas from '$components/Metas.svelte'
@@ -34,14 +35,13 @@
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
let introEl = $state<HTMLElement>() let introEl: HTMLElement
let photosListEl = $state<HTMLElement>() let photosListEl = $state<HTMLElement>()
let scrollY = $state<number>()
let observerPhotos: IntersectionObserver let observerPhotos: IntersectionObserver
let mutationPhotos: MutationObserver let mutationPhotos: MutationObserver
let currentPage = $state(1) let currentPage = $state(1)
let currentPhotosAmount = $derived(photos.length) let currentPhotosAmount = $derived(photos.length)
let heroOffsetY = $state(0) let heroParallax = $state(0)
const ended = $derived(currentPhotosAmount === totalPhotos) const ended = $derived(currentPhotosAmount === totalPhotos)
const latestPhoto = $derived(photos[0]) const latestPhoto = $derived(photos[0])
@@ -50,23 +50,7 @@
/** /**
* Load photos * Load photos
*/ */
/** Load more photos from CTA */ /** Load photos helper */
const loadMorePhotos = async () => {
// Append more photos from API
const newPhotos: any = await loadPhotos(currentPage + 1)
if (newPhotos) {
photos = [...photos, ...newPhotos]
// Define actions if the number of new photos is the expected ones
if (newPhotos.length === Number(PUBLIC_LIST_INCREMENT)) {
// Increment the current page
currentPage++
}
}
}
// Load photos helper
const loadPhotos = async (page?: number) => { const loadPhotos = async (page?: number) => {
const res = await fetch('/api/data', { const res = await fetch('/api/data', {
method: 'POST', method: 'POST',
@@ -94,15 +78,21 @@
} }
} }
/** Load more photos from CTA */
const loadMorePhotos = async () => {
// Append more photos from API
const newPhotos: any[] = await loadPhotos(currentPage + 1)
/** if (newPhotos) {
* Add parallax on illustration when scrolling photos = [...photos, ...newPhotos]
*/
$effect(() => { // Define actions if the number of new photos is the expected ones
if (scrollY && scrollY < introEl.offsetHeight) { if (newPhotos.length === Number(PUBLIC_LIST_INCREMENT)) {
heroOffsetY = scrollY * 0.1 // Increment the current page
currentPage++
}
} }
}) }
$effect(() => { $effect(() => {
@@ -147,6 +137,15 @@
} }
/**
* Add parallax on illustration when scrolling
*/
scroll(({ y }) => heroParallax = lerp(-15, 10, y.progress), {
target: introEl,
offset: ['start end', 'end start'],
})
/** /**
* Animations * Animations
*/ */
@@ -205,8 +204,6 @@
}) })
</script> </script>
<svelte:window bind:scrollY />
<Metas <Metas
title="Houses Of {location.name}" title="Houses Of {location.name}"
description="Discover {totalPhotos} beautiful homes from {location.name}, {location.country.name}" description="Discover {totalPhotos} beautiful homes from {location.name}, {location.country.name}"
@@ -215,7 +212,7 @@
<main class="location-page"> <main class="location-page">
<section class="location-page__intro grid" bind:this={introEl}> <section bind:this={introEl} class="location-page__intro grid">
<h1 class="title" class:is-short={location.name.length <= 4}> <h1 class="title" class:is-short={location.name.length <= 4}>
<span class="housesof mask"> <span class="housesof mask">
<strong class="word">Houses</strong> <strong class="word">Houses</strong>
@@ -269,7 +266,7 @@
</div> </div>
{#if location?.hero?.id} {#if location?.hero?.id}
<picture class="location-page__hero" style:--parallax-y="{heroOffsetY}px"> <picture class="location-page__hero" style:--parallax="{heroParallax}%">
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.hero.id, 'hero-large')}> <source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.hero.id, 'hero-large')}>
<source media="(min-width: 768px)" srcset={getAssetUrlKey(location.hero.id, 'hero-small')}> <source media="(min-width: 768px)" srcset={getAssetUrlKey(location.hero.id, 'hero-small')}>
<img <img

View File

@@ -6,11 +6,11 @@
import { navigating } from '$app/stores' import { navigating } from '$app/stores'
import { quartOut as quartOutSvelte } from 'svelte/easing' import { quartOut as quartOutSvelte } from 'svelte/easing'
import { fade, fly } from 'svelte/transition' import { fade, fly } from 'svelte/transition'
import { animate, inView, stagger, timeline } from 'motion' import { animate, inView, scroll, stagger, timeline } from 'motion'
import { mailtoClipboard } from '$utils/functions' import { mailtoClipboard } from '$utils/functions'
import { getAssetUrlKey } from '$utils/api' import { getAssetUrlKey } from '$utils/api'
import { DELAY } from '$utils/constants' import { DELAY } from '$utils/constants'
import { map } from 'utils/math' import { lerp } from 'utils/math'
import { sendEvent } from '$utils/analytics' import { sendEvent } from '$utils/analytics'
import { quartOut } from '$animations/easings' import { quartOut } from '$animations/easings'
// Components // Components
@@ -23,16 +23,14 @@
let { data } = $props() let { data } = $props()
let scrollY = $state<number>()
let innerWidth = $state<number>() let innerWidth = $state<number>()
let innerHeight = $state<number>() let photosGridEl: HTMLElement
let photosGridEl = $state<HTMLElement>() let photosGridParallax = $state<number>()
let photosGridOffset = $state<number>() let currentStepIndex = $state(0)
let currentStep = $state(0) const currentStep = $derived(data.about.process_steps[currentStepIndex])
let emailCopied = $state<string>() let emailCopied = $state<string>()
let emailCopiedTimeout: ReturnType<typeof setTimeout> | number let emailCopiedTimeout: ReturnType<typeof setTimeout> | number
const parallaxPhotos = $derived(photosGridEl && map(scrollY, photosGridOffset - innerHeight, photosGridOffset + innerHeight / 1.5, 0, innerHeight * 0.15, true))
const fadedPhotosIndexes = $derived( const fadedPhotosIndexes = $derived(
innerWidth > 768 innerWidth > 768
? [0, 2, 5, 7, 9, 12, 17, 20, 22, 26, 30, 32, 34] ? [0, 2, 5, 7, 9, 12, 17, 20, 22, 26, 30, 32, 34]
@@ -41,9 +39,11 @@
$effect(() => { $effect(() => {
// Update photos grid top offset // Add parallax to photos grid
photosGridOffset = photosGridEl.offsetTop scroll(({ y }) => photosGridParallax = lerp(-15, 10, y.progress), {
target: photosGridEl,
offset: ['start end', 'end start']
})
/** /**
* Animations * Animations
@@ -171,10 +171,10 @@
}) })
</script> </script>
<svelte:window bind:scrollY bind:innerWidth bind:innerHeight /> <svelte:window bind:innerWidth />
<Metas <Metas
title={data.data.about.seo_title} title={data.about.seo_title}
description={data.about.seo_description} description={data.about.seo_description}
image={data.about.seo_image ? getAssetUrlKey(data.about.seo_image.id, 'share-image') : null} image={data.about.seo_image ? getAssetUrlKey(data.about.seo_image.id, 'share-image') : null}
/> />
@@ -313,13 +313,13 @@
<ol> <ol>
{#each data.about.process_steps as { title }, index} {#each data.about.process_steps as { title }, index}
<li class:is-active={index === currentStep}> <li class:is-active={index === currentStepIndex}>
<a <a
class="title-big" class="title-big"
href="#step-{index + 1}" href="#step-{index + 1}"
onclick={(event) => { onclick={(event) => {
event.preventDefault() event.preventDefault()
currentStep = index currentStepIndex = index
sendEvent('aboutStepSwitch') sendEvent('aboutStepSwitch')
}} }}
> >
@@ -331,25 +331,23 @@
</aside> </aside>
<div class="steps"> <div class="steps">
{#each data.about.process_steps as { text, image, video_mp4, video_webm }, index} {#key currentStep}
{#if index === currentStep} <ProcessStep
<ProcessStep text={currentStep.text}
{index} {text} image={currentStep.image}
image={image ?? undefined} video={{
video={{ mp4: currentStep.video_mp4?.id,
mp4: video_mp4?.id, webm: currentStep.video_webm?.id
webm: video_webm?.id }}
}} />
/> {/key}
{/if}
{/each}
</div> </div>
</div> </div>
</section> </section>
<section class="about__photos" bind:this={photosGridEl}> <section class="about__photos" bind:this={photosGridEl}>
<div class="container-wide"> <div class="container-wide">
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px"> <div class="photos-grid" style:--parallax="{photosGridParallax}%">
{#each data.photos as { image: { id }, title }, index} {#each data.photos as { image: { id }, title }, index}
<AboutGridPhoto <AboutGridPhoto
class="grid-photo" class="grid-photo"
@@ -379,7 +377,7 @@
in:fly={{ y: 4, duration: 325, easing: quartOutSvelte, delay: 250 }} in:fly={{ y: 4, duration: 325, easing: quartOutSvelte, delay: 250 }}
out:fade={{ duration: 250, easing: quartOutSvelte }} out:fade={{ duration: 250, easing: quartOutSvelte }}
use:mailtoClipboard use:mailtoClipboard
oncopied={(email: string) => { oncopied={({ detail: email }: CustomEvent<string>) => {
emailCopied = email emailCopied = email
// Clear timeout and add timeout to hide message // Clear timeout and add timeout to hide message
clearTimeout(emailCopiedTimeout) clearTimeout(emailCopiedTimeout)

View File

@@ -492,7 +492,7 @@
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
gap: 1.75vw; gap: 1.75vw;
margin: -5% -5% 0; margin: -5% -5% 0;
transform: rotate(-6deg) translateY(var(--parallax-y)) translateZ(0); transform: rotate(-6deg) translateY(var(--parallax)) translateZ(0);
transition: transform 0.8s var(--ease-quart); transition: transform 0.8s var(--ease-quart);
@include bp (sm) { @include bp (sm) {

View File

@@ -173,8 +173,8 @@
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
filter: grayscale(25%); filter: grayscale(25%);
transform: translate3d(0, var(--parallax-y), 0); transform: translateY(var(--parallax)) translateZ(0);
transition: transform 0.7s var(--ease-quart); transition: transform 0.5s var(--ease-quart);
will-change: transform, opacity; will-change: transform, opacity;
} }
} }

View File

@@ -14,7 +14,7 @@ export const mailtoClipboard = (node: HTMLElement) => {
navigator.clipboard.writeText(emailAddress) navigator.clipboard.writeText(emailAddress)
// Send event // Send event
node.dispatchEvent(new CustomEvent('copied', emailAddress)) node.dispatchEvent(new CustomEvent('copied', { detail: emailAddress }))
// Record event in analytics // Record event in analytics
sendEvent('emailCopy') sendEvent('emailCopy')