refactor: migrate to Svelte 5
use runes ($props, $state, $derived, $effect, etc)
This commit is contained in:
@@ -45,7 +45,7 @@
|
|||||||
"postcss-preset-env": "^9.6.0",
|
"postcss-preset-env": "^9.6.0",
|
||||||
"postcss-sort-media-queries": "^5.2.0",
|
"postcss-sort-media-queries": "^5.2.0",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"svelte": "^4.2.18",
|
"svelte": "^5.0.0-next.205",
|
||||||
"svelte-check": "^3.8.5",
|
"svelte-check": "^3.8.5",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let domain: string
|
let {
|
||||||
export let enabled = !import.meta.env.DEV
|
domain,
|
||||||
|
enabled = !import.meta.env.DEV,
|
||||||
|
}: {
|
||||||
|
domain: string
|
||||||
|
enabled?: boolean
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -4,13 +4,23 @@
|
|||||||
|
|
||||||
const { settings }: any = getContext('global')
|
const { settings }: any = getContext('global')
|
||||||
|
|
||||||
export let title: string
|
let {
|
||||||
export let description: string = undefined
|
title,
|
||||||
export let image: string = getAssetUrlKey(settings.seo_image.id, 'share-image')
|
description,
|
||||||
export let url: string = undefined
|
image = getAssetUrlKey(settings.seo_image.id, 'share-image'),
|
||||||
export let type = 'website'
|
url,
|
||||||
export let card = 'summary_large_image'
|
type = 'website',
|
||||||
export let creator: string = undefined
|
card = 'summary_large_image',
|
||||||
|
creator,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
url?: string
|
||||||
|
type?: string
|
||||||
|
card?: string
|
||||||
|
creator?: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -20,16 +30,16 @@
|
|||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title} />
|
||||||
<meta name="twitter:title" content={title} />
|
<meta name="twitter:title" content={title} />
|
||||||
{#if description}
|
{#if description}
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
{/if}
|
{/if}
|
||||||
<meta property="og:type" content={type} />
|
<meta property="og:type" content={type} />
|
||||||
{#if image}
|
{#if image}
|
||||||
<meta property="og:image" content={image} />
|
<meta property="og:image" content={image} />
|
||||||
<meta name="twitter:image" content={image} />
|
<meta name="twitter:image" content={image} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if url}
|
{#if url}
|
||||||
<meta property="og:url" content={url} />
|
<meta property="og:url" content={url} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<meta property="twitter:card" content={card} />
|
<meta property="twitter:card" content={card} />
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import Lenis from 'lenis'
|
import Lenis from 'lenis'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
|
|
||||||
let smoothScrollRAF = 0
|
let smoothScrollRAF = $state(0)
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
// Setup smooth scroll
|
// Setup smooth scroll
|
||||||
$smoothScroll = new Lenis({
|
$smoothScroll = new Lenis({
|
||||||
duration: 1.2,
|
duration: 1.2,
|
||||||
|
|||||||
@@ -2,16 +2,23 @@
|
|||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
import { splitText } from 'utils/text'
|
import { splitText } from 'utils/text'
|
||||||
|
|
||||||
export let text: string
|
let {
|
||||||
export let mode: string = undefined
|
text,
|
||||||
export let clone = false
|
mode,
|
||||||
|
clone = false,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
text: string
|
||||||
|
mode?: string
|
||||||
|
clone?: boolean
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
$: split = splitText(text, mode)
|
const split = $derived(splitText(text, mode))
|
||||||
|
const classes = $derived(cx(
|
||||||
$: classes = cx(
|
|
||||||
'text-split',
|
'text-split',
|
||||||
$$props.class,
|
props.class,
|
||||||
)
|
))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if clone}
|
{#if clone}
|
||||||
|
|||||||
@@ -2,20 +2,31 @@
|
|||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
import Image from './Image.svelte'
|
import Image from './Image.svelte'
|
||||||
|
|
||||||
export let id: string
|
let {
|
||||||
export let alt: string
|
id,
|
||||||
export let disabled = false
|
alt,
|
||||||
|
disabled = false,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
|
alt: string
|
||||||
|
disabled?: boolean
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let hovering = false
|
let hovering = $state(false)
|
||||||
let timer: ReturnType<typeof setTimeout> | number = null
|
let timer: ReturnType<typeof setTimeout>
|
||||||
|
|
||||||
$: classes = cx(
|
const classes = $derived(cx(
|
||||||
hovering ? 'is-hovered' : undefined,
|
hovering ? 'is-hovered' : undefined,
|
||||||
disabled ? 'is-disabled' : undefined,
|
disabled ? 'is-disabled' : undefined,
|
||||||
$$props.class
|
props.class,
|
||||||
)
|
))
|
||||||
|
|
||||||
// Hovering functions
|
|
||||||
|
/**
|
||||||
|
* Hovering functions
|
||||||
|
*/
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
hovering = true
|
hovering = true
|
||||||
@@ -26,9 +37,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<figure class={classes}
|
<figure
|
||||||
on:mouseenter={handleMouseEnter}
|
class={classes}
|
||||||
on:mouseleave={handleMouseLeave}
|
onmouseenter={handleMouseEnter}
|
||||||
|
onmouseleave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
{id}
|
{id}
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let text: string
|
let {
|
||||||
export let size = 'small'
|
text,
|
||||||
|
size = 'small',
|
||||||
|
}: {
|
||||||
|
text: string
|
||||||
|
size?: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="badge badge--{size}">
|
<div class="badge badge--{size}">
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
color: $color-secondary-light;
|
color: $color-secondary-light;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|||||||
@@ -5,10 +5,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$components/atoms/Icon.svelte'
|
import Icon from '$components/atoms/Icon.svelte'
|
||||||
|
|
||||||
export let icon: string
|
let {
|
||||||
export let alt: string
|
icon,
|
||||||
export let label: string
|
alt,
|
||||||
export let url: string
|
label,
|
||||||
|
url,
|
||||||
|
}: {
|
||||||
|
icon: string
|
||||||
|
alt: string
|
||||||
|
label: string
|
||||||
|
url: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href={url} class="box-cta">
|
<a href={url} class="box-cta">
|
||||||
|
|||||||
@@ -6,56 +6,71 @@
|
|||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
import SplitText from '$components/SplitText.svelte'
|
import SplitText from '$components/SplitText.svelte'
|
||||||
|
|
||||||
export let text: string
|
let {
|
||||||
export let url: string = undefined
|
text,
|
||||||
export let color: string = undefined
|
url,
|
||||||
export let size: 'xsmall' | 'small' | 'medium' | 'large'
|
color,
|
||||||
export let effect = 'link-3d'
|
size,
|
||||||
export let disabled: boolean = undefined
|
effect = 'link-3d',
|
||||||
export let slotPosition = 'before'
|
disabled,
|
||||||
|
slotPosition = 'before',
|
||||||
|
onclick,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
text: string
|
||||||
|
url?: string
|
||||||
|
color?: string
|
||||||
|
size: 'xsmall' | 'small' | 'medium' | 'large'
|
||||||
|
effect?: string
|
||||||
|
disabled?: boolean
|
||||||
|
slotPosition?: 'before' | 'after'
|
||||||
|
onclick?: any
|
||||||
|
children?: any
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let tag: 'a' | 'button'
|
const tag = $derived(url ? 'a' : 'button')
|
||||||
$: tag = url ? 'a' : 'button'
|
const classes = $derived(cx(
|
||||||
|
|
||||||
$: classes = cx(
|
|
||||||
'button',
|
'button',
|
||||||
effect ? effect : undefined,
|
effect,
|
||||||
...[color, size].map(variant => variant && `button--${variant}`),
|
...[color, size].map(variant => variant && `button--${variant}`),
|
||||||
Object.keys($$slots).length !== 0 ? `has-icon-${slotPosition}` : undefined,
|
children && `has-icon-${slotPosition}`,
|
||||||
$$props.class,
|
props.class,
|
||||||
)
|
))
|
||||||
|
|
||||||
// Define external links
|
// Define external links
|
||||||
$: isExternal = /^(http|https):\/\//i.test(url)
|
const isExternal = $derived(/^(http|https):\/\//i.test(url))
|
||||||
$: isProtocol = /^(mailto|tel):/i.test(url)
|
const isProtocol = $derived(/^(mailto|tel):/i.test(url))
|
||||||
$: rel = isExternal ? 'external noopener' : null
|
const rel = $derived(isExternal ? 'external noopener' : null)
|
||||||
$: target = isExternal ? '_blank' : null
|
const target = $derived(isExternal ? '_blank' : null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if tag === 'button'}
|
{#if tag === 'button'}
|
||||||
<button class={classes} tabindex="0" {disabled} on:click>
|
<button class={classes} tabindex="0" {disabled} {onclick}>
|
||||||
{#if slotPosition === 'before'}
|
{#if children && slotPosition === 'before'}
|
||||||
<slot />
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
<SplitText {text} clone={true} />
|
<SplitText {text} clone={true} />
|
||||||
{#if slotPosition === 'after'}
|
{#if children && slotPosition === 'after'}
|
||||||
<slot />
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{:else if tag === 'a'}
|
{:else if tag === 'a'}
|
||||||
<a
|
<a
|
||||||
href={url} class={classes}
|
href={url}
|
||||||
|
class={classes}
|
||||||
{target} {rel}
|
{target} {rel}
|
||||||
data-sveltekit-noscroll={isExternal || isProtocol ? 'off' : ''}
|
data-sveltekit-noscroll={isExternal || isProtocol ? 'off' : ''}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click
|
{onclick}
|
||||||
>
|
>
|
||||||
{#if slotPosition === 'before'}
|
{#if children && slotPosition === 'before'}
|
||||||
<slot />
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
<SplitText {text} clone={true} />
|
<SplitText {text} clone={true} />
|
||||||
{#if slotPosition === 'after'}
|
{#if children && slotPosition === 'after'}
|
||||||
<slot />
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="button-cart">
|
<div class="button-cart">
|
||||||
<ButtonCircle color="purple" on:click={openCart}>
|
<ButtonCircle color="purple" onclick={openCart}>
|
||||||
<Icon icon="bag" label="Cart icon" />
|
<Icon icon="bag" label="Cart icon" />
|
||||||
{#if $cartAmount > 0}
|
{#if $cartAmount > 0}
|
||||||
<span class="quantity" transition:scale={{ start: 0.6, duration: 400, easing: quartOut }}>{$cartAmount}</span>
|
<span class="quantity" transition:scale={{ start: 0.6, duration: 400, easing: quartOut }}>{$cartAmount}</span>
|
||||||
|
|||||||
@@ -5,42 +5,58 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
|
|
||||||
export let tag = 'button'
|
let {
|
||||||
export let url: string = undefined
|
tag = 'button',
|
||||||
export let color: string = undefined
|
url,
|
||||||
export let size: string = undefined
|
color,
|
||||||
export let type: 'button' | 'reset' | 'submit' = undefined
|
size,
|
||||||
export let clone = false
|
type,
|
||||||
export let disabled: boolean = undefined
|
clone = false,
|
||||||
export let label: string = undefined
|
disabled,
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
onclick,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
tag?: string
|
||||||
|
url?: string
|
||||||
|
color?: string
|
||||||
|
size?: string
|
||||||
|
type?: 'button' | 'reset' | 'submit'
|
||||||
|
clone?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
label?: string
|
||||||
|
children?: any
|
||||||
|
onclick?: any
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const className = 'button-circle'
|
const buttonClass = 'button-circle'
|
||||||
$: classes = cx(
|
const classes = $derived(cx(
|
||||||
className,
|
buttonClass,
|
||||||
...[color, size].map(variant => variant && `${className}--${variant}`),
|
...[color, size].map(variant => variant && `${buttonClass}--${variant}`),
|
||||||
clone ? 'has-clone' : null,
|
clone ? 'has-clone' : null,
|
||||||
$$props.class
|
props.class,
|
||||||
)
|
))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet content()}
|
||||||
|
{#if clone}
|
||||||
|
{#each Array(2) as _}
|
||||||
|
{@render children()}
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
|
||||||
{#if tag === 'a'}
|
{#if tag === 'a'}
|
||||||
<a href={url} class={classes} tabindex="0" aria-label={label} on:click>
|
<a href={url} class={classes} tabindex="0" aria-label={label} {onclick}>
|
||||||
{#if clone}
|
{@render content()}
|
||||||
{#each Array(2) as _}
|
|
||||||
<slot />
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<button {type} class={classes} disabled={disabled} tabindex="0" aria-label={label} on:click>
|
<button {type} class={classes} disabled={disabled} tabindex="0" aria-label={label} {onclick}>
|
||||||
{#if clone}
|
{@render content()}
|
||||||
{#each Array(2) as _}
|
|
||||||
<slot />
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cx } from 'classix'
|
let {
|
||||||
|
icon,
|
||||||
export let icon: string
|
label,
|
||||||
export let label: string = undefined
|
...props
|
||||||
|
}: {
|
||||||
$: classes = cx($$props.class)
|
icon: string
|
||||||
|
label?: string
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class={classes} aria-label={label} width="32" height="32">
|
<svg class={props.class} aria-label={label} width="32" height="32">
|
||||||
<use xlink:href="#icon-{icon}" />
|
<use xlink:href="#icon-{icon}" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -18,11 +18,17 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let color: string = undefined
|
let {
|
||||||
export let flip = false
|
color,
|
||||||
|
flip = false,
|
||||||
|
}: {
|
||||||
|
color?: string
|
||||||
|
flip?: boolean
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg width="12" height="14"
|
<svg
|
||||||
|
width="12" height="14"
|
||||||
class="arrow arrow--{color}"
|
class="arrow arrow--{color}"
|
||||||
class:arrow--flip={flip}
|
class:arrow--flip={flip}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,12 +9,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
|
|
||||||
export let animate = false
|
let {
|
||||||
|
animate = false,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
animate?: boolean
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
$: classes = cx(
|
const classes = $derived(cx(
|
||||||
'icon-earth',
|
'icon-earth',
|
||||||
$$props.class,
|
props.class,
|
||||||
)
|
))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class={classes} width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<svg class={classes} width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getAssetUrlKey } from '$utils/api'
|
import { getAssetUrlKey } from '$utils/api'
|
||||||
|
|
||||||
export let src: string = undefined
|
let {
|
||||||
export let id: string = undefined
|
src,
|
||||||
export let sizeKey: string = undefined
|
id,
|
||||||
export let sizes: Sizes = undefined
|
sizeKey,
|
||||||
export let width: number = sizes?.medium?.width
|
sizes,
|
||||||
export let height: number = sizes?.medium?.height
|
width = sizes?.medium?.width,
|
||||||
export let ratio: number = undefined
|
height = sizes?.medium?.height,
|
||||||
export let alt: string
|
ratio,
|
||||||
export let lazy = true
|
alt,
|
||||||
export let decoding: 'auto' | 'sync' | 'async' = 'auto'
|
lazy = true,
|
||||||
|
decoding,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
src?: string
|
||||||
|
id?: string
|
||||||
|
sizeKey?: string
|
||||||
|
sizes?: Sizes
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
ratio?: number
|
||||||
|
alt: string
|
||||||
|
lazy?: boolean
|
||||||
|
decoding?: 'auto' | 'sync' | 'async'
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
interface Sizes {
|
interface Sizes {
|
||||||
small?: { width?: number; height?: number }
|
small?: { width?: number; height?: number }
|
||||||
@@ -29,19 +44,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: imgWidth = sizes?.small?.width || width
|
const imgWidth = $derived(sizes?.small?.width || width)
|
||||||
$: imgHeight = sizes?.small?.height || height
|
const imgHeight = $derived(sizes?.small?.height || height)
|
||||||
$: imgSrc = id ? getAssetUrlKey(id, `${sizeKey}-small`) : src
|
const imgSrc = $derived(id ? getAssetUrlKey(id, `${sizeKey}-small`) : src)
|
||||||
$: srcSet = sizes
|
const srcSet = $derived(
|
||||||
? [
|
sizes ? [
|
||||||
`${getAssetUrlKey(id, `${sizeKey}-small`)} 345w`,
|
`${getAssetUrlKey(id, `${sizeKey}-small`)} 345w`,
|
||||||
sizes.medium && `${getAssetUrlKey(id, `${sizeKey}-medium`)} 768w`,
|
sizes.medium && `${getAssetUrlKey(id, `${sizeKey}-medium`)} 768w`,
|
||||||
sizes.large && `${getAssetUrlKey(id, `${sizeKey}-large`)} 1280w`,
|
sizes.large && `${getAssetUrlKey(id, `${sizeKey}-large`)} 1280w`,
|
||||||
]
|
] : [getAssetUrlKey(id, sizeKey)]
|
||||||
: [getAssetUrlKey(id, sizeKey)]
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<picture class={$$props.class}>
|
<picture class={props.class}>
|
||||||
<img
|
<img
|
||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
sizes={sizes ? '(min-width: 1200px) 864px, (min-width: 992px) 708px, (min-width: 768px) 540px, 100%' : undefined}
|
sizes={sizes ? '(min-width: 1200px) 864px, (min-width: 992px) 708px, (min-width: 768px) 540px, 100%' : undefined}
|
||||||
|
|||||||
@@ -12,39 +12,53 @@
|
|||||||
import { map } from 'utils/math'
|
import { map } from 'utils/math'
|
||||||
import reveal from '$animations/reveal'
|
import reveal from '$animations/reveal'
|
||||||
|
|
||||||
export let tag: string
|
let {
|
||||||
export let label: string = undefined
|
tag,
|
||||||
export let parallax: number = undefined
|
label,
|
||||||
export let offsetStart: number = undefined
|
parallax,
|
||||||
export let offsetEnd: number = undefined
|
offsetStart,
|
||||||
export let animate = true
|
offsetEnd,
|
||||||
|
animate = true,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
tag: string
|
||||||
|
label?: string
|
||||||
|
parallax?: number
|
||||||
|
offsetStart?: number
|
||||||
|
offsetEnd?: number
|
||||||
|
animate?: boolean
|
||||||
|
class?: string
|
||||||
|
children?: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let scrollY: number
|
let scrollY = $state<number>()
|
||||||
let innerWidth: number
|
let innerWidth = $state<number>()
|
||||||
let innerHeight: number
|
let innerHeight = $state<number>()
|
||||||
let titleEl: HTMLElement
|
let titleEl = $state<HTMLElement>()
|
||||||
let isLarger: boolean
|
|
||||||
|
|
||||||
// Define default values
|
|
||||||
$: if (titleEl && !offsetStart && !offsetEnd) {
|
|
||||||
offsetStart = titleEl.offsetTop - innerHeight * (innerWidth < 768 ? 0.2 : 0.75)
|
|
||||||
offsetEnd = titleEl.offsetTop + innerHeight * (innerWidth < 768 ? 0.5 : 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if title is larger than viewport to translate it
|
// Check if title is larger than viewport to translate it
|
||||||
$: isLarger = titleEl && titleEl.offsetWidth >= innerWidth
|
const isLarger = $derived<boolean>(titleEl && titleEl.offsetWidth >= innerWidth)
|
||||||
|
|
||||||
// Calculate the parallax value
|
$effect(() => {
|
||||||
$: if (titleEl) {
|
// Define default values
|
||||||
const toTranslate = 100 - (innerWidth / titleEl.offsetWidth * 100)
|
if (titleEl && !offsetStart && !offsetEnd) {
|
||||||
parallax = isLarger ? map(scrollY, offsetStart, offsetEnd, 0, -toTranslate, true) : 0
|
offsetStart = titleEl.offsetTop - innerHeight * (innerWidth < 768 ? 0.2 : 0.75)
|
||||||
}
|
offsetEnd = titleEl.offsetTop + innerHeight * (innerWidth < 768 ? 0.5 : 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
$: classes = cx(
|
// Calculate the parallax value
|
||||||
|
if (titleEl) {
|
||||||
|
const toTranslate = 100 - (innerWidth / titleEl.offsetWidth * 100)
|
||||||
|
parallax = isLarger ? map(scrollY, offsetStart, offsetEnd, 0, -toTranslate, true) : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const classes = $derived(cx(
|
||||||
'scrolling-title',
|
'scrolling-title',
|
||||||
'title-huge',
|
'title-huge',
|
||||||
$$props.class
|
props.class,
|
||||||
)
|
))
|
||||||
|
|
||||||
const revealOptions = animate ? {
|
const revealOptions = animate ? {
|
||||||
children: '.char',
|
children: '.char',
|
||||||
@@ -65,9 +79,10 @@
|
|||||||
|
|
||||||
<svelte:element this={tag}
|
<svelte:element this={tag}
|
||||||
bind:this={titleEl}
|
bind:this={titleEl}
|
||||||
class={classes} aria-label={label}
|
class={classes}
|
||||||
|
aria-label={label}
|
||||||
style:--parallax-x="{parallax}%"
|
style:--parallax-x="{parallax}%"
|
||||||
use:reveal={revealOptions}
|
use:reveal={revealOptions}
|
||||||
>
|
>
|
||||||
<slot />
|
{@render children()}
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
|
|||||||
@@ -7,8 +7,13 @@
|
|||||||
import reveal from '$animations/reveal'
|
import reveal from '$animations/reveal'
|
||||||
import { DURATION } from '$utils/constants'
|
import { DURATION } from '$utils/constants'
|
||||||
|
|
||||||
export let variant = 'lines'
|
let {
|
||||||
export let tag = 'h1'
|
variant = 'lines',
|
||||||
|
tag = 'h1',
|
||||||
|
}: {
|
||||||
|
variant?: 'inline' | 'lines'
|
||||||
|
tag?: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if tag === 'h1'}
|
{#if tag === 'h1'}
|
||||||
|
|||||||
@@ -12,20 +12,16 @@
|
|||||||
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
|
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
|
||||||
import Carousel from '$components/organisms/Carousel/Carousel.svelte'
|
import Carousel from '$components/organisms/Carousel/Carousel.svelte'
|
||||||
|
|
||||||
export let product: any
|
let {
|
||||||
export let shopProduct: any
|
product,
|
||||||
|
shopProduct,
|
||||||
|
}: {
|
||||||
|
product: any
|
||||||
|
shopProduct: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
$: hasStock = shopProduct.stock_level > 0
|
const hasStock = $derived(shopProduct.stock_level > 0)
|
||||||
|
const lastPreviewPhoto = $derived<any>(product?.photos_preview[product.photos_preview.length - 1]?.directus_files_id)
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview photos specs
|
|
||||||
*/
|
|
||||||
let lastPreviewPhoto: any = undefined
|
|
||||||
|
|
||||||
$: if (product && product.photos_preview.length) {
|
|
||||||
lastPreviewPhoto = product.photos_preview[product.photos_preview.length - 1].directus_files_id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Images sizes
|
// Images sizes
|
||||||
const photosPreview = [
|
const photosPreview = [
|
||||||
@@ -82,7 +78,7 @@
|
|||||||
text={hasStock ? 'Add to cart' : 'Sold out'}
|
text={hasStock ? 'Add to cart' : 'Sold out'}
|
||||||
color="pinklight"
|
color="pinklight"
|
||||||
disabled={!hasStock}
|
disabled={!hasStock}
|
||||||
on:click={() => addToCart(shopProduct)}
|
onclick={() => addToCart(shopProduct)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,28 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
// Components
|
|
||||||
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
||||||
import Select from '$components/molecules/Select.svelte'
|
import Select from '$components/molecules/Select.svelte'
|
||||||
|
|
||||||
export let item: any
|
let {
|
||||||
|
item,
|
||||||
|
quantityLimit = 5,
|
||||||
|
onUpdatedQuantity,
|
||||||
|
onremoved,
|
||||||
|
}: {
|
||||||
|
item: any
|
||||||
|
quantityLimit?: number
|
||||||
|
onUpdatedQuantity?: any
|
||||||
|
onremoved?: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
/** Handle item quantity change */
|
||||||
const quantityLimit = 5
|
const updateQuantity = (value: string) => {
|
||||||
|
onUpdatedQuantity({
|
||||||
|
|
||||||
// When changing item quantity
|
|
||||||
const updateQuantity = ({ detail }: any) => {
|
|
||||||
dispatch('updatedQuantity', {
|
|
||||||
id: item.id,
|
id: item.id,
|
||||||
quantity: Number(detail)
|
quantity: Number(value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// When removing item
|
|
||||||
const removeItem = () => {
|
|
||||||
dispatch('removed', item.id)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="cart-item shadow-small">
|
<div class="cart-item shadow-small">
|
||||||
@@ -50,16 +49,17 @@
|
|||||||
selected: index + 1 === item.quantity,
|
selected: index + 1 === item.quantity,
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
on:change={updateQuantity}
|
onchange={updateQuantity}
|
||||||
value={String(item.quantity)}
|
value={String(item.quantity)}
|
||||||
>
|
>
|
||||||
<span>Quantity:</span>
|
<span>Quantity:</span>
|
||||||
</Select>
|
</Select>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ButtonCircle class="remove"
|
<ButtonCircle
|
||||||
|
class="remove"
|
||||||
size="tiny" color="gray"
|
size="tiny" color="gray"
|
||||||
on:click={removeItem}
|
onclick={() => onremoved(item.id)}
|
||||||
>
|
>
|
||||||
<svg width="8" height="8">
|
<svg width="8" height="8">
|
||||||
<use xlink:href="#cross" />
|
<use xlink:href="#cross" />
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
import IconArrow from '$components/atoms/IconArrow.svelte'
|
import IconArrow from '$components/atoms/IconArrow.svelte'
|
||||||
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
||||||
|
|
||||||
export let past = false
|
let { past = false }: { past?: boolean } = $props()
|
||||||
|
|
||||||
let inputInFocus = false
|
let inputInFocus = $state(false)
|
||||||
let formStatus: FormStatus = null
|
let formStatus = $state<FormStatus>()
|
||||||
let formMessageTimeout: ReturnType<typeof setTimeout> | number
|
let formMessageTimeout: ReturnType<typeof setTimeout> | number
|
||||||
|
|
||||||
interface FormStatus {
|
interface FormStatus {
|
||||||
@@ -27,13 +27,15 @@
|
|||||||
INVALID_EMAIL: `Woops. This email doesn't seem to be valid.`,
|
INVALID_EMAIL: `Woops. This email doesn't seem to be valid.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isSuccess = formStatus && formStatus.success
|
const isSuccess = $derived(formStatus && formStatus.success)
|
||||||
|
|
||||||
// Toggle input focus
|
// Toggle input focus
|
||||||
const toggleFocus = () => inputInFocus = !inputInFocus
|
const toggleFocus = () => inputInFocus = !inputInFocus
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
async function handleForm (event: Event | HTMLFormElement) {
|
async function handleForm (event: Event | HTMLFormElement) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
const data = new FormData(this)
|
const data = new FormData(this)
|
||||||
const email = data.get('email')
|
const email = data.get('email')
|
||||||
|
|
||||||
@@ -61,13 +63,16 @@
|
|||||||
|
|
||||||
<div class="newsletter-form">
|
<div class="newsletter-form">
|
||||||
{#if !isSuccess}
|
{#if !isSuccess}
|
||||||
<form method="POST" action="/api/newsletter" on:submit|preventDefault={handleForm}
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="/api/newsletter"
|
||||||
|
onsubmit={handleForm}
|
||||||
out:fly|local={{ y: -8, easing: quartOut, duration: 600 }}
|
out:fly|local={{ y: -8, easing: quartOut, duration: 600 }}
|
||||||
>
|
>
|
||||||
<div class="email" class:is-focused={inputInFocus}>
|
<div class="email" class:is-focused={inputInFocus}>
|
||||||
<input type="email" placeholder="Your email address" name="email" id="newsletter_email" required
|
<input type="email" placeholder="Your email address" name="email" id="newsletter_email" required
|
||||||
on:focus={toggleFocus}
|
onfocus={toggleFocus}
|
||||||
on:blur={toggleFocus}
|
onblur={toggleFocus}
|
||||||
>
|
>
|
||||||
<ButtonCircle
|
<ButtonCircle
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -94,7 +99,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if formStatus && formStatus.message}
|
{#if formStatus && formStatus.message}
|
||||||
<div class="message shadow-small"
|
<div
|
||||||
|
class="message shadow-small"
|
||||||
class:is-error={!isSuccess}
|
class:is-error={!isSuccess}
|
||||||
class:is-success={isSuccess}
|
class:is-success={isSuccess}
|
||||||
in:fly|local={{ y: 8, easing: quartOut, duration: 600, delay: isSuccess ? 600 : 0 }}
|
in:fly|local={{ y: 8, easing: quartOut, duration: 600, delay: isSuccess ? 600 : 0 }}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SiteTitle from '$components/atoms/SiteTitle/SiteTitle.svelte'
|
import SiteTitle from '$components/atoms/SiteTitle/SiteTitle.svelte'
|
||||||
|
|
||||||
export let text: string
|
let { text }: { text: string } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="heading">
|
<section class="heading">
|
||||||
|
|||||||
@@ -8,15 +8,27 @@
|
|||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Icon from '$components/atoms/Icon.svelte'
|
import Icon from '$components/atoms/Icon.svelte'
|
||||||
|
|
||||||
export let url: string
|
let {
|
||||||
export let photoId: string
|
url,
|
||||||
export let photoAlt: string
|
photoId,
|
||||||
export let title: string
|
photoAlt,
|
||||||
export let index: string
|
title,
|
||||||
export let ratio: number
|
index,
|
||||||
export let date: string = undefined
|
ratio,
|
||||||
export let city: string = undefined
|
date,
|
||||||
export let location: string
|
city,
|
||||||
|
location,
|
||||||
|
}: {
|
||||||
|
url: string
|
||||||
|
photoId: string
|
||||||
|
photoAlt: string
|
||||||
|
title: string
|
||||||
|
index: string
|
||||||
|
ratio: number
|
||||||
|
date?: string
|
||||||
|
city?: string
|
||||||
|
location: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="house grid">
|
<div class="house grid">
|
||||||
|
|||||||
@@ -3,46 +3,53 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { spring } from 'svelte/motion'
|
import { spring } from 'svelte/motion'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
import { lerp } from 'utils/math'
|
import { lerp } from 'utils/math'
|
||||||
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
|
||||||
import { seenLocations } from '$utils/stores'
|
import { seenLocations } from '$utils/stores'
|
||||||
// Components
|
// Components
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Badge from '$components/atoms/Badge.svelte'
|
import Badge from '$components/atoms/Badge.svelte'
|
||||||
|
|
||||||
export let location: any
|
let {
|
||||||
export let latestPhoto: any
|
location,
|
||||||
|
latestPhoto,
|
||||||
|
}: {
|
||||||
|
location: any
|
||||||
|
latestPhoto: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const { settings }: any = getContext('global')
|
const { settings }: any = getContext('global')
|
||||||
|
|
||||||
let locationEl: HTMLElement
|
let locationEl = $state<HTMLElement>()
|
||||||
let photoIndex = 0
|
let photoIndex = $state(0)
|
||||||
|
|
||||||
// Location date limit
|
// Location date limit
|
||||||
let isNew = false
|
let isNew = $state(false)
|
||||||
const dateNowOffset = dayjs().subtract(settings.limit_new, 'day')
|
const dateNowOffset = dayjs().subtract(settings.limit_new, 'day')
|
||||||
const parsedSeenLocations = JSON.parse($seenLocations)
|
const parsedSeenLocations = JSON.parse($seenLocations)
|
||||||
|
|
||||||
$: if (latestPhoto) {
|
$effect(() => {
|
||||||
const dateUpdated = dayjs(latestPhoto.date_created)
|
if (latestPhoto) {
|
||||||
|
const dateUpdated = dayjs(latestPhoto.date_created)
|
||||||
|
|
||||||
// Detect if location has new content
|
// Detect if location has new content
|
||||||
const seenLocationDate = dayjs(parsedSeenLocations[location.id])
|
const seenLocationDate = dayjs(parsedSeenLocations[location.id])
|
||||||
const isLocationSeen = location.id in parsedSeenLocations
|
const isLocationSeen = location.id in parsedSeenLocations
|
||||||
|
|
||||||
// Define if location is has new photos
|
// Define if location is has new photos
|
||||||
if (seenLocationDate && isLocationSeen) {
|
if (seenLocationDate && isLocationSeen) {
|
||||||
// A more recent photo has been added (if has been seen and has a seen date)
|
// A more recent photo has been added (if has been seen and has a seen date)
|
||||||
isNew = dateUpdated.isAfter(dateNowOffset) && dateUpdated.isAfter(seenLocationDate)
|
isNew = dateUpdated.isAfter(dateNowOffset) && dateUpdated.isAfter(seenLocationDate)
|
||||||
} else {
|
} else {
|
||||||
// The photo is after the offset
|
// The photo is after the offset
|
||||||
isNew = dateUpdated.isAfter(dateNowOffset)
|
isNew = dateUpdated.isAfter(dateNowOffset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +73,7 @@
|
|||||||
photoIndex = Math.round(lerp(0, Number(PUBLIC_PREVIEW_COUNT) - 1, moveProgress))
|
photoIndex = Math.round(lerp(0, Number(PUBLIC_PREVIEW_COUNT) - 1, moveProgress))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leaving mouseover
|
/** Leaving mouseover */
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
offset.update($c => ({
|
offset.update($c => ({
|
||||||
x: $c.x,
|
x: $c.x,
|
||||||
@@ -80,9 +87,10 @@
|
|||||||
style:--offset-y="{$offset.y}px"
|
style:--offset-y="{$offset.y}px"
|
||||||
style:--rotate="{$offset.x * 0.125}deg"
|
style:--rotate="{$offset.x * 0.125}deg"
|
||||||
>
|
>
|
||||||
<a href="/{location.country.slug}/{location.slug}"
|
<a
|
||||||
on:mousemove={handleMouseMove}
|
href="/{location.country.slug}/{location.slug}"
|
||||||
on:mouseleave={handleMouseLeave}
|
onmousemove={handleMouseMove}
|
||||||
|
onmouseleave={handleMouseLeave}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -6,12 +6,21 @@
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let title: string
|
let {
|
||||||
export let issue: number
|
title,
|
||||||
export let date: string
|
issue,
|
||||||
export let link: string
|
date,
|
||||||
export let thumbnail: { id: string }
|
link,
|
||||||
export let size: string = undefined
|
thumbnail,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
issue: number
|
||||||
|
date: string
|
||||||
|
link: string
|
||||||
|
thumbnail: { id: string }
|
||||||
|
size?: string
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="issue" class:is-large={size === 'large'}>
|
<div class="issue" class:is-large={size === 'large'}>
|
||||||
|
|||||||
@@ -7,9 +7,15 @@
|
|||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
import { cartOpen } from '$utils/stores/shop'
|
import { cartOpen } from '$utils/stores/shop'
|
||||||
|
|
||||||
export let title: string
|
let {
|
||||||
export let name: string
|
image,
|
||||||
export let image: string
|
name,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
title: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
|
|
||||||
const closeNotification = () => {
|
const closeNotification = () => {
|
||||||
@@ -18,10 +24,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="notification-cart shadow-small"
|
<div
|
||||||
|
class="notification-cart shadow-small"
|
||||||
transition:fly={{ y: 20, duration: 700, easing: quartOut }}
|
transition:fly={{ y: 20, duration: 700, easing: quartOut }}
|
||||||
on:click={closeNotification}
|
onclick={closeNotification}
|
||||||
on:keydown
|
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
|
|||||||
@@ -3,23 +3,32 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let ended = false
|
let {
|
||||||
export let current: number
|
ended = false,
|
||||||
export let total: number
|
current,
|
||||||
|
total,
|
||||||
|
onclick,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
ended?: boolean
|
||||||
|
current: number
|
||||||
|
total: number
|
||||||
|
onclick?: any
|
||||||
|
children?: any
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="pagination"
|
class="pagination"
|
||||||
class:is-disabled={ended ? ended : undefined}
|
class:is-disabled={ended ? ended : undefined}
|
||||||
role="button" tabindex="0"
|
role="presentation"
|
||||||
on:click
|
{onclick}
|
||||||
on:keydown
|
|
||||||
>
|
>
|
||||||
<div class="pagination__progress">
|
<div class="pagination__progress">
|
||||||
<span class="current">{current}</span>
|
<span class="current">{current}</span>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span class="total">{total}</span>
|
<span class="total">{total}</span>
|
||||||
|
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,46 +3,62 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let id: string
|
let {
|
||||||
export let alt: string
|
id,
|
||||||
export let url: string = undefined
|
alt,
|
||||||
export let title: string = undefined
|
url,
|
||||||
export let location: any = undefined
|
title,
|
||||||
export let city: string = undefined
|
location,
|
||||||
export let hovered = false
|
city,
|
||||||
export let lazy = true
|
hovered = false,
|
||||||
|
lazy = true,
|
||||||
|
onhover,
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
|
alt: string
|
||||||
|
url?: string
|
||||||
|
title?: string
|
||||||
|
location?: any
|
||||||
|
city?: string
|
||||||
|
hovered?: boolean
|
||||||
|
lazy?: boolean
|
||||||
|
onhover?: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const sizes = {
|
const sizes = {
|
||||||
small: { width: 224 },
|
small: { width: 224 },
|
||||||
medium: { width: 464 },
|
medium: { width: 464 },
|
||||||
large: { width: 864 },
|
large: { width: 864 },
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendHover = (hover: boolean) => dispatch('hover', hover)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="photo-card"
|
{#snippet image()}
|
||||||
|
<Image
|
||||||
|
{id}
|
||||||
|
sizeKey="postcard"
|
||||||
|
{sizes}
|
||||||
|
ratio={1.5}
|
||||||
|
{alt}
|
||||||
|
{lazy}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="photo-card"
|
||||||
class:is-hovered={hovered}
|
class:is-hovered={hovered}
|
||||||
on:mouseenter={() => sendHover(true)}
|
onmouseenter={() => onhover(true)}
|
||||||
on:focus={() => sendHover(true)}
|
onfocus={() => onhover(true)}
|
||||||
on:mouseout={() => sendHover(false)}
|
onmouseout={() => onhover(false)}
|
||||||
on:blur={() => sendHover(false)}
|
onblur={() => onhover(false)}
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
{#if url}
|
{#if url}
|
||||||
<div class="photo-card__content">
|
<div class="photo-card__content">
|
||||||
<a href={url} data-sveltekit-noscroll>
|
<a href={url} data-sveltekit-noscroll>
|
||||||
<Image
|
{@render image()}
|
||||||
{id}
|
|
||||||
sizeKey="postcard"
|
|
||||||
{sizes}
|
|
||||||
ratio={1.5}
|
|
||||||
{alt}
|
|
||||||
{lazy}
|
|
||||||
/>
|
|
||||||
{#if title && location}
|
{#if title && location}
|
||||||
<div class="photo-card__info">
|
<div class="photo-card__info">
|
||||||
<Image
|
<Image
|
||||||
@@ -58,13 +74,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Image
|
{@render image()}
|
||||||
{id}
|
|
||||||
sizeKey="postcard"
|
|
||||||
{sizes}
|
|
||||||
ratio={1.5}
|
|
||||||
{alt}
|
|
||||||
{lazy}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,19 +6,30 @@
|
|||||||
import { cx } from 'classix'
|
import { cx } from 'classix'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let street: string
|
let {
|
||||||
export let location: string
|
street,
|
||||||
export let region: string = undefined
|
location,
|
||||||
export let country: string
|
region,
|
||||||
export let flagId: string
|
country,
|
||||||
export let size: string = undefined
|
flagId,
|
||||||
|
size,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
street: string
|
||||||
|
location: string
|
||||||
|
region?: string
|
||||||
|
country: string
|
||||||
|
flagId: string
|
||||||
|
size?: string
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const className = 'postcard'
|
const cardClass = 'postcard'
|
||||||
$: classes = cx(
|
const classes = $derived(cx(
|
||||||
className,
|
cardClass,
|
||||||
...[size].map(variant => variant && `${className}--${variant}`),
|
...[size].map(variant => variant && `${cardClass}--${variant}`),
|
||||||
$$props.class
|
props.class,
|
||||||
)
|
))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={classes}>
|
<div class={classes}>
|
||||||
|
|||||||
@@ -9,15 +9,23 @@
|
|||||||
import Button from '$components/atoms/Button/Button.svelte'
|
import Button from '$components/atoms/Button/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let product: any
|
let {
|
||||||
export let location: { name: string, slug: string }
|
product,
|
||||||
export let image: any
|
location,
|
||||||
|
image,
|
||||||
|
}: {
|
||||||
|
product: any
|
||||||
|
location: { name: string, slug: string }
|
||||||
|
image: any
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="poster">
|
<div class="poster">
|
||||||
{#if image}
|
{#if image}
|
||||||
<a href="/shop/poster-{location.slug}" data-sveltekit-noscroll
|
<a
|
||||||
on:click={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
href="/shop/poster-{location.slug}"
|
||||||
|
onclick={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
||||||
|
data-sveltekit-noscroll
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
id={image.id}
|
id={image.id}
|
||||||
@@ -37,13 +45,13 @@
|
|||||||
size="xsmall"
|
size="xsmall"
|
||||||
url="/shop/poster-{location.slug}"
|
url="/shop/poster-{location.slug}"
|
||||||
text="View"
|
text="View"
|
||||||
on:click={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
onclick={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
text="Add to cart"
|
text="Add to cart"
|
||||||
color="pink"
|
color="pink"
|
||||||
on:click={() => addToCart(product)}
|
onclick={() => addToCart(product)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,45 +4,53 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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'
|
||||||
|
|
||||||
export let index: number
|
let {
|
||||||
export let text: string
|
index,
|
||||||
export let image: any = undefined
|
text,
|
||||||
export let video: any = undefined
|
image,
|
||||||
|
video,
|
||||||
|
}: {
|
||||||
|
index: number
|
||||||
|
text: string
|
||||||
|
image?: any
|
||||||
|
video?: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const imageRatio = image ? image.width / image.height : undefined
|
const imageRatio = $derived(image ? image.width / image.height : undefined)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="step grid" style:--index={index}
|
<div
|
||||||
|
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:scaleFade|local={{ scale: [1, 0.9], opacity: [1, 0], x: [0, -20] }}
|
||||||
>
|
>
|
||||||
{#if image || video}
|
{#if image || video}
|
||||||
<div class="media">
|
<div class="media">
|
||||||
{#if image}
|
{#if image}
|
||||||
<Image
|
<Image
|
||||||
class="image shadow-box-dark"
|
class="image shadow-box-dark"
|
||||||
id={image.id}
|
id={image.id}
|
||||||
sizeKey="product"
|
sizeKey="product"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
medium: { width: 600 },
|
medium: { width: 600 },
|
||||||
}}
|
}}
|
||||||
ratio={imageRatio}
|
ratio={imageRatio}
|
||||||
alt={image.title}
|
alt={image.title}
|
||||||
/>
|
/>
|
||||||
{:else if video && video.mp4 && video.webm}
|
{:else if video && video.mp4 && video.webm}
|
||||||
<video muted loop playsinline autoplay>
|
<video muted loop playsinline autoplay>
|
||||||
<source type="video/mp4" src={getAssetUrlKey(video.mp4, 'step')} />
|
<source type="video/mp4" src={getAssetUrlKey(video.mp4, 'step')} />
|
||||||
<source type="video/webm" src={getAssetUrlKey(video.webm, 'step')} />
|
<source type="video/webm" src={getAssetUrlKey(video.webm, 'step')} />
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
</video>
|
</video>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="text text-xsmall">
|
<div class="text text-xsmall">
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
let {
|
||||||
|
id,
|
||||||
interface Option {
|
name,
|
||||||
value: string
|
options,
|
||||||
|
value,
|
||||||
|
onchange,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
name: string
|
name: string
|
||||||
default?: boolean
|
options: {
|
||||||
selected?: boolean
|
value: string
|
||||||
}
|
name: string
|
||||||
|
default?: boolean
|
||||||
|
selected?: boolean
|
||||||
|
}[]
|
||||||
|
value?: string
|
||||||
|
onchange?: any
|
||||||
|
children?: any
|
||||||
|
} = $props()
|
||||||
|
|
||||||
export let id: string
|
|
||||||
export let name: string
|
|
||||||
export let options: Option[]
|
|
||||||
export let value: string = undefined
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const defaultOption = options.find(option => option.default)
|
const defaultOption = options.find(option => option.default)
|
||||||
|
|
||||||
let selected = value || options[0].value
|
let selected = $state(value || options[0].value)
|
||||||
$: currentOption = options.find(option => option.value === selected)
|
const currentOption = $derived(options.find(option => option.value === selected))
|
||||||
|
|
||||||
// Redefine value from parent (when reset)
|
// Redefine value from parent (when reset)
|
||||||
$: if (value === defaultOption.value) {
|
$effect(() => {
|
||||||
selected = defaultOption.value
|
if (value === defaultOption.value) {
|
||||||
}
|
selected = defaultOption.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,17 +39,17 @@
|
|||||||
const handleChange = ({ target: { value } }: any) => {
|
const handleChange = ({ target: { value } }: any) => {
|
||||||
const option = options.find(option => option.value === value)
|
const option = options.find(option => option.value === value)
|
||||||
|
|
||||||
// Dispatch event to parent
|
// Send value to parent
|
||||||
dispatch('change', option.value)
|
onchange(option.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<slot />
|
{@render children()}
|
||||||
|
|
||||||
<span>{currentOption.name}</span>
|
<span>{currentOption.name}</span>
|
||||||
|
|
||||||
<select {name} {id} bind:value={selected} on:change={handleChange}>
|
<select {name} {id} bind:value={selected} onchange={handleChange}>
|
||||||
{#each options as { value, name }}
|
{#each options as { value, name }}
|
||||||
<option {value} selected={value === selected}>
|
<option {value} selected={value === selected}>
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -9,18 +9,26 @@
|
|||||||
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
|
|
||||||
export let isOver = false
|
let {
|
||||||
|
isOver = false,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
isOver?: boolean
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const { shopLocations }: any = getContext('shop')
|
const { shopLocations }: any = getContext('shop')
|
||||||
|
|
||||||
const classes = cx(
|
const classes = $derived(cx(
|
||||||
'shop-locationswitcher',
|
'shop-locationswitcher',
|
||||||
isOver && 'is-over',
|
isOver && 'is-over',
|
||||||
$$props.class
|
props.class,
|
||||||
)
|
))
|
||||||
|
|
||||||
|
|
||||||
// Quick location change
|
/**
|
||||||
|
* Quick location change
|
||||||
|
*/
|
||||||
const quickLocationChange = async ({ target: { value } }: any) => {
|
const quickLocationChange = async ({ target: { value } }: any) => {
|
||||||
const pathTo = `/shop/poster-${value}`
|
const pathTo = `/shop/poster-${value}`
|
||||||
goto(pathTo, { replaceState: true, noScroll: true, keepFocus: true })
|
goto(pathTo, { replaceState: true, noScroll: true, keepFocus: true })
|
||||||
@@ -37,7 +45,7 @@
|
|||||||
<svg width="18" height="18">
|
<svg width="18" height="18">
|
||||||
<use xlink:href="#icon-map-pin" />
|
<use xlink:href="#icon-map-pin" />
|
||||||
</svg>
|
</svg>
|
||||||
<select on:change={quickLocationChange}>
|
<select onchange={quickLocationChange}>
|
||||||
{#each shopLocations as { name, slug }}
|
{#each shopLocations as { name, slug }}
|
||||||
<option value={slug} selected={slug === $shopCurrentProductSlug}>{name}</option>
|
<option value={slug} selected={slug === $shopCurrentProductSlug}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
const { settings: { switcher_links } }: any = getContext('global')
|
const { settings: { switcher_links } }: any = getContext('global')
|
||||||
|
|
||||||
let switcherEl: HTMLElement
|
let switcherEl = $state<HTMLElement>()
|
||||||
let isOpen = false
|
let isOpen = $state(false)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,9 +37,11 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:click={windowClick} />
|
<svelte:window onclick={windowClick} />
|
||||||
|
|
||||||
<aside class="switcher" bind:this={switcherEl}
|
<aside
|
||||||
|
bind:this={switcherEl}
|
||||||
|
class="switcher"
|
||||||
class:is-open={isOpen}
|
class:is-open={isOpen}
|
||||||
use:reveal={{
|
use:reveal={{
|
||||||
animation: { y: [24, 0], opacity: [0, 1] },
|
animation: { y: [24, 0], opacity: [0, 1] },
|
||||||
@@ -50,12 +52,15 @@
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button class="switcher__button" title="{!isOpen ? 'Open' : 'Close'} menu" tabindex="0"
|
<button
|
||||||
on:click={toggleSwitcher}
|
class="switcher__button"
|
||||||
|
title="{!isOpen ? 'Open' : 'Close'} menu"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={toggleSwitcher}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{#each Array(3) as _}
|
{#each Array(3) as _}
|
||||||
<i />
|
<i></i>
|
||||||
{/each}
|
{/each}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -63,7 +68,7 @@
|
|||||||
<ul class="switcher__links" data-sveltekit-noscroll>
|
<ul class="switcher__links" data-sveltekit-noscroll>
|
||||||
{#each switcher_links as { text, url, icon, icon_label }}
|
{#each switcher_links as { text, url, icon, icon_label }}
|
||||||
<li class:is-active={$page.url.pathname === url}>
|
<li class:is-active={$page.url.pathname === url}>
|
||||||
<a href={url} on:click={toggleSwitcher} tabindex="0">
|
<a href={url} onclick={toggleSwitcher} tabindex="0">
|
||||||
<Icon class="icon" icon={icon} label={icon_label} />
|
<Icon class="icon" icon={icon} label={icon_label} />
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { fade, fly } from 'svelte/transition'
|
import { fade, fly } from 'svelte/transition'
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
import { browser } from '$app/environment'
|
import { browser } from '$app/environment'
|
||||||
@@ -12,25 +11,33 @@
|
|||||||
import Button from '$components/atoms/Button/Button.svelte'
|
import Button from '$components/atoms/Button/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let id: string
|
let {
|
||||||
export let type: 'global' | 'local'
|
id,
|
||||||
export let text: string
|
type,
|
||||||
export let cta: {
|
text,
|
||||||
label: string
|
cta,
|
||||||
url: string
|
images,
|
||||||
color: string
|
show = false,
|
||||||
} = undefined
|
loopDuration = 3000,
|
||||||
export let images: { id: string, title: string }[] = undefined
|
...props
|
||||||
export let show = false
|
}: {
|
||||||
|
id: string
|
||||||
$: if (browser) {
|
type: 'global' | 'local'
|
||||||
show = !localStorage.getItem(`toast-${id}`)
|
text: string
|
||||||
}
|
cta?: {
|
||||||
|
label: string
|
||||||
|
url: string
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
images?: { id: string, title: string }[]
|
||||||
|
show?: boolean
|
||||||
|
loopDuration?: number
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
// Image rotation
|
// Image rotation
|
||||||
let imagesLoop: ReturnType<typeof setTimeout>
|
let imagesLoop: ReturnType<typeof setTimeout>
|
||||||
let currentImageIndex = 0
|
let currentImageIndex = $state(0)
|
||||||
const loopDuration = 3000
|
|
||||||
|
|
||||||
const incrementCurrentImageIndex = () => {
|
const incrementCurrentImageIndex = () => {
|
||||||
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
|
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
|
||||||
@@ -43,15 +50,19 @@
|
|||||||
show = false
|
show = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$: classes = cx(
|
const classes = $derived(cx(
|
||||||
'toast',
|
'toast',
|
||||||
`toast--${type}`,
|
`toast--${type}`,
|
||||||
'shadow-small',
|
'shadow-small',
|
||||||
$$props.class,
|
props.class,
|
||||||
)
|
))
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
|
if (browser) {
|
||||||
|
show = !localStorage.getItem(`toast-${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (images.length > 1) {
|
if (images.length > 1) {
|
||||||
imagesLoop = setTimeout(incrementCurrentImageIndex, loopDuration)
|
imagesLoop = setTimeout(incrementCurrentImageIndex, loopDuration)
|
||||||
}
|
}
|
||||||
@@ -103,7 +114,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="close" on:click={close} title="Close">
|
<button class="close" onclick={close} title="Close">
|
||||||
<svg width="10" height="10">
|
<svg width="10" height="10">
|
||||||
<use xlink:href="#cross" />
|
<use xlink:href="#cross" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -6,9 +6,15 @@
|
|||||||
// Components
|
// Components
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let title: string
|
let {
|
||||||
export let image: any
|
title,
|
||||||
export let back = false
|
image,
|
||||||
|
back = false,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
image: any
|
||||||
|
back?: boolean
|
||||||
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="banner">
|
<section class="banner">
|
||||||
|
|||||||
@@ -3,20 +3,27 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
|
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
|
||||||
|
import cx from 'classix'
|
||||||
// Components
|
// Components
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let slides: any
|
let {
|
||||||
|
slides,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
slides: any[]
|
||||||
|
class?: string
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let carouselEl: HTMLElement
|
let carouselEl = $state<HTMLElement>()
|
||||||
let carousel: EmblaCarouselType
|
let carousel: EmblaCarouselType
|
||||||
let currentSlide = 0
|
let currentSlide = $state(0)
|
||||||
let arrowDirection: string = null
|
let arrowDirection = $state<'next' | 'prev'>()
|
||||||
$: isFirstSlide = currentSlide === 0
|
|
||||||
$: isLastSlide = currentSlide === slides.length - 1
|
const isFirstSlide = $derived(currentSlide === 0)
|
||||||
|
const isLastSlide = $derived(currentSlide === slides.length - 1)
|
||||||
|
|
||||||
|
|
||||||
/** Navigate to specific slide */
|
/** Navigate to specific slide */
|
||||||
@@ -65,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
// Init carousel
|
// Init carousel
|
||||||
carousel = EmblaCarousel(carouselEl, {
|
carousel = EmblaCarousel(carouselEl, {
|
||||||
loop: false
|
loop: false
|
||||||
@@ -82,12 +89,13 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="carousel {$$props.class ?? ''}">
|
<div class={cx('carousel', props.class)}>
|
||||||
{#if slides.length}
|
{#if slides.length}
|
||||||
<div class="carousel__viewport" bind:this={carouselEl}
|
<div
|
||||||
on:mousemove={handleArrowMove}
|
bind:this={carouselEl}
|
||||||
on:click={handleArrowClick}
|
class="carousel__viewport"
|
||||||
on:keydown
|
onmousemove={handleArrowMove}
|
||||||
|
onclick={handleArrowClick}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div class="carousel__slides">
|
<div class="carousel__slides">
|
||||||
@@ -111,15 +119,16 @@
|
|||||||
<ul class="carousel__dots">
|
<ul class="carousel__dots">
|
||||||
{#each slides as _, index}
|
{#each slides as _, index}
|
||||||
<li class:is-active={index === currentSlide}>
|
<li class:is-active={index === currentSlide}>
|
||||||
<button on:click={() => goToSlide(index)} aria-label="Go to slide #{index + 1}" />
|
<button onclick={() => goToSlide(index)} aria-label="Go to slide #{index + 1}"></button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<span class="carousel__arrow"
|
<span
|
||||||
|
class="carousel__arrow"
|
||||||
|
class:is-flipped={arrowDirection === 'prev' && !isFirstSlide || isLastSlide}
|
||||||
style:--x="{$arrowPosition.x}px"
|
style:--x="{$arrowPosition.x}px"
|
||||||
style:--y="{$arrowPosition.y}px"
|
style:--y="{$arrowPosition.y}px"
|
||||||
class:is-flipped={arrowDirection === 'prev' && !isFirstSlide || isLastSlide}
|
|
||||||
>
|
>
|
||||||
<svg width="29" height="32">
|
<svg width="29" height="32">
|
||||||
<use xlink:href="#arrow" />
|
<use xlink:href="#arrow" />
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment'
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { fade, fly } from 'svelte/transition'
|
import { fade, fly } from 'svelte/transition'
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
@@ -18,24 +16,12 @@
|
|||||||
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher/ShopLocationSwitcher.svelte'
|
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher/ShopLocationSwitcher.svelte'
|
||||||
|
|
||||||
|
|
||||||
// Block scroll if cart is open
|
/** Closing the cart */
|
||||||
$: if (browser && $smoothScroll) {
|
|
||||||
if ($cartOpen) {
|
|
||||||
$smoothScroll.stop()
|
|
||||||
} else {
|
|
||||||
$smoothScroll.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
document.documentElement.classList.toggle('block-scroll', $cartOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Closing the cart
|
|
||||||
const handleCloseCart = () => {
|
const handleCloseCart = () => {
|
||||||
$cartOpen = false
|
$cartOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item quantity changed
|
/** Item quantity changed */
|
||||||
const changedQuantity = async ({ detail: { id, quantity } }) => {
|
const changedQuantity = async ({ detail: { id, quantity } }) => {
|
||||||
// Cart is now updating
|
// Cart is now updating
|
||||||
$cartIsUpdating = true
|
$cartIsUpdating = true
|
||||||
@@ -50,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item removed
|
/** Item removed */
|
||||||
const removedItem = async ({ detail: id }) => {
|
const removedItem = async ({ detail: id }) => {
|
||||||
// Cart is now updating
|
// Cart is now updating
|
||||||
$cartIsUpdating = true
|
$cartIsUpdating = true
|
||||||
@@ -66,15 +52,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMount(async () => {
|
$effect(() => {
|
||||||
// Init Swell
|
// Init Swell
|
||||||
initSwell()
|
initSwell()
|
||||||
|
|
||||||
// Fetch cart
|
// Fetch cart
|
||||||
const cart = await getCart()
|
getCart().then(cart => {
|
||||||
if (cart) {
|
|
||||||
// Store cart data
|
// Store cart data
|
||||||
$cartData = cart
|
if (cart) {
|
||||||
|
$cartData = cart
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Block scroll if cart is open
|
||||||
|
if ($smoothScroll) {
|
||||||
|
if ($cartOpen) {
|
||||||
|
$smoothScroll.stop()
|
||||||
|
} else {
|
||||||
|
$smoothScroll.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.documentElement.classList.toggle('block-scroll', $cartOpen)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -84,21 +82,22 @@
|
|||||||
<ShopLocationSwitcher isOver={true} />
|
<ShopLocationSwitcher isOver={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="cart shadow-box-dark"
|
<aside
|
||||||
|
class="cart shadow-box-dark"
|
||||||
class:is-updating={$cartIsUpdating}
|
class:is-updating={$cartIsUpdating}
|
||||||
transition:fly={{ x: 48, duration: 600, easing: quartOut }}
|
transition:fly={{ x: 48, duration: 600, easing: quartOut }}
|
||||||
>
|
>
|
||||||
<header class="cart__heading">
|
<header class="cart__heading">
|
||||||
<h2>Cart</h2>
|
<h2>Cart</h2>
|
||||||
<button class="text-label" on:click={handleCloseCart}>Close</button>
|
<button class="text-label" onclick={handleCloseCart}>Close</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="cart__content">
|
<div class="cart__content">
|
||||||
{#if $cartAmount > 0}
|
{#if $cartAmount > 0}
|
||||||
{#each $cartData.items as item}
|
{#each $cartData.items as item}
|
||||||
<CartItem {item}
|
<CartItem {item}
|
||||||
on:updatedQuantity={changedQuantity}
|
onUpdatedQuantity={changedQuantity}
|
||||||
on:removed={removedItem}
|
onremoved={removedItem}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -140,7 +139,7 @@
|
|||||||
url={$cartData && $cartData.checkout_url}
|
url={$cartData && $cartData.checkout_url}
|
||||||
text="Checkout"
|
text="Checkout"
|
||||||
color="pink"
|
color="pink"
|
||||||
on:click={() => sendEvent('cartCheckout', { props: { amount: $cartAmount } })}
|
onclick={() => sendEvent('cartCheckout', { props: { amount: $cartAmount } })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -148,9 +147,10 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="cart-overlay"
|
<div
|
||||||
|
class="cart-overlay"
|
||||||
transition:fade={{ duration: 600, easing: quartOut }}
|
transition:fade={{ duration: 600, easing: quartOut }}
|
||||||
on:click={handleCloseCart}
|
onclick={handleCloseCart}
|
||||||
on:keydown
|
role="presentation"
|
||||||
/>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PhotoCard from '$components/molecules/PhotoCard/PhotoCard.svelte'
|
import PhotoCard from '$components/molecules/PhotoCard/PhotoCard.svelte'
|
||||||
|
|
||||||
export let photos: any[] = []
|
let { photos }: { photos: any[] } = $props()
|
||||||
|
|
||||||
let hovered: number = null
|
let hovered = $state<number>(null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if photos}
|
{#if photos}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
city={city}
|
city={city}
|
||||||
hovered={hovered === index}
|
hovered={hovered === index}
|
||||||
lazy={false}
|
lazy={false}
|
||||||
on:hover={({ detail }) => hovered = detail ? index : null}
|
onhover={(id: number) => hovered = id ? index : null}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { fade, fly as flySvelte } from 'svelte/transition'
|
import { fade, fly as flySvelte } from 'svelte/transition'
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
import { Globe, type Marker } from '$modules/globe'
|
import { Globe, type Marker } from '$modules/globe'
|
||||||
@@ -15,20 +15,31 @@
|
|||||||
|
|
||||||
const isDev = import.meta.env.DEV
|
const isDev = import.meta.env.DEV
|
||||||
|
|
||||||
export let type: string = undefined
|
let {
|
||||||
export let autoRotate = true
|
type,
|
||||||
export let enableMarkers = true
|
autoRotate = true,
|
||||||
export let enableMarkersLinks = true
|
enableMarkers = true,
|
||||||
export let speed = 0.1
|
enableMarkersLinks = true,
|
||||||
export let pane: boolean = isDev
|
speed = 0.1,
|
||||||
export let width: number = undefined
|
pane = isDev,
|
||||||
|
width,
|
||||||
|
}: {
|
||||||
|
type?: string
|
||||||
|
autoRotate?: boolean
|
||||||
|
enableMarkers?: boolean
|
||||||
|
enableMarkersLinks?: boolean
|
||||||
|
speed?: number
|
||||||
|
pane?: boolean
|
||||||
|
width?: number
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let innerWidth: number
|
let innerWidth = $state<number>()
|
||||||
let globeParentEl: HTMLElement, globeEl: HTMLElement
|
let globeParentEl = $state<HTMLElement>()
|
||||||
let globe: any
|
let globeEl = $state<HTMLElement>()
|
||||||
|
let globe = $state<any>()
|
||||||
let observer: IntersectionObserver
|
let observer: IntersectionObserver
|
||||||
let animation: number
|
let animation = $state<number>()
|
||||||
let hoveredMarker: { name: string, country: string } = null
|
let hoveredMarker: { name: string, country: string } = $state()
|
||||||
|
|
||||||
const { continents, locations }: any = getContext('global')
|
const { continents, locations }: any = getContext('global')
|
||||||
const randomContinent: any = getRandomItem(continents)
|
const randomContinent: any = getRandomItem(continents)
|
||||||
@@ -41,7 +52,7 @@
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
const globeResolution = innerWidth > 1440 && window.devicePixelRatio > 1 ? 4 : 2
|
const globeResolution = innerWidth > 1440 && window.devicePixelRatio > 1 ? 4 : 2
|
||||||
|
|
||||||
globe = new Globe({
|
globe = new Globe({
|
||||||
@@ -92,51 +103,56 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** Update rendering */
|
||||||
* Methods
|
|
||||||
*/
|
|
||||||
// Update
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
animation = requestAnimationFrame(update)
|
animation = requestAnimationFrame(update)
|
||||||
globe.render()
|
globe.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop
|
/** Stop rendering */
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
cancelAnimationFrame(animation)
|
cancelAnimationFrame(animation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize
|
/** Handle resize */
|
||||||
const resize = debounce(() => {
|
const resize = debounce(() => {
|
||||||
globe.resize()
|
globe.resize()
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
// Destroy
|
/** Destroy globe */
|
||||||
const destroy = () => {
|
const destroy = () => {
|
||||||
stop()
|
stop()
|
||||||
globe.destroy()
|
globe.destroy()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth
|
<svelte:window
|
||||||
on:resize={resize}
|
bind:innerWidth
|
||||||
|
onresize={resize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="globe" bind:this={globeParentEl}
|
<div
|
||||||
|
bind:this={globeParentEl}
|
||||||
|
class="globe"
|
||||||
class:is-cropped={type === 'cropped'}
|
class:is-cropped={type === 'cropped'}
|
||||||
style:--width={width ? `${width}px` : null}
|
style:--width={width ? `${width}px` : null}
|
||||||
>
|
>
|
||||||
<div class="globe__canvas" bind:this={globeEl}
|
<div
|
||||||
|
bind:this={globeEl}
|
||||||
|
class="globe__canvas"
|
||||||
class:is-faded={hoveredMarker}
|
class:is-faded={hoveredMarker}
|
||||||
>
|
>
|
||||||
<ul class="globe__markers">
|
<ul class="globe__markers">
|
||||||
{#each markers as { name, slug, country, lat, lng }}
|
{#each markers as { name, slug, country, lat, lng }}
|
||||||
<li class="globe__marker" data-location={slug} data-lat={lat} data-lng={lng}>
|
<li class="globe__marker" data-location={slug} data-lat={lat} data-lng={lng}>
|
||||||
<a href="/{country.slug}/{slug}" aria-label={name} data-sveltekit-noscroll
|
<a
|
||||||
on:mouseenter={() => hoveredMarker = { name, country: country.name }}
|
href="/{country.slug}/{slug}"
|
||||||
on:mouseleave={() => hoveredMarker = null}
|
aria-label={name}
|
||||||
|
data-sveltekit-noscroll
|
||||||
|
onmouseenter={() => hoveredMarker = { name, country: country.name }}
|
||||||
|
onmouseleave={() => hoveredMarker = null}
|
||||||
>
|
>
|
||||||
<i />
|
<i></i>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -145,7 +161,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if hoveredMarker}
|
{#if hoveredMarker}
|
||||||
<div class="globe__location"
|
<div
|
||||||
|
class="globe__location"
|
||||||
in:revealSplit={{ duration: 1 }}
|
in:revealSplit={{ duration: 1 }}
|
||||||
out:fade={{ duration: 300, easing: quartOut }}
|
out:fade={{ duration: 300, easing: quartOut }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let { children }: { children: any } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
<ul class="list-cta" data-sveltekit-noscroll>
|
<ul class="list-cta" data-sveltekit-noscroll>
|
||||||
<slot />
|
{@render children()}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -14,29 +14,32 @@
|
|||||||
import Button from '$components/atoms/Button/Button.svelte'
|
import Button from '$components/atoms/Button/Button.svelte'
|
||||||
import Location from '$components/molecules/Location/Location.svelte'
|
import Location from '$components/molecules/Location/Location.svelte'
|
||||||
|
|
||||||
export let locations: any[]
|
let { locations }: { locations: any[] } = $props()
|
||||||
|
|
||||||
const { continents, settings: { explore_list } }: any = getContext('global')
|
const { continents, settings: { explore_list } }: any = getContext('global')
|
||||||
|
|
||||||
// Continents filtering logic
|
|
||||||
let currentContinent: string = undefined
|
|
||||||
|
|
||||||
$: filteredLocations = locations.filter(({ country: { continent } }: any) => {
|
/**
|
||||||
if (!currentContinent) {
|
* Continents filtering logic
|
||||||
// Show all locations by default
|
*/
|
||||||
return true
|
let currentContinent = $state<string>()
|
||||||
} else {
|
const filteredLocations = $derived.by(() => {
|
||||||
// Location's continent matches the clicked continent
|
return locations.filter(({ country: { continent } }: any) => {
|
||||||
return continent.slug === currentContinent
|
if (!currentContinent) {
|
||||||
}
|
// Show all locations by default
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// Location's continent matches the clicked continent
|
||||||
|
return continent.slug === currentContinent
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/* Filter locations from continent */
|
||||||
* Filter locations from continent
|
|
||||||
*/
|
|
||||||
const filterLocation = throttle((continent: string) => {
|
const filterLocation = throttle((continent: string) => {
|
||||||
currentContinent = continent !== currentContinent ? continent : null
|
currentContinent = continent !== currentContinent ? continent : null
|
||||||
|
sendEvent('filterContinent')
|
||||||
}, 600)
|
}, 600)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,10 +56,7 @@
|
|||||||
text={name}
|
text={name}
|
||||||
slotPosition="after"
|
slotPosition="after"
|
||||||
class={'is-disabled'}
|
class={'is-disabled'}
|
||||||
on:click={() => {
|
onclick={() => filterLocation(slug)}
|
||||||
filterLocation(slug)
|
|
||||||
sendEvent('filterContinent')
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg width="12" height="12">
|
<svg width="12" height="12">
|
||||||
<use xlink:href="#cross" />
|
<use xlink:href="#cross" />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// Components
|
// Components
|
||||||
import EmailForm from '$components/molecules/EmailForm/EmailForm.svelte'
|
import EmailForm from '$components/molecules/EmailForm/EmailForm.svelte'
|
||||||
|
|
||||||
export let theme = 'default'
|
let { theme = 'default' }: { theme?: 'default' | 'light' } = $props()
|
||||||
|
|
||||||
const { settings: { newsletter_text, newsletter_subtitle } }: any = getContext('global')
|
const { settings: { newsletter_text, newsletter_subtitle } }: any = getContext('global')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,19 +3,23 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
|
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
|
||||||
// Components
|
// Components
|
||||||
import Poster from '$components/molecules/Poster/Poster.svelte'
|
import Poster from '$components/molecules/Poster/Poster.svelte'
|
||||||
import { debounce } from 'utils/actions'
|
import { debounce } from 'utils/actions'
|
||||||
|
|
||||||
export let posters: any = []
|
let {
|
||||||
|
posters,
|
||||||
|
}: {
|
||||||
|
posters: any[]
|
||||||
|
} = $props()
|
||||||
|
|
||||||
let innerWidth: number
|
let innerWidth = $state<number>()
|
||||||
let carouselEl: HTMLElement
|
let carouselEl = $state<HTMLElement>()
|
||||||
let carousel: EmblaCarouselType
|
let carousel = $state<EmblaCarouselType>()
|
||||||
let currentSlide = 0
|
let currentSlide = $state(0)
|
||||||
let carouselDots = []
|
let carouselDots = $state([])
|
||||||
|
|
||||||
const { shopProducts }: any = getContext('shop')
|
const { shopProducts }: any = getContext('shop')
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@
|
|||||||
const handleResize = debounce(initCarousel, 200)
|
const handleResize = debounce(initCarousel, 200)
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
if (innerWidth < 1200) {
|
if (innerWidth < 1200) {
|
||||||
initCarousel()
|
initCarousel()
|
||||||
}
|
}
|
||||||
@@ -88,7 +92,7 @@
|
|||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
bind:innerWidth
|
bind:innerWidth
|
||||||
on:resize={handleResize}
|
onresize={handleResize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="shop-page__posters grid">
|
<section class="shop-page__posters grid">
|
||||||
@@ -110,7 +114,7 @@
|
|||||||
<ul class="set__dots">
|
<ul class="set__dots">
|
||||||
{#each carouselDots as _, index}
|
{#each carouselDots as _, index}
|
||||||
<li class:is-active={index === currentSlide}>
|
<li class:is-active={index === currentSlide}>
|
||||||
<button on:click={() => goToSlide(index)} aria-label="Go to slide #{index + 1}" />
|
<button onclick={() => goToSlide(index)} aria-label="Go to slide #{index + 1}"></button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
import { cartOpen } from '$utils/stores/shop'
|
import { cartOpen } from '$utils/stores/shop'
|
||||||
@@ -15,17 +15,18 @@
|
|||||||
import ButtonCart from '$components/atoms/ButtonCart/ButtonCart.svelte'
|
import ButtonCart from '$components/atoms/ButtonCart/ButtonCart.svelte'
|
||||||
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher/ShopLocationSwitcher.svelte'
|
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher/ShopLocationSwitcher.svelte'
|
||||||
|
|
||||||
export let product: any = undefined
|
let { product }: { product?: any } = $props()
|
||||||
|
|
||||||
const { shop, shopLocations }: any = getContext('shop')
|
const { shop, shopLocations }: any = getContext('shop')
|
||||||
|
|
||||||
let innerWidth: number
|
let innerWidth = $state<number>()
|
||||||
let navObserver: IntersectionObserver
|
let navObserver: IntersectionObserver
|
||||||
let introEl: HTMLElement, navChooseEl: HTMLElement
|
let introEl = $state<HTMLElement>()
|
||||||
let scrolledPastIntro = false
|
let navChooseEl = $state<HTMLElement>()
|
||||||
|
let scrolledPastIntro = $state(false)
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
// Reveal the nav past the Intro
|
// Reveal the nav past the Intro
|
||||||
navObserver = new IntersectionObserver(entries => {
|
navObserver = new IntersectionObserver(entries => {
|
||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
@@ -111,7 +112,7 @@
|
|||||||
<section class="shop-banner" bind:this={introEl}>
|
<section class="shop-banner" bind:this={introEl}>
|
||||||
<div class="top container">
|
<div class="top container">
|
||||||
<a href="/" class="back" data-sveltekit-noscroll>
|
<a href="/" class="back" data-sveltekit-noscroll>
|
||||||
<svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="5" height="8" viewBox="0 0 5 8" fill="none">
|
||||||
<path d="M4 1 1 4l3 3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M4 1 1 4l3 3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Back to Houses Of</span>
|
<span>Back to Houses Of</span>
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
<ul bind:this={navChooseEl} data-sveltekit-noscroll>
|
<ul bind:this={navChooseEl} data-sveltekit-noscroll>
|
||||||
{#each shopLocations as { name, slug }}
|
{#each shopLocations as { name, slug }}
|
||||||
<li class:is-active={product && slug === product.location.slug}>
|
<li class:is-active={product && slug === product.location.slug}>
|
||||||
<a href="/shop/poster-{slug}" on:click={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}>
|
<a href="/shop/poster-{slug}" onclick={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}>
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -155,7 +156,8 @@
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<nav class="shop-quicknav"
|
<nav
|
||||||
|
class="shop-quicknav"
|
||||||
class:is-visible={scrolledPastIntro}
|
class:is-visible={scrolledPastIntro}
|
||||||
class:is-overlaid={$cartOpen}
|
class:is-overlaid={$cartOpen}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
// Components
|
// Components
|
||||||
import Button from '$components/atoms/Button/Button.svelte'
|
import Button from '$components/atoms/Button/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
@@ -17,13 +17,25 @@
|
|||||||
// Return name only
|
// Return name only
|
||||||
.map((loc: Location) => loc.name)
|
.map((loc: Location) => loc.name)
|
||||||
|
|
||||||
export let images: any[] = shop.module_images
|
let {
|
||||||
export let title: string = shop.module_title
|
images = shop.module_images,
|
||||||
export let text: string = shop.module_text
|
title = shop.module_title,
|
||||||
export let textBottom: string = undefined
|
text = shop.module_text,
|
||||||
export let buttonText = 'Shop'
|
textBottom,
|
||||||
export let url = '/shop'
|
buttonText = 'Shop',
|
||||||
export let enabled = true
|
url = '/shop',
|
||||||
|
enabled = true,
|
||||||
|
loopDuration = 3000,
|
||||||
|
}: {
|
||||||
|
images?: any[]
|
||||||
|
title?: string
|
||||||
|
text?: string
|
||||||
|
textBottom?: string
|
||||||
|
buttonText?: string
|
||||||
|
url?: string
|
||||||
|
enabled?: boolean
|
||||||
|
loopDuration?: number
|
||||||
|
} = $props()
|
||||||
|
|
||||||
if (textBottom !== null) {
|
if (textBottom !== null) {
|
||||||
textBottom = `Posters available for ${locationsWithPoster.join(', ').replace(/,(?!.*,)/gmi, ' and')}.`
|
textBottom = `Posters available for ${locationsWithPoster.join(', ').replace(/,(?!.*,)/gmi, ' and')}.`
|
||||||
@@ -37,15 +49,14 @@
|
|||||||
|
|
||||||
// Image rotation
|
// Image rotation
|
||||||
let imagesLoop: ReturnType<typeof setTimeout>
|
let imagesLoop: ReturnType<typeof setTimeout>
|
||||||
let currentImageIndex = 0
|
let currentImageIndex = $state(0)
|
||||||
const loopDuration = 3000
|
|
||||||
|
|
||||||
const incrementCurrentImageIndex = () => {
|
const incrementCurrentImageIndex = () => {
|
||||||
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
|
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
|
||||||
imagesLoop = setTimeout(() => requestAnimationFrame(incrementCurrentImageIndex), loopDuration)
|
imagesLoop = setTimeout(() => requestAnimationFrame(incrementCurrentImageIndex), loopDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
if (images.length > 1) {
|
if (images.length > 1) {
|
||||||
imagesLoop = setTimeout(incrementCurrentImageIndex, loopDuration)
|
imagesLoop = setTimeout(incrementCurrentImageIndex, loopDuration)
|
||||||
}
|
}
|
||||||
@@ -63,20 +74,20 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="shop__images">
|
<div class="shop__images">
|
||||||
{#if images}
|
{#if images}
|
||||||
<a href={enabled ? url : undefined} title="Visit our shop" data-sveltekit-noscroll>
|
<a href={enabled ? url : undefined} title="Visit our shop" data-sveltekit-noscroll>
|
||||||
{#each images as { directus_files_id: { id, title } }, index}
|
{#each images as { directus_files_id: { id, title } }, index}
|
||||||
<Image
|
<Image
|
||||||
class={index === currentImageIndex ? 'is-visible' : null}
|
class={index === currentImageIndex ? 'is-visible' : null}
|
||||||
{id}
|
{id}
|
||||||
sizeKey="square"
|
sizeKey="square"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400, height: 400 },
|
small: { width: 400, height: 400 },
|
||||||
large: { width: 800, height: 800 },
|
large: { width: 800, height: 800 },
|
||||||
}}
|
}}
|
||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,14 @@
|
|||||||
import Cart from '$components/organisms/Cart/Cart.svelte'
|
import Cart from '$components/organisms/Cart/Cart.svelte'
|
||||||
import NotificationCart from '$components/molecules/NotificationCart/NotificationCart.svelte'
|
import NotificationCart from '$components/molecules/NotificationCart/NotificationCart.svelte'
|
||||||
|
|
||||||
export let data
|
let {
|
||||||
|
data,
|
||||||
|
children,
|
||||||
|
} = $props()
|
||||||
|
|
||||||
const { shop, locations, posters, shopProducts, settings } = data
|
const { shop, locations, posters, shopProducts, settings } = data
|
||||||
|
|
||||||
let scrollY: number
|
let scrollY = $state<number>()
|
||||||
|
|
||||||
// Locations with an existing poster product
|
// Locations with an existing poster product
|
||||||
const shopLocations = locations.filter(({ slug }: any) => {
|
const shopLocations = locations.filter(({ slug }: any) => {
|
||||||
@@ -38,12 +41,8 @@
|
|||||||
|
|
||||||
<div class="notifications" class:is-top={scrollY <= 100}>
|
<div class="notifications" class:is-top={scrollY <= 100}>
|
||||||
{#each $cartNotifications as { id, title, name, image } (id)}
|
{#each $cartNotifications as { id, title, name, image } (id)}
|
||||||
<NotificationCart
|
<NotificationCart {title} {name} {image} />
|
||||||
title={title}
|
|
||||||
name={name}
|
|
||||||
image={image}
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot />
|
{@render children()}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import ShopHeader from '$components/organisms/ShopBanner/ShopBanner.svelte'
|
import ShopHeader from '$components/organisms/ShopBanner/ShopBanner.svelte'
|
||||||
import PosterLayout from '$components/layouts/PosterLayout/PosterLayout.svelte'
|
import PosterLayout from '$components/layouts/PosterLayout/PosterLayout.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
const { product, shopProduct }: { product: any, shopProduct: any } = data
|
const { product, shopProduct }: { product: any, shopProduct: any } = data
|
||||||
const { posters, settings }: any = getContext('shop')
|
const { posters, settings }: any = getContext('shop')
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import PostersGrid from '$components/organisms/PostersGrid/PostersGrid.svelte'
|
import PostersGrid from '$components/organisms/PostersGrid/PostersGrid.svelte'
|
||||||
import PosterLayout from '$components/layouts/PosterLayout/PosterLayout.svelte'
|
import PosterLayout from '$components/layouts/PosterLayout/PosterLayout.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
const { posters }: any = getContext('shop')
|
const { posters }: any = getContext('shop')
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page, navigating } from '$app/stores'
|
import { page, navigating } from '$app/stores'
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
@@ -24,34 +23,33 @@
|
|||||||
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
||||||
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
|
let photos = $state<any[]>(data.photos)
|
||||||
|
let totalPhotos = $state(data.totalPhotos)
|
||||||
|
|
||||||
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
|
||||||
$: ({ photos, totalPhotos } = data)
|
|
||||||
const { location, product = undefined }: { location: any, totalPhotos: number, product: any } = data
|
const { location, product = undefined }: { location: any, totalPhotos: number, product: any } = data
|
||||||
const { params } = $page
|
const { params } = $page
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
let introEl: HTMLElement
|
let introEl = $state<HTMLElement>()
|
||||||
let photosListEl: HTMLElement
|
let photosListEl = $state<HTMLElement>()
|
||||||
let scrollY: number
|
let scrollY = $state<number>()
|
||||||
let observerPhotos: IntersectionObserver
|
let observerPhotos: IntersectionObserver
|
||||||
let mutationPhotos: MutationObserver
|
let mutationPhotos: MutationObserver
|
||||||
let currentPage = 1
|
let currentPage = $state(1)
|
||||||
let ended: boolean
|
let currentPhotosAmount = $derived(photos.length)
|
||||||
let currentPhotosAmount: number
|
let heroOffsetY = $state(0)
|
||||||
let heroOffsetY = 0
|
|
||||||
|
|
||||||
$: latestPhoto = photos[0]
|
const ended = $derived(currentPhotosAmount === totalPhotos)
|
||||||
$: currentPhotosAmount = photos.length
|
const latestPhoto = $derived(photos[0])
|
||||||
$: ended = currentPhotosAmount === totalPhotos
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load photos
|
* Load photos
|
||||||
*/
|
*/
|
||||||
// Load more photos from CTA
|
/** Load more photos from CTA */
|
||||||
const loadMorePhotos = async () => {
|
const loadMorePhotos = async () => {
|
||||||
// Append more photos from API
|
// Append more photos from API
|
||||||
const newPhotos: any = await loadPhotos(currentPage + 1)
|
const newPhotos: any = await loadPhotos(currentPage + 1)
|
||||||
@@ -64,9 +62,6 @@
|
|||||||
// Increment the current page
|
// Increment the current page
|
||||||
currentPage++
|
currentPage++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the currently visible amount of photos
|
|
||||||
currentPhotosAmount += newPhotos.length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,12 +97,14 @@
|
|||||||
/**
|
/**
|
||||||
* Add parallax on illustration when scrolling
|
* Add parallax on illustration when scrolling
|
||||||
*/
|
*/
|
||||||
$: if (scrollY && scrollY < introEl.offsetHeight) {
|
$effect(() => {
|
||||||
heroOffsetY = scrollY * 0.1
|
if (scrollY && scrollY < introEl.offsetHeight) {
|
||||||
}
|
heroOffsetY = scrollY * 0.1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
// Define location's last seen state
|
// Define location's last seen state
|
||||||
$seenLocations = JSON.stringify({
|
$seenLocations = JSON.stringify({
|
||||||
// Add existing values
|
// Add existing values
|
||||||
@@ -264,9 +261,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{#if location.has_poster}
|
{#if location.has_poster}
|
||||||
<Button size="medium" url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
|
<Button size="medium" url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small" />
|
||||||
<!-- <IconEarth /> -->
|
|
||||||
</Button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,7 +304,7 @@
|
|||||||
ended={ended}
|
ended={ended}
|
||||||
current={currentPhotosAmount}
|
current={currentPhotosAmount}
|
||||||
total={totalPhotos}
|
total={totalPhotos}
|
||||||
on:click={() => !ended && loadMorePhotos()}
|
onclick={() => !ended && loadMorePhotos()}
|
||||||
>
|
>
|
||||||
{#if !ended}
|
{#if !ended}
|
||||||
<p class="more">See more photos</p>
|
<p class="more">See more photos</p>
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment'
|
|
||||||
import { page, navigating } from '$app/stores'
|
import { page, navigating } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto, replaceState } from '$app/navigation'
|
||||||
import { onMount, tick } from 'svelte'
|
import { tick } from 'svelte'
|
||||||
import { fade, scale } from 'svelte/transition'
|
import { fade, scale } from 'svelte/transition'
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -24,70 +23,74 @@
|
|||||||
import IconArrow from '$components/atoms/IconArrow.svelte'
|
import IconArrow from '$components/atoms/IconArrow.svelte'
|
||||||
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
let { photos, currentIndex }: { photos: any[], currentIndex: number } = data
|
|
||||||
const { location, countPhotos, limit, offset }: { location: any, countPhotos: number, limit: number, offset: number } = data
|
const { location, countPhotos, limit, offset }: { location: any, countPhotos: number, limit: number, offset: number } = data
|
||||||
|
|
||||||
enum directions { PREV, NEXT }
|
enum directions { PREV, NEXT }
|
||||||
|
|
||||||
let innerWidth: number
|
let innerWidth = $state<number>()
|
||||||
let fullscreenEl: HTMLElement
|
let fullscreenEl = $state<HTMLElement>()
|
||||||
let globalOffset = offset
|
let photos = $state<any[]>(data.photos)
|
||||||
let isLoading = false
|
let currentIndex = $state(data.currentIndex)
|
||||||
let isFullscreen = false
|
let globalOffset = $state(offset)
|
||||||
let hasNext = offset + limit < countPhotos
|
let isLoading = $state(false)
|
||||||
let hasPrev = offset > 0
|
let isFullscreen = $state(false)
|
||||||
|
let hasNext = $state(offset + limit < countPhotos)
|
||||||
|
let hasPrev = $state(offset > 0)
|
||||||
|
|
||||||
// Define if we can navigate depending on loading state, existing photos and index being first or last
|
// Define if we can navigate depending on loading state, existing photos and index being first or last
|
||||||
$: canGoPrev = !isLoading && (hasNext || currentIndex !== photos.length - 1)
|
const canGoPrev = $derived(!isLoading && (hasNext || currentIndex !== photos.length - 1))
|
||||||
$: canGoNext = !isLoading && (hasPrev || currentIndex !== 0)
|
const canGoNext = $derived(!isLoading && (hasPrev || currentIndex !== 0))
|
||||||
|
|
||||||
// Define current photo
|
// Define current photo
|
||||||
$: currentPhoto = photos[currentIndex]
|
const currentPhoto = $derived(photos[currentIndex])
|
||||||
$: currentPhotoIndex = globalOffset + currentIndex + 1
|
const currentPhotoIndex = $derived(globalOffset + currentIndex + 1)
|
||||||
|
|
||||||
// Take 7 photos in the global photos array (5 for current, 1 before first and 1 after last)
|
// Take 7 photos in the global photos array (5 for current, 1 before first and 1 after last)
|
||||||
// Start one index before the current image since the first one will be invisible
|
// Start one index before the current image since the first one will be invisible
|
||||||
$: sliceStart = Math.max(currentIndex - 1, 0)
|
const sliceStart = $derived(Math.max(currentIndex - 1, 0))
|
||||||
$: visiblePhotos = photos.slice(sliceStart, sliceStart + 7)
|
const visiblePhotos = $derived(photos.slice(sliceStart, sliceStart + 7))
|
||||||
|
|
||||||
// Load previous photos
|
$effect(() => {
|
||||||
$: if (browser && currentIndex === 0 && hasPrev) {
|
// Load previous photos
|
||||||
loadPhotos(photos[0].id)
|
if (currentIndex === 0 && hasPrev) {
|
||||||
}
|
loadPhotos(photos[0].id)
|
||||||
// Load next photos
|
}
|
||||||
$: if (browser && currentIndex === photos.length - 5 && hasNext) {
|
|
||||||
loadPhotos(photos[photos.length - 1].id, directions.NEXT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change URL to current photo slug
|
// Load next photos
|
||||||
$: if (browser && currentPhoto) {
|
if (currentIndex === photos.length - 5 && hasNext) {
|
||||||
window.history.replaceState(null, '', $page.url.pathname.replace($page.params.photo, currentPhoto.slug))
|
loadPhotos(photos[photos.length - 1].id, directions.NEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change URL to current photo slug
|
||||||
|
if (currentPhoto) {
|
||||||
|
replaceState('', $page.url.pathname.replace($page.params.photo, currentPhoto.slug))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Define previous URL
|
// Define previous URL
|
||||||
$: previousUrl = $previousPage ? $previousPage : `/${location.country.slug}/${location.slug}`
|
const previousUrl = $derived($previousPage ? $previousPage : `/${location.country.slug}/${location.slug}`)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Photo navigation
|
* Photo navigation
|
||||||
*/
|
*/
|
||||||
// Go to next photo
|
/** Go to next photo */
|
||||||
const goToNext = throttle(() => {
|
const goToNext = throttle(() => {
|
||||||
canGoPrev && currentIndex++
|
canGoPrev && currentIndex++
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
// Go to previous photo
|
/** Go to previous photo */
|
||||||
const goToPrevious = throttle(() => {
|
const goToPrevious = throttle(() => {
|
||||||
canGoNext && (currentIndex = Math.max(currentIndex - 1, 0))
|
canGoNext && (currentIndex = Math.max(currentIndex - 1, 0))
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
// Close viewer and go to previous page
|
/** Close viewer and go to previous page */
|
||||||
const closeViewer = () => {
|
const closeViewer = () => {
|
||||||
goto(previousUrl, { replaceState: false, noScroll: true, keepFocus: true })
|
goto(previousUrl, { replaceState: false, noScroll: true, keepFocus: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable navigation with keyboard
|
/** Enable navigation with keyboard */
|
||||||
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
|
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
|
||||||
if (defaultPrevented) return
|
if (defaultPrevented) return
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -98,7 +101,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable swipe gestures
|
/** Enable swipe gestures */
|
||||||
const handleSwipe = ({ detail }: CustomEvent<string>) => {
|
const handleSwipe = ({ detail }: CustomEvent<string>) => {
|
||||||
// Swipe up and down on mobile/small screens
|
// Swipe up and down on mobile/small screens
|
||||||
if (innerWidth < 992) {
|
if (innerWidth < 992) {
|
||||||
@@ -214,7 +217,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
/**
|
/**
|
||||||
* Animations
|
* Animations
|
||||||
*/
|
*/
|
||||||
@@ -289,14 +292,14 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth on:keydown={handleKeydown} />
|
<svelte:window bind:innerWidth onkeydown={handleKeydown} />
|
||||||
|
|
||||||
{#if currentPhoto}
|
{#if currentPhoto}
|
||||||
<Metas
|
<Metas
|
||||||
title="{currentPhoto.title} - Houses Of {location.name}"
|
title="{currentPhoto.title} - Houses Of {location.name}"
|
||||||
description="Photo of a beautiful home from {location.name}, {location.country.name}"
|
description="Photo of a beautiful home from {location.name}, {location.country.name}"
|
||||||
image={getAssetUrlKey(currentPhoto.image.id, 'share')}
|
image={getAssetUrlKey(currentPhoto.image.id, 'share')}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
@@ -317,10 +320,11 @@
|
|||||||
</ButtonCircle>
|
</ButtonCircle>
|
||||||
|
|
||||||
<div class="photo-page__carousel">
|
<div class="photo-page__carousel">
|
||||||
<div class="photo-page__images"
|
<div
|
||||||
use:swipe
|
use:swipe
|
||||||
on:swipe={handleSwipe}
|
class="photo-page__images"
|
||||||
on:tap={toggleFullscreen}
|
onswipe={handleSwipe}
|
||||||
|
ontap={toggleFullscreen}
|
||||||
>
|
>
|
||||||
{#each visiblePhotos as { id, image, title }, index (id)}
|
{#each visiblePhotos as { id, image, title }, index (id)}
|
||||||
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
|
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
|
||||||
@@ -340,10 +344,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<div class="photo-page__controls">
|
<div class="photo-page__controls">
|
||||||
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} on:click={goToPrevious}>
|
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} onclick={goToPrevious}>
|
||||||
<IconArrow color="pink" flip={true} />
|
<IconArrow color="pink" flip={true} />
|
||||||
</ButtonCircle>
|
</ButtonCircle>
|
||||||
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} on:click={goToNext}>
|
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} onclick={goToNext}>
|
||||||
<IconArrow color="pink" />
|
<IconArrow color="pink" />
|
||||||
</ButtonCircle>
|
</ButtonCircle>
|
||||||
</div>
|
</div>
|
||||||
@@ -378,10 +382,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isFullscreen}
|
{#if isFullscreen}
|
||||||
<div class="photo-page__fullscreen" bind:this={fullscreenEl}
|
<div
|
||||||
on:click={toggleFullscreen} on:keydown
|
bind:this={fullscreenEl}
|
||||||
|
class="photo-page__fullscreen"
|
||||||
|
onclick={toggleFullscreen}
|
||||||
in:fade={{ easing: quartOut, duration: 1000 }}
|
in:fade={{ easing: quartOut, duration: 1000 }}
|
||||||
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}
|
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div class="inner" transition:scale={{ easing: quartOut, start: 1.1, duration: 1000 }}>
|
<div class="inner" transition:scale={{ easing: quartOut, start: 1.1, duration: 1000 }}>
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import { onMount, afterUpdate } from 'svelte'
|
|
||||||
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, stagger, timeline } from 'motion'
|
||||||
@@ -22,23 +21,30 @@
|
|||||||
import ProcessStep from '$components/molecules/ProcessStep/ProcessStep.svelte'
|
import ProcessStep from '$components/molecules/ProcessStep/ProcessStep.svelte'
|
||||||
import Banner from '$components/organisms/Banner/Banner.svelte'
|
import Banner from '$components/organisms/Banner/Banner.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
const { about, photos } = data
|
|
||||||
|
|
||||||
let scrollY: number, innerWidth: number, innerHeight: number
|
let scrollY = $state<number>()
|
||||||
let photosGridEl: HTMLElement
|
let innerWidth = $state<number>()
|
||||||
let photosGridOffset: number = photosGridEl && photosGridEl.offsetTop
|
let innerHeight = $state<number>()
|
||||||
let currentStep = 0
|
let photosGridEl = $state<HTMLElement>()
|
||||||
let emailCopied: string = null
|
let photosGridOffset = $state<number>()
|
||||||
|
let currentStep = $state(0)
|
||||||
|
let emailCopied = $state<string>()
|
||||||
let emailCopiedTimeout: ReturnType<typeof setTimeout> | number
|
let emailCopiedTimeout: ReturnType<typeof setTimeout> | number
|
||||||
|
|
||||||
$: parallaxPhotos = photosGridEl && map(scrollY, photosGridOffset - innerHeight, photosGridOffset + innerHeight / 1.5, 0, innerHeight * 0.15, true)
|
const parallaxPhotos = $derived(photosGridEl && map(scrollY, photosGridOffset - innerHeight, photosGridOffset + innerHeight / 1.5, 0, innerHeight * 0.15, true))
|
||||||
$: fadedPhotosIndexes = innerWidth > 768
|
const fadedPhotosIndexes = $derived(
|
||||||
? [0, 2, 5, 7, 9, 12, 17, 20, 22, 26, 30, 32, 34]
|
innerWidth > 768
|
||||||
: [1, 4, 5, 7, 11, 14, 17, 20, 24, 27, 30, 33, 34, 36, 40, 43]
|
? [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]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
// Update photos grid top offset
|
||||||
|
photosGridOffset = photosGridEl.offsetTop
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
/**
|
/**
|
||||||
* Animations
|
* Animations
|
||||||
*/
|
*/
|
||||||
@@ -163,20 +169,14 @@
|
|||||||
// Run animation
|
// Run animation
|
||||||
requestAnimationFrame(animation.play)
|
requestAnimationFrame(animation.play)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
// Update photos grid top offset
|
|
||||||
photosGridOffset = photosGridEl.offsetTop
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:scrollY bind:innerWidth bind:innerHeight />
|
<svelte:window bind:scrollY bind:innerWidth bind:innerHeight />
|
||||||
|
|
||||||
<Metas
|
<Metas
|
||||||
title={about.seo_title}
|
title={data.data.about.seo_title}
|
||||||
description={about.seo_description}
|
description={data.about.seo_description}
|
||||||
image={about.seo_image ? getAssetUrlKey(about.seo_image.id, 'share-image') : null}
|
image={data.about.seo_image ? getAssetUrlKey(data.about.seo_image.id, 'share-image') : null}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
@@ -191,13 +191,13 @@
|
|||||||
|
|
||||||
<section class="about__introduction">
|
<section class="about__introduction">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<h2 class="title-small">{about.intro_title}</h2>
|
<h2 class="title-small">{data.about.intro_title}</h2>
|
||||||
<div class="heading text-big">
|
<div class="heading text-big">
|
||||||
{@html about.intro_heading}
|
{@html data.about.intro_heading}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text text-small">
|
<div class="text text-small">
|
||||||
{@html about.intro_text}
|
{@html data.about.intro_text}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -207,8 +207,8 @@
|
|||||||
<figure class="first-photo">
|
<figure class="first-photo">
|
||||||
<Image
|
<Image
|
||||||
class="picture shadow-box-dark"
|
class="picture shadow-box-dark"
|
||||||
id={about.intro_firstphoto.id}
|
id={data.about.intro_firstphoto.id}
|
||||||
alt={about.intro_firstphoto.title}
|
alt={data.about.intro_firstphoto.title}
|
||||||
sizeKey="photo-list"
|
sizeKey="photo-list"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
@@ -218,29 +218,29 @@
|
|||||||
ratio={1.5}
|
ratio={1.5}
|
||||||
/>
|
/>
|
||||||
<figcaption class="text-info">
|
<figcaption class="text-info">
|
||||||
{about.intro_firstphoto_caption}<br>
|
{data.about.intro_firstphoto_caption}<br>
|
||||||
in
|
in
|
||||||
<a href="/{about.intro_firstlocation.country.slug}/{about.intro_firstlocation.slug}" data-sveltekit-noscroll>
|
<a href="/{data.about.intro_firstlocation.country.slug}/{data.about.intro_firstlocation.slug}" data-sveltekit-noscroll>
|
||||||
<img src={getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small')} width="32" height="32" alt={about.intro_firstlocation.country.flag.title}>
|
<img src={getAssetUrlKey(data.about.intro_firstlocation.country.flag.id, 'square-small')} width="32" height="32" alt={data.about.intro_firstlocation.country.flag.title}>
|
||||||
<span>Naarm Australia (Melbourne)</span>
|
<span>Naarm Australia (Melbourne)</span>
|
||||||
</a>
|
</a>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h2 class="title-small" data-reveal>{about.creation_title}</h2>
|
<h2 class="title-small" data-reveal>{data.about.creation_title}</h2>
|
||||||
<div class="heading text-huge" data-reveal>
|
<div class="heading text-huge" data-reveal>
|
||||||
{@html about.creation_heading}
|
{@html data.about.creation_heading}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text text-small" data-reveal>
|
<div class="text text-small" data-reveal>
|
||||||
{@html about.creation_text}
|
{@html data.about.creation_text}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<figure class="picture portrait-photo" data-reveal-image>
|
<figure class="picture portrait-photo" data-reveal-image>
|
||||||
<Image
|
<Image
|
||||||
class="shadow-box-dark"
|
class="shadow-box-dark"
|
||||||
id={about.creation_portrait.id}
|
id={data.about.creation_portrait.id}
|
||||||
alt={about.creation_portrait.title}
|
alt={data.about.creation_portrait.title}
|
||||||
sizeKey="photo-list"
|
sizeKey="photo-list"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
@@ -250,7 +250,7 @@
|
|||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<span class="portrait-photo__caption text-info">
|
<span class="portrait-photo__caption text-info">
|
||||||
{about.creation_portrait_caption}
|
{data.about.creation_portrait_caption}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -260,8 +260,8 @@
|
|||||||
<figure class="picture" data-reveal-image>
|
<figure class="picture" data-reveal-image>
|
||||||
<Image
|
<Image
|
||||||
class="shadow-box-dark"
|
class="shadow-box-dark"
|
||||||
id={about.present_image.id}
|
id={data.about.present_image.id}
|
||||||
alt={about.present_image.title}
|
alt={data.about.present_image.title}
|
||||||
sizeKey="photo-list"
|
sizeKey="photo-list"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
@@ -272,26 +272,26 @@
|
|||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h2 class="title-small" data-reveal>{about.present_title}</h2>
|
<h2 class="title-small" data-reveal>{data.about.present_title}</h2>
|
||||||
<div class="text text-small" data-reveal>
|
<div class="text text-small" data-reveal>
|
||||||
<p>{about.present_text}</p>
|
<p>{data.about.present_text}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="heading text-big" data-reveal>
|
<div class="heading text-big" data-reveal>
|
||||||
{@html about.present_heading}
|
{@html data.about.present_heading}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="conclusion text-small" data-reveal>
|
<div class="conclusion text-small" data-reveal>
|
||||||
<p>{about.present_conclusion}</p>
|
<p>{data.about.present_conclusion}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if about.image_showcase}
|
{#if data.about.image_showcase}
|
||||||
<div class="about__showcase container grid">
|
<div class="about__showcase container grid">
|
||||||
<Image
|
<Image
|
||||||
id={about.image_showcase.id}
|
id={data.about.image_showcase.id}
|
||||||
alt={about.image_showcase.title}
|
alt={data.about.image_showcase.title}
|
||||||
sizeKey="showcase"
|
sizeKey="showcase"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
@@ -307,15 +307,18 @@
|
|||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<aside>
|
<aside>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<h2 class="title-medium">{about.process_title}</h2>
|
<h2 class="title-medium">{data.about.process_title}</h2>
|
||||||
<p class="text-xsmall">{about.process_subtitle}</p>
|
<p class="text-xsmall">{data.about.process_subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
{#each 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 === currentStep}>
|
||||||
<a href="#step-{index + 1}" class="title-big"
|
<a
|
||||||
on:click|preventDefault={() => {
|
class="title-big"
|
||||||
|
href="#step-{index + 1}"
|
||||||
|
onclick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
currentStep = index
|
currentStep = index
|
||||||
sendEvent('aboutStepSwitch')
|
sendEvent('aboutStepSwitch')
|
||||||
}}
|
}}
|
||||||
@@ -328,7 +331,7 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="steps">
|
<div class="steps">
|
||||||
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
|
{#each data.about.process_steps as { text, image, video_mp4, video_webm }, index}
|
||||||
{#if index === currentStep}
|
{#if index === currentStep}
|
||||||
<ProcessStep
|
<ProcessStep
|
||||||
{index} {text}
|
{index} {text}
|
||||||
@@ -347,8 +350,9 @@
|
|||||||
<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-y="{parallaxPhotos}px">
|
||||||
{#each photos as { image: { id }, title }, index}
|
{#each data.photos as { image: { id }, title }, index}
|
||||||
<AboutGridPhoto class="grid-photo"
|
<AboutGridPhoto
|
||||||
|
class="grid-photo"
|
||||||
{id}
|
{id}
|
||||||
alt={title}
|
alt={title}
|
||||||
disabled={fadedPhotosIndexes.includes(index)}
|
disabled={fadedPhotosIndexes.includes(index)}
|
||||||
@@ -360,9 +364,9 @@
|
|||||||
|
|
||||||
<section class="about__interest container grid">
|
<section class="about__interest container grid">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<h2 class="title-xl">{about.contact_title}</h2>
|
<h2 class="title-xl">{data.about.contact_title}</h2>
|
||||||
<div class="blocks">
|
<div class="blocks">
|
||||||
{#each about.contact_blocks as { title, text, link, button }}
|
{#each data.about.contact_blocks as { title, text, link, button }}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h3 class="text-label">{title}</h3>
|
<h3 class="text-label">{title}</h3>
|
||||||
<div class="text text-normal">
|
<div class="text text-normal">
|
||||||
@@ -370,25 +374,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#if link}
|
{#if link}
|
||||||
{#key emailCopied === link}
|
{#key emailCopied === link}
|
||||||
<div class="wrap"
|
<div class="wrap"
|
||||||
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
|
||||||
on:copied={({ detail }) => {
|
oncopied={(email: string) => {
|
||||||
emailCopied = detail.email
|
emailCopied = email
|
||||||
// Clear timeout and add timeout to hide message
|
// Clear timeout and add timeout to hide message
|
||||||
clearTimeout(emailCopiedTimeout)
|
clearTimeout(emailCopiedTimeout)
|
||||||
emailCopiedTimeout = setTimeout(() => emailCopied = null, 2500)
|
emailCopiedTimeout = setTimeout(() => emailCopied = null, 2500)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if emailCopied !== link}
|
{#if emailCopied !== link}
|
||||||
<Button size="small" url="mailto:{link}" text={button} />
|
<Button size="small" url="mailto:{link}" text={button} />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="clipboard">Email copied in clipboard</span>
|
<span class="clipboard">Email copied in clipboard</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import { DELAY } from '$utils/constants'
|
import { DELAY } from '$utils/constants'
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
@@ -14,11 +13,11 @@
|
|||||||
import Heading from '$components/molecules/Heading/Heading.svelte'
|
import Heading from '$components/molecules/Heading/Heading.svelte'
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe/InteractiveGlobe.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
const { credit } = data
|
const { credit } = data
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
/**
|
/**
|
||||||
* Animations
|
* Animations
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +76,31 @@
|
|||||||
<h2 class="title-small">{title}</h2>
|
<h2 class="title-small">{title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each credits as { name, role, website }}
|
{#each credits as { name, role, website }}
|
||||||
|
<li>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
{#if website}
|
||||||
|
<h3>
|
||||||
|
<a href={website} rel="noopener external" target="_blank" tabindex="0">{name}</a>
|
||||||
|
</h3>
|
||||||
|
{:else}
|
||||||
|
<h3>{name}</h3>
|
||||||
|
{/if}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{role}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="credits__category grid">
|
||||||
|
<h2 class="title-small">Photography</h2>
|
||||||
|
<ul>
|
||||||
|
{#each credit as { name, website, location }}
|
||||||
<li>
|
<li>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
@@ -89,52 +113,27 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{role}
|
<ul data-sveltekit-noscroll>
|
||||||
|
{#each location as loc}
|
||||||
|
{#if loc.location_id}
|
||||||
|
<li>
|
||||||
|
<a href="/{loc.location_id.country.slug}/{loc.location_id.slug}" tabindex="0">
|
||||||
|
<Image
|
||||||
|
id={loc.location_id.country.flag.id}
|
||||||
|
sizeKey="square-small"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
alt="Flag of {loc.location_id.country.slug}"
|
||||||
|
/>
|
||||||
|
<span>{loc.location_id.name}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<div class="credits__category grid">
|
|
||||||
<h2 class="title-small">Photography</h2>
|
|
||||||
<ul>
|
|
||||||
{#each credit as { name, website, location }}
|
|
||||||
<li>
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
{#if website}
|
|
||||||
<h3>
|
|
||||||
<a href={website} rel="noopener external" target="_blank" tabindex="0">{name}</a>
|
|
||||||
</h3>
|
|
||||||
{:else}
|
|
||||||
<h3>{name}</h3>
|
|
||||||
{/if}
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
<ul data-sveltekit-noscroll>
|
|
||||||
{#each location as loc}
|
|
||||||
{#if loc.location_id}
|
|
||||||
<li>
|
|
||||||
<a href="/{loc.location_id.country.slug}/{loc.location_id.slug}" tabindex="0">
|
|
||||||
<Image
|
|
||||||
id={loc.location_id.country.flag.id}
|
|
||||||
sizeKey="square-small"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
alt="Flag of {loc.location_id.country.slug}"
|
|
||||||
/>
|
|
||||||
<span>{loc.location_id.name}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</li>
|
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page, navigating } from '$app/stores'
|
import { page, navigating } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { fly } from 'svelte/transition'
|
import { fly } from 'svelte/transition'
|
||||||
import { quartOut as quartOutSvelte } from 'svelte/easing'
|
import { quartOut as quartOutSvelte } from 'svelte/easing'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import { DELAY } from '$utils/constants'
|
import { DELAY } from '$utils/constants'
|
||||||
import { map, lerp } from 'utils/math'
|
import { map, lerp } from 'utils/math'
|
||||||
import { throttle } from 'utils/actions'
|
|
||||||
import { getAssetUrlKey } from '$utils/api'
|
import { getAssetUrlKey } from '$utils/api'
|
||||||
import { quartOut } from '$animations/easings'
|
import { quartOut } from '$animations/easings'
|
||||||
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_INCREMENT } from '$env/static/public'
|
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_INCREMENT } from '$env/static/public'
|
||||||
@@ -30,64 +29,53 @@
|
|||||||
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
||||||
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
let photos = $state<any[]>(data.photos)
|
||||||
$: ({ photos, totalPhotos } = data)
|
const totalPhotos = $derived<number>(data.totalPhotos)
|
||||||
const { filteredCountryExists, settings }: { filteredCountryExists: boolean, settings: any } = data
|
const { filteredCountryExists, settings }: { filteredCountryExists: boolean, settings: any } = data
|
||||||
const { countries, locations }: any = getContext('global')
|
const { countries, locations }: any = getContext('global')
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
let photosGridEl: HTMLElement
|
let photosGridEl = $state<HTMLElement>()
|
||||||
let observerPhotos: IntersectionObserver
|
let observerPhotos: IntersectionObserver
|
||||||
let mutationPhotos: MutationObserver
|
let mutationPhotos: MutationObserver
|
||||||
let scrollY: number
|
let scrollY = $state<number>()
|
||||||
let innerWidth: number, innerHeight: number
|
let innerWidth = $state<number>()
|
||||||
|
let innerHeight = $state<number>()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters
|
* Filters
|
||||||
*/
|
*/
|
||||||
const defaultCountry: string = PUBLIC_FILTERS_DEFAULT_COUNTRY
|
const defaultCountry = PUBLIC_FILTERS_DEFAULT_COUNTRY
|
||||||
const defaultSort: string = PUBLIC_FILTERS_DEFAULT_SORT
|
const defaultSort = PUBLIC_FILTERS_DEFAULT_SORT
|
||||||
const urlFiltersParams = new URLSearchParams()
|
const urlFiltersParams = new URLSearchParams()
|
||||||
let filtered: boolean
|
let filterCountry = $state($page.url.searchParams.get('country') || defaultCountry)
|
||||||
let filterCountry = $page.url.searchParams.get('country') || defaultCountry
|
let filterSort = $state($page.url.searchParams.get('sort') || defaultSort)
|
||||||
let filterSort = $page.url.searchParams.get('sort') || defaultSort
|
const filtered = $derived(filterCountry !== defaultCountry || filterSort !== defaultSort)
|
||||||
let countryFlagId: string
|
const latestPhoto = $derived(photos && photos[0])
|
||||||
$: filtered = filterCountry !== defaultCountry || filterSort !== defaultSort
|
const currentCountry = $derived(countries.find((country: any) => country.slug === filterCountry))
|
||||||
$: latestPhoto = photos && photos[0]
|
|
||||||
$: currentCountry = countries.find((country: any) => country.slug === filterCountry)
|
|
||||||
|
|
||||||
// Pages related informations
|
// Pages related informations
|
||||||
let currentPage = 1
|
let currentPage = $state(1)
|
||||||
let ended: boolean
|
const currentPhotosAmount = $derived(photos?.length)
|
||||||
let currentPhotosAmount: number
|
const ended = $derived(currentPhotosAmount === totalPhotos)
|
||||||
$: currentPhotosAmount = photos && photos.length
|
|
||||||
$: ended = currentPhotosAmount === totalPhotos
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container margins
|
* Container margins
|
||||||
*/
|
*/
|
||||||
let scrollProgress: number
|
const viewportScroll = $derived((innerHeight / innerWidth) <= 0.6 ? innerHeight * 1.5 : innerHeight)
|
||||||
let sideMargins: number = innerWidth < 1200 ? 16 : 8
|
const scrollProgress = $derived(map(scrollY, 0, viewportScroll, 0, 1, true))
|
||||||
$: viewportScroll = (innerHeight / innerWidth) <= 0.6 ? innerHeight * 1.5 : innerHeight
|
const sideMargins = $derived(lerp(innerWidth < 1200 ? 16 : 8, 30, scrollProgress))
|
||||||
|
|
||||||
// Define sides margin on scroll
|
|
||||||
const setSidesMargin = throttle(() => {
|
|
||||||
if (window.innerWidth >= 992) {
|
|
||||||
scrollProgress = map(scrollY, 0, viewportScroll, 0, 1, true)
|
|
||||||
sideMargins = lerp(innerWidth < 1200 ? 16 : 8, 30, scrollProgress)
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle URL query params
|
* Handle URL query params
|
||||||
*/
|
*/
|
||||||
$: countryFlagId = currentCountry ? currentCountry.flag.id : undefined
|
const countryFlagId = $derived(currentCountry ? currentCountry.flag.id : undefined)
|
||||||
|
|
||||||
// Update URL filtering params from filter values
|
// Update URL filtering params from filter values
|
||||||
const applyFilters = () => {
|
const applyFilters = () => {
|
||||||
@@ -103,7 +91,7 @@
|
|||||||
* Define small photo size from index
|
* Define small photo size from index
|
||||||
* With different grid patterns depending on window width
|
* With different grid patterns depending on window width
|
||||||
*/
|
*/
|
||||||
$: isSmall = (index: number) => {
|
const isSmall = (index: number) => {
|
||||||
let modulo = index % 5
|
let modulo = index % 5
|
||||||
let notOn = [0]
|
let notOn = [0]
|
||||||
|
|
||||||
@@ -122,21 +110,21 @@
|
|||||||
/**
|
/**
|
||||||
* Filters change events
|
* Filters change events
|
||||||
*/
|
*/
|
||||||
// Country select
|
/** Country select */
|
||||||
const handleCountryChange = ({ detail: value }) => {
|
const handleCountryChange = ({ detail: value }) => {
|
||||||
filterCountry = value === defaultCountry ? defaultCountry : value
|
filterCountry = value === defaultCountry ? defaultCountry : value
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
applyFilters()
|
applyFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort select
|
/** Sort select */
|
||||||
const handleSortChange = ({ detail: value }) => {
|
const handleSortChange = ({ detail: value }) => {
|
||||||
filterSort = value === defaultSort ? defaultSort : value
|
filterSort = value === defaultSort ? defaultSort : value
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
applyFilters()
|
applyFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset filters
|
/** Reset filters */
|
||||||
const resetFiltered = () => {
|
const resetFiltered = () => {
|
||||||
filterCountry = defaultCountry
|
filterCountry = defaultCountry
|
||||||
filterSort = defaultSort
|
filterSort = defaultSort
|
||||||
@@ -148,7 +136,7 @@
|
|||||||
/**
|
/**
|
||||||
* Load photos
|
* Load photos
|
||||||
*/
|
*/
|
||||||
// [function] Load photos helper
|
/** 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',
|
||||||
@@ -194,7 +182,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load more photos from CTA
|
/** Load more photos from CTA */
|
||||||
const loadMorePhotos = async () => {
|
const loadMorePhotos = async () => {
|
||||||
// Append more photos from API including options and page
|
// Append more photos from API including options and page
|
||||||
const newPhotos: any = await loadPhotos(currentPage + 1)
|
const newPhotos: any = await loadPhotos(currentPage + 1)
|
||||||
@@ -207,14 +195,11 @@
|
|||||||
// Increment the current page
|
// Increment the current page
|
||||||
currentPage++
|
currentPage++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the currently visible amount of photos
|
|
||||||
currentPhotosAmount += newPhotos.length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
/**
|
/**
|
||||||
* Observers
|
* Observers
|
||||||
*/
|
*/
|
||||||
@@ -290,15 +275,12 @@
|
|||||||
image={getAssetUrlKey(settings.seo_image_photos.id, 'share-image')}
|
image={getAssetUrlKey(settings.seo_image_photos.id, 'share-image')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window bind:scrollY bind:innerWidth bind:innerHeight />
|
||||||
bind:scrollY bind:innerWidth bind:innerHeight
|
|
||||||
on:scroll={setSidesMargin}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<main class="photos-page">
|
<main class="photos-page">
|
||||||
<section class="photos-page__intro">
|
<section class="photos-page__intro">
|
||||||
<ScrollingTitle tag="h1" text="Houses">
|
<ScrollingTitle tag="h1" label="Houses">
|
||||||
<SplitText text="Houses" mode="chars" />
|
<SplitText text="Houses" mode="chars" />
|
||||||
</ScrollingTitle>
|
</ScrollingTitle>
|
||||||
|
|
||||||
@@ -324,7 +306,7 @@
|
|||||||
selected: filterCountry === slug,
|
selected: filterCountry === slug,
|
||||||
}))
|
}))
|
||||||
]}
|
]}
|
||||||
on:change={handleCountryChange}
|
onchange={handleCountryChange}
|
||||||
value={filterCountry}
|
value={filterCountry}
|
||||||
>
|
>
|
||||||
{#if countryFlagId}
|
{#if countryFlagId}
|
||||||
@@ -356,7 +338,7 @@
|
|||||||
selected: filterSort === 'oldest'
|
selected: filterSort === 'oldest'
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
on:change={handleSortChange}
|
onchange={handleSortChange}
|
||||||
value={filterSort}
|
value={filterSort}
|
||||||
>
|
>
|
||||||
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-label="Sort icon">
|
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-label="Sort icon">
|
||||||
@@ -368,8 +350,9 @@
|
|||||||
|
|
||||||
<div class="filters__actions">
|
<div class="filters__actions">
|
||||||
{#if filtered}
|
{#if filtered}
|
||||||
<button class="reset button-link"
|
<button
|
||||||
on:click={resetFiltered}
|
class="reset button-link"
|
||||||
|
onclick={resetFiltered}
|
||||||
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
|
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
@@ -422,7 +405,7 @@
|
|||||||
size="large"
|
size="large"
|
||||||
color="beige"
|
color="beige"
|
||||||
text={!ended ? 'See more photos' : "You've seen it all!"}
|
text={!ended ? 'See more photos' : "You've seen it all!"}
|
||||||
on:click={loadMorePhotos}
|
onclick={loadMorePhotos}
|
||||||
disabled={ended}
|
disabled={ended}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import { DELAY } from '$utils/constants'
|
import { DELAY } from '$utils/constants'
|
||||||
import { quartOut } from '$animations/easings'
|
import { quartOut } from '$animations/easings'
|
||||||
@@ -15,13 +14,12 @@
|
|||||||
import NewsletterIssue from '$components/molecules/NewsletterIssue/NewsletterIssue.svelte'
|
import NewsletterIssue from '$components/molecules/NewsletterIssue/NewsletterIssue.svelte'
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe/InteractiveGlobe.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
const { issues } = data
|
const latestIssue = $derived(data.issues[0])
|
||||||
const latestIssue = issues[0]
|
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
/**
|
/**
|
||||||
* Animations
|
* Animations
|
||||||
*/
|
*/
|
||||||
@@ -79,10 +77,10 @@
|
|||||||
<NewsletterIssue size="large" date={latestIssue.date_sent} {...latestIssue} />
|
<NewsletterIssue size="large" date={latestIssue.date_sent} {...latestIssue} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if issues.length > 1}
|
{#if data.issues.length > 1}
|
||||||
<h2 class="title-small">Past Issues</h2>
|
<h2 class="title-small">Past Issues</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each issues.slice(1) as { issue, title, date_sent: date, link, thumbnail }}
|
{#each data.issues.slice(1) as { issue, title, date_sent: date, link, thumbnail }}
|
||||||
<li class="issue-container">
|
<li class="issue-container">
|
||||||
<NewsletterIssue {issue} {title} {link} {thumbnail} {date} />
|
<NewsletterIssue {issue} {title} {link} {thumbnail} {date} />
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import Heading from '$components/molecules/Heading/Heading.svelte'
|
import Heading from '$components/molecules/Heading/Heading.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
const { legal } = data
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
</script>
|
</script>
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
|
|
||||||
<section class="terms__categories">
|
<section class="terms__categories">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
{#each legal.terms as { title, text }, index}
|
{#each data.legal.terms as { title, text }, index}
|
||||||
<article class="terms__section grid">
|
<article class="terms__section grid">
|
||||||
<h2 class="title-small">{index + 1}. {title}</h2>
|
<h2 class="title-small">{index + 1}. {title}</h2>
|
||||||
<div class="text text-info">
|
<div class="text text-info">
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p class="text-info">
|
<p class="text-info">
|
||||||
Updated: <time datetime={dayjs(legal.date_updated).format('YYYY-MM-DD')}>{dayjs().to(dayjs(legal.date_updated))}</time>
|
Updated: <time datetime={dayjs(data.legal.date_updated).format('YYYY-MM-DD')}>{dayjs().to(dayjs(data.legal.date_updated))}</time>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { beforeNavigate, afterNavigate } from '$app/navigation'
|
import { beforeNavigate, afterNavigate } from '$app/navigation'
|
||||||
import { PUBLIC_ANALYTICS_DOMAIN } from '$env/static/public'
|
import { PUBLIC_ANALYTICS_DOMAIN } from '$env/static/public'
|
||||||
import { setContext, onMount } from 'svelte'
|
import { setContext } from 'svelte'
|
||||||
import { fade } from 'svelte/transition'
|
import { fade } from 'svelte/transition'
|
||||||
import { DELAY, DURATION } from '$utils/constants'
|
import { DELAY, DURATION } from '$utils/constants'
|
||||||
import { pageLoading, previousPage } from '$utils/stores'
|
import { pageLoading, previousPage } from '$utils/stores'
|
||||||
@@ -35,10 +35,9 @@
|
|||||||
import Toast from '$components/molecules/Toast/Toast.svelte'
|
import Toast from '$components/molecules/Toast/Toast.svelte'
|
||||||
import Footer from '$components/organisms/Footer/Footer.svelte'
|
import Footer from '$components/organisms/Footer/Footer.svelte'
|
||||||
|
|
||||||
export let data
|
let { data, children } = $props()
|
||||||
|
|
||||||
let innerHeight: number
|
let innerHeight = $state<number>()
|
||||||
$: innerHeight && document.body.style.setProperty('--vh', `${innerHeight}px`)
|
|
||||||
|
|
||||||
// Fonts to preload
|
// Fonts to preload
|
||||||
const fonts = [
|
const fonts = [
|
||||||
@@ -77,17 +76,18 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: if (browser) {
|
|
||||||
|
$effect(() => {
|
||||||
|
// Set viewport height
|
||||||
|
innerHeight && document.body.style.setProperty('--vh', `${innerHeight}px`)
|
||||||
|
|
||||||
|
// Avoid FOUC
|
||||||
|
document.body.style.opacity = '1'
|
||||||
|
|
||||||
// Define page loading
|
// Define page loading
|
||||||
document.body.classList.toggle('is-loading', $pageLoading)
|
document.body.classList.toggle('is-loading', $pageLoading)
|
||||||
// Block scroll on certain conditions
|
// Block scroll on certain conditions
|
||||||
// document.body.classList.toggle('block-scroll', condition)
|
// document.body.classList.toggle('block-scroll', condition)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Avoid FOUC
|
|
||||||
document.body.style.opacity = '1'
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
in:fade={{ duration: DURATION.PAGE_IN, delay: DELAY.PAGE_LOADING }}
|
in:fade={{ duration: DURATION.PAGE_IN, delay: DELAY.PAGE_LOADING }}
|
||||||
out:fade={{ duration: DURATION.PAGE_OUT }}
|
out:fade={{ duration: DURATION.PAGE_OUT }}
|
||||||
>
|
>
|
||||||
<slot />
|
{@render children()}
|
||||||
|
|
||||||
{#if !$page.params.photo}
|
{#if !$page.params.photo}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { timeline, stagger } from 'motion'
|
import { timeline, stagger } from 'motion'
|
||||||
import { DELAY } from '$utils/constants'
|
import { DELAY } from '$utils/constants'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
@@ -26,15 +26,15 @@
|
|||||||
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
||||||
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
||||||
|
|
||||||
export let data
|
let { data } = $props()
|
||||||
|
|
||||||
const { photos } = data
|
|
||||||
const { settings, locations }: any = getContext('global')
|
const { settings, locations }: any = getContext('global')
|
||||||
|
|
||||||
let scrollY: number, innerHeight: number
|
let scrollY = $state<number>()
|
||||||
|
let innerHeight = $state<number>()
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
/**
|
/**
|
||||||
* Animations
|
* Animations
|
||||||
*/
|
*/
|
||||||
@@ -81,7 +81,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<main class="homepage">
|
<main class="homepage">
|
||||||
<section class="homepage__intro"
|
<section
|
||||||
|
class="homepage__intro"
|
||||||
use:reveal={{
|
use:reveal={{
|
||||||
animation: { opacity: [0, 1] },
|
animation: { opacity: [0, 1] },
|
||||||
options: {
|
options: {
|
||||||
@@ -108,7 +109,7 @@
|
|||||||
size="medium"
|
size="medium"
|
||||||
url="#locations"
|
url="#locations"
|
||||||
text="Explore locations"
|
text="Explore locations"
|
||||||
on:click={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}
|
onclick={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}
|
||||||
>
|
>
|
||||||
<IconEarth animate={true} />
|
<IconEarth animate={true} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -116,7 +117,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="homepage__photos">
|
<section class="homepage__photos">
|
||||||
<Collage {photos} />
|
<Collage photos={data.photos} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="homepage__ctas">
|
<div class="homepage__ctas">
|
||||||
@@ -124,28 +125,13 @@
|
|||||||
|
|
||||||
<ListCTAs>
|
<ListCTAs>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA url="/photos" icon="photos" label="Browse all photos" alt="Photos" />
|
||||||
url="/photos"
|
|
||||||
icon="photos"
|
|
||||||
label="Browse all photos"
|
|
||||||
alt="Photos"
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA url="/shop" icon="bag" label="Shop our products" alt="Shopping bag" />
|
||||||
url="/shop"
|
|
||||||
icon="bag"
|
|
||||||
label="Shop our products"
|
|
||||||
alt="Shopping bag"
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA url="/about" icon="compass" label="Learn about the project" alt="Compass" />
|
||||||
url="/about"
|
|
||||||
icon="compass"
|
|
||||||
label="Learn about the project"
|
|
||||||
alt="Compass"
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
</ListCTAs>
|
</ListCTAs>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ export const mailtoClipboard = (node: HTMLElement) => {
|
|||||||
navigator.clipboard.writeText(emailAddress)
|
navigator.clipboard.writeText(emailAddress)
|
||||||
|
|
||||||
// Send event
|
// Send event
|
||||||
node.dispatchEvent(new CustomEvent('copied', {
|
node.dispatchEvent(new CustomEvent('copied', emailAddress))
|
||||||
detail: { email: emailAddress }
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Record event in analytics
|
// Record event in analytics
|
||||||
sendEvent('emailCopy')
|
sendEvent('emailCopy')
|
||||||
|
|||||||
Reference in New Issue
Block a user