[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 () => {
|
||||
// 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()
|
||||
setTimeout(() => {
|
||||
// Scroll back to top between page transitions
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { addToCart } from '$utils/functions/shop'
|
||||
import { capitalizeFirstLetter } from '$utils/functions'
|
||||
// Components
|
||||
import SplitText from '$components/SplitText.svelte'
|
||||
import Button from '$components/atoms/Button.svelte'
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
|
||||
import Carousel from '$components/organisms/Carousel.svelte'
|
||||
|
||||
export let product: any
|
||||
@@ -60,9 +62,11 @@
|
||||
</script>
|
||||
|
||||
<section class="poster-layout grid" id="poster">
|
||||
<h2 class="title-huge">
|
||||
{product.location.name}
|
||||
</h2>
|
||||
<div class="poster-layout__title">
|
||||
<ScrollingTitle tag="h2" label={product.location.name}>
|
||||
<SplitText mode="chars" text={product.location.name} />
|
||||
</ScrollingTitle>
|
||||
</div>
|
||||
|
||||
<aside class="poster-layout__buy">
|
||||
<div class="poster-layout__info">
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
text="View"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
text="Add to cart"
|
||||
color="pinklight"
|
||||
tag="button"
|
||||
size="xsmall"
|
||||
on:click={() => addToCart($cartId, product)}
|
||||
text="Add to cart"
|
||||
color="pink"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import { shopLocations } from '$utils/stores/shop'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let isOver: boolean = false
|
||||
|
||||
const { shopLocations } = getContext('shop')
|
||||
|
||||
const classes = [
|
||||
'shop-locationswitcher',
|
||||
isOver && 'is-over',
|
||||
@@ -26,7 +28,7 @@
|
||||
<use xlink:href="#icon-map-pin" />
|
||||
</svg>
|
||||
<select on:change={quickLocationChange}>
|
||||
{#each $shopLocations as { name, slug }}
|
||||
{#each shopLocations as { name, slug }}
|
||||
<option value={slug} selected={slug === $page.params.name}>{name}</option>
|
||||
{/each}
|
||||
</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 { onMount, setContext } from 'svelte'
|
||||
import { pageLoading, previousPage } from '$utils/stores'
|
||||
import { scrollToTop } from '$utils/functions'
|
||||
import { DURATION } from '$utils/contants'
|
||||
import '$utils/polyfills'
|
||||
// Components
|
||||
|
||||
@@ -1,22 +1,199 @@
|
||||
<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'
|
||||
|
||||
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>
|
||||
|
||||
<PosterLayout
|
||||
product={product}
|
||||
shopProduct={shopProduct}
|
||||
<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
|
||||
product={product}
|
||||
shopProduct={shopProduct}
|
||||
/>
|
||||
|
||||
<PostersGrid {posters} />
|
||||
</PageTransition>
|
||||
|
||||
<script context="module" lang="ts">
|
||||
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 {
|
||||
props: {
|
||||
product: stuff.product,
|
||||
shopProduct: stuff.shopProduct,
|
||||
product: productAPI,
|
||||
shopProduct,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,215 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env'
|
||||
import { onMount } from 'svelte'
|
||||
import { shopLocations, cartOpen, cartNotifications } from '$utils/stores/shop'
|
||||
import anime from 'animejs'
|
||||
import type { AnimeTimelineInstance } from 'animejs'
|
||||
import { setContext } from 'svelte'
|
||||
import { cartNotifications } from '$utils/stores/shop'
|
||||
// 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 NotificationCart from '$components/molecules/NotificationCart.svelte'
|
||||
|
||||
export let shop: any
|
||||
export let locations: any
|
||||
export let posters: any
|
||||
export let product: any
|
||||
export let shopProducts: any
|
||||
|
||||
let introEl: HTMLElement
|
||||
let navObserver: IntersectionObserver
|
||||
let scrolledPastIntro = false
|
||||
|
||||
|
||||
// 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)) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
setContext('shop', {
|
||||
shop,
|
||||
posters,
|
||||
shopLocations,
|
||||
shopProducts,
|
||||
})
|
||||
</script>
|
||||
|
||||
<Metas
|
||||
title="Shop – Houses Of"
|
||||
description=""
|
||||
image=""
|
||||
/>
|
||||
|
||||
|
||||
<PageTransition name="shop-page">
|
||||
<Cart />
|
||||
|
||||
<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"
|
||||
<Cart />
|
||||
<div class="cart-notifications">
|
||||
{#each $cartNotifications as { id, title, name, image } (id)}
|
||||
<NotificationCart
|
||||
title={title}
|
||||
name={name}
|
||||
image={image}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<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)}
|
||||
<NotificationCart
|
||||
title={title}
|
||||
name={name}
|
||||
image={image}
|
||||
/>
|
||||
{/each}
|
||||
</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 />
|
||||
{/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>
|
||||
<slot />
|
||||
|
||||
|
||||
<script context="module" lang="ts">
|
||||
import { fetchAPI } from '$utils/api'
|
||||
import { getRandomElement } from '$utils/functions'
|
||||
|
||||
export async function load ({ page, fetch, session, stuff }) {
|
||||
// Get content from API
|
||||
@@ -259,24 +91,13 @@
|
||||
}
|
||||
`)
|
||||
|
||||
const { data } = 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)
|
||||
const { data: { shop, location, posters } } = res
|
||||
|
||||
|
||||
/**
|
||||
* Get products data from Swell
|
||||
*/
|
||||
let shopProducts: any
|
||||
let shopProduct: any
|
||||
|
||||
const shopProductRes = await fetch('/api/swell', {
|
||||
method: 'POST',
|
||||
@@ -291,27 +112,20 @@
|
||||
if (results) {
|
||||
shopProducts = results
|
||||
}
|
||||
|
||||
// Define current product
|
||||
const product = results.find((result: any) => result.slug.includes(productAPI.location.slug))
|
||||
if (product) {
|
||||
shopProduct = product
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
props: {
|
||||
shop: data.shop,
|
||||
locations: data.location,
|
||||
posters: data.posters,
|
||||
product: productAPI,
|
||||
shop,
|
||||
locations: location,
|
||||
posters,
|
||||
shopProducts,
|
||||
shopProduct,
|
||||
},
|
||||
stuff: {
|
||||
product: productAPI,
|
||||
shopProduct,
|
||||
shop,
|
||||
posters,
|
||||
shopProducts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,201 @@
|
||||
<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>
|
||||
|
||||
<PosterLayout
|
||||
product={product}
|
||||
shopProduct={shopProduct}
|
||||
<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'
|
||||
|
||||
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 {
|
||||
props: {
|
||||
product: stuff.product,
|
||||
shopProduct: stuff.shopProduct,
|
||||
product: productAPI,
|
||||
shopProduct,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,22 @@
|
||||
}
|
||||
|
||||
// Title Location
|
||||
h2 {
|
||||
&__title {
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
grid-column: 1 / span var(--columns);
|
||||
display: flex;
|
||||
}
|
||||
h2 {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 16px;
|
||||
font-size: clamp(200px, 20vw, 340px);
|
||||
color: $color-secondary;
|
||||
margin-bottom: 40px;
|
||||
|
||||
@include bp (mob-lg) {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
@include bp (sm) {
|
||||
grid-column: 2 / span calc(var(--columns) - 1);
|
||||
margin-bottom: 88px;
|
||||
@@ -36,14 +46,25 @@
|
||||
|
||||
// Product Info
|
||||
&__info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32px;
|
||||
|
||||
@include bp (mob-lg) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@include bp (sm) {
|
||||
margin-bottom: 56px;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 24px;
|
||||
|
||||
@include bp (mob-lg) {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
// Title
|
||||
dt {
|
||||
color: $color-secondary;
|
||||
@@ -51,16 +72,16 @@
|
||||
font-size: rem(36px);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
// Text
|
||||
dd {
|
||||
font-size: rem(16px);
|
||||
color: $color-gray;
|
||||
margin-top: 8px;
|
||||
max-width: 140px;
|
||||
font-size: rem(14px);
|
||||
line-height: 1.4;
|
||||
color: $color-gray;
|
||||
|
||||
@include bp (sm) {
|
||||
max-width: none;
|
||||
font-size: rem(16px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,22 +118,27 @@
|
||||
|
||||
&--1 {
|
||||
grid-column: 1 / span 7;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 1 / span 7;
|
||||
}
|
||||
}
|
||||
&--2 {
|
||||
grid-column: 2 / span 5;
|
||||
grid-column: 2 / span 7;
|
||||
margin: 32px 0;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 2 / span 7;
|
||||
margin: 48px 0;
|
||||
}
|
||||
}
|
||||
&--3 {
|
||||
grid-column: 4 / span 5;
|
||||
grid-column: 1 / span 6;
|
||||
z-index: 10;
|
||||
margin-bottom: -64px;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 5 / span 5;
|
||||
grid-column: 5 / span 8;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -122,11 +148,13 @@
|
||||
}
|
||||
&--4 {
|
||||
grid-column: 1 / span 8;
|
||||
margin-bottom: 56px;
|
||||
margin: -56px 20px 20px;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 8 / span 13;
|
||||
grid-column: 5 / span 15;
|
||||
margin-bottom: 120px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,8 +162,8 @@
|
||||
// About
|
||||
&__about {
|
||||
background-color: #fff;
|
||||
padding: 144px 0 72px;
|
||||
margin: -100px 0 -120px;
|
||||
padding: 144px 0 112px;
|
||||
margin: -24px 0 0;
|
||||
font-size: rem(36px);
|
||||
font-weight: 300;
|
||||
color: $color-primary-tertiary20;
|
||||
@@ -152,7 +180,7 @@
|
||||
max-width: 768px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-size: rem(28px);
|
||||
font-size: clamp(#{rem(22px)}, 5vw, #{rem(28px)});
|
||||
font-weight: 200;
|
||||
|
||||
@include bp (sm) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
|
||||
// Left Image
|
||||
&__left {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
overflow: hidden;
|
||||
max-width: 326px;
|
||||
|
||||
@include bp (sm) {
|
||||
@include bp (md) {
|
||||
background-color: $color-primary-tertiary20;
|
||||
max-width: 600px;
|
||||
}
|
||||
@@ -33,12 +33,10 @@
|
||||
// Buttons
|
||||
.buttons {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
margin-top: 32px;
|
||||
justify-content: center;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: transform 0.8s var(--ease-quart);
|
||||
|
||||
@include bp (sm) {
|
||||
@include bp (md) {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 4.25%;
|
||||
@@ -46,19 +44,43 @@
|
||||
width: 100%;
|
||||
align-items: 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);
|
||||
text-align: center;
|
||||
& > * {
|
||||
background: none;
|
||||
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 {
|
||||
img {
|
||||
transform: scale(0.8) translate3d(0, -5%, 0);
|
||||
border-radius: 8px;
|
||||
@include bp (md) {
|
||||
transform: scale(0.8) translate3d(0, -5%, 0);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
.buttons {
|
||||
transform: translate3d(0,0,0);
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
.cart {
|
||||
position: fixed;
|
||||
z-index: 202;
|
||||
top: 80px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 80px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding: 20px 20px 24px;
|
||||
background-color: $color-cream;
|
||||
border-radius: 8px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -85,10 +97,11 @@
|
||||
}
|
||||
}
|
||||
span {
|
||||
margin-left: 20px;
|
||||
margin-left: 12px;
|
||||
font-size: rem(12px);
|
||||
|
||||
@include bp (sm) {
|
||||
margin-bottom: 20px;
|
||||
font-size: rem(13px);
|
||||
}
|
||||
}
|
||||
@@ -106,20 +119,25 @@
|
||||
|
||||
// Checkout
|
||||
&--checkout {
|
||||
display: flex;
|
||||
@include bp (md) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 180px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 16px;
|
||||
padding-right: 12px;
|
||||
font-size: rem(11px);
|
||||
line-height: 1.5;
|
||||
color: $color-gray;
|
||||
|
||||
@include bp (sm) {
|
||||
max-width: 304px;
|
||||
font-size: rem(12px);
|
||||
line-height: 1.6;
|
||||
}
|
||||
@include bp (md) {
|
||||
max-width: 304px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,7 +217,7 @@
|
||||
// Location switcher
|
||||
&-switcher {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
z-index: 202;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
will-change: transform, opacity;
|
||||
@@ -213,7 +231,7 @@
|
||||
// Overlay
|
||||
&-overlay {
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
z-index: 201;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
@@ -20,54 +20,53 @@
|
||||
transform: translate3d(0, -88px, 0);
|
||||
transition: transform 1s var(--ease-quart);
|
||||
transition-delay: 100ms;
|
||||
pointer-events: none;
|
||||
|
||||
@include bp (sm) {
|
||||
--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
|
||||
&.is-visible {
|
||||
transform: translate3d(0,0,0);
|
||||
.shop-location__notifications {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cart
|
||||
.cart {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
top: 72px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 72px);
|
||||
|
||||
@include bp (sm) {
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
width: clamp(320px, 45vw, 500px);
|
||||
height: calc(100vh - 48px);
|
||||
max-height: 1000px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cart
|
||||
.cart {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
top: 72px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 72px);
|
||||
|
||||
@include bp (sm) {
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
width: clamp(320px, 45vw, 500px);
|
||||
height: calc(100vh - 48px);
|
||||
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%;
|
||||
|
||||
.container {
|
||||
padding: 0 16px 24px;
|
||||
padding: 0 0 24px;
|
||||
|
||||
@include bp (md) {
|
||||
display: grid;
|
||||
@@ -166,10 +166,18 @@
|
||||
|
||||
// Navigation
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: auto;
|
||||
|
||||
@include bp (sm) {
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
li {
|
||||
display: block;
|
||||
@@ -199,4 +207,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cart button
|
||||
.button-cart {
|
||||
@include bp (sm, max) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,31 @@
|
||||
*/
|
||||
&__posters {
|
||||
background-color: $color-primary-darker;
|
||||
padding: 0 20px 72px;
|
||||
padding: clamp(56px, 10vw, 120px) 20px 72px;
|
||||
border-bottom: solid 1px $color-primary-tertiary20 ;
|
||||
|
||||
@include bp (sm) {
|
||||
@include bp (sd) {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
// Title
|
||||
h3 {
|
||||
grid-column: 2 / span 6;
|
||||
margin-bottom: 48px;
|
||||
font-family: $font-serif;
|
||||
color: $color-cream;
|
||||
font-size: rem(32px);
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 3 / span 14;
|
||||
max-width: 360px;
|
||||
margin-bottom: 48px;
|
||||
font-size: rem(48px);
|
||||
text-align: left;
|
||||
max-width: 360px;
|
||||
}
|
||||
@include bp (sd) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@@ -32,64 +35,135 @@
|
||||
// Posters Set
|
||||
.set {
|
||||
--gap: 24px;
|
||||
grid-column: 1 / span 8;
|
||||
grid-column: span calc(var(--columns));
|
||||
display: block;
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 2 / span 22;
|
||||
}
|
||||
cursor: grab;
|
||||
|
||||
@include bp (mob-lg) {
|
||||
--gap: 32px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--gap);
|
||||
grid-column: 2 / span calc(var(--columns) - 2);
|
||||
}
|
||||
@include bp (sm) {
|
||||
grid-column: 3 / span calc(var(--columns) - 4);
|
||||
}
|
||||
@include bp (md) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
cursor: default;
|
||||
}
|
||||
@include bp (sd) {
|
||||
--gap: 48px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
&.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);
|
||||
gap: var(--gap);
|
||||
margin: 0;
|
||||
}
|
||||
@include bp (sd) {
|
||||
--gap: 48px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
&:nth-child(2n + 1) {
|
||||
@include bp (sm, max) {
|
||||
margin-top: calc(var(--gap) * 2);
|
||||
}
|
||||
}
|
||||
// &:nth-child(2n + 1) {
|
||||
// @include bp (sm, max) {
|
||||
// margin-top: calc(var(--gap) * 2);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 3 columns
|
||||
&:nth-child(3n + 1) {
|
||||
@include bp (sd, max) {
|
||||
margin-top: calc(var(--gap) * 2);
|
||||
}
|
||||
}
|
||||
&:nth-child(3n + 2) {
|
||||
@include bp (sd, max) {
|
||||
margin-top: var(--gap);
|
||||
}
|
||||
}
|
||||
// &:nth-child(3n + 1) {
|
||||
// @include bp (sd, max) {
|
||||
// margin-top: calc(var(--gap) * 2);
|
||||
// }
|
||||
// }
|
||||
// &:nth-child(3n + 2) {
|
||||
// @include bp (sd, max) {
|
||||
// margin-top: var(--gap);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 4 columns
|
||||
&:nth-child(4n + 1) {
|
||||
@include bp (sd) {
|
||||
margin-top: 64px * 3;
|
||||
// &:nth-child(4n + 1) {
|
||||
// @include bp (sd) {
|
||||
// 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) {
|
||||
@include bp (sd) {
|
||||
margin-top: 64px;
|
||||
.is-active {
|
||||
button {
|
||||
box-shadow: inset 0 0 0 8px $color-tertiary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,10 +173,10 @@
|
||||
.subscribe {
|
||||
grid-column: 1 / span var(--columns);
|
||||
max-width: 380px;
|
||||
margin: 72px auto 0;
|
||||
margin: 56px auto 0;
|
||||
text-align: center;
|
||||
|
||||
@include bp (sm) {
|
||||
@include bp (md) {
|
||||
grid-column: 14 / span 8;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
@@ -129,10 +203,14 @@
|
||||
@include bp (sm) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__email {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&__bottom {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.newsletter-form__bottom {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
import { writable, derived } from 'svelte/store'
|
||||
|
||||
|
||||
/**
|
||||
* Shop
|
||||
*/
|
||||
export const shopLocations = writable([])
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user