81 Commits

Author SHA1 Message Date
Rakantor fd712b0631 chore: update gh-pages CNAME 2026-04-07 17:53:42 +02:00
Rakantor 7063f3843d fix: align vuetify and i18n config with upgraded toolchain 2026-04-07 17:52:56 +02:00
Rakantor d9e5f22909 chore: update packages to fix known security vulnerabilities 2026-04-07 17:52:24 +02:00
Manuel fe10b0e017 Update README.md 2025-07-28 21:52:26 +02:00
Rakantor 146e455bb1 Update deps 2024-09-04 20:41:58 +02:00
Rakantor 7a57617da5 Update deps 2024-06-19 23:45:40 +02:00
Rakantor 4054a46516 Update deps 2024-06-14 00:40:54 +02:00
Rakantor 851873bc5a Update deps 2024-06-04 23:26:32 +02:00
Rakantor 15dccd4939 Update deps 2024-05-23 23:27:44 +02:00
Rakantor 76ebcdcca4 Update deps 2024-05-18 01:20:43 +02:00
Rakantor c65b242a8d Update deps 2024-05-14 19:00:59 +02:00
Rakantor f9b58db61f Update some titles & descriptions 2024-03-19 23:34:45 +01:00
Rakantor 0076490dc4 Update deps 2024-03-16 21:04:53 +01:00
Rakantor ea95202f6b Update deps 2024-03-13 23:22:15 +01:00
Rakantor cf03ba9662 Update deps 2024-03-02 23:35:24 +01:00
Rakantor e00fb94b08 Update deps 2023-11-01 12:20:33 +01:00
Rakantor ab3dd42df6 Update deps 2023-10-26 10:58:03 +02:00
Rakantor 7f9b2c2f53 Update deps 2023-10-12 20:06:55 +02:00
Rakantor af944028e9 Update deps 2023-10-07 21:05:39 +02:00
Rakantor a5a5921594 Update deps 2023-10-01 23:51:39 +02:00
Rakantor 6a5b1b755a Update deps 2023-09-26 20:20:24 +02:00
Rakantor d8af58a8da Update deps 2023-09-24 20:38:52 +02:00
Rakantor a1e8b47e43 Update deps 2023-09-22 22:24:07 +02:00
Rakantor a594bc4aff Update deps 2023-09-20 11:15:50 +02:00
Rakantor 4ed5885f00 Update deps 2023-09-11 19:23:24 +02:00
Rakantor baea30a690 Update deps 2023-09-05 23:43:47 +02:00
Rakantor f5bf32edcf Update deps 2023-08-31 21:52:04 +02:00
Rakantor 114d4cb33e Update deps 2023-08-26 22:31:06 +02:00
Rakantor 4ecd3ccaed Update deps 2023-08-23 13:28:02 +02:00
Rakantor 96b3a237fb Update deps 2023-08-09 13:40:21 +02:00
Rakantor 77ae8be73d Update deps 2023-08-06 11:52:33 +02:00
Rakantor 176a6b73d4 Update deps 2023-07-31 21:56:02 +02:00
Rakantor 5d06ae512b Update deps 2023-07-28 01:24:44 +02:00
Rakantor fc8ab04cde Update deps 2023-07-22 17:40:50 +02:00
Rakantor 517200f02e Update deps 2023-07-19 12:32:18 +02:00
Rakantor e9f966d2a6 Update deps 2023-07-15 15:36:53 +02:00
Rakantor 07e4045198 Update deps 2023-07-14 12:00:24 +02:00
Rakantor 33db600f64 Update deps 2023-07-12 22:06:13 +02:00
Rakantor 256abf7e8c Update deps 2023-07-07 16:34:07 +02:00
Rakantor 289b336dfb Update deps 2023-07-04 09:14:46 +02:00
Rakantor ce459e97f0 Update some headlines 2023-06-29 21:57:27 +02:00
Rakantor aa0fc2579b Add node.js to skillset 2023-06-29 21:56:41 +02:00
Rakantor 0fb7cb24ed Update deps 2023-06-29 09:54:13 +02:00
Rakantor cf786b52ff Update deps 2023-06-25 14:25:51 +02:00
Rakantor 4d6a372e6d Update deps 2023-06-22 10:21:42 +02:00
Rakantor 5576fc8327 Update deps 2023-06-17 19:18:13 +02:00
Rakantor ea9306aac4 Update deps 2023-06-14 15:32:42 +02:00
Rakantor 84f52baf2a Update deps 2023-06-10 23:02:53 +02:00
Rakantor 253205baf3 Add Docker, remove WP from skillset 2023-06-09 00:31:58 +02:00
Rakantor 9e773db293 Update deps 2023-06-07 13:51:38 +02:00
Rakantor d52bfe7353 Update deps 2023-06-01 20:41:10 +02:00
Rakantor 2ac82b4ad0 Update deps 2023-05-27 19:30:20 +02:00
Rakantor 1d30db8b63 Remove unnecessary comment 2023-05-26 17:16:56 +02:00
Rakantor ace2829614 Add { "type": "module" } 2023-05-26 17:16:19 +02:00
Rakantor 5367707f17 Update deps 2023-05-24 12:58:45 +02:00
Rakantor 8dae14735e Update deps 2023-05-22 23:59:22 +02:00
Rakantor 2b3b639498 Update deps 2023-05-20 13:20:07 +02:00
Rakantor 4d164f83ab Update deps 2023-05-17 19:02:50 +02:00
Rakantor 7a3f41e1b4 Update deps 2023-05-16 11:03:21 +02:00
Rakantor 962ad5d436 Remove package @nuxtjs/axios 2023-05-11 23:58:39 +02:00
Rakantor 1353d9199c Update deps 2023-05-09 22:59:13 +02:00
Rakantor 048660c9df Add tech and update portfolio 2023-05-08 16:42:01 +02:00
Rakantor 06d92e19c4 Update @nuxtjs/i18n 2023-05-08 16:41:28 +02:00
Rakantor c48c782154 Refactor variables 2023-05-08 16:10:37 +02:00
Rakantor cca9967af6 Migrate environment vars to Nuxt runtimeConfig 2023-05-08 15:51:31 +02:00
Rakantor f39e02c5b7 Add horizontal projects list layout 2023-05-08 01:09:25 +02:00
Rakantor 3111daaf02 Fix carousel controls obscuring the image 2023-05-08 00:51:54 +02:00
Rakantor 1d35d4926b Create ProjectCard.vue 2023-05-08 00:48:23 +02:00
Rakantor ab50b61295 Change page content max width 2023-05-07 21:45:55 +02:00
Rakantor 97f3580f69 Add page transitions 2023-05-07 14:00:04 +02:00
Rakantor 83d6722d8c Update deps 2023-05-07 11:40:46 +02:00
Rakantor 2a49820127 Refactor variable names 2023-05-05 01:26:27 +02:00
Rakantor 273e224146 Change avatar imgs 2023-05-05 00:49:36 +02:00
Rakantor 1b83170b50 Update deps 2023-05-04 13:22:52 +02:00
Rakantor 406982f124 Update deps 2023-05-02 15:29:52 +02:00
Rakantor 97288d3208 Make years of expierence an dynamic value 2023-04-30 18:41:30 +02:00
Rakantor d2b04f2081 VCard: add border by default 2023-04-30 18:40:38 +02:00
Rakantor ce0225af9a Change default v-overlay bg color 2023-04-29 14:28:15 +02:00
Rakantor 48e8007717 Cleanup code 2023-04-29 14:27:21 +02:00
Rakantor bccd82fcf9 Add close button 2023-04-29 14:27:04 +02:00
Rakantor 3f2284a17f Change default v-carousel color 2023-04-29 14:12:16 +02:00
19 changed files with 10882 additions and 5830 deletions
+7 -2
View File
@@ -1,3 +1,8 @@
# mave.dev # Personal Portfolio v1
My personal website/portfolio. Built with [Nuxt](https://nuxt.com) and [Vuetify](https://vuetifyjs.com). Hosted with [GitHub Pages](https://pages.github.com/). My first personal website/portfolio. Built with [Nuxt](https://nuxt.com) and [Vuetify](https://vuetifyjs.com). Hosted with [GitHub Pages](https://pages.github.com/).
## Newer Iterations
- [v3](https://github.com/Rakantor/personal-portfolio-v3)
- [v2](https://github.com/Rakantor/personal-portfolio-v2)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+34
View File
@@ -33,6 +33,17 @@ p a {
} }
} }
.page-content {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.v-overlay__scrim {
background: rgb(var(--v-theme-background)) !important;
opacity: 0.9 !important;
}
.link { .link {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
@@ -42,3 +53,26 @@ p a {
color: rgb(var(--v-theme-primary)); color: rgb(var(--v-theme-primary));
} }
} }
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.2s;
}
.slide-left-enter-from {
opacity: 0;
transform: translate(50px, 0);
}
.slide-left-leave-to {
opacity: 0;
transform: translate(-50px, 0);
}
.slide-right-enter-from {
opacity: 0;
transform: translate(-50px, 0);
}
.slide-right-leave-to {
opacity: 0;
transform: translate(50px, 0);
}
+19 -9
View File
@@ -1,14 +1,21 @@
<template> <template>
<v-dialog v-model="show"> <v-dialog v-model="show">
<v-btn
position="absolute"
location="top right"
icon="mdi-close"
@click.prevent="show = false"
style="z-index: 5000;"
></v-btn>
<v-carousel <v-carousel
:model-value="index" :model-value="index"
:show-arrows="images.length > 1" :show-arrows="images.length > 1"
hide-delimiters
class="ma-auto" class="ma-auto"
:height="carouselHeight" height="90vh"
> >
<v-carousel-item v-for="image of images" :key="image" <v-carousel-item v-for="image of images" :key="image"
:src="image" :src="image"
class="img-height"
></v-carousel-item> ></v-carousel-item>
</v-carousel> </v-carousel>
</v-dialog> </v-dialog>
@@ -21,11 +28,14 @@ export default {
show: false, show: false,
images: [], images: [],
index: 0 index: 0
}), })
computed: {
carouselHeight () {
return window.innerHeight * 0.9
}
}
} }
</script> </script>
<style scoped lang="scss">
@use 'vuetify/settings';
.img-height {
height: calc(90vh - settings.$carousel-controls-size);
}
</style>
+209
View File
@@ -0,0 +1,209 @@
<template>
<ImageCarousel ref="imageCarousel" />
<!-- HORIZONTAL VIEW CARD -->
<v-card v-if="view === 'horizontal' && $vuetify.display.smAndUp" class="page-content">
<div class="d-flex flex-no-wrap justify-space-between">
<v-card-text class="my-auto">
<v-avatar size="200" rounded="lg">
<v-carousel
v-model="carouselIndex"
:show-arrows="images.length > 1 ? 'hover' : false"
hide-delimiters
width="200"
height="200"
>
<v-carousel-item v-for="image of images" :key="image"
:src="`https://${$config.public.cdn}${image}`"
cover
@click="showImageCarousel"
></v-carousel-item>
</v-carousel>
</v-avatar>
</v-card-text>
<div class="d-flex flex-column" style="width: 100%;">
<v-card-item>
<span v-if="overline" class="text-overline text-primary">{{ $t(overline) }}</span>
<v-card-title class="d-flex font-weight-bold">
<span v-if="links.length > 0">
<a :href="`https://${links[0].url}`" target="_blank" class="link">
{{ title }}
</a>
</span>
<span v-else>{{ title }}</span>
<v-spacer></v-spacer>
<v-btn
v-for="link in [...links].reverse()" :key="link.url"
variant="text"
:icon="link.icon"
density="comfortable"
color="on-surface"
:href="`https://${link.url}`"
target="_blank"
class="link"
/>
</v-card-title>
<v-card-subtitle>{{ subtitle }}</v-card-subtitle>
</v-card-item>
<v-card-text>{{ $t(description) }}</v-card-text>
<v-spacer></v-spacer>
<v-card-actions class="d-flex flex-row flex-wrap justify-left align-center">
<img v-for="t of tech" :key="technologies[t].title"
:src="generateBadgen(technologies[t].title, technologies[t].icon)"
:height="20"
class="ma-1"
/>
</v-card-actions>
</div>
</div>
</v-card>
<!-- VERTICAL VIEW CARD -->
<v-card v-else height="100%" class="d-flex flex-column">
<v-carousel
v-model="carouselIndex"
:show-arrows="images.length > 1 ? 'hover' : false"
:hide-delimiters="images.length <= 1"
height="auto"
>
<v-carousel-item v-for="image of images" :key="image"
:src="`https://${$config.public.cdn}${image}`"
@click="showImageCarousel"
></v-carousel-item>
</v-carousel>
<v-card-item>
<span v-if="overline" class="text-overline text-primary">{{ $t(overline) }}</span>
<v-card-title class="d-flex font-weight-bold">
<span v-if="links.length > 0">
<a :href="`https://${links[0].url}`" target="_blank" class="link">
{{ title }}
</a>
</span>
<span v-else>{{ title }}</span>
<v-spacer></v-spacer>
<v-btn
v-for="link in [...links].reverse()" :key="link.url"
variant="text"
:icon="link.icon"
density="comfortable"
color="on-surface"
:href="`https://${link.url}`"
target="_blank"
class="link"
/>
</v-card-title>
<v-card-subtitle>{{ subtitle }}</v-card-subtitle>
</v-card-item>
<v-card-text>{{ $t(description) }}</v-card-text>
<v-spacer></v-spacer>
<v-card-actions class="d-flex flex-row flex-wrap justify-center align-center">
<img v-for="t of tech" :key="technologies[t].title"
:src="generateBadgen(technologies[t].title, technologies[t].icon)"
:height="20"
class="ma-1"
/>
<!-- colored badges
<img v-for="t of tech" :key="technologies[t].title"
:src="`https://badgen.net/badge/icon/${technologies[t].title}?icon=${technologies[t].icon}${technologies[t].color}&label`"
:height="20"
class="ma-1"
/>
-->
<!-- colored badges w/ links to brand websites
<a v-for="t of tech" :key="technologies[t].title"
:href="`https://${technologies[t].projectUrl}`"
target="_blank"
>
<img v-for="t of tech" :key="technologies[t].title"
:src="`https://badgen.net/badge/icon/${technologies[t].title}?icon=${technologies[t].icon}${technologies[t].color}&label`"
:height="20"
class="ma-1"
/>
</a>
-->
</v-card-actions>
</v-card>
</template>
<script>
import { badgen } from 'badgen'
import { siAndroid, siAmazonaws, siMicrosoftazure, siFirebase, siGithub,
siHeroku, siCoffeescript, siYoutubegaming, siMysql, siNuxtdotjs, siPhp,
siJavascript, siVuedotjs, siVuetify, siIbmwatson, siWordpress, siTypescript } from 'simple-icons'
export default {
name: 'PortfolioPage',
props: {
view: {
type: String,
required: true
},
title: String,
subtitle: String,
description: String,
overline: String,
tech: {
type: Array,
default: []
},
images: {
type: Array,
default: []
},
links: {
type: Array,
default: []
}
},
data: () => ({
technologies: {
android: { title: 'Android', color: '3DDC84', icon: siAndroid },
aws: { title: 'AWS', color: '232F3E', icon: siAmazonaws },
azure: { title: 'Azure', color: '0078D4', icon: siMicrosoftazure },
firebase: { title: 'Firebase', color: 'FFCA28', icon: siFirebase },
ghpages: { title: 'GH Pages', color: '222222', icon: siGithub },
heroku: { title: 'Heroku', color: '430098', icon: siHeroku },
java: { title: 'Java', color: 'FF7800', icon: siCoffeescript },
js: { title: 'JavaScript', color: 'F7DF1E', icon: siJavascript },
libgdx: { title: 'libGDX', color: '990000', icon: siYoutubegaming },
mysql: { title: 'MySQL', color: '4479A1', icon: siMysql },
nuxt: { title: 'Nuxt', color: '00DC82', icon: siNuxtdotjs },
php: { title: 'PHP', color: '777BB4', icon: siPhp },
ts: { title: 'TypeScript', color: '3178C6', icon: siTypescript },
vue: { title: 'Vue', color: '4FC08D', icon: siVuedotjs },
vuetify: { title: 'Vuetify', color: '1867C0', icon: siVuetify },
watson: { title: 'Watson', color: 'BE95FF', icon: siIbmwatson },
wordpress: { title: 'WordPress', color: '21759B', icon: siWordpress }
},
carouselIndex: []
}),
methods: {
/*
* https://github.com/badgen/badgen
*/
generateBadgen (label, icon) {
// const iconColor = icon.hex
const iconColor = 'FFFFFF' // white
const iconSvg = icon.svg.replace('<path ', `<path fill="#${iconColor}" `) // Change SVG icon color
const svg = badgen({
label: '',
status: label,
// color: iconColor,
color: this.$vuetify.theme.current.colors.backgroundTertiary.slice(1), // Remove leading '#' from hex string
style: 'classic', // Can be 'classic' or 'flat'
icon: `data:image/svg+xml;utf8,${encodeURIComponent(iconSvg)}`
})
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
},
showImageCarousel () {
this.$refs.imageCarousel.images = this.images.map(i => `https://${this.$config.public.cdn+i}`)
this.$refs.imageCarousel.index = this.carouselIndex
this.$refs.imageCarousel.show = true
}
}
}
</script>
+1 -1
View File
@@ -11,7 +11,7 @@ cd .output/public
touch .nojekyll touch .nojekyll
# if you are deploying to a custom domain # if you are deploying to a custom domain
echo 'mave.dev' > CNAME echo 'v1.mave.dev' > CNAME
git init git init
git add -A git add -A
+98 -21
View File
@@ -1,42 +1,119 @@
export default defineI18nConfig(nuxt => ({ export default defineI18nConfig(() => ({
legacy: false, legacy: false,
locale: 'en', locale: 'en',
messages: { messages: {
en: { en: {
bi: 'BI', // Business Informatics
bioBody: 'My passion for programming ignited in 2007, as I delved into SQL in an attempt to set up\
a private server for my favorite {mmorpg} - a venture that proved successful.\
Eager to dive deeper into the world of coding, I attended a {htl}\
specializing in IT in 2009, where I learned the basics of C, Java, HTML and CSS.\
Some years later, I took my passion further by heading to university, where I got my degree in {bi},\
specializing in web and mobile app development.\
Over the past {yearsOfExp} years, self-study and hands-on experience with personal projects\
have been the cornerstone of my learning journey.',
bioTitle: 'My Journey as a Developer',
bioSubtitle: 'Here\'s some of the tech that I\'ve worked with before:',
getInTouch: 'Get in touch',
greeting: 'Hey. I\'m Manuel.', greeting: 'Hey. I\'m Manuel.',
iuGamerApp: `An Android app designed to help a group of board game enthusiasts\ headerAbout: 'About',
headerContact: 'Contact',
headerHome: 'Home',
headerWork: 'Projects',
introduction: 'I\'m a Software Developer from Vienna, Austria.\
I have a passion for crafting a wide range of applications.\
Feel free to explore my {portfolio} to see some of my projects.',
imprint: 'Imprint',
iuGamerApp: 'An Android app designed to help a group of board game enthusiasts\
better organize their regular evening game sessions.\ better organize their regular evening game sessions.\
Users will be reliably informed about the time and location of the next appointment.\ Users will be reliably informed about the time and location of the next appointment.\
They can suggest games, vote on suggested games, rate past appointments,\ They can suggest games, vote on suggested games, rate past appointments,\
and communicate with each other via an integrated chat feature.`, and communicate with each other via an integrated chat feature.',
iuQuizApp: 'An online quiz system that supports distance learning students at IU in solidifying their learning content in preparation for the exam. It enables students to cooperatively and collaboratively find answers to subject-related questions. The focus is particularly on working together and playing/learning together. Similar to the popular app Quizduel, students can, but do not have to, play against each other.', iuQuizApp: 'An online quiz system that supports distance learning students at IU\
in solidifying their learning content in preparation for the exam.\
It enables students to cooperatively and collaboratively find answers to subject-related questions.\
The focus is particularly on working together and playing/learning together.\
Similar to the popular app Quizduel, students can, but do not have to, play against each other.',
lang: 'Languages',
languagesFrameworks: 'Languages, Frameworks & Libraries',
os: 'Operating Systems',
personalWebsite: 'My first portfolio website.', personalWebsite: 'My first portfolio website.',
pmb: 'A 2D game inspired by the 2000s Pokémon games. Built from scratch! Features include animated battles (online multiplayer), overworld sprites, custom maps built with Tiled, game sound, 3 difficulty levels and so much more.', pmb: 'A 2D game inspired by the 2000s Pokémon games. Built from scratch!\
toriiJava: 'A Japanese vocabulary learning tool that harnesses the power of spaced repetition to make memorizing new words a breeze.', Features include animated NPCs, objects and battles (including online multiplayer),\
toriiWeb: `Version 2 of my Japanese vocabulary learning tool brings significant enhancements\ custom maps built in Tiled, game sound, 3 difficulty levels and so much more.',
over its predecessor. New features include offline functionality (thanks to utilizing IndexedDB),\ portfolio: 'portfolio',
an integrated dictionary for single-click word addition,\ portfolioTitle: 'Featured Projects',
additional review methods for learned words,\ toolsPlatforms: 'Technologies & Tools',
a review forecast chart for planning study sessions,\ toriiInfo: '25,000+ registered users!',
and improved word search and statistics for better user insights.` toriiJava: 'A Japanese vocabulary learning tool that harnesses the power of spaced repetition\
to make memorizing new words a breeze. It combines a straightforward interface with robust features.\
Designed for both casual learners and those preparing for the Japanese Language Proficiency Test (JLPT),\
Torii offers specialized vocabulary lists and review methods.\
Key features include audio reviews, font randomization, progress tracking,\
and automatic cloud synchronization.',
toriiWeb: 'Version 2 of my Japanese vocabulary learning tool introduces significant enhancements.\
Offline functionality is now available (through the utilization of IndexedDB).\
The web app features an integrated dictionary for efficient word addition,\
expanded review methods for enhancing vocabulary retention,\
a review forecast chart for effective study session planning,\
and enhancements to the word search and statistics functions for improved user insights.'
}, },
de: { de: {
bi: 'WI', // Wirtschaftsinformatik
bioBody: 'Den Einstieg in die Programmierung habe ich 2007 gemacht, als ich mich mit SQL beschäftigt habe,\
in dem Versuch, einen Privatserver für mein Lieblings-{mmorpg} aufzusetzen - mit Erfolg.\
Nach dem Gymnasium habe ich mich 2009 dazu entschlossen, eine auf IT spezialisierte {htl} zu besuchen,\
wo ich die Grundlagen in C, Java, HTML and CSS gelernt habe.\
Ein paar Jahre später habe ich {bi} studiert - Wahlpflichtfächer Web- und Mobile-App-Development.\
Den Großteil meines Wissens habe ich mir im Zuge persönlicher Projekte in den letzten {yearsOfExp} Jahren\
autodidaktisch angeeignet.',
bioTitle: 'Mein Werdegang als Developer',
bioSubtitle: 'Einige der Technologien, mit denen ich gearbeitet habe:',
getInTouch: 'Kontaktieren',
greeting: 'Hey. Ich bin Manuel.', greeting: 'Hey. Ich bin Manuel.',
headerAbout: 'Über mich',
headerContact: 'Kontakt',
headerHome: 'Home',
headerWork: 'Projekte',
introduction: 'Ich bin ein Software Developer aus Wien, Österreich.\
Ich entwickle ganz unterschiedliche Applikationen.\
Die wichtigsten Projekte habe ich in meinem {portfolio} aufgelistet.',
imprint: 'Impressum',
iuGamerApp: `Eine Android App, die einer Gruppe von Brettspielfans dabei helfen soll,\ iuGamerApp: `Eine Android App, die einer Gruppe von Brettspielfans dabei helfen soll,\
ihre regelmäßigen abendlichen Spieltermine besser zu organisieren.\ ihre regelmäßigen abendlichen Spieltermine besser zu organisieren.\
Benutzer werden zuverlässig über Zeit & Ort des nächsten Termins informiert.\ Benutzer werden zuverlässig über Zeit & Ort des nächsten Termins informiert.\
Sie können Spiele vorschlagen und über Vorschläge abstimmen, vergangene Termine bewerten\ Sie können Spiele vorschlagen und über Vorschläge abstimmen, vergangene Termine bewerten\
und sich über einen integrierten Chat gegenseitig Nachrichten zukommen lassen.`, und sich über einen integrierten Chat gegenseitig Nachrichten zukommen lassen.`,
iuQuizApp: 'Ein Online-Quizsystem, das Studierende des Fernstudiums der IU bei der Festigung der Lerninhalte zur Vorbereitung auf die Klausur unterstützt. Es ermöglicht Studierenden, kooperativ und kollaborativ Antworten zu fachlichen Fragen zu finden. Das miteinander bzw. das gemeinsame Spielen/Erarbeiten steht dabei besonders im Fokus. Ähnlich wie bei der populären App Quizduell kann, jedoch muss nicht gegeneinander gespielt werden.', iuQuizApp: 'Ein Online-Quizsystem, das Fernstudenten der IU bei\
der Festigung der Lerninhalte zur Vorbereitung auf die Klausur unterstützt.\
Es ermöglicht Studierenden, kooperativ und kollaborativ Antworten zu fachlichen Fragen zu finden.\
Das miteinander bzw. das gemeinsame Spielen/Erarbeiten steht dabei besonders im Fokus.\
Ähnlich wie bei der populären App Quizduell kann, jedoch muss nicht gegeneinander gespielt werden.',
lang: 'Sprachen',
languagesFrameworks: 'Sprachen, Frameworks & Bibliotheken',
os: 'Betriebssysteme',
personalWebsite: 'Meine erste Portfolio-Website.', personalWebsite: 'Meine erste Portfolio-Website.',
pmb: 'A 2D game inspired by the 2000s Pokémon games. Built from scratch! Features animated battles (online multiplayer), overworld sprites, custom maps, sound, 3 difficulty levels and so much more.', pmb: 'Ein 2D Game, inspiriert von den Pokémon Game Boy Spielen der 2000er.\
toriiJava: 'DEU', Funktionen umfassen animierte NPCs, Objekte und Kämpfe (inklusive Online-Multiplayer),\
toriiWeb: `Version 2 meiner Vokabeltrainer-App bringt bedeutende Verbesserungen\ einzigartige Maps erstellt mit Tiled, Sound, 3 Schwierigkeitsstufen und jede Menge mehr.',
gegenüber seinem Vorgänger. Neue Funktionen umfassen Offline-Funktionalität (dank der Nutzung von IndexedDB),\ portfolio: 'Portfolio',
ein integriertes Wörterbuch für das Hinzufügen neuer Wörter mit nur einem Klick,\ portfolioTitle: 'Ausgewählte Projekte',
zusätzliche Überprüfungsmethoden für gelernte Vokabeln,\ toolsPlatforms: 'Technologien & Tools',
ein Vorschau-Diagramm zur Planung von Lernsitzungen sowie\ toriiInfo: '25.000+ registrierte Benutzer!',
verbesserte Wortsuche und Statistiken.` toriiJava: 'Eine japanische Vokabeltrainer-App, die die Spaced Repetition Lernmethode nutzt, um\
den Lernprozess neuer Wörter so effektiv und effizient wie möglich zu gestalten.\
Die App verbindet eine simple Benutzeroberfläche mit leistungsstarken Funktionen.\
Torii eignet sich dank spezialisierter Vokabellisten und verschiedenen Wiederholungsmethoden\
sowohl für Casual-Learners als auch zur Vorbereitung auf den\
Japanese Language Proficiency Test (JLPT).\
Zu den wichtigsten Funktionen zählen Audio-Reviews, Font-Randomization, Progress-Tracking,\
und automatische Cloud-Synchronisation.',
toriiWeb: `Version 2 meiner Vokabeltrainer-App bietet deutliche Verbesserungen im Vergleich zur\
vorherigen Version. Zu den neuen Funktionen zählen die Offline-Nutzung durch Einsatz von IndexedDB,\
ein integriertes Wörterbuch, das das Hinzufügen neuer Wörter mit einem einzigen Klick ermöglicht,\
erweiterte Überprüfungsoptionen für bereits gelernte Vokabeln,\
ein Vorschau-Diagramm zur effektiven Planung von Lerneinheiten sowie optimierte Funktionen für\
die Wortsuche und Statistiken zur besseren Nachverfolgung des Lernfortschritts.`
} }
} }
})) }))
+5 -5
View File
@@ -28,7 +28,7 @@
outlined outlined
color="primary" color="primary"
prepend-icon="mdi-email-outline" prepend-icon="mdi-email-outline"
:href="`mailto:<${$myEmail}>`" :href="`mailto:<${$config.public.myEmail}>`"
> >
Contact Contact
</v-btn> </v-btn>
@@ -43,10 +43,10 @@
style="cursor: pointer" style="cursor: pointer"
@click.native="$router.push('/')" @click.native="$router.push('/')"
> >
<img src="~/assets/avatar_transparent.png" width="55" /> <img src="~/assets/avatar_blueish.png" width="55" />
</v-avatar> </v-avatar>
</template> </template>
<!-- v-app-bar-title class="text-subtitle-1" v-text="$myName" /--> <!-- v-app-bar-title class="text-subtitle-1" v-text="$config.public.myName" /-->
<v-spacer /> <v-spacer />
<v-app-bar-nav-icon <v-app-bar-nav-icon
v-if="smAndDown" v-if="smAndDown"
@@ -56,7 +56,7 @@
<v-btn :ripple="false" to="/">{{ $t('headerHome') }}</v-btn> <v-btn :ripple="false" to="/">{{ $t('headerHome') }}</v-btn>
<v-btn :ripple="false" to="/bio">{{ $t('headerAbout') }}</v-btn> <v-btn :ripple="false" to="/bio">{{ $t('headerAbout') }}</v-btn>
<v-btn :ripple="false" to="/portfolio">{{ $t('headerWork') }}</v-btn> <v-btn :ripple="false" to="/portfolio">{{ $t('headerWork') }}</v-btn>
<v-btn :ripple="false" :href="`mailto:<${$myEmail}>`">{{ $t('headerContact') }}</v-btn> <v-btn :ripple="false" :href="`mailto:<${$config.public.myEmail}>`">{{ $t('headerContact') }}</v-btn>
</v-btn-toggle> </v-btn-toggle>
</v-app-bar> </v-app-bar>
<v-main> <v-main>
@@ -86,7 +86,7 @@
</v-col> </v-col>
<v-col cols="4" class="text-center my-auto"> <v-col cols="4" class="text-center my-auto">
<span class="text-caption text-no-wrap"> <span class="text-caption text-no-wrap">
&copy; {{ new Date().getFullYear() }} {{ $myName }} &copy; {{ new Date().getFullYear() }} {{ $config.public.myName }}
</span> </span>
</v-col> </v-col>
<v-col cols="4" class="my-auto" :class="$vuetify.display.mobile ? 'text-right' : 'text-center'"> <v-col cols="4" class="my-auto" :class="$vuetify.display.mobile ? 'text-right' : 'text-center'">
+8
View File
@@ -0,0 +1,8 @@
export default defineNuxtRouteMiddleware((to, from) => {
if (typeof to.meta.pageTransition === 'object' && typeof from.meta.pageTransition === 'object') {
if (typeof to.meta.id !== 'number' || typeof from.meta.id !== 'number') return
const transition = to.meta.id > from.meta.id ? 'slide-left' : 'slide-right'
to.meta.pageTransition.name = from.meta.pageTransition.name = transition
}
})
+12 -128
View File
@@ -2,23 +2,26 @@ import vuetify from 'vite-plugin-vuetify'
// https://v3.nuxtjs.org/api/configuration/nuxt.config // https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({ export default defineNuxtConfig({
runtimeConfig: {
public: {
cdn: 'd29l6egdxvgg9c.cloudfront.net/',
myName: 'Manuel Veigel',
myEmail: 'maveigel@gmail.com',
}
},
ssr: false, ssr: false,
css: [ css: [
'vuetify/styles', 'vuetify/styles',
'~/assets/variables.scss' '~/assets/variables.scss'
], ],
vite: { vite: {
ssr: { ssr: {
noExternal: ['vuetify'] // add the vuetify vite plugin noExternal: ['vuetify'] // add the vuetify vite plugin
} }
}, },
build: { build: {
transpile: ['vuetify'] transpile: ['vuetify']
}, },
app: { app: {
head: { head: {
// titleTemplate: '%s | Home', // titleTemplate: '%s | Home',
@@ -40,14 +43,10 @@ export default defineNuxtConfig({
], ],
} }
}, },
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [], plugins: [],
// Modules: https://go.nuxtjs.dev/config-modules // Modules: https://go.nuxtjs.dev/config-modules
modules: [ modules: [
// https://go.nuxtjs.dev/axios
// ['@nuxtjs/axios', { proxyHeaders: false }],
'@nuxtjs/i18n', '@nuxtjs/i18n',
async (options, nuxt) => { async (options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', config => nuxt.hooks.hook('vite:extendConfig', config =>
@@ -56,126 +55,11 @@ export default defineNuxtConfig({
) )
} }
], ],
i18n: { i18n: {
// { vueI18n: './i18n.config.ts' } restructureDir: false,
// TODO: Revert to config file once the bug in 8.0.0-beta.11 has been fixed vueI18n: './i18n.config.ts',
// https://github.com/nuxt-modules/i18n/issues/1990 bundle: {
vueI18n: { optimizeTranslationDirective: false,
legacy: false, },
locale: 'en',
messages: {
en: {
bi: 'BI', // Business Informatics
bioBody: 'My interest in programming was sparked in 2007 when I began tinkering with SQL\
in an attempt to setup a private server for my favorite {mmorpg} (it was a success!)\
Eager to dive deeper into the world of coding, I attended a {htl}\
specializing in IT in 2009, where I learned C, Java, HTML and CSS.\
Some years later, I pursued a degree in {bi} with a specialization\
in Web and App development. I have acquired the majority of my knowledge through self-teaching\
while working on personal projects over the past ~15 years.',
bioTitle: 'My Journey as a Developer',
bioSubtitle: 'Here\'s some of the tech that I\'ve worked with before:',
getInTouch: 'Get in touch',
greeting: 'Hey. I\'m Manuel.',
headerAbout: 'About',
headerContact: 'Contact',
headerHome: 'Home',
headerWork: 'Projects',
introduction: 'I\'m a Software Developer based in Vienna, Austria.\
I develop various types of applications.\
Explore my {portfolio} to view a showcase of my projects.',
imprint: 'Imprint',
iuGamerApp: 'An Android app designed to help a group of board game enthusiasts\
better organize their regular evening game sessions.\
Users will be reliably informed about the time and location of the next appointment.\
They can suggest games, vote on suggested games, rate past appointments,\
and communicate with each other via an integrated chat feature.',
iuQuizApp: 'An online quiz system that supports distance learning students at IU\
in solidifying their learning content in preparation for the exam.\
It enables students to cooperatively and collaboratively find answers to subject-related questions.\
The focus is particularly on working together and playing/learning together.\
Similar to the popular app Quizduel, students can, but do not have to, play against each other.',
lang: 'Languages',
languagesFrameworks: 'Languages & Frameworks',
os: 'Operating Systems',
personalWebsite: 'My first portfolio website.',
pmb: 'A 2D game inspired by the 2000s Pokémon games. Built from scratch!\
Features include animated NPCs, objects and battles (including online multiplayer),\
custom maps built in Tiled, game sound, 3 difficulty levels and so much more.',
portfolio: 'portfolio',
portfolioTitle: 'Some of my projects',
toolsPlatforms: 'Tools & Platforms',
toriiJava: 'A Japanese vocabulary learning tool that harnesses the power of spaced repetition\
to make memorizing new words a breeze. It combines a straightforward interface with robust features.\
Designed for both casual learners and those preparing for the Japanese Language Proficiency Test (JLPT),\
Torii offers specialized vocabulary lists and review methods.\
Key features include audio reviews, font randomization, progress tracking,\
and automatic cloud synchronization.',
toriiWeb: 'Version 2 of my Japanese vocabulary learning tool brings significant enhancements\
over its predecessor. New features include offline functionality (thanks to utilizing IndexedDB),\
an integrated dictionary for single-click word addition,\
additional review methods for learned words,\
a review forecast chart for planning study sessions,\
and improved word search and statistics for better user insights.'
},
de: {
bi: 'WI', // Wirtschaftsinformatik
bioBody: 'Den Einstieg in die Programmierung habe ich 2007 gemacht, als ich mich mit SQL beschäftigt habe,\
in dem Versuch, einen Privatserver für mein Lieblings-{mmorpg} aufzusetzen (erfolgreich!)\
Nach dem Gymnasium habe ich mich 2009 dazu entschlossen, eine auf IT spezialisierte {htl} zu besuchen,\
wo ich die Grundlagen in C, Java, HTML and CSS gelernt habe.\
Ein paar Jahre später habe ich {bi} studiert - Wahlpflichtfächer Web- und App-Development.\
Den Großteil meines Wissens habe ich mir im Zuge persönlicher Projekte in den letzten ~15 Jahren\
autodidaktisch angeeignet.',
bioTitle: 'Mein Werdegang als Developer',
bioSubtitle: 'Einige der Technologien, mit denen ich gearbeitet habe:',
getInTouch: 'Kontaktieren',
greeting: 'Hey. Ich bin Manuel.',
headerAbout: 'Über mich',
headerContact: 'Kontakt',
headerHome: 'Home',
headerWork: 'Projekte',
introduction: 'Ich bin ein Software Developer aus Wien, Österreich.\
Ich entwickle ganz unterschiedliche Applikationen.\
Die wichtigsten Projekte habe ich in meinem {portfolio} aufgelistet.',
imprint: 'Impressum',
iuGamerApp: `Eine Android App, die einer Gruppe von Brettspielfans dabei helfen soll,\
ihre regelmäßigen abendlichen Spieltermine besser zu organisieren.\
Benutzer werden zuverlässig über Zeit & Ort des nächsten Termins informiert.\
Sie können Spiele vorschlagen und über Vorschläge abstimmen, vergangene Termine bewerten\
und sich über einen integrierten Chat gegenseitig Nachrichten zukommen lassen.`,
iuQuizApp: 'Ein Online-Quizsystem, das Fernstudenten der IU bei\
der Festigung der Lerninhalte zur Vorbereitung auf die Klausur unterstützt.\
Es ermöglicht Studierenden, kooperativ und kollaborativ Antworten zu fachlichen Fragen zu finden.\
Das miteinander bzw. das gemeinsame Spielen/Erarbeiten steht dabei besonders im Fokus.\
Ähnlich wie bei der populären App Quizduell kann, jedoch muss nicht gegeneinander gespielt werden.',
lang: 'Sprachen',
languagesFrameworks: 'Sprachen & Frameworks',
os: 'Betriebssysteme',
personalWebsite: 'Meine erste Portfolio-Website.',
pmb: 'Ein 2D Game, inspiriert von den Pokémon Game Boy Spielen der 2000er.\
Funktionen umfassen animierte NPCs, Objekte und Kämpfe (inklusive Online-Multiplayer),\
einzigartige Maps erstellt mit Tiled, Sound, 3 Schwierigkeitsstufen und jede Menge mehr.',
portfolio: 'Portfolio',
portfolioTitle: 'Ausgewählte Projekte',
toolsPlatforms: 'Tools & Plattformen',
toriiJava: 'Eine japanische Vokabeltrainer-App, die die Spaced Repetition Lernmethode nutzt, um\
den Lernprozess neuer Wörter so effektiv und effizient wie möglich zu gestalten.\
Die App verbindet eine simple Benutzeroberfläche mit leistungsstarken Funktionen.\
Torii eignet sich dank spezialisierter Vokabellisten und verschiedenen Wiederholungsmethoden\
sowohl für Casual-Learners als auch zur Vorbereitung auf den\
Japanese Language Proficiency Test (JLPT).\
Zu den wichtigsten Funktionen zählen Audio-Reviews, Font-Randomization, Progress-Tracking,\
und automatische Cloud-Synchronisation.',
toriiWeb: `Version 2 meiner Vokabeltrainer-App bringt bedeutende Verbesserungen\
gegenüber seinem Vorgänger. Neue Funktionen umfassen Offline-Funktionalität (dank der Nutzung von IndexedDB),\
ein integriertes Wörterbuch für das Hinzufügen neuer Wörter mit nur einem Klick,\
zusätzliche Überprüfungsmethoden für gelernte Vokabeln,\
ein Vorschau-Diagramm zur Planung von Lernsessions sowie\
verbesserte Wortsuche und Statistiken.`
}
}
}
} }
}) })
+10210 -5330
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -2,6 +2,7 @@
"name": "personal-portfolio", "name": "personal-portfolio",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev", "dev": "nuxt dev",
@@ -10,17 +11,16 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.0.96", "@mdi/font": "^7.4.47",
"@nuxtjs/i18n": "8.0.0-beta.10", "@nuxtjs/i18n": "^9.5.6",
"nuxt": "^3.4.0" "nuxt": "^3.21.2"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.13.6", "badgen": "^3.2.3",
"badgen": "^3.2.2", "lodash-es": "^4.18.1",
"lodash-es": "^4.17.21", "sass": "^1.99.0",
"sass": "^1.56.0", "simple-icons": "^8.15.0",
"simple-icons": "^8.11.0", "vite-plugin-vuetify": "^2.1.3",
"vite-plugin-vuetify": "^1.0.0", "vuetify": "^3.12.5"
"vuetify": "^3.0.0"
} }
} }
+128 -98
View File
@@ -1,60 +1,74 @@
<template> <template>
<v-row> <div class="page-content">
<v-col cols="12"> <v-row>
<span class="text-h5 text-md-h4 text-high-emphasis">{{ $t('bioTitle') }}</span> <v-col cols="12">
</v-col> <span class="text-h5 text-md-h4 text-high-emphasis">
<v-col cols="12"> {{ $t('bioTitle') }}
<v-sheet color="transparent" class="text-medium-emphasis"> </span>
<i18n-t </v-col>
keypath="bioBody" <v-col cols="12">
tag="p" <v-sheet color="transparent" class="text-medium-emphasis">
> <i18n-t keypath="bioBody" tag="p">
<template v-slot:mmorpg> <template v-slot:mmorpg>
<NuxtLink :href="mmorpgWikiUrl" target="_blank">MMORPG</NuxtLink> <NuxtLink :href="mmorpgWikiUrl" target="_blank">MMORPG</NuxtLink>
</template> </template>
<template v-slot:htl> <template v-slot:htl>
<NuxtLink :href="htlWikiUrl" target="_blank">HTL</NuxtLink> <NuxtLink :href="htlWikiUrl" target="_blank">HTL</NuxtLink>
</template> </template>
<template v-slot:bi> <template v-slot:bi>
<NuxtLink :href="biWikiUrl" target="_blank">{{ $t('bi') }}</NuxtLink> <NuxtLink :href="biWikiUrl" target="_blank">{{ $t('bi') }}</NuxtLink>
</template> </template>
</i18n-t> <template v-slot:yearsOfExp>
<br/> {{ yearsOfExp }}
<p>{{ $t('bioSubtitle') }}</p> </template>
</v-sheet> </i18n-t>
</v-col> <br/>
<v-col v-for="set, ind in sets" :key="ind" cols="12"> <p>{{ $t('bioSubtitle') }}</p>
<v-card> </v-sheet>
<v-card-item> </v-col>
<v-card-title>{{ $t(set.title) }}</v-card-title> <v-col v-for="set, ind in sets" :key="ind" cols="12">
</v-card-item> <v-card>
<v-card-text class="text-center"> <v-card-item>
<v-row> <v-card-title>{{ $t(set.title) }}</v-card-title>
<v-col v-for="(v, i) in set.data" :key="i" cols="12"> </v-card-item>
<v-row justify="center"> <v-card-text class="text-center">
<v-col cols="auto" v-for="(value, index) in v" :key="index"> <v-row>
<v-btn <v-col v-for="(v, i) in set.data" :key="i" cols="12">
variant="text" <v-row justify="center">
:size="getButtonSize(value.level)" <v-col cols="auto" v-for="(value, index) in v" :key="index">
color="primary" <v-btn
class="mx-1" variant="text"
:href="`https://${value.url}`" :size="brandIconSize(value.emphasis)"
target="_blank" color="primary"
> class="mx-1"
<v-icon :icon="value.icon" :size="getButtonSize(value.level) - 10"></v-icon> :href="`https://${value.url}`"
</v-btn> target="_blank"
<span class="text-overline text-primary d-flex flex-column ma-0 pa-0">{{ value.title }}</span> >
</v-col> <v-icon :icon="value.icon" :size="brandIconSize(value.emphasis) - 10"></v-icon>
</v-row> </v-btn>
</v-col> <span class="text-overline text-primary d-flex flex-column ma-0 pa-0">
</v-row> {{ value.title }}
</v-card-text> </span>
</v-card> </v-col>
</v-col> </v-row>
</v-row> </v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
</template> </template>
<script> <script>
definePageMeta({
id: 2,
pageTransition: {
name: 'slide',
mode: 'out-in'
}
})
import _groupBy from 'lodash-es/groupBy' import _groupBy from 'lodash-es/groupBy'
export default { export default {
@@ -64,76 +78,92 @@ export default {
htlWikiUrl: 'https://en.wikipedia.org/wiki/H%C3%B6here_Technische_Lehranstalt', htlWikiUrl: 'https://en.wikipedia.org/wiki/H%C3%B6here_Technische_Lehranstalt',
biWikiUrl: 'https://en.wikipedia.org/wiki/Business_informatics', biWikiUrl: 'https://en.wikipedia.org/wiki/Business_informatics',
languages: [ languages: [
{ title: 'Java', icon: 'mdi-language-java', url: 'java.com', level: 3 }, { title: 'Java', icon: 'mdi-language-java', url: 'java.com', emphasis: 'high' },
{ title: 'C/C++', icon: 'mdi-language-cpp', url: 'isocpp.org', level: 1 }, { title: 'C/C++', icon: 'mdi-language-cpp', url: 'isocpp.org', emphasis: 'low' },
{ title: 'Python', icon: 'mdi-language-python', url: 'python.org', level: 1 }, { title: 'Python', icon: 'mdi-language-python', url: 'python.org', emphasis: 'low' },
{ title: 'JavaScript (ES6+)', icon: 'mdi-language-javascript', url: 'javascript.com', level: 3 }, { title: 'JavaScript (ES6+)', icon: 'mdi-language-javascript', url: 'javascript.com', emphasis: 'high' },
{ title: 'TypeScript', icon: 'mdi-language-typescript', url: 'typescriptlang.org/', level: 1 }, { title: 'TypeScript', icon: 'mdi-language-typescript', url: 'typescriptlang.org/', emphasis: 'low' },
{ title: 'HTML', icon: 'mdi-language-html5', url: 'html.spec.whatwg.org/multipage', level: 2 }, { title: 'HTML', icon: 'mdi-language-html5', url: 'html.spec.whatwg.org/multipage', emphasis: 'medium' },
{ title: 'CSS', icon: 'mdi-language-css3', url: 'w3.org/Style/CSS', level: 2 }, { title: 'CSS', icon: 'mdi-language-css3', url: 'w3.org/Style/CSS', emphasis: 'medium' },
{ title: 'PHP', icon: 'mdi-language-php', url: 'php.net', level: 2 }, { title: 'PHP', icon: 'mdi-language-php', url: 'php.net', emphasis: 'medium' },
{ title: 'SQL', icon: 'mdi-database', url: 'iso.org/standard/63555.html', level: 2 }, { title: 'SQL', icon: 'mdi-database', url: 'iso.org/standard/63555.html', emphasis: 'medium' },
{ title: 'Lua', icon: 'mdi-language-lua', url: 'lua.org', level: 2 } { title: 'Lua', icon: 'mdi-language-lua', url: 'lua.org', emphasis: 'medium' }
], ],
frameworks: [ frameworks: [
{ title: 'Android', icon: 'mdi-android', url: 'android.com', level: 2 }, { title: 'Android', icon: 'mdi-android', url: 'android.com', emphasis: 'medium' },
{ title: 'Vue.js', icon: 'mdi-vuejs', url: 'vuejs.org', level: 3 }, { title: 'Vue.js', icon: 'mdi-vuejs', url: 'vuejs.org', emphasis: 'high' },
{ title: 'Nuxt', icon: 'mdi-nuxt', url: 'nuxt.com', level: 3 }, { title: 'Nuxt', icon: 'mdi-nuxt', url: 'nuxt.com', emphasis: 'high' },
{ title: 'Vuetify', icon: 'mdi-vuetify', url: 'vuetifyjs.com', level: 3 }, { title: 'Vuetify', icon: 'mdi-vuetify', url: 'vuetifyjs.com', emphasis: 'high' },
{ title: 'Bootstrap', icon: 'mdi-bootstrap', url: 'bootstrap-vue.org', level: 1 }, { title: 'Bootstrap', icon: 'mdi-bootstrap', url: 'bootstrap-vue.org', emphasis: 'low' },
{ title: 'libGDX', icon: 'mdi-alpha-l-box-outline', url: 'libgdx.com', level: 1 } { title: 'libGDX', icon: 'mdi-alpha-l-box-outline', url: 'libgdx.com', emphasis: 'low' }
], ],
tech: [ tech: [
{ title: 'Amazon Web Services', icon: 'mdi-aws', url: 'aws.amazon.com', level: 3 }, { title: 'Amazon Web Services', icon: 'mdi-aws', url: 'aws.amazon.com', emphasis: 'high' },
{ title: 'Firebase', icon: 'mdi-firebase', url: 'firebase.google.com', level: 3 }, { title: 'Firebase', icon: 'mdi-firebase', url: 'firebase.google.com', emphasis: 'high' },
{ title: 'Azure', icon: 'mdi-microsoft-azure', url: 'azure.microsoft.com', level: 1 }, { title: 'Node.js', icon: 'mdi-nodejs', url: 'nodejs.org', emphasis: 'medium' },
{ title: 'Heroku', icon: 'brands:heroku', url: 'heroku.com', level: 1 }, { title: 'Docker', icon: 'mdi-docker', url: 'docker.com', emphasis: 'medium' },
{ title: 'WordPress', icon: 'mdi-wordpress', url: 'wordpress.com', level: 1 }, { title: 'Azure', icon: 'mdi-microsoft-azure', url: 'azure.microsoft.com', emphasis: 'low' },
{ title: 'Unity', icon: 'mdi-unity', url: 'unity.com', level: 1 }, { title: 'Heroku', icon: 'brands:heroku', url: 'heroku.com', emphasis: 'low' },
{ title: 'Unreal Engine', icon: 'mdi-unreal', url: 'unrealengine.com', level: 1 } // { title: 'WordPress', icon: 'mdi-wordpress', url: 'wordpress.com', emphasis: 'low' },
{ title: 'Unity', icon: 'mdi-unity', url: 'unity.com', emphasis: 'low' },
{ title: 'Unreal Engine', icon: 'mdi-unreal', url: 'unrealengine.com', emphasis: 'low' }
], ],
os: [ os: [
{ title: 'Windows', icon: 'mdi-microsoft-windows', url: 'microsoft.com/windows', level: 3 }, { title: 'Windows', icon: 'mdi-microsoft-windows', url: 'microsoft.com/windows', emphasis: 'medium' },
{ title: 'Apple macOS', icon: 'brands:apple', url: 'apple.com/macos', level: 3 }, { title: 'macOS', icon: 'brands:apple', url: 'apple.com/macos', emphasis: 'medium' },
{ title: 'Linux Mint', icon: 'mdi-linux-mint', url: 'linuxmint.com', level: 3 }, { title: 'Linux Mint', icon: 'mdi-linux-mint', url: 'linuxmint.com', emphasis: 'medium' },
{ title: 'Fedora Workstation', icon: 'mdi-fedora', url: 'getfedora.org', level: 3 }, { title: 'Fedora', icon: 'mdi-fedora', url: 'getfedora.org', emphasis: 'medium' },
{ title: 'Arch Linux', icon: 'mdi-arch', url: 'archlinux.org', level: 1 }, { title: 'Arch Linux', icon: 'mdi-arch', url: 'archlinux.org', emphasis: 'low' },
{ title: 'Ubuntu', icon: 'mdi-ubuntu', url: 'ubuntu.com', level: 1 }, { title: 'Ubuntu', icon: 'mdi-ubuntu', url: 'ubuntu.com', emphasis: 'low' },
{ title: 'Apple iOS', icon: 'mdi-apple-ios', url: 'apple.com/ios', level: 1 }, { title: 'iOS', icon: 'mdi-apple-ios', url: 'apple.com/ios', emphasis: 'low' },
{ title: 'Android', icon: 'mdi-android', url: 'android.com', level: 1 }, { title: 'Android', icon: 'mdi-android', url: 'android.com', emphasis: 'low' },
] ]
}), }),
computed: { computed: {
sets () { sets () {
const languages = _groupBy([...this.languages, ...this.frameworks], e => e.level) const languages = _groupBy([...this.languages, ...this.frameworks], e => e.emphasis)
// const frameworks = _groupBy(this.frameworks, e => e.level) // const frameworks = _groupBy(this.frameworks, e => e.emphasis)
const tech = _groupBy(this.tech, e => e.level) const tech = _groupBy(this.tech, e => e.emphasis)
const os = _groupBy(this.os, e => e.level) const os = _groupBy(this.os, e => e.emphasis)
function compareEmphasis (x, y) {
const map = { low: 1, medium: 2, high: 3 }
return map[y] - map[x]
}
return [ return [
{ {
title: 'languagesFrameworks', title: 'languagesFrameworks',
data: Object.keys(languages).sort().reverse().map(e => languages[e]) data: Object.keys(languages).sort(compareEmphasis).map(e => languages[e])
}, },
{ {
title: 'toolsPlatforms', title: 'toolsPlatforms',
data: Object.keys(tech).sort().reverse().map(e => tech[e]) data: Object.keys(tech).sort(compareEmphasis).map(e => tech[e])
}, },
{ {
title: 'os', title: 'os',
data: Object.keys(os).sort().reverse().map(e => os[e]) data: Object.keys(os).sort(compareEmphasis).map(e => os[e])
} }
] ]
}, },
skills () { yearsOfExp () {
return [...this.languages, ...this.frameworks, ...this.tech, ...this.os] // startingYear = The year I started my first truly personal project (predecessor of Menacing Blue).
// Fun fact: I used Notepad++ and javac in cmd for coding rather than an IDE for like the first 6-12 months.
const startingYear = 2011
const currentYear = new Date().getFullYear()
return currentYear - startingYear
} }
}, },
methods: { methods: {
getButtonSize (level) { brandIconSize (emphasis) {
switch (level) { const large = this.$vuetify.display.smAndDown ? 64 : 96
case 1: return this.$vuetify.display.smAndDown ? 32 : 48 const medium = this.$vuetify.display.smAndDown ? 48 : 64
case 2: return this.$vuetify.display.smAndDown ? 48 : 64 const small = this.$vuetify.display.smAndDown ? 32 : 48
default: return this.$vuetify.display.smAndDown ? 64 : 96
switch (emphasis) {
case 'low': return small
case 'medium': return medium
default: return large
} }
} }
} }
+12 -5
View File
@@ -1,13 +1,14 @@
<template> <template>
<div>
<v-card class="mb-2"> <v-card class="mb-2">
<v-card-item> <v-card-item>
<v-card-title>Offenlegung nach § 25 Mediengesetz</v-card-title> <v-card-title>Offenlegung nach § 25 Mediengesetz</v-card-title>
</v-card-item> </v-card-item>
<v-card-text> <v-card-text>
<strong>Medieninhaber:</strong><br /> <strong>Medieninhaber:</strong><br />
{{ $myName }}<br /> {{ $config.public.myName }}<br />
Wien, Österreich<br /> Wien, Österreich<br />
{{ $myEmail }}<br /> {{ $config.public.myEmail }}<br />
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
Zweck dieser Website: Präsentation des Medieninhabers. Zweck dieser Website: Präsentation des Medieninhabers.
@@ -45,11 +46,11 @@
<p>Sollten Sie Fragen zum Datenschutz oder zur Verarbeitung personenbezogener Daten haben, finden Sie nachfolgend die <p>Sollten Sie Fragen zum Datenschutz oder zur Verarbeitung personenbezogener Daten haben, finden Sie nachfolgend die
Kontaktdaten der verantwortlichen Person bzw. Stelle:<br /> Kontaktdaten der verantwortlichen Person bzw. Stelle:<br />
<span style="font-weight: 400"> <span style="font-weight: 400">
{{ $myName }}<br /> {{ $config.public.myName }}<br />
Wien, Österreich Wien, Österreich
</span> </span>
<br /> <br />
E-Mail: <a :href="`mailto:${$myEmail}`">{{ $myEmail }}</a> E-Mail: <a :href="`mailto:${$config.public.myEmail}`">{{ $config.public.myEmail }}</a>
</p> </p>
<h2 id="rechte-dsgvo">Rechte laut Datenschutz-Grundverordnung</h2> <h2 id="rechte-dsgvo">Rechte laut Datenschutz-Grundverordnung</h2>
<p>Gemäß Artikel 13, 14 DSGVO informieren wir Sie über die folgenden Rechte, die Ihnen zustehen, damit es zu einer <p>Gemäß Artikel 13, 14 DSGVO informieren wir Sie über die folgenden Rechte, die Ihnen zustehen, damit es zu einer
@@ -960,11 +961,17 @@
</p> </p>
</v-card-text> </v-card-text>
</v-card> </v-card>
</div>
</template> </template>
<script> <script>
definePageMeta({
id: 4,
pageTransition: false
})
export default { export default {
name: 'ImprintPage', name: 'ImprintPage'
} }
</script> </script>
+9 -1
View File
@@ -22,7 +22,7 @@
variant="outlined" variant="outlined"
color="primary" color="primary"
prepend-icon="mdi-email-outline" prepend-icon="mdi-email-outline"
:href="`mailto:<${$myEmail}>`" :href="`mailto:<${$config.public.myEmail}>`"
> >
{{ $t('getInTouch') }} {{ $t('getInTouch') }}
</v-btn> </v-btn>
@@ -31,6 +31,14 @@
</template> </template>
<script> <script>
definePageMeta({
id: 1,
pageTransition: {
name: 'slide',
mode: 'out-in'
}
})
export default { export default {
name: 'IndexPage' name: 'IndexPage'
} }
+113 -212
View File
@@ -1,104 +1,81 @@
<template> <template>
<ImageCarousel ref="imageCarousel" /> <div>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<span class="text-h5 font-weight-medium">{{ $t('portfolioTitle') }}</span> <v-sheet
</v-col> width="100%"
<v-col v-for="(project, index) of projects" :key="index" cols="12" lg="4" md="6"> color="transparent"
<v-card height="100%" class="d-flex flex-column"> class="d-flex flex-no-wrap justify-space-between"
<v-carousel :class="view === 'horizontal' ? 'page-content' : ''"
v-model="carouselIndex[index]"
:show-arrows="project.images.length > 1 ? 'hover' : false"
:hide-delimiters="project.images.length <= 1"
hide-delimiter-background
height="auto"
> >
<v-carousel-item v-for="image of project.images" :key="image" <span class="text-h5 font-weight-medium">
:src="`${cdn}${image}`" {{ $t('portfolioTitle') }}
:class="project.class" </span>
@click="showImageCarousel(project.images, carouselIndex[index])" <v-btn-toggle
></v-carousel-item> v-model="selectedViewIndex"
</v-carousel> mandatory
<v-card-item> density="compact"
<v-card-title class="d-flex"> color="primary"
<span>{{ project.title }}</span>
<v-spacer></v-spacer>
<v-btn
v-if="project.repoUrl"
variant="text"
icon="mdi-github"
density="comfortable"
color="on-surface"
:href="`https://${project.repoUrl}`"
target="_blank"
class="link"
/>
<v-btn
v-if="project.projectUrl"
variant="text"
icon="mdi-open-in-new"
density="comfortable"
color="on-surface"
:href="`https://${project.projectUrl}`"
target="_blank"
class="link"
/>
</v-card-title>
<v-card-subtitle>{{ project.subtitle }}</v-card-subtitle>
</v-card-item>
<v-card-text>{{ $t(project.description) }}</v-card-text>
<v-spacer></v-spacer>
<v-card-actions class="d-flex flex-row flex-wrap justify-center align-center">
<img v-for="t of project.tech" :key="tech[t].title"
:src="generateBadgen(tech[t].title, tech[t].iconUrl)"
:height="20"
class="ma-1"
/>
<!-- colored badges
<img v-for="t of project.tech" :key="tech[t].title"
:src="`https://badgen.net/badge/icon/${tech[t].title}?icon=${tech[t].iconUrl}${tech[t].color}&label`"
:height="20"
class="ma-1"
/>
-->
<!-- colored badges w/ links to brand websites
<a v-for="t of project.tech" :key="tech[t].title"
:href="`https://${tech[t].projectUrl}`"
target="_blank"
> >
<img v-for="t of project.tech" :key="tech[t].title" <v-btn>
:src="`https://badgen.net/badge/icon/${tech[t].title}?icon=${tech[t].iconUrl}${tech[t].color}&label`" <v-icon icon="mdi-view-sequential" color="white"></v-icon>
:height="20" </v-btn>
class="ma-1" <v-btn>
/> <v-icon icon="mdi-view-column" color="white"></v-icon>
</a> </v-btn>
--> </v-btn-toggle>
</v-sheet>
</v-card-actions> </v-col>
</v-card> <v-col v-for="(project, index) of projects" :key="index"
</v-col> :cols="viewCols.cols"
</v-row> :sm="viewCols.sm"
:md="viewCols.md"
:lg="viewCols.lg"
:xl="viewCols.xl"
>
<ProjectCard
:view="view"
:title="project.title"
:subtitle="project.subtitle"
:description="project.description"
:overline="project.overline"
:tech="project.tech"
:images="project.images"
:links="project.links"
/>
</v-col>
</v-row>
</div>
</template> </template>
<script> <script>
import { badgen } from 'badgen' definePageMeta({
import { siAndroid, siAmazonaws, siMicrosoftazure, siFirebase, siGithub, id: 3,
siHeroku, siCoffeescript, siYoutubegaming, siMysql, siNuxtdotjs, siPhp, pageTransition: {
siVuedotjs, siVuetify, siIbmwatson, siWordpress } from 'simple-icons' name: 'slide',
mode: 'out-in'
}
})
const runtimeConfig = useRuntimeConfig()
export default { export default {
name: 'PortfolioComponent', name: 'PortfolioPage',
data: () => ({ data: () => ({
cdn: 'https://d29l6egdxvgg9c.cloudfront.net/', selectedViewIndex: 0,
views: {
horizontal: 0,
vertical: 1
},
projects: [ projects: [
{ {
title: 'Torii SRS (v2-beta)', title: 'Torii SRS (v2)',
subtitle: 'Progressive Web App', subtitle: 'Progressive Web App (SPA)',
description: 'toriiWeb', description: 'toriiWeb',
tech: ['vue2', 'vuetify2', 'mysql', 'php', 'aws', 'azure', 'watson', 'heroku'], tech: ['js', 'vue', 'vuetify', 'mysql', 'php', 'aws', 'azure', 'watson', 'heroku'],
projectUrl: 'beta.torii-srs.com', links: [
{ url: 'beta.torii-srs.com', icon: 'mdi-open-in-new' }
],
images: [ images: [
'torii-v2-01.jpg', 'torii-v2-03.jpg', 'torii-v2-04.jpg', 'torii-v2-01.jpg', 'torii-v2-03.jpg', 'torii-v2-04.jpg',
'torii-v2-05.jpg', 'torii-v2-06.jpg', 'torii-v2-07.jpg', 'torii-v2-05.jpg', 'torii-v2-06.jpg', 'torii-v2-07.jpg',
@@ -107,10 +84,13 @@ export default {
}, },
{ {
title: 'Torii SRS (v1)', title: 'Torii SRS (v1)',
subtitle: 'Cross-platform app', subtitle: 'Cross-Platform App',
description: 'toriiJava', description: 'toriiJava',
overline: 'toriiInfo',
tech: ['java', 'libgdx', 'mysql', 'php', 'aws', 'wordpress'], tech: ['java', 'libgdx', 'mysql', 'php', 'aws', 'wordpress'],
projectUrl: 'torii-srs.com', links: [
{ url: 'torii-srs.com', icon: 'mdi-open-in-new' }
],
images: [ images: [
'torii-v1-1.jpg', 'torii-v1-2.png', 'torii-v1-3.png', 'torii-v1-1.jpg', 'torii-v1-2.png', 'torii-v1-3.png',
'torii-v1-4.png', 'torii-v1-5.png', 'torii-v1-6.png' 'torii-v1-4.png', 'torii-v1-5.png', 'torii-v1-6.png'
@@ -118,148 +98,69 @@ export default {
}, },
{ {
title: 'IU Quiz App', title: 'IU Quiz App',
subtitle: 'Web App', subtitle: 'Web App (SPA)',
description: 'iuQuizApp', description: 'iuQuizApp',
tech: ['nuxt2', 'vuetify2', 'firebase'], tech: ['js', 'vue', 'nuxt', 'vuetify', 'firebase'],
repoUrl: 'github.com/Rakantor/iu-quiz-app', links: [
projectUrl: 'iu-quiz-app.web.app', { url: 'iu-quiz-app.web.app', icon: 'mdi-open-in-new' },
{ url: 'github.com/Rakantor/iu-quiz-app', icon: 'mdi-github' },
{ url: `${runtimeConfig.public.cdn}iu-quiz-app-projektbericht.pdf`, icon: 'mdi-file-pdf-box' }
],
images: ['iu-quiz-app-2.jpg'] images: ['iu-quiz-app-2.jpg']
}, },
{ {
title: 'Menacing Blue', title: 'Menacing Blue',
subtitle: 'Cross-platform app', subtitle: 'Cross-Platform App',
description: 'pmb', description: 'pmb',
tech: ['java', 'libgdx'], tech: ['java', 'libgdx'],
images: ['pmb-6.png', 'pmb-1.png', 'pmb-2.png', 'pmb-3.png', 'pmb-4.png', 'pmb-5.png', 'pmb-7.jpg'] images: ['pmb-6.png', 'pmb-1.png', 'pmb-2.png', 'pmb-3.png', 'pmb-4.png', 'pmb-5.png', 'pmb-7.jpg']
}, },
{ {
title: 'Personal Website', title: 'Personal Website',
subtitle: 'Web App', subtitle: 'Web App (SPA)',
description: 'personalWebsite', description: 'personalWebsite',
tech: ['nuxt3', 'vuetify3', 'ghpages'], tech: ['ts', 'vue', 'nuxt', 'vuetify', 'ghpages'],
repoUrl: 'github.com/Rakantor/personal-portfolio', links: [
projectUrl: 'mave.dev', { url: 'mave.dev', icon: 'mdi-open-in-new' },
{ url: 'github.com/Rakantor/personal-portfolio', icon: 'mdi-github' }
],
images: ['personal-website-1.jpg'], images: ['personal-website-1.jpg'],
class: 'pa-2'
}, },
{ {
title: 'IU Gamer App', title: 'IU Gamer App',
subtitle: 'Android app', subtitle: 'Android App',
description: 'iuGamerApp', description: 'iuGamerApp',
tech: ['java', 'android', 'firebase'], tech: ['java', 'android', 'firebase'],
repoUrl: 'github.com/Rakantor/iubh-gamer-app', links: [
{ url: 'github.com/Rakantor/iubh-gamer-app', icon: 'mdi-github' }
],
images: ['iu-gamer-app-1.jpg', 'iu-gamer-app-2.jpg'] images: ['iu-gamer-app-1.jpg', 'iu-gamer-app-2.jpg']
} }
], ]
tech: {
android: {
title: 'Android',
color: '3DDC84',
iconUrl: siAndroid,
},
aws: {
title: 'AWS',
color: '232F3E',
iconUrl: siAmazonaws,
},
azure: {
title: 'Azure',
color: '0078D4',
iconUrl: siMicrosoftazure,
},
firebase: {
title: 'Firebase',
color: 'FFCA28',
iconUrl: siFirebase,
},
ghpages: {
title: 'GH Pages',
color: '222222',
iconUrl: siGithub,
},
heroku: {
title: 'Heroku',
color: '430098',
iconUrl: siHeroku,
},
java: {
title: 'Java',
color: 'FF7800',
iconUrl: siCoffeescript,
},
libgdx: {
title: 'libGDX',
color: '990000',
iconUrl: siYoutubegaming,
},
mysql: {
title: 'MySQL',
color: '4479A1',
iconUrl: siMysql,
},
nuxt2: {
title: 'Nuxt 2',
color: '00DC82',
iconUrl: siNuxtdotjs,
},
nuxt3: {
title: 'Nuxt 3',
color: '00DC82',
iconUrl: siNuxtdotjs,
},
php: {
title: 'PHP',
color: '777BB4',
iconUrl: siPhp,
},
vue2: {
title: 'Vue 2',
color: '4FC08D',
iconUrl: siVuedotjs,
},
vuetify2: {
title: 'Vuetify 2',
color: '1867C0',
iconUrl: siVuetify,
},
vuetify3: {
title: 'Vuetify 3',
color: '1867C0',
iconUrl: siVuetify,
},
watson: {
title: 'Watson',
color: 'BE95FF',
iconUrl: siIbmwatson,
},
wordpress: {
title: 'WordPress',
color: '21759B',
iconUrl: siWordpress,
}
},
carouselIndex: []
}), }),
methods: { computed: {
generateBadgen (label, iconUrl) { cdn () {
// const iconColor = iconUrl.hex return this.$config.public.cdn
const iconColor = 'FFFFFF' // white
const iconSvg = iconUrl.svg.replace('<path ', `<path fill="#${iconColor}" `)
const svg = badgen({
label: '',
status: label,
// color: iconColor,
color: this.$vuetify.theme.current.colors.backgroundTertiary.slice(1),
style: 'classic',
icon: `data:image/svg+xml;utf8,${encodeURIComponent(iconSvg)}`
})
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
}, },
showImageCarousel (images, carouselIndex) { viewCols () {
this.$refs.imageCarousel.images = images.map(i => this.cdn+i) const horizontal = {
this.$refs.imageCarousel.index = carouselIndex cols: 12,
this.$refs.imageCarousel.show = true sm: 12,
md: 12,
lg: 12,
xl: 12
}
const vertical = {
cols: 12,
sm: 6,
md: 6,
lg: 4,
xl: 4
}
return this.view === 'horizontal' ? horizontal : vertical
},
view () {
return this.selectedViewIndex === this.views.horizontal && this.$vuetify.display.smAndUp ? 'horizontal' : 'vertical'
} }
} }
} }
+7 -8
View File
@@ -1,12 +1,11 @@
import { createVuetify, ThemeDefinition } from 'vuetify' import { createVuetify } from 'vuetify'
import type { ThemeDefinition } from 'vuetify'
import * as components from 'vuetify/components' import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives' import * as directives from 'vuetify/directives'
import { md3 } from 'vuetify/blueprints' import { md3 } from 'vuetify/blueprints'
import '@mdi/font/css/materialdesignicons.css' import '@mdi/font/css/materialdesignicons.css'
import { mdi } from 'vuetify/iconsets/mdi' import { mdi } from 'vuetify/iconsets/mdi'
import { brands } from '~/iconsets/brands' import { brands } from '~/iconsets/brands'
// @ts-expect-error Missing type definitions
import colors from 'vuetify/lib/util/colors'
const myCustomDarkTheme: ThemeDefinition = { const myCustomDarkTheme: ThemeDefinition = {
dark: true, dark: true,
@@ -47,8 +46,12 @@ export default defineNuxtPlugin(nuxtApp => {
blueprint: md3, blueprint: md3,
defaults: { defaults: {
VCard: { VCard: {
border: 'sm',
elevation: 0 elevation: 0
}, },
VCarousel: {
color: 'primary'
}
}, },
theme: { theme: {
// customVariables: ['~/assets/variables.scss'], // customVariables: ['~/assets/variables.scss'],
@@ -67,8 +70,4 @@ export default defineNuxtPlugin(nuxtApp => {
}) })
nuxtApp.vueApp.use(vuetify) nuxtApp.vueApp.use(vuetify)
})
// Define global variables
nuxtApp.provide('myName', 'Manuel Veigel')
nuxtApp.provide('myEmail', 'maveigel@gmail.com')
})