2026-06-12 20:22:56 -05:00
< script lang = "ts" >
// module-scoped so the reveal only plays once per visit
let sectionsRevealed = false ;
< / script >
2026-04-29 14:39:34 -05:00
< script setup lang = "ts" >
import { ref } from 'vue' ;
2026-06-22 00:44:50 -05:00
const siteUrl = 'https://jaron.sh' ;
const currentUrl = useRequestURL ( ) . href ;
const personId = ` ${ siteUrl } /#person ` ;
const webpageId = ` ${ siteUrl } /#homepage ` ;
2026-06-22 00:28:43 -05:00
useSeoMeta ( {
title : "Hey, I'm Jaron." ,
ogTitle : "Hey, I'm Jaron." ,
description :
'Software Engineer. I build things to solve problems from the ground up.' ,
ogDescription :
'Software Engineer. I build things to solve problems from the ground up.' ,
2026-06-22 00:44:50 -05:00
ogUrl : currentUrl ,
2026-06-22 00:28:43 -05:00
} ) ;
2026-04-29 16:32:57 -05:00
const titles = [
2026-06-22 00:28:43 -05:00
"Hey, I'm Jaron." ,
2026-04-29 16:32:57 -05:00
'Nice to meet you.' ,
'Welcome.' ,
'Willkommen.' ,
'Bienvenue.' ,
] ;
2026-04-29 14:39:34 -05:00
2026-06-22 00:28:43 -05:00
const selectedWorks = [
{
title : 'Edlink' ,
description :
"I built many things I'm proud of at Edlink. Notably, I designed and shipped Edlink’ s internal billing platform for ~150 clients, a flat file ingestion engine that enabled arbitrary data mapping, client-embedded UI widgets, user onboarding flows, and a secure sandbox for running untrusted code with a live data-preview service." ,
link : 'https://ed.link' ,
images : [
{
src : '/work/edlink/embedded-onboarding.gif' ,
alt : 'Embedded Onboarding' ,
} ,
{
src : '/work/edlink/authentication-widget.gif' ,
alt : 'Auth Widget' ,
} ,
{
src : '/work/edlink/integration-sharing-rules-widget.png' ,
alt : 'Data Sharing Editor Widget' ,
} ,
{
src : '/work/edlink/billing-overview.png' ,
alt : 'Billing Overview' ,
} ,
{
src : '/work/edlink/invoice-sheet.png' ,
alt : 'Invoice View' ,
} ,
{
src : '/work/edlink/add-payment-method.png' ,
alt : 'Add Payment Method' ,
} ,
{
src : '/work/edlink/onboarding-select-provider.png' ,
alt : 'Provider Selection' ,
} ,
{
src : '/work/edlink/onboarding-edit-source.png' ,
alt : 'Source Creation' ,
} ,
{
src : '/work/edlink/onboarding-start.png' ,
alt : 'Onboarding Start' ,
} ,
{
src : '/work/edlink/custom-code-editor.png' ,
alt : 'Custom Code Editor' ,
} ,
{
src : '/work/edlink/edit-licensing-rules.png' ,
alt : 'Licensing Editor' ,
} ,
{
src : '/work/edlink/materialization-preview.png' ,
alt : 'Pending Data Change Preview' ,
} ,
] ,
} ,
] ;
const projects = [
{
title : 'Unicord' ,
href : 'https://unicord.hat.fish/' ,
description : 'Discord bot SDK inspired by webserver architecture.' ,
} ,
{
title : 'Mystwright' ,
href : 'https://mystwright.com/' ,
description :
'An AI-driven mystery detective game. Generate, investigate, solve.' ,
} ,
{
title : 'Rune Buddy' ,
href : 'https://github.com/jaronpate/rift-buddy' ,
description :
'League of Legends runepage manager. Save & organise your pages for free.' ,
} ,
] ;
2026-06-22 00:44:50 -05:00
useHead ( {
script : [
{
key : 'json-ld-homepage' ,
type : 'application/ld+json' ,
innerHTML : JSON . stringify ( {
'@context' : 'https://schema.org' ,
'@type' : 'ProfilePage' ,
'@id' : webpageId ,
url : siteUrl ,
name : "Hey, I'm Jaron." ,
description :
'Software Engineer. I build things to solve problems from the ground up.' ,
inLanguage : 'en-US' ,
isPartOf : { '@id' : ` ${ siteUrl } /#website ` } ,
about : { '@id' : personId } ,
mainEntity : { '@id' : personId } ,
2026-06-22 00:57:06 -05:00
mentions : [
... selectedWorks . map ( ( work ) => ( {
'@type' : 'CreativeWork' ,
name : work . title ,
description : work . description ,
url : work . link ,
} ) ) ,
... projects . map ( ( project ) => ( {
'@type' : 'CreativeWork' ,
name : project . title ,
description : project . description ,
url : project . href ,
} ) ) ,
2026-06-22 00:44:50 -05:00
] ,
} ) ,
} ,
] ,
} ) ;
2026-06-22 00:28:43 -05:00
const photos = [
{
src : '/photography/athens_red_scooter.jpg' ,
alt : 'Red scooter in Athens' ,
location : 'Athens' ,
year : '2026' ,
} ,
{
src : '/photography/athens_subway.jpg' ,
alt : 'Athens metro station' ,
location : 'Athens' ,
year : '2026' ,
} ,
{
src : '/photography/athens_sunset.jpg' ,
alt : 'Athens at sunset' ,
location : 'Athens' ,
year : '2026' ,
} ,
{
src : '/photography/london_porsche.jpg' ,
alt : 'Porsche shop in London' ,
location : 'London' ,
year : '2025' ,
} ,
{
src : '/photography/underground.jpg' ,
alt : 'Entrace to the London underground' ,
location : 'London' ,
year : '2026' ,
} ,
{
src : '/photography/austin_parking.jpg' ,
alt : 'Parking garage in Austin' ,
location : 'Austin' ,
year : '2024' ,
} ,
] ;
2026-04-29 14:39:34 -05:00
const pageTitle = ref ( titles [ 0 ] ) ;
const rotateTitle = ( ) => {
setTimeout ( ( ) => {
const i = titles . findIndex ( ( t ) => t === pageTitle . value ) ;
pageTitle . value = titles [ i + 1 ] ? ? titles [ 0 ] ? ? 'Oops!' ;
rotateTitle ( ) ;
} , 2000 ) ;
} ;
2026-04-29 23:13:10 -05:00
definePageMeta ( {
2026-06-12 20:22:56 -05:00
layout : false ,
} ) ;
const { $gsap } = useNuxtApp ( ) ;
const pageRoot = ref < HTMLElement | null > ( null ) ;
onMounted ( ( ) => {
2026-06-22 00:28:43 -05:00
rotateTitle ( ) ;
useHead ( {
title : pageTitle ,
} ) ;
2026-06-12 20:22:56 -05:00
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 ( ) ) ;
2026-04-29 23:13:10 -05:00
} ) ;
2026-04-29 14:39:34 -05:00
< / script >
< template >
2026-06-12 20:22:56 -05:00
< div ref = "pageRoot" >
< NuxtLayout name = "main" >
2026-06-22 00:28:43 -05:00
< section class = "selected-work" >
2026-06-12 20:22:56 -05:00
< h5 > Selected Work < / h5 >
< div class = "section__content" >
< a
2026-06-22 00:28:43 -05:00
v-for = "work in selectedWorks"
:key = "work.title"
class = "work"
:href = "work.link"
2026-06-12 20:22:56 -05:00
target = "_blank"
>
2026-06-22 00:28:43 -05:00
< h3 class = "work__title" > { { work . title } } < / h3 >
< p class = "work__description" >
{ { work . description } }
2026-06-12 20:22:56 -05:00
< / p >
2026-06-22 00:28:43 -05:00
< div class = "work__images" >
< figure
v-for = "image in work.images"
:key = "image.src"
class = "work__image"
>
< img :src = "image.src" :alt = "image.alt" / >
< / figure >
< / div >
2026-06-12 20:22:56 -05:00
< / a >
2026-06-22 00:28:43 -05:00
< / div >
< / section >
< section class = "projects" >
< h5 > Projects < / h5 >
< div class = "section__content" >
2026-06-12 20:22:56 -05:00
< a
2026-06-22 00:28:43 -05:00
v-for = "project in projects"
:key = "project.href"
:href = "project.href"
2026-06-12 20:22:56 -05:00
target = "_blank"
class = "project"
>
2026-06-22 00:28:43 -05:00
< h3 class = "project__title" > { { project . title } } < / h3 >
2026-06-12 20:22:56 -05:00
< p class = "project__description" >
2026-06-22 00:28:43 -05:00
{ { project . description } }
2026-06-12 20:22:56 -05:00
< / p >
< / a >
< / div >
< / section >
< section class = "about" >
< h5 > About < / h5 >
< div class = "section__content" >
2026-06-14 17:29:58 -05:00
< p > I build things to solve problems from the ground up . < / p >
2026-06-12 20:22:56 -05:00
< 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
2026-06-14 17:29:58 -05:00
decisions I' ve ever made . I 've learned a lot since then
and
2026-06-18 21:37:32 -05:00
<em>still</em> much to learn and build.
2026-04-29 23:13:10 -05:00
</p>
2026-06-12 20:22:56 -05:00
<p>
2026-06-14 17:29:58 -05:00
If you want to build something cool or connect hit me up
— we' ll grab a coffee or beer in you 're in Austin! I
still dont know enough people to form a band yet ;)
2026-04-29 23:13:10 -05:00
</p>
2026-06-12 20:22:56 -05:00
</div>
</section>
2026-06-14 17:29:58 -05:00
<!-- <section class="photos">
2026-06-12 20:22:56 -05:00
<h5>Photos</h5>
<div class="section__content">
<div class="photo-grid">
2026-06-22 00:28:43 -05:00
<div
v-for="photo in photos"
:key="photo.src"
class="photo-grid__item"
>
2026-06-12 20:22:56 -05:00
<div class="photo__image">
2026-06-22 00:28:43 -05:00
<img :src="photo.src" :alt="photo.alt" />
2026-06-12 20:22:56 -05:00
</div>
<div class="photo__overlay">
2026-06-22 00:28:43 -05:00
<p>{{ photo.location }}</p>
<p>{{ photo.year }}</p>
2026-06-12 20:22:56 -05:00
</div>
2026-05-02 21:03:02 -05:00
</div>
</div>
</div>
2026-06-14 17:29:58 -05:00
</section> -->
2026-06-12 20:22:56 -05:00
</NuxtLayout>
</div>
2026-04-29 14:39:34 -05:00
</template>
2026-05-02 21:28:14 -05:00
<style scoped>
2026-04-29 14:39:34 -05:00
section {
margin-top: 2rem;
}
section > .section__content {
padding: 0 2rem;
}
2026-05-02 21:28:14 -05:00
/* section:not(:has(h5)) > .section__content {
padding: 2rem;
} */
2026-04-29 14:39:34 -05:00
section > h5::before {
content: ' ';
height: 1px;
background: var(--muted);
order: 0;
width: 1rem;
}
section > h5::after {
content: ' ';
height: 1px;
background: var(--muted);
flex-grow: 1;
}
section > h5 {
font-weight: 400;
font-style: italic;
color: var(--muted);
display: flex;
align-items: center;
gap: 1rem;
margin-top: 0;
}
section:last-of-type {
margin-bottom: 2rem;
}
2026-06-22 00:28:43 -05:00
.work {
}
.work__images {
/*display: flex;
flex-wrap: no-wrap;
align-items: center;
justify-content: center;*/
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: var(--gap);
overflow-x: auto;
overflow-y: hidden;
margin-top: var(--gap);
}
.work__image {
--dot-size: 6%;
margin: 0;
/*padding: calc(var(--gap) * 2) var(--gap) var(--gap);*/
padding: var(--gap);
flex-shrink: 0;
border-radius: var(--radius);
background-image: radial-gradient(
circle at 4px 4px,
var(--dot-color) var(--dot-size),
transparent calc(var(--dot-size) + 1%)
);
background-position: 4px 4px;
background-size: 12px 12px;
background-repeat: repeat;
background-color: var(--bg-secondary);
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--gap);
}
.work__image img {
border-radius: var(--radius);
object-fit: cover;
max-height: 350px;
}
@media screen and (max-width: 750px) {
.work__image img {
max-height: 300px;
}
}
.work__image figcaption {
font-size: 0.6rem;
line-height: 1;
font-family: var(--font-plain);
background: var(--bg-secondary);
border-radius: 2px;
padding: 3px 4px;
background: var(--dot-color);
/*border: 1px solid var(--dot-color);*/
display: flex;
align-items: center;
justify-content: center;
}
.project,
.work {
/*border-bottom: 1px solid var(--muted);*/
padding: var(--gap) 0;
2026-04-29 14:39:34 -05:00
display: block;
text-decoration: none;
color: inherit;
}
2026-06-22 00:28:43 -05:00
.work {
border: 0;
}
.project:first-of-type,
.work:first-of-type {
2026-04-29 16:32:57 -05:00
padding-top: 0;
}
2026-06-22 00:28:43 -05:00
.project:last-of-type,
.work:last-of-type {
2026-04-29 16:32:57 -05:00
padding-bottom: 0;
}
2026-04-29 14:39:34 -05:00
.project:last-child {
border-bottom: none;
}
.project:hover > .project__title {
text-decoration: underline;
text-underline-offset: 4px;
font-style: italic;
2026-05-01 09:54:25 -05:00
font-family: var(--font-hover);
2026-04-29 14:39:34 -05:00
}
2026-06-22 00:28:43 -05:00
.project__title,
.work__title {
2026-04-29 14:39:34 -05:00
margin: 0;
font-weight: 400;
2026-06-22 00:28:43 -05:00
margin-bottom: calc(var(--gap) * 0.5);
2026-04-29 14:39:34 -05:00
}
2026-06-22 00:28:43 -05:00
.project__title::after,
.work__title::after {
2026-06-12 20:22:56 -05:00
content: ' ↗ ' ;
position : relative ;
display : inline - block ;
margin - left : 0.4 em ;
font - size : 0.75 em ;
opacity : 0 ;
vertical - align : center ;
transform : translate ( - 6 px , 6 px ) ;
transition :
opacity 0.25 s ease ,
transform 0.25 s ease ;
}
2026-06-22 00:28:43 -05:00
. project : hover . project _ _title : : after ,
. work : hover . work _ _title : : after {
2026-06-12 20:22:56 -05:00
opacity : 1 ;
transform : translate ( 0 , 0 ) ;
}
2026-06-22 00:28:43 -05:00
. project _ _description ,
. work _ _description {
2026-04-29 14:39:34 -05:00
margin : 0 ;
font - size : small ;
font - weight : 400 ;
font - style : italic ;
color : var ( -- muted ) ;
}
. photo - grid {
display : grid ;
grid - template - columns : repeat ( 3 , 1 fr ) ;
}
. photo - grid _ _item {
cursor : pointer ;
position : relative ;
aspect - ratio : 1 / 1 ;
}
. photo - grid _ _item : hover > . photo _ _overlay {
opacity : 1 ;
}
. photo _ _image {
width : 100 % ;
height : 100 % ;
}
. photo _ _image img {
width : 100 % ;
height : 100 % ;
object - fit : cover ;
}
. photo _ _overlay {
opacity : 0 ;
position : absolute ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 100 % ;
background - color : rgba ( 0 , 0 , 0 , 0.5 ) ;
backdrop - filter : blur ( 2 px ) ;
2026-06-12 20:22:56 -05:00
color : var ( -- color - white - 500 ) ;
2026-04-29 14:39:34 -05:00
z - index : 2 ;
transition : opacity 0.2 s ease - in - out ;
display : flex ;
align - items : center ;
justify - content : center ;
flex - direction : column ;
}
. photo _ _overlay p {
font - weight : 500 ;
margin : 0 ;
}
. photo _ _overlay p : nth - child ( 2 ) {
font - weight : 300 ;
font - style : italic ;
2026-05-02 21:03:02 -05:00
font - size : small ;
2026-04-29 14:39:34 -05:00
}
2026-05-12 19:12:41 -05:00
. about p : last - child {
2026-04-29 16:32:57 -05:00
margin : 0 ;
}
2026-04-29 14:39:34 -05:00
@ media ( max - width : 768 px ) {
. photo - grid {
grid - template - columns : 1 fr ;
}
}
< / style >