upgrade to pixi8

This commit is contained in:
saiminh 2026-01-19 17:23:26 +13:00
parent 01b217a2a5
commit 2149372530
15 changed files with 211 additions and 1648 deletions

1226
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -38,14 +38,10 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@pixi/core": "^7.4.3",
"@pixi/filter-alpha": "^7.4.3",
"@pixi/filter-blur": "^7.4.3",
"@pixi/unsafe-eval": "^7.4.3",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"mdsvex": "^0.11.0", "mdsvex": "^0.11.0",
"pixi-filters": "^5.2.1", "pixi-filters": "^6.1.5",
"pixi.js": "^7.2.4", "pixi.js": "^8.0.0",
"superjson": "^1.13.1", "superjson": "^1.13.1",
"svelte-cloudinary": "^1.1.0" "svelte-cloudinary": "^1.1.0"
} }

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import '$lib/utils/pixiInit'; // Initialize PixiJS settings before importing filters import '$lib/utils/pixiInit'; // Initialize PixiJS settings before importing filters
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { BulgePinchFilter, TwistFilter } from 'pixi-filters'; import * as filters from 'pixi-filters';
import gsap from 'gsap'; import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { SplitText } from 'gsap/SplitText'; import { SplitText } from 'gsap/SplitText';
@ -23,6 +23,9 @@ let app: PIXI.Application;
let canvas: HTMLCanvasElement; let canvas: HTMLCanvasElement;
onMount(()=>{ onMount(()=>{
let cleanup: (() => void) | undefined;
(async ()=>{
function xFrac(x: number){ function xFrac(x: number){
return window.innerWidth * x; return window.innerWidth * x;
@ -39,13 +42,14 @@ onMount(()=>{
gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText); gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText);
app = new PIXI.Application({ app = new PIXI.Application();
await app.init({
canvas: canvas,
resizeTo: window, resizeTo: window,
antialias: true, antialias: true,
autoDensity: true, autoDensity: true,
resolution: 2, resolution: 2,
backgroundAlpha: 0, backgroundAlpha: 0,
view: canvas,
}); });
//for debugging but Typescript has an issue with this: //for debugging but Typescript has an issue with this:
@ -60,9 +64,8 @@ onMount(()=>{
app.stage.addChild(bulgegroup); app.stage.addChild(bulgegroup);
let bulgebg = new PIXI.Graphics(); let bulgebg = new PIXI.Graphics();
bulgebg.beginFill('rgb(0, 0, 0)'); bulgebg.rect(0, 0, xFrac(1), yFrac(1));
bulgebg.drawRect(0, 0, xFrac(1), yFrac(1)); bulgebg.fill('rgb(0, 0, 0)');
bulgebg.endFill();
bulgebg.alpha = 0; bulgebg.alpha = 0;
bulgebg.pivot.set(xFrac(.5), yFrac(.5)); bulgebg.pivot.set(xFrac(.5), yFrac(.5));
bulgebg.x = xFrac(0.5); bulgebg.x = xFrac(0.5);
@ -71,13 +74,13 @@ onMount(()=>{
let center = [0.5, 0.5]; let center = [0.5, 0.5];
let bulgefilter = new BulgePinchFilter(); let bulgefilter = new filters.BulgePinchFilter();
bulgefilter.radius = is_landscape ? xFrac(0.5) : xFrac(0.55); bulgefilter.radius = is_landscape ? xFrac(0.5) : xFrac(0.55);
bulgefilter.strength = 0.5; bulgefilter.strength = 0.5;
bulgefilter.center = is_landscape ? center : [0.5, 0]; bulgefilter.center = is_landscape ? center : [0.5, 0];
bulgefilter.resolution = 2; bulgefilter.resolution = 2;
let twistfilter = new TwistFilter(); let twistfilter = new filters.TwistFilter();
twistfilter.angle = 0; twistfilter.angle = 0;
twistfilter.radius = is_landscape ? window.innerWidth/4 : window.innerWidth/2; twistfilter.radius = is_landscape ? window.innerWidth/4 : window.innerWidth/2;
twistfilter.offset = new PIXI.Point(window.innerWidth/2, window.innerHeight/3); twistfilter.offset = new PIXI.Point(window.innerWidth/2, window.innerHeight/3);
@ -207,8 +210,8 @@ onMount(()=>{
* ----------------------------------*/ * ----------------------------------*/
let elapsed = 0.0; let elapsed = 0.0;
app.ticker.add((delta) => { app.ticker.add((ticker) => {
elapsed += delta; elapsed += ticker.deltaTime;
if (!is_landscape) { if (!is_landscape) {
// bulgefilter.center = [(tween.x + Math.sin(elapsed/200)/20 ),(tween.y + Math.cos(elapsed/200)/20 )]; // bulgefilter.center = [(tween.x + Math.sin(elapsed/200)/20 ),(tween.y + Math.cos(elapsed/200)/20 )];
let movingCenter = [(0.5 + Math.sin(elapsed/200)/20 ),(0.33 + Math.cos(elapsed/200)/20 )]; let movingCenter = [(0.5 + Math.sin(elapsed/200)/20 ),(0.33 + Math.cos(elapsed/200)/20 )];
@ -229,11 +232,17 @@ onMount(()=>{
window.addEventListener('resize', (e) => { window.addEventListener('resize', (e) => {
tween.y = is_fine ? 0.5 : 250/window.innerHeight; tween.y = is_fine ? 0.5 : 250/window.innerHeight;
}) })
return () => {
cleanup = () => {
gsap.killTweensOf(imgElems); gsap.killTweensOf(imgElems);
gsap.killTweensOf(elems); gsap.killTweensOf(elems);
ScrollTrigger.getAll().forEach( instance => instance.kill() ); ScrollTrigger.getAll().forEach( instance => instance.kill() );
} }
})();
return () => {
if (cleanup) cleanup();
}
}) // <- end onMount }) // <- end onMount
onDestroy(() => { onDestroy(() => {

View file

@ -23,6 +23,9 @@ let app: PIXI.Application;
let canvas: HTMLCanvasElement; let canvas: HTMLCanvasElement;
onMount(()=>{ onMount(()=>{
let cleanup: (() => void) | undefined;
(async ()=>{
function xFrac(x: number){ function xFrac(x: number){
return window.innerWidth * x; return window.innerWidth * x;
@ -38,13 +41,14 @@ onMount(()=>{
gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText); gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText);
app = new PIXI.Application({ app = new PIXI.Application();
await app.init({
canvas: canvas,
resizeTo: document.querySelector('.canvasResizeToThis') as HTMLElement, resizeTo: document.querySelector('.canvasResizeToThis') as HTMLElement,
antialias: true, antialias: true,
autoDensity: true, autoDensity: true,
resolution: 2, resolution: 2,
backgroundAlpha: 0, backgroundAlpha: 0,
view: canvas
}); });
//for debugging but Typescript has an issue with this: //for debugging but Typescript has an issue with this:
@ -59,9 +63,8 @@ onMount(()=>{
app.stage.addChild(bulgegroup); app.stage.addChild(bulgegroup);
let bulgebg = new PIXI.Graphics(); let bulgebg = new PIXI.Graphics();
bulgebg.beginFill('rgb(0, 0, 0)'); bulgebg.rect(0, 0, xFrac(1.2), yFrac(1.2));
bulgebg.drawRect(0, 0, xFrac(1.2), yFrac(1.2)); bulgebg.fill('rgb(0, 0, 0)');
bulgebg.endFill();
bulgebg.alpha = 0; bulgebg.alpha = 0;
bulgebg.pivot.set(xFrac(.5), yFrac(.5)); bulgebg.pivot.set(xFrac(.5), yFrac(.5));
bulgebg.x = xFrac(0.5); bulgebg.x = xFrac(0.5);
@ -202,8 +205,8 @@ onMount(()=>{
* ----------------------------------*/ * ----------------------------------*/
let elapsed = 0.0; let elapsed = 0.0;
app.ticker.add((delta) => { app.ticker.add((ticker) => {
elapsed += delta; elapsed += ticker.deltaTime;
if (is_portrait) { if (is_portrait) {
bulgefilter.center = [(0.5 + Math.sin(elapsed/200)/40 ),(0.45 + Math.cos(elapsed/200)/20 )]; bulgefilter.center = [(0.5 + Math.sin(elapsed/200)/40 ),(0.45 + Math.cos(elapsed/200)/20 )];
// bulgefilter.center = [0.5, 0.5]; // bulgefilter.center = [0.5, 0.5];
@ -218,11 +221,17 @@ onMount(()=>{
window.addEventListener('resize', (e) => { window.addEventListener('resize', (e) => {
tween.y = is_fine ? 0.5 : 250/window.innerHeight; tween.y = is_fine ? 0.5 : 250/window.innerHeight;
}) })
return () => {
cleanup = () => {
gsap.killTweensOf(imgElems); gsap.killTweensOf(imgElems);
gsap.killTweensOf(elems); gsap.killTweensOf(elems);
ScrollTrigger.getAll().forEach( instance => instance.kill() ); ScrollTrigger.getAll().forEach( instance => instance.kill() );
} }
})();
return () => {
if (cleanup) cleanup();
}
}) // <- end onMount }) // <- end onMount
onDestroy(() => { onDestroy(() => {

View file

@ -24,7 +24,10 @@ let {
let app: PIXI.Application; let app: PIXI.Application;
let canvas: HTMLCanvasElement; let canvas: HTMLCanvasElement;
onMount( () => { onMount(() => {
let cleanup: (() => void) | undefined;
(async () => {
let is_fine = window.matchMedia('(pointer:fine)').matches let is_fine = window.matchMedia('(pointer:fine)').matches
let is_landscape = window.matchMedia('(orientation:landscape)').matches let is_landscape = window.matchMedia('(orientation:landscape)').matches
@ -37,14 +40,15 @@ onMount( () => {
const highlightColorFromRoot = () => { return getComputedStyle(root).getPropertyValue('--color-highlight') || 'rgb(0, 0, 0)' }; const highlightColorFromRoot = () => { return getComputedStyle(root).getPropertyValue('--color-highlight') || 'rgb(0, 0, 0)' };
const thisElemBgColor = (elem: HTMLElement) => { return getComputedStyle(elem).getPropertyValue('background-color') || 'rgb(255, 255, 255)' }; const thisElemBgColor = (elem: HTMLElement) => { return getComputedStyle(elem).getPropertyValue('background-color') || 'rgb(255, 255, 255)' };
app = new PIXI.Application({ app = new PIXI.Application();
await app.init({
canvas: canvas,
resizeTo: window, resizeTo: window,
antialias: true, antialias: true,
autoDensity: true, autoDensity: true,
resolution: 2, resolution: 2,
backgroundColor: bgColorFromRoot(), backgroundColor: bgColorFromRoot(),
backgroundAlpha: 0, backgroundAlpha: 0,
view: canvas,
}); });
//for debugging but Typescript has an issue with this: //for debugging but Typescript has an issue with this:
@ -68,9 +72,8 @@ onMount( () => {
let group_background = new PIXI.Graphics(); let group_background = new PIXI.Graphics();
function draw_group_background(group_background: PIXI.Graphics) { function draw_group_background(group_background: PIXI.Graphics) {
group_background.clear(); group_background.clear();
group_background.beginFill(bgColorFromRoot()); group_background.rect(0, 0, xFrac(1), yFrac(1));
group_background.drawRect(0, 0, xFrac(1), yFrac(1)); group_background.fill(bgColorFromRoot());
group_background.endFill();
group_background.alpha = 0; group_background.alpha = 0;
group_background.pivot.set(xFrac(.5), yFrac(.5)); group_background.pivot.set(xFrac(.5), yFrac(.5));
group_background.x = xFrac(0.5); group_background.x = xFrac(0.5);
@ -162,9 +165,8 @@ onMount( () => {
workinfos.forEach( workinfo => { workinfos.forEach( workinfo => {
const workinfoRect = workinfo.getBoundingClientRect(); const workinfoRect = workinfo.getBoundingClientRect();
let workinfoGraphic = new PIXI.Graphics(); let workinfoGraphic = new PIXI.Graphics();
workinfoGraphic.beginFill(textColorFromRoot()); workinfoGraphic.rect(workinfoRect.x, workinfoRect.y, workinfoRect.width, workinfoRect.height);
workinfoGraphic.drawRect(workinfoRect.x, workinfoRect.y, workinfoRect.width, workinfoRect.height); workinfoGraphic.fill(textColorFromRoot());
workinfoGraphic.endFill();
workinfoGraphic.pivot.set(workinfoRect.x, workinfoRect.y); workinfoGraphic.pivot.set(workinfoRect.x, workinfoRect.y);
workinfoBgs.push(workinfoGraphic); workinfoBgs.push(workinfoGraphic);
app.stage.addChild(workinfoGraphic); app.stage.addChild(workinfoGraphic);
@ -178,9 +180,8 @@ onMount( () => {
workinfos.forEach((workinfo, index) => { workinfos.forEach((workinfo, index) => {
const workinfoRect = workinfo.getBoundingClientRect(); const workinfoRect = workinfo.getBoundingClientRect();
workinfoBgs[index].clear(); workinfoBgs[index].clear();
workinfoBgs[index].beginFill(thisElemBgColor(workinfo as HTMLElement)); workinfoBgs[index].rect(workinfoRect.x, workinfoRect.y, workinfoRect.width, workinfoRect.height);
workinfoBgs[index].drawRect(workinfoRect.x, workinfoRect.y, workinfoRect.width, workinfoRect.height); workinfoBgs[index].fill(thisElemBgColor(workinfo as HTMLElement));
workinfoBgs[index].endFill();
workinfoBgs[index].pivot.set(workinfoRect.x, workinfoRect.y); workinfoBgs[index].pivot.set(workinfoRect.x, workinfoRect.y);
workinfoBgs[index].position.set(workinfoRect.x - ((tween.x - 0.5) * 50), workinfoRect.y - ((tween.y - 0.5) * 50)); workinfoBgs[index].position.set(workinfoRect.x - ((tween.x - 0.5) * 50), workinfoRect.y - ((tween.y - 0.5) * 50));
workinfoBgs[index].alpha = window.getComputedStyle(workinfo).opacity as unknown as number; workinfoBgs[index].alpha = window.getComputedStyle(workinfo).opacity as unknown as number;
@ -224,8 +225,8 @@ onMount( () => {
* ----------------------------------*/ * ----------------------------------*/
let elapsed = 0.0; let elapsed = 0.0;
app.ticker.add((delta) => { app.ticker.add((ticker) => {
elapsed += delta; elapsed += ticker.deltaTime;
// bulgefilter.center = center; // bulgefilter.center = center;
// bulgefilter.center = [(center[0] + Math.sin(elapsed/200)/20 ),(center[1] + Math.cos(elapsed/200)/20 )]; // bulgefilter.center = [(center[0] + Math.sin(elapsed/200)/20 ),(center[1] + Math.cos(elapsed/200)/20 )];
bulgefilter.center = [tween.x, tween.y]; bulgefilter.center = [tween.x, tween.y];
@ -233,7 +234,19 @@ onMount( () => {
updateImgs(); updateImgs();
updateText(); updateText();
updateWorkInfoBgs(); updateWorkInfoBgs();
}) });
cleanup = () => {
gsap.killTweensOf(imgElems);
gsap.killTweensOf(elems);
gsap.killTweensOf(tween);
ScrollTrigger.getAll().forEach( instance => instance.kill() );
};
})();
return () => {
if (cleanup) cleanup();
};
}) // <- end onMount }) // <- end onMount
onDestroy(() => { onDestroy(() => {

View file

@ -1,7 +1,7 @@
:root { :root {
--spacing-outer: 5vw; --spacing-outer: 5vw;
--spacing-nav: 5vw; --spacing-nav: 5vw;
--color-bg: rgb(207, 63, 63); --color-bg: rgb(63, 111, 207);
--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;

View file

@ -14,7 +14,7 @@ export default function createCanvasText(
if (elemSrc.includes('.svg')) { if (elemSrc.includes('.svg')) {
scalefactor = Number(elem.attributes.getNamedItem('data-svgscale')?.value); scalefactor = Number(elem.attributes.getNamedItem('data-svgscale')?.value);
const canvasImgTexture = PIXI.Texture.from(elemSrc, { resourceOptions: { scale: scalefactor } }); const canvasImgTexture = PIXI.Texture.from(elemSrc, true as any);
// Use provided sprite object or create a new one // Use provided sprite object or create a new one
if (spriteObject) { if (spriteObject) {

View file

@ -26,7 +26,7 @@ export default function createCanvasText(
// Use provided text object or create a new one // Use provided text object or create a new one
const canvasText = textObject || new Text(elem.textContent as string, { const canvasText = textObject || new Text(elem.textContent as string, {
fontFamily: elemFontFamily, fontFamily: elemFontFamily,
fontSize: elemFontSize, fontSize: parseInt(elemFontSize),
fontWeight: elemFontWeight as PIXI.TextStyleFontWeight, fontWeight: elemFontWeight as PIXI.TextStyleFontWeight,
fontStyle: elemFontStyle as PIXI.TextStyleFontStyle, fontStyle: elemFontStyle as PIXI.TextStyleFontStyle,
letterSpacing: elemLetterSpacing, letterSpacing: elemLetterSpacing,

View file

@ -7,11 +7,4 @@ import * as PIXI from 'pixi.js';
// Set default resolution early to avoid deprecation warnings // Set default resolution early to avoid deprecation warnings
// from filter packages that use the deprecated settings.FILTER_RESOLUTION // from filter packages that use the deprecated settings.FILTER_RESOLUTION
PIXI.Filter.defaultResolution = 2; (PIXI.Filter as any).defaultResolution = 2;
// Also set the deprecated setting for backward compatibility with older packages
// This prevents warnings from packages like @pixi/filter-advanced-bloom that
// still check the deprecated settings.FILTER_RESOLUTION during module initialization
if (PIXI.settings && 'FILTER_RESOLUTION' in PIXI.settings) {
(PIXI.settings as any).FILTER_RESOLUTION = 2;
}

View file

@ -13,21 +13,24 @@
const textPool: PIXI.Text[] = []; const textPool: PIXI.Text[] = [];
onMount(() => { onMount(() => {
let cleanup: (() => void) | undefined;
(async () => {
let highLightColor = window.getComputedStyle(document.body).getPropertyValue('--color-highlight'); let highLightColor = window.getComputedStyle(document.body).getPropertyValue('--color-highlight');
let is_landscape = window.matchMedia('(orientation:landscape)').matches;
let is_fine = window.matchMedia('(pointer:fine)').matches let isDestroyed = false;
let is_landscape = window.matchMedia('(orientation:landscape)').matches
gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(ScrollTrigger);
let app = new PIXI.Application({ let app = new PIXI.Application();
await app.init({
canvas: canvas,
resizeTo: window, resizeTo: window,
antialias: true, antialias: true,
autoDensity: true, autoDensity: true,
resolution: 2, resolution: 2,
backgroundColor: 'rgb(255, 255, 255)', backgroundColor: 'rgb(29, 12, 18)',
backgroundAlpha: 0, backgroundAlpha: 0,
view: canvas,
}); });
let textgroup = new PIXI.Container(); let textgroup = new PIXI.Container();
@ -134,8 +137,10 @@
ease: 'power2.inOut', ease: 'power2.inOut',
overwrite: true, overwrite: true,
onComplete: () => { onComplete: () => {
introDone = true; if (!isDestroyed) {
createScrollTrigger(); introDone = true;
createScrollTrigger();
}
} }
}) })
tweens.push(textgroupIn); tweens.push(textgroupIn);
@ -166,6 +171,10 @@
let scrollTriggerTweens: Array<GSAPTween> = []; let scrollTriggerTweens: Array<GSAPTween> = [];
function createScrollTrigger() { function createScrollTrigger() {
// Check if component is destroyed before creating ScrollTriggers
if (isDestroyed) return;
const tween1 = gsap.to([textRows[0], textRows[1]], { const tween1 = gsap.to([textRows[0], textRows[1]], {
y: '+=' + -textRows[0][0].height * 0.9, y: '+=' + -textRows[0][0].height * 0.9,
duration: 1, duration: 1,
@ -193,8 +202,10 @@
scrollTriggerTweens.push(tween1, tween2); scrollTriggerTweens.push(tween1, tween2);
} }
return () => { cleanup = () => {
isDestroyed = true; // Flag to prevent further actions
cancelAnimationFrame(animationFrameId); cancelAnimationFrame(animationFrameId);
// IMPORTANT: Kill ScrollTrigger and GSAP animations FIRST, // IMPORTANT: Kill ScrollTrigger and GSAP animations FIRST,
// before destroying PixiJS objects they reference // before destroying PixiJS objects they reference
scrollTriggerTweens.forEach(tween => { scrollTriggerTweens.forEach(tween => {
@ -205,10 +216,23 @@
}); });
// Kill all other tweens // Kill all other tweens
tweens.forEach(tween => tween.kill()); tweens.forEach(tween => tween.kill());
// Kill orphanes ScrollTriggers
ScrollTrigger.getAll().forEach((trigger) => {
if (trigger.trigger === document.querySelector('article')) {
trigger.kill();
}
});
// Return objects to pools // Return objects to pools
allTexts.forEach(text => returnTextToPool(text)); allTexts.forEach(text => returnTextToPool(text));
// Now safe to destroy PixiJS app // Now safe to destroy PixiJS app
app.destroy(true, { children: true, texture: true, baseTexture: true }); app.destroy(true, { children: true, texture: true });
}
})();
return () => {
if (cleanup) cleanup();
} }
}) })
</script> </script>

View file

@ -18,7 +18,7 @@
backgroundAlpha: 0, backgroundAlpha: 0,
resizeTo: window resizeTo: window
}); });
PIXI.Filter.defaultResolution = window.devicePixelRatio; (PIXI.Filter as any).defaultResolution = window.devicePixelRatio;
const container = new PIXI.Container(); const container = new PIXI.Container();
app.stage.addChild(container); app.stage.addChild(container);

View file

@ -1,279 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { CldImage } from 'svelte-cloudinary';
export let data;
gsap.registerPlugin(ScrollTrigger);
let visible = false;
function animForDesktop() {
ScrollTrigger.getAll().forEach(t => t.kill());
gsap.to('.heromask, .coverclone', { duration: .6, x: "-10%", ease: "cubic.inOut" })
gsap.to('.work', {
xPercent: -100,
duration: .6,
ease: "expo.out",
delay: .2
})
let heroheight = document.querySelector('.heromask')?.getBoundingClientRect().height || 100;
gsap.to('.heromask', {
clipPath: "polygon(0 0, 60% 0, 35% 100%, 0% 100%)",
duration: 1,
ease: "power4.out",
onStart: () => {
setTimeout(() => {
document.querySelector('.coverclone')?.remove();
}, 100);
},
onComplete: () => {
gsap.to('.heromask', {
ease: "none",
clipPath: "polygon(0 0, 50% 0, 50% 100%, 0% 100%)",
scrollTrigger: {
trigger: '.work',
start: 'top top',
end: `200px top`,
scrub: true
}
})
}
})
}
function animForMobile() {
ScrollTrigger.getAll().forEach(t => t.kill());
gsap.to('.heromask, .coverclone', { duration: .6, y: -20, ease: "cubic.inOut" })
gsap.to('.work', {
opacity: 1,
yPercent: -100,
duration: .4,
ease: "expo.out",
delay: .2,
})
gsap.to('.heromask', {
clipPath: "polygon(0 0, 100% 0, 100% 75%, 0% 100%)",
duration: .6,
ease: "cubic.inOut",
onStart: () => {
setTimeout(() => {
document.querySelector('.coverclone')?.remove();
}, 100);
},
onComplete: () => {
gsap.to('.heromask', {
ease: "power1.out",
scrollTrigger: {
trigger: '.work',
markers: false,
start: '0px -10px',
end: `0px -20px`,
scrub: false,
onEnterBack: () => {
gsap.to('.heromask', {
clipPath: "polygon(0 0, 100% 0, 100% 75%, 0% 100%)",
duration: .6,
ease: "expo.out",
})
},
onEnter: () => {
gsap.to('.heromask', {
clipPath: "polygon(0 0, 100% 0, 100% 0%, 0% 0%)",
duration: .6,
ease: "expo.out",
})
}
}
})
}
})
}
function animForSize(){
if ( window.matchMedia("(min-width: 768px) and (orientation: landscape)").matches ) {
animForDesktop();
} else {
animForMobile();
}
}
onMount(() => {
visible = true;
document.querySelector('.heromask img')?.addEventListener('load', () => {
animForSize();
let portrait = window.matchMedia("(orientation: portrait)");
portrait.addEventListener("change", function(e) {
animForSize();
})
})
})
</script>
<div class="heromask">
<CldImage
src={data.header_bg_image}
alt="{data.title}"
sizes="100vw"
width={2100}
height={1400}
placeholder="blur"
loading="eager"
objectFit="fill"
/>
</div>
<div class="subnav">
<a href="/work" class="subnav-item">← Back</a>
</div>
<article class="work">
{#if visible}
<div class="work-content">
{#if data.tags != undefined && data.tags.length > 0 }
<div class="tags">
{#each data.tags as tag }
<div class="tag">{tag}</div>
{ /each }
</div>
{/if}
<h1><span class="svg-logo">{@html data.svg}</span><span class="name">{data.title}</span></h1>
<div class="work-content-text">
{@html data.Content.html}
</div>
</div>
{/if}
</article>
<style lang="scss">
.work {
width: 100vw;
min-height: 100svh;
overflow: hidden;
box-sizing: border-box;
transform: translateY(100%);
@media screen and (min-width: 768px) {
transform: translateX(100%);
}
}
.subnav {
position: fixed;
top: 0;
right: 0;
z-index: 4;
padding: var(--spacing-outer);
& a {
text-decoration: none;
color: var(--color-highlight);
}
}
.heromask {
position: fixed;
top: 0;
left:0;
aspect-ratio: var(--aspect-ratio-heroes);
width: 100%;
height: auto;
z-index: 2;
clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
}
:global(.heromask img) {
z-index: 0;
display: block;
position: relative;
width: 100%;
height: 100%;
aspect-ratio: var(--aspect-ratio-heroes);
margin: 0;
object-fit: fill;
}
.work-content {
padding: 0 var(--spacing-outer);
padding-top: calc(66.6vw + 1em);
position: relative;
z-index: 1;
color: var(--color-text);
& > :last-child {
margin-bottom: 100px;
}
@media screen and (min-width: 768px) {
margin-left: 40vw;
max-width: 60vw;
padding-top: calc( 3 * var(--spacing-outer) );
padding-left: calc(var(--spacing-outer) * 1.5);
padding-right: calc(var(--spacing-outer) * 2.5);
}
}
h1 {
position: relative;
z-index: 1;
margin: 0;
@media screen and (min-width: 768px) {
padding: 0 0 1em 0;
}
& .name {
display: none;
}
& .svg-logo :global(svg) {
width: auto;
height: auto;
max-width: 250px;
max-height: 80px;
margin-bottom: 1em;
@media screen and (min-width: 768px) {
max-width: 400px;
max-height: 200px;
}
}
}
.tags {
padding-bottom: .25em;
font-size: 1em;
margin-bottom: 2em;
line-height: 1.1;
&:after {
content: '';
display: block;
width: calc(100% + var(--spacing-outer) * 2.5);
height: 1px;
background-color: var(--color-text);
margin-top: .5em;
}
}
.tag {
display: inline-block;
margin-right: .5em;
padding: .125em 0;
font-weight: 400;
text-transform: uppercase;
letter-spacing: -.005em;
&:after {
content: ','
}
&:last-child:after {
content: none
}
}
:global(.header-nav){
transition: all .3s cubic-bezier(0.075, 0.82, 0.165, 1);
}
:global(.work .header-nav){
transform: translateY(100%);
}
</style>

View file

@ -1,71 +0,0 @@
<script lang="ts">
import WorkCanvas from '$lib/components/WorkCanvas.svelte';
import { onMount } from 'svelte';
import { workClickHandler, initWorkPage } from './workUtils.js';
import { workbulge } from '$lib/utils/stores.js';
import { CldImage } from 'svelte-cloudinary';
export let data;
const orderedPosts = data.posts.sort((a, b) => {
return a.meta.order - b.meta.order;
});
let canvasTextElems: Array<HTMLElement>;
let canvasImgElems: Array<HTMLElement>;
let bulge = {factor: 0};
workbulge.subscribe((val) => {
bulge.factor = val;
});
onMount(() => {
const headline: HTMLElement = document.querySelector('.headline') as HTMLElement;
const headlines: Array<HTMLElement> = Array.from(document.querySelectorAll('h2, li'));
const images: Array<HTMLElement> = Array.from(document.querySelectorAll('.work img'));
let canvasElems = initWorkPage( headlines, images );
canvasTextElems = canvasElems.text as Array<HTMLElement>;
canvasImgElems = canvasElems.images;
});
</script>
<div class="works-wrapper">
<h1 class="headline"><span>Outstanding work</span></h1>
<div class="works">
{#each orderedPosts as work, i}
<a
data-sveltekit-preload-data
href="{work.path}"
class="work"
on:click={ (e) => workClickHandler(e) }
>
<CldImage
src={work.meta.header_bg_image}
sizes={ i === 0 ? `(min-width: 768px) 60vw, 50vw` : `(min-width: 768px) 20vw, 50vw`}
alt={work.meta.title}
width="2100"
height="1400"
objectFit="fill"
loading= "lazy"
/>
<div class="work-info">
<h2 class="title"><span class="title-words">{work.meta.title}</span></h2>
{#if work.meta.tags}
<ul class="tags">
{#each work.meta.tags as tag, index}
<li class="tag">{tag}{index < work.meta.tags.length-1 ? ' | ' : ''}</li>
{/each}
</ul>
{/if}
</div>
</a>
{/each}
</div>
<WorkCanvas
textsToCanvas={canvasTextElems}
imgsToCanvas={canvasImgElems}
bulgeFactor={bulge.factor}
/>
</div>
<style src="./work.scss" lang="scss"></style>

View file

@ -1,113 +0,0 @@
.works-wrapper {
max-width: 1200px;
margin: auto;
}
h1 {
font-size: 16vw;
margin: var(--spacing-nav) var(--spacing-nav) .25em var(--spacing-nav);
padding: 0 0 calc(var(--spacing-nav) / 3) 0;
position: relative;
z-index: -1;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
border-bottom: 1px solid;
@media screen and (min-width: 768px) {
font-size: 7vw;
}
}
.works {
padding: 0.5em;
display: flex;
gap: 0.25em;
flex-wrap: wrap;
@media screen and (min-width: 768px) {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
padding: 0 var(--spacing-nav);
margin: 0 auto;
padding-bottom: 20svh;
}
}
.work-info,
.work:visited .work-info {
position: absolute;
left: 0.25em;
top: 0.25em;
padding: 0.5em .75em;
overflow: hidden;
box-sizing: border-box;
background-color: var(--color-text);
visibility: hidden;
& h2 {
text-transform: none;
font-style: normal;
font-weight: 400;
margin: 0;
padding: 0;
font-size: 1.2em;
letter-spacing: -0.02em;
line-height: 1;
visibility: hidden;
color: var(--color-bg);
}
& .tags {
visibility: hidden;
list-style: none;
padding: 0;
margin: 10px 0 0 0 ;
display: flex;
flex-wrap: wrap;
gap: 0.15em;
transition: all .4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
& .tag {
font-size: .6em;
line-height: 1;
letter-spacing: -0.01em;
box-sizing: border-box;
padding: 0;
border-radius: 3px;
text-transform: uppercase;
// font-weight: 800;
// font-style: italic;
color: var(--color-text);
}
}
.work:hover .work-info {
background-color: var(--color-bg);
& h2 {
color: var(--color-text);
}
}
.work {
flex: 0 0 calc(50% - 0.125em );
display: block;
position: relative;
padding: 0;
// transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
text-decoration: none;
overflow: hidden;
@media screen and (min-width: 768px) {
grid-column-end: span 1;
&:first-child {
grid-column-end: span 2;
grid-row-end: span 2;
}
}
}
.work .tag {
opacity: 0;
}
:global(.work img) {
width: 100%;
height: auto;
aspect-ratio: var(--aspect-ratio-heroes);
object-fit: fill;
visibility: hidden;
display: block;
}

View file

@ -61,7 +61,7 @@
opacity: 0; opacity: 0;
transform: translateZ(700px); transform: translateZ(700px);
} }
.work-logo { :global(.work-logo) {
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--color-bg); color: var(--color-bg);