🚧 Use PageTransition as a div and switch

- use a page classed div for PageTransition which avoids to make global style for the page
- fix the loading spinner that was too short and would come and go before arriving on the page, now fades out when changing page as pageLoading is defined on the PageTransition afterUpdate
This commit is contained in:
2022-10-09 14:44:25 +02:00
parent 8e6a38381f
commit fd37c36595
23 changed files with 1252 additions and 1246 deletions

View File

@@ -1,18 +1,26 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores' import { page } from '$app/stores'
import { afterUpdate } from 'svelte'
import { fade } from 'svelte/transition' import { fade } from 'svelte/transition'
import { pageLoading } from '$utils/stores'
import { scrollToTop } from '$utils/functions' import { scrollToTop } from '$utils/functions'
import { DURATION } from '$utils/contants' import { DURATION } from '$utils/contants'
export let name: string let loadingTimeout: ReturnType<typeof setTimeout> | number = null
$: doNotScroll = !$page.url.searchParams.get('country') && !$page.url.pathname.includes('/shop/') $: doNotScroll = !$page.url.searchParams.get('country') && !$page.url.pathname.includes('/shop/')
afterUpdate(() => {
// Turn page loading on page mount
clearTimeout(loadingTimeout)
loadingTimeout = setTimeout(() => $pageLoading = false, DURATION.PAGE_IN)
})
</script> </script>
<main class={name} <div class="page"
in:fade={{ duration: DURATION.PAGE_IN, delay: DURATION.PAGE_DELAY }} in:fade={{ duration: DURATION.PAGE_IN, delay: DURATION.PAGE_DELAY }}
out:fade={{ duration: DURATION.PAGE_OUT }} out:fade={{ duration: DURATION.PAGE_OUT }}
on:outroend={() => doNotScroll && scrollToTop()} on:outroend={() => doNotScroll && scrollToTop()}
> >
<slot /> <slot />
</main> </div>

View File

@@ -34,49 +34,51 @@
title="{errors[$page.status].title} Houses Of" title="{errors[$page.status].title} Houses Of"
/> />
<PageTransition name="page-error"> <PageTransition>
<div class="page-error__top"> <main class="page-error">
<Heading <div class="page-error__top">
text="{$page.error.message ?? errors[$page.status].message} <br>{defaultMessage}" <Heading
/> 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>
</div> </main>
</PageTransition> </PageTransition>

View File

