Merge branch 'dev'
This commit is contained in:
@@ -9,13 +9,11 @@ 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: () => {
|
|
||||||
animate(node, {
|
|
||||||
scale,
|
scale,
|
||||||
opacity,
|
opacity,
|
||||||
x,
|
x,
|
||||||
@@ -26,7 +24,10 @@ export const scaleFade = (node: HTMLElement, {
|
|||||||
delay,
|
delay,
|
||||||
})
|
})
|
||||||
|
|
||||||
return null
|
return {
|
||||||
|
duration: anim.duration * 1000,
|
||||||
|
tick: (t) => {
|
||||||
|
anim.currentTime = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,9 +43,7 @@ export const revealSplit = (node: HTMLElement, {
|
|||||||
duration = 1,
|
duration = 1,
|
||||||
delay = 0,
|
delay = 0,
|
||||||
}): TransitionConfig => {
|
}): TransitionConfig => {
|
||||||
return {
|
const anim = animate(node.querySelectorAll(children), {
|
||||||
css: () => {
|
|
||||||
animate(node.querySelectorAll(children), {
|
|
||||||
opacity,
|
opacity,
|
||||||
y,
|
y,
|
||||||
z: 0,
|
z: 0,
|
||||||
@@ -54,7 +53,10 @@ export const revealSplit = (node: HTMLElement, {
|
|||||||
delay: stagger(0.04, { start: delay }),
|
delay: stagger(0.04, { start: delay }),
|
||||||
})
|
})
|
||||||
|
|
||||||
return null
|
return {
|
||||||
|
duration: anim.duration * 1000,
|
||||||
|
tick: (t) => {
|
||||||
|
anim.currentTime = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{index} {text}
|
text={currentStep.text}
|
||||||
image={image ?? undefined}
|
image={currentStep.image}
|
||||||
video={{
|
video={{
|
||||||
mp4: video_mp4?.id,
|
mp4: currentStep.video_mp4?.id,
|
||||||
webm: video_webm?.id
|
webm: currentStep.video_webm?.id
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/key}
|
||||||
{/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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user