Added some animations and tweaked some styles

This commit is contained in:
2026-06-12 20:22:56 -05:00
parent d8d93f4517
commit 3cb4b9b71f
9 changed files with 404 additions and 202 deletions
+34 -3
View File
@@ -25,8 +25,8 @@
.dark-mode {
--text: #fbfbf7;
--bg: #111111;
--muted: #d1d1d1;
--bg: #0c0c0c;
--muted: #a3a3a3;
--color-white-500: #fbfbf7;
}
@@ -73,6 +73,34 @@ body {
line-height: 1.6;
}
/* full-page film grain */
body::after {
content: '';
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
opacity: 0.06;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
mix-blend-mode: multiply;
}
.dark-mode body::after {
mix-blend-mode: screen;
opacity: 0.045;
}
/* quick page transition */
.page-enter-active,
.page-leave-active {
transition: opacity 0.18s ease;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
.basic-link {
color: var(--text);
transition: opacity 0.2s ease;
@@ -92,7 +120,10 @@ body {
transition: all 200ms ease;
padding: 0 4px;
margin: 0 -4px;
background-image: linear-gradient(var(--color-white-500), var(--color-white-500));
background-image: linear-gradient(
var(--color-white-500),
var(--color-white-500)
);
background-size: 100% 1px;
background-position: bottom;
background-repeat: no-repeat;
+90 -14
View File
@@ -1,3 +1,9 @@
<script lang="ts">
// module-scoped so it survives client-side navigation —
// the intro should only ever play once per visit
let introPlayed = false;
</script>
<script setup lang="ts">
defineProps<{
title?: string;
@@ -9,6 +15,60 @@ const theme = useColorMode();
const toggleTheme = () => {
theme.preference = theme.preference === 'light' ? 'dark' : 'light';
};
const { $gsap } = useNuxtApp();
const pageEl = ref<HTMLElement | null>(null);
onMounted(() => {
if (!$gsap || !pageEl.value || introPlayed) return;
introPlayed = true;
const reduced = window.matchMedia(
'(prefers-reduced-motion: reduce)',
).matches;
const ctx = $gsap.context(() => {
if (reduced) return;
const tl = $gsap.timeline({
defaults: { ease: 'power3.out' },
});
// trace lines draw down the page
tl.from('.trace', {
scaleY: 0,
transformOrigin: 'top center',
duration: 1.4,
ease: 'power3.inOut',
})
// header panel fades in as the lines pass it
.from(
'.header-lower',
{ autoAlpha: 0, duration: 0.9, ease: 'power2.out' },
0.3,
)
// masked title reveal
.from('.name', { yPercent: 110, duration: 0.9 }, 0.55)
.from('.title', { yPercent: 120, autoAlpha: 0, duration: 0.8 }, 0.7)
// footer appears as the lines reach the bottom
.from(
['.footer-upper', '.footer-lower'],
{ autoAlpha: 0, duration: 0.7, ease: 'power2.out' },
0.9,
)
// nav chrome fades in last
.from('.header-upper', { autoAlpha: 0, duration: 0.7 }, 0.9);
// body content waits for the intro
tl.from(
'main',
{ autoAlpha: 0, y: 16, duration: 0.8, ease: 'power2.out' },
0.7,
);
}, pageEl.value);
onBeforeUnmount(() => ctx.revert());
});
</script>
<template>
@@ -24,7 +84,7 @@ const toggleTheme = () => {
<feBlend mode="multiply" in="SourceGraphic"></feBlend>
</filter>
</svg>
<div class="page-wrapper">
<div class="page-wrapper" ref="pageEl">
<div class="trace left"></div>
<div class="trace right"></div>
@@ -41,24 +101,36 @@ const toggleTheme = () => {
{{ theme.value === 'dark' ? '🌚' : '🌝' }}
</button>
<div class="flex-ff"></div>
<a
class="basic-link"
href="https://github.com/jaronpate"
>
GitHub
</a>
<!-- <NuxtLink to="/resume" class="basic-link"
>Resume</NuxtLink
> -->
<!-- <NuxtLink to="/writing" class="basic-link">
<NuxtLink to="/writing" class="basic-link">
Writing
</NuxtLink> -->
</NuxtLink>
</div>
</slot>
</div>
<div class="header-lower">
<slot name="header-lower">
<h1 class="name">{{ title || "Hello, I'm Jaron" }}</h1>
<div class="mask">
<h1 class="name">
{{ title || "Hello, I'm Jaron" }}
</h1>
</div>
<div class="mask">
<p class="title">
{{
description ||
'Software Engineer by trade — builder by nature.'
}}
</p>
</div>
</slot>
</div>
</header>
@@ -69,33 +141,27 @@ const toggleTheme = () => {
</main>
<footer class="footer">
<div class="footer-upper">
<div class="flex-center">
<div class="footer-upper flex-center">
<p>
<a class="punch-link" href="mailto:yo@jp.wtf"
>Say hi.</a
>
</p>
<div class="flex-ff"></div>
<p style="text-align: right">
Let's make something cool.
</p>
<p style="text-align: right">Let's make something cool.</p>
</div>
</div>
<div class="footer-lower">
<div class="flex-center" style="gap: var(--gap)">
<div class="footer-lower flex-center" style="gap: var(--gap)">
<p style="text-align: right">Est. 2003</p>
<!-- <p style="font-family: monospace">🐟🎩.dev</p> -->
<div class="flex-ff"></div>
<p style="text-align: right">Austin, TX</p>
</div>
</div>
</footer>
</div>
</div>
</template>
<style>
<style scoped>
.page-wrapper {
--main-width: 50vw;
width: var(--main-width);
@@ -133,6 +199,10 @@ svg[height='0'] {
position: fixed;
}
.mask {
overflow: hidden;
}
.name {
font-size: 3rem;
font-weight: 400;
@@ -144,6 +214,7 @@ svg[height='0'] {
font-weight: 300;
margin: 0;
font-style: italic;
color: var(--color-white-500);
}
.header {
@@ -226,12 +297,17 @@ svg[height='0'] {
font-weight: bold;
color: var(--text);
transition: opacity 0.2s ease;
padding: 0;
}
.theme-toggle:hover {
opacity: 0.5;
}
.footer p {
margin: 0;
}
@media (max-width: 1024px) {
.page-wrapper {
--main-width: 80vw;
+74 -10
View File
@@ -1,3 +1,8 @@
<script lang="ts">
// module-scoped so the reveal only plays once per visit
let sectionsRevealed = false;
</script>
<script setup lang="ts">
import { ref } from 'vue';
@@ -26,11 +31,48 @@ useHead({
});
definePageMeta({
layout: 'main',
layout: false,
});
const { $gsap } = useNuxtApp();
const pageRoot = ref<HTMLElement | null>(null);
onMounted(() => {
if (!$gsap || !pageRoot.value || sectionsRevealed) return;
sectionsRevealed = true;
const reduced = window.matchMedia(
'(prefers-reduced-motion: reduce)',
).matches;
const ctx = $gsap.context(() => {
if (reduced) return;
// sections in view on load are revealed by the layout's intro;
// below-the-fold sections rise in as they enter the viewport
$gsap.utils.toArray<HTMLElement>('main section').forEach((el) => {
const inView = el.getBoundingClientRect().top < window.innerHeight;
if (inView) return;
$gsap.from(el, {
autoAlpha: 0,
y: 24,
duration: 0.6,
ease: 'power2.out',
scrollTrigger: {
trigger: el,
start: 'top 92%',
},
});
});
}, pageRoot.value);
onBeforeUnmount(() => ctx.revert());
});
</script>
<template>
<div ref="pageRoot">
<NuxtLayout name="main">
<section class="projects">
<h5>Selected Work</h5>
@@ -63,8 +105,8 @@ definePageMeta({
>
<h3 class="project__title">Rune Buddy</h3>
<p class="project__description">
League of Legends runepage manager. Save & organise your
pages for free.
League of Legends runepage manager. Save & organise
your pages for free.
</p>
</a>
</div>
@@ -78,17 +120,19 @@ definePageMeta({
</p>
<p>
I left school and my job at Chick-fil-A in 2022 to write
code full time. It's definitely one of the best decisions
I've ever made. I've learned so much since then and
code full time. It's definitely one of the best
decisions I've ever made. I've learned so much since
then and
<em>still</em> have so much more to learn.
</p>
<p>
If you want to build something cool or connect hit me up! I
still dont know enough people to form a band yet ;)
If you want to build something cool or connect hit me
up! I still dont know enough people to form a band yet
;)
</p>
</div>
</section>
<!-- <section class="photos">
<section class="photos">
<h5>Photos</h5>
<div class="section__content">
<div class="photo-grid">
@@ -166,8 +210,9 @@ definePageMeta({
</div>
</div>
</div>
</section> -->
</section>
</NuxtLayout>
</div>
</template>
<style scoped>
@@ -244,6 +289,25 @@ section:last-of-type {
font-weight: 400;
}
.project__title::after {
content: '↗';
position: relative;
display: inline-block;
margin-left: 0.4em;
font-size: 0.75em;
opacity: 0;
vertical-align: center;
transform: translate(-6px, 6px);
transition:
opacity 0.25s ease,
transform 0.25s ease;
}
.project:hover .project__title::after {
opacity: 1;
transform: translate(0, 0);
}
.project__description {
margin: 0;
font-size: small;
@@ -287,7 +351,7 @@ section:last-of-type {
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
color: var(--bg);
color: var(--color-white-500);
z-index: 2;
transition: opacity 0.2s ease-in-out;
display: flex;
+3 -1
View File
@@ -16,11 +16,12 @@ useSeoMeta({
});
definePageMeta({
layout: 'main',
layout: false,
});
</script>
<template>
<div>
<NuxtLayout
name="main"
:title="page?.title"
@@ -40,6 +41,7 @@ definePageMeta({
</template>
<ContentRenderer class="writing__content" v-if="page" :value="page" />
</NuxtLayout>
</div>
</template>
<style scoped>
+16 -5
View File
@@ -1,16 +1,22 @@
<script setup lang="ts">
const { data: pages } = await useAsyncData('writing-pages', () =>
queryCollection('writing').all(),
);
// const { data: pages } = await useAsyncData('writing-pages', () =>
// queryCollection('writing').all(),
// );
const pages: any[] = [];
const formatDate = (dateString: string) => {
const date = new Date(dateString);
// ex. May 5, 2026
return `${date.toLocaleDateString('en-US', { month: 'short' })} ${date.getDate()}, ${date.getFullYear()}`;
};
definePageMeta({
layout: false,
});
</script>
<template>
<div>
<NuxtLayout
name="main"
title="Writing"
@@ -18,7 +24,11 @@ const formatDate = (dateString: string) => {
>
<template #header-upper>
<div class="header-upper-inner">
<NuxtLink to="/" class="back-link" aria-label="Back to Home">
<NuxtLink
to="/"
class="back-link"
aria-label="Back to Home"
>
<span class="arrow">←</span>
<span>Home</span>
</NuxtLink>
@@ -44,12 +54,13 @@ const formatDate = (dateString: string) => {
</template>
<template v-else>
<div class="empty-state">
<p>Nothing here yet.</p>
<p>No thoughts...head empty</p>
</div>
</template>
</div>
</section>
</NuxtLayout>
</div>
</template>
<style scoped>
+13
View File
@@ -0,0 +1,13 @@
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
export default defineNuxtPlugin(() => {
gsap.registerPlugin(ScrollTrigger);
return {
provide: {
gsap,
ScrollTrigger,
},
};
});
+3
View File
@@ -7,6 +7,7 @@
"dependencies": {
"@nuxt/content": "3.13.0",
"better-sqlite3": "^12.9.0",
"gsap": "^3.15.0",
"nuxt": "^4.4.2",
"vue": "^3.5.33",
"vue-router": "^5.0.6",
@@ -981,6 +982,8 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"gsap": ["gsap@3.15.0", "", {}, "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A=="],
"gzip-size": ["gzip-size@7.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA=="],
"h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
+1
View File
@@ -30,6 +30,7 @@ export default defineNuxtConfig({
},
app: {
pageTransition: { name: 'page', mode: 'out-in' },
head: {
htmlAttrs: { lang: 'en' },
charset: 'utf-8',
+1
View File
@@ -13,6 +13,7 @@
"dependencies": {
"@nuxt/content": "3.13.0",
"better-sqlite3": "^12.9.0",
"gsap": "^3.15.0",
"nuxt": "^4.4.2",
"vue": "^3.5.33",
"vue-router": "^5.0.6",