upgrades and updates

This commit is contained in:
saiminh 2026-01-22 17:24:51 +13:00
parent 2149372530
commit d7cab5d526
11 changed files with 163 additions and 73 deletions

2
package-lock.json generated
View file

@ -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"
},

View file

@ -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"
}

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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()}

View file

@ -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>

View file

@ -0,0 +1,43 @@
<article>
<div class="eyebrow">
Research &amp; 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>

View file

@ -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) => {});
})
}

View file

@ -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 {

View file

@ -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%;