Merge branch 'dev'
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
"start": "directus start"
|
"start": "directus start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"directus": "^10.1.0",
|
"directus": "^10.2.1",
|
||||||
"pg": "^8.10.0"
|
"pg": "^8.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,41 +15,42 @@
|
|||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@studio-freight/lenis": "^1.0.12",
|
"@studio-freight/lenis": "^1.0.14",
|
||||||
"dayjs": "^1.11.7",
|
"classix": "^2.1.32",
|
||||||
|
"dayjs": "^1.11.8",
|
||||||
"embla-carousel": "^7.1.0",
|
"embla-carousel": "^7.1.0",
|
||||||
"focus-visible": "^5.2.0",
|
"focus-visible": "^5.2.0",
|
||||||
"motion": "^10.15.5",
|
"motion": "^10.16.2",
|
||||||
"ogl": "^0.0.117",
|
"ogl": "^0.0.117",
|
||||||
"sanitize.css": "^13.0.0",
|
"sanitize.css": "^13.0.0",
|
||||||
"swell-js": "3.21.6",
|
"swell-js": "3.22.0",
|
||||||
"tweakpane": "^3.1.9",
|
"tweakpane": "^3.1.9",
|
||||||
"utils": "workspace:*"
|
"utils": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^2.0.1",
|
"@sveltejs/adapter-auto": "^2.1.0",
|
||||||
"@sveltejs/adapter-vercel": "^2.4.3",
|
"@sveltejs/adapter-vercel": "^3.0.1",
|
||||||
"@sveltejs/kit": "^1.16.3",
|
"@sveltejs/kit": "^1.20.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
||||||
"@typescript-eslint/parser": "^5.59.5",
|
"@typescript-eslint/parser": "^5.59.9",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"browserslist": "^4.21.5",
|
"browserslist": "^4.21.7",
|
||||||
"config": "workspace:*",
|
"config": "workspace:*",
|
||||||
"cssnano": "^6.0.1",
|
"cssnano": "^6.0.1",
|
||||||
"eslint": "^8.40.0",
|
"eslint": "^8.42.0",
|
||||||
"eslint-plugin-svelte": "^2.28.0",
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.24",
|
||||||
"postcss-focus-visible": "^8.0.2",
|
"postcss-focus-visible": "^8.0.2",
|
||||||
"postcss-normalize": "^10.0.1",
|
"postcss-normalize": "^10.0.1",
|
||||||
"postcss-preset-env": "^8.3.2",
|
"postcss-preset-env": "^8.4.2",
|
||||||
"postcss-sort-media-queries": "^5.1.0",
|
"postcss-sort-media-queries": "^5.2.0",
|
||||||
"sass": "^1.62.1",
|
"sass": "^1.63.3",
|
||||||
"svelte": "^3.59.1",
|
"svelte": "^3.59.1",
|
||||||
"svelte-check": "^3.3.2",
|
"svelte-check": "^3.4.3",
|
||||||
"svelte-preprocess": "^5.0.3",
|
"svelte-preprocess": "^5.0.4",
|
||||||
"tslib": "^2.5.0",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.1.3",
|
||||||
"vite": "^4.3.5"
|
"vite": "^4.3.9"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { page } from '$app/stores'
|
|
||||||
import { afterUpdate } from 'svelte'
|
|
||||||
import { fade } from 'svelte/transition'
|
|
||||||
import { scrollToTop } from 'utils/scroll'
|
|
||||||
import { pageLoading } from '$utils/stores'
|
|
||||||
import { DELAY, DURATION } from '$utils/constants'
|
|
||||||
|
|
||||||
let loadingTimeout: ReturnType<typeof setTimeout> | number = null
|
|
||||||
|
|
||||||
$: doNotScroll = !$page.url.searchParams.get('country') && !$page.url.pathname.includes('/shop/')
|
|
||||||
|
|
||||||
// Hide page loading indicator on page update
|
|
||||||
afterUpdate(() => {
|
|
||||||
clearTimeout(loadingTimeout)
|
|
||||||
loadingTimeout = setTimeout(() => $pageLoading = false, DURATION.PAGE_IN)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="page"
|
|
||||||
in:fade={{ duration: DURATION.PAGE_IN, delay: DELAY.PAGE_LOADING }}
|
|
||||||
out:fade={{ duration: DURATION.PAGE_OUT }}
|
|
||||||
on:outrostart={() => {
|
|
||||||
// Show page loading indicator
|
|
||||||
$pageLoading = true
|
|
||||||
}}
|
|
||||||
on:outroend={() => {
|
|
||||||
// Scroll back to top
|
|
||||||
doNotScroll && requestAnimationFrame(() => scrollToTop())
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
if (browser) {
|
if (browser) {
|
||||||
$smoothScroll = new Lenis({
|
$smoothScroll = new Lenis({
|
||||||
duration: 1.2,
|
duration: 1.2,
|
||||||
easing: (t: number) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)), // https://easings.net/
|
easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // https://easings.net/
|
||||||
smoothWheel: true,
|
smoothWheel: true,
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
import { splitText } from 'utils/text'
|
import { splitText } from 'utils/text'
|
||||||
|
|
||||||
export let text: string
|
export let text: string
|
||||||
@@ -7,7 +8,10 @@
|
|||||||
|
|
||||||
$: split = splitText(text, mode)
|
$: split = splitText(text, mode)
|
||||||
|
|
||||||
const classes = ['text-split', $$props.class].join(' ').trim()
|
$: classes = cx(
|
||||||
|
'text-split',
|
||||||
|
$$props.class,
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if clone}
|
{#if clone}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
import Image from './Image.svelte'
|
import Image from './Image.svelte'
|
||||||
|
|
||||||
export let id: string
|
export let id: string
|
||||||
@@ -8,11 +9,11 @@
|
|||||||
let hovering = false
|
let hovering = false
|
||||||
let timer: ReturnType<typeof setTimeout> | number = null
|
let timer: ReturnType<typeof setTimeout> | number = null
|
||||||
|
|
||||||
$: classes = [
|
$: classes = cx(
|
||||||
hovering ? 'is-hovered' : undefined,
|
hovering ? 'is-hovered' : undefined,
|
||||||
disabled ? 'is-disabled' : undefined,
|
disabled ? 'is-disabled' : undefined,
|
||||||
$$props.class
|
$$props.class
|
||||||
].join(' ').trim()
|
)
|
||||||
|
|
||||||
// Hovering functions
|
// Hovering functions
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
|
|||||||
@@ -3,25 +3,27 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
import SplitText from '$components/SplitText.svelte'
|
import SplitText from '$components/SplitText.svelte'
|
||||||
|
|
||||||
export let tag = 'a'
|
|
||||||
export let text: string
|
export let text: string
|
||||||
export let url: string = undefined
|
export let url: string = undefined
|
||||||
export let color: string = undefined
|
export let color: string = undefined
|
||||||
export let size: string = undefined
|
export let size: 'xsmall' | 'small' | 'medium' | 'large'
|
||||||
export let effect = 'link-3d'
|
export let effect = 'link-3d'
|
||||||
export let disabled: boolean = undefined
|
export let disabled: boolean = undefined
|
||||||
export let slotPosition = 'before'
|
export let slotPosition = 'before'
|
||||||
|
|
||||||
const className = 'button'
|
let tag: 'a' | 'button'
|
||||||
const classes = [
|
$: tag = url ? 'a' : 'button'
|
||||||
className,
|
|
||||||
|
$: classes = cx(
|
||||||
|
'button',
|
||||||
effect ? effect : undefined,
|
effect ? effect : undefined,
|
||||||
...[color, size].map(variant => variant && `${className}--${variant}`),
|
...[color, size].map(variant => variant && `button--${variant}`),
|
||||||
Object.keys($$slots).length !== 0 ? `has-icon-${slotPosition}` : undefined,
|
Object.keys($$slots).length !== 0 ? `has-icon-${slotPosition}` : undefined,
|
||||||
$$props.class
|
$$props.class,
|
||||||
].join(' ').trim()
|
)
|
||||||
|
|
||||||
// Define external links
|
// Define external links
|
||||||
$: isExternal = /^(http|https):\/\//i.test(url)
|
$: isExternal = /^(http|https):\/\//i.test(url)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
|
|
||||||
export let tag = 'button'
|
export let tag = 'button'
|
||||||
export let url: string = undefined
|
export let url: string = undefined
|
||||||
export let color: string = undefined
|
export let color: string = undefined
|
||||||
@@ -13,12 +15,12 @@
|
|||||||
export let label: string = undefined
|
export let label: string = undefined
|
||||||
|
|
||||||
const className = 'button-circle'
|
const className = 'button-circle'
|
||||||
const classes = [
|
$: classes = cx(
|
||||||
className,
|
className,
|
||||||
...[color, size].map(variant => variant && `${className}--${variant}`),
|
...[color, size].map(variant => variant && `${className}--${variant}`),
|
||||||
clone ? 'has-clone' : null,
|
clone ? 'has-clone' : null,
|
||||||
$$props.class
|
$$props.class
|
||||||
].join(' ').trim()
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if tag === 'a'}
|
{#if tag === 'a'}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
|
|
||||||
export let icon: string
|
export let icon: string
|
||||||
export let label: string = undefined
|
export let label: string = undefined
|
||||||
|
|
||||||
const classes = [$$props.class].join(' ').trim()
|
$: classes = cx($$props.class)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class={classes} aria-label={label} width="32" height="32">
|
<svg class={classes} aria-label={label} width="32" height="32">
|
||||||
|
|||||||
@@ -7,9 +7,14 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
|
|
||||||
export let animate = false
|
export let animate = false
|
||||||
|
|
||||||
const classes = ['icon-earth', $$props.class].join(' ').trim()
|
$: classes = cx(
|
||||||
|
'icon-earth',
|
||||||
|
$$props.class,
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import cx from 'classix'
|
||||||
import { map } from 'utils/math'
|
import { map } from 'utils/math'
|
||||||
import reveal from '$animations/reveal'
|
import reveal from '$animations/reveal'
|
||||||
|
|
||||||
@@ -39,11 +40,11 @@
|
|||||||
parallax = isLarger ? map(scrollY, offsetStart, offsetEnd, 0, -toTranslate, true) : 0
|
parallax = isLarger ? map(scrollY, offsetStart, offsetEnd, 0, -toTranslate, true) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = [
|
$: classes = cx(
|
||||||
'scrolling-title',
|
'scrolling-title',
|
||||||
'title-huge',
|
'title-huge',
|
||||||
$$props.class
|
$$props.class
|
||||||
].join(' ').trim()
|
)
|
||||||
|
|
||||||
const revealOptions = animate ? {
|
const revealOptions = animate ? {
|
||||||
children: '.char',
|
children: '.char',
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<dd class="text-info">{shopProduct.name} – {shopProduct.price}€</dd>
|
<dd class="text-info">{shopProduct.name} – {shopProduct.price}€</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<Button
|
<Button
|
||||||
tag="button"
|
size="medium"
|
||||||
text={hasStock ? 'Add to cart' : 'Sold out'}
|
text={hasStock ? 'Add to cart' : 'Sold out'}
|
||||||
color="pinklight"
|
color="pinklight"
|
||||||
disabled={!hasStock}
|
disabled={!hasStock}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
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 { lerp } from 'utils/math'
|
import { lerp } from 'utils/math'
|
||||||
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
||||||
import { seenLocations } from '$utils/stores'
|
import { seenLocations } from '$utils/stores'
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
<div class="location__photos">
|
<div class="location__photos">
|
||||||
{#each location.photos as { image }, index}
|
{#each location.photos as { image }, index}
|
||||||
{#if image}
|
{#if image}
|
||||||
{@const classes = ['location__photo', index === photoIndex ? 'is-visible' : null].join(' ').trim()}
|
{@const classes = cx('location__photo', index === photoIndex && 'is-visible')}
|
||||||
<Image
|
<Image
|
||||||
class={classes}
|
class={classes}
|
||||||
id={image.id}
|
id={image.id}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { cx } from 'classix'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
export let street: string
|
export let street: string
|
||||||
@@ -13,11 +14,11 @@
|
|||||||
export let size: string = undefined
|
export let size: string = undefined
|
||||||
|
|
||||||
const className = 'postcard'
|
const className = 'postcard'
|
||||||
$: classes = [
|
$: classes = cx(
|
||||||
className,
|
className,
|
||||||
...[size].map(variant => variant && `${className}--${variant}`),
|
...[size].map(variant => variant && `${className}--${variant}`),
|
||||||
$$props.class
|
$$props.class
|
||||||
].join(' ').trim()
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={classes}>
|
<div class={classes}>
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
on:click={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
on:click={() => $smoothScroll.scrollTo('#poster', { duration: 2 })}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tag="button"
|
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
text="Add to cart"
|
text="Add to cart"
|
||||||
color="pink"
|
color="pink"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { getContext, tick } from 'svelte'
|
import { getContext, tick } from 'svelte'
|
||||||
|
import { cx } from 'classix'
|
||||||
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
||||||
import { smoothScroll } from '$utils/stores'
|
import { smoothScroll } from '$utils/stores'
|
||||||
|
|
||||||
@@ -12,11 +13,11 @@
|
|||||||
|
|
||||||
const { shopLocations }: any = getContext('shop')
|
const { shopLocations }: any = getContext('shop')
|
||||||
|
|
||||||
const classes = [
|
const classes = cx(
|
||||||
'shop-locationswitcher',
|
'shop-locationswitcher',
|
||||||
isOver && 'is-over',
|
isOver && 'is-over',
|
||||||
$$props.class
|
$$props.class
|
||||||
].join(' ').trim()
|
)
|
||||||
|
|
||||||
|
|
||||||
// Quick location change
|
// Quick location change
|
||||||
|
|||||||
111
apps/website/src/components/molecules/Toast.svelte
Normal file
111
apps/website/src/components/molecules/Toast.svelte
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/molecules/toast";
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { fade, fly } from 'svelte/transition'
|
||||||
|
import { quartOut } from 'svelte/easing'
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
import { cx } from 'classix'
|
||||||
|
// Components
|
||||||
|
import Button from '$components/atoms/Button.svelte'
|
||||||
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
|
||||||
|
export let id: string
|
||||||
|
export let type: 'global' | 'local'
|
||||||
|
export let text: string
|
||||||
|
export let cta: {
|
||||||
|
label: string
|
||||||
|
url: string
|
||||||
|
color: string
|
||||||
|
} = undefined
|
||||||
|
export let images: { id: string, title: string }[] = undefined
|
||||||
|
export let show = false
|
||||||
|
|
||||||
|
$: if (browser) {
|
||||||
|
show = !localStorage.getItem(`toast-${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image rotation
|
||||||
|
let imagesLoop: ReturnType<typeof setTimeout>
|
||||||
|
let currentImageIndex = 0
|
||||||
|
|
||||||
|
const incrementCurrentImageIndex = () => {
|
||||||
|
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
|
||||||
|
imagesLoop = setTimeout(() => requestAnimationFrame(incrementCurrentImageIndex), 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close toast
|
||||||
|
const close = () => {
|
||||||
|
localStorage.setItem(`toast-${id}`, 'closed')
|
||||||
|
show = false
|
||||||
|
}
|
||||||
|
|
||||||
|
$: classes = cx(
|
||||||
|
'toast',
|
||||||
|
`toast--${type}`,
|
||||||
|
'shadow-small',
|
||||||
|
$$props.class,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (images.length > 1) {
|
||||||
|
incrementCurrentImageIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Clear rotating words timeout
|
||||||
|
if (imagesLoop) {
|
||||||
|
clearTimeout(imagesLoop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if show}
|
||||||
|
<div
|
||||||
|
class={classes}
|
||||||
|
in:fly={{ y: '10%', duration: 1200, easing: quartOut }}
|
||||||
|
out:fade={{ duration: 800, easing: quartOut }}
|
||||||
|
>
|
||||||
|
{#if images}
|
||||||
|
<div class="media">
|
||||||
|
<a href={cta.url}>
|
||||||
|
{#each images as { id, title }, index}
|
||||||
|
<Image
|
||||||
|
class={index === currentImageIndex ? 'is-visible' : null}
|
||||||
|
{id}
|
||||||
|
sizeKey="square"
|
||||||
|
sizes={{
|
||||||
|
small: { width: 200, height: 200 },
|
||||||
|
large: { width: 350, height: 350 },
|
||||||
|
}}
|
||||||
|
alt={title}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<p class="text">{@html text}</p>
|
||||||
|
|
||||||
|
{#if cta}
|
||||||
|
<Button
|
||||||
|
size="xsmall"
|
||||||
|
text={cta.label}
|
||||||
|
url={cta.url}
|
||||||
|
color="pink"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="close" on:click={close} title="Close">
|
||||||
|
<svg width="10" height="10">
|
||||||
|
<use xlink:href="#cross" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -132,14 +132,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="cart__total--checkout">
|
<div class="cart__total--checkout">
|
||||||
<p>Free shipping worldwide!</p>
|
<p>Save 10% on your order with the code <strong>WORLDCITIZEN</strong>. Free shipping.</p>
|
||||||
{#if $cartData && $cartAmount > 0 && $cartData.checkout_url}
|
{#if $cartData && $cartAmount > 0 && $cartData.checkout_url}
|
||||||
<div transition:fly={{ y: 8, duration: 600, easing: quartOut }}>
|
<div transition:fly={{ y: 8, duration: 600, easing: quartOut }}>
|
||||||
<Button
|
<Button
|
||||||
|
size="small"
|
||||||
url={$cartData && $cartData.checkout_url}
|
url={$cartData && $cartData.checkout_url}
|
||||||
text="Checkout"
|
text="Checkout"
|
||||||
color="pink"
|
color="pink"
|
||||||
size="small"
|
|
||||||
on:click={() => sendEvent('cartCheckout', { props: { amount: $cartAmount } })}
|
on:click={() => sendEvent('cartCheckout', { props: { amount: $cartAmount } })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
{#each continents as { name, slug }}
|
{#each continents as { name, slug }}
|
||||||
<li class:is-active={currentContinent === slug}>
|
<li class:is-active={currentContinent === slug}>
|
||||||
<Button
|
<Button
|
||||||
tag="button" text={name} size="small"
|
size="small"
|
||||||
|
text={name}
|
||||||
slotPosition="after"
|
slotPosition="after"
|
||||||
class={'is-disabled'}
|
class={'is-disabled'}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
<h2 class="title-medium">{title}</h2>
|
<h2 class="title-medium">{title}</h2>
|
||||||
<p class="text-small">{text}</p>
|
<p class="text-small">{text}</p>
|
||||||
{#if enabled}
|
{#if enabled}
|
||||||
<Button {url} text={buttonText} color="pinklight" />
|
<Button size="medium" {url} text={buttonText} color="pinklight" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if textBottom}
|
{#if textBottom}
|
||||||
<p class="detail">{textBottom}</p>
|
<p class="detail">{textBottom}</p>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
||||||
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
||||||
|
|
||||||
@@ -25,19 +24,17 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="shop-page">
|
||||||
<main class="shop-page">
|
<ShopHeader />
|
||||||
<ShopHeader />
|
|
||||||
|
|
||||||
<section class="shop-page__error">
|
<section class="shop-page__error">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<h2 class="title-big">Uh oh!</h2>
|
<h2 class="title-big">Uh oh!</h2>
|
||||||
<p class="text-medium">{errors[$page.status].message}</p>
|
<p class="text-medium">{errors[$page.status].message}</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<PostersGrid {posters} />
|
<PostersGrid {posters} />
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { LayoutServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { fetchSwell } from '$utils/functions/shopServer'
|
import { fetchSwell } from '$utils/functions/shopServer'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = async () => {
|
||||||
try {
|
try {
|
||||||
// Get content from API
|
// Get content from API
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
@@ -76,4 +75,4 @@ export const load = (async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies LayoutServerLoad
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { setContext } from 'svelte'
|
import { setContext } from 'svelte'
|
||||||
import { cartNotifications } from '$utils/stores/shop'
|
import { cartNotifications } from '$utils/stores/shop'
|
||||||
// Components
|
// Components
|
||||||
import Cart from '$components/organisms/Cart.svelte'
|
import Cart from '$components/organisms/Cart.svelte'
|
||||||
import NotificationCart from '$components/molecules/NotificationCart.svelte'
|
import NotificationCart from '$components/molecules/NotificationCart.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
const { shop, locations, posters, shopProducts, settings } = data
|
const { shop, locations, posters, shopProducts, settings } = data
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { getRandomItem } from 'utils/array'
|
import { getRandomItem } from 'utils/array'
|
||||||
import { fetchSwell } from '$utils/functions/shopServer'
|
import { fetchSwell } from '$utils/functions/shopServer'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Get content from API
|
// Get content from API
|
||||||
const data = await fetchAPI(`query {
|
const data = await fetchAPI(`query {
|
||||||
@@ -54,4 +53,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { getAssetUrlKey } from '$utils/api'
|
import { getAssetUrlKey } from '$utils/api'
|
||||||
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
||||||
// Components
|
// Components
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
||||||
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
||||||
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
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')
|
||||||
@@ -26,15 +24,13 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="shop-page">
|
||||||
<main class="shop-page">
|
<ShopHeader {product} />
|
||||||
<ShopHeader {product} />
|
|
||||||
|
|
||||||
<PosterLayout
|
<PosterLayout
|
||||||
product={product}
|
product={product}
|
||||||
shopProduct={shopProduct}
|
shopProduct={shopProduct}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PostersGrid {posters} />
|
<PostersGrid {posters} />
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { fetchSwell } from '$utils/functions/shopServer'
|
import { fetchSwell } from '$utils/functions/shopServer'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ params, setHeaders }) => {
|
export const load = async ({ params, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Get content from API
|
// Get content from API
|
||||||
const data = await fetchAPI(`query {
|
const data = await fetchAPI(`query {
|
||||||
@@ -51,4 +50,4 @@ export const load = (async ({ params, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(404)
|
throw error(404)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { getAssetUrlKey } from '$utils/api'
|
import { getAssetUrlKey } from '$utils/api'
|
||||||
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
import { shopCurrentProductSlug } from '$utils/stores/shop'
|
||||||
import { capitalizeFirstLetter } from 'utils/string'
|
import { capitalizeFirstLetter } from 'utils/string'
|
||||||
// Components
|
// Components
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
import ShopHeader from '$components/organisms/ShopBanner.svelte'
|
||||||
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
||||||
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
const { posters }: any = getContext('shop')
|
const { posters }: any = getContext('shop')
|
||||||
|
|
||||||
@@ -25,15 +23,13 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="shop-page">
|
||||||
<main class="shop-page">
|
<ShopHeader product={data.product} />
|
||||||
<ShopHeader product={data.product} />
|
|
||||||
|
|
||||||
<PosterLayout
|
<PosterLayout
|
||||||
product={data.product}
|
product={data.product}
|
||||||
shopProduct={data.shopProduct}
|
shopProduct={data.shopProduct}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PostersGrid {posters} />
|
<PostersGrid {posters} />
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { PUBLIC_LIST_AMOUNT } from '$env/static/public'
|
import { PUBLIC_LIST_AMOUNT } from '$env/static/public'
|
||||||
import { fetchAPI, photoFields } from '$utils/api'
|
import { fetchAPI, photoFields } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ params, setHeaders }) => {
|
export const load = async ({ params, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const { location: slug } = params
|
const { location: slug } = params
|
||||||
|
|
||||||
@@ -88,4 +87,4 @@ export const load = (async ({ params, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page, navigating } from '$app/stores'
|
import { page, navigating } from '$app/stores'
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { stagger, timeline } from 'motion'
|
import { stagger, timeline } from 'motion'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -17,7 +16,6 @@
|
|||||||
import { PUBLIC_LIST_INCREMENT } from '$env/static/public'
|
import { PUBLIC_LIST_INCREMENT } from '$env/static/public'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import IconEarth from '$components/atoms/IconEarth.svelte'
|
import IconEarth from '$components/atoms/IconEarth.svelte'
|
||||||
@@ -26,7 +24,7 @@
|
|||||||
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
||||||
import ShopModule from '$components/organisms/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
||||||
$: ({ photos, totalPhotos } = data)
|
$: ({ photos, totalPhotos } = data)
|
||||||
@@ -219,149 +217,147 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="location-page">
|
||||||
<main class="location-page">
|
<section class="location-page__intro grid" bind:this={introEl}>
|
||||||
<section class="location-page__intro grid" bind:this={introEl}>
|
<h1 class="title" class:is-short={location.name.length <= 4}>
|
||||||
<h1 class="title" class:is-short={location.name.length <= 4}>
|
<span class="housesof mask">
|
||||||
<span class="housesof mask">
|
<strong class="word">Houses</strong>
|
||||||
<strong class="word">Houses</strong>
|
<span class="of">of</span>
|
||||||
<span class="of">of</span>
|
</span>
|
||||||
</span>
|
<strong class="city mask">
|
||||||
<strong class="city mask">
|
<span class="word">{location.name}</span>
|
||||||
<span class="word">{location.name}</span>
|
</strong>
|
||||||
</strong>
|
</h1>
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="location-page__description grid">
|
<div class="location-page__description grid">
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<div class="text-medium">
|
<div class="text-medium">
|
||||||
Houses of {location.name} {location.description ?? 'has no description yet'}
|
Houses of {location.name} {location.description ?? 'has no description yet'}
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p class="text-label">
|
<p class="text-label">
|
||||||
Photos by
|
Photos by
|
||||||
{#each location.credits as { credit_id: { name, website } }}
|
{#each location.credits as { credit_id: { name, website } }}
|
||||||
{#if website}
|
{#if website}
|
||||||
<a href={website} target="_blank" rel="noopener external">
|
<a href={website} target="_blank" rel="noopener external">
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
</p>
|
||||||
|
{#if latestPhoto}
|
||||||
|
·
|
||||||
|
<p class="text-label" title={dayjs(latestPhoto.date_created).format('DD/MM/YYYY, hh:mm')}>
|
||||||
|
Updated <time datetime={dayjs(latestPhoto.date_created).format('YYYY-MM-DD')}>
|
||||||
|
{dayjs().to(dayjs(latestPhoto.date_created))}
|
||||||
|
</time>
|
||||||
</p>
|
</p>
|
||||||
{#if latestPhoto}
|
{/if}
|
||||||
·
|
</div>
|
||||||
<p class="text-label" title={dayjs(latestPhoto.date_created).format('DD/MM/YYYY, hh:mm')}>
|
|
||||||
Updated <time datetime={dayjs(latestPhoto.date_created).format('YYYY-MM-DD')}>
|
|
||||||
{dayjs().to(dayjs(latestPhoto.date_created))}
|
|
||||||
</time>
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ctas">
|
<div class="ctas">
|
||||||
<Button url="/locations" text="Change location" class="shadow-small">
|
<Button size="medium" url="/locations" text="Change location" class="shadow-small">
|
||||||
<IconEarth />
|
<IconEarth />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{#if location.has_poster}
|
||||||
|
<Button size="medium" url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
|
||||||
|
<!-- <IconEarth /> -->
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
{#if location.has_poster}
|
|
||||||
<Button url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
|
|
||||||
<!-- <IconEarth /> -->
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if hasIllustration}
|
{#if hasIllustration}
|
||||||
<picture class="location-page__illustration" style:--parallax-y="{illustrationOffsetY}px">
|
<picture class="location-page__illustration" style:--parallax-y="{illustrationOffsetY}px">
|
||||||
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.illustration_desktop_2x.id, 'illustration-desktop-2x')}>
|
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.illustration_desktop_2x.id, 'illustration-desktop-2x')}>
|
||||||
<source media="(min-width: 768px)" srcset={getAssetUrlKey(location.illustration_desktop.id, 'illustration-desktop-1x')}>
|
<source media="(min-width: 768px)" srcset={getAssetUrlKey(location.illustration_desktop.id, 'illustration-desktop-1x')}>
|
||||||
<img
|
<img
|
||||||
src={getAssetUrlKey(location.illustration_mobile.id, 'illustration-mobile')}
|
src={getAssetUrlKey(location.illustration_mobile.id, 'illustration-mobile')}
|
||||||
width={320}
|
width={320}
|
||||||
height={824}
|
height={824}
|
||||||
alt="Illustration for {location.name}"
|
alt="Illustration for {location.name}"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
{/if}
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{#if photos.length}
|
||||||
|
<section class="location-page__houses" bind:this={photosListEl} data-sveltekit-noscroll>
|
||||||
|
{#each photos as { title, image: { id, title: alt, width, height }, slug, city, date_taken }, index}
|
||||||
|
<House
|
||||||
|
{title}
|
||||||
|
photoId={id}
|
||||||
|
photoAlt={alt}
|
||||||
|
url="/{params.country}/{params.location}/{slug}"
|
||||||
|
{city}
|
||||||
|
location={location.name}
|
||||||
|
ratio={width / height}
|
||||||
|
date={date_taken}
|
||||||
|
index="{(totalPhotos - index < 10) ? '0' : ''}{totalPhotos - index}"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if photos.length}
|
<section class="location-page__next container">
|
||||||
<section class="location-page__houses" bind:this={photosListEl} data-sveltekit-noscroll>
|
<Pagination
|
||||||
{#each photos as { title, image: { id, title: alt, width, height }, slug, city, date_taken }, index}
|
ended={ended}
|
||||||
<House
|
current={currentPhotosAmount}
|
||||||
{title}
|
total={totalPhotos}
|
||||||
photoId={id}
|
on:click={() => !ended && loadMorePhotos()}
|
||||||
photoAlt={alt}
|
>
|
||||||
url="/{params.country}/{params.location}/{slug}"
|
{#if !ended}
|
||||||
{city}
|
<p class="more">See more photos</p>
|
||||||
location={location.name}
|
{:else}
|
||||||
ratio={width / height}
|
<p>You've seen it all!</p>
|
||||||
date={date_taken}
|
{/if}
|
||||||
index="{(totalPhotos - index < 10) ? '0' : ''}{totalPhotos - index}"
|
</Pagination>
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="location-page__next container">
|
{#if ended}
|
||||||
<Pagination
|
<div class="grid-modules">
|
||||||
ended={ended}
|
<div class="container grid">
|
||||||
current={currentPhotosAmount}
|
<div class="wrap">
|
||||||
total={totalPhotos}
|
{#if location.has_poster}
|
||||||
on:click={() => !ended && loadMorePhotos()}
|
<ShopModule
|
||||||
>
|
title="Poster available"
|
||||||
{#if !ended}
|
text="Houses of {location.name} is available as a poster on our shop."
|
||||||
<p class="more">See more photos</p>
|
images={product.photos_product}
|
||||||
{:else}
|
textBottom={null}
|
||||||
<p>You've seen it all!</p>
|
buttonText="Buy"
|
||||||
{/if}
|
url="/shop/poster-{location.slug}"
|
||||||
</Pagination>
|
/>
|
||||||
|
{:else}
|
||||||
{#if ended}
|
<ShopModule />
|
||||||
<div class="grid-modules">
|
{/if}
|
||||||
<div class="container grid">
|
<NewsletterModule theme="light" />
|
||||||
<div class="wrap">
|
|
||||||
{#if location.has_poster}
|
|
||||||
<ShopModule
|
|
||||||
title="Poster available"
|
|
||||||
text="Houses of {location.name} is available as a poster on our shop."
|
|
||||||
images={product.photos_product}
|
|
||||||
textBottom={null}
|
|
||||||
buttonText="Buy"
|
|
||||||
url="/shop/poster-{location.slug}"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<ShopModule />
|
|
||||||
{/if}
|
|
||||||
<NewsletterModule theme="light" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if location.acknowledgement}
|
{#if location.acknowledgement}
|
||||||
<div class="acknowledgement">
|
<div class="acknowledgement">
|
||||||
<Image
|
<Image
|
||||||
class="flag"
|
class="flag"
|
||||||
id={location.country.flag.id}
|
id={location.country.flag.id}
|
||||||
sizeKey="square-small"
|
sizeKey="square-small"
|
||||||
width={32} height={32}
|
width={32} height={32}
|
||||||
alt="Flag of {location.country.name}"
|
alt="Flag of {location.country.name}"
|
||||||
/>
|
/>
|
||||||
<p>{location.acknowledgement}</p>
|
<p>{location.acknowledgement}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="location-page__message">
|
<div class="location-page__message">
|
||||||
<p>
|
<p>
|
||||||
No photos available for {location.name}.<br>
|
No photos available for {location.name}.<br>
|
||||||
Come back later!
|
Come back later!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ params, setHeaders }) => {
|
export const load = async ({ params, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Get the first photo ID
|
// Get the first photo ID
|
||||||
const firstPhoto = await fetchAPI(`query {
|
const firstPhoto = await fetchAPI(`query {
|
||||||
@@ -89,4 +88,4 @@ export const load = (async ({ params, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import { browser } from '$app/environment'
|
import { browser } from '$app/environment'
|
||||||
import { page, navigating } from '$app/stores'
|
import { page, navigating } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { onMount, tick } from 'svelte'
|
import { onMount, 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'
|
||||||
@@ -20,13 +19,12 @@
|
|||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import SplitText from '$components/SplitText.svelte'
|
import SplitText from '$components/SplitText.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
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'
|
||||||
import IconArrow from '$components/atoms/IconArrow.svelte'
|
import IconArrow from '$components/atoms/IconArrow.svelte'
|
||||||
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
|
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
let { photos, currentIndex }: { photos: any[], currentIndex: number } = data
|
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
|
||||||
@@ -302,105 +300,103 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="photo-page">
|
||||||
<main class="photo-page">
|
<div class="container grid">
|
||||||
<div class="container grid">
|
<p class="photo-page__notice text-label">Tap for fullscreen</p>
|
||||||
<p class="photo-page__notice text-label">Tap for fullscreen</p>
|
|
||||||
|
|
||||||
<ButtonCircle
|
<ButtonCircle
|
||||||
tag="a"
|
tag="a"
|
||||||
url={previousUrl}
|
url={previousUrl}
|
||||||
color="purple"
|
color="purple"
|
||||||
class="close shadow-box-dark"
|
class="close shadow-box-dark"
|
||||||
label="Close"
|
label="Close"
|
||||||
|
>
|
||||||
|
<svg width="12" height="12">
|
||||||
|
<use xlink:href="#cross">
|
||||||
|
</svg>
|
||||||
|
</ButtonCircle>
|
||||||
|
|
||||||
|
<div class="photo-page__carousel">
|
||||||
|
<div class="photo-page__images"
|
||||||
|
use:swipe
|
||||||
|
on:swipe={handleSwipe}
|
||||||
|
on:tap={toggleFullscreen}
|
||||||
>
|
>
|
||||||
<svg width="12" height="12">
|
{#each visiblePhotos as { id, image, title }, index (id)}
|
||||||
<use xlink:href="#cross">
|
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
|
||||||
</svg>
|
<Image
|
||||||
</ButtonCircle>
|
class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}"
|
||||||
|
id={image.id}
|
||||||
<div class="photo-page__carousel">
|
alt={title}
|
||||||
<div class="photo-page__images"
|
sizeKey="photo-list"
|
||||||
use:swipe
|
sizes={{
|
||||||
on:swipe={handleSwipe}
|
small: { width: 500 },
|
||||||
on:tap={toggleFullscreen}
|
medium: { width: 850 },
|
||||||
>
|
large: { width: 1280 },
|
||||||
{#each visiblePhotos as { id, image, title }, index (id)}
|
}}
|
||||||
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
|
ratio={1.5}
|
||||||
<Image
|
/>
|
||||||
class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}"
|
|
||||||
id={image.id}
|
|
||||||
alt={title}
|
|
||||||
sizeKey="photo-list"
|
|
||||||
sizes={{
|
|
||||||
small: { width: 500 },
|
|
||||||
medium: { width: 850 },
|
|
||||||
large: { width: 1280 },
|
|
||||||
}}
|
|
||||||
ratio={1.5}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<div class="photo-page__controls">
|
|
||||||
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} on:click={goToPrevious}>
|
|
||||||
<IconArrow color="pink" flip={true} />
|
|
||||||
</ButtonCircle>
|
|
||||||
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} on:click={goToNext}>
|
|
||||||
<IconArrow color="pink" />
|
|
||||||
</ButtonCircle>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="photo-page__controls">
|
||||||
<div class="photo-page__index title-index">
|
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} on:click={goToPrevious}>
|
||||||
<SplitText text="{(currentPhotoIndex < 10) ? '0' : ''}{currentPhotoIndex}" mode="chars" />
|
<IconArrow color="pink" flip={true} />
|
||||||
</div>
|
</ButtonCircle>
|
||||||
|
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} on:click={goToNext}>
|
||||||
|
<IconArrow color="pink" />
|
||||||
|
</ButtonCircle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="photo-page__info">
|
|
||||||
<h1 class="title-medium">{currentPhoto.title}</h1>
|
|
||||||
|
|
||||||
<div class="detail text-info">
|
<div class="photo-page__index title-index">
|
||||||
<a href="/{location.country.slug}/{location.slug}" data-sveltekit-noscroll>
|
<SplitText text="{(currentPhotoIndex < 10) ? '0' : ''}{currentPhotoIndex}" mode="chars" />
|
||||||
<Icon class="icon" icon="map-pin" label="Map pin" />
|
</div>
|
||||||
<span>
|
</div>
|
||||||
{#if currentPhoto.city}
|
|
||||||
{currentPhoto.city}, {location.name}, {location.country.name}
|
<div class="photo-page__info">
|
||||||
{:else}
|
<h1 class="title-medium">{currentPhoto.title}</h1>
|
||||||
{location.name}, {location.country.name}
|
|
||||||
{/if}
|
<div class="detail text-info">
|
||||||
</span>
|
<a href="/{location.country.slug}/{location.slug}" data-sveltekit-noscroll>
|
||||||
</a>
|
<Icon class="icon" icon="map-pin" label="Map pin" />
|
||||||
{#if currentPhoto.date_taken}
|
<span>
|
||||||
<span class="sep">·</span>
|
{#if currentPhoto.city}
|
||||||
<time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM YYYY')}</time>
|
{currentPhoto.city}, {location.name}, {location.country.name}
|
||||||
{/if}
|
{:else}
|
||||||
</div>
|
{location.name}, {location.country.name}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{#if currentPhoto.date_taken}
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM YYYY')}</time>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if isFullscreen}
|
{#if isFullscreen}
|
||||||
<div class="photo-page__fullscreen" bind:this={fullscreenEl}
|
<div class="photo-page__fullscreen" bind:this={fullscreenEl}
|
||||||
on:click={toggleFullscreen} on:keydown
|
on:click={toggleFullscreen} on:keydown
|
||||||
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 }}
|
||||||
>
|
>
|
||||||
<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
|
||||||
id={currentPhoto.image.id}
|
id={currentPhoto.image.id}
|
||||||
sizeKey="photo-grid-large"
|
sizeKey="photo-grid-large"
|
||||||
width={1266}
|
width={1266}
|
||||||
height={844}
|
height={844}
|
||||||
alt={currentPhoto.title}
|
alt={currentPhoto.title}
|
||||||
/>
|
/>
|
||||||
<ButtonCircle color="gray-medium" class="close">
|
<ButtonCircle color="gray-medium" class="close">
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="#fff" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="#fff" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.751 0c4.274 0 7.752 3.477 7.752 7.751 0 1.846-.65 3.543-1.73 4.875l3.99 3.991a.81.81 0 1 1-1.146 1.146l-3.99-3.991a7.714 7.714 0 0 1-4.876 1.73C3.477 15.503 0 12.027 0 7.753 0 3.476 3.477 0 7.751 0Zm0 1.62a6.138 6.138 0 0 0-6.13 6.131 6.138 6.138 0 0 0 6.13 6.132 6.138 6.138 0 0 0 6.131-6.132c0-3.38-2.75-6.13-6.13-6.13Zm2.38 5.321a.81.81 0 1 1 0 1.62h-4.76a.81.81 0 1 1 0-1.62h4.76Z" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.751 0c4.274 0 7.752 3.477 7.752 7.751 0 1.846-.65 3.543-1.73 4.875l3.99 3.991a.81.81 0 1 1-1.146 1.146l-3.99-3.991a7.714 7.714 0 0 1-4.876 1.73C3.477 15.503 0 12.027 0 7.753 0 3.476 3.477 0 7.751 0Zm0 1.62a6.138 6.138 0 0 0-6.13 6.131 6.138 6.138 0 0 0 6.13 6.132 6.138 6.138 0 0 0 6.131-6.132c0-3.38-2.75-6.13-6.13-6.13Zm2.38 5.321a.81.81 0 1 1 0 1.62h-4.76a.81.81 0 1 1 0-1.62h4.76Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</ButtonCircle>
|
</ButtonCircle>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</main>
|
{/if}
|
||||||
</PageTransition>
|
</main>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { getRandomItems } from 'utils/array'
|
import { getRandomItems } from 'utils/array'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Get data and total of published photos
|
// Get data and total of published photos
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
@@ -102,4 +101,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
import { onMount, afterUpdate } from 'svelte'
|
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 type { PageData } from './$types'
|
|
||||||
import { animate, inView, stagger, timeline } from 'motion'
|
import { animate, inView, stagger, timeline } from 'motion'
|
||||||
import { mailtoClipboard } from '$utils/functions'
|
import { mailtoClipboard } from '$utils/functions'
|
||||||
import { getAssetUrlKey } from '$utils/api'
|
import { getAssetUrlKey } from '$utils/api'
|
||||||
@@ -17,14 +16,13 @@
|
|||||||
import { quartOut } from '$animations/easings'
|
import { quartOut } from '$animations/easings'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
|
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
|
||||||
import ProcessStep from '$components/molecules/ProcessStep.svelte'
|
import ProcessStep from '$components/molecules/ProcessStep.svelte'
|
||||||
import Banner from '$components/organisms/Banner.svelte'
|
import Banner from '$components/organisms/Banner.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
const { about, photos } = data
|
const { about, photos } = data
|
||||||
|
|
||||||
let scrollY: number, innerWidth: number, innerHeight: number
|
let scrollY: number, innerWidth: number, innerHeight: number
|
||||||
@@ -182,222 +180,220 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="about">
|
||||||
<main class="about">
|
<Banner
|
||||||
<Banner
|
title="About"
|
||||||
title="About"
|
image={{
|
||||||
image={{
|
id: '699b4050-6bbf-4a40-be53-d84aca484f9d',
|
||||||
id: '699b4050-6bbf-4a40-be53-d84aca484f9d',
|
alt: 'Photo caption',
|
||||||
alt: 'Photo caption',
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<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">{about.intro_title}</h2>
|
||||||
<div class="heading text-big">
|
<div class="heading text-big">
|
||||||
{@html about.intro_heading}
|
{@html about.intro_heading}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text text-small">
|
|
||||||
{@html about.intro_text}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="about__creation">
|
<div class="text text-small">
|
||||||
<div class="container grid">
|
{@html about.intro_text}
|
||||||
<figure class="first-photo">
|
|
||||||
<Image
|
|
||||||
class="picture shadow-box-dark"
|
|
||||||
id={about.intro_firstphoto.id}
|
|
||||||
alt={about.intro_firstphoto.title}
|
|
||||||
sizeKey="photo-list"
|
|
||||||
sizes={{
|
|
||||||
small: { width: 400 },
|
|
||||||
medium: { width: 600 },
|
|
||||||
large: { width: 800 },
|
|
||||||
}}
|
|
||||||
ratio={1.5}
|
|
||||||
/>
|
|
||||||
<figcaption class="text-info">
|
|
||||||
{about.intro_firstphoto_caption}<br>
|
|
||||||
in
|
|
||||||
<a href="/{about.intro_firstlocation.country.slug}/{about.intro_firstlocation.slug}" data-sveltekit-noscroll>
|
|
||||||
<img src={getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')} width="32" height="32" alt={about.intro_firstlocation.country.flag.title}>
|
|
||||||
<span>Naarm Australia (Melbourne)</span>
|
|
||||||
</a>
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<h2 class="title-small" data-reveal>{about.creation_title}</h2>
|
|
||||||
<div class="heading text-huge" data-reveal>
|
|
||||||
{@html about.creation_heading}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text text-small" data-reveal>
|
|
||||||
{@html about.creation_text}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<figure class="picture portrait-photo" data-reveal-image>
|
|
||||||
<Image
|
|
||||||
class="shadow-box-dark"
|
|
||||||
id={about.creation_portrait.id}
|
|
||||||
alt={about.creation_portrait.title}
|
|
||||||
sizeKey="photo-list"
|
|
||||||
sizes={{
|
|
||||||
small: { width: 400 },
|
|
||||||
medium: { width: 750 },
|
|
||||||
}}
|
|
||||||
ratio={1.425}
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
<span class="portrait-photo__caption text-info">
|
|
||||||
{about.creation_portrait_caption}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="about__present">
|
<section class="about__creation">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<figure class="picture" data-reveal-image>
|
<figure class="first-photo">
|
||||||
<Image
|
|
||||||
class="shadow-box-dark"
|
|
||||||
id={about.present_image.id}
|
|
||||||
alt={about.present_image.title}
|
|
||||||
sizeKey="photo-list"
|
|
||||||
sizes={{
|
|
||||||
small: { width: 400 },
|
|
||||||
medium: { width: 600 },
|
|
||||||
large: { width: 800 },
|
|
||||||
}}
|
|
||||||
ratio={1.5}
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<h2 class="title-small" data-reveal>{about.present_title}</h2>
|
|
||||||
<div class="text text-small" data-reveal>
|
|
||||||
<p>{about.present_text}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="heading text-big" data-reveal>
|
|
||||||
{@html about.present_heading}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conclusion text-small" data-reveal>
|
|
||||||
<p>{about.present_conclusion}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{#if about.image_showcase}
|
|
||||||
<div class="about__showcase container grid">
|
|
||||||
<Image
|
<Image
|
||||||
id={about.image_showcase.id}
|
class="picture shadow-box-dark"
|
||||||
alt={about.image_showcase.title}
|
id={about.intro_firstphoto.id}
|
||||||
sizeKey="showcase"
|
alt={about.intro_firstphoto.title}
|
||||||
|
sizeKey="photo-list"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 400 },
|
small: { width: 400 },
|
||||||
medium: { width: 1000 },
|
medium: { width: 600 },
|
||||||
large: { width: 1800 },
|
large: { width: 800 },
|
||||||
}}
|
}}
|
||||||
ratio={1.2}
|
ratio={1.5}
|
||||||
/>
|
/>
|
||||||
|
<figcaption class="text-info">
|
||||||
|
{about.intro_firstphoto_caption}<br>
|
||||||
|
in
|
||||||
|
<a href="/{about.intro_firstlocation.country.slug}/{about.intro_firstlocation.slug}" data-sveltekit-noscroll>
|
||||||
|
<img src={getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')} width="32" height="32" alt={about.intro_firstlocation.country.flag.title}>
|
||||||
|
<span>Naarm Australia (Melbourne)</span>
|
||||||
|
</a>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h2 class="title-small" data-reveal>{about.creation_title}</h2>
|
||||||
|
<div class="heading text-huge" data-reveal>
|
||||||
|
{@html about.creation_heading}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<section class="about__process">
|
<div class="text text-small" data-reveal>
|
||||||
<div class="container grid">
|
{@html about.creation_text}
|
||||||
<aside>
|
</div>
|
||||||
<div class="heading">
|
|
||||||
<h2 class="title-medium">{about.process_title}</h2>
|
|
||||||
<p class="text-xsmall">{about.process_subtitle}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ol>
|
<figure class="picture portrait-photo" data-reveal-image>
|
||||||
{#each about.process_steps as { title }, index}
|
<Image
|
||||||
<li class:is-active={index === currentStep}>
|
class="shadow-box-dark"
|
||||||
<a href="#step-{index + 1}" class="title-big"
|
id={about.creation_portrait.id}
|
||||||
on:click|preventDefault={() => {
|
alt={about.creation_portrait.title}
|
||||||
currentStep = index
|
sizeKey="photo-list"
|
||||||
sendEvent('aboutStepSwitch')
|
sizes={{
|
||||||
|
small: { width: 400 },
|
||||||
|
medium: { width: 750 },
|
||||||
|
}}
|
||||||
|
ratio={1.425}
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
<span class="portrait-photo__caption text-info">
|
||||||
|
{about.creation_portrait_caption}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about__present">
|
||||||
|
<div class="container grid">
|
||||||
|
<figure class="picture" data-reveal-image>
|
||||||
|
<Image
|
||||||
|
class="shadow-box-dark"
|
||||||
|
id={about.present_image.id}
|
||||||
|
alt={about.present_image.title}
|
||||||
|
sizeKey="photo-list"
|
||||||
|
sizes={{
|
||||||
|
small: { width: 400 },
|
||||||
|
medium: { width: 600 },
|
||||||
|
large: { width: 800 },
|
||||||
|
}}
|
||||||
|
ratio={1.5}
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h2 class="title-small" data-reveal>{about.present_title}</h2>
|
||||||
|
<div class="text text-small" data-reveal>
|
||||||
|
<p>{about.present_text}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="heading text-big" data-reveal>
|
||||||
|
{@html about.present_heading}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="conclusion text-small" data-reveal>
|
||||||
|
<p>{about.present_conclusion}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{#if about.image_showcase}
|
||||||
|
<div class="about__showcase container grid">
|
||||||
|
<Image
|
||||||
|
id={about.image_showcase.id}
|
||||||
|
alt={about.image_showcase.title}
|
||||||
|
sizeKey="showcase"
|
||||||
|
sizes={{
|
||||||
|
small: { width: 400 },
|
||||||
|
medium: { width: 1000 },
|
||||||
|
large: { width: 1800 },
|
||||||
|
}}
|
||||||
|
ratio={1.2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<section class="about__process">
|
||||||
|
<div class="container grid">
|
||||||
|
<aside>
|
||||||
|
<div class="heading">
|
||||||
|
<h2 class="title-medium">{about.process_title}</h2>
|
||||||
|
<p class="text-xsmall">{about.process_subtitle}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
{#each about.process_steps as { title }, index}
|
||||||
|
<li class:is-active={index === currentStep}>
|
||||||
|
<a href="#step-{index + 1}" class="title-big"
|
||||||
|
on:click|preventDefault={() => {
|
||||||
|
currentStep = index
|
||||||
|
sendEvent('aboutStepSwitch')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{title}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="steps">
|
||||||
|
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
|
||||||
|
{#if index === currentStep}
|
||||||
|
<ProcessStep
|
||||||
|
{index} {text}
|
||||||
|
image={image ?? undefined}
|
||||||
|
video={{
|
||||||
|
mp4: video_mp4?.id,
|
||||||
|
webm: video_webm?.id
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about__photos" bind:this={photosGridEl}>
|
||||||
|
<div class="container-wide">
|
||||||
|
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px">
|
||||||
|
{#each photos as { image: { id }, title }, index}
|
||||||
|
<AboutGridPhoto class="grid-photo"
|
||||||
|
{id}
|
||||||
|
alt={title}
|
||||||
|
disabled={fadedPhotosIndexes.includes(index)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about__interest container grid">
|
||||||
|
<div class="container grid">
|
||||||
|
<h2 class="title-xl">{about.contact_title}</h2>
|
||||||
|
<div class="blocks">
|
||||||
|
{#each about.contact_blocks as { title, text, link, button }}
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="text-label">{title}</h3>
|
||||||
|
<div class="text text-normal">
|
||||||
|
{@html text}
|
||||||
|
</div>
|
||||||
|
<div class="button-container">
|
||||||
|
{#if link}
|
||||||
|
{#key emailCopied === link}
|
||||||
|
<div class="wrap"
|
||||||
|
in:fly={{ y: 4, duration: 325, easing: quartOutSvelte, delay: 250 }}
|
||||||
|
out:fade={{ duration: 250, easing: quartOutSvelte }}
|
||||||
|
use:mailtoClipboard
|
||||||
|
on:copied={({ detail }) => {
|
||||||
|
emailCopied = detail.email
|
||||||
|
// Clear timeout and add timeout to hide message
|
||||||
|
clearTimeout(emailCopiedTimeout)
|
||||||
|
emailCopiedTimeout = setTimeout(() => emailCopied = null, 2500)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{title}</span>
|
{#if emailCopied !== link}
|
||||||
</a>
|
<Button size="small" url="mailto:{link}" text={button} />
|
||||||
</li>
|
{:else}
|
||||||
{/each}
|
<span class="clipboard">Email copied in clipboard</span>
|
||||||
</ol>
|
{/if}
|
||||||
</aside>
|
</div>
|
||||||
|
{/key}
|
||||||
<div class="steps">
|
{/if}
|
||||||
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
|
|
||||||
{#if index === currentStep}
|
|
||||||
<ProcessStep
|
|
||||||
{index} {text}
|
|
||||||
image={image ?? undefined}
|
|
||||||
video={{
|
|
||||||
mp4: video_mp4?.id,
|
|
||||||
webm: video_webm?.id
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="about__photos" bind:this={photosGridEl}>
|
|
||||||
<div class="container-wide">
|
|
||||||
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px">
|
|
||||||
{#each photos as { image: { id }, title }, index}
|
|
||||||
<AboutGridPhoto class="grid-photo"
|
|
||||||
{id}
|
|
||||||
alt={title}
|
|
||||||
disabled={fadedPhotosIndexes.includes(index)}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="about__interest container grid">
|
|
||||||
<div class="container grid">
|
|
||||||
<h2 class="title-xl">{about.contact_title}</h2>
|
|
||||||
<div class="blocks">
|
|
||||||
{#each about.contact_blocks as { title, text, link, button }}
|
|
||||||
<div class="block">
|
|
||||||
<h3 class="text-label">{title}</h3>
|
|
||||||
<div class="text text-normal">
|
|
||||||
{@html text}
|
|
||||||
</div>
|
|
||||||
<div class="button-container">
|
|
||||||
{#if link}
|
|
||||||
{#key emailCopied === link}
|
|
||||||
<div class="wrap"
|
|
||||||
in:fly={{ y: 4, duration: 325, easing: quartOutSvelte, delay: 250 }}
|
|
||||||
out:fade={{ duration: 250, easing: quartOutSvelte }}
|
|
||||||
use:mailtoClipboard
|
|
||||||
on:copied={({ detail }) => {
|
|
||||||
emailCopied = detail.email
|
|
||||||
// Clear timeout and add timeout to hide message
|
|
||||||
clearTimeout(emailCopiedTimeout)
|
|
||||||
emailCopiedTimeout = setTimeout(() => emailCopied = null, 2500)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if emailCopied !== link}
|
|
||||||
<Button size="small" url="mailto:{link}" text={button} />
|
|
||||||
{:else}
|
|
||||||
<span class="clipboard">Email copied in clipboard</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</section>
|
||||||
</PageTransition>
|
</main>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { RequestHandler } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const POST = (async ({ request, setHeaders }) => {
|
export const POST = async ({ request, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const body = await request.text()
|
const body = await request.text()
|
||||||
|
|
||||||
@@ -22,4 +21,4 @@ export const POST = (async ({ request, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies RequestHandler
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
credits {
|
credits {
|
||||||
@@ -41,4 +40,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -4,19 +4,17 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { onMount } from 'svelte'
|
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'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Heading from '$components/molecules/Heading.svelte'
|
import Heading from '$components/molecules/Heading.svelte'
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
const { credit } = data
|
const { credit } = data
|
||||||
|
|
||||||
|
|
||||||
@@ -67,44 +65,18 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="credits">
|
||||||
<main class="credits">
|
<Heading
|
||||||
<Heading
|
text={data.credits.text}
|
||||||
text={data.credits.text}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<section class="credits__list">
|
|
||||||
<div class="grid container">
|
|
||||||
{#each data.credits.list as { title, credits }}
|
|
||||||
<div class="credits__category grid">
|
|
||||||
<h2 class="title-small">{title}</h2>
|
|
||||||
<ul>
|
|
||||||
{#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}
|
|
||||||
|
|
||||||
|
<section class="credits__list">
|
||||||
|
<div class="grid container">
|
||||||
|
{#each data.credits.list as { title, credits }}
|
||||||
<div class="credits__category grid">
|
<div class="credits__category grid">
|
||||||
<h2 class="title-small">Photography</h2>
|
<h2 class="title-small">{title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each credit as { name, website, location }}
|
{#each credits as { name, role, website }}
|
||||||
<li>
|
<li>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
@@ -117,33 +89,57 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<ul data-sveltekit-noscroll>
|
{role}
|
||||||
{#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}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
</section>
|
|
||||||
|
|
||||||
<InteractiveGlobe type="cropped" />
|
<div class="credits__category grid">
|
||||||
</main>
|
<h2 class="title-small">Photography</h2>
|
||||||
</PageTransition>
|
<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}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<InteractiveGlobe type="cropped" />
|
||||||
|
</main>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { RequestHandler } from './$types'
|
|
||||||
import { fetchSwell } from '$utils/functions/shopServer'
|
import { fetchSwell } from '$utils/functions/shopServer'
|
||||||
import { fetchAPI, getAssetUrlKey } from '$utils/api'
|
import { fetchAPI, getAssetUrlKey } from '$utils/api'
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ const gCategories = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const GET = (async ({ url, setHeaders }) => {
|
export const GET = async ({ url, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const products = []
|
const products = []
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ export const GET = (async ({ url, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies RequestHandler
|
}
|
||||||
|
|
||||||
|
|
||||||
// Render sitemap
|
// Render sitemap
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
||||||
import Locations from '$components/organisms/Locations.svelte'
|
import Locations from '$components/organisms/Locations.svelte'
|
||||||
import ShopModule from '$components/organisms/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule.svelte'
|
||||||
@@ -23,20 +22,18 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="explore">
|
||||||
<main class="explore">
|
<Heading {text} />
|
||||||
<Heading {text} />
|
|
||||||
|
|
||||||
<section class="explore__locations">
|
<section class="explore__locations">
|
||||||
<InteractiveGlobe />
|
<InteractiveGlobe />
|
||||||
<Locations {locations} />
|
<Locations {locations} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid-modules is-spaced grid">
|
<section class="grid-modules is-spaced grid">
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<ShopModule />
|
<ShopModule />
|
||||||
<NewsletterModule />
|
<NewsletterModule />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_AMOUNT } from '$env/static/public'
|
import { PUBLIC_FILTERS_DEFAULT_COUNTRY, PUBLIC_FILTERS_DEFAULT_SORT, PUBLIC_GRID_AMOUNT } from '$env/static/public'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ url, setHeaders }) => {
|
export const load = async ({ url, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Query parameters
|
// Query parameters
|
||||||
const queryCountry = url.searchParams.get('country') || PUBLIC_FILTERS_DEFAULT_COUNTRY
|
const queryCountry = url.searchParams.get('country') || PUBLIC_FILTERS_DEFAULT_COUNTRY
|
||||||
@@ -85,4 +84,4 @@ export const load = (async ({ url, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
<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 type { PageData } from './$types'
|
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext, onMount } 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'
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
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'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import SplitText from '$components/SplitText.svelte'
|
import SplitText from '$components/SplitText.svelte'
|
||||||
import IconEarth from '$components/atoms/IconEarth.svelte'
|
import IconEarth from '$components/atoms/IconEarth.svelte'
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
@@ -32,7 +30,7 @@
|
|||||||
import ShopModule from '$components/organisms/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule.svelte'
|
||||||
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
||||||
$: ({ photos, totalPhotos } = data)
|
$: ({ photos, totalPhotos } = data)
|
||||||
@@ -337,164 +335,162 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="photos-page">
|
||||||
<main class="photos-page">
|
<section class="photos-page__intro"
|
||||||
<section class="photos-page__intro"
|
class:is-passed={scrolledPastIntro}
|
||||||
class:is-passed={scrolledPastIntro}
|
>
|
||||||
|
<ScrollingTitle tag="h1" text="Houses">
|
||||||
|
<SplitText text="Houses" mode="chars" />
|
||||||
|
</ScrollingTitle>
|
||||||
|
|
||||||
|
<DiscoverText />
|
||||||
|
|
||||||
|
<div class="filters"
|
||||||
|
class:is-over={filtersOver}
|
||||||
|
class:is-transitioning={filtersTransitioning}
|
||||||
|
class:is-visible={filtersVisible}
|
||||||
>
|
>
|
||||||
<ScrollingTitle tag="h1" text="Houses">
|
<div class="filters__bar">
|
||||||
<SplitText text="Houses" mode="chars" />
|
<span class="text-label filters__label">Filter photos</span>
|
||||||
</ScrollingTitle>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Select
|
||||||
|
name="country" id="filter_country"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: defaultCountry,
|
||||||
|
name: 'Worldwide',
|
||||||
|
default: true,
|
||||||
|
selected: filterCountry === defaultCountry,
|
||||||
|
},
|
||||||
|
...countries.map(({ slug, name }) => ({
|
||||||
|
value: slug,
|
||||||
|
name,
|
||||||
|
selected: filterCountry === slug,
|
||||||
|
}))
|
||||||
|
]}
|
||||||
|
on:change={handleCountryChange}
|
||||||
|
value={filterCountry}
|
||||||
|
>
|
||||||
|
{#if countryFlagId}
|
||||||
|
<Image
|
||||||
|
class="icon"
|
||||||
|
id={countryFlagId}
|
||||||
|
sizeKey="square-small"
|
||||||
|
width={26} height={26}
|
||||||
|
alt="{filterCountry} flag"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<IconEarth class="icon" />
|
||||||
|
{/if}
|
||||||
|
</Select>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Select
|
||||||
|
name="sort" id="filter_sort"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: 'latest',
|
||||||
|
name: 'Latest',
|
||||||
|
default: true,
|
||||||
|
selected: filterSort === defaultSort
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'oldest',
|
||||||
|
name: 'Oldest',
|
||||||
|
selected: filterSort === 'oldest'
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
on:change={handleSortChange}
|
||||||
|
value={filterSort}
|
||||||
|
>
|
||||||
|
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-label="Sort icon">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.878 15.93h-4.172c-.638 0-1.153.516-1.153 1.154 0 .639.515 1.154 1.153 1.154h4.172c.638 0 1.153-.515 1.153-1.154a1.152 1.152 0 0 0-1.153-1.153Zm3.244-5.396h-7.405c-.639 0-1.154.515-1.154 1.153 0 .639.515 1.154 1.154 1.154h7.405c.639 0 1.154-.515 1.154-1.154a1.145 1.145 0 0 0-1.154-1.153Zm3.244-5.408h-10.65c-.638 0-1.153.515-1.153 1.154 0 .639.515 1.154 1.154 1.154h10.65c.638 0 1.153-.515 1.153-1.154 0-.639-.515-1.154-1.154-1.154ZM7.37 20.679V3.376c0-.145-.03-.289-.082-.433a1.189 1.189 0 0 0-.628-.618 1.197 1.197 0 0 0-.886 0 1.045 1.045 0 0 0-.36.237c-.01 0-.01 0-.021.01L.82 7.145a1.156 1.156 0 0 0 0 1.638 1.156 1.156 0 0 0 1.637 0l2.596-2.596v11.7l-2.596-2.595a1.156 1.156 0 0 0-1.637 0 1.156 1.156 0 0 0 0 1.638l4.573 4.573c.103.103.237.185.37.247.135.062.289.082.433.082h.02c.145 0 .3-.03.433-.093a1.14 1.14 0 0 0 .629-.628.987.987 0 0 0 .092-.432Z" />
|
||||||
|
</svg>
|
||||||
|
</Select>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<DiscoverText />
|
<div class="filters__actions">
|
||||||
|
{#if filtered}
|
||||||
<div class="filters"
|
<button class="reset button-link"
|
||||||
class:is-over={filtersOver}
|
on:click={resetFiltered}
|
||||||
class:is-transitioning={filtersTransitioning}
|
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
|
||||||
class:is-visible={filtersVisible}
|
>
|
||||||
>
|
Reset
|
||||||
<div class="filters__bar">
|
</button>
|
||||||
<span class="text-label filters__label">Filter photos</span>
|
{/if}
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Select
|
|
||||||
name="country" id="filter_country"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
value: defaultCountry,
|
|
||||||
name: 'Worldwide',
|
|
||||||
default: true,
|
|
||||||
selected: filterCountry === defaultCountry,
|
|
||||||
},
|
|
||||||
...countries.map(({ slug, name }) => ({
|
|
||||||
value: slug,
|
|
||||||
name,
|
|
||||||
selected: filterCountry === slug,
|
|
||||||
}))
|
|
||||||
]}
|
|
||||||
on:change={handleCountryChange}
|
|
||||||
value={filterCountry}
|
|
||||||
>
|
|
||||||
{#if countryFlagId}
|
|
||||||
<Image
|
|
||||||
class="icon"
|
|
||||||
id={countryFlagId}
|
|
||||||
sizeKey="square-small"
|
|
||||||
width={26} height={26}
|
|
||||||
alt="{filterCountry} flag"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<IconEarth class="icon" />
|
|
||||||
{/if}
|
|
||||||
</Select>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Select
|
|
||||||
name="sort" id="filter_sort"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
value: 'latest',
|
|
||||||
name: 'Latest',
|
|
||||||
default: true,
|
|
||||||
selected: filterSort === defaultSort
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'oldest',
|
|
||||||
name: 'Oldest',
|
|
||||||
selected: filterSort === 'oldest'
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
on:change={handleSortChange}
|
|
||||||
value={filterSort}
|
|
||||||
>
|
|
||||||
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-label="Sort icon">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.878 15.93h-4.172c-.638 0-1.153.516-1.153 1.154 0 .639.515 1.154 1.153 1.154h4.172c.638 0 1.153-.515 1.153-1.154a1.152 1.152 0 0 0-1.153-1.153Zm3.244-5.396h-7.405c-.639 0-1.154.515-1.154 1.153 0 .639.515 1.154 1.154 1.154h7.405c.639 0 1.154-.515 1.154-1.154a1.145 1.145 0 0 0-1.154-1.153Zm3.244-5.408h-10.65c-.638 0-1.153.515-1.153 1.154 0 .639.515 1.154 1.154 1.154h10.65c.638 0 1.153-.515 1.153-1.154 0-.639-.515-1.154-1.154-1.154ZM7.37 20.679V3.376c0-.145-.03-.289-.082-.433a1.189 1.189 0 0 0-.628-.618 1.197 1.197 0 0 0-.886 0 1.045 1.045 0 0 0-.36.237c-.01 0-.01 0-.021.01L.82 7.145a1.156 1.156 0 0 0 0 1.638 1.156 1.156 0 0 0 1.637 0l2.596-2.596v11.7l-2.596-2.595a1.156 1.156 0 0 0-1.637 0 1.156 1.156 0 0 0 0 1.638l4.573 4.573c.103.103.237.185.37.247.135.062.289.082.433.082h.02c.145 0 .3-.03.433-.093a1.14 1.14 0 0 0 .629-.628.987.987 0 0 0 .092-.432Z" />
|
|
||||||
</svg>
|
|
||||||
</Select>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="filters__actions">
|
|
||||||
{#if filtered}
|
|
||||||
<button class="reset button-link"
|
|
||||||
on:click={resetFiltered}
|
|
||||||
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="photos-page__content" bind:this={photosContentEl} style:--margin-sides="{sideMargins}px">
|
<section class="photos-page__content" bind:this={photosContentEl} style:--margin-sides="{sideMargins}px">
|
||||||
<div class="grid container">
|
<div class="grid container">
|
||||||
{#if photos}
|
{#if photos}
|
||||||
<div class="photos-page__grid" bind:this={photosGridEl} data-sveltekit-noscroll>
|
<div class="photos-page__grid" bind:this={photosGridEl} data-sveltekit-noscroll>
|
||||||
{#each photos as { id, image, slug, location, title, city }, index (id)}
|
{#each photos as { id, image, slug, location, title, city }, index (id)}
|
||||||
<figure class="photo shadow-photo">
|
<figure class="photo shadow-photo">
|
||||||
<a href="/{location.country.slug}/{location.slug}/{slug}" tabindex="0">
|
<a href="/{location.country.slug}/{location.slug}/{slug}" tabindex="0">
|
||||||
<Image
|
<Image
|
||||||
id={image.id}
|
id={image.id}
|
||||||
sizeKey="photo-grid"
|
sizeKey="photo-grid"
|
||||||
sizes={{
|
sizes={{
|
||||||
small: { width: 500 },
|
small: { width: 500 },
|
||||||
medium: { width: 900 },
|
medium: { width: 900 },
|
||||||
large: { width: 1440 },
|
large: { width: 1440 },
|
||||||
}}
|
}}
|
||||||
ratio={1.5}
|
ratio={1.5}
|
||||||
alt={image.title}
|
alt={image.title}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<figcaption>
|
<figcaption>
|
||||||
<PostCard
|
<PostCard
|
||||||
street={title}
|
street={title}
|
||||||
location={city ? `${city}, ${location.name}` : location.name}
|
location={city ? `${city}, ${location.name}` : location.name}
|
||||||
region={location.region}
|
region={location.region}
|
||||||
country={location.country.name}
|
country={location.country.name}
|
||||||
flagId={location.country.flag.id}
|
flagId={location.country.flag.id}
|
||||||
size={isSmall(index) ? 'small' : null}
|
size={isSmall(index) ? 'small' : null}
|
||||||
/>
|
/>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls grid">
|
<div class="controls grid">
|
||||||
<p class="controls__date" title={dayjs(latestPhoto.date_created).format('DD/MM/YYYY, hh:mm')}>
|
<p class="controls__date" title={dayjs(latestPhoto.date_created).format('DD/MM/YYYY, hh:mm')}>
|
||||||
Last updated: <time datetime={dayjs(latestPhoto.date_created).format('YYYY-MM-DD')}>{dayjs().to(dayjs(latestPhoto.date_created))}</time>
|
Last updated: <time datetime={dayjs(latestPhoto.date_created).format('YYYY-MM-DD')}>{dayjs().to(dayjs(latestPhoto.date_created))}</time>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
tag="button"
|
size="large"
|
||||||
text={!ended ? 'See more photos' : "You've seen it all!"}
|
color="beige"
|
||||||
size="large" color="beige"
|
text={!ended ? 'See more photos' : "You've seen it all!"}
|
||||||
on:click={loadMorePhotos}
|
on:click={loadMorePhotos}
|
||||||
disabled={ended}
|
disabled={ended}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="controls__count">
|
<div class="controls__count">
|
||||||
<span class="current">{currentPhotosAmount}</span>
|
<span class="current">{currentPhotosAmount}</span>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span class="total">{totalPhotos}</span>
|
<span class="total">{totalPhotos}</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if !filteredCountryExists}
|
|
||||||
<div class="photos-page__message">
|
|
||||||
<p>
|
|
||||||
<strong>{$page.url.searchParams.get('country').replace(/(^|\s)\S/g, letter => letter.toUpperCase())}</strong> is not available… yet 👀
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="grid-modules">
|
|
||||||
<div class="wrap">
|
|
||||||
<ShopModule />
|
|
||||||
<NewsletterModule theme="light" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if !filteredCountryExists}
|
||||||
|
<div class="photos-page__message">
|
||||||
|
<p>
|
||||||
|
<strong>{$page.url.searchParams.get('country').replace(/(^|\s)\S/g, letter => letter.toUpperCase())}</strong> is not available… yet 👀
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="grid-modules">
|
||||||
|
<div class="wrap">
|
||||||
|
<ShopModule />
|
||||||
|
<NewsletterModule theme="light" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</section>
|
||||||
</PageTransition>
|
</main>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
settings {
|
settings {
|
||||||
@@ -36,4 +35,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -4,20 +4,18 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { onMount } from 'svelte'
|
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'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Heading from '$components/molecules/Heading.svelte'
|
import Heading from '$components/molecules/Heading.svelte'
|
||||||
import EmailForm from '$components/molecules/EmailForm.svelte'
|
import EmailForm from '$components/molecules/EmailForm.svelte'
|
||||||
import NewsletterIssue from '$components/molecules/NewsletterIssue.svelte'
|
import NewsletterIssue from '$components/molecules/NewsletterIssue.svelte'
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
const { issues } = data
|
const { issues } = data
|
||||||
const latestIssue = issues[0]
|
const latestIssue = issues[0]
|
||||||
@@ -66,34 +64,32 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="subscribe">
|
||||||
<main class="subscribe">
|
<div class="subscribe__top">
|
||||||
<div class="subscribe__top">
|
<Heading
|
||||||
<Heading
|
text={data.newsletter_page_text}
|
||||||
text={data.newsletter_page_text}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<EmailForm />
|
<EmailForm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="subscribe__issues">
|
||||||
|
<h2 class="title-small">Latest Issue</h2>
|
||||||
|
<div class="issue-container">
|
||||||
|
<NewsletterIssue size="large" date={latestIssue.date_sent} {...latestIssue} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="subscribe__issues">
|
{#if issues.length > 1}
|
||||||
<h2 class="title-small">Latest Issue</h2>
|
<h2 class="title-small">Past Issues</h2>
|
||||||
<div class="issue-container">
|
<ul>
|
||||||
<NewsletterIssue size="large" date={latestIssue.date_sent} {...latestIssue} />
|
{#each issues.slice(1) as { issue, title, date_sent: date, link, thumbnail }}
|
||||||
</div>
|
<li class="issue-container">
|
||||||
|
<NewsletterIssue {issue} {title} {link} {thumbnail} {date} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
{#if issues.length > 1}
|
<InteractiveGlobe type="cropped" />
|
||||||
<h2 class="title-small">Past Issues</h2>
|
</main>
|
||||||
<ul>
|
|
||||||
{#each issues.slice(1) as { issue, title, date_sent: date, link, thumbnail }}
|
|
||||||
<li class="issue-container">
|
|
||||||
<NewsletterIssue {issue} {title} {link} {thumbnail} {date} />
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<InteractiveGlobe type="cropped" />
|
|
||||||
</main>
|
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
legal {
|
legal {
|
||||||
@@ -24,4 +23,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
// Components
|
// Components
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import Heading from '$components/molecules/Heading.svelte'
|
import Heading from '$components/molecules/Heading.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
const { legal } = data
|
const { legal } = data
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
@@ -23,27 +21,25 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="terms">
|
||||||
<main class="terms">
|
<Heading text="Everything you need to know about using our website or buying our products" />
|
||||||
<Heading text="Everything you need to know about using our website or buying our products" />
|
|
||||||
|
|
||||||
<section class="terms__categories">
|
<section class="terms__categories">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
{#each legal.terms as { title, text }, index}
|
{#each 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">
|
||||||
{@html text}
|
{@html text}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<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(legal.date_updated).format('YYYY-MM-DD')}>{dayjs().to(dayjs(legal.date_updated))}</time>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</PageTransition>
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import BoxCTA from '$components/atoms/BoxCTA.svelte'
|
import BoxCTA from '$components/atoms/BoxCTA.svelte'
|
||||||
import Heading from '$components/molecules/Heading.svelte'
|
import Heading from '$components/molecules/Heading.svelte'
|
||||||
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
|
||||||
@@ -35,51 +34,49 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<main class="page-error">
|
||||||
<main class="page-error">
|
<div class="page-error__top">
|
||||||
<div class="page-error__top">
|
<Heading
|
||||||
<Heading
|
text="{$page.error.message ?? errors[$page.status].message} <br>{defaultMessage}"
|
||||||
text="{$page.error.message ?? errors[$page.status].message} <br>{defaultMessage}"
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<ListCTAs>
|
<ListCTAs>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA
|
||||||
url="/photos"
|
url="/photos"
|
||||||
icon="photos"
|
icon="photos"
|
||||||
label="Browse all photos"
|
label="Browse all photos"
|
||||||
alt="Photos"
|
alt="Photos"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA
|
||||||
url="/shop"
|
url="/shop"
|
||||||
icon="bag"
|
icon="bag"
|
||||||
label="Shop our products"
|
label="Shop our products"
|
||||||
alt="Shopping bag"
|
alt="Shopping bag"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BoxCTA
|
<BoxCTA
|
||||||
url="/about"
|
url="/about"
|
||||||
icon="compass"
|
icon="compass"
|
||||||
label="Learn about the project"
|
label="Learn about the project"
|
||||||
alt="Compass"
|
alt="Compass"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ListCTAs>
|
</ListCTAs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InteractiveGlobe />
|
<InteractiveGlobe />
|
||||||
<Locations {locations} />
|
<Locations {locations} />
|
||||||
|
|
||||||
<div class="grid-modules">
|
<div class="grid-modules">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<ShopModule />
|
<ShopModule />
|
||||||
<NewsletterModule />
|
<NewsletterModule />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</PageTransition>
|
</main>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { LayoutServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
import { PUBLIC_PREVIEW_COUNT } from '$env/static/public'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = async ({ url }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchAPI(`query {
|
const res = await fetchAPI(`query {
|
||||||
locations: location (filter: { status: { _eq: "published" }}) {
|
locations: location (filter: { status: { _eq: "published" }}) {
|
||||||
@@ -97,9 +96,10 @@ export const load = (async () => {
|
|||||||
locations: countLocations[0].count.id,
|
locations: countLocations[0].count.id,
|
||||||
countries: countCountries[0].count.id,
|
countries: countCountries[0].count.id,
|
||||||
},
|
},
|
||||||
|
currentPath: url.pathname,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err || 'Failed to fetch data')
|
throw error(500, err || 'Failed to fetch data')
|
||||||
}
|
}
|
||||||
}) satisfies LayoutServerLoad
|
}
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
|
<style lang="scss">
|
||||||
|
// Toast
|
||||||
|
:global(.toast--global) {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 20;
|
||||||
|
max-width: 420px;
|
||||||
|
bottom: var(--offset-sides);
|
||||||
|
right: var(--offset-sides);
|
||||||
|
left: calc(var(--offset-sides) + 44px + 24px);
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../style/global.scss'
|
import '../style/global.scss'
|
||||||
|
|
||||||
import { browser } from '$app/environment'
|
import { browser } from '$app/environment'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { beforeNavigate } 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 type { PageData } from './$types'
|
import { setContext, onMount } from 'svelte'
|
||||||
import { onMount, setContext } from 'svelte'
|
import { fade } from 'svelte/transition'
|
||||||
|
import { DELAY, DURATION } from '$utils/constants'
|
||||||
import { pageLoading, previousPage } from '$utils/stores'
|
import { pageLoading, previousPage } from '$utils/stores'
|
||||||
|
import { scrollToTop } from 'utils/scroll'
|
||||||
import '$utils/polyfills'
|
import '$utils/polyfills'
|
||||||
// Components
|
// Components
|
||||||
import SVGSprite from '$components/SVGSprite.svelte'
|
import SVGSprite from '$components/SVGSprite.svelte'
|
||||||
import SmoothScroll from '$components/SmoothScroll.svelte'
|
import SmoothScroll from '$components/SmoothScroll.svelte'
|
||||||
import Analytics from '$components/Analytics.svelte'
|
import Analytics from '$components/Analytics.svelte'
|
||||||
import Switcher from '$components/molecules/Switcher.svelte'
|
import Switcher from '$components/molecules/Switcher.svelte'
|
||||||
|
import Toast from '$components/molecules/Toast.svelte'
|
||||||
import Footer from '$components/organisms/Footer.svelte'
|
import Footer from '$components/organisms/Footer.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
let innerHeight: number
|
let innerHeight: number
|
||||||
$: innerHeight && document.body.style.setProperty('--vh', `${innerHeight}px`)
|
$: innerHeight && document.body.style.setProperty('--vh', `${innerHeight}px`)
|
||||||
@@ -38,13 +57,30 @@
|
|||||||
/**
|
/**
|
||||||
* On page change
|
* On page change
|
||||||
*/
|
*/
|
||||||
// Store previous page (for photo Viewer close button)
|
beforeNavigate(({ from, to }) => {
|
||||||
beforeNavigate(({ from }) => {
|
// Store previous page (for photo Viewer close button)
|
||||||
$previousPage = from.url.pathname
|
$previousPage = from.url.pathname
|
||||||
|
|
||||||
|
// Enable page loading state if URL changed
|
||||||
|
if (from.route.id !== to.route.id) {
|
||||||
|
$pageLoading = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterNavigate(() => {
|
||||||
|
// Remove page loading state
|
||||||
|
setTimeout(() => $pageLoading = false, DELAY.PAGE_LOADING)
|
||||||
|
|
||||||
|
// Scroll back to top when new page is ready (excepted certain pages)
|
||||||
|
if (!$page.url.searchParams.get('country') && !$page.url.pathname.includes('/shop/')) {
|
||||||
|
setTimeout(scrollToTop, DELAY.PAGE_IN)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Define page loading
|
// Define page loading
|
||||||
$: browser && document.body.classList.toggle('is-loading', $pageLoading)
|
$: if (browser) {
|
||||||
|
document.body.classList.toggle('is-loading', $pageLoading)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -66,10 +102,33 @@
|
|||||||
|
|
||||||
<Switcher />
|
<Switcher />
|
||||||
|
|
||||||
<slot />
|
{#key data.currentPath}
|
||||||
|
<div
|
||||||
|
in:fade={{ duration: DURATION.PAGE_IN, delay: DELAY.PAGE_LOADING }}
|
||||||
|
out:fade={{ duration: DURATION.PAGE_OUT }}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
|
||||||
{#if !$page.params.photo}
|
{#if !$page.params.photo}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
|
||||||
|
{#if !['/[photo]'].some(sub => $page.route.id.includes(sub))}
|
||||||
|
<Toast
|
||||||
|
type="global"
|
||||||
|
id="posters-promo"
|
||||||
|
text="Upgrade your walls! <strong>10% off</strong> in cart with free shipping on our graphic posters."
|
||||||
|
cta={{
|
||||||
|
label: 'View posters',
|
||||||
|
url: '/shop',
|
||||||
|
color: 'pink',
|
||||||
|
}}
|
||||||
|
images={data.shop.module_images.map(({ directus_files_id: { id, title } }) => ({ id, title }))}
|
||||||
|
class="toast-home"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<SVGSprite />
|
<SVGSprite />
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { PageServerLoad } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { getRandomItems } from 'utils/array'
|
import { getRandomItems } from 'utils/array'
|
||||||
|
|
||||||
|
|
||||||
export const load = (async ({ setHeaders }) => {
|
export const load = async ({ setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
// Get total of published photos
|
// Get total of published photos
|
||||||
const totalRes = await fetchAPI(`query {
|
const totalRes = await fetchAPI(`query {
|
||||||
@@ -53,4 +52,4 @@ export const load = (async ({ setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { navigating } from '$app/stores'
|
import { navigating } from '$app/stores'
|
||||||
import type { PageData } from './$types'
|
|
||||||
import { getContext, onMount } from 'svelte'
|
import { getContext, onMount } from 'svelte'
|
||||||
import { timeline, stagger } from 'motion'
|
import { timeline, stagger } from 'motion'
|
||||||
import { DELAY } from '$utils/constants'
|
import { DELAY } from '$utils/constants'
|
||||||
@@ -14,7 +13,6 @@
|
|||||||
import { quartOut } from '$animations/easings'
|
import { quartOut } from '$animations/easings'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import SplitText from '$components/SplitText.svelte'
|
import SplitText from '$components/SplitText.svelte'
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import IconEarth from '$components/atoms/IconEarth.svelte'
|
import IconEarth from '$components/atoms/IconEarth.svelte'
|
||||||
@@ -28,7 +26,7 @@
|
|||||||
import ShopModule from '$components/organisms/ShopModule.svelte'
|
import ShopModule from '$components/organisms/ShopModule.svelte'
|
||||||
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
||||||
|
|
||||||
export let data: PageData
|
export let data
|
||||||
|
|
||||||
const { photos } = data
|
const { photos } = data
|
||||||
const { settings, locations }: any = getContext('global')
|
const { settings, locations }: any = getContext('global')
|
||||||
@@ -82,89 +80,92 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<PageTransition>
|
<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: {
|
duration: 1,
|
||||||
duration: 1,
|
},
|
||||||
},
|
}}
|
||||||
}}
|
>
|
||||||
|
<ScrollingTitle
|
||||||
|
tag="h1"
|
||||||
|
class="title-houses"
|
||||||
|
label="Houses of the World"
|
||||||
|
offsetStart={-300}
|
||||||
|
offsetEnd={400}
|
||||||
>
|
>
|
||||||
<ScrollingTitle
|
<SplitText text="Houses" mode="chars" />
|
||||||
tag="h1"
|
</ScrollingTitle>
|
||||||
class="title-houses"
|
|
||||||
label="Houses of the World"
|
<div class="homepage__headline">
|
||||||
offsetStart={-300}
|
<p class="text-medium">
|
||||||
offsetEnd={400}
|
{settings.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="medium"
|
||||||
|
url="#locations"
|
||||||
|
text="Explore locations"
|
||||||
|
on:click={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}
|
||||||
>
|
>
|
||||||
<SplitText text="Houses" mode="chars" />
|
<IconEarth animate={true} />
|
||||||
</ScrollingTitle>
|
</Button>
|
||||||
|
|
||||||
<div class="homepage__headline">
|
|
||||||
<p class="text-medium">
|
|
||||||
{settings.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Button url="#locations" text="Explore locations" on:click={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}>
|
|
||||||
<IconEarth animate={true} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="homepage__photos">
|
|
||||||
<Collage {photos} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="homepage__ctas">
|
|
||||||
<DiscoverText />
|
|
||||||
|
|
||||||
<ListCTAs>
|
|
||||||
<li>
|
|
||||||
<BoxCTA
|
|
||||||
url="/photos"
|
|
||||||
icon="photos"
|
|
||||||
label="Browse all photos"
|
|
||||||
alt="Photos"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BoxCTA
|
|
||||||
url="/shop"
|
|
||||||
icon="bag"
|
|
||||||
label="Shop our products"
|
|
||||||
alt="Shopping bag"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<BoxCTA
|
|
||||||
url="/about"
|
|
||||||
icon="compass"
|
|
||||||
label="Learn about the project"
|
|
||||||
alt="Compass"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ListCTAs>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="homepage__locations" id="locations">
|
<section class="homepage__photos">
|
||||||
<InteractiveGlobe />
|
<Collage {photos} />
|
||||||
|
</section>
|
||||||
|
|
||||||
<ScrollingTitle tag="p" class="title-world mask">
|
<div class="homepage__ctas">
|
||||||
<SplitText text="World" mode="chars" />
|
<DiscoverText />
|
||||||
</ScrollingTitle>
|
|
||||||
|
|
||||||
<Locations {locations} />
|
<ListCTAs>
|
||||||
</section>
|
<li>
|
||||||
|
<BoxCTA
|
||||||
|
url="/photos"
|
||||||
|
icon="photos"
|
||||||
|
label="Browse all photos"
|
||||||
|
alt="Photos"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BoxCTA
|
||||||
|
url="/shop"
|
||||||
|
icon="bag"
|
||||||
|
label="Shop our products"
|
||||||
|
alt="Shopping bag"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<BoxCTA
|
||||||
|
url="/about"
|
||||||
|
icon="compass"
|
||||||
|
label="Learn about the project"
|
||||||
|
alt="Compass"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ListCTAs>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid-modules">
|
<section class="homepage__locations" id="locations">
|
||||||
<div class="container grid">
|
<InteractiveGlobe />
|
||||||
<div class="wrap">
|
|
||||||
<ShopModule />
|
<ScrollingTitle tag="p" class="title-world mask">
|
||||||
<NewsletterModule />
|
<SplitText text="World" mode="chars" />
|
||||||
</div>
|
</ScrollingTitle>
|
||||||
|
|
||||||
|
<Locations {locations} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="grid-modules">
|
||||||
|
<div class="container grid">
|
||||||
|
<div class="wrap">
|
||||||
|
<ShopModule />
|
||||||
|
<NewsletterModule />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</PageTransition>
|
</main>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { NEWSLETTER_API_TOKEN, NEWSLETTER_LIST_ID } from '$env/static/private'
|
import { NEWSLETTER_API_TOKEN, NEWSLETTER_LIST_ID } from '$env/static/private'
|
||||||
import type { RequestHandler } from './$types'
|
|
||||||
|
|
||||||
|
|
||||||
export const POST = (async ({ request, fetch }) => {
|
export const POST = async ({ request, fetch }) => {
|
||||||
const data: { email: string } = await request.json()
|
const data: { email: string } = await request.json()
|
||||||
const { email } = data
|
const { email } = data
|
||||||
|
|
||||||
@@ -38,4 +37,4 @@ export const POST = (async ({ request, fetch }) => {
|
|||||||
}), {
|
}), {
|
||||||
status: 200
|
status: 200
|
||||||
})
|
})
|
||||||
}) satisfies RequestHandler
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
import type { RequestHandler } from './$types'
|
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
|
|
||||||
|
|
||||||
export const GET = (async ({ url, setHeaders }) => {
|
export const GET = async ({ url, setHeaders }) => {
|
||||||
try {
|
try {
|
||||||
const locations = []
|
const locations = []
|
||||||
const products = []
|
const products = []
|
||||||
@@ -72,7 +71,7 @@ export const GET = (async ({ url, setHeaders }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw error(500, err.message)
|
throw error(500, err.message)
|
||||||
}
|
}
|
||||||
}) satisfies RequestHandler
|
}
|
||||||
|
|
||||||
|
|
||||||
const render = (origin: string, pages: any[]) => {
|
const render = (origin: string, pages: any[]) => {
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
// CSS Variables
|
|
||||||
:root {
|
|
||||||
// Sizes
|
|
||||||
--container-width: #{$container-width};
|
|
||||||
|
|
||||||
// Offsets
|
|
||||||
--switcher-offset: 16px;
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
--ease-quart: cubic-bezier(.165, .84, .44, 1);
|
|
||||||
--ease-cubic: cubic-bezier(.785, .135, .15, .86);
|
|
||||||
--ease-inout-quart: cubic-bezier(.76, 0, .24, 1);
|
|
||||||
}
|
|
||||||
@include bp (sm) {
|
|
||||||
:root {
|
|
||||||
// Offsets
|
|
||||||
--switcher-offset: clamp(20px, 3vw, 40px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font: #{$base-font-size}/1.2 $font-sans;
|
font: #{$base-font-size}/1.2 $font-sans;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
|
|
||||||
|
&.block-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@include font-smooth;
|
@include font-smooth;
|
||||||
@@ -33,9 +17,6 @@ body {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
|
||||||
&.block-scroll {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
&.is-loading * {
|
&.is-loading * {
|
||||||
cursor: wait !important;
|
cursor: wait !important;
|
||||||
}
|
}
|
||||||
|
|||||||
17
apps/website/src/style/_variables-css.scss
Normal file
17
apps/website/src/style/_variables-css.scss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
:root {
|
||||||
|
// Sizes
|
||||||
|
--container-width: #{$container-width};
|
||||||
|
|
||||||
|
// Offsets
|
||||||
|
--offset-sides: 16px;
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
--ease-quart: cubic-bezier(.165, .84, .44, 1);
|
||||||
|
--ease-cubic: cubic-bezier(.785, .135, .15, .86);
|
||||||
|
--ease-inout-quart: cubic-bezier(.76, 0, .24, 1);
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
// Offsets
|
||||||
|
--offset-sides: clamp(20px, 3vw, 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
.button {
|
.button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 40px;
|
|
||||||
padding: 0 16px;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
font: 900 #{rem(16px)}/1 $font-sans;
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
font-family: $font-sans;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 100vh;
|
border-radius: 100vh;
|
||||||
@@ -12,12 +12,6 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: background-color 0.55s var(--ease-quart), color 0.55s var(--ease-quart);
|
transition: background-color 0.55s var(--ease-quart), color 0.55s var(--ease-quart);
|
||||||
|
|
||||||
@include bp (md) {
|
|
||||||
height: 48px;
|
|
||||||
padding: 1px 24px 0;
|
|
||||||
font-size: rem(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon
|
// Icon
|
||||||
:global(img), :global(svg) {
|
:global(img), :global(svg) {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -47,7 +41,7 @@
|
|||||||
// XSmall
|
// XSmall
|
||||||
&--xsmall {
|
&--xsmall {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 12px;
|
||||||
|
|
||||||
@include bp (md) {
|
@include bp (md) {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -68,6 +62,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Medium
|
||||||
|
&--medium {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: rem(16px);
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
height: 48px;
|
||||||
|
font-size: rem(18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Large
|
// Large
|
||||||
&--large {
|
&--large {
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
@import "tools/helpers";
|
@import "tools/helpers";
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
|
@import "variables-css";
|
||||||
@import "base";
|
@import "base";
|
||||||
@import "fonts";
|
@import "fonts";
|
||||||
@import "typography";
|
@import "typography";
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
$shadow-color: rgba(0, 0, 0, 0.05);
|
$shadow-color: rgba(0, 0, 0, 0.05);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
bottom: var(--switcher-offset);
|
bottom: var(--offset-sides);
|
||||||
left: var(--switcher-offset);
|
left: var(--offset-sides);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
@include bp (md) {
|
@include bp (md) {
|
||||||
|
|||||||
120
apps/website/src/style/molecules/_toast.scss
Normal file
120
apps/website/src/style/molecules/_toast.scss
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
.toast {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
padding-right: 28px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
padding-right: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media
|
||||||
|
.media {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex: 0 0 clamp(40px, 14vw, 64px);
|
||||||
|
aspect-ratio: 1 / 1.25;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
flex: 0 0 min(7vw, 104px);
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(picture) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.075);
|
||||||
|
transition: opacity 0.8s, transform 1.6s var(--ease-quart);
|
||||||
|
}
|
||||||
|
:global(img) {
|
||||||
|
object-position: center 32%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.is-visible) {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(img) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content
|
||||||
|
.content {
|
||||||
|
color: $color-text;
|
||||||
|
font-size: rem(14px);
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
font-size: rem(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(strong) {
|
||||||
|
font-weight: normal;
|
||||||
|
color: $color-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close
|
||||||
|
.close {
|
||||||
|
--size: 28px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
--size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(svg) {
|
||||||
|
transition: transform 0.6s var(--ease-quart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover
|
||||||
|
&:hover {
|
||||||
|
:global(svg) {
|
||||||
|
transform: rotate(90deg) translateZ(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,11 @@
|
|||||||
@include bp (md) {
|
@include bp (md) {
|
||||||
max-width: 304px;
|
max-width: 304px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(strong) {
|
||||||
|
font-weight: normal;
|
||||||
|
color: $color-secondary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// Delays
|
|
||||||
export const DELAY = {
|
|
||||||
PAGE_LOADING: 600,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Durations
|
// Durations
|
||||||
export const DURATION = {
|
export const DURATION = {
|
||||||
PAGE_IN: 400,
|
PAGE_IN: 400,
|
||||||
PAGE_OUT: 400,
|
PAGE_OUT: 400,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delays
|
||||||
|
export const DELAY = {
|
||||||
|
PAGE_LOADING: 600,
|
||||||
|
PAGE_IN: DURATION.PAGE_OUT + 1,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "housesof",
|
"name": "housesof",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"turbo": "^1.9.4"
|
"turbo": "^1.10.3"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ module.exports = {
|
|||||||
'ts-nocheck': false,
|
'ts-nocheck': false,
|
||||||
'ts-expect-error': 'allow-with-description',
|
'ts-expect-error': 'allow-with-description',
|
||||||
}],
|
}],
|
||||||
|
// Ignore some no-used-vars
|
||||||
|
'@typescript-eslint/no-unused-vars': ['warn', {
|
||||||
|
'argsIgnorePattern': '^_',
|
||||||
|
}],
|
||||||
|
|
||||||
/* Svelte
|
/* Svelte
|
||||||
========================================= */
|
========================================= */
|
||||||
@@ -72,6 +76,8 @@ module.exports = {
|
|||||||
avoidInvalidUnquotedInHTML: false,
|
avoidInvalidUnquotedInHTML: false,
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
// Enable @html
|
||||||
|
'svelte/no-at-html-tags': 'off',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
},
|
},
|
||||||
|
|||||||
4494
pnpm-lock.yaml
generated
4494
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user