1
0

Init Commit

This commit is contained in:
Kyle Austad 2025-02-26 15:40:10 -06:00
commit 8ca7e1ce91
95 changed files with 14713 additions and 0 deletions

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
node_modules
dist
android
.git
.gitignore
Dockerfile
.dockerignore
README.md
.env
.vscode
.output
.nuxt

24
.gitignore vendored Normal file
View File

@ -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

75
README.md Normal file
View File

@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

159
assets/WorkEntries.ts Normal file
View File

@ -0,0 +1,159 @@
import type { WorkEntry } from "~/types/WorkEntry";
export const workEntries: WorkEntry[] = [
{
title: "Just Canvas",
desc: "With a background in Door-to-Door, and development experience, I set out to create a simple app for canvassing and door-to-door. Composed of three parts, the back-end database using MongoDB, the API written in express.js, and the front-end app. The front-end was originally written in Ionic/Vue, but later re-written using Vue3 and PrimeVue. The full-stack is build and deployed to a self-hosted UnRaid server and Docker registry.",
coverImg: "/justCanvas/justcanvas-banner.png",
stack: [
"/js-logo.png",
"/css.png",
"/mongo.svg",
"/vue.png",
"/express.webp",
"/git.png",
"/docker.webp",
],
links: [
{
label: "App",
severity: "info",
link: "https://app.justcanvas.app",
icon: "pi pi-check",
},
{
label: "LinkedIn",
severity: "help",
link: "https://www.linkedin.com/pulse/justcanvas-app-api-kyle-austad-nsdlc/",
icon: "pi pi-linkedin",
},
{
label: "Website",
severity: "info",
link: "https://justcanvas.app",
icon: "pi pi-external-link",
},
],
gallery: [
{ itemImageSrc: "/justCanvas/jc-1.png" },
{ itemImageSrc: "/justCanvas/jc-2.png" },
{ itemImageSrc: "/justCanvas/jc-3.png" },
{ itemImageSrc: "/justCanvas/jc-4.png" },
{ itemImageSrc: "/justCanvas/jc-5.png" },
{ itemImageSrc: "/justCanvas/jc-6.png" },
{ itemImageSrc: "/justCanvas/jc-7.png" },
{ itemImageSrc: "/justCanvas/jc-8.png" },
{ itemImageSrc: "/justCanvas/jc-9.png" },
{ itemImageSrc: "/justCanvas/jc-10.png" },
{ itemImageSrc: "/justCanvas/jc-11.png" },
],
},
{
title: "Metro Island",
desc: "A complete game focused on building an island city and stat management. Build a thriving metropolis on remote islands and manage your city with the buildings you build. Dont let any of them get too high or too low though! The goal here was to develop a complete game with a cohesive experience. While there are flaws, the core mechanics and art style turned out very fun and cozy!",
coverImg: "/metro/metro-banner.jpg",
stack: [
"/cpp.png",
"/unreal.svg",
"/git.png",
"/blender.png",
],
links: [
{
label: "Itch.io",
severity: "info",
link: "https://thiscketcrab.itch.io/metro-island",
icon: "pi pi-check",
},
],
gallery: [
{ itemImageSrc: "/metro/m-1.jpg" },
{ itemImageSrc: "/metro/m-2.jpg" },
{ itemImageSrc: "/metro/m-3.jpg" },
{ itemImageSrc: "/metro/m-4.jpg" },
{ itemImageSrc: "/metro/m-5.jpg" },
{ itemImageSrc: "/metro/m-6.jpg" },
{ itemImageSrc: "/metro/m-7.jpg" },
{ itemImageSrc: "/metro/m-8.jpg" },
{ itemImageSrc: "/metro/m-9.jpg" },
{ itemImageSrc: "/metro/m-10.jpg" },
{ itemImageSrc: "/metro/m-11.jpg" },
],
},
{
title: "Unreal Assets",
desc: "A wide collection of asset packs made for Unreal Engine, using varying degrees of C++ and Unreal Blueprints. Focused on ease of use and simple implementation of commonly created systems, ranging from objective and quest management, dialogue systems, to a complete re-creation of the spell system from Morrowind.",
coverImg: "/fab/fab-banner.webp",
stack: [
"/cpp.png",
"/unreal.svg",
"/git.png",
"/blender.png",
"/substance.png",
"/docker.webp",
],
links: [
{
label: "Fab",
severity: "info",
link: "https://www.fab.com/sellers/Crab%20Interactive",
icon: "pi pi-external-link",
},
{
label: "Git Repos",
severity: "help",
link: "https://git.crabinteractive.com/explore/repos",
icon: "pi pi-github",
},
],
gallery: [
{ itemImageSrc: "/fab/fab-1.webp" },
{ itemImageSrc: "/fab/fab-2.webp" },
{ itemImageSrc: "/fab/fab-3.webp" },
{ itemImageSrc: "/fab/fab-4.webp" },
{ itemImageSrc: "/fab/fab-5.webp" },
{ itemImageSrc: "/fab/fab-6.webp" },
{ itemImageSrc: "/fab/fab-7.webp" },
{ itemImageSrc: "/fab/fab-8.webp" },
{ itemImageSrc: "/fab/fab-9.webp" },
],
},
{
title: "3D Renders",
desc: "A selection of renders done using various tools in Blender and beyond with many different styles. From photo-realistic to stylized. Covering a range of subjects from landscapes to portraits.",
coverImg: "/blender/tv-banner.webp",
stack: [
"/git.png",
"/blender.png",
"/substance.png",
],
links: [
{
label: "3D Gallery",
severity: "help",
link: "https://git.crabinteractive.com/explore/repos",
icon: "pi pi-images",
},
],
gallery: [
{ itemImageSrc: "/blender/blend-1.webp" },
{ itemImageSrc: "/blender/blend-2.webp" },
{ itemImageSrc: "/blender/blend-3.webp" },
{ itemImageSrc: "/blender/blend-4.webp" },
{ itemImageSrc: "/blender/blend-5.webp" },
{ itemImageSrc: "/blender/blend-6.webp" },
{ itemImageSrc: "/blender/tv-banner.webp" },
],
},
];

