This commit is contained in:
Simon Einzinger 2025-01-29 12:01:09 +01:00
parent 0ed88f38a4
commit 44b7768d6f
31 changed files with 1146 additions and 141 deletions

View file

@ -1,4 +1,8 @@
import { defineConfig, DefaultTheme } from "vitepress";
import {
groupIconMdPlugin,
groupIconVitePlugin,
} from "vitepress-plugin-group-icons";
interface CustomThemeConfig extends DefaultTheme.Config {
socialLinks?: Array<{
@ -6,14 +10,27 @@ interface CustomThemeConfig extends DefaultTheme.Config {
link: string;
icon: string;
}>;
nav?: Array<{
text: string;
link: string;
}>;
}
// https://vitepress.dev/reference/site-config
export default defineConfig<CustomThemeConfig>({
title: "Simon Einzinger",
description: "Cybersecurity Student",
markdown: {
theme: "github-light",
theme: {
light: "github-light",
dark: "github-dark",
},
// lineNumbers: true,
config(md: any) {
md.use(groupIconMdPlugin);
},
},
vite: {
plugins: [groupIconVitePlugin()],
},
themeConfig: {
socialLinks: [
@ -33,5 +50,10 @@ export default defineConfig<CustomThemeConfig>({
link: "mailto:info@simon-einzinger.de",
},
],
nav: [
{ text: "Home", link: "/" },
{ text: "Blog", link: "/blog/" },
{ text: "CV", link: "/cv/" },
],
},
} satisfies { themeConfig: CustomThemeConfig });

View file

@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed } from "vue";
import { data as cvItems } from "../composables/cv.data";
import CVTimelineItem from "../layout/CVTimelineItem.vue";
import CVProjectCard from "../layout/CVProjectCard.vue";
function sortByDateDesc(a, b) {
return b.startValue - a.startValue || (b.endValue || 0) - (a.endValue || 0);
}
const educationItems = computed(() =>
cvItems.filter((i) => i.section === "education").sort(sortByDateDesc),
);
const workItems = computed(() =>
cvItems.filter((i) => i.section === "work-experience").sort(sortByDateDesc),
);
const projectItems = computed(() =>
cvItems.filter((i) => i.section === "projects").sort(sortByDateDesc),
);
</script>
<template>
<div class="cv-index">
<section v-if="educationItems.length" class="cv-section">
<h1>Education</h1>
<div class="cv-timeline">
<CVTimelineItem
v-for="(item, idx) in educationItems"
:key="item.url + idx"
:item="item"
/>
</div>
</section>
<section v-if="workItems.length" class="cv-section">
<h1>Work Experience</h1>
<div class="cv-timeline">
<CVTimelineItem
v-for="(item, idx) in workItems"
:key="item.url + idx"
:item="item"
/>
</div>
</section>
<section v-if="projectItems.length" class="cv-section">
<h1>Projects</h1>
<div class="cv-project-list">
<CVProjectCard
v-for="(item, idx) in projectItems"
:key="item.url + idx"
:item="item"
/>
</div>
</section>
</div>
</template>
<style scoped>
.cv-index {
}
.cv-section {
margin-bottom: 3em;
}
.cv-section h1 {
margin-bottom: 1em;
font-size: 1.8em;
position: relative;
}
.cv-timeline {
position: relative;
margin: 1em 0;
}
.cv-timeline::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 1rem;
width: 3px;
background: linear-gradient(
180deg,
var(--highlight-color) 0%,
var(--primary-color) 100%
);
border-radius: 2px;
}
.cv-project-list {
display: flex;
flex-direction: column;
gap: 1em;
}
</style>

View file

@ -0,0 +1,70 @@
import { createContentLoader } from "vitepress";
interface ProjectLink {
label: string;
url: string;
}
interface CVItemData {
section: "education" | "work-experience" | "projects";
institution?: string;
degree?: string;
company?: string;
position?: string;
title?: string;
links?: ProjectLink[];
technologies?: string[];
start: string;
end?: string;
startValue: number;
endValue?: number;
excerpt?: string;
url: string;
body?: string;
}
declare const data: CVItemData[];
export { data };
export default createContentLoader("cv/*.md", {
excerpt: true,
transform(raw) {
return raw.map(({ frontmatter, url, excerpt: autoExcerpt }) => {
const section = frontmatter.section as CVItemData["section"];
const startRaw = frontmatter.start as string;
const endRaw = (frontmatter.end as string) || "";
const startValue = parseMonthYear(startRaw);
const endValue =
endRaw.toLowerCase() === "present" ? Infinity : parseMonthYear(endRaw);
const finalExcerpt = frontmatter.excerpt || autoExcerpt || "";
const links = frontmatter.links ?? [];
const technologies = frontmatter.technologies ?? [];
return {
...frontmatter,
section,
url,
excerpt: finalExcerpt,
start: startRaw,
end: endRaw,
startValue,
endValue,
links,
technologies,
};
}) as CVItemData[];
},
});
function parseMonthYear(str: string): number {
if (!str) return 0;
const [mm, yyyy] = str.split("/");
const month = parseInt(mm, 10);
const year = parseInt(yyyy, 10);
if (isNaN(month) || isNaN(year)) return 0;
return year * 100 + month;
}

View file

@ -1,21 +0,0 @@
import { createContentLoader } from "vitepress";
interface Index {
title: string;
url: string;
}
declare const data: Index[];
export { data };
export default createContentLoader("*.md", {
transform(raw): Index[] {
return raw
.map(({ url, frontmatter, excerpt }) => ({
title: frontmatter.title,
url,
}))
.sort((a, b) => a.title.localeCompare(b.title));
},
});

View file

@ -1,22 +1,19 @@
// https://vitepress.dev/guide/custom-theme
import { Theme } from "vitepress";
import DefaultLayout from "./layout/Layout.vue";
import BlogLayout from "./layout/BlogLayout.vue";
import type { Theme } from "vitepress";
import DefaultLayout from "./layout/Layout.vue";
import "./style.css";
import "./styles/cards.css";
import "./styles/blogpost.css";
import "virtual:group-icons.css";
import "./styles/code.css";
import BlogIndex from "./components/BlogIndex.vue";
// @ts-ignore
const components = [BlogIndex];
import CVIndex from "./components/CVIndex.vue";
const theme: Theme = {
Layout: DefaultLayout,
enhanceApp({ app, router, siteData }) {
enhanceApp({ app }) {
app.component("BlogIndex", BlogIndex);
},
layouts: {
blog: BlogLayout,
app.component("CVPage", CVIndex);
},
};

View file

@ -10,91 +10,19 @@ const props = defineProps<BlogItemProps>();
</script>
<template>
<div class="blog-card">
<div class="blog-header">
<h2 class="blog-title">
<a :href="props.url">
{{ props.title }}
</a>
</h2>
<span class="blog-date">{{ props.date }}</span>
<div class="card card-lift card-gradient-border">
<div class="card-content">
<div class="card-header">
<h2 class="card-title">
<a :href="props.url">
{{ props.title }}
</a>
</h2>
<span class="card-meta">{{ props.date }}</span>
</div>
<div v-if="props.excerpt" v-html="props.excerpt"></div>
</div>
<div v-if="props.excerpt" v-html="props.excerpt"></div>
</div>
</template>
<style scoped>
.blog-card {
position: relative;
margin-bottom: 1.5em;
padding: 1.5em;
border: 1px solid var(--outline-color);
border-radius: 8px;
background-color: transparent;
transition:
transform 0.3s,
box-shadow 0.3s,
opacity 0.3s;
}
.blog-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: 8px;
background: linear-gradient(
45deg,
var(--primary-color),
var(--highlight-color)
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
z-index: -1;
}
.blog-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.blog-card:hover::before {
opacity: 1;
}
.blog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
}
.blog-title {
margin: 0;
font-size: 1.2em;
color: var(--secondary-text-color);
font-weight: var(--font-weight-bold);
}
.blog-title a {
text-decoration: none;
color: inherit; /* Inherit the .blog-title color */
}
.blog-date {
font-size: 0.9em;
color: var(--text-color);
}
.blog-excerpt {
margin: 0;
margin-top: 0.5em;
color: var(--text-color);
font-weight: var(--font-weight-regular);
}
</style>
<style scoped></style>

View file

