add first draft for work examples
This commit is contained in:
parent
46d559784a
commit
5faecf4f25
18 changed files with 609 additions and 106 deletions
|
|
@ -7,37 +7,28 @@ import SplitText from 'gsap/dist/SplitText';
|
|||
import { PixiPlugin } from "gsap/dist/PixiPlugin";
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import createCanvasText from '$lib/utils/creareCanvasText';
|
||||
import createCanvasText from '$lib/utils/createCanvasText';
|
||||
import createCanvasImg from '$lib/utils/createCanvasImg';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
export let textsToCanvas: NodeListOf<Element>;
|
||||
export let textsToCanvas: Array<HTMLElement> = [];
|
||||
export let imgsToCanvas: Array<HTMLElement> = [];
|
||||
let app: PIXI.Application;
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
onMount(()=>{
|
||||
|
||||
gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText);
|
||||
|
||||
if (document.querySelector('.homeCanvas')) {
|
||||
app = new PIXI.Application({
|
||||
resizeTo: window,
|
||||
antialias: true,
|
||||
autoDensity: true,
|
||||
resolution: 2,
|
||||
backgroundAlpha: 0,
|
||||
view: document.querySelector('.homeCanvas') as HTMLCanvasElement,
|
||||
view: canvas,
|
||||
});
|
||||
}
|
||||
else {
|
||||
app = new PIXI.Application({
|
||||
resizeTo: window,
|
||||
antialias: true,
|
||||
autoDensity: true,
|
||||
resolution: 2,
|
||||
backgroundAlpha: 0,
|
||||
});
|
||||
let canvaselem = document.body.appendChild(app.view as HTMLCanvasElement);
|
||||
canvaselem.classList.add('homeCanvas');
|
||||
}
|
||||
|
||||
//for debugging but Typescript has an issue with this:
|
||||
// globalThis.__PIXI_APP__ = app as any;
|
||||
|
||||
|
|
@ -86,12 +77,11 @@ onMount(()=>{
|
|||
async function convertText(){
|
||||
await tick();
|
||||
textsToCanvas.forEach((element) => {
|
||||
elems.push(element as HTMLElement);
|
||||
let canvasText = createCanvasText(element as HTMLElement, app.stage);
|
||||
canvasTexts.push(canvasText as PIXI.Text);
|
||||
elems.push(element);
|
||||
let canvasText = createCanvasText(element, app.stage);
|
||||
canvasTexts.push(canvasText);
|
||||
})
|
||||
}
|
||||
convertText();
|
||||
|
||||
|
||||
/*----------------------------------
|
||||
|
|
@ -106,11 +96,45 @@ onMount(()=>{
|
|||
})
|
||||
}
|
||||
|
||||
/*----------------------------------
|
||||
* Convert images to canvas
|
||||
* createCanvacImgs function
|
||||
----------------------------------*/
|
||||
let canvasImgs: Array<PIXI.Sprite> = [];
|
||||
let imgElems: Array<HTMLElement> = [];
|
||||
|
||||
function convertImgs(){
|
||||
imgsToCanvas.forEach((element) => {
|
||||
imgElems.push(element);
|
||||
let canvasImg = createCanvasImg(element, app.stage);
|
||||
canvasImgs.push(canvasImg);
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
convertImgs();
|
||||
convertText();
|
||||
}, 100);
|
||||
// convertImgs();
|
||||
// convertText();
|
||||
|
||||
/*----------------------------------
|
||||
* Function to update text on canvas
|
||||
* runs in the Ticker
|
||||
----------------------------------*/
|
||||
function updateImgs(){
|
||||
canvasImgs.forEach((image, index) => {
|
||||
let imagePosition = imgElems[index].getBoundingClientRect();
|
||||
image.position.set(imagePosition.x, imagePosition.y);
|
||||
image.width = imagePosition.width;
|
||||
image.height = imagePosition.height;
|
||||
})
|
||||
}
|
||||
|
||||
/*----------------------------------
|
||||
* Mousemove events
|
||||
*----------------------------------*/
|
||||
window.addEventListener('pointermove', (e) => {
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
const pointerX = e.clientX / window.innerWidth;
|
||||
const pointerY = e.clientY / window.innerHeight;
|
||||
const pointerXfrac = pointerX - 0.5;
|
||||
|
|
@ -128,9 +152,9 @@ onMount(()=>{
|
|||
app.ticker.add((delta) => {
|
||||
elapsed += delta;
|
||||
bulgefilter.center = [(center[0] + Math.sin(elapsed/200)/20 ),(center[1] + Math.cos(elapsed/200)/20 )];
|
||||
updateImgs();
|
||||
updateText();
|
||||
})
|
||||
console.log('HomeCanvas mounted', textsToCanvas);
|
||||
}) // <- end onMount
|
||||
|
||||
onDestroy(() => {
|
||||
|
|
@ -140,3 +164,14 @@ onDestroy(() => {
|
|||
}) // <- end onDestroy
|
||||
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
205
src/lib/components/WorkCanvas.svelte
Normal file
205
src/lib/components/WorkCanvas.svelte
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<script lang="ts">
|
||||
import * as PIXI from 'pixi.js';
|
||||
import { RGBSplitFilter, BulgePinchFilter } from 'pixi-filters';
|
||||
import gsap from 'gsap';
|
||||
import ScrollTrigger from 'gsap/dist/ScrollTrigger';
|
||||
import SplitText from 'gsap/dist/SplitText';
|
||||
import { PixiPlugin } from "gsap/dist/PixiPlugin";
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import createCanvasText from '$lib/utils/createCanvasText';
|
||||
import createCanvasImg from '$lib/utils/createCanvasImg';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
export let textsToCanvas: Array<HTMLElement> = [];
|
||||
export let imgsToCanvas: Array<HTMLElement> = [];
|
||||
export let bulgeFactor: number = 0.15;
|
||||
let app: PIXI.Application;
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
onMount(()=>{
|
||||
|
||||
gsap.registerPlugin(PixiPlugin, ScrollTrigger, SplitText);
|
||||
|
||||
app = new PIXI.Application({
|
||||
resizeTo: window,
|
||||
antialias: true,
|
||||
autoDensity: true,
|
||||
resolution: 2,
|
||||
backgroundAlpha: 0,
|
||||
view: canvas,
|
||||
});
|
||||
|
||||
|
||||
//for debugging but Typescript has an issue with this:
|
||||
// globalThis.__PIXI_APP__ = app as any;
|
||||
|
||||
PixiPlugin.registerPIXI(PIXI);
|
||||
|
||||
function xFrac(x: number){
|
||||
return window.innerWidth * x;
|
||||
}
|
||||
function yFrac(y: number){
|
||||
return window.innerHeight * y;
|
||||
}
|
||||
|
||||
let group = new PIXI.Container();
|
||||
group.pivot.set(window.innerWidth / 2, window.innerHeight / 2);
|
||||
group.x = window.innerWidth / 2;
|
||||
group.y = window.innerHeight / 2;
|
||||
app.stage.addChild(group);
|
||||
|
||||
let recty = new PIXI.Graphics();
|
||||
recty.beginFill('rgb(0, 0, 0)');
|
||||
recty.drawRect(0, 0, xFrac(1), yFrac(1));
|
||||
recty.endFill();
|
||||
recty.alpha = 0;
|
||||
recty.pivot.set(xFrac(.5), yFrac(.5));
|
||||
recty.x = xFrac(0.5);
|
||||
recty.y = yFrac(0.5);
|
||||
group.addChild(recty);
|
||||
|
||||
let center = [0.5, 0.5];
|
||||
let bulgefilter = new BulgePinchFilter();
|
||||
bulgefilter.radius = xFrac(0.6);
|
||||
bulgefilter.strength = bulgeFactor;
|
||||
bulgefilter.center = center;
|
||||
bulgefilter.resolution = 2;
|
||||
// app.stage.filters = [bulgefilter];
|
||||
let rgbFilter = new RGBSplitFilter();
|
||||
rgbFilter.red = [0, 0];
|
||||
rgbFilter.green = [0, 0];
|
||||
rgbFilter.blue = [0, 0];
|
||||
rgbFilter.resolution = 2;
|
||||
app.stage.filters = [bulgefilter];
|
||||
|
||||
|
||||
/*----------------------------------
|
||||
* Convert text to canvas using
|
||||
* createCanvasText function
|
||||
----------------------------------*/
|
||||
let canvasTexts: Array<PIXI.Text> = [];
|
||||
let elems: Array<HTMLElement> = [];
|
||||
|
||||
async function convertText(){
|
||||
await tick();
|
||||
textsToCanvas.forEach((element) => {
|
||||
elems.push(element);
|
||||
let canvasText = createCanvasText(element, app.stage);
|
||||
canvasTexts.push(canvasText);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------
|
||||
* Function to update text on canvas
|
||||
* runs in the Ticker
|
||||
----------------------------------*/
|
||||
function updateText(){
|
||||
canvasTexts.forEach((text, index) => {
|
||||
let headlinePosition = elems[index].getBoundingClientRect();
|
||||
text.position.set(headlinePosition.x, headlinePosition.y);
|
||||
text.alpha = elems[index].style.opacity as unknown as number;
|
||||
})
|
||||
}
|
||||
|
||||
/*----------------------------------
|
||||
* Convert images to canvas
|
||||
* createCanvacImgs function
|
||||
----------------------------------*/
|
||||
let canvasImgs: Array<PIXI.Sprite> = [];
|
||||
let imgElems: Array<HTMLElement> = [];
|
||||
|
||||
function convertImgs(){
|
||||
imgsToCanvas.forEach((element) => {
|
||||
imgElems.push(element);
|
||||
let canvasImg = createCanvasImg(element, app.stage);
|
||||
// canvasImg.tint = 0xff9494;
|
||||
canvasImgs.push(canvasImg);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------
|
||||
* Function to update text on canvas
|
||||
* runs in the Ticker
|
||||
----------------------------------*/
|
||||
function updateImgs(){
|
||||
canvasImgs.forEach((image, index) => {
|
||||
let imagePosition = imgElems[index].getBoundingClientRect();
|
||||
image.position.set(imagePosition.x, imagePosition.y);
|
||||
image.width = imagePosition.width;
|
||||
image.height = imagePosition.height;
|
||||
// image.alpha = imgElems[index].style.opacity as unknown as number;
|
||||
image.alpha = window.getComputedStyle(imgElems[index]).opacity as unknown as number;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
convertImgs();
|
||||
convertText();
|
||||
}, 100);
|
||||
// convertImgs();
|
||||
// convertText();
|
||||
|
||||
/*----------------------------------
|
||||
* Mousemove events
|
||||
*----------------------------------*/
|
||||
let tween = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
const pointerX = e.clientX / window.innerWidth;
|
||||
const pointerY = e.clientY / window.innerHeight;
|
||||
const pointerXfrac = pointerX - 0.5;
|
||||
const pointerYfrac = pointerY - 0.5;
|
||||
rgbFilter.red = [pointerXfrac * 10, pointerYfrac * 10];
|
||||
rgbFilter.green = [pointerXfrac * -10, pointerYfrac * -10];
|
||||
|
||||
gsap.to(tween, {
|
||||
duration: .5,
|
||||
ease: 'power3.out',
|
||||
overwrite: true,
|
||||
x: pointerX,
|
||||
y: pointerY,
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
/*----------------------------------
|
||||
* The Ticker
|
||||
* ----------------------------------*/
|
||||
let elapsed = 0.0;
|
||||
|
||||
app.ticker.add((delta) => {
|
||||
elapsed += delta;
|
||||
// bulgefilter.center = center;
|
||||
// bulgefilter.center = [(center[0] + Math.sin(elapsed/200)/20 ),(center[1] + Math.cos(elapsed/200)/20 )];
|
||||
bulgefilter.center = [tween.x, tween.y];
|
||||
bulgefilter.strength = bulgeFactor;
|
||||
updateImgs();
|
||||
updateText();
|
||||
})
|
||||
}) // <- end onMount
|
||||
|
||||
onDestroy(() => {
|
||||
if (browser){
|
||||
app.destroy(true, true);
|
||||
}
|
||||
}) // <- end onDestroy
|
||||
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,6 +4,7 @@ type Post = {
|
|||
date: string
|
||||
description: string
|
||||
tags: string[]
|
||||
header_bg_image: string
|
||||
}
|
||||
default: {
|
||||
render: () => string
|
||||
|
|
|
|||
|
|
@ -15,12 +15,6 @@ body {
|
|||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-size: 1em;
|
||||
font-weight: 900;
|
||||
|
|
|
|||
19
src/lib/utils/createCanvasImg.ts
Normal file
19
src/lib/utils/createCanvasImg.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import * as PIXI from 'pixi.js';
|
||||
|
||||
export default function createCanvasText( element: HTMLElement, stage: PIXI.Container ){
|
||||
|
||||
const elem = element;
|
||||
// console.log(elem);
|
||||
const elemSrc = elem.getAttribute('src') || '';
|
||||
const elemPosition = elem.getBoundingClientRect();
|
||||
|
||||
const canvasImg = PIXI.Sprite.from(elemSrc);
|
||||
canvasImg.position.set(elemPosition.x, elemPosition.y);
|
||||
canvasImg.width = elemPosition.width;
|
||||
canvasImg.height = elemPosition.height;
|
||||
stage.addChild(canvasImg);
|
||||
|
||||
// elem.style.opacity = '0';
|
||||
elem.style.visibility = 'hidden';
|
||||
return canvasImg;
|
||||
}
|
||||
|
|
@ -6,7 +6,9 @@ export default function createCanvasText( element: HTMLElement, stage: PIXI.Con
|
|||
const elem = element;
|
||||
const elemStyles = window.getComputedStyle(elem);
|
||||
const elemFontSize = elemStyles.getPropertyValue('font-size');
|
||||
const elemFontWeight = elemStyles.getPropertyValue('font-weight');
|
||||
const elemFontFamily = elemStyles.getPropertyValue('font-family');
|
||||
const elemFontStyle = elemStyles.getPropertyValue('font-style');
|
||||
const elemLetterSpacing = parseInt(elemStyles.getPropertyValue('letter-spacing'));
|
||||
const elemColor = elemStyles.getPropertyValue('color');
|
||||
const elemAlignment = elemStyles.getPropertyValue('text-align');
|
||||
|
|
@ -15,11 +17,14 @@ export default function createCanvasText( element: HTMLElement, stage: PIXI.Con
|
|||
const canvasText = new Text(elem?.textContent as string, {
|
||||
fontFamily: elemFontFamily,
|
||||
fontSize: elemFontSize,
|
||||
fontWeight: elemFontWeight as PIXI.TextStyleFontWeight,
|
||||
fontStyle: elemFontStyle as PIXI.TextStyleFontStyle,
|
||||
letterSpacing: elemLetterSpacing,
|
||||
fill: elemColor,
|
||||
align: elemAlignment as PIXI.TextStyleAlign,
|
||||
});
|
||||
canvasText.position.set(elemPosition.x, elemPosition.y);
|
||||
// canvasText.zIndex = 100;
|
||||
stage.addChild(canvasText);
|
||||
|
||||
elem.style.opacity = '0';
|
||||
|
|
@ -6,30 +6,33 @@
|
|||
|
||||
onMount(() => {
|
||||
let navlinks = document.querySelectorAll('nav a');
|
||||
|
||||
navlinks.forEach((link, index) => {
|
||||
link.addEventListener('click', (e)=> {
|
||||
const checkbox = document.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
checkbox ? checkbox.checked = false : null;
|
||||
gsap.to('.content', {duration: 0.5, y: '0%', autoAlpha: 1, ease: 'power4.out'});
|
||||
gsap.to('.content > *', {duration: 0.5, y: '0%', autoAlpha: 1, ease: 'power4.out'});
|
||||
})
|
||||
});
|
||||
|
||||
let checkbox = document.getElementById('menustate') as HTMLInputElement;
|
||||
|
||||
checkbox.addEventListener('change', (e)=> {
|
||||
if (checkbox.checked) {
|
||||
gsap.to('.content', {duration: 0.75, y: -200, autoAlpha: 0.5, ease: 'power2.inOut'});
|
||||
gsap.to('.content > *', {duration: 0.75, y: -200, autoAlpha: 0.5, ease: 'power2.inOut'});
|
||||
gsap.from('#nav', {duration: 0.5, autoAlpha: 0, y: '100%', ease: 'power4.out', delay: 0.1});
|
||||
gsap.from('nav a', {duration: 0.5, autoAlpha: 0, y: 20, stagger: 0.1, ease: 'power4.out' , delay: 0.2});
|
||||
} else {
|
||||
gsap.to('.content', {duration: 0.5, y: '0%', autoAlpha: 1, ease: 'power4.out'});
|
||||
gsap.to('.content > *', {duration: 0.5, y: '0%', autoAlpha: 1, ease: 'power4.out'});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
</script>
|
||||
<a href='/' class="logo">
|
||||
<a href='/' class="logo">
|
||||
<Logo />
|
||||
</a>
|
||||
<header>
|
||||
</a>
|
||||
<header>
|
||||
<input aria-hidden="true" type="checkbox" id="menustate" />
|
||||
<label for="menustate" aria-hidden="true">
|
||||
<span class="open">≡</span>
|
||||
|
|
@ -41,7 +44,7 @@
|
|||
<a href="/work">About</a>
|
||||
<a href="/work">Hire</a>
|
||||
</nav>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<slot />
|
||||
|
|
@ -52,13 +55,24 @@
|
|||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
padding: var(--spacing-outer);
|
||||
display: flex;
|
||||
gap: .75em;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
// width: 100%;
|
||||
|
||||
// &:before {
|
||||
// content: '';
|
||||
// position: absolute;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background: linear-gradient(0deg, #00117fFF 0%, #00117fFF 30%, #00117f00 100%);
|
||||
// }
|
||||
}
|
||||
label {
|
||||
height: 36px;
|
||||
|
|
@ -95,7 +109,7 @@
|
|||
width: auto;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
margin: var(--spacing-outer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
import SplitText from 'gsap/dist/SplitText';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let canvasElems: NodeListOf<Element>;
|
||||
let canvasElems: Array<HTMLElement>;
|
||||
onMount(() => {
|
||||
console.log('Home mounted');
|
||||
|
||||
gsap.registerPlugin( ScrollTrigger, SplitText );
|
||||
|
||||
|
|
@ -51,7 +50,7 @@
|
|||
})
|
||||
}
|
||||
})
|
||||
canvasElems = document.querySelectorAll('.lineChildren') as NodeListOf<Element>;
|
||||
canvasElems = Array.from(document.querySelectorAll('.lineChildren'));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -98,13 +97,7 @@
|
|||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{#await onMount}
|
||||
waiting
|
||||
{:then}
|
||||
<HomeCanvas textsToCanvas={canvasElems}/>
|
||||
{:catch error}
|
||||
<p>error</p>
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
article {
|
||||
|
|
@ -142,11 +135,11 @@
|
|||
h1 {
|
||||
letter-spacing: -0.05em;
|
||||
line-height: .9;
|
||||
font-size: 2.5em;
|
||||
font-size: 17vw;
|
||||
margin-top: -.5em;
|
||||
text-align: center;
|
||||
@media screen and (min-width: 768px) {
|
||||
font-size: 2.7em;
|
||||
font-size: 12vw;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
|
|
|
|||
|
|
@ -1,59 +1,222 @@
|
|||
<script lang="ts">
|
||||
import HomeCanvas from '$lib/components/HomeCanvas.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import WorkCanvas from '$lib/components/WorkCanvas.svelte';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import gsap from 'gsap';
|
||||
import ScrollTrigger from 'gsap/dist/ScrollTrigger';
|
||||
import SplitText from 'gsap/dist/SplitText';
|
||||
|
||||
export let data;
|
||||
|
||||
let canvasElems: NodeListOf<Element>;
|
||||
onMount(() => {
|
||||
canvasElems = document.querySelectorAll('h1') as NodeListOf<Element>;
|
||||
// console.log('Work mounted', canvasElems);
|
||||
let canvasTextElems: Array<HTMLElement>;
|
||||
let canvasImgElems: Array<HTMLElement>;
|
||||
let bulge = { factor: 0.15 };
|
||||
|
||||
gsap.set(canvasElems, { autoAlpha: 0 });
|
||||
gsap.to(canvasElems, {
|
||||
onMount(() => {
|
||||
|
||||
gsap.registerPlugin( ScrollTrigger, SplitText );
|
||||
|
||||
let h1 = document.querySelector('h1') as HTMLElement;
|
||||
let h1Text = h1?.innerHTML || '';
|
||||
h1.innerHTML = h1Text + h1Text + h1Text + h1Text || '';
|
||||
h1.style.overflow = 'hidden';
|
||||
h1.style.whiteSpace = 'nowrap';
|
||||
|
||||
h1.querySelectorAll('span').forEach((span) => {
|
||||
gsap.to(span, {xPercent: -100, duration: 4, ease: 'none', repeat: -1 })
|
||||
});
|
||||
|
||||
const split = new SplitText('h1', { type: 'words', wordsClass: 'words' });
|
||||
gsap.set(split.words, { opacity: 0 });
|
||||
|
||||
canvasTextElems = Array.from(document.querySelectorAll('.words'));
|
||||
gsap.set(canvasTextElems, {
|
||||
opacity: 0, yPercent: -10
|
||||
})
|
||||
gsap.to(canvasTextElems, {
|
||||
duration: 1,
|
||||
opacity: 1,
|
||||
yPercent: 0,
|
||||
stagger: 0.1,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
|
||||
canvasImgElems = Array.from(document.querySelectorAll('.workhero'));
|
||||
gsap.set(canvasImgElems, {
|
||||
opacity: 0, yPercent: -10
|
||||
})
|
||||
gsap.to(canvasImgElems, {
|
||||
duration: 1,
|
||||
opacity: 1,
|
||||
yPercent: 0,
|
||||
stagger: 0.1,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
function workClickHandler(e:Event){
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLElement;
|
||||
const originalLink = target.getAttribute('href') as string;
|
||||
const targetImg = target.querySelector('.workhero') as HTMLElement;
|
||||
const targetImgRect = targetImg.getBoundingClientRect();
|
||||
const targetImgAspectRatio = targetImgRect.width / targetImgRect.height;
|
||||
target.classList.add('active');
|
||||
targetImg.style.width = `${targetImgRect.width}px`;
|
||||
targetImg.style.height = `${targetImgRect.height}px`;
|
||||
targetImg.style.top = `${targetImgRect.top}px`;
|
||||
targetImg.style.left = `${targetImgRect.left}px`;
|
||||
targetImg.style.position = 'fixed';
|
||||
|
||||
gsap.to('.work:not(.active) .workhero', {
|
||||
duration: .3,
|
||||
opacity: 0,
|
||||
yPercent: 10,
|
||||
stagger: 0.1,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
gsap.set(targetImg, {
|
||||
zIndex: 100,
|
||||
})
|
||||
gsap.to(targetImg, {
|
||||
duration: .6,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: window.innerWidth,
|
||||
height: window.innerWidth / targetImgAspectRatio,
|
||||
ease: 'circ.inOut',
|
||||
delay: .3,
|
||||
})
|
||||
gsap.to(bulge, {
|
||||
duration: .3,
|
||||
factor: 0,
|
||||
ease: 'circ.inOut',
|
||||
onUpdate: () => {
|
||||
bulge.factor = bulge.factor;
|
||||
}
|
||||
})
|
||||
gsap.to('h1', {
|
||||
duration: .3,
|
||||
yPercent: -200,
|
||||
ease: 'circ.inOut',
|
||||
})
|
||||
|
||||
// after a timeout of 1000ms (1s), navigate to the original link
|
||||
setTimeout(() => {
|
||||
window.location.href = originalLink;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="work">
|
||||
<h1>My work examples:</h1>
|
||||
<h1><span>Casestudies</span></h1>
|
||||
<div class="works">
|
||||
|
||||
{#each data.posts as work}
|
||||
<a href="{work.path}" class="work" on:click={ workClickHandler }>
|
||||
<img class="workhero" src="{work.meta.header_bg_image}" alt="{work.meta.title}"/>
|
||||
<h2>{work.meta.title}</h2>
|
||||
<a href="{work.path}">Link to work</a>
|
||||
</a>
|
||||
|
||||
{/each}
|
||||
{#each data.posts as work}
|
||||
<a href="{work.path}" class="work" on:click={ workClickHandler }>
|
||||
<img class="workhero" src="{work.meta.header_bg_image}" alt="{work.meta.title}"/>
|
||||
<h2>{work.meta.title}</h2>
|
||||
</a>
|
||||
|
||||
{/each}
|
||||
{#each data.posts as work}
|
||||
<a href="{work.path}" class="work" on:click={ workClickHandler }>
|
||||
<img class="workhero" src="{work.meta.header_bg_image}" alt="{work.meta.title}"/>
|
||||
<h2>{work.meta.title}</h2>
|
||||
</a>
|
||||
|
||||
{/each}
|
||||
{#each data.posts as work}
|
||||
<a href="{work.path}" class="work" on:click={ workClickHandler }>
|
||||
<img class="workhero" src="{work.meta.header_bg_image}" alt="{work.meta.title}"/>
|
||||
<h2>{work.meta.title}</h2>
|
||||
</a>
|
||||
|
||||
{/each}
|
||||
</div>
|
||||
{#await onMount}
|
||||
waiting
|
||||
{:then}
|
||||
<HomeCanvas textsToCanvas={canvasElems}/>
|
||||
{:catch error}
|
||||
<p>error</p>
|
||||
{/await}
|
||||
<WorkCanvas textsToCanvas={canvasTextElems} imgsToCanvas={canvasImgElems} bulgeFactor={bulge.factor} />
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
h1, h2 {
|
||||
h1 {
|
||||
font-size: 12vw;
|
||||
font-style: italic;
|
||||
margin: 0.5em 0 0.5em 0;
|
||||
letter-spacing: -0.04em;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
visibility: hidden;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
font-size: 7vw;
|
||||
}
|
||||
|
||||
& span {
|
||||
display: inline-block;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.025em;
|
||||
font-weight: 400;
|
||||
font-size: 2vw;
|
||||
// visibility: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
right: 0%;
|
||||
top: 0%;
|
||||
transform: scale(0.8);
|
||||
margin: 0;
|
||||
padding: 0.1em 0.4em 0.2em 0.4em;
|
||||
border-radius: .25em;
|
||||
color: var(--color-bg);
|
||||
text-align: center;
|
||||
transform-origin: center center;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
background-color: var(--color-text);
|
||||
transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
|
||||
.works .work:hover & {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
h1 {
|
||||
letter-spacing: -0.05em;
|
||||
line-height: .9;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
.works {
|
||||
padding: 24vw 0.5em 0.5em 0.5em;
|
||||
@media screen and (min-width: 768px) {
|
||||
font-size: 5.7em;
|
||||
padding: 14vw 0.5em 0.5em 0.5em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
width: 100%;
|
||||
padding-bottom: 20svh;
|
||||
}
|
||||
}
|
||||
.work {
|
||||
padding: var(--spacing-outer);
|
||||
flex: 0 0 calc(33% - .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;
|
||||
// visibility: hidden;
|
||||
}
|
||||
.workhero {
|
||||
width: 100%;
|
||||
// aspect-ratio: 4/3;
|
||||
object-fit: cover;
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
export async function load({ params }){
|
||||
try {
|
||||
const post = await import(`../${params.slug}.md`)
|
||||
const { title, date } = post.metadata
|
||||
const { title, date, header_bg_image } = post.metadata
|
||||
const Content = post.default.render()
|
||||
|
||||
return {
|
||||
title,
|
||||
date,
|
||||
header_bg_image,
|
||||
Content
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,45 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
export let data;
|
||||
|
||||
let visible = false;
|
||||
|
||||
onMount(() => {
|
||||
let workheros = document.querySelector('.hero');
|
||||
console.log(workheros);
|
||||
visible = true;
|
||||
})
|
||||
</script>
|
||||
<article>
|
||||
<h1>{data.title}</h1>
|
||||
<p>{@html data.Content.html}</p>
|
||||
<img class="hero" src="{data.header_bg_image}" alt="{data.title}" />
|
||||
{#if visible}
|
||||
<h1 in:fly>{data.title}</h1>
|
||||
<p in:fly>{@html data.Content.html}</p>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<style lang="scss">
|
||||
article {
|
||||
padding: var(--spacing-outer);
|
||||
}
|
||||
.hero {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left:0;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
height: auto;
|
||||
// object-fit: cover;
|
||||
}
|
||||
h1, p {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@media (max-width: 767px){
|
||||
h1 {
|
||||
margin-top: calc(100vw / 1.333);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
title: Adidas
|
||||
category: Visual Design
|
||||
permalink: /work/adidas
|
||||
header_bg_image: ./img/work_adidas/adidas_hero_bw.jpg
|
||||
extra_classes: portfolio theme-adidas
|
||||
header_bg_image: /work/adidas/adidas_hero.jpg
|
||||
order: 12
|
||||
description: Adidas, as you might have heard, is an international sports and lifestyle clothing brand. I worked on several digital campaigns as a graphic designer and illustrator.
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 1440 900" viewBox="0 0 1440 900">
|
||||
<path d="M1440 900v-.5l-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.2-8.3.4-11.9H707.9l-.1.6 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.2 8.3-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.5 14.2 14.2 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.3 14.1 8.2V900z" style="fill:#ffb7ab"/>
|
||||
</svg>'
|
||||
|
|
|
|||
21
src/routes/work/formo.md
Normal file
21
src/routes/work/formo.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: Formo.bio
|
||||
header_bg_image: /work/formo/formo_hero.png
|
||||
order: 12
|
||||
description: Formo, as you might have heard, is an international sports and lifestyle clothing brand. I worked on several digital campaigns as a graphic designer and illustrator.
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 1440 900" viewBox="0 0 1440 900">
|
||||
<path d="M1440 900v-.5l-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.2-8.3.4-11.9H707.9l-.1.6 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.2 8.3-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.5 14.2 14.2 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.3 14.1 8.2V900z" style="fill:#ffb7ab"/>
|
||||
</svg>'
|
||||
---
|
||||
<div class="infobox">
|
||||
<p>Client: <a href="https://www.adidas-group.com/en/">Adidas E-commerce dept</a></p>
|
||||
Tasks:
|
||||
<ul>
|
||||
<li>Visual Design</li>
|
||||
<li>Campaign asset production</li>
|
||||
<li>Illustration</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p><span class="drop_cap">A</span>didas, as you might have heard, is an international sports and lifestyle clothing brand. I worked on several digital campaigns as a graphic designer and illustrator.</p>
|
||||
</div>
|
||||
21
src/routes/work/jpl.md
Normal file
21
src/routes/work/jpl.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: JustPeace Labs
|
||||
header_bg_image: /work/jpl/jpl_hero.jpg
|
||||
description: JustPeace Labs is a non-profit organization that works with local communities to build peace and prevent violence.
|
||||
order: 11
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 1440 900" viewBox="0 0 1440 900">
|
||||
<path d="M1440 900v-.5l-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.2.5-14.3-14.2-8.3.5-14.3-14.2-8.2.5-14.3-14.1-8.3.4-14.2-14.1-8.3.4-14.3-14.1-8.3.4-14.3-14.1-8.2.4-14.3-14.1-8.3.4-14.3-14.1-8.3.5-14.2-14.2-8.3.5-14.3-14.2-8.3.4-11.9H707.9l-.1.6 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.2-.5 14.3 14.2 8.3-.5 14.2 14.2 8.3-.5 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.2-.5 14.3 14.1 8.3-.4 14.3 14.1 8.2-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.5 14.2 14.2 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.5 14.3 14.2 8.3-.5 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.2 14.1 8.3-.4 14.3 14.1 8.3-.4 14.3 14.1 8.2V900z" style="fill:#ffb7ab"/>
|
||||
</svg>'
|
||||
---
|
||||
<div class="infobox">
|
||||
<p>Client: <a href="https://www.adidas-group.com/en/">Adidas E-commerce dept</a></p>
|
||||
Tasks:
|
||||
<ul>
|
||||
<li>Visual Design</li>
|
||||
<li>Campaign asset production</li>
|
||||
<li>Illustration</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p><span class="drop_cap">A</span>didas, as you might have heard, is an international sports and lifestyle clothing brand. I worked on several digital campaigns as a graphic designer and illustrator.</p>
|
||||
</div>
|
||||
BIN
static/work/adidas/adidas_hero.jpg
Normal file
BIN
static/work/adidas/adidas_hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
BIN
static/work/formo/formo_hero.png
Normal file
BIN
static/work/formo/formo_hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
BIN
static/work/jpl/jpl_hero.jpg
Normal file
BIN
static/work/jpl/jpl_hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
|
|
@ -1,6 +1,7 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue