✨ Finish About page
Add entering and scroll animations
This commit is contained in:
@@ -19,21 +19,20 @@ export const load: PageServerLoad = async () => {
|
||||
|
||||
about {
|
||||
description
|
||||
intro_firstphoto {
|
||||
id
|
||||
title
|
||||
}
|
||||
intro_portraits {
|
||||
id
|
||||
title
|
||||
}
|
||||
intro_firstphoto { id, title }
|
||||
intro_firstphoto_caption
|
||||
intro_portraits { id, title }
|
||||
intro_text
|
||||
intro_firstlocation {
|
||||
slug
|
||||
name
|
||||
country {
|
||||
flag { id }
|
||||
flag { id, title }
|
||||
slug
|
||||
}
|
||||
illustration_desktop { id }
|
||||
illustration_desktop_2x { id }
|
||||
illustration_mobile { id }
|
||||
}
|
||||
|
||||
purpose_text
|
||||
|
||||
@@ -3,28 +3,31 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { navigating } from '$app/stores'
|
||||
import { onMount, afterUpdate } from 'svelte'
|
||||
import type { PageData } from './$types'
|
||||
import { scroll, animate, inView, type ScrollOptions, timeline, stagger } from 'motion'
|
||||
import { map } from '$utils/functions'
|
||||
import { scroll, animate, inView, type ScrollOptions } from 'motion'
|
||||
import { getAssetUrlKey } from '$utils/api'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { quartOut } from '$animations/easings'
|
||||
// Components
|
||||
import Metas from '$components/Metas.svelte'
|
||||
import PageTransition from '$components/PageTransition.svelte'
|
||||
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
import Button from '$components/atoms/Button.svelte'
|
||||
import AboutGridPhoto from '$components/atoms/AboutGridPhoto.svelte'
|
||||
import Heading from '$components/molecules/Heading.svelte'
|
||||
import ProcessStep from '$components/molecules/ProcessStep.svelte'
|
||||
import ShopModule from '$components/organisms/ShopModule.svelte'
|
||||
import NewsletterModule from '$components/organisms/NewsletterModule.svelte'
|
||||
|
||||
export let data: PageData
|
||||
|
||||
// console.log(data)
|
||||
const { about, photos } = data
|
||||
|
||||
let scrollY: number, innerWidth: number, innerHeight: number
|
||||
let purposeEl: HTMLElement
|
||||
let stepsEl: HTMLElement
|
||||
let photosGridEl: HTMLElement
|
||||
let purposeEl: HTMLElement, stepsEl: HTMLElement, photosGridEl: HTMLElement
|
||||
let photoFirstEl: HTMLElement, photoUsEl: HTMLElement
|
||||
let photosGridOffset: number = photosGridEl && photosGridEl.offsetTop
|
||||
|
||||
$: parallaxPhotos = photosGridEl && map(scrollY, photosGridOffset - innerHeight, photosGridOffset + innerHeight / 1.5, 0, innerHeight * 0.15, true)
|
||||
@@ -32,20 +35,120 @@
|
||||
? [0, 2, 5, 7, 9, 12, 17, 20, 22, 26, 30, 32, 34]
|
||||
: [0]
|
||||
|
||||
const introText = about.intro_text
|
||||
.replace('<strong>',
|
||||
`<a href="/${about.intro_firstlocation.country.slug}/${about.intro_firstlocation.slug}" sveltekit:noscroll sveltekit:prefetch>
|
||||
<img src="${getAssetUrlKey(about.intro_firstlocation.country.flag.id, 'square-small-jpg')}" width="32" height="32" alt="${about.intro_firstlocation.country.flag.title}">
|
||||
<strong>
|
||||
`)
|
||||
.replace('</strong>', '</strong></a>')
|
||||
|
||||
|
||||
onMount(() => {
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
const animation = timeline([
|
||||
// Heading
|
||||
['.heading .text', {
|
||||
y: [24, 0],
|
||||
opacity: [0, 1],
|
||||
z: 0,
|
||||
}, {
|
||||
at: 0.5,
|
||||
}],
|
||||
|
||||
// First photo
|
||||
[photoFirstEl, {
|
||||
y: ['10%', 0],
|
||||
rotate: [0, getComputedStyle(photoFirstEl).getPropertyValue('--rotate')],
|
||||
opacity: [0, 1],
|
||||
z: 0,
|
||||
}, {
|
||||
at: 0.75,
|
||||
opacity: {
|
||||
duration: 1
|
||||
}
|
||||
}],
|
||||
|
||||
// Portrait photo
|
||||
[photoUsEl, {
|
||||
y: ['10%', 0],
|
||||
x: [0, '5%'],
|
||||
rotate: [0, 5],
|
||||
opacity: [0, 1],
|
||||
z: 0,
|
||||
}, {
|
||||
at: 1,
|
||||
opacity: {
|
||||
duration: 1
|
||||
}
|
||||
}],
|
||||
|
||||
// Text
|
||||
['.about__introduction .text', {
|
||||
y: [32, 0],
|
||||
opacity: [0, 1],
|
||||
z: 0,
|
||||
}, {
|
||||
at: 1.2,
|
||||
}],
|
||||
], {
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
},
|
||||
})
|
||||
animation.stop()
|
||||
|
||||
// Run animation
|
||||
requestAnimationFrame(animation.play)
|
||||
|
||||
|
||||
/**
|
||||
* Intro parallax
|
||||
*/
|
||||
// First photo
|
||||
scroll(animate(photoFirstEl.querySelector('figure'), {
|
||||
y: ['-5%', '5%'],
|
||||
x: [0, '-3%'],
|
||||
rotate: [-3, 0],
|
||||
z: 0,
|
||||
}), {
|
||||
target: photoFirstEl,
|
||||
offset: ["-200%", "150%"]
|
||||
})
|
||||
// Portrait photo
|
||||
scroll(animate(photoUsEl.querySelector('figure'), {
|
||||
y: [0, '5%'],
|
||||
x: [0, '-2%'],
|
||||
rotate: [-2, 0],
|
||||
z: 0,
|
||||
}), {
|
||||
target: photoUsEl,
|
||||
offset: ["-250%", "150%"]
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Purpose reveal
|
||||
*/
|
||||
inView(purposeEl, ({ target, isIntersecting }) => {
|
||||
target.classList.toggle('is-visible', isIntersecting)
|
||||
}, { amount: 0.75 })
|
||||
}, { amount: 0.6 })
|
||||
|
||||
// Parallax
|
||||
scroll(animate(purposeEl.querySelector('picture img'), {
|
||||
y: [0, '40%'],
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* Steps scroll animation
|
||||
*/
|
||||
const cards = stepsEl.querySelectorAll('.step')
|
||||
const cardsAmount = data.about.process_steps.length
|
||||
const cardsAmount = about.process_steps.length
|
||||
|
||||
cards.forEach((card: HTMLElement, i: number) => {
|
||||
const index = i + 1
|
||||
@@ -86,37 +189,90 @@
|
||||
|
||||
<PageTransition name="about">
|
||||
<Heading
|
||||
text={data.about.description}
|
||||
text={about.description}
|
||||
/>
|
||||
|
||||
<section class="about__introduction">
|
||||
<div class="container grid">
|
||||
<div class="photo-first" bind:this={photoFirstEl}>
|
||||
<figure>
|
||||
<Image
|
||||
class="shadow-box-dark"
|
||||
id={about.intro_firstphoto.id}
|
||||
alt={about.intro_firstphoto.title}
|
||||
sizeKey="photo-list"
|
||||
sizes={{
|
||||
small: { width: 400 },
|
||||
medium: { width: 600 },
|
||||
large: { width: 800 },
|
||||
}}
|
||||
ratio={1.5}
|
||||
/>
|
||||
<figcaption class="text-info">{about.intro_firstphoto_caption}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div class="photo-us" bind:this={photoUsEl}>
|
||||
<figure>
|
||||
<Image
|
||||
class="shadow-box-dark"
|
||||
id={about.intro_portraits.id}
|
||||
alt={about.intro_portraits.title}
|
||||
sizeKey="square"
|
||||
sizes={{
|
||||
small: { width: 250 }
|
||||
}}
|
||||
ratio={1}
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div class="text text-normal">
|
||||
{@html introText}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="about__purpose" bind:this={purposeEl}>
|
||||
<div class="container-wide">
|
||||
<div class="text title-xl" role="heading">
|
||||
{@html data.about.purpose_text}
|
||||
{@html about.purpose_text}
|
||||
</div>
|
||||
|
||||
<div class="background" />
|
||||
<div class="background">
|
||||
<picture class="background__illustration">
|
||||
<source media="(min-width: 1200px)" srcset={getAssetUrlKey(about.intro_firstlocation.illustration_desktop_2x.id, 'illustration-desktop-2x')}>
|
||||
<source media="(min-width: 768px)" srcset={getAssetUrlKey(about.intro_firstlocation.illustration_desktop.id, 'illustration-desktop-1x')}>
|
||||
<img
|
||||
src={getAssetUrlKey(about.intro_firstlocation.illustration_mobile.id, 'illustration-mobile')}
|
||||
width={320}
|
||||
height={824}
|
||||
alt="Illustration for {about.intro_firstlocation.name}"
|
||||
decoding="async"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="about__process">
|
||||
<div class="container grid">
|
||||
<div class="title">
|
||||
<h2 class="title-big">{data.about.process_title}</h2>
|
||||
<p class="text-normal">{data.about.process_subtitle}</p>
|
||||
<h2 class="title-big">{about.process_title}</h2>
|
||||
<p class="text-normal">{about.process_subtitle}</p>
|
||||
</div>
|
||||
|
||||
<div class="steps" bind:this={stepsEl}
|
||||
style:--cards-amount={data.about.process_steps.length}
|
||||
style:--cards-amount={about.process_steps.length}
|
||||
>
|
||||
{#each data.about.process_steps as step, index}
|
||||
{#each about.process_steps as step, index}
|
||||
<ProcessStep {...step} index={index} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="intention">
|
||||
<p class="intention__content title-medium">
|
||||
{data.about.process_intention}
|
||||
{about.process_intention}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +281,7 @@
|
||||
<section class="about__photos" bind:this={photosGridEl}>
|
||||
<div class="container-wide">
|
||||
<div class="photos-grid" style:--parallax-y="{parallaxPhotos}px">
|
||||
{#each data.photos as { image: { id }, title }, index}
|
||||
{#each photos as { image: { id }, title }, index}
|
||||
<AboutGridPhoto class="about-grid-photo"
|
||||
{id}
|
||||
alt={title}
|
||||
@@ -138,10 +294,10 @@
|
||||
|
||||
<div class="about__bottom container grid">
|
||||
<section class="about__interest grid">
|
||||
<h2 class="title-xl">{data.about.contact_title}</h2>
|
||||
<h2 class="title-xl">{about.contact_title}</h2>
|
||||
|
||||
<div class="blocks">
|
||||
{#each data.about.contact_blocks as { title, text, link, button }}
|
||||
{#each about.contact_blocks as { title, text, link, button }}
|
||||
<div class="block">
|
||||
<h3 class="text-label">{title}</h3>
|
||||
<p class="text-normal">{text}</p>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
background-position: 0 100%;
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
transition: color 0.6s var(--ease-quart);
|
||||
transition: color 0.4s var(--ease-quart);
|
||||
|
||||
&:hover {
|
||||
color: $color-secondary-light;
|
||||
|
||||
@@ -1,21 +1,159 @@
|
||||
.about {
|
||||
/*
|
||||
** Introduction
|
||||
*/
|
||||
&__introduction {
|
||||
:global(picture) {
|
||||
overflow: hidden;
|
||||
|
||||
:global(img) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// First photo
|
||||
.photo-first {
|
||||
--rotate: -3deg;
|
||||
$offset: 32px;
|
||||
grid-column: 1 / -2;
|
||||
grid-row: 1;
|
||||
margin-left: -$offset;
|
||||
transform: rotate(var(--rotate)) translateZ(0);
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 2 / -6;
|
||||
}
|
||||
@include bp (md) {
|
||||
grid-column: 4 / span 12;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:global(picture) {
|
||||
border-radius: clamp(8px, 1vw, 16px);
|
||||
}
|
||||
|
||||
figcaption {
|
||||
width: 75%;
|
||||
max-width: 250px;
|
||||
margin-top: 12px;
|
||||
margin-left: $offset;
|
||||
color: rgba($color-tertiary, 0.5);
|
||||
|
||||
@include bp (mob-lg) {
|
||||
max-width: 400px;
|
||||
}
|
||||
@include bp (sm) {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Portrait
|
||||
.photo-us {
|
||||
position: relative;
|
||||
grid-row: 1;
|
||||
z-index: 2;
|
||||
grid-column: 6 / -1;
|
||||
margin-top: clamp(56px, 12vw, 120px);
|
||||
|
||||
@include bp (sm) {
|
||||
grid-column: 18 / -1;
|
||||
}
|
||||
@include bp (md) {
|
||||
grid-column: 16 / span 4;
|
||||
margin-top: clamp(56px, 7vw, 112px);
|
||||
}
|
||||
|
||||
:global(picture) {
|
||||
overflow: hidden;
|
||||
border-radius: clamp(6px, 1vw, 12px);
|
||||
transform: translate3d(clamp(32px, 3vw, 56px), 0, 0);
|
||||
|
||||
@include bp (md) {
|
||||
transform: translate3d(calc(-1 * clamp(32px, 3vw, 56px)), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
.text {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
color: $color-tertiary;
|
||||
margin-top: 48px;
|
||||
|
||||
@include bp (sm) {
|
||||
margin-top: 96px;
|
||||
grid-column: 3 / -3;
|
||||
}
|
||||
@include bp (md) {
|
||||
grid-column: 17 / span 7;
|
||||
// max-width: 420px;
|
||||
margin-top: calc(-1 * clamp(80px, 8vw, 120px));
|
||||
}
|
||||
|
||||
:global(img) {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: auto;
|
||||
margin-right: 4px;
|
||||
margin-left: 8px;
|
||||
margin-bottom: 3px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
:global(a) {
|
||||
display: inline-block;
|
||||
color: currentColor;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
:global(strong) {
|
||||
color: $color-secondary-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
:global(strong) {
|
||||
font-weight: normal;
|
||||
padding-bottom: 2px;
|
||||
background-image: linear-gradient(rgba($color-tertiary, 0.3), rgba($color-tertiary, 0.3));
|
||||
background-position: 0 100%;
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
transition: color 0.4s var(--ease-quart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Purpose
|
||||
*/
|
||||
&__purpose {
|
||||
margin-top: 72px;
|
||||
text-align: center;
|
||||
|
||||
@include bp (sm) {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.container-wide {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - var(--sides));
|
||||
height: calc(100vh - var(--sides));
|
||||
max-height: 720px;
|
||||
color: #fff;
|
||||
padding: 0 8%;
|
||||
|
||||
@include bp (sm) {
|
||||
padding: 0 18%;
|
||||
min-height: calc(100vh - var(--sides));
|
||||
max-height: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +177,49 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
background-color: $color-primary-tertiary20;
|
||||
border-radius: 8px;
|
||||
border-radius: 0px;
|
||||
transform: scale(1.15) translateZ(0);
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 3s;
|
||||
transition-timing-function: var(--ease-quart);
|
||||
transition-property: opacity, transform, border-radius;
|
||||
transition-delay: 0.25s;
|
||||
|
||||
@include bp (sm) {
|
||||
border-radius: 16px;
|
||||
&, &__illustration img {
|
||||
transition-duration: 2.5s;
|
||||
transition-timing-function: var(--ease-quart);
|
||||
}
|
||||
|
||||
// Illustration
|
||||
&__illustration {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.75;
|
||||
$mask: linear-gradient(180deg, #000 0%, rgba(0,0,0,0) 62.5%);
|
||||
-webkit-mask-image: $mask;
|
||||
mask-image: $mask;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
bottom: 60%;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-delay: 0.6s;
|
||||
|
||||
@include bp (md) {
|
||||
bottom: 50%;
|
||||
}
|
||||
@include bp (sd) {
|
||||
bottom: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +230,21 @@
|
||||
opacity: 1;
|
||||
|
||||
:global(strong) {
|
||||
color: $color-secondary-light;
|
||||
color: darken($color-secondary-light, 4);
|
||||
}
|
||||
}
|
||||
.background {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateZ(0);
|
||||
border-radius: 8px;
|
||||
|
||||
@include bp (sm) {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
&__illustration img {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +256,7 @@
|
||||
&__process {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-top: 72px;
|
||||
|
||||
@include bp (sm) {
|
||||
margin: 128px 0 0;
|
||||
|
||||
Reference in New Issue
Block a user