23
assets/css/main.css Normal file
View File

@ -0,0 +1,23 @@
/* If you write something like this: */
@import "primeicons/primeicons.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer tailwind-base, primevue, tailwind-utilities;
@layer tailwind-base {
@tailwind base;
}
@layer tailwind-utilities {
@tailwind components;
@tailwind utilities;
}
:root {
font-family: Dosis;
background-color: #292626;
}

View File

@ -0,0 +1,63 @@
<template>
<div class="flex bg-sky-400 min-w-[40%] min-h-[20%] rounded-xl p-4">
<div
class="flex flex-row justify-between gap-4 bg-zinc-900 grow p-4 rounded-xl"
>
<a href="mailto:me@kyleaustad.com" class="text-center">
<nuxt-img
class="rounded-xl transform transition-all duration-300 ease-out hover:scale-[1.2] max-w-[52px]"
src="/email.png"
></nuxt-img>
<p>Email</p>
</a>
<a href="https://www.linkedin.com/in/kyle-austad/" class="text-center">
<nuxt-img
class="rounded-xl transform transition-all duration-300 ease-out hover:scale-[1.2] max-w-[52px]"
src="/linkedin.png"
></nuxt-img>
<p>LinkedIn</p>
</a>
<a
href="https://github.com/kyaustad?tab=repositories"
class="text-center"
>
<nuxt-img
class="rounded-xl transform transition-all duration-300 ease-out hover:scale-[1.2] max-w-[52px]"
src="/github.png"
></nuxt-img>
<p>GitHub</p>
</a>
<a
href="https://git.crabinteractive.com/explore/repos"
class="text-center"
>
<nuxt-img
class="rounded-xl transform transition-all duration-300 ease-out hover:scale-[1.2] max-w-[52px]"
src="/gitea.webp"
></nuxt-img>
<p>Gitea</p>
</a>
</div>
</div>
<div
class="flex bg-sky-400 max-w-[60%] sm:max-w-[40%] min-h-[20%] rounded-xl p-2"
>
<div
class="flex flex-col justify-between gap-4 bg-zinc-900 text-center grow p-2 rounded-xl"
>
<h3 class="text-white font-base text-sm">
This website was made using Nuxt 3, PrimeVue, jsParticles, and
TailwindCSS.
</h3>
<h3 class="text-white font-base text-sm">
Thank you to the open source community!
</h3>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style></style>

View File

