floter.design/src/routes/contact/+page.svelte
2026-02-14 17:35:00 +01:00

174 lines
No EOL
7.5 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { enhance } from '$app/forms';
import ContactCanvas from '$lib/components/ContactCanvas.svelte';
import gsap from 'gsap';
import { SplitText } from 'gsap/SplitText';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
export let form: { error?: string; fields?: { name?: string; email?: string; contact?: string } } | undefined;
let canvasTexts: Array<HTMLElement> = [];
let contactFormVisible = false;
let contactFormClickHandler = (e: Event) => {
if (contactFormVisible) {
gsap.to('.wordChildren', { autoAlpha: 1, yPercent: 0, duration: 1, ease: 'power4.inOut' })
gsap.to('.alternatives > *', { autoAlpha: 1, scale: 1, duration: .5, stagger: 0.1, ease: 'power4.inOut' })
gsap.fromTo('.formwrapper',
{ autoAlpha: 1, yPercent: 0 },
{ autoAlpha: 0, yPercent: 50, duration: .6, ease: 'power4.inOut', onComplete: () => {
gsap.set('.formwrapper', { zIndex: -1 })
} }
)
} else {
gsap.to('.wordChildren', { autoAlpha: 1, yPercent: -100, duration: 1, ease: 'power4.inOut' })
gsap.to('.alternatives > *', { autoAlpha: 0, scale: 0.5, duration: .5, stagger: 0.1, ease: 'power4.inOut' })
gsap.set('.formwrapper', { zIndex: 2 })
gsap.fromTo('.formwrapper',
{ autoAlpha: 1, yPercent: 100 },
{ autoAlpha: 1, yPercent: 0, duration: 1, ease: 'power4.inOut' }
)
gsap.fromTo('.formwrapper label > *',
{ autoAlpha: 0, yPercent: 100 },
{ autoAlpha: 1, yPercent: 0, duration: .5, stagger: 0.0125, ease: 'power4.out', delay: .25}
)
}
contactFormVisible = !contactFormVisible;
}
onMount( () => {
gsap.registerPlugin( SplitText, ScrollTrigger );
let split = new SplitText('.toCanvas', { type: 'words, lines', wordsClass: 'wordChildren', linesClass: 'lineChildren' });
let alignmiddles: Array<HTMLElement> = Array.from(document.querySelectorAll('.toCanvas'));
alignmiddles.forEach( (toCanvas) => {
let spanlengths: Array<number> = [];
toCanvas.querySelectorAll('.lineChildren').forEach( (line) => {
line.innerHTML = '<span>' + line.innerHTML + '</span>'
})
toCanvas.querySelectorAll('.lineChildren span').forEach( (span) => {
spanlengths.push(span.getBoundingClientRect().width);
})
// get the longest spanlength
let longestSpan = Math.max(...spanlengths);
toCanvas.style.position = 'relative';
toCanvas.style.left = (toCanvas.offsetWidth - longestSpan) / 2 + 'px';
})
canvasTexts = Array.from(document.querySelectorAll('.wordChildren'));
const introInElem = document.querySelector('.pagewrapper') as HTMLElement;
gsap.fromTo(introInElem.querySelectorAll('.wordChildren'), {
autoAlpha: 0, yPercent: -100
}, {
autoAlpha: 1, yPercent: 0, duration: 1.5, stagger: 0.025, ease: 'power4.inOut',
})
gsap.fromTo(introInElem?.querySelectorAll('.alternatives p, .alternatives .button'), {
autoAlpha: 0, yPercent: 100
}, {
autoAlpha: 1, yPercent: 0, duration: 1, stagger: 0.025, ease: 'power4.inOut',
})
return () => {
gsap.killTweensOf('.toCanvas, .wordChildren');
}
})
</script>
<div class="pagewrapper">
<div class="intro w-full min-h-screen flex flex-col justify-center pb-16 md:pb-0">
<h1 class="toCanvas contact-h1">Let's be strange,<br><em>not strangers.</em></h1>
<div class="alternatives mx-auto my-0 mb-4 w-full text-center md:mb-8 md:pb-4 [&_p]:opacity-0 [&_.button]:opacity-0 [&_p]:text-base [&_p]:my-2 md:[&_p]:text-[1.33em] [&_ul]:mx-(--spacing-outer) [&_ul]:list-none [&_ul]:flex [&_ul]:justify-center [&_ul]:gap-1 [&_ul_li]:block [&_ul_li]:m-0 [&_.button]:my-1 [&_.button]:block [&_.button]:text-[0.9em] md:[&_.button]:text-[1.2em]">
<p>Choose your flavour of contact:</p>
<ul>
<li><span class="button button--primary" on:click={contactFormClickHandler} on:keydown={contactFormClickHandler} role="button" tabindex="0">Contact form</span></li>
<li><a class="button" href="mailto:simon@floter.design">Email</a></li>
<li><a class="button" href="https://www.linkedin.com/in/floter/">LinkedIn</a></li>
</ul>
</div>
</div>
<div class="formwrapper fixed inset-0 w-full h-full p-(--spacing-outer) pb-16 pl-(--spacing-outer) pr-(--spacing-outer) bg-(--color-bg) z-0 opacity-0 invisible overflow-y-auto [--form-maxwidth:1000px]">
<span class="button button-back m-0" on:click={contactFormClickHandler} on:keydown={contactFormClickHandler} role="button" tabindex="0">← Back</span>
<form name="contact" method="POST" use:enhance class="box-border max-w-(--form-maxwidth) mx-auto overflow-hidden py-4">
<div class="inputs-flex-row md:flex md:gap-4 md:justify-center md:max-w-(--form-maxwidth) md:mx-auto [&_label]:md:basis-1/2">
<label for="name" class="contact-label">
<input type="text" name="name" id="name" placeholder="Your name" value={form?.fields?.name ?? ''} class="contact-input">
</label>
<label for="email" class="contact-label">
<input type="email" name="email" id="email" placeholder="Your Email?*" required value={form?.fields?.email ?? ''} class="contact-input">
</label>
</div>
<label for="contact" class="contact-label">
<textarea rows="8" name="contact" id="contact" placeholder="Your business propositions, praise, complaints and/or threats" required class="contact-input">{form?.fields?.contact ?? ''}</textarea>
</label>
<div class="disclaimer max-w-(--form-maxwidth) my-4 mx-auto text-base">
<p>Disclaimer: I will only use the data you submit here (name, email, message) to respond. I will not pass it on to any third party. If I don't hear from you I will delete the data and keep no records of it.</p>
</div>
{#if form?.error}
<p class="form-error max-w-(--form-maxwidth) mx-auto mb-4 p-3 border border-(--color-text) rounded text-[0.95em] bg-white/10" role="alert" aria-live="polite">{form.error}</p>
{/if}
<div class="send max-w-(--form-maxwidth) mx-auto">
<button class="button button--xl button--primary" type="submit">Send it!</button>
</div>
</form>
</div>
</div>
<ContactCanvas textsToCanvas={canvasTexts} />
<style>
.contact-h1 {
visibility: hidden;
font-size: 12vw;
line-height: .9;
letter-spacing: -0.02em;
margin: 1em var(--spacing-nav) 1em var(--spacing-nav);
& em {
color: var(--color-highlight);
}
}
@media screen and (min-width: 768px) {
.contact-h1 {
margin: 1em var(--spacing-outer) .75em var(--spacing-outer);
font-size: 4.5em;
}
}
.contact-label {
font-size: 1em;
}
@media screen and (min-width: 768px) {
.contact-label { font-size: 1.25em; }
}
.contact-input {
width: 100%;
border: 0 solid var(--color-text);
background-color: var(--color-text);
color: var(--color-bg);
border-radius: 4px;
font-family: 'Stratos', sans-serif;
font-size: 1em;
line-height: 1.2;
padding: .75em;
transition: all 0.3s ease-out;
margin: 0 auto 1em auto;
display: block;
max-width: var(--form-maxwidth);
}
.contact-input:focus {
outline: none;
color: var(--color-bg);
background-color: var(--color-text);
}
.contact-input:placeholder-shown {
background-color: rgba(255, 255, 225, 0.2);
}
.contact-input:focus:placeholder-shown {
background-color: var(--color-text);
}
.contact-input::placeholder {
color: var(--color-text);
opacity: .8;
}
</style>