upgrades and updates
This commit is contained in:
parent
2149372530
commit
d7cab5d526
11 changed files with 163 additions and 73 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -11,7 +11,7 @@
|
|||
"gsap": "^3.13.0",
|
||||
"mdsvex": "^0.11.0",
|
||||
"pixi-filters": "^6.1.5",
|
||||
"pixi.js": "^8.0.0",
|
||||
"pixi.js": "^8.15.0",
|
||||
"superjson": "^1.13.1",
|
||||
"svelte-cloudinary": "^1.1.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@
|
|||
"dependencies": {
|
||||
"gsap": "^3.13.0",
|
||||
"mdsvex": "^0.11.0",
|
||||
"pixi.js": "^8.15.0",
|
||||
"pixi-filters": "^6.1.5",
|
||||
"pixi.js": "^8.0.0",
|
||||
"superjson": "^1.13.1",
|
||||
"svelte-cloudinary": "^1.1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<!-- <HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
|
|
@ -79,8 +80,7 @@
|
|||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape />
|
||||
<HomeIlluShape /> -->
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
gap: clamp(1px, 0.3vw, 5px);
|
||||
}
|
||||
:global(.home-illu-shapes > *) {
|
||||
flex-basis: calc(20% - clamp(1px, 0.3vw, 5px));
|
||||
flex-basis: calc(33.333% - clamp(1px, 0.3vw, 5px));
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang='ts'>
|
||||
import { onMount } from "svelte";
|
||||
import { gsap } from "gsap";
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
|
||||
onMount(() => {
|
||||
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
let navlinks = document.querySelectorAll('nav a');
|
||||
navlinks.forEach((link, index) => {
|
||||
link.addEventListener('click', (e)=> {
|
||||
if (e.target?.toString() === $page.url.toString()) {
|
||||
if (e.target?.toString() === page.url.toString()) {
|
||||
return;
|
||||
}
|
||||
const checkbox = document.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
|
|
@ -52,11 +52,11 @@
|
|||
<span class="close">×</span>
|
||||
</label>
|
||||
<nav id="nav">
|
||||
<a href="/" class="navlink {$page.route.id === '/' ? 'current' : ''}">Home</a>
|
||||
<a href="/work" class="navlink {$page.route.id?.includes('/work') ? 'current' : ''}">Work</a>
|
||||
<a href="/service" class="navlink {$page.route.id === '/service' ? 'current' : ''}">Services</a>
|
||||
<!-- <a href="/about" class="navlink {$page.route.id === '/about' ? 'current' : ''}">About</a> -->
|
||||
<a href="/contact" class="navlink {$page.route.id === '/contact' ? 'current' : ''}">Contact</a>
|
||||
<a href="/" class="navlink {page.route.id === '/' ? 'current' : ''}">Home</a>
|
||||
<a href="/work" class="navlink {page.route.id?.includes('/work') ? 'current' : ''}">Work</a>
|
||||
<a href="/service" class="navlink {page.route.id === '/service' ? 'current' : ''}">Services</a>
|
||||
<!-- <a href="/about" class="navlink {page.route.id === '/about' ? 'current' : ''}">About</a> -->
|
||||
<a href="/contact" class="navlink {page.route.id === '/contact' ? 'current' : ''}">Contact</a>
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
--spacing-outer: 5vw;
|
||||
--spacing-nav: 5vw;
|
||||
--color-bg: rgb(63, 111, 207);
|
||||
--color-bg-variant-1: rgb(63, 111, 207);
|
||||
--color-bg-variant-2: rgb(207, 63, 70);
|
||||
--color-bg-variant-3: rgb(63, 207, 80);
|
||||
--color-bg-variant-4: rgb(255, 174, 0);
|
||||
--color-text: rgb(255, 234, 217);
|
||||
--color-highlight: rgb(29, 12, 18);
|
||||
--aspect-ratio-heroes: 1.5;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,50 @@
|
|||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import '$lib/styles/global.scss';
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Loader from '$lib/components/Loader.svelte';
|
||||
import { navigating, page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface LayoutData {
|
||||
pathname: string;
|
||||
}
|
||||
let { data, children }: { data: LayoutData, children: Snippet } = $props();
|
||||
|
||||
// Track pathname changes - if pathname changes but navigating is still true,
|
||||
// navigation might be stuck (though this shouldn't happen normally)
|
||||
let lastPathname = $derived(data.pathname);
|
||||
$effect(() => {
|
||||
if (data.pathname !== lastPathname) {
|
||||
lastPathname = data.pathname;
|
||||
let { data, children }: { data: LayoutData; children: Snippet } = $props();
|
||||
|
||||
let showLoader = $state(false);
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let navToken = 0;
|
||||
|
||||
function clearTimer() {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
beforeNavigate((nav) => {
|
||||
// token to avoid races between overlapping navigations
|
||||
const token = ++navToken;
|
||||
|
||||
clearTimer();
|
||||
showLoader = false;
|
||||
|
||||
// only show if navigation takes >150ms
|
||||
timer = setTimeout(() => {
|
||||
if (token === navToken) showLoader = true;
|
||||
}, 150);
|
||||
|
||||
// hide loader when THIS navigation completes (even if afterNavigate timing is odd)
|
||||
nav.complete.finally(() => {
|
||||
if (token !== navToken) return;
|
||||
clearTimer();
|
||||
showLoader = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -25,14 +52,14 @@
|
|||
<meta property="og:url" content={`https://floter.design`+data.pathname} />
|
||||
<meta property="og:image" content="https://floter.design/ogimage.png">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="description" content={$page.data.description ? $page.data.description : 'Simon Flöter is a designer and creative developer'} />
|
||||
<title>{$page.data.title ? $page.data.title : 'Simon Flöter, Designer and Creative Developer'}</title>
|
||||
<meta name="description" content={page.data.description ? page.data.description : 'Simon Flöter is a designer and creative developer'} />
|
||||
<title>{page.data.title ? page.data.title : 'Simon Flöter, Designer and Creative Developer'}</title>
|
||||
</svelte:head>
|
||||
|
||||
<Header />
|
||||
{#key data.pathname}
|
||||
<div class="content">
|
||||
{#if $navigating}
|
||||
{#if showLoader}
|
||||
<Loader />
|
||||
{/if}
|
||||
{@render children()}
|
||||
|
|
|
|||
|
|
@ -93,9 +93,6 @@
|
|||
|
||||
</script>
|
||||
|
||||
{#if !mounted}
|
||||
<Loader />
|
||||
{/if}
|
||||
<article class="scroller">
|
||||
<section class="canvasized splash">
|
||||
<h1 class="align-middle">I create digital experiences that <em>stand out</em> from the rest.</h1>
|
||||
|
|
|
|||
43
src/routes/ethical-webdevelopment/+page.svelte
Normal file
43
src/routes/ethical-webdevelopment/+page.svelte
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<article>
|
||||
<div class="eyebrow">
|
||||
Research & Thoughts
|
||||
</div>
|
||||
<h1>Ethical Web Development</h1>
|
||||
<p>
|
||||
What does that mean? For me, it means building websites and applications that prioritize user privacy, accessibility, and sustainability.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
article {
|
||||
max-width: 1200px;
|
||||
padding: 0
|
||||
var(--spacing-outer)
|
||||
100px
|
||||
var(--spacing-outer);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
margin: 0
|
||||
var(--spacing-outer)
|
||||
0
|
||||
150px;
|
||||
padding: calc(50vh - var(--spacing-outer) - 6vw)
|
||||
var(--spacing-outer)
|
||||
var(--spacing-outer)
|
||||
var(--spacing-outer);
|
||||
width: calc(100% - 150px - var(--spacing-outer));
|
||||
}
|
||||
}
|
||||
.eyebrow {
|
||||
font-size: 1.25em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.01em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: clamp(2.5rem, 7vw, 7rem);
|
||||
letter-spacing: -0.04em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -94,12 +94,12 @@
|
|||
transformOrigin: '0 0',
|
||||
})
|
||||
gsap.set('.work',{
|
||||
opacity: 0,
|
||||
autoAlpha: 0,
|
||||
yPercent: 50,
|
||||
scaleY: 0,
|
||||
})
|
||||
gsap.to('.work',{
|
||||
opacity:1,
|
||||
autoAlpha:1,
|
||||
yPercent: 0,
|
||||
scaleY: 1,
|
||||
duration: .75,
|
||||
|
|
@ -129,37 +129,51 @@
|
|||
overwrite: true,
|
||||
})
|
||||
}
|
||||
// Hover states of .work
|
||||
works.forEach((work, index) => {
|
||||
work.addEventListener('mouseenter', (e) => {
|
||||
if (isZoomed) return;
|
||||
gsap.to(work, {
|
||||
backgroundColor: 'var(--color-bg)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
gsap.to(work.querySelector('.work-logo'), {
|
||||
color: 'var(--color-highlight)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
})
|
||||
work.addEventListener('mouseleave', (e) => {
|
||||
if (isZoomed) return;
|
||||
gsap.to(work, {
|
||||
backgroundColor: 'var(--color-highlight)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
gsap.to(work.querySelector('.work-logo'), {
|
||||
color: 'var(--color-bg)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
})
|
||||
})
|
||||
window.addEventListener('mousemove', mouseMoveHandler);
|
||||
|
||||
// Create handler functions outside the loop for proper cleanup
|
||||
const handleMouseEnter = (work: HTMLElement) => {
|
||||
if (isZoomed) return;
|
||||
gsap.killTweensOf([work, work.querySelector('.work-logo')]);
|
||||
gsap.to(work, {
|
||||
backgroundColor: 'rgb(63, 111, 207)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
const logo = work.querySelector('.work-logo');
|
||||
if (logo) {
|
||||
gsap.to(logo, {
|
||||
color: 'rgb(29, 12, 18)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseLeave = (work: HTMLElement) => {
|
||||
if (isZoomed) return;
|
||||
gsap.killTweensOf([work, work.querySelector('.work-logo')]);
|
||||
gsap.to(work, {
|
||||
backgroundColor: 'rgb(29, 12, 18)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
const logo = work.querySelector('.work-logo');
|
||||
if (logo) {
|
||||
gsap.to(logo, {
|
||||
color: 'rgb(63, 111, 207)',
|
||||
duration: .125,
|
||||
ease: 'power1.inOut',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Hover states of .work
|
||||
works.forEach((work) => {
|
||||
work.addEventListener('mouseenter', () => handleMouseEnter(work));
|
||||
work.addEventListener('mouseleave', () => handleMouseLeave(work));
|
||||
})
|
||||
|
||||
// Move the works the same way on touch drag
|
||||
let touchMoveHandler = (e: TouchEvent) => {
|
||||
let x = e.touches[0].clientX;
|
||||
|
|
@ -186,9 +200,9 @@
|
|||
window.removeEventListener('mousemove', mouseMoveHandler);
|
||||
window.removeEventListener('touchmove', touchMoveHandler);
|
||||
gsap.killTweensOf('.works, .work');
|
||||
works.forEach((work, index) => {
|
||||
work.removeEventListener('mouseenter', (e) => {});
|
||||
work.removeEventListener('mouseleave', (e) => {});
|
||||
works.forEach((work) => {
|
||||
work.removeEventListener('mouseenter', () => handleMouseEnter(work));
|
||||
work.removeEventListener('mouseleave', () => handleMouseLeave(work));
|
||||
work.removeEventListener('click', (e) => {});
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@
|
|||
|
||||
gsap.to('.logo-wrapper', {
|
||||
opacity: 0,
|
||||
scale: 0.85,
|
||||
zIndex: -1,
|
||||
duration: 1,
|
||||
duration: .5,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
gsap.from('.work', {
|
||||
|
|
@ -163,10 +164,10 @@
|
|||
</div>
|
||||
<article class="work">
|
||||
{#if visible}
|
||||
<h1>{data.title}</h1>
|
||||
<div class="description">
|
||||
{data.description}
|
||||
</div>
|
||||
<h1>{data.title}</h1>
|
||||
<div class="work-content">
|
||||
<div class="infobox">
|
||||
<div class="tasks">
|
||||
|
|
@ -245,19 +246,20 @@
|
|||
}
|
||||
}
|
||||
h1 {
|
||||
margin: .5em 0 0.25em 0;
|
||||
margin: .125em 0 0.5em 0;
|
||||
font-size: 2.5rem;
|
||||
@media screen and (min-width: 768px) {
|
||||
font-size: 5rem;
|
||||
font-size: 6rem;
|
||||
}
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 1.5em;
|
||||
margin-top: 1.5em;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.0075em;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.25rem;
|
||||
@media screen and (min-width: 768px) {
|
||||
font-size: 2.5rem;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
.infobox {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
overflow: hidden;
|
||||
perspective: 700px;
|
||||
transform-origin: 0 0;
|
||||
will-change: transform;
|
||||
}
|
||||
.headline {
|
||||
position: fixed;
|
||||
|
|
@ -48,9 +49,8 @@
|
|||
display: grid;
|
||||
grid-template-columns: repeat(6, 100vw);
|
||||
grid-auto-rows: 100vh;
|
||||
// grid-template-rows: repeat(6, 100vh);
|
||||
// grid-auto-flow: column;
|
||||
gap: 6px;
|
||||
will-change: transform;
|
||||
}
|
||||
.work {
|
||||
background-color: var(--color-highlight);
|
||||
|
|
@ -59,7 +59,9 @@
|
|||
width: 100vw;
|
||||
height: 100vh;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateZ(700px);
|
||||
will-change: transform;
|
||||
}
|
||||
:global(.work-logo) {
|
||||
width: 100%;
|
||||
|
|
@ -70,6 +72,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
will-change: transform;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
width: 60%;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue