init
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Folio Nuxt
|
||||
|
||||
This is a Nuxt.js port of Jaron's portfolio.
|
||||
|
||||
## Development
|
||||
|
||||
Make sure you have [Bun](https://bun.sh) installed.
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Start development server
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
```bash
|
||||
# Build the project
|
||||
bun run build
|
||||
|
||||
# Preview the production build
|
||||
bun run preview
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import '~/assets/css/main.css';
|
||||
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg width="0" height="0" aria-hidden="true" style="position: absolute">
|
||||
<filter id="grainy" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency=".85"
|
||||
numOctaves="3"
|
||||
></feTurbulence>
|
||||
<feColorMatrix type="saturate" values="0"></feColorMatrix>
|
||||
<feBlend mode="multiply" in="SourceGraphic"></feBlend>
|
||||
</filter>
|
||||
</svg>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'SohneBuch';
|
||||
src:
|
||||
url('/Sohne-font-main/Sohne-Buch.woff') format('woff'),
|
||||
url('/Sohne-font-main/Sohne-Buch.ttf') format('truetype');
|
||||
}
|
||||
|
||||
:root {
|
||||
/* --font: 'Baskervville', serif; */
|
||||
--font: "SohneBuch", sans-serif;
|
||||
--text: #333;
|
||||
--muted: #666;
|
||||
--bg: #fbfbf7;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-ff {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app';
|
||||
|
||||
const props = defineProps<{ error: NuxtError }>();
|
||||
|
||||
useHead({
|
||||
title: 'Sorry...',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-card">
|
||||
<div class="error-bg"></div>
|
||||
<div class="error-content">
|
||||
<h1>{{ error.status }}</h1>
|
||||
<p>Sorry, something went wrong.</p>
|
||||
<NuxtLink to="/">Back to the homepage.</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 16rem;
|
||||
color: var(--bg);
|
||||
background: url('/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
filter: url(#grainy);
|
||||
}
|
||||
|
||||
.error-bg {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* glass effect on background */
|
||||
/* background: rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(2px);
|
||||
-webkit-backdrop-filter: blur(5px); */
|
||||
}
|
||||
|
||||
.error-content {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
color: var(--bg);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a::before {
|
||||
content: '';
|
||||
background-color: var(--bg);
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
/* left: -4px; */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* width: calc(100% + 8px); */
|
||||
width: 100%;
|
||||
/* height: 0; */
|
||||
height: calc(100% + 6px);
|
||||
transform: scaleY(0);
|
||||
transform-origin: bottom;
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
|
||||
a:hover::before {
|
||||
/* transform: scaleY(1); */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
mix-blend-mode: screen;
|
||||
color: var(--text);
|
||||
background-color: var(--bg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,433 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const titles = ["Hello, I'm Jaron.", 'Nice to meet you.'];
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
rotateTitle();
|
||||
|
||||
useHead({
|
||||
title: pageTitle,
|
||||
link: [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{
|
||||
rel: 'preconnect',
|
||||
href: 'https://fonts.gstatic.com',
|
||||
crossorigin: '',
|
||||
},
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Baskervville:ital,wght@0,400..700;1,400..700&display=swap',
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-wrapper">
|
||||
<div class="trace left"></div>
|
||||
<div class="trace right"></div>
|
||||
|
||||
<header class="header">
|
||||
<h1 class="name">Jaron</h1>
|
||||
<p class="title">
|
||||
Software Engineer by trade — tinkerer by nature.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="projects">
|
||||
<h5>Selected Work</h5>
|
||||
<div class="section__content">
|
||||
<a href="#" class="project">
|
||||
<h3 class="project__title">Mystwright</h3>
|
||||
<p class="project__description">
|
||||
An AI-driven mystery detective game. Generate,
|
||||
investigate, solve.
|
||||
</p>
|
||||
</a>
|
||||
<a href="#" class="project">
|
||||
<h3 class="project__title">Rune Buddy</h3>
|
||||
<p class="project__description">
|
||||
League of Legends runepage manager. Save &
|
||||
organise your pages for free.
|
||||
</p>
|
||||
</a>
|
||||
<a href="#" class="project">
|
||||
<h3 class="project__title">Unicord</h3>
|
||||
<p class="project__description">
|
||||
Lightweight Discord's bot SDK inspired by
|
||||
webserver architecture.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h5>About</h5>
|
||||
<div class="section__content">
|
||||
<p>
|
||||
I work across the full stack; backend, frontend, and
|
||||
the product decisions in between. My favorite side
|
||||
projects are ones that bring me new skills. Even
|
||||
better are ones where I solve a problem or build a
|
||||
tool I can use. I'm always open to interesting work
|
||||
and conversations.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="photos">
|
||||
<h5>Photos</h5>
|
||||
<div class="section__content">
|
||||
<div class="photo-grid">
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/underground.jpg"
|
||||
alt="London Underground Sign"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>London, UK</p>
|
||||
<p>2026</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/london_porsche.jpg"
|
||||
alt="London Porsche"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>London, UK</p>
|
||||
<p>2025</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/austin_parking.jpg"
|
||||
alt="Austin Parking Sign"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>Austin, TX</p>
|
||||
<p>2024</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/athens_red_scooter.jpg"
|
||||
alt="Athens Red Scooter"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>Athens, Greece</p>
|
||||
<p>2026</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/athens_subway.jpg"
|
||||
alt="Athens Subway"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>Athens, Greece</p>
|
||||
<p>2026</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-grid__item">
|
||||
<div class="photo__image">
|
||||
<img
|
||||
src="/athens_sunset.jpg"
|
||||
alt="Athens Sunset"
|
||||
/>
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>Athens, Greece</p>
|
||||
<p>2026</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div style="flex: 1"></div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-upper">
|
||||
<div class="flex-center">
|
||||
<p>
|
||||
<a style="color: var(--bg)" 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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-lower">
|
||||
<div class="flex-center">
|
||||
<p style="text-align: right">Est. 2003</p>
|
||||
<div class="flex-ff"></div>
|
||||
<p style="text-align: right">Austin, TX</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.page-wrapper {
|
||||
--main-width: 50vw;
|
||||
width: var(--main-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.trace {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: var(--muted);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.trace.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.trace.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
svg[height='0'] {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 4rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
|
||||
border-top: 1px solid var(--muted);
|
||||
border-bottom: 1px solid var(--muted);
|
||||
background: url('/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: left top;
|
||||
background-repeat: no-repeat;
|
||||
backdrop-filter: url(#grainy);
|
||||
mix-blend-mode: multiply;
|
||||
padding: 2rem;
|
||||
color: var(--bg);
|
||||
|
||||
margin-top: 8vh;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer-upper {
|
||||
border-top: 1px solid var(--muted);
|
||||
border-bottom: 1px solid var(--muted);
|
||||
background: url('/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: left bottom;
|
||||
background-repeat: no-repeat;
|
||||
backdrop-filter: url(#grainy);
|
||||
mix-blend-mode: multiply;
|
||||
padding: 2rem;
|
||||
color: var(--bg);
|
||||
min-height: 10vh;
|
||||
}
|
||||
|
||||
.footer-lower {
|
||||
padding: 1rem;
|
||||
height: 8vh;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
section > .section__content {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.project {
|
||||
border-bottom: 1px solid var(--muted);
|
||||
padding: 2rem 0;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.project:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.project:hover > .project__title {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.project__title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.project__description {
|
||||
margin: 0;
|
||||
font-size: small;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.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(2px);
|
||||
color: var(--bg);
|
||||
z-index: 2;
|
||||
transition: opacity 0.2s 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;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.page-wrapper {
|
||||
--main-width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-wrapper {
|
||||
--main-width: 100vw;
|
||||
}
|
||||
|
||||
.trace {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.photo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2026-04-28',
|
||||
devtools: { enabled: true },
|
||||
nitro: {
|
||||
prerender: {
|
||||
autoSubfolderIndex: false,
|
||||
},
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: { lang: 'en' },
|
||||
charset: 'utf-8',
|
||||
viewport: 'width=device-width, initial-scale=1',
|
||||
title: 'Jaron',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Software Engineer by trade — tinkerer by nature.',
|
||||
},
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "folio-nuxt",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "^4.4.2",
|
||||
"vue": "^3.5.33",
|
||||
"vue-router": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||