203 lines
5.7 KiB
Svelte
203 lines
5.7 KiB
Svelte
<script lang="ts">
|
||
import { browser } from '$app/env'
|
||
import { getContext, onMount } from 'svelte'
|
||
import anime from 'animejs'
|
||
import type { AnimeTimelineInstance } from 'animejs'
|
||
import { cartOpen } from '$utils/stores/shop'
|
||
// Components
|
||
import PageTransition from '$components/PageTransition.svelte'
|
||
import Metas from '$components/Metas.svelte'
|
||
import Image from '$components/atoms/Image.svelte'
|
||
import SiteTitle from '$components/atoms/SiteTitle.svelte'
|
||
import ButtonCart from '$components/atoms/ButtonCart.svelte'
|
||
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher.svelte'
|
||
import PostersGrid from '$components/organisms/PostersGrid.svelte'
|
||
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
||
|
||
export let product: any
|
||
export let shopProduct: any
|
||
|
||
const { shop, shopLocations, posters } = getContext('shop')
|
||
|
||
let introEl: HTMLElement
|
||
let navObserver: IntersectionObserver
|
||
let scrolledPastIntro = false
|
||
|
||
|
||
/**
|
||
* Transition: Anime timeline
|
||
*/
|
||
let timeline: AnimeTimelineInstance
|
||
|
||
if (browser) {
|
||
requestAnimationFrame(() => {
|
||
timeline = anime.timeline({
|
||
duration: 1600,
|
||
easing: 'easeOutQuart',
|
||
autoplay: false,
|
||
})
|
||
|
||
// Hero image
|
||
timeline.add({
|
||
targets: '.shop-page__background',
|
||
scale: [1.06, 1],
|
||
opacity: [0, 1],
|
||
duration: 2400,
|
||
}, 400)
|
||
|
||
// Intro top elements
|
||
timeline.add({
|
||
targets: '.shop-page__intro .top > *',
|
||
translateY: [-100, 0],
|
||
delay: anime.stagger(250),
|
||
}, 400)
|
||
|
||
// Intro navbar
|
||
timeline.add({
|
||
targets: '.shop-page__nav .container > *',
|
||
translateY: [100, 0],
|
||
delay: anime.stagger(250),
|
||
}, 700)
|
||
})
|
||
}
|
||
|
||
|
||
onMount(() => {
|
||
// Reveal the nav past the Intro
|
||
navObserver = new IntersectionObserver(entries => {
|
||
entries.forEach(entry => {
|
||
scrolledPastIntro = !entry.isIntersecting
|
||
})
|
||
}, {
|
||
threshold: 0,
|
||
rootMargin: '0px 0px 0px'
|
||
})
|
||
navObserver.observe(introEl)
|
||
|
||
// Transition in
|
||
requestAnimationFrame(() => {
|
||
timeline.play()
|
||
})
|
||
|
||
|
||
// Destroy
|
||
return () => {
|
||
navObserver && navObserver.unobserve(introEl)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<Metas
|
||
title="Shop – Houses Of"
|
||
description=""
|
||
image=""
|
||
/>
|
||
|
||
|
||
<PageTransition name="shop-page">
|
||
<section class="shop-page__intro" bind:this={introEl}>
|
||
<div class="top container">
|
||
<a href="/" class="back" sveltekit:noscroll>
|
||
<svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M4 1 1 4l3 3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
<span>Back to Houses Of</span>
|
||
</a>
|
||
|
||
<span class="shop-title">Shop</span>
|
||
</div>
|
||
|
||
<SiteTitle
|
||
variant="inline"
|
||
/>
|
||
|
||
<nav class="shop-page__nav">
|
||
<div class="container">
|
||
<p class="text-label">Shop your city</p>
|
||
<nav>
|
||
<ul>
|
||
{#each shopLocations as { name, slug }}
|
||
<li class:is-active={slug === product.location.slug}>
|
||
<a href="/shop/poster-{slug}" sveltekit:prefetch sveltekit:noscroll>
|
||
{name}
|
||
</a>
|
||
</li>
|
||
{/each}
|
||
</ul>
|
||
</nav>
|
||
|
||
<ButtonCart />
|
||
</div>
|
||
</nav>
|
||
|
||
<Image
|
||
class="shop-page__background"
|
||
id={shop.page_heroimage.id}
|
||
alt="photo"
|
||
sizeKey="hero"
|
||
sizes={{
|
||
large: { width: 1800, height: 1200 },
|
||
medium: { width: 1200, height: 800 },
|
||
small: { width: 700, height: 700 },
|
||
}}
|
||
/>
|
||
</section>
|
||
|
||
<nav class="shop-location"
|
||
class:is-visible={scrolledPastIntro}
|
||
class:is-overlaid={$cartOpen}
|
||
>
|
||
<ShopLocationSwitcher />
|
||
<ButtonCart />
|
||
</nav>
|
||
|
||
<section class="shop-page__about grid">
|
||
<div class="description grid text-normal">
|
||
<div class="description__inner">
|
||
{@html shop.about}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<PosterLayout
|
||
product={product}
|
||
shopProduct={shopProduct}
|
||
/>
|
||
|
||
<PostersGrid {posters} />
|
||
</PageTransition>
|
||
|
||
|
||
<script context="module" lang="ts">
|
||
import { getRandomElement } from '$utils/functions'
|
||
|
||
/** @type {import('@sveltejs/kit').Load} */
|
||
export async function load ({ url, params, fetch, session, stuff }) {
|
||
let shopProduct: any
|
||
|
||
// Get a random product from the API
|
||
const productAPI = getRandomElement(stuff.posters)
|
||
|
||
// Fetch this product on Swell API
|
||
if (productAPI) {
|
||
const shopProductRes = await fetch('/api/swell', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
action: 'getProduct',
|
||
productId: productAPI.product_id,
|
||
})
|
||
})
|
||
if (shopProductRes.ok) {
|
||
shopProduct = await shopProductRes.json()
|
||
}
|
||
}
|
||
|
||
|
||
return {
|
||
props: {
|
||
product: productAPI,
|
||
shopProduct,
|
||
}
|
||
}
|
||
}
|
||
</script> |