@ -0,0 +1,53 @@
<template>
<div class="card flex justify-center">
<Galleria
v-model:visible="displayBasic"
:fullScreen="true"
:showItemNavigators="false"
containerClass="bg-zinc-950 pt-6"
class="bg-zinc-900"
:value="images"
:responsiveOptions="responsiveOptions"
:showIndicators="true"
:showThumbnails="false"
:changeItemOnIndicatorHover="true"
>
<template #item="slotProps">
<nuxt-img
:src="slotProps.item.itemImageSrc"
class="max-w-[80%] max-h-[80%] block"
></nuxt-img>
</template>
</Galleria>
<div>
<Button
label="Images"
severity="info"
size="small"
icon="pi pi-eye"
@click="displayBasic = true"
/>
</div>
</div>
</template>
<script lang="ts" setup>
const displayBasic = ref(false);
const responsiveOptions = ref([
{
breakpoint: "1300px",
numVisible: 4,
},
{
breakpoint: "575px",
numVisible: 1,
},
]);
const props = defineProps<{
images: { itemImageSrc: string }[];
}>();
</script>
<style></style>

View File

@ -0,0 +1,16 @@
<template>
<div class="flex flex-col mt-24">
<h1 class="text-white font-bold text-4xl sm:text-5xl">{{ props.title }}</h1>
<div
class="fixed rounded-sm bg-red-500 ml-6 -z-[30] mt-6 min-h-[20px] min-w-[100px] sm:mt-8 sm:ml-8"
></div>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
title: string;
}>();
</script>
<style></style>

View File

@ -0,0 +1,55 @@
<template>
<div
class="p-[1px] max-w-[95px] items-center justify-center rounded-xl bg-gradient-to-br from-red-500 to-blue-500 transform transition-all duration-300 ease-out shadow-xl"
ref="avatar"
:style="{
transform: `scale(${scale}) rotateX(${tiltY}deg) rotateY(${tiltX}deg)`,
}"
@mousemove="handleMouseMove"
@mouseleave="resetRotation"
v-motion-fade-visible
>
<div
class="flex flex-col items-center justify-center gap-1 text-center p-4 rounded-xl bg-[#292626]"
>
<nuxt-img :src="props.img" class="max-w-[30px] md:max-w-[50px] rounded" />
<h3 class="mb-0 text-sm md:text-base">{{ props.name }}</h3>
</div>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
img: string;
name: string;
}>();
const avatar = ref<HTMLElement | null>(null);
const scale = ref(1);
const tiltX = ref(0);
const tiltY = ref(0);
const handleMouseMove = (event: MouseEvent) => {
if (avatar.value) {
const rect = avatar.value.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const offsetX = (event.clientX - centerX) / rect.width;
const offsetY = (event.clientY - centerY) / rect.height;
tiltX.value = offsetX * 80;
tiltY.value = offsetY * 80 * -1;
scale.value = 1.15;
}
};
const resetRotation = () => {
tiltX.value = 0;
tiltY.value = 0;
scale.value = 1;
};
</script>
<style></style>

View File

@ -0,0 +1,44 @@
<template>
<div class="flex flex-col m-2 text-center">
<h3 class="text-white font-base text-3xl sm:text-4xl mb-10">Skills</h3>
<p class="text-[7px] font-base md:text-[10px] mb-3">... hover me</p>
<div
class="grid grid-cols-1 justify-between md:grid-cols-4 gap-4 md:gap-x-8"
>
<!-- First Column -->
<div class="flex md:flex-col gap-4 items-center justify-around">
<SkillAvatar img="/js-logo.png" name="JavaScript" />
<SkillAvatar img="/node-logo.png" name="Node.js" />
<SkillAvatar img="/vue.png" name="Vue 3" />
<SkillAvatar img="/nuxt.png" name="Nuxt" />
</div>
<!-- Second Column -->
<div class="flex md:flex-col gap-4 items-center justify-between">
<SkillAvatar img="/html.png" name="HTML" />
<SkillAvatar img="/css.png" name="CSS" />
<SkillAvatar img="/tailwind.png" name="Tailwind CSS" />
<SkillAvatar img="/express.webp" name="express.js" />
</div>
<!-- Third Column -->
<div class="flex md:flex-col gap-4 items-center justify-around">
<SkillAvatar img="/docker.webp" name="Docker" />
<SkillAvatar img="/mongo.svg" name="MongoDB" />
<SkillAvatar img="/cpp.png" name="C++" />
<SkillAvatar img="/unreal.svg" name="Unreal Engine" />
</div>
<div class="flex w-full md:flex-col gap-4 items-center justify-between">
<SkillAvatar img="/blender.png" name="Blender" />
<SkillAvatar img="/git.png" name="Git" />
<SkillAvatar img="/substance.png" name="Substance Painter" />
<SkillAvatar img="/brain.png" name="A Brain" />
</div>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style></style>

View File

@ -0,0 +1,32 @@
<template>
<section id="about">
<div
class="snap-start gap-36 flex flex-col items-center mt-[25px] w-full min-h-[1800px]"
v-motion-fade-visible
>
<SectionHeader title="About" />
<img src="/headshot.png" class="max-w-[200px]" />
<div
class="text-center items-center flex max-w-[360px] border-red-500 border-2 p-7 rounded-xl"
>
<p class="text-white text-xl font-base">
Having a lifelong passion for learning and growth, I have been able to
develop skills through various verticals. I'm a passionate developer
for full-stack web applications using JavaScript, Vue, Nuxt, MongoDB,
or express.js. I believe technology should be used to empower
indivuduals and their creative visions, whether it is C++ and
Blueprints in Unreal Engine or the latest in Progressive Web Apps.
With a unique background in sales, I know how to be a self-starter,
and keep my excitement and passion for all things 'development'.
</p>
</div>
<SkillColumn />
</div>
</section>
</template>
<script lang="ts" setup></script>
<style></style>

View File

@ -0,0 +1,15 @@
<template>
<section id="contact">
<div
class="snap-start gap-36 flex flex-col items-center mt-[25px] w-full min-h-[1000px]"
>
<SectionHeader title="Contact Me" />
<ContactElement />
</div>
</section>
</template>
<script lang="ts" setup></script>
<style></style>

View File

@ -0,0 +1,64 @@
<template>
<div class="sticky z-[80] top-0 flex min-w-full">
<header
class="top-0 left-0 w-full h-[80px] bg-zinc-700 flex justify-center md:justify-end items-center px-4 shadow-lg"
>
<div class="inset-0 flex gap-10 justify-between items-center">
<Button
@click="scrollToSection('about')"
label="About"
:outlined="activeSection !== 'about'"
severity="info"
/>
<Button
@click="scrollToSection('work')"
label="My Work"
:outlined="activeSection !== 'work'"
severity="info"
/>
<Button
@click="scrollToSection('contact')"
label="Contact"
:outlined="activeSection !== 'contact'"
severity="info"
/>
</div>
</header>
</div>
</template>
<script lang="ts" setup>
const activeSection = ref<string | null>(null);
const scrollToSection = (sectionId: string) => {
const section = document.getElementById(sectionId);
if (section) {
section.scrollIntoView({ behavior: "smooth", block: "start" });
// window.scrollTo({ top: section.offsetTop, behavior: "smooth" });
}
};
const handleScroll = () => {
const sections = ["about", "work", "contact"];
for (const sectionId of sections) {
const section = document.getElementById(sectionId);
if (section) {
const rect = section.getBoundingClientRect();
if (rect.top <= 100 && rect.bottom >= 100) {
activeSection.value = sectionId;
break;
}
}
}
};
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
</script>
<style></style>

View File

@ -0,0 +1,16 @@
<template>
<section id="work">
<div class="snap-start gap-36 flex flex-col items-center mt-[25px] w-full">
<SectionHeader title="My Work" />
<LazyWorkCard v-for="entry in workEntries" :entry="entry">
<ImageGallery :images="entry.gallery" />
</LazyWorkCard>
</div>
</section>
</template>
<script lang="ts" setup>
import { workEntries } from "~/assets/WorkEntries";
</script>
<style></style>

83
components/WorkCard.vue Normal file
View File

@ -0,0 +1,83 @@
<template>
<div
class="w-[90%] h-[550px] md:h-[450px] md:w-[80%] lg:w-[60%] bg-gradient-to-tr from-red-500 to-red-400 flex flex-col sm:flex-row rounded-xl justify-between items-center"
>
<div
class="rounded-xl flex flex-col sm:max-w-[60%] align-middle items-center justify-between grow hover:scale-[0.98] m-2 p-2 bg-zinc-800 h-[95%] transform transition-all duration-300 ease-out shadow-xl text-center"
>
<h3 class="text-white text-wrap mb-1 mt-0 font-bold text-lg md:text-2xl">
{{ entry.title }}
</h3>
<div
class="max-h-[300px] flex justify-center hover:scale-[1.02] transform transition-all duration-300 ease-out"
>
<a :href="entry.coverImg">
<nuxt-img
:src="`${entry.coverImg}`"
class="aspect-auto max-h-[95%]"
/>
</a>
</div>
<div
class="max-h-[18px] sm:max-h-[26px] md:max-h-[32px] w-full md:mb-2 align-middle flex justify-around"
>
<!-- <h3 class="text-white font-base text-xs mt-0">Tech Stack:</h3> -->
<nuxt-img
:src="`${img}`"
class="aspect-auto"
v-for="img in entry.stack"
/>
</div>
</div>
<div
class="rounded-xl flex flex-col justify-around sm:max-w-[40%] hover:scale-[0.98] m-2 p-2 bg-zinc-800 h-[95%] transform transition-all duration-300 ease-out shadow-xl text-center"
>
<p class="text-white text-wrap m-2 font-base text-base">
{{ entry.desc }}
</p>
<div class="flex w-full justify-around">
<Button
severity="help"
icon="pi pi-link"
@click="showAlert = true"
size="small"
label="Links"
/>
<Dialog
v-model:visible="showAlert"
modal
:header="`${entry.title} Links`"
:style="{ width: '25rem' }"
>
<div
class="flex items-center justify-center gap-4 mb-8"
v-for="item in entry.links"
>
<a :href="item.link">
<Button
:severity="item.severity"
:label="item.label"
:icon="item.icon"
/></a>
</div>
</Dialog>
<slot />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { WorkEntry } from "~/types/WorkEntry";
const props = defineProps<{
entry: WorkEntry;
}>();
const showAlert = ref(false);
</script>
<style></style>

32
dockerfile Normal file
View File

@ -0,0 +1,32 @@
# Use the official Node.js image as the base image
FROM node:20-alpine AS builder
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and package-lock.json (or yarn.lock)
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Build the Nuxt application
RUN npm run build
# Use a smaller image for the final stage
FROM node:20-alpine AS runner
# Set the working directory
WORKDIR /app
# Copy only the necessary files from the builder stage
COPY --from=builder /app/.output /app/.output
# Expose the port Nuxt runs on (default is 3000)
EXPOSE 3000
# Start the Nuxt server
CMD ["node", "/app/.output/server/index.mjs"]

163
layouts/default.vue Normal file
View File

@ -0,0 +1,163 @@
<template>
<div class="relative min-h-screen items-center justify-center text-center">
<!-- Text Container -->
<div
class="absolute inset-0 z-[50] items-center justify-center text-center flex flex-col"
v-motion-pop
>
<h1 class="text-white text-4xl md:text-5xl font-base">Hi,</h1>
<div class="flex">
<h1 class="text-white text-4xl md:text-5xl font-base">my name is</h1>
<h1 class="text-blue-500 text-4xl md:text-5xl font-bold ml-4">Kyle</h1>
<h1 class="text-white text-4xl md:text-5xl font-base">.</h1>
</div>
<h1 class="text-white text-3xl md:text-4xl font-base mt-10 mb-6">
I like to code things.
</h1>
<Button
@click="handleClick"
class="mt-10 min-w-[20%]"
label="See My Work"
severity="info"
raised
size="large"
icon="pi pi-chevron-down"
>
</Button>
</div>
<div
class="relative min-h-screen min-w-full flex bg-stone-900 shadow-md"
v-motion-fade
>
<!-- Particles -->
<NuxtParticles
id="tsparticles"
:options="particleOptions"
class="min-w-full"
>
</NuxtParticles>
</div>
</div>
<!-- Main Content -->
<main class="pt-[0px]">
<slot />
</main>
</template>
<script lang="ts" setup>
import type { ISourceOptions as ParticlesOptions } from "@tsparticles/engine";
const router = useRouter();
const handleClick = () => {
router.push("/#about");
};
const particleOptions: ParticlesOptions = {
autoPlay: true,
background: {
color: "#1c1917", // Black background
},
backgroundMask: {
enable: false, // No background mask
},
clear: true,
detectRetina: true,
fpsLimit: 60,
fullScreen: {
enable: false,
zIndex: -100, // Ensure particles are behind other content
},
interactivity: {
events: {
onClick: {
enable: true,
mode: "push", // Add particles on click
},
onHover: {
enable: true,
mode: ["bubble", "grab", "connect", "trail"], // Repulse particles on hover
},
},
modes: {
push: {
quantity: 4, // Number of particles to add on click
},
repulse: {
distance: 100, // Distance of repulsion
duration: 0.4,
},
attract: {
distance: 150, // Distance to attract particles
duration: 0.4, // Duration of the attraction effect
speed: 0.3, // Speed of attraction
},
trail: {
delay: 2, // Delay between trail particles
quantity: 1, // Number of particles in the trail
},
slow: {
factor: 0.5, // Speed reduction factor (0.5 = 50% slower)
radius: 200, // Radius of the slowdown effect
},
grab: {
distance: 200, // Distance to grab particles
links: {
opacity: 0.5, // Opacity of the connecting lines
},
},
connect: {
distance: 220, // Maximum distance to connect particles
links: {
opacity: 0.5, // Opacity of the connecting lines
},
},
bubble: {
distance: 200, // Distance from the cursor to activate the effect
size: 10, // Size increase of particles
duration: 2, // Duration of the effect
opacity: 0.8, // Opacity change during the effect
},
},
},
particles: {
color: {
value: "#ffffff", // White particles
},
links: {
color: "#ff9090", // White links
distance: 150, // Maximum distance between linked particles
enable: true, // Enable links between particles
opacity: 0.25, // Link opacity
width: 0.85, // Link width
},
move: {
enable: true,
speed: 0.45, // Movement speed of particles
},
number: {
density: {
enable: true,
// Density of particles in the canvas
},
value: 160, // Number of particles
},
opacity: {
value: 0.5, // Particle opacity
},
shape: {
type: "circle", // Circular particles
},
size: {
value: { min: 1, max: 2 }, // Random size between 1 and 3
},
},
pauseOnBlur: true,
pauseOnOutsideViewport: true,
smooth: true,
themes: [],
zLayers: 1,
};
</script>
<style></style>

44
nuxt.config.ts Normal file
View File

@ -0,0 +1,44 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import Aura from '@primeuix/themes/aura'
export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
modules: [
'@primevue/nuxt-module',
'@nuxt/fonts',
'@nuxtjs/tailwindcss',
'@vueuse/nuxt',
'@vueuse/motion/nuxt',
'nuxt-particles',
'@nuxt/image'
],
devServer: {
host: '0.0.0.0'
},
css: [ '@/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
},
primevue: {
importTheme: { from: '@/themes/mytheme.js'},
options: {
theme: {
preset: Aura,
options: {
cssLayer: {
name: 'primevue',
order: 'tailwind-base, primevue, tailwind-utilities'
}
}
},
},
autoImport: true
},
particles: {},
})

13458
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/fonts": "^0.10.3",
"@nuxt/image": "^1.9.0",
"@nuxtjs/tailwindcss": "^6.13.1",
"@primeuix/themes": "^1.0.0",
"@vueuse/motion": "^2.2.6",
"@vueuse/nuxt": "^12.7.0",
"nuxt": "^3.15.4",
"primeicons": "^7.0.0",
"primevue": "^4.3.1",
"tailwindcss-primeui": "^0.5.1",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@primevue/nuxt-module": "^4.3.1",
"autoprefixer": "^10.4.20",
"nuxt-particles": "^0.3.0",
"postcss": "^8.5.3",
"tailwindcss": "^3.4.17"
}
}

80
pages/index.vue Normal file
View File

@ -0,0 +1,80 @@
<template>
<section
class="snap-y relative min-w-screen min-w-full flex flex-col items-center justify-center"
v-motion-roll-top
>
<!-- Particles Background -->
<div class="absolute inset-0 -z-[1] min-h-screen min-w-full" v-motion-fade>
<NuxtParticles
id="tsparticlesMain"
:options="particleOptions"
class="min-w-full min-h-full"
>
</NuxtParticles>
</div>
<!-- Content Container -->
<div
class="relative z-[50] flex flex-col items center justify-center w-full"
>
<TheHeaderBar />
<LazyTheAboutSection />
<LazyTheWorkSection />
<LazyTheContactSection />
</div>
</section>
</template>
<script lang="ts" setup>
import type { ISourceOptions as ParticlesOptions } from "@tsparticles/engine";
const particleOptions: ParticlesOptions = {
autoPlay: true,
background: {
color: "#292626", // Black background
},
backgroundMask: {
enable: false, // No background mask
},
clear: true,
detectRetina: true,
fpsLimit: 30,
fullScreen: {
enable: true,
zIndex: -1000, // Ensure particles are behind other content
},
particles: {
color: {
value: ["#ffe9e9", "#ffb2b2", "#7c94ff"], // White particles
},
move: {
enable: true,
speed: 0.05, // Movement speed of particles
},
number: {
density: {
enable: true,
// Density of particles in the canvas
},
value: 100, // Number of particles
},
opacity: {
value: 0.25, // Particle opacity
},
shape: {
type: "circle", // Circular particles
},
size: {
value: { min: 1, max: 3 }, // Random size between 1 and 3
},
},
pauseOnBlur: true,
pauseOnOutsideViewport: true,
smooth: true,
themes: [],
zLayers: 1,
};
</script>
<style></style>

BIN
public/blender.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/blender/blend-1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
public/blender/blend-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
public/blender/blend-3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
public/blender/blend-4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
public/blender/blend-5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/blender/blend-6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
public/brain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/cpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/css.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

101
public/css.svg Normal file
View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="102.3716mm"
height="144.49777mm"
viewBox="0 0 362.73401 511.99998"
id="svg3476"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="CSS3.svg">
<defs
id="defs3478" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="181.367"
inkscape:cy="256"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="704"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata3481">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-193.633,-276.3622)">
<g
id="g3013"
transform="translate(119,276.3622)">
<polygon
id="polygon2989"
points="437.367,100.62 404.321,470.819 255.778,512 107.644,470.877 74.633,100.62 "
style="fill:#264de4" />
<polygon
id="polygon2991"
points="376.03,447.246 404.27,130.894 256,130.894 256,480.523 "
style="fill:#2965f1" />
<polygon
id="polygon2993"
points="150.31,268.217 154.38,313.627 256,313.627 256,268.217 "
style="fill:#ebebeb" />
<polygon
id="polygon2995"
points="256,176.305 255.843,176.305 142.132,176.305 146.26,221.716 256,221.716 "
style="fill:#ebebeb" />
<polygon
id="polygon2997"
points="256,433.399 256,386.153 255.801,386.206 205.227,372.55 201.994,336.333 177.419,336.333 156.409,336.333 162.771,407.634 255.791,433.457 "
style="fill:#ebebeb" />
<path
id="path2999"
d="m 160,0 55,0 0,23 -32,0 0,23 32,0 0,23 -55,0 z"
inkscape:connector-curvature="0" />
<path
id="path3001"
d="m 226,0 55,0 0,20 -32,0 0,4 32,0 0,46 -55,0 0,-21 32,0 0,-4 -32,0 z"
inkscape:connector-curvature="0" />
<path
id="path3003"
d="m 292,0 55,0 0,20 -32,0 0,4 32,0 0,46 -55,0 0,-21 32,0 0,-4 -32,0 z"
inkscape:connector-curvature="0" />
<polygon
id="polygon3005"
points="311.761,313.627 306.49,372.521 255.843,386.191 255.843,433.435 348.937,407.634 349.62,399.962 360.291,280.411 361.399,268.217 369.597,176.305 255.843,176.305 255.843,221.716 319.831,221.716 315.699,268.217 255.843,268.217 255.843,313.627 "
style="fill:#ffffff" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/docker.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/email.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/express.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/fab/fab-1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/fab/fab-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
public/fab/fab-3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
public/fab/fab-4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
public/fab/fab-5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
public/fab/fab-6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
public/fab/fab-7.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
public/fab/fab-8.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
public/fab/fab-9.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
public/fab/fab-banner.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/fonts/Dosis-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/git.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/gitea.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
public/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
public/headshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
public/html.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/js-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/justCanvas/jc-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/justCanvas/jc-10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/justCanvas/jc-11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/justCanvas/jc-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/justCanvas/jc-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

BIN
public/justCanvas/jc-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

BIN
public/justCanvas/jc-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

BIN
public/justCanvas/jc-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
public/justCanvas/jc-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
public/justCanvas/jc-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
public/justCanvas/jc-9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
public/linkedin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/metro/m-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
public/metro/m-10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
public/metro/m-11.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

BIN
public/metro/m-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
public/metro/m-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

BIN
public/metro/m-4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

BIN
public/metro/m-5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
public/metro/m-6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
public/metro/m-7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
public/metro/m-8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
public/metro/m-9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

5
public/mongo.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" style="fill:#13aa52"/>
<path d="M648.86 449.44c-32.34-142.73-108.77-189.66-117-207.59-9-12.65-18.12-35.15-18.12-35.15-.15-.38-.39-1.05-.67-1.7-.93 12.65-1.41 17.53-13.37 30.29-18.52 14.48-113.54 94.21-121.27 256.37-7.21 151.24 109.25 241.36 125 252.85l1.79 1.27v-.11c.1.76 5 36 8.44 73.34H526a726.68 726.68 0 0 1 13-78.53l1-.65a204.48 204.48 0 0 0 20.11-16.45l.72-.65c33.48-30.93 93.67-102.47 93.08-216.53a347.07 347.07 0 0 0-5.05-56.76zM512.35 659.12s0-212.12 7-212.08c5.46 0 12.53 273.61 12.53 273.61-9.72-1.17-19.53-45.03-19.53-61.53z" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 818 B

BIN
public/node-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/nuxt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
public/robots.txt Normal file
View File

@ -0,0 +1 @@

BIN
public/substance.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/tailwind.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

5
public/unreal.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="400" height="400"><svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 399C309.904 399 399 309.904 399 200C399 90.096 309.904 0.999985 200 0.999985C90.096 0.999985 1 90.096 1 200C1 309.904 90.096 399 200 399ZM250.9 310.635C299.562 287.65 314.817 245.204 318.25 228.668C300.458 246.889 280.772 265.359 267.533 252.573C267.533 252.573 266.808 184.338 266.808 156.36C266.808 118.736 302.488 90.6961 302.488 90.6961C282.865 94.1911 259.302 101.178 234.29 125.723C232.033 127.962 229.894 130.313 227.879 132.772C217.161 124.567 203.271 127.026 203.271 127.026C210.777 131.149 218.268 143.157 218.268 153.098V250.813C218.268 250.813 201.903 265.21 189.291 265.21C186.449 265.232 183.642 264.572 181.104 263.288C178.567 262.004 176.375 260.132 174.711 257.828C173.713 256.525 172.886 255.098 172.252 253.584V132.859C168.524 135.934 155.925 138.469 155.925 117.555C155.925 104.561 165.287 89.073 181.959 79.5739C159.065 83.0657 137.623 92.9566 120.108 108.106C106.8 119.604 96.1562 133.867 88.9176 149.896C81.679 165.924 78.0161 183.337 78.1871 200.927C78.1871 200.927 90.379 162.834 105.649 159.314C107.872 158.729 110.204 158.67 112.456 159.146C114.707 159.622 116.815 160.614 118.615 162.05C120.416 163.483 121.852 165.318 122.816 167.408C123.78 169.497 124.243 171.782 124.165 174.08V263.525C124.165 272.591 118.32 274.572 112.919 274.476C109.259 274.218 105.643 273.537 102.142 272.445C113.292 287.535 127.782 299.845 144.476 308.408C161.17 316.972 179.621 321.558 198.383 321.81L231.358 288.664L250.9 310.635Z" fill="black"></path>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: invert(100%); } }
</style></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/vue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/wp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

3
server/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

18
tailwind.config.js Normal file
View File

@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
// tailwind.config.js
import PrimeUI from "tailwindcss-primeui";
export default {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
],
theme: {
extend: {},
},
plugins: [PrimeUI],
};

24
themes/mytheme.js Normal file
View File

@ -0,0 +1,24 @@
import { definePreset } from "@primeuix/themes";
import Aura from "@primeuix/themes/aura";
const MyPreset = definePreset(Aura, {
semantic: {
primary: {
50: "{red.50}",
100: "{red.100}",
200: "{red.200}",
300: "{red.300}",
400: "{red.400}",
500: "{red.500}",
600: "{red.600}",
700: "{red.700}",
800: "{red.800}",
900: "{red.900}",
950: "{red.950}",
},
},
});
export default {
preset: MyPreset,
};

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

8
types/WorkEntry.ts Normal file
View File

@ -0,0 +1,8 @@
export interface WorkEntry {
title: string
desc: string
coverImg: string
stack: string[]
links: {label: string; severity: string; link: string; icon?: string}[]
gallery: {itemImageSrc: string}[]
}