Init Commit
This commit is contained in:
63
components/ContactElement.vue
Normal file
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
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
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
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
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
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
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
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
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
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>
|
Reference in New Issue
Block a user