@ -30,7 +30,6 @@ const prevPost = computed(() => {
<div class="blog-layout">
<TopBar />
<!-- The main post content: the markdown for this page -->
<article class="blog-post">
<Content />
</article>
@ -42,7 +41,6 @@ const prevPost = computed(() => {
<div v-if="prevPost" class="prev-post">
<a :href="prevPost.url"> {{ prevPost.title }}</a>
</div>
<!-- Next post link -->
<div v-if="nextPost" class="next-post">
<a :href="nextPost.url">{{ nextPost.title }} </a>
</div>
@ -52,7 +50,6 @@ const prevPost = computed(() => {
<style scoped>
.blog-layout {
/* your layout styling */
}
.blog-post {
max-width: 700px;

View file

@ -0,0 +1,91 @@
<script setup lang="ts">
interface ProjectLink {
label: string;
url: string;
}
interface ProjectItemData {
title?: string;
excerpt?: string;
links?: ProjectLink[];
technologies?: string[];
}
const props = defineProps<{ item: ProjectItemData }>();
</script>
<template>
<div class="card card-lift card-gradient-border">
<div class="card-content">
<!-- Header row: Title on left, Links on right -->
<div class="card-header project-header">
<h3 class="card-title">
{{ props.item.title || "Untitled Project" }}
</h3>
<div
class="project-links"
v-if="props.item.links && props.item.links.length"
>
<a
v-for="link in props.item.links"
:key="link.url"
:href="link.url"
target="_blank"
rel="noopener"
>
{{ link.label }}
</a>
</div>
</div>
<p
v-if="props.item.excerpt"
class="card-excerpt project-excerpt"
v-html="props.item.excerpt"
></p>
<div
class="project-technologies"
v-if="props.item.technologies && props.item.technologies.length"
>
{{ props.item.technologies.join(" · ") }}
</div>
</div>
</div>
</template>
<style scoped>
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-links {
display: flex;
flex-wrap: wrap;
gap: 1em;
}
.project-links a {
color: var(--highlight-color);
text-decoration: none;
font-weight: var(--font-weight-bold);
transition: color 0.3s;
}
.project-links a:hover {
color: var(--primary-color);
}
.project-excerpt {
color: var(--text-color);
}
.project-technologies {
margin-top: 1em;
font-size: 0.9em;
color: var(--secondary-text-color);
text-align: center;
}
</style>

View file

@ -0,0 +1,108 @@
<script setup lang="ts">
import { computed } from "vue";
interface TimelineItemData {
section: "education" | "work-experience" | "projects";
institution?: string;
degree?: string;
company?: string;
position?: string;
start: string;
end?: string;
excerpt?: string;
}
const props = defineProps<{ item: TimelineItemData }>();
const roleTitle = computed(() => {
if (props.item.section === "education") {
return props.item.degree || "No Title";
} else if (props.item.section === "work-experience") {
return props.item.position || "No Position";
}
return "";
});
const placeTitle = computed(() => {
if (props.item.section === "education") {
return props.item.institution || "";
} else if (props.item.section === "work-experience") {
return props.item.company || "";
}
return "";
});
const dateRange = computed(() => {
const { start, end } = props.item;
if (!end || end.toLowerCase() === "present") {
return `${start} - Present`;
}
return `${start} - ${end}`;
});
</script>
<template>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="card card-lift card-gradient-border">
<div class="card-content">
<div class="card-header timeline-header">
<h3 class="card-title timeline-role">{{ roleTitle }}</h3>
<span class="card-meta">{{ dateRange }}</span>
</div>
<div class="timeline-place" v-if="placeTitle">
{{ placeTitle }}
</div>
<p
v-if="item.excerpt"
class="card-excerpt timeline-excerpt"
v-html="item.excerpt"
></p>
</div>
</div>
</div>
</template>
<style scoped>
.timeline-item {
position: relative;
margin-bottom: 3em;
margin-left: 2.5rem;
}
.timeline-dot {
position: absolute;
left: -2rem;
top: 1.25rem;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: var(--background-color);
border: 2px solid var(--outline-color);
transition:
background-color 0.3s,
border-color 0.3s;
}
.timeline-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.5em;
}
.timeline-role {
margin: 0.2em 0 0.25em;
font-size: 1.2em;
color: var(--secondary-text-color);
font-weight: bold;
}
.timeline-place {
font-size: 1em;
color: var(--text-color);
margin-bottom: 0.75em;
}
</style>

View file

@ -0,0 +1,28 @@
<script setup lang="ts">
import { useData } from "vitepress";
const { site } = useData();
const currentYear = new Date().getFullYear();
</script>
<template>
<footer class="site-footer">
<p>
CC {{ currentYear }} - {{ site.title }} |
<a href="/legals" class="footer-link">Data Protection & Privacy</a>
</p>
</footer>
</template>
<style scoped>
.site-footer {
margin-top: 1rem;
text-align: center;
border-top: 1px solid var(--outline-color);
color: var(--text-color);
font-size: 0.9em;
}
.footer-link {
color: var(--secondary-text-color);
text-decoration: none;
}
</style>

View file

@ -4,14 +4,36 @@ import { useData } from "vitepress";
import TopBar from "./TopBar.vue";
import Home from "./Home.vue";
import NotFound from "./NotFound.vue";
import Footer from "./Footer.vue";
// https://vitepress.dev/reference/runtime-api#usedata
const { frontmatter, page } = useData();
</script>
<template>
<TopBar />
<Home v-if="frontmatter.home" />
<NotFound v-else-if="page.isNotFound" />
<Content v-else />
<div class="custom-layout">
<TopBar />
<div class="content-container" v-if="frontmatter.home">
<Home />
</div>
<NotFound v-else-if="page.isNotFound" />
<div class="content-container" v-else>
<Content />
</div>
<Footer />
</div>
</template>
<style scoped>
.custom-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content-container {
flex: 1;
max-width: 1200px;
width: 100%;
margin: 0 auto;
}
</style>

View file

@ -2,8 +2,6 @@
import { useData } from "vitepress";
import { computed } from "vue";
import { data as index } from "../composables/index.data.js";
import ThemeToggle from "./ThemeToggle.vue";
const { site, theme } = useData();
@ -13,14 +11,14 @@ const socialLinks = computed(() => {
});
const indexList = computed(() => {
return index;
return theme.value.nav ?? [];
});
</script>
<template>
<div class="TopBar">
<div class="ProfileImageContainer">
<img src="/images/me.jpeg" alt="Simon Einzinger" class="ProfileImage" />
<img src="/images/me.jpeg" alt="{{ site.title }}" class="ProfileImage" />
</div>
<h1 class="title">{{ site.title }}</h1>
@ -47,8 +45,8 @@ const indexList = computed(() => {
<div class="Separator"></div>
<ul class="IndexList">
<li v-for="{ title, url } of indexList" :key="title">
<a :href="url">#{{ title }}</a>
<li v-for="{ text, link } of indexList" :key="title">
<a :href="link">#{{ text }}</a>
</li>
</ul>
</template>
@ -131,15 +129,16 @@ const indexList = computed(() => {
.IndexList {
list-style: none;
margin: 1em 0;
margin-bottom: 2em;
padding: 0;
display: flex;
justify-content: center;
gap: 1.5em;
gap: 2.5em;
}
.IndexList li a {
text-decoration: none;
color: var(--text-color);
padding: 0 0.75em;
padding: 0 0.5em;
}
</style>

View file

@ -0,0 +1,104 @@
.table-of-contents {
margin: 2em 0;
padding: 1em;
border: 1px solid var(--outline-color);
border-radius: 6px;
background-color: var(--background-color);
}
.table-of-contents::before {
content: "Table of Contents";
display: block;
margin-bottom: 0.5em;
font-weight: bold;
font-size: 1.1em;
}
.table-of-contents ul {
margin: 0;
padding: 0;
list-style: none;
}
.table-of-contents li {
position: relative;
margin: 0.4em 0;
padding-left: 1.2em;
}
.table-of-contents li::before {
content: "•";
position: absolute;
left: 0;
color: var(--primary-color);
}
.table-of-contents a {
color: inherit;
text-decoration: none;
}
.custom-block {
position: relative;
margin: 1.5em 0;
padding: 1em 1.25em;
border-left: 4px solid var(--outline-color);
border-radius: 6px;
background-color: var(--background-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.custom-block .custom-block-title {
margin-top: 0;
margin-bottom: 0.5em;
font-size: 1em;
font-weight: bold;
}
.note.custom-block {
border-color: #2196f3;
}
.note .custom-block-title {
color: #2196f3;
}
.note .custom-block-title::before {
content: "📝 ";
}
.tip.custom-block {
border-color: #4caf50;
}
.tip .custom-block-title::before {
content: "💡 ";
}
.tip .custom-block-title {
color: #4caf50;
.important.custom-block {
border-color: #e91e63;
}
.important .custom-block-title::before {
content: "❗️ ";
}
.important .custom-block-title {
color: #e91e63;
}
.warning.custom-block {
border-color: #ff9800;
}
.warning .custom-block-title::before {
content: "⚠️ ";
}
.warning .custom-block-title {
color: #ff9800;
}
.caution.custom-block {
border-color: #ff5722;
}
.caution .custom-block-title::before {
content: "🔥 ";
}
.caution .custom-block-title {
color: #ff5722;
}

View file

@ -0,0 +1,78 @@
.card {
position: relative;
margin-bottom: 1.5em;
border: 1px solid var(--outline-color);
border-radius: 8px;
background-color: transparent;
overflow: hidden;
}
.card-lift {
transition:
transform 0.3s,
box-shadow 0.3s;
}
.card-lift:hover {
transform: translateY(-3px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.card-gradient-border {
position: relative;
}
.card-gradient-border::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(
45deg,
var(--primary-color),
var(--highlight-color)
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
z-index: -1;
}
.card-gradient-border:hover::before {
opacity: 1;
}
.card-content {
padding: 1.5em;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
}
.card-title {
margin: 0;
font-size: 1.2em;
font-weight: bold;
color: var(--secondary-text-color);
}
.card-meta,
.card-dates {
font-size: 0.9em;
color: var(--text-color);
}
.card-excerpt {
margin: 0.5em 0 0;
color: var(--secondary-text-color);
line-height: 1.5;
}

View file

@ -0,0 +1,164 @@
[data-theme="light"] .shiki .line [style*="--shiki-light:"] {
color: var(--shiki-light) !important;
}
[data-theme="dark"] .shiki .line [style*="--shiki-dark:"] {
color: var(--shiki-dark) !important;
}
.vp-code-group.vp-adaptive-theme,
.vp-code-block-title {
margin: 1.5em 0;
border: 1px solid var(--outline-color);
border-radius: 6px;
background-color: var(--background-color);
overflow: hidden;
}
.vp-code-block-title-bar {
margin: 0;
}
.vp-code-group.vp-adaptive-theme .tabs {
display: flex;
border-bottom: 1px solid var(--outline-color);
background-color: var(--background-color);
}
.vp-code-group.vp-adaptive-theme .tabs input {
display: none;
}
/* Each label acts like a tab */
.vp-code-group.vp-adaptive-theme .tabs label {
margin: 0;
padding: 0.6em 1em;
font-size: 0.9rem;
cursor: pointer;
user-select: none;
border-right: 1px solid var(--outline-color);
color: var(--text-color);
transition:
background-color 0.2s,
color 0.2s;
}
/* Hover effect for a tab */
.vp-code-group.vp-adaptive-theme .tabs label:hover {
background-color: rgba(0, 0, 0, 0.05);
}
/* The "checked" tab => active style */
.vp-code-group.vp-adaptive-theme .tabs input:checked + label {
background-color: var(--primary-color);
color: #fff;
font-weight: bold;
border-right-color: var(--primary-color);
}
/* The container for each code block */
.vp-code-group.vp-adaptive-theme .blocks {
position: relative;
}
/* Hide all code blocks except the one with .active */
.vp-code-group.vp-adaptive-theme .blocks > div:not(.active) {
display: none;
}
/* Show the active one */
.vp-code-group.vp-adaptive-theme .blocks > .active {
display: block;
}
[class^="language-"] {
border: 1px solid var(--outline-color);
border-radius: 6px;
}
.vp-code-group [class^="language-"],
.vp-code-block-title [class^="language-"] {
border: 0;
}
/* Vertical line above language- in .vp-code-block-title */
.vp-code-block-title [class^="language-"] {
border-top: 1px solid var(--outline-color);
}
[class^="language-"].vp-adaptive-theme {
position: relative;
background-color: var(--background-color);
overflow: hidden;
transition: background-color 0.2s;
}
.shiki.vp-code {
margin: 0;
padding: 1em;
font-family: "Fira Code", monospace;
font-size: 0.9rem;
line-height: 0.75;
overflow: auto;
}
.shiki .line {
display: block;
white-space: pre;
}
/* Language label (e.g. “sh”, “py”), top-right by default */
[class^="language-"].vp-adaptive-theme .lang {
position: absolute;
top: 0.4em;
right: 0.6em;
padding: 0.1em 0.5em;
font-size: 0.75rem;
color: var(--secondary-text-color);
opacity: 1;
transition: opacity 0.2s;
}
/* Copy button.
Note that after copying, VitePress might change the class to copied code.
So lets target BOTH .copy and .copied.code. */
[class^="language-"].vp-adaptive-theme .copy,
[class^="language-"].vp-adaptive-theme .copied {
position: absolute;
top: 0.4em;
right: 0.6em;
background: transparent;
border: none;
font-size: 0.75rem;
padding: 0.1em 0.5em;
border-radius: 3px;
cursor: pointer;
opacity: 0;
pointer-events: none;
color: var(--secondary-text-color);
transition:
opacity 0.2s,
background-color 0.2s;
}
[class^="language-"].vp-adaptive-theme .copy:after {
content: "Copy";
}
[class^="language-"].vp-adaptive-theme .copied:after {
content: "Copied";
}
[class^="language-"].vp-adaptive-theme:hover .lang {
opacity: 0;
pointer-events: none;
}
[class^="language-"].vp-adaptive-theme:hover .copy,
[class^="language-"].vp-adaptive-theme:hover .copied.code {
opacity: 1;
pointer-events: auto;
}
/* Hover effect on the button */
[class^="language-"].vp-adaptive-theme .copy:hover,
[class^="language-"].vp-adaptive-theme .copied.code:hover {
background-color: var(--outline-color);
color: var(--text-color);
}

View file

@ -1,3 +1,5 @@
---
title: CV
---
<CVPage />

8
docs/cv/education-1.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "education"
institution: "Gymnasium Stein"
degree: "Abitur"
start: "09/2013"
end: "07/2021"
excerpt: "Final Grade: 1.6"
---

7
docs/cv/education-2.md Normal file
View file

@ -0,0 +1,7 @@
---
section: "education"
institution: "Saarland University"
degree: "Bachelor of Science (Cybersecurity)"
start: "10/2021"
end: "present"
---

14
docs/cv/project-1.md Normal file
View file

@ -0,0 +1,14 @@
---
section: "projects"
title: "Personal Webpage"
excerpt: "My personal website including a blog and my CV"
links:
- label: "Code"
url: "https://git.simon-einzinger.de/einSimon/webpage"
- label: "Deployment"
url: "https://simon-einzinger.de"
technologies:
- "Vue 3"
- "TypeScript"
- "VitePress"
---

10
docs/cv/project-2.md Normal file
View file

@ -0,0 +1,10 @@
---
section: "projects"
title: "BrainFucK-me"
excerpt: "A simple, yet overengineered Brainfuck interpreter/debugger written in Kotlin"
links:
- label: "Code"
url: "https://github.com/einCyberSimon/BrainFucK-me"
technologies:
- "Kotlin"
---

8
docs/cv/work-1.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "CISPA Helmholtz Center for Information Security"
position: "Undergraduate Research Assistant"
start: "05/2022"
end: "09/2022"
excerpt: "Conducted research investigations on Cross-Site Request Forgery (CSRF) Headers exploring inconsistencies between headers in the <a href='https://swag.cispa.saarland/'>Secure Web Applications Group</a> lead by Dr.-Ing. Ben Stock."
---

8
docs/cv/work-2.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "CISPA Helmholtz Center for Information Security"
position: "Student Assistant - Scientific Outreach Team"
start: "08/2022"
end: "09/2022"
excerpt: "Assisted in organizing <em>How to Capture-The-Flag(CTF)</em> workshops for highschool students"
---

8
docs/cv/work-3.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "Saarland University"
position: "Tutor - Foundations of Cybersecurity 1"
start: "11/2022"
end: "04/2023"
excerpt: "Tutoring the <a href='https://cms.cispa.saarland/cysec1_2223/'>Foundations of Cybersecurity 1</a> lecture held by Dr.-Ing. Ben Stock"
---

8
docs/cv/work-4.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "Saarland University"
position: "Tutor - Foundations of Cybersecurity 2"
start: "04/2023"
end: "09/2023"
excerpt: "Tutoring the <a href='https://cms.cispa.saarland/cysecii2023/'>Foundations of Cybersecurity 2</a> lecture held by Dr. Michael Schwarz"
---

8
docs/cv/work-5.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "Saarland University"
position: "Teaching Assistant - Foundations of Cybersecurity 1"
start: "10/2023"
end: "03/2024"
excerpt: "Administrative work as Teaching Assistant for the <a href='https://cms.cispa.saarland/cysec1_2324/'>Foundation of Cybersecurity 1</a> lecture held by Dr.-Ing. Ben Stock."
---

8
docs/cv/work-6.md Normal file
View file

@ -0,0 +1,8 @@
---
section: "work-experience"
company: "CISPA Helmholtz Center for Information Security"
position: "Undergraduate Research Assistant"
start: "04/2024"
end: "present"
excerpt: "Currently working at the <a href='https://roots.ec'>ROOTSEC</a> research group led by Dr. Michael Schwarz focusing on microarchitectural attacks and CPU security."
---

View file

@ -4,6 +4,6 @@ title: About
---
Hello there, I am Simon, a 23 year-old cybersecurity student at Saarland University.
Currently, I am in my last bachelor semester writing my thesis on [Side-channel attacks](https://en.wikipedia.org/wiki/Side-channel_attack).
Parts of my freetime are dedicated to playing Capture-The-Flag competitions (usually with my team [saarsec](https://saarsec.rocks)).
Currently, I am in my last bachelor semester writing my thesis on [Side-channel attacks](https://en.wikipedia.org/wiki/Side-channel_attack).
Parts of my freetime are dedicated to playing Capture-The-Flag competitions (usually with my local team [saarsec](https://saarsec.rocks)).
Thus, I will be looking forward to showing some cool challenge solutions in the [Blogs](/blog) section.

13
docs/legals.md Normal file
View file

@ -0,0 +1,13 @@
# Impressum
## Kontakt
Name: Simon Einzinger
E-Mail: [info(at)simon-einzinger.de](mailto:info@simon-einzinger.de)
## Haftung für und Überprüfung von Inhalten:
Durch die Vorgaben in § 7 Absatz 1 TMG bin ich als Webmaster für die Inhalte meines Blogs verantwortlich.
Gleichzeitig befreien mich §§ 8 bis einschließlich § 10 TMG von der Verantwortung, übermittelte oder gespeicherte fremde Inhalte zu überwachen.
Trotzdem bin ich mir meiner Pflicht bewusst, der Sperrung und Entfernung von Informationen nachzukommen, wie es geltende Gesetze vorgeben.

View file

@ -13,18 +13,42 @@ Some excerpt for the blog post teasing what it is _about_
with some lorem ipsum text
```python
```python [test.py]
def hello_world() -> str:
return "Hello, World!"
return "Hello, World!"
if __name__ == "__main__":
string = hello_world()
print(string)
string = hello_world()
print(string)
```
> [!NOTE]
::: code-group
```sh [npm]
npm install vitepress-plugin-group-icons
```
```sh [yarn]
yarn add vitepress-plugin-group-icons
```
```sh [pnpm]
pnpm add vitepress-plugin-group-icons
```
```sh [bun]
bun add vitepress-plugin-group-icons
```
:::
> [!TIP]
> Highlights information that users should take into account, even when skimming.
::: tip
Some nice tip here
:::
```js
export default {
name: "MyComponent",

190
package-lock.json generated
View file

@ -11,6 +11,7 @@
"@types/markdown-it": "^12.2.3",
"@types/node": "^20.11.27",
"vitepress": "^1.5.0",
"vitepress-plugin-group-icons": "^1.3.5",
"vue": "^3.5.0-beta.3"
}
},
@ -256,6 +257,28 @@
"node": ">= 14.0.0"
}
},
"node_modules/@antfu/install-pkg": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
"integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==",
"dev": true,
"dependencies": {
"package-manager-detector": "^0.2.0",
"tinyexec": "^0.3.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@antfu/utils": {
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz",
"integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
@ -748,6 +771,15 @@
"node": ">=12"
}
},
"node_modules/@iconify-json/logos": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@iconify-json/logos/-/logos-1.2.4.tgz",
"integrity": "sha512-XC4If5D/hbaZvUkTV8iaZuGlQCyG6CNOlaAaJaGa13V5QMYwYjgtKk3vPP8wz3wtTVNVEVk3LRx1fOJz+YnSMw==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/simple-icons": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.18.tgz",
@ -758,6 +790,15 @@
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/vscode-icons": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@iconify-json/vscode-icons/-/vscode-icons-1.2.10.tgz",
"integrity": "sha512-qjp/j2RcHEZkesuAT6RP8BfcuHa+oERr7K1twfsulrIHrKZlpxxBeEyFm+3evZSAOgD+sjgU5CuTYS3RfCL+Pg==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
@ -765,6 +806,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/@iconify/utils": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.2.1.tgz",
"integrity": "sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==",
"dev": true,
"dependencies": {
"@antfu/install-pkg": "^0.4.1",
"@antfu/utils": "^0.7.10",
"@iconify/types": "^2.0.0",
"debug": "^4.4.0",
"globals": "^15.13.0",
"kolorist": "^1.8.0",
"local-pkg": "^0.5.1",
"mlly": "^1.7.3"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@ -1553,6 +1610,18 @@
}
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/algoliasearch": {
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.19.0.tgz",
@ -1632,6 +1701,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true
},
"node_modules/copy-anything": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
@ -1655,6 +1730,23 @@
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@ -1770,6 +1862,18 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/globals": {
"version": "15.14.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
"integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz",
@ -1839,6 +1943,28 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/kolorist": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"dev": true
},
"node_modules/local-pkg": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
"integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
"dev": true,
"dependencies": {
"mlly": "^1.7.3",
"pkg-types": "^1.2.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@ -1986,6 +2112,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/mlly": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
"integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
"dev": true,
"dependencies": {
"acorn": "^8.14.0",
"pathe": "^2.0.1",
"pkg-types": "^1.3.0",
"ufo": "^1.5.4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
@ -2017,6 +2161,18 @@
"regex-recursion": "^5.1.1"
}
},
"node_modules/package-manager-detector": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.8.tgz",
"integrity": "sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==",
"dev": true
},
"node_modules/pathe": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
"integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==",
"dev": true
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@ -2031,6 +2187,17 @@
"dev": true,
"license": "ISC"
},
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
@ -2257,6 +2424,12 @@
"url": "https://github.com/sponsors/alfiejones"
}
},
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true
},
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@ -2268,6 +2441,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/ufo": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
"dev": true
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
@ -2480,6 +2659,17 @@
}
}
},
"node_modules/vitepress-plugin-group-icons": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/vitepress-plugin-group-icons/-/vitepress-plugin-group-icons-1.3.5.tgz",
"integrity": "sha512-1f1NP7osRYlNTR0yS5CAqcaasKHRSAzFKpeCUOfCPwYLAFxhCxsEbRtPBm0U1CfrDVa303MsjX18ngGpFGxIMA==",
"dev": true,
"dependencies": {
"@iconify-json/logos": "^1.2.4",
"@iconify-json/vscode-icons": "^1.2.10",
"@iconify/utils": "^2.2.1"
}
},
"node_modules/vitepress/node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",

View file

@ -1,9 +1,10 @@
{
"type": "module",
"devDependencies": {
"vitepress": "^1.5.0",
"@types/markdown-it": "^12.2.3",
"@types/node": "^20.11.27",
"vitepress": "^1.5.0",
"vitepress-plugin-group-icons": "^1.3.5",
"vue": "^3.5.0-beta.3"
},
"scripts": {