[wip] Rework Shop
- Change strategy for content fetching - Add a route per page instead of using __layout for all - Change the behaviors of the posters section (add a carousel on small screens) - Change Poster buttons styling and make interactions only for desktop
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Disable when changing filters on /photos and shop?
|
// Disable when changing filters on /photos and shop?
|
||||||
if (!$page.query.get('country') && !$page.path.includes('/shop/poster')) {
|
if (!$page.query.get('country') && !$page.path.includes('/shop')) {
|
||||||
await tick()
|
await tick()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Scroll back to top between page transitions
|
// Scroll back to top between page transitions
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
import { addToCart } from '$utils/functions/shop'
|
import { addToCart } from '$utils/functions/shop'
|
||||||
import { capitalizeFirstLetter } from '$utils/functions'
|
import { capitalizeFirstLetter } from '$utils/functions'
|
||||||
// Components
|
// Components
|
||||||
|
import SplitText from '$components/SplitText.svelte'
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
|
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
|
||||||
import Carousel from '$components/organisms/Carousel.svelte'
|
import Carousel from '$components/organisms/Carousel.svelte'
|
||||||
|
|
||||||
export let product: any
|
export let product: any
|
||||||
@@ -60,9 +62,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="poster-layout grid" id="poster">
|
<section class="poster-layout grid" id="poster">
|
||||||
<h2 class="title-huge">
|
<div class="poster-layout__title">
|
||||||
{product.location.name}
|
<ScrollingTitle tag="h2" label={product.location.name}>
|
||||||
</h2>
|
<SplitText mode="chars" text={product.location.name} />
|
||||||
|
</ScrollingTitle>
|
||||||
|
</div>
|
||||||
|
|
||||||
<aside class="poster-layout__buy">
|
<aside class="poster-layout__buy">
|
||||||
<div class="poster-layout__info">
|
<div class="poster-layout__info">
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
text="View"
|
text="View"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
tag="button"
|
||||||
text="Add to cart"
|
|
||||||
color="pinklight"
|
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
on:click={() => addToCart($cartId, product)}
|
on:click={() => addToCart($cartId, product)}
|
||||||
|
text="Add to cart"
|
||||||
|
color="pink"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { shopLocations } from '$utils/stores/shop'
|
import { getContext } from 'svelte'
|
||||||
|
|
||||||
export let isOver: boolean = false
|
export let isOver: boolean = false
|
||||||
|
|
||||||
|
const { shopLocations } = getContext('shop')
|
||||||
|
|
||||||
const classes = [
|
const classes = [
|
||||||
'shop-locationswitcher',
|
'shop-locationswitcher',
|
||||||
isOver && 'is-over',
|
isOver && 'is-over',
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
<use xlink:href="#icon-map-pin" />
|
<use xlink:href="#icon-map-pin" />
|
||||||
</svg>
|
</svg>
|
||||||
<select on:change={quickLocationChange}>
|
<select on:change={quickLocationChange}>
|
||||||
{#each $shopLocations as { name, slug }}
|
{#each shopLocations as { name, slug }}
|
||||||
<option value={slug} selected={slug === $page.params.name}>{name}</option>
|
<option value={slug} selected={slug === $page.params.name}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
115
src/components/organisms/PostersGrid.svelte
Normal file
115
src/components/organisms/PostersGrid.svelte
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext, onMount } from 'svelte'
|
||||||
|
import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel'
|
||||||
|
// Components
|
||||||
|
import Poster from '$components/molecules/Poster.svelte'
|
||||||
|
import EmailForm from '$components/molecules/EmailForm.svelte'
|
||||||
|
import { debounce } from '$utils/functions'
|
||||||
|
|
||||||
|
export let posters: any = []
|
||||||
|
|
||||||
|
let innerWidth: number
|
||||||
|
let carouselEl: HTMLElement
|
||||||
|
let carousel: EmblaCarouselType
|
||||||
|
let currentSlide = 0
|
||||||
|
let carouselDots = []
|
||||||
|
|
||||||
|
const { shopProducts } = getContext('shop')
|
||||||
|
|
||||||
|
|
||||||
|
/** Navigate to specific slide */
|
||||||
|
const goToSlide = (index: number = 0) => {
|
||||||
|
carousel.scrollTo(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Init Carousel */
|
||||||
|
const initCarousel = () => {
|
||||||
|
if (innerWidth < 992) {
|
||||||
|
if (!carousel) {
|
||||||
|
carousel = EmblaCarousel(carouselEl, {
|
||||||
|
slidesToScroll: innerWidth < 550 ? 1 : 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
// On init
|
||||||
|
carousel.on('init', () => {
|
||||||
|
// Define amounts of dots
|
||||||
|
carouselDots = carousel.scrollSnapList()
|
||||||
|
})
|
||||||
|
// On slide change
|
||||||
|
carousel.on('select', () => {
|
||||||
|
// Define current slide
|
||||||
|
currentSlide = carousel.selectedScrollSnap()
|
||||||
|
})
|
||||||
|
// On resize
|
||||||
|
carousel.on('resize', () => {
|
||||||
|
// Redefine options
|
||||||
|
carousel.reInit({
|
||||||
|
slidesToScroll: innerWidth < 550 ? 1 : 2
|
||||||
|
})
|
||||||
|
// Define amounts of dots
|
||||||
|
carouselDots = carousel.scrollSnapList()
|
||||||
|
// Define current slide
|
||||||
|
currentSlide = carousel.selectedScrollSnap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (carousel) {
|
||||||
|
carousel.destroy()
|
||||||
|
carousel = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destroy carousel for larger screens */
|
||||||
|
const handleResize = debounce(initCarousel, 200)
|
||||||
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
initCarousel()
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
return () => {
|
||||||
|
if (carousel) {
|
||||||
|
carousel.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
bind:innerWidth
|
||||||
|
on:resize={handleResize}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if posters}
|
||||||
|
<section class="shop-page__posters grid">
|
||||||
|
<h3>View all of our available posters</h3>
|
||||||
|
|
||||||
|
<div class="set" bind:this={carouselEl}>
|
||||||
|
<div class="set__content">
|
||||||
|
{#each posters as { location, photos_product }}
|
||||||
|
<Poster
|
||||||
|
location={location}
|
||||||
|
image={photos_product.length && photos_product[1].directus_files_id}
|
||||||
|
product={shopProducts.find(item => item.slug.includes(location.slug))}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if carousel}
|
||||||
|
<ul class="set__dots">
|
||||||
|
{#each carouselDots as _, index}
|
||||||
|
<li class:is-active={index === currentSlide}>
|
||||||
|
<button on:click={() => goToSlide(index)} aria-label="Go to slide #{index + 1}" />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="subscribe">
|
||||||
|
<label class="subscribe__text" for="SUB_EMAIL">Subscribe to be notified when new posters become available</label>
|
||||||
|
<EmailForm />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import { navigating, page } from '$app/stores'
|
import { navigating, page } from '$app/stores'
|
||||||
import { onMount, setContext } from 'svelte'
|
import { onMount, setContext } from 'svelte'
|
||||||
import { pageLoading, previousPage } from '$utils/stores'
|
import { pageLoading, previousPage } from '$utils/stores'
|
||||||
import { scrollToTop } from '$utils/functions'
|
|
||||||
import { DURATION } from '$utils/contants'
|
import { DURATION } from '$utils/contants'
|
||||||
import '$utils/polyfills'
|
import '$utils/polyfills'
|
||||||
// Components
|
// Components
|
||||||
|
|||||||
@@ -1,22 +1,199 @@
|
|||||||
<script lang="ts">
|
<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'
|
||||||
|
import { capitalizeFirstLetter } from '$utils/functions'
|
||||||
|
// 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'
|
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
||||||
|
|
||||||
export let product: any
|
export let product: any
|
||||||
export let shopProduct: 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>
|
</script>
|
||||||
|
|
||||||
|
<Metas
|
||||||
|
title="{product.location.name} {capitalizeFirstLetter(product.type)} – Houses Of Shop"
|
||||||
|
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
|
<PosterLayout
|
||||||
product={product}
|
product={product}
|
||||||
shopProduct={shopProduct}
|
shopProduct={shopProduct}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PostersGrid {posters} />
|
||||||
|
</PageTransition>
|
||||||
|
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
export async function load ({ page, fetch, session, stuff }) {
|
export async function load ({ page, fetch, session, stuff }) {
|
||||||
// Get content from stuff
|
let shopProduct: any
|
||||||
|
|
||||||
|
// Get a random product from the API
|
||||||
|
const productAPI = stuff.posters.find(poster => poster.location.slug === page.params.name)
|
||||||
|
|
||||||
|
// 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 {
|
return {
|
||||||
props: {
|
props: {
|
||||||
product: stuff.product,
|
product: productAPI,
|
||||||
shopProduct: stuff.shopProduct,
|
shopProduct,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/env'
|
import { setContext } from 'svelte'
|
||||||
import { onMount } from 'svelte'
|
import { cartNotifications } from '$utils/stores/shop'
|
||||||
import { shopLocations, cartOpen, cartNotifications } from '$utils/stores/shop'
|
|
||||||
import anime from 'animejs'
|
|
||||||
import type { AnimeTimelineInstance } from 'animejs'
|
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
|
||||||
import PageTransition from '$components/PageTransition.svelte'
|
|
||||||
import SiteTitle from '$components/atoms/SiteTitle.svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
|
||||||
import ButtonCart from '$components/atoms/ButtonCart.svelte'
|
|
||||||
import Poster from '$components/molecules/Poster.svelte'
|
|
||||||
import NotificationCart from '$components/molecules/NotificationCart.svelte'
|
|
||||||
import EmailForm from '$components/molecules/EmailForm.svelte'
|
|
||||||
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher.svelte'
|
|
||||||
import Cart from '$components/organisms/Cart.svelte'
|
import Cart from '$components/organisms/Cart.svelte'
|
||||||
|
import NotificationCart from '$components/molecules/NotificationCart.svelte'
|
||||||
|
|
||||||
export let shop: any
|
export let shop: any
|
||||||
export let locations: any
|
export let locations: any
|
||||||
export let posters: any
|
export let posters: any
|
||||||
export let product: any
|
|
||||||
export let shopProducts: any
|
export let shopProducts: any
|
||||||
|
|
||||||
let introEl: HTMLElement
|
|
||||||
let navObserver: IntersectionObserver
|
|
||||||
let scrolledPastIntro = false
|
|
||||||
|
|
||||||
|
|
||||||
// Locations with an existing poster product
|
// Locations with an existing poster product
|
||||||
$shopLocations = locations.filter(({ slug }: any) => {
|
const shopLocations = locations.filter(({ slug }: any) => {
|
||||||
if (posters.find((poster: any) => poster.location.slug === slug)) {
|
if (posters.find((poster: any) => poster.location.slug === slug)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setContext('shop', {
|
||||||
/**
|
shop,
|
||||||
* Transition: Anime timeline
|
posters,
|
||||||
*/
|
shopLocations,
|
||||||
let timeline: AnimeTimelineInstance
|
shopProducts,
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<Metas
|
|
||||||
title="Shop – Houses Of"
|
|
||||||
description=""
|
|
||||||
image=""
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<PageTransition name="shop-page">
|
|
||||||
<Cart />
|
<Cart />
|
||||||
|
<div class="cart-notifications">
|
||||||
<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 site</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 />
|
|
||||||
|
|
||||||
<div class="shop-location__notifications">
|
|
||||||
{#each $cartNotifications as { id, title, name, image } (id)}
|
{#each $cartNotifications as { id, title, name, image } (id)}
|
||||||
<NotificationCart
|
<NotificationCart
|
||||||
title={title}
|
title={title}
|
||||||
@@ -172,44 +36,12 @@
|
|||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
|
||||||
|
|
||||||
<section class="shop-page__about grid">
|
|
||||||
<div class="description grid text-normal">
|
|
||||||
<div class="description__inner">
|
|
||||||
{@html shop.about}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{#key product}
|
|
||||||
<slot />
|
<slot />
|
||||||
{/key}
|
|
||||||
|
|
||||||
{#if posters}
|
|
||||||
<section class="shop-page__posters grid">
|
|
||||||
<h3>View all of our available posters</h3>
|
|
||||||
<div class="set">
|
|
||||||
{#each posters as { location, photos_product }}
|
|
||||||
<Poster
|
|
||||||
location={location}
|
|
||||||
image={photos_product.length && photos_product[1].directus_files_id}
|
|
||||||
product={shopProducts.find(item => item.slug.includes(location.slug))}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="subscribe">
|
|
||||||
<label class="subscribe__text" for="SUB_EMAIL">Subscribe to be notified when new posters become available</label>
|
|
||||||
<EmailForm />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
</PageTransition>
|
|
||||||
|
|
||||||
|
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import { fetchAPI } from '$utils/api'
|
import { fetchAPI } from '$utils/api'
|
||||||
import { getRandomElement } from '$utils/functions'
|
|
||||||
|
|
||||||
export async function load ({ page, fetch, session, stuff }) {
|
export async function load ({ page, fetch, session, stuff }) {
|
||||||
// Get content from API
|
// Get content from API
|
||||||
@@ -259,24 +91,13 @@
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const { data } = res
|
const { data: { shop, location, posters } } = res
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define product
|
|
||||||
*/
|
|
||||||
const productAPI = (!page.params.type && !page.params.name)
|
|
||||||
// Get a random product
|
|
||||||
? getRandomElement(data.posters)
|
|
||||||
// Get the current product from slug
|
|
||||||
: data.posters.find(({ location }: any) => location.slug === page.params.name)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get products data from Swell
|
* Get products data from Swell
|
||||||
*/
|
*/
|
||||||
let shopProducts: any
|
let shopProducts: any
|
||||||
let shopProduct: any
|
|
||||||
|
|
||||||
const shopProductRes = await fetch('/api/swell', {
|
const shopProductRes = await fetch('/api/swell', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -291,27 +112,20 @@
|
|||||||
if (results) {
|
if (results) {
|
||||||
shopProducts = results
|
shopProducts = results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define current product
|
|
||||||
const product = results.find((result: any) => result.slug.includes(productAPI.location.slug))
|
|
||||||
if (product) {
|
|
||||||
shopProduct = product
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
shop: data.shop,
|
shop,
|
||||||
locations: data.location,
|
locations: location,
|
||||||
posters: data.posters,
|
posters,
|
||||||
product: productAPI,
|
|
||||||
shopProducts,
|
shopProducts,
|
||||||
shopProduct,
|
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
product: productAPI,
|
shop,
|
||||||
shopProduct,
|
posters,
|
||||||
|
shopProducts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,201 @@
|
|||||||
<script lang="ts">
|
<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'
|
import PosterLayout from '$components/layouts/PosterLayout.svelte'
|
||||||
|
|
||||||
export let product: any
|
export let product: any
|
||||||
export let shopProduct: 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>
|
</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
|
<PosterLayout
|
||||||
product={product}
|
product={product}
|
||||||
shopProduct={shopProduct}
|
shopProduct={shopProduct}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PostersGrid {posters} />
|
||||||
|
</PageTransition>
|
||||||
|
|
||||||
|
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
|
import { getRandomElement } from '$utils/functions'
|
||||||
|
|
||||||
export async function load ({ page, fetch, session, stuff }) {
|
export async function load ({ page, fetch, session, stuff }) {
|
||||||
// Get content from 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 {
|
return {
|
||||||
props: {
|
props: {
|
||||||
product: stuff.product,
|
product: productAPI,
|
||||||
shopProduct: stuff.shopProduct,
|
shopProduct,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Title Location
|
// Title Location
|
||||||
h2 {
|
&__title {
|
||||||
|
overflow: hidden;
|
||||||
|
width: auto;
|
||||||
grid-column: 1 / span var(--columns);
|
grid-column: 1 / span var(--columns);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 16px;
|
||||||
font-size: clamp(200px, 20vw, 340px);
|
font-size: clamp(200px, 20vw, 340px);
|
||||||
color: $color-secondary;
|
color: $color-secondary;
|
||||||
margin-bottom: 40px;
|
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
grid-column: 2 / span calc(var(--columns) - 1);
|
grid-column: 2 / span calc(var(--columns) - 1);
|
||||||
margin-bottom: 88px;
|
margin-bottom: 88px;
|
||||||
@@ -36,14 +46,25 @@
|
|||||||
|
|
||||||
// Product Info
|
// Product Info
|
||||||
&__info {
|
&__info {
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
margin-bottom: 56px;
|
margin-bottom: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Title
|
// Title
|
||||||
dt {
|
dt {
|
||||||
color: $color-secondary;
|
color: $color-secondary;
|
||||||
@@ -51,16 +72,16 @@
|
|||||||
font-size: rem(36px);
|
font-size: rem(36px);
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
dd {
|
dd {
|
||||||
font-size: rem(16px);
|
|
||||||
color: $color-gray;
|
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
max-width: 140px;
|
font-size: rem(14px);
|
||||||
|
line-height: 1.4;
|
||||||
|
color: $color-gray;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
font-size: rem(16px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,22 +118,27 @@
|
|||||||
|
|
||||||
&--1 {
|
&--1 {
|
||||||
grid-column: 1 / span 7;
|
grid-column: 1 / span 7;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
grid-column: 1 / span 7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&--2 {
|
&--2 {
|
||||||
grid-column: 2 / span 5;
|
grid-column: 2 / span 7;
|
||||||
margin: 32px 0;
|
margin: 32px 0;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
|
grid-column: 2 / span 7;
|
||||||
margin: 48px 0;
|
margin: 48px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&--3 {
|
&--3 {
|
||||||
grid-column: 4 / span 5;
|
grid-column: 1 / span 6;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
margin-bottom: -64px;
|
margin-bottom: -64px;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
grid-column: 5 / span 5;
|
grid-column: 5 / span 8;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +148,13 @@
|
|||||||
}
|
}
|
||||||
&--4 {
|
&--4 {
|
||||||
grid-column: 1 / span 8;
|
grid-column: 1 / span 8;
|
||||||
margin-bottom: 56px;
|
margin: -56px 20px 20px;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
grid-column: 8 / span 13;
|
grid-column: 5 / span 15;
|
||||||
margin-bottom: 120px;
|
margin-bottom: 120px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,8 +162,8 @@
|
|||||||
// About
|
// About
|
||||||
&__about {
|
&__about {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 144px 0 72px;
|
padding: 144px 0 112px;
|
||||||
margin: -100px 0 -120px;
|
margin: -24px 0 0;
|
||||||
font-size: rem(36px);
|
font-size: rem(36px);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: $color-primary-tertiary20;
|
color: $color-primary-tertiary20;
|
||||||
@@ -152,7 +180,7 @@
|
|||||||
max-width: 768px;
|
max-width: 768px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
font-size: rem(28px);
|
font-size: clamp(#{rem(22px)}, 5vw, #{rem(28px)});
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
// Left Image
|
// Left Image
|
||||||
&__left {
|
&__left {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 326px;
|
max-width: 326px;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (md) {
|
||||||
background-color: $color-primary-tertiary20;
|
background-color: $color-primary-tertiary20;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
@@ -33,12 +33,10 @@
|
|||||||
// Buttons
|
// Buttons
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 40px;
|
margin-top: 32px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transform: translate3d(0, 100%, 0);
|
|
||||||
transition: transform 0.8s var(--ease-quart);
|
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (md) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
bottom: 4.25%;
|
bottom: 4.25%;
|
||||||
@@ -46,20 +44,44 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-top: 40px;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transition: transform 0.8s var(--ease-quart);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
& > * {
|
||||||
margin: 0 clamp(4px, 0.3vw, 8px);
|
background: none;
|
||||||
text-align: center;
|
font-size: rem(14px);
|
||||||
|
font-weight: 300;
|
||||||
|
color: $color-tertiary;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-right: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 12px;
|
||||||
|
color: $color-secondary;
|
||||||
|
border: 1px solid rgba($color-secondary, 0.4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hover effect
|
// Hover effect
|
||||||
&:hover {
|
&:hover {
|
||||||
img {
|
img {
|
||||||
|
@include bp (md) {
|
||||||
transform: scale(0.8) translate3d(0, -5%, 0);
|
transform: scale(0.8) translate3d(0, -5%, 0);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.buttons {
|
.buttons {
|
||||||
transform: translate3d(0,0,0);
|
transform: translate3d(0,0,0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
.cart {
|
.cart {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 202;
|
||||||
|
top: 80px;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 20px 20px 24px;
|
padding: 20px 20px 24px;
|
||||||
background-color: $color-cream;
|
background-color: $color-cream;
|
||||||
border-radius: 8px;
|
border-radius: 8px 8px 0 0;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: clamp(320px, 45vw, 500px);
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
max-height: 1000px;
|
||||||
|
border-radius: 8px;
|
||||||
padding: 24px 32px 32px;
|
padding: 24px 32px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +97,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
margin-left: 20px;
|
margin-left: 12px;
|
||||||
font-size: rem(12px);
|
font-size: rem(12px);
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
|
margin-bottom: 20px;
|
||||||
font-size: rem(13px);
|
font-size: rem(13px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,20 +119,25 @@
|
|||||||
|
|
||||||
// Checkout
|
// Checkout
|
||||||
&--checkout {
|
&--checkout {
|
||||||
|
@include bp (md) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
max-width: 180px;
|
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-right: 12px;
|
||||||
font-size: rem(11px);
|
font-size: rem(11px);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
max-width: 304px;
|
|
||||||
font-size: rem(12px);
|
font-size: rem(12px);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
@include bp (md) {
|
||||||
|
max-width: 304px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +217,7 @@
|
|||||||
// Location switcher
|
// Location switcher
|
||||||
&-switcher {
|
&-switcher {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100;
|
z-index: 202;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
@@ -213,7 +231,7 @@
|
|||||||
// Overlay
|
// Overlay
|
||||||
&-overlay {
|
&-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 99;
|
z-index: 201;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -20,35 +20,15 @@
|
|||||||
transform: translate3d(0, -88px, 0);
|
transform: translate3d(0, -88px, 0);
|
||||||
transition: transform 1s var(--ease-quart);
|
transition: transform 1s var(--ease-quart);
|
||||||
transition-delay: 100ms;
|
transition-delay: 100ms;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
--inset: 32px;
|
--inset: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifications
|
|
||||||
&__notifications {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 100;
|
|
||||||
top: 72px;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.7s var(--ease-quart);
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Visible state
|
// Visible state
|
||||||
&.is-visible {
|
&.is-visible {
|
||||||
transform: translate3d(0,0,0);
|
transform: translate3d(0,0,0);
|
||||||
.shop-location__notifications {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,4 +50,23 @@
|
|||||||
max-height: 1000px;
|
max-height: 1000px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
.notifications {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
top: 104px;
|
||||||
|
right: 20px;
|
||||||
|
transition: opacity 0.7s var(--ease-quart);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 0 16px 24px;
|
padding: 0 0 24px;
|
||||||
|
|
||||||
@include bp (md) {
|
@include bp (md) {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -166,10 +166,18 @@
|
|||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
nav {
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -199,4 +207,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cart button
|
||||||
|
.button-cart {
|
||||||
|
@include bp (sm, max) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,28 +3,31 @@
|
|||||||
*/
|
*/
|
||||||
&__posters {
|
&__posters {
|
||||||
background-color: $color-primary-darker;
|
background-color: $color-primary-darker;
|
||||||
padding: 0 20px 72px;
|
padding: clamp(56px, 10vw, 120px) 20px 72px;
|
||||||
border-bottom: solid 1px $color-primary-tertiary20 ;
|
border-bottom: solid 1px $color-primary-tertiary20 ;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sd) {
|
||||||
padding: 120px 0;
|
padding: 120px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
h3 {
|
h3 {
|
||||||
grid-column: 2 / span 6;
|
grid-column: 2 / span 6;
|
||||||
|
margin-bottom: 48px;
|
||||||
font-family: $font-serif;
|
font-family: $font-serif;
|
||||||
color: $color-cream;
|
color: $color-cream;
|
||||||
font-size: rem(32px);
|
font-size: rem(32px);
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
grid-column: 3 / span 14;
|
grid-column: 3 / span 14;
|
||||||
|
max-width: 360px;
|
||||||
|
margin-bottom: 48px;
|
||||||
font-size: rem(48px);
|
font-size: rem(48px);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
max-width: 360px;
|
}
|
||||||
|
@include bp (sd) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,64 +35,135 @@
|
|||||||
// Posters Set
|
// Posters Set
|
||||||
.set {
|
.set {
|
||||||
--gap: 24px;
|
--gap: 24px;
|
||||||
grid-column: 1 / span 8;
|
grid-column: span calc(var(--columns));
|
||||||
display: block;
|
display: block;
|
||||||
|
cursor: grab;
|
||||||
@include bp (sm) {
|
|
||||||
grid-column: 2 / span 22;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include bp (mob-lg) {
|
@include bp (mob-lg) {
|
||||||
--gap: 32px;
|
grid-column: 2 / span calc(var(--columns) - 2);
|
||||||
display: grid;
|
}
|
||||||
grid-template-columns: repeat(2, 1fr);
|
@include bp (sm) {
|
||||||
gap: var(--gap);
|
grid-column: 3 / span calc(var(--columns) - 4);
|
||||||
}
|
}
|
||||||
@include bp (md) {
|
@include bp (md) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-dragging {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 -8px;
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
margin: 0 -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @include bp (sm) {
|
||||||
|
// grid-column: 2 / span 22;
|
||||||
|
// }
|
||||||
|
// @include bp (mob-lg) {
|
||||||
|
// --gap: 32px;
|
||||||
|
// display: grid;
|
||||||
|
// grid-template-columns: repeat(2, 1fr);
|
||||||
|
// gap: var(--gap);
|
||||||
|
// }
|
||||||
|
@include bp (md) {
|
||||||
|
--gap: 32px;
|
||||||
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--gap);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
@include bp (sd) {
|
@include bp (sd) {
|
||||||
--gap: 48px;
|
--gap: 48px;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Poster
|
// Poster
|
||||||
.poster {
|
.poster {
|
||||||
margin: 0 auto auto;
|
position: relative;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
margin: 0 12px;
|
||||||
|
|
||||||
|
@include bp (mob-lg) {
|
||||||
|
flex: 0 0 calc(50% - 24px);
|
||||||
|
}
|
||||||
|
@include bp (sm) {
|
||||||
|
flex: 0 0 calc(50% - 24px);
|
||||||
|
}
|
||||||
|
// margin: 0 auto auto;
|
||||||
|
|
||||||
// 2 columns
|
// 2 columns
|
||||||
&:nth-child(2n + 1) {
|
// &:nth-child(2n + 1) {
|
||||||
@include bp (sm, max) {
|
// @include bp (sm, max) {
|
||||||
margin-top: calc(var(--gap) * 2);
|
// margin-top: calc(var(--gap) * 2);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 3 columns
|
// 3 columns
|
||||||
&:nth-child(3n + 1) {
|
// &:nth-child(3n + 1) {
|
||||||
@include bp (sd, max) {
|
// @include bp (sd, max) {
|
||||||
margin-top: calc(var(--gap) * 2);
|
// margin-top: calc(var(--gap) * 2);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
&:nth-child(3n + 2) {
|
// &:nth-child(3n + 2) {
|
||||||
@include bp (sd, max) {
|
// @include bp (sd, max) {
|
||||||
margin-top: var(--gap);
|
// margin-top: var(--gap);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 4 columns
|
// 4 columns
|
||||||
&:nth-child(4n + 1) {
|
// &:nth-child(4n + 1) {
|
||||||
@include bp (sd) {
|
// @include bp (sd) {
|
||||||
margin-top: 64px * 3;
|
// margin-top: 64px * 3;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// &:nth-child(4n + 2) {
|
||||||
|
// @include bp (sd) {
|
||||||
|
// margin-top: 64px * 2;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// &:nth-child(4n + 3) {
|
||||||
|
// @include bp (sd) {
|
||||||
|
// margin-top: 64px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
&__dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 32px 0 48px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: block;
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// Hover
|
||||||
|
&:hover {
|
||||||
|
button {
|
||||||
|
box-shadow: inset 0 0 0 8px $color-tertiary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:nth-child(4n + 2) {
|
|
||||||
@include bp (sd) {
|
|
||||||
margin-top: 64px * 2;
|
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: $color-primary-tertiary20;
|
||||||
|
padding: 0;
|
||||||
|
transition: box-shadow 1.0s var(--ease-quart);
|
||||||
}
|
}
|
||||||
&:nth-child(4n + 3) {
|
.is-active {
|
||||||
@include bp (sd) {
|
button {
|
||||||
margin-top: 64px;
|
box-shadow: inset 0 0 0 8px $color-tertiary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,10 +173,10 @@
|
|||||||
.subscribe {
|
.subscribe {
|
||||||
grid-column: 1 / span var(--columns);
|
grid-column: 1 / span var(--columns);
|
||||||
max-width: 380px;
|
max-width: 380px;
|
||||||
margin: 72px auto 0;
|
margin: 56px auto 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (md) {
|
||||||
grid-column: 14 / span 8;
|
grid-column: 14 / span 8;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -129,10 +203,14 @@
|
|||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.newsletter-form__bottom {
|
&__email {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&__bottom {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
import { writable, derived } from 'svelte/store'
|
import { writable, derived } from 'svelte/store'
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shop
|
|
||||||
*/
|
|
||||||
export const shopLocations = writable([])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cart
|
* Cart
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user