@@ -7,7 +7,6 @@
import type { PageData } from './$types' import type { PageData } from './$types'
import { onMount, setContext } from 'svelte' import { onMount, setContext } from 'svelte'
import { pageLoading, previousPage } from '$utils/stores' import { pageLoading, previousPage } from '$utils/stores'
import { DURATION } from '$utils/contants'
import '$utils/polyfills' import '$utils/polyfills'
import { PUBLIC_ANALYTICS_KEY, PUBLIC_ANALYTICS_URL } from '$env/static/public' import { PUBLIC_ANALYTICS_KEY, PUBLIC_ANALYTICS_URL } from '$env/static/public'
// Components // Components
@@ -46,14 +45,7 @@
// Define page loading from navigating store // Define page loading from navigating store
navigating.subscribe((store: any) => { navigating.subscribe((store: any) => {
if (store) { store && ($pageLoading = true)
$pageLoading = true
// Turn page loading when changing page
setTimeout(() => {
$pageLoading = false
}, DURATION.PAGE_IN * 1.25)
}
}) })
onMount(() => { onMount(() => {

View File

@@ -82,87 +82,89 @@
/> />
<!-- image={getAssetUrlKey(settings.seo_image.id, 'share-image')} --> <!-- image={getAssetUrlKey(settings.seo_image.id, 'share-image')} -->
<PageTransition name="homepage"> <PageTransition>
<section class="homepage__intro" <main class="homepage">
use:reveal={{ <section class="homepage__intro"
animation: { opacity: [0, 1] }, use:reveal={{
options: { animation: { opacity: [0, 1] },
duration: 1, options: {
}, duration: 1,
}} },
> }}
<ScrollingTitle
tag="h1"
class="title-houses"
label="Houses of the World"
offsetStart={-300}
offsetEnd={400}
> >
<SplitText text="Houses" mode="chars" /> <ScrollingTitle
</ScrollingTitle> tag="h1"
class="title-houses"
label="Houses of the World"
offsetStart={-300}
offsetEnd={400}
>
<SplitText text="Houses" mode="chars" />
</ScrollingTitle>
<div class="homepage__headline"> <div class="homepage__headline">
<p class="text-medium"> <p class="text-medium">
{settings.description} {settings.description}
</p> </p>
<Button url="#locations" text="Explore locations" on:click={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}> <Button url="#locations" text="Explore locations" on:click={() => $smoothScroll.scrollTo('#locations', { duration: 2 })}>
<IconEarth animate={true} /> <IconEarth animate={true} />
</Button> </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__photos"> <section class="homepage__locations" id="locations">
<Collage {photos} /> <InteractiveGlobe />
</section>
<div class="homepage__ctas"> <ScrollingTitle tag="p" class="title-world mask">
<DiscoverText /> <SplitText text="World" mode="chars" />
</ScrollingTitle>
<ListCTAs> <Locations {locations} />
<li> </section>
<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>
<section class="homepage__locations" id="locations"> <div class="grid-modules">
<InteractiveGlobe /> <div class="container grid">
<div class="wrap">
<ScrollingTitle tag="p" class="title-world mask"> <ShopModule />
<SplitText text="World" mode="chars" /> <NewsletterModule />
</ScrollingTitle> </div>
<Locations {locations} />
</section>
<div class="grid-modules">
<div class="container grid">
<div class="wrap">
<ShopModule />
<NewsletterModule />
</div> </div>
</div> </div>
</div> </main>
</PageTransition> </PageTransition>

View File

@@ -218,148 +218,149 @@
<svelte:window bind:scrollY /> <svelte:window bind:scrollY />
<PageTransition>
<main class="location-page">
<section class="location-page__intro grid" bind:this={introEl}>
<h1 class="title" class:is-short={location.name.length <= 4}>
<span class="housesof mask">
<strong class="word">Houses</strong>
<span class="of">of</span>
</span>
<strong class="city mask">
<span class="word">{location.name}</span>
</strong>
</h1>
<PageTransition name="location-page"> <div class="location-page__description grid">
<section class="location-page__intro grid" bind:this={introEl}> <div class="wrap">
<h1 class="title" class:is-short={location.name.length <= 4}> <div class="text-medium">
<span class="housesof mask"> Houses of {location.name} {location.description ?? 'has no description yet'}
<strong class="word">Houses</strong> </div>
<span class="of">of</span> <div class="info">
</span> <p class="text-label">
<strong class="city mask"> Photos by
<span class="word">{location.name}</span> {#each location.credits as { credit_id: { name, website }}}
</strong> {#if website}
</h1> <a href={website} target="_blank" rel="noopener external">
{name}
<div class="location-page__description grid"> </a>
<div class="wrap"> {:else}
<div class="text-medium"> <span>{name}</span>
Houses of {location.name} {location.description ?? 'has no description yet'} {/if}
</div> {/each}
<div class="info">
<p class="text-label">
Photos by
{#each location.credits as { credit_id: { name, website }}}
{#if website}
<a href={website} target="_blank" rel="noopener external">
{name}
</a>
{:else}
<span>{name}</span>
{/if}
{/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} {#if latestPhoto}
</div> &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>
{/if}
</div>
<div class="ctas"> <div class="ctas">
<Button url="/locations" text="Change location" class="shadow-small"> <Button url="/locations" text="Change location" class="shadow-small">
<IconEarth /> <IconEarth />
</Button>
{#if location.has_poster}
<Button url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
<!-- <IconEarth /> -->
</Button> </Button>
{/if}
</div>
</div>
</div>
{#if hasIllustration} {#if location.has_poster}
<picture class="location-page__illustration" style:--parallax-y="{illustrationOffsetY}px"> <Button url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.illustration_desktop_2x.id, 'illustration-desktop-2x')}> <!-- <IconEarth /> -->
<source media="(min-width: 768px)" srcset={getAssetUrlKey(location.illustration_desktop.id, 'illustration-desktop-1x')}> </Button>
<img {/if}
src={getAssetUrlKey(location.illustration_mobile.id, 'illustration-mobile')}
width={320}
height={824}
alt="Illustration for {location.name}"
decoding="async"
/>
</picture>
{/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 class="location-page__next container">
<Pagination
ended={ended}
current={currentPhotosAmount}
total={totalPhotos}
on:click={!ended && loadMorePhotos}
>
{#if !ended}
<p class="more">See more photos</p>
{:else}
<p>You've seen it all!</p>
{/if}
</Pagination>
{#if ended}
<div class="grid-modules">
<div class="container grid">
<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 location.acknowledgement} {#if hasIllustration}
<div class="acknowledgement"> <picture class="location-page__illustration" style:--parallax-y="{illustrationOffsetY}px">
<Image <source media="(min-width: 1200px)" srcset={getAssetUrlKey(location.illustration_desktop_2x.id, 'illustration-desktop-2x')}>
class="flag" <source media="(min-width: 768px)" srcset={getAssetUrlKey(location.illustration_desktop.id, 'illustration-desktop-1x')}>
id={location.country.flag.id} <img
sizeKey="square-small" src={getAssetUrlKey(location.illustration_mobile.id, 'illustration-mobile')}
width={32} height={32} width={320}
alt="Flag of {location.country.name}" height={824}
alt="Illustration for {location.name}"
decoding="async"
/> />
<p>{location.acknowledgement}</p> </picture>
</div>
{/if} {/if}
</section> </section>
{:else}
<div class="location-page__message"> {#if photos.length}
<p> <section class="location-page__houses" bind:this={photosListEl} data-sveltekit-noscroll>
No photos available for {location.name}.<br> {#each photos as { title, image: { id, title: alt, width, height }, slug, city, date_taken }, index}
Come back later! <House
</p> {title}
</div> photoId={id}
{/if} 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 class="location-page__next container">
<Pagination
ended={ended}
current={currentPhotosAmount}
total={totalPhotos}
on:click={!ended && loadMorePhotos}
>
{#if !ended}
<p class="more">See more photos</p>
{:else}
<p>You've seen it all!</p>
{/if}
</Pagination>
{#if ended}
<div class="grid-modules">
<div class="container grid">
<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>
{/if}
{#if location.acknowledgement}
<div class="acknowledgement">
<Image
class="flag"
id={location.country.flag.id}
sizeKey="square-small"
width={32} height={32}
alt="Flag of {location.country.name}"
/>
<p>{location.acknowledgement}</p>
</div>
{/if}
</section>
{:else}
<div class="location-page__message">
<p>
No photos available for {location.name}.<br>
Come back later!
</p>
</div>
{/if}
</main>
</PageTransition> </PageTransition>

View File

@@ -305,98 +305,100 @@
{/if} {/if}
<PageTransition name="photo-page"> <PageTransition>
<div class="container grid"> <main class="photo-page">
<p class="photo-page__notice text-label">Tap for fullscreen</p> <div class="container grid">
<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"> <svg width="12" height="12">
<use xlink:href="#cross"> <use xlink:href="#cross">
</svg> </svg>
</ButtonCircle> </ButtonCircle>
<div class="photo-page__carousel"> <div class="photo-page__carousel">
<div class="photo-page__images" use:swipe on:swipe={handleSwipe} on:tap={toggleFullscreen}> <div class="photo-page__images" use:swipe on:swipe={handleSwipe} on:tap={toggleFullscreen}>
{#each visiblePhotos as { id, image, title }, index (id)} {#each visiblePhotos as { id, image, title }, index (id)}
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}"> <div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
<Image <Image
class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}" class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}"
id={image.id} id={image.id}
alt={title} alt={title}
sizeKey="photo-list" sizeKey="photo-list"
sizes={{ sizes={{
small: { width: 500 }, small: { width: 500 },
medium: { width: 850 }, medium: { width: 850 },
large: { width: 1280 }, large: { width: 1280 },
}} }}
ratio={1.5} 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">
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} on:click={goToPrevious}> <div class="photo-page__index title-index">
<IconArrow color="pink" flip={true} /> <SplitText text="{(currentPhotoIndex < 10) ? '0' : ''}{currentPhotoIndex}" mode="chars" />
</ButtonCircle> </div>
<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="photo-page__index title-index"> <div class="detail text-info">
<SplitText text="{(currentPhotoIndex < 10) ? '0' : ''}{currentPhotoIndex}" mode="chars" /> <a href="/{location.country.slug}/{location.slug}" data-sveltekit-prefetch data-sveltekit-noscroll>
</div> <Icon class="icon" icon="map-pin" label="Map pin" />
</div> <span>
{#if currentPhoto.city}
<div class="photo-page__info"> {currentPhoto.city}, {location.name}, {location.country.name}
<h1 class="title-medium">{currentPhoto.title}</h1> {:else}
{location.name}, {location.country.name}
<div class="detail text-info"> {/if}
<a href="/{location.country.slug}/{location.slug}" data-sveltekit-prefetch data-sveltekit-noscroll> </span>
<Icon class="icon" icon="map-pin" label="Map pin" /> </a>
<span> {#if currentPhoto.date_taken}
{#if currentPhoto.city} <span class="sep">&middot;</span>
{currentPhoto.city}, {location.name}, {location.country.name} <time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM YYYY')}</time>
{:else} {/if}
{location.name}, {location.country.name} </div>
{/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} on:click={toggleFullscreen} <div class="photo-page__fullscreen" bind:this={fullscreenEl} on:click={toggleFullscreen}
in:fade={{ easing: quartOut, duration: 1000 }} in:fade={{ easing: quartOut, duration: 1000 }}
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }} out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}
> >
<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>
</div> {/if}
{/if} </main>
</PageTransition> </PageTransition>

View File

@@ -180,215 +180,216 @@
image={about.seo_image ? getAssetUrlKey(about.seo_image.id, 'share-image') : null} image={about.seo_image ? getAssetUrlKey(about.seo_image.id, 'share-image') : null}
/> />
<PageTransition>
<main class="about">
<Banner
title="About"
image={{
id: '699b4050-6bbf-4a40-be53-d84aca484f9d',
alt: 'Photo caption',
}}
/>
<PageTransition name="about"> <section class="about__introduction">
<Banner <div class="container grid">
title="About" <h2 class="title-small">{about.intro_title}</h2>
image={{ <div class="heading text-big">
id: '699b4050-6bbf-4a40-be53-d84aca484f9d', {@html about.intro_heading}
alt: 'Photo caption',
}}
/>
<section class="about__introduction">
<div class="container grid">
<h2 class="title-small">{about.intro_title}</h2>
<div class="heading text-big">
{@html about.intro_heading}
</div>
<div class="text text-small">
{@html about.intro_text}
</div>
</div>
</section>
<section class="about__creation">
<div class="container grid">
<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 data-sveltekit-prefetch>
<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>
</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> </div>
<ol> <div class="text text-small">
{#each about.process_steps as { title }, index} {@html about.intro_text}
<li class:is-active={index === currentStep}> </div>
<a href="#step-{index + 1}" class="title-big">
<span>{title}</span>
</a>
</li>
{/each}
</ol>
</aside>
<div class="steps">
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
<ProcessStep
{index} {text}
image={image ?? undefined}
video={video_mp4 && video_webm ? {
mp4: video_mp4.id,
webm: video_webm.id
} : undefined}
visible={index === currentStep}
/>
{/each}
</div> </div>
</div> </section>
</section>
<section class="about__photos" bind:this={photosGridEl}> <section class="about__creation">
<div class="container-wide"> <div class="container grid">
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px"> <figure class="first-photo">
{#each photos as { image: { id }, title }, index} <Image
<AboutGridPhoto class="grid-photo" class="picture shadow-box-dark"
{id} id={about.intro_firstphoto.id}
alt={title} alt={about.intro_firstphoto.title}
disabled={fadedPhotosIndexes.includes(index)} sizeKey="photo-list"
sizes={{
small: { width: 400 },
medium: { width: 600 },
large: { width: 800 },
}}
ratio={1.5}
/> />
{/each} <figcaption class="text-info">
</div> {about.intro_firstphoto_caption}<br>
</div> in
</section> <a href="/{about.intro_firstlocation.country.slug}/{about.intro_firstlocation.slug}" data-sveltekit-noscroll data-sveltekit-prefetch>
<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>
<section class="about__interest container grid"> <h2 class="title-small" data-reveal>{about.creation_title}</h2>
<div class="container grid"> <div class="heading text-huge" data-reveal>
<h2 class="title-xl">{about.contact_title}</h2> {@html about.creation_heading}
<div class="blocks"> </div>
{#each about.contact_blocks as { title, text, link, button }}
<div class="block"> <div class="text text-small" data-reveal>
<h3 class="text-label">{title}</h3> {@html about.creation_text}
<div class="text text-normal"> </div>
{@html text}
</div> <figure class="picture portrait-photo" data-reveal-image>
<div class="button-container"> <Image
{#if link} class="shadow-box-dark"
{#key emailCopied === link} id={about.creation_portrait.id}
<div class="wrap" alt={about.creation_portrait.title}
in:fly={{ y: 4, duration: 325, easing: quartOutSvelte, delay: 250 }} sizeKey="photo-list"
out:fade={{ duration: 250, easing: quartOutSvelte }} sizes={{
use:mailtoClipboard small: { width: 400 },
on:copied={({ detail }) => { medium: { width: 750 },
emailCopied = detail.email }}
// Clear timeout and add timeout to hide message ratio={1.425}
clearTimeout(emailCopiedTimeout) />
emailCopiedTimeout = setTimeout(() => emailCopied = null, 2500) </figure>
}} <span class="portrait-photo__caption text-info">
> {about.creation_portrait_caption}
{#if emailCopied !== link} </span>
<Button size="small" url="mailto:{link}" text={button} /> </div>
{:else} </section>
<span class="clipboard">Email copied in clipboard</span>
{/if} <section class="about__present">
</div> <div class="container grid">
{/key} <figure class="picture" data-reveal-image>
{/if} <Image
</div> 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> </div>
{/each}
<ol>
{#each about.process_steps as { title }, index}
<li class:is-active={index === currentStep}>
<a href="#step-{index + 1}" class="title-big">
<span>{title}</span>
</a>
</li>
{/each}
</ol>
</aside>
<div class="steps">
{#each about.process_steps as { text, image, video_mp4, video_webm }, index}
<ProcessStep
{index} {text}
image={image ?? undefined}
video={video_mp4 && video_webm ? {
mp4: video_mp4.id,
webm: video_webm.id
} : undefined}
visible={index === currentStep}
/>
{/each}
</div>
</div> </div>
</div> </section>
</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>
{/each}
</div>
</div>
</section>
</main>
</PageTransition> </PageTransition>

View File

@@ -17,7 +17,7 @@
import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte' import InteractiveGlobe from '$components/organisms/InteractiveGlobe.svelte'
export let data: PageData export let data: PageData
const { credits, credit } = data const { credit } = data
onMount(() => { onMount(() => {
@@ -63,22 +63,47 @@
<Metas <Metas
title="Credits Houses Of" title="Credits Houses Of"
description={credits.text} description={data.credits.text}
/> />
<PageTransition>
<main class="credits">
<Heading
text={data.credits.text}
/>
<PageTransition name="credits"> <section class="credits__list">
<Heading <div class="grid container">
text={credits.text} {#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 credits.list as { title, credits }}
<div class="credits__category grid"> <div class="credits__category grid">
<h2 class="title-small">{title}</h2> <h2 class="title-small">Photography</h2>
<ul> <ul>
{#each credits as { name, role, website }} {#each credit as { name, website, location }}
<li> <li>
<dl> <dl>
<dt> <dt>
@@ -91,57 +116,33 @@
{/if} {/if}
</dt> </dt>
<dd> <dd>
{role} <ul data-sveltekit-noscroll>
{#each location as loc}
{#if loc.location_id}
<li>
<a href="/{loc.location_id.country.slug}/{loc.location_id.slug}" tabindex="0">
<Image
id={loc.location_id.country.flag.id}
sizeKey="square-small"
width={16}
height={16}
alt="Flag of {loc.location_id.country.slug}"
/>
<span>{loc.location_id.name}</span>
</a>
</li>
{/if}
{/each}
</ul>
</dd> </dd>
</dl> </dl>
</li> </li>
{/each} {/each}
</ul> </ul>
</div> </div>
{/each}
<div class="credits__category grid">
<h2 class="title-small">Photography</h2>
<ul>
{#each credit as { name, website, location }}
<li>
<dl>
<dt>
{#if website}
<h3>
<a href={website} rel="noopener external" target="_blank" tabindex="0">{name}</a>
</h3>
{:else}
<h3>{name}</h3>
{/if}
</dt>
<dd>
<ul data-sveltekit-noscroll>
{#each location as loc}
{#if loc.location_id}
<li>
<a href="/{loc.location_id.country.slug}/{loc.location_id.slug}" tabindex="0">
<Image
id={loc.location_id.country.flag.id}
sizeKey="square-small"
width={16}
height={16}
alt="Flag of {loc.location_id.country.slug}"
/>
<span>{loc.location_id.name}</span>
</a>
</li>
{/if}
{/each}
</ul>
</dd>
</dl>
</li>
{/each}
</ul>
</div> </div>
</div> </section>
</section>
<InteractiveGlobe type="cropped" /> <InteractiveGlobe type="cropped" />
</main>
</PageTransition> </PageTransition>

View File

@@ -23,18 +23,20 @@
image="" image=""
/> />
<PageTransition name="explore"> <PageTransition>
<Heading {text} /> <main class="explore">
<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>
</PageTransition> </PageTransition>

View File

@@ -336,162 +336,164 @@
/> />
<PageTransition name="photos-page"> <PageTransition>
<section class="photos-page__intro" <main class="photos-page">
class:is-passed={scrolledPastIntro} <section class="photos-page__intro"
> 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}
> >
<div class="filters__bar"> <ScrollingTitle tag="h1" text="Houses">
<span class="text-label filters__label">Filter photos</span> <SplitText text="Houses" mode="chars" />
<ul> </ScrollingTitle>
<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"> <DiscoverText />
{#if filtered}
<button class="reset button-link"
on:click={resetFiltered}
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
>
Reset
</button>
{/if}
</div>
</div>
</div>
</section>
<section class="photos-page__content" bind:this={photosContentEl} style:--margin-sides="{sideMargins}px"> <div class="filters"
<div class="grid container"> class:is-over={filtersOver}
{#if photos} class:is-transitioning={filtersTransitioning}
<div class="photos-page__grid" bind:this={photosGridEl} data-sveltekit-noscroll data-sveltekit-prefetch> class:is-visible={filtersVisible}
{#each photos as { id, image, slug, location, title, city }, index (id)} >
<figure class="photo shadow-photo"> <div class="filters__bar">
<a href="/{location.country.slug}/{location.slug}/{slug}" tabindex="0"> <span class="text-label filters__label">Filter photos</span>
<Image <ul>
id={image.id} <li>
sizeKey="photo-grid" <Select
sizes={{ name="country" id="filter_country"
small: { width: 500 }, options={[
medium: { width: 900 }, {
large: { width: 1440 }, value: defaultCountry,
}} name: 'Worldwide',
ratio={1.5} default: true,
alt={image.title} selected: filterCountry === defaultCountry,
/> },
</a> ...countries.map(({ slug, name }) => ({
<figcaption> value: slug,
<PostCard name,
street={title} selected: filterCountry === slug,
location={city ? `${city}, ${location.name}` : location.name} }))
region={location.region} ]}
country={location.country.name} on:change={handleCountryChange}
flagId={location.country.flag.id} value={filterCountry}
size={isSmall(index) ? 'small' : null} >
/> {#if countryFlagId}
</figcaption> <Image
</figure> class="icon"
{/each} id={countryFlagId}
</div> 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="controls grid"> <div class="filters__actions">
<p class="controls__date" title={dayjs(latestPhoto.date_created).format('DD/MM/YYYY, hh:mm')}> {#if filtered}
Last updated: <time datetime={dayjs(latestPhoto.date_created).format('YYYY-MM-DD')}>{dayjs().to(dayjs(latestPhoto.date_created))}</time> <button class="reset button-link"
</p> on:click={resetFiltered}
transition:fly={{ y: 4, duration: 600, easing: quartOutSvelte }}
<Button >
tag="button" Reset
text={!ended ? 'See more photos' : "You've seen it all!"} </button>
size="large" color="beige" {/if}
on:click={loadMorePhotos}
disabled={ended}
/>
<div class="controls__count">
<span class="current">{currentPhotosAmount}</span>
<span>/</span>
<span class="total">{totalPhotos}</span>
</div> </div>
</div> </div>
{:else if !filteredCountryExists} </div>
<div class="photos-page__message"> </section>
<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"> <section class="photos-page__content" bind:this={photosContentEl} style:--margin-sides="{sideMargins}px">
<div class="wrap"> <div class="grid container">
<ShopModule /> {#if photos}
<NewsletterModule theme="light" /> <div class="photos-page__grid" bind:this={photosGridEl} data-sveltekit-noscroll data-sveltekit-prefetch>
{#each photos as { id, image, slug, location, title, city }, index (id)}
<figure class="photo shadow-photo">
<a href="/{location.country.slug}/{location.slug}/{slug}" tabindex="0">
<Image
id={image.id}
sizeKey="photo-grid"
sizes={{
small: { width: 500 },
medium: { width: 900 },
large: { width: 1440 },
}}
ratio={1.5}
alt={image.title}
/>
</a>
<figcaption>
<PostCard
street={title}
location={city ? `${city}, ${location.name}` : location.name}
region={location.region}
country={location.country.name}
flagId={location.country.flag.id}
size={isSmall(index) ? 'small' : null}
/>
</figcaption>
</figure>
{/each}
</div>
<div class="controls grid">
<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>
</p>
<Button
tag="button"
text={!ended ? 'See more photos' : "You've seen it all!"}
size="large" color="beige"
on:click={loadMorePhotos}
disabled={ended}
/>
<div class="controls__count">
<span class="current">{currentPhotosAmount}</span>
<span>/</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> </div>
</div> </section>
</section> </main>
</PageTransition> </PageTransition>

View File

@@ -25,17 +25,19 @@
/> />
<PageTransition name="shop-page"> <PageTransition>
<ShopHeader /> <main class="shop-page">
<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>
</div> </section>
</section>
<PostersGrid {posters} /> <PostersGrid {posters} />
</main>
</PageTransition> </PageTransition>

View File

@@ -26,13 +26,15 @@
/> />
<PageTransition name="shop-page"> <PageTransition>
<ShopHeader {product} /> <main class="shop-page">
<ShopHeader {product} />
<PosterLayout <PosterLayout
product={product} product={product}
shopProduct={shopProduct} shopProduct={shopProduct}
/> />
<PostersGrid {posters} /> <PostersGrid {posters} />
</main>
</PageTransition> </PageTransition>

View File

@@ -24,13 +24,15 @@
/> />
<PageTransition name="shop-page"> <PageTransition>
<ShopHeader product={data.product} /> <main class="shop-page">
<ShopHeader product={data.product} />
<PosterLayout <PosterLayout
product={data.product} product={data.product}
shopProduct={data.shopProduct} shopProduct={data.shopProduct}
/> />
<PostersGrid {posters} /> <PostersGrid {posters} />
</main>
</PageTransition> </PageTransition>

View File

@@ -65,32 +65,34 @@
description="Subscribe to the Houses Of newsletter to be notified when new photos or locations are added to the site and when more prints are available on our shop" description="Subscribe to the Houses Of newsletter to be notified when new photos or locations are added to the site and when more prints are available on our shop"
/> />
<PageTransition name="subscribe"> <PageTransition>
<div class="subscribe__top"> <main class="subscribe">
<Heading <div class="subscribe__top">
text={data.newsletter_page_text} <Heading
/> 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>
{#if issues.length > 1} <section class="subscribe__issues">
<h2 class="title-small">Past Issues</h2> <h2 class="title-small">Latest Issue</h2>
<ul> <div class="issue-container">
{#each issues.slice(1) as { issue, title, date_sent: date, link, thumbnail }} <NewsletterIssue size="large" date={latestIssue.date_sent} {...latestIssue} />
<li class="issue-container"> </div>
<NewsletterIssue {issue} {title} {link} {thumbnail} {date} />
</li>
{/each}
</ul>
{/if}
</section>
<InteractiveGlobe type="cropped" /> {#if issues.length > 1}
<h2 class="title-small">Past Issues</h2>
<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> </PageTransition>

View File

@@ -23,25 +23,27 @@
/> />
<PageTransition name="terms"> <PageTransition>
<Heading text="Everything you need to know about using our website or buying our products" /> <main class="terms">
<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>
</PageTransition> </PageTransition>

View File

@@ -1,4 +1,4 @@
:global(.about) { .about {
:global(.picture) { :global(.picture) {
overflow: hidden; overflow: hidden;
background: $color-primary-tertiary20; background: $color-primary-tertiary20;
@@ -9,9 +9,7 @@
border-radius: 16px; border-radius: 16px;
} }
} }
}
.about {
/* /*
** Introduction ** Introduction
*/ */

View File

@@ -23,9 +23,7 @@
} }
} }
} }
}
:global(.page-error) {
// Globe // Globe
:global(.globe) { :global(.globe) {
margin-top: 96px; margin-top: 96px;

View File

@@ -1,9 +1,10 @@
// Explore Page // Explore Page
:global(.explore) { .explore {
overflow: hidden; overflow: hidden;
}
:global(.explore__locations) { &__locations {
@include bp (sm, max) { @include bp (sm, max) {
margin-top: 72px; margin-top: 72px;
}
} }
} }

View File

@@ -1,9 +1,7 @@
:global(.homepage) {
overflow: hidden;
}
// Homepage // Homepage
.homepage { .homepage {
overflow: hidden;
// Intro Section // Intro Section
&__intro { &__intro {
padding-bottom: calc(96px + 20vw); padding-bottom: calc(96px + 20vw);

View File

@@ -1,9 +1,7 @@
:global(.location-page) {
background: #fff;
}
// Location Page // Location Page
.location-page { .location-page {
background: #fff;
// Intro // Intro
&__intro { &__intro {
position: relative; position: relative;

View File

@@ -1,5 +1,34 @@
:global(.shop-page) { .shop-page {
position: relative; position: relative;
// Error
&__error {
padding: 64px 0;
background: $color-cream;
color: $color-text;
text-align: center;
@include bp (sm) {
padding: clamp(64px, 12vw, 160px) 0;
text-align: left;
}
:global(.inner) {
grid-column: 1 / span 8;
@include bp (sm) {
grid-column: 3 / span 12;
}
}
:global(h2) {
margin-bottom: 8px;
color: $color-secondary;
@include bp (sm) {
margin-bottom: 16px;
}
}
}
} }
// Nav // Nav
@@ -21,40 +50,11 @@
--inset: 32px; --inset: 32px;
justify-content: space-between; justify-content: space-between;
} }
}
// Visible state // Visible state
:global(.shop-location.is-visible) { &.is-visible {
transform: translate3d(0,0,0); transform: translate3d(0,0,0);
} }
// Error
:global(.shop-page__error) {
padding: 64px 0;
background: $color-cream;
color: $color-text;
text-align: center;
@include bp (sm) {
padding: clamp(64px, 12vw, 160px) 0;
text-align: left;
}
:global(.inner) {
grid-column: 1 / span 8;
@include bp (sm) {
grid-column: 3 / span 12;
}
}
:global(h2) {
margin-bottom: 8px;
color: $color-secondary;
@include bp (sm) {
margin-bottom: 16px;
}
}
} }
// Notifications // Notifications

View File

@@ -22,12 +22,13 @@
// Past Issues // Past Issues
&__issues { &__issues {
margin: 64px auto 0; margin: 64px auto 96px;
padding: 0 20px; padding: 0 20px;
@include bp (sm) { @include bp (sm) {
max-width: 800px; max-width: 800px;
margin-top: 0; margin-top: 0;
margin-bottom: 156px;
} }
// Title // Title
@@ -62,13 +63,4 @@
} }
} }
} }
}
// Globe
:global(.subscribe .globe) {
margin-top: 96px;
@include bp (sm) {
margin-top: 156px;
}
} }

View File

@@ -1,4 +1,4 @@
:global(.photo-page) { .photo-page {
position: relative; position: relative;
width: 100vw; width: 100vw;
height: var(--vh); height: var(--vh);
@@ -16,420 +16,418 @@
} }
} }
.photo-page { // Carousel
// Carousel &__carousel {
&__carousel { position: absolute;
position: absolute; top: 0;
top: 0; left: 50%;
left: 50%; transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0); grid-column: 1 / -1;
grid-column: 1 / -1; display: grid;
display: grid; grid-row-gap: 20px;
grid-row-gap: 20px; width: calc(100% - 40px);
width: calc(100% - 40px); height: 100%;
height: 100%; max-width: 720px;
max-width: 720px; position: relative;
@include bp (md) {
position: relative; position: relative;
max-width: none;
@include bp (md) { margin: auto 0;
position: relative; grid-column: 2 / span 17;
max-width: none; grid-row-gap: 40px;
margin: auto 0; transform: translate3d(-50%, 2.5%, 0);
grid-column: 2 / span 17;
grid-row-gap: 40px;
transform: translate3d(-50%, 2.5%, 0);
}
@include bp (sd) {
grid-column: 3 / span 16;
}
} }
@include bp (sd) {
// Images grid-column: 3 / span 16;
&__images {
position: relative;
width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
touch-action: none;
} }
}
&__picture { // Images
--opacity: 1; &__images {
position: relative;
width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
touch-action: none;
}
&__picture {
--opacity: 1;
--scale: 0.6;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: 0%;
position: absolute;
z-index: 8;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateZ(0);
will-change: transform, opacity;
@include bp (md) {
--scale: 0.6; --scale: 0.6;
--rotate: 0deg; --rotate: 5deg;
--offset-x: 0%; --offset-x: 28.5%;
--offset-y: 0%; --offset-y: 0%;
top: 0;
left: 0;
transform-origin: bottom right;
}
:global(.photo) {
position: absolute; position: absolute;
z-index: 8;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
transform: translateZ(0); transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate));
will-change: transform, opacity; transition: opacity 1s var(--ease-quart), transform 1s var(--ease-quart);
will-change: transform;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
@include bp (md) { :global(picture) {
--scale: 0.6;
--rotate: 5deg;
--offset-x: 28.5%;
--offset-y: 0%;
top: 0;
left: 0;
transform-origin: bottom right;
}
:global(.photo) {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate)); overflow: hidden;
transition: opacity 1s var(--ease-quart), transform 1s var(--ease-quart); background: $color-primary;
will-change: transform; cursor: default;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
:global(picture) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: $color-primary;
cursor: default;
}
:global(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
opacity: var(--opacity);
transform: translateZ(0);
pointer-events: none;
user-select: none;
transition: opacity 1s var(--ease-quart);
}
} }
:global(img) {
// Ratio is not landscape display: block;
:global(.not-landscape) { width: 100%;
:global(img) { height: 100%;
object-fit: contain; object-fit: cover;
} opacity: var(--opacity);
} transform: translateZ(0);
// Hidden photo over
&.is-0 {
--scale: 1.03;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: -7%;
z-index: 9;
pointer-events: none; pointer-events: none;
user-select: none;
:global(.photo) { transition: opacity 1s var(--ease-quart);
opacity: 0;
}
@include bp (md) {
--scale: 1.075;
--rotate: -1deg;
--offset-x: -9%;
--offset-y: 0%;
}
}
// First visible photo
&.is-1 {
--scale: 1;
--rotate: 0deg;
--offset-y: 0%;
@include bp (md) {
--offset-x: 0%;
--offset-y: 0%;
}
}
&.is-2 {
--scale: 0.9;
--opacity: 0.75;
--offset-y: 12%;
z-index: 7;
@include bp (md) {
--scale: 0.9;
--rotate: 1deg;
--offset-x: 9.5%;
--offset-y: 0%;
}
}
&.is-3 {
--scale: 0.83;
--opacity: 0.55;
--offset-y: 20%;
z-index: 6;
@include bp (md) {
--scale: 0.83;
--rotate: 2deg;
--offset-x: 16.25%;
--offset-y: 0%;
}
}
&.is-4 {
--scale: 0.75;
--opacity: 0.45;
--offset-y: 27.5%;
z-index: 5;
@include bp (md) {
--scale: 0.75;
--rotate: 3deg;
--offset-x: 22%;
--offset-y: 0%;
}
}
&.is-5 {
--scale: 0.68;
--opacity: 0.25;
--offset-y: 33%;
z-index: 4;
@include bp (md) {
--scale: 0.68;
--rotate: 4deg;
--offset-x: 27%;
--offset-y: 0%;
}
}
&.is-6 {
--opacity: 0.25;
z-index: 3;
:global(.photo) {
opacity: 0;
}
}
&.is-7 {
:global(.photo) {
opacity: 0;
}
} }
} }
// Infos // Ratio is not landscape
&__info { :global(.not-landscape) {
bottom: 0; :global(img) {
margin-top: auto; object-fit: contain;
margin-bottom: 40px;
padding: 0 20px;
text-align: center;
@include bp (md) {
position: static;
margin-top: 0;
padding: 0;
text-align: left;
}
@include bp (lg) {
display: grid;
grid-template-columns: 50% 50%;
align-items: baseline;
}
h1 {
color: $color-secondary;
font-size: clamp(#{rem(18px)}, 5.5vw, #{rem(28px)});
line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
}
}
// Details
.detail {
display: inline-block;
align-items: center;
color: rgba($color-tertiary, 0.7);
line-height: 1.5;
@include bp (lg) {
margin-left: auto;
text-align: right;
padding-left: 12px;
}
a {
color: inherit;
text-decoration: none;
transition: color 0.3s;
&:hover {
color: $color-tertiary;
}
}
// Icon
:global(.icon) {
display: inline-block;
width: 17px;
height: 17px;
margin-top: -5px;
margin-right: 4px;
}
// Separator
.sep {
display: inline-block;
margin: 0 4px;
line-height: 1;
}
} }
} }
// Index // Hidden photo over
&__index { &.is-0 {
position: absolute; --scale: 1.03;
z-index: 1; --rotate: 0deg;
left: 50%; --offset-x: 0%;
bottom: calc(91% + 1vw); --offset-y: -7%;
display: block; z-index: 9;
line-height: 1;
color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
white-space: nowrap;
pointer-events: none; pointer-events: none;
user-select: none;
overflow: hidden;
@include bp (md, max) { :global(.photo) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)}); opacity: 0;
} }
@include bp (md) { @include bp (md) {
top: 50%; --scale: 1.075;
left: auto; --rotate: -1deg;
right: calc(-1 * min(30vw, 400px)); --offset-x: -9%;
width: 350px; --offset-y: 0%;
text-align: center;
bottom: auto;
transform: translate3d(0, -50%, 0);
} }
@include bp (lg) { }
right: calc(-1 * min(25vw, 460px)); // First visible photo
&.is-1 {
--scale: 1;
--rotate: 0deg;
--offset-y: 0%;
@include bp (md) {
--offset-x: 0%;
--offset-y: 0%;
}
}
&.is-2 {
--scale: 0.9;
--opacity: 0.75;
--offset-y: 12%;
z-index: 7;
@include bp (md) {
--scale: 0.9;
--rotate: 1deg;
--offset-x: 9.5%;
--offset-y: 0%;
}
}
&.is-3 {
--scale: 0.83;
--opacity: 0.55;
--offset-y: 20%;
z-index: 6;
@include bp (md) {
--scale: 0.83;
--rotate: 2deg;
--offset-x: 16.25%;
--offset-y: 0%;
}
}
&.is-4 {
--scale: 0.75;
--opacity: 0.45;
--offset-y: 27.5%;
z-index: 5;
@include bp (md) {
--scale: 0.75;
--rotate: 3deg;
--offset-x: 22%;
--offset-y: 0%;
}
}
&.is-5 {
--scale: 0.68;
--opacity: 0.25;
--offset-y: 33%;
z-index: 4;
@include bp (md) {
--scale: 0.68;
--rotate: 4deg;
--offset-x: 27%;
--offset-y: 0%;
}
}
&.is-6 {
--opacity: 0.25;
z-index: 3;
:global(.photo) {
opacity: 0;
}
}
&.is-7 {
:global(.photo) {
opacity: 0;
}
}
}
// Infos
&__info {
bottom: 0;
margin-top: auto;
margin-bottom: 40px;
padding: 0 20px;
text-align: center;
@include bp (md) {
position: static;
margin-top: 0;
padding: 0;
text-align: left;
}
@include bp (lg) {
display: grid;
grid-template-columns: 50% 50%;
align-items: baseline;
}
h1 {
color: $color-secondary;
font-size: clamp(#{rem(18px)}, 5.5vw, #{rem(28px)});
line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
} }
} }
// Controls // Details
&__controls { .detail {
display: none; display: inline-block;
align-items: center;
color: rgba($color-tertiary, 0.7);
line-height: 1.5;
@include bp (md) { @include bp (lg) {
position: absolute; margin-left: auto;
z-index: 20; text-align: right;
display: flex; padding-left: 12px;
left: -28px;
right: -28px;
top: 50%;
transform: translateY(-50%);
justify-content: space-between;
pointer-events: none;
} }
:global(button) { a {
pointer-events: auto; color: inherit;
text-decoration: none;
transition: color 0.3s;
// Prev button &:hover {
&:first-child { color: $color-tertiary;
& > :global(*:nth-child(2)) { }
transform: translate3d(100%, -50%, 0) rotate(180deg); }
}
// Hover // Icon
&:not([disabled]):hover { :global(.icon) {
& > :global(*:nth-child(1)) { display: inline-block;
transform: translate3d(-20%, 0, 0) rotate(180deg); width: 17px;
} height: 17px;
& > :global(*:nth-child(2)) { margin-top: -5px;
transform: translate3d(-50%, -50%, 0) rotate(180deg); margin-right: 4px;
} }
} // Separator
.sep {
display: inline-block;
margin: 0 4px;
line-height: 1;
}
}
}
// Index
&__index {
position: absolute;
z-index: 1;
left: 50%;
bottom: calc(91% + 1vw);
display: block;
line-height: 1;
color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
white-space: nowrap;
pointer-events: none;
user-select: none;
overflow: hidden;
@include bp (md, max) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)});
}
@include bp (md) {
top: 50%;
left: auto;
right: calc(-1 * min(30vw, 400px));
width: 350px;
text-align: center;
bottom: auto;
transform: translate3d(0, -50%, 0);
}
@include bp (lg) {
right: calc(-1 * min(25vw, 460px));
}
}
// Controls
&__controls {
display: none;
@include bp (md) {
position: absolute;
z-index: 20;
display: flex;
left: -28px;
right: -28px;
top: 50%;
transform: translateY(-50%);
justify-content: space-between;
pointer-events: none;
}
:global(button) {
pointer-events: auto;
// Prev button
&:first-child {
& > :global(*:nth-child(2)) {
transform: translate3d(100%, -50%, 0) rotate(180deg);
} }
// Hover // Hover
&:not([disabled]):hover { &:not([disabled]):hover {
background-color: $color-secondary; & > :global(*:nth-child(1)) {
color: #fff; transform: translate3d(-20%, 0, 0) rotate(180deg);
}
:global(svg:nth-child(2)) { & > :global(*:nth-child(2)) {
color: #fff; transform: translate3d(-50%, -50%, 0) rotate(180deg);
} }
} }
} }
}
// Fullscreen viewer // Hover
&__fullscreen { &:not([disabled]):hover {
position: absolute; background-color: $color-secondary;
z-index: 102; color: #fff;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $color-primary-darker;
.inner { :global(svg:nth-child(2)) {
width: 100%; color: #fff;
height: 100%;
}
// Photo
:global(picture) {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
cursor: pointer;
:global(img) {
display: block;
width: auto;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
} }
} }
}
}
// Close // Fullscreen viewer
:global(.close) { &__fullscreen {
$color-shadow: rgba(#000, 0.15); position: absolute;
position: absolute; z-index: 102;
z-index: 2; top: 0;
bottom: 24px; left: 0;
left: 50%; width: 100%;
transform: translateX(-50%); height: 100%;
box-shadow: background: $color-primary-darker;
0 6px 6px $color-shadow,
0 12px 12px $color-shadow, .inner {
0 24px 24px $color-shadow; width: 100%;
height: 100%;
}
// Photo
:global(picture) {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
cursor: pointer;
:global(img) {
display: block;
width: auto;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
} }
} }
// Notice // Close
&__notice { :global(.close) {
$color-shadow: rgba(#000, 0.15);
position: absolute; position: absolute;
top: 16px; z-index: 2;
left: 20px; bottom: 24px;
line-height: 44px; left: 50%;
color: rgba($color-tertiary, 0.5); transform: translateX(-50%);
box-shadow:
0 6px 6px $color-shadow,
0 12px 12px $color-shadow,
0 24px 24px $color-shadow;
}
}
@include bp (md) { // Notice
display: none; &__notice {
} position: absolute;
top: 16px;
left: 20px;
line-height: 44px;
color: rgba($color-tertiary, 0.5);
@include bp (md) {
display: none;
} }
} }