Merge branch 'dev'

This commit is contained in:
2023-06-11 22:42:33 +02:00
62 changed files with 3604 additions and 3265 deletions

View File

@@ -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"
} }
} }

View File

@@ -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": [

View File

@@ -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>

View File

@@ -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',
}) })

View File

@@ -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}

View File

@@ -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 = () => {

View File

@@ -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)

View File

@@ -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'}

View File

@@ -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">

View File

@@ -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"

View File

@@ -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',

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}>

View File

@@ -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"

View File

@@ -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

View 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}

View File

@@ -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>

View File

@@ -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={() => {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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}
&middot;
<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}
&middot; </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>

View File

@@ -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 }

View File

@@ -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">&middot;</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">&middot;</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>

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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 />

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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[]) => {

View File

@@ -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;
} }

View 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);
}
}

View File

@@ -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;

View File

@@ -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";

View File

@@ -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) {

View 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);
}
}
}

View File

@@ -140,6 +140,11 @@
@include bp (md) { @include bp (md) {
max-width: 304px; max-width: 304px;
} }
:global(strong) {
font-weight: normal;
color: $color-secondary;
}
} }
} }
} }

View File

@@ -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,
}

View File

@@ -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": {

View File

@@ -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

File diff suppressed because it is too large Load Diff