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",
|
"gsap": "^3.13.0",
|
||||||
"mdsvex": "^0.11.0",
|
"mdsvex": "^0.11.0",
|
||||||
"pixi-filters": "^6.1.5",
|
"pixi-filters": "^6.1.5",
|
||||||
"pixi.js": "^8.0.0",
|
"pixi.js": "^8.15.0",
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"svelte-cloudinary": "^1.1.0"
|
"svelte-cloudinary": "^1.1.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gsap": "^3.13.0",
|
"gsap": "^3.13.0",
|
||||||
"mdsvex": "^0.11.0",
|
"mdsvex": "^0.11.0",
|
||||||
|
"pixi.js": "^8.15.0",
|
||||||
"pixi-filters": "^6.1.5",
|
"pixi-filters": "^6.1.5",
|
||||||
"pixi.js": "^8.0.0",
|
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"svelte-cloudinary": "^1.1.0"
|
"svelte-cloudinary": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
|
<!-- <HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
|
|
@ -79,8 +80,7 @@
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape />
|
||||||
<HomeIlluShape />
|
<HomeIlluShape /> -->
|
||||||
<HomeIlluShape />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
gap: clamp(1px, 0.3vw, 5px);
|
gap: clamp(1px, 0.3vw, 5px);
|
||||||
}
|
}
|
||||||
:global(.home-illu-shapes > *) {
|
: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;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { gsap } from "gsap";
|
import { gsap } from "gsap";
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/state";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
let navlinks = document.querySelectorAll('nav a');
|
let navlinks = document.querySelectorAll('nav a');
|
||||||
navlinks.forEach((link, index) => {
|
navlinks.forEach((link, index) => {
|
||||||
link.addEventListener('click', (e)=> {
|
link.addEventListener('click', (e)=> {
|
||||||
if (e.target?.toString() === $page.url.toString()) {
|
if (e.target?.toString() === page.url.toString()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const checkbox = document.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
const checkbox = document.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||||
|
|
@ -52,11 +52,11 @@
|
||||||
<span class="close">×</span>
|
<span class="close">×</span>
|
||||||
</label>
|
</label>
|
||||||
<nav id="nav">
|
<nav id="nav">
|
||||||
<a href="/" class="navlink {$page.route.id === '/' ? 'current' : ''}">Home</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="/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="/service" class="navlink {page.route.id === '/service' ? 'current' : ''}">Services</a>
|
||||||
<!-- <a href="/about" class="navlink {$page.route.id === '/about' ? 'current' : ''}">About</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="/contact" class="navlink {page.route.id === '/contact' ? 'current' : ''}">Contact</a>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
--spacing-outer: 5vw;
|
--spacing-outer: 5vw;
|
||||||
--spacing-nav: 5vw;
|
--spacing-nav: 5vw;
|
||||||
--color-bg: rgb(63, 111, 207);
|
--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-text: rgb(255, 234, 217);
|
||||||
--color-highlight: rgb(29, 12, 18);
|
--color-highlight: rgb(29, 12, 18);
|
||||||
--aspect-ratio-heroes: 1.5;
|
--aspect-ratio-heroes: 1.5;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,50 @@
|
||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import '$lib/styles/global.scss';
|
import '$lib/styles/global.scss';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import Loader from '$lib/components/Loader.svelte';
|
import Loader from '$lib/components/Loader.svelte';
|
||||||
import { navigating, page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
import { beforeNavigate } from '$app/navigation';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
interface LayoutData {
|
interface LayoutData {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
}
|
}
|
||||||
let { data, children }: { data: LayoutData, children: Snippet } = $props();
|
|
||||||
|
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 showLoader = $state(false);
|
||||||
let lastPathname = $derived(data.pathname);
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
$effect(() => {
|
let navToken = 0;
|
||||||
if (data.pathname !== lastPathname) {
|
|
||||||
lastPathname = data.pathname;
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -25,14 +52,14 @@
|
||||||
<meta property="og:url" content={`https://floter.design`+data.pathname} />
|
<meta property="og:url" content={`https://floter.design`+data.pathname} />
|
||||||
<meta property="og:image" content="https://floter.design/ogimage.png">
|
<meta property="og:image" content="https://floter.design/ogimage.png">
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<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'} />
|
<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>
|
<title>{page.data.title ? page.data.title : 'Simon Flöter, Designer and Creative Developer'}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
{#key data.pathname}
|
{#key data.pathname}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{#if $navigating}
|
{#if showLoader}
|
||||||
<Loader />
|
<Loader />
|
||||||
{/if}
|
{/if}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,6 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !mounted}
|
|
||||||
<Loader />
|
|
||||||
{/if}
|
|
||||||
<article class="scroller">
|
<article class="scroller">
|
||||||
<section class="canvasized splash">
|
<section class="canvasized splash">
|
||||||
<h1 class="align-middle">I create digital experiences that <em>stand out</em> from the rest.</h1>
|
<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',
|
transformOrigin: '0 0',
|
||||||
})
|
})
|
||||||
gsap.set('.work',{
|
gsap.set('.work',{
|
||||||
opacity: 0,
|
autoAlpha: 0,
|
||||||
yPercent: 50,
|
yPercent: 50,
|
||||||
scaleY: 0,
|
scaleY: 0,
|
||||||
})
|
})
|
||||||
gsap.to('.work',{
|
gsap.to('.work',{
|
||||||
opacity:1,
|
autoAlpha:1,
|
||||||
yPercent: 0,
|
yPercent: 0,
|
||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
duration: .75,
|
duration: .75,
|
||||||
|
|
@ -129,36 +129,50 @@
|
||||||
overwrite: true,
|
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);
|
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
|
// Move the works the same way on touch drag
|
||||||
let touchMoveHandler = (e: TouchEvent) => {
|
let touchMoveHandler = (e: TouchEvent) => {
|
||||||
|
|
@ -186,9 +200,9 @@
|
||||||
window.removeEventListener('mousemove', mouseMoveHandler);
|
window.removeEventListener('mousemove', mouseMoveHandler);
|
||||||
window.removeEventListener('touchmove', touchMoveHandler);
|
window.removeEventListener('touchmove', touchMoveHandler);
|
||||||
gsap.killTweensOf('.works, .work');
|
gsap.killTweensOf('.works, .work');
|
||||||
works.forEach((work, index) => {
|
works.forEach((work) => {
|
||||||
work.removeEventListener('mouseenter', (e) => {});
|
work.removeEventListener('mouseenter', () => handleMouseEnter(work));
|
||||||
work.removeEventListener('mouseleave', (e) => {});
|
work.removeEventListener('mouseleave', () => handleMouseLeave(work));
|
||||||
work.removeEventListener('click', (e) => {});
|
work.removeEventListener('click', (e) => {});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,9 @@
|
||||||
|
|
||||||
gsap.to('.logo-wrapper', {
|
gsap.to('.logo-wrapper', {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
scale: 0.85,
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
duration: 1,
|
duration: .5,
|
||||||
ease: 'power2.out',
|
ease: 'power2.out',
|
||||||
})
|
})
|
||||||
gsap.from('.work', {
|
gsap.from('.work', {
|
||||||
|
|
@ -163,10 +164,10 @@
|
||||||
</div>
|
</div>
|
||||||
<article class="work">
|
<article class="work">
|
||||||
{#if visible}
|
{#if visible}
|
||||||
<h1>{data.title}</h1>
|
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{data.description}
|
{data.description}
|
||||||
</div>
|
</div>
|
||||||
|
<h1>{data.title}</h1>
|
||||||
<div class="work-content">
|
<div class="work-content">
|
||||||
<div class="infobox">
|
<div class="infobox">
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
|
|
@ -245,19 +246,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin: .5em 0 0.25em 0;
|
margin: .125em 0 0.5em 0;
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
font-size: 5rem;
|
font-size: 6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.description {
|
.description {
|
||||||
margin-bottom: 1.5em;
|
margin-top: 1.5em;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
letter-spacing: -0.0075em;
|
letter-spacing: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
font-size: 2.5rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.infobox {
|
.infobox {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
perspective: 700px;
|
perspective: 700px;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
.headline {
|
.headline {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -48,9 +49,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(6, 100vw);
|
grid-template-columns: repeat(6, 100vw);
|
||||||
grid-auto-rows: 100vh;
|
grid-auto-rows: 100vh;
|
||||||
// grid-template-rows: repeat(6, 100vh);
|
|
||||||
// grid-auto-flow: column;
|
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
.work {
|
.work {
|
||||||
background-color: var(--color-highlight);
|
background-color: var(--color-highlight);
|
||||||
|
|
@ -59,7 +59,9 @@
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
transform: translateZ(700px);
|
transform: translateZ(700px);
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
:global(.work-logo) {
|
:global(.work-logo) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -70,6 +72,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue