Init Commit
12
.dockerignore
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
android
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
README.md
|
||||||
|
.env
|
||||||
|
.vscode
|
||||||
|
.output
|
||||||
|
.nuxt
|
24
.gitignore
vendored
Normal 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
@ -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
@ -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. Don’t 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
@ -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;
|
||||||
|
}
|
63
components/ContactElement.vue
Normal 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>
|
53
components/ImageGallery.vue
Normal 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>
|
16
components/SectionHeader.vue
Normal 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>
|
55
components/SkillAvatar.vue
Normal 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>
|
44
components/SkillColumn.vue
Normal 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>
|
32
components/TheAboutSection.vue
Normal 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>
|
15
components/TheContactSection.vue
Normal 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>
|
64
components/TheHeaderBar.vue
Normal 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>
|
16
components/TheWorkSection.vue
Normal 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
@ -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
@ -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
@ -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
@ -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
33
package.json
Normal 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
@ -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
After Width: | Height: | Size: 2.2 KiB |
BIN
public/blender/blend-1.webp
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
public/blender/blend-2.webp
Normal file
After Width: | Height: | Size: 119 KiB |
BIN
public/blender/blend-3.webp
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
public/blender/blend-4.webp
Normal file
After Width: | Height: | Size: 156 KiB |
BIN
public/blender/blend-5.webp
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public/blender/blend-6.webp
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
public/blender/tv-banner.webp
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
public/brain.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
public/cpp.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
public/css.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
101
public/css.svg
Normal 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
After Width: | Height: | Size: 2.6 KiB |
BIN
public/email.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
public/express.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/fab/fab-1.webp
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
public/fab/fab-2.webp
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
public/fab/fab-3.webp
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
public/fab/fab-4.webp
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
public/fab/fab-5.webp
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
public/fab/fab-6.webp
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
public/fab/fab-7.webp
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
public/fab/fab-8.webp
Normal file
After Width: | Height: | Size: 213 KiB |
BIN
public/fab/fab-9.webp
Normal file
After Width: | Height: | Size: 233 KiB |
BIN
public/fab/fab-banner.webp
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
public/fonts/Dosis-Bold.ttf
Normal file
BIN
public/fonts/Dosis-Light.ttf
Normal file
BIN
public/fonts/Dosis-Regular.ttf
Normal file
BIN
public/git.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
public/gitea.webp
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
public/github.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
public/headshot.png
Normal file
After Width: | Height: | Size: 163 KiB |
BIN
public/html.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/js-logo.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/justCanvas/jc-1.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/justCanvas/jc-10.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
public/justCanvas/jc-11.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/justCanvas/jc-2.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/justCanvas/jc-3.png
Normal file
After Width: | Height: | Size: 388 KiB |
BIN
public/justCanvas/jc-4.png
Normal file
After Width: | Height: | Size: 374 KiB |
BIN
public/justCanvas/jc-5.png
Normal file
After Width: | Height: | Size: 522 KiB |
BIN
public/justCanvas/jc-6.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/justCanvas/jc-7.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/justCanvas/jc-8.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/justCanvas/jc-9.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/justCanvas/justcanvas-banner.png
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
public/linkedin.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/metro/m-1.jpg
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
public/metro/m-10.jpg
Normal file
After Width: | Height: | Size: 276 KiB |
BIN
public/metro/m-11.jpg
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
public/metro/m-2.jpg
Normal file
After Width: | Height: | Size: 267 KiB |
BIN
public/metro/m-3.jpg
Normal file
After Width: | Height: | Size: 248 KiB |
BIN
public/metro/m-4.jpg
Normal file
After Width: | Height: | Size: 294 KiB |
BIN
public/metro/m-5.jpg
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
public/metro/m-6.jpg
Normal file
After Width: | Height: | Size: 214 KiB |
BIN
public/metro/m-7.jpg
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
public/metro/m-8.jpg
Normal file
After Width: | Height: | Size: 236 KiB |
BIN
public/metro/m-9.jpg
Normal file
After Width: | Height: | Size: 376 KiB |
BIN
public/metro/metro-banner.jpg
Normal file
After Width: | Height: | Size: 265 KiB |
5
public/mongo.svg
Normal 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
After Width: | Height: | Size: 2.7 KiB |
BIN
public/nuxt.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
1
public/robots.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
BIN
public/substance.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
public/tailwind.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
5
public/unreal.svg
Normal 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
After Width: | Height: | Size: 1.9 KiB |
BIN
public/wp.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
3
server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
18
tailwind.config.js
Normal 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
@ -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
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
8
types/WorkEntry.ts
Normal 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}[]
|
||||||
|
}
|