Initial commit

This commit is contained in:
Simon Einzinger 2025-01-24 19:05:29 +01:00
commit 0ed88f38a4
27 changed files with 3345 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
**/cache

View file

@ -0,0 +1,37 @@
import { defineConfig, DefaultTheme } from "vitepress";
interface CustomThemeConfig extends DefaultTheme.Config {
socialLinks?: Array<{
label: string;
link: string;
icon: string;
}>;
}
// https://vitepress.dev/reference/site-config
export default defineConfig<CustomThemeConfig>({
title: "Simon Einzinger",
description: "Cybersecurity Student",
markdown: {
theme: "github-light",
},
themeConfig: {
socialLinks: [
{
label: "GitHub",
icon: "/icons/github.svg",
link: "https://github.com/einCyberSimon",
},
{
label: "LinkedIn",
icon: "/icons/linkedin.svg",
link: "https://www.linkedin.com/in/simon-einzinger",
},
{
label: "Email",
icon: "/icons/email.svg",
link: "mailto:info@simon-einzinger.de",
},
],
},
} satisfies { themeConfig: CustomThemeConfig });

View file

@ -0,0 +1,35 @@
<script setup lang="ts">
import { computed } from "vue";
import { data as posts } from "../composables/posts.data";
import BlogItem from "../layout/BlogItem.vue";
const sortedPosts = computed(() => {
return [...posts].sort((a, b) => b.date.time - a.date.time);
});
</script>
<template>
<div class="blog-index">
<h1>All Posts</h1>
<ul class="blog-list">
<li v-for="post in sortedPosts" :key="post.url">
<BlogItem
:url="post.url"
:title="post.title"
:date="post.date.string"
:excerpt="post.excerpt"
/>
</li>
</ul>
</div>
</template>
<style scoped>
.blog-index {
}
.blog-list {
list-style: none;
padding: 0;
margin: 1em 0;
}
</style>

View file

@ -0,0 +1,21 @@
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

@ -0,0 +1,42 @@
import { createContentLoader } from "vitepress";
interface Post {
title: string;
url: string;
date: {
time: number;
string: string;
};
excerpt: string | undefined;
}
declare const data: Post[];
export { data };
export default createContentLoader("posts/*.md", {
excerpt: true,
transform(raw): Post[] {
return raw
.map(({ url, frontmatter, excerpt }) => ({
title: frontmatter.title,
url,
excerpt,
date: formatDate(frontmatter.date),
}))
.sort((a, b) => a.title.localeCompare(b.title));
},
});
function formatDate(raw: string): Post["date"] {
const date = new Date(raw);
date.setUTCHours(12);
return {
time: +date,
string: date.toLocaleDateString("de-DE", {
year: "numeric",
month: "long",
day: "numeric",
}),
};
}

View file

@ -0,0 +1,33 @@
import { ref, onMounted } from "vue";
export function useTheme() {
const isDarkMode = ref(false);
function setTheme(dark: boolean) {
isDarkMode.value = dark;
document.documentElement.setAttribute(
"data-theme",
dark ? "dark" : "light",
);
localStorage.setItem("theme", dark ? "dark" : "light");
}
function toggleTheme() {
setTheme(!isDarkMode.value);
}
onMounted(() => {
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
setTheme(savedTheme === "dark");
} else {
setTheme(false);
}
});
return {
isDarkMode,
setTheme,
toggleTheme,
};
}

View file

@ -0,0 +1,23 @@
// 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 "./style.css";
import BlogIndex from "./components/BlogIndex.vue";
// @ts-ignore
const components = [BlogIndex];
const theme: Theme = {
Layout: DefaultLayout,
enhanceApp({ app, router, siteData }) {
app.component("BlogIndex", BlogIndex);
},
layouts: {
blog: BlogLayout,
},
};
export default theme;

View file

@ -0,0 +1,100 @@
<script setup lang="ts">
interface BlogItemProps {
url: string;
title: string;
date: string;
excerpt: string;
}
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>
<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>

View file

@ -0,0 +1,68 @@
<script setup lang="ts">
import { useData } from "vitepress";
import { computed } from "vue";
import { data as posts } from "../composables/posts.data";
import TopBar from "./TopBar.vue";
import { useRoute } from "vitepress";
const route = useRoute();
const currentIndex = computed(() =>
posts.findIndex((p) => p.url === route.path),
);
const currentPost = computed(() =>
currentIndex.value > -1 ? posts[currentIndex.value] : null,
);
const nextPost = computed(() => {
return currentIndex.value < posts.length - 1
? posts[currentIndex.value + 1]
: null;
});
const prevPost = computed(() => {
return currentIndex.value > 0 ? posts[currentIndex.value - 1] : null;
});
</script>
<template>
<div class="blog-layout">
<TopBar />
<!-- The main post content: the markdown for this page -->
<article class="blog-post">
<Content />
</article>
<hr />
<nav class="post-navigation">
<!-- Previous post link -->
<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>
</nav>
</div>
</template>
<style scoped>
.blog-layout {
/* your layout styling */
}
.blog-post {
max-width: 700px;
margin: 2rem auto;
padding: 0 1rem;
}
.post-navigation {
display: flex;
justify-content: space-between;
margin: 2rem auto;
max-width: 700px;
}
</style>

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import { computed } from "vue";
import { data as posts } from "../composables/posts.data.js";
import BlogItem from "./BlogItem.vue";
const latestPosts = computed(() => {
return posts.slice(0, 3);
});
</script>
<template>
<Content />
<div>
<h1>Latest Blog Posts</h1>
<ul class="blog-list">
<li v-for="{ title, url, date, excerpt } of posts" :key="url">
<BlogItem
:url="url"
:title="title"
:date="date.string"
:excerpt="excerpt"
/>
</li>
</ul>
</div>
</template>
<style scoped>
.blog-list {
list-style: none;
margin: 1em;
padding: 0;
}
</style>

View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import { useData } from "vitepress";
import TopBar from "./TopBar.vue";
import Home from "./Home.vue";
import NotFound from "./NotFound.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 />
</template>

View file

@ -0,0 +1,9 @@
<template>
<h1>404 Not found</h1>
</template>
<script>
export default {};
</script>
<style></style>

View file

@ -0,0 +1,68 @@
<script setup lang="ts">
import { useTheme } from "../composables/useTheme";
const { isDarkMode, toggleTheme } = useTheme();
</script>
<template>
<div class="ThemeToggle">
<button
class="theme-toggle"
:class="{ 'theme-toggle--toggled': isDarkMode }"
type="button"
title="Toggle theme"
aria-label="Toggle theme"
@click="toggleTheme"
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
width="2.5em"
height="2.5em"
fill="currentColor"
class="theme-toggle__around"
viewBox="0 0 32 32"
>
<clipPath id="theme-toggle__around__cutout">
<path d="M0 0h42v30a1 1 0 00-16 13H0Z" />
</clipPath>
<g clip-path="url(#theme-toggle__around__cutout)">
<circle cx="16" cy="16" r="8.4" />
<g>
<circle cx="16" cy="3.3" r="2.3" />
<circle cx="27" cy="9.7" r="2.3" />
<circle cx="27" cy="22.3" r="2.3" />
<circle cx="16" cy="28.7" r="2.3" />
<circle cx="5" cy="22.3" r="2.3" />
<circle cx="5" cy="9.7" r="2.3" />
</g>
</g>
</svg>
</button>
</div>
</template>
<style scoped>
@import "theme-toggles/css/around.css";
.ThemeToggle {
margin: 1em 1em;
transition:
transform 0.3s,
opacity 0.3s;
}
.theme-toggle {
background: none;
border: none;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
color: var(--text-color);
}
.theme-toggle:hover {
color: var(--primary-color);
}
</style>

View file

@ -0,0 +1,145 @@
<script setup lang="ts">
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();
const socialLinks = computed(() => {
return theme.value.socialLinks ?? [];
});
const indexList = computed(() => {
return index;
});
</script>
<template>
<div class="TopBar">
<div class="ProfileImageContainer">
<img src="/images/me.jpeg" alt="Simon Einzinger" class="ProfileImage" />
</div>
<h1 class="title">{{ site.title }}</h1>
<p class="subtitle">{{ site.description }}</p>
<div class="RightSideTop">
<ThemeToggle />
</div>
<div class="RightSideBottom">
<div class="social-icons">
<a
v-for="(icon, index) in socialLinks"
:key="index"
:href="icon.link"
target="_blank"
rel="noopener noreferrer"
>
<img :src="icon.icon" :alt="icon.label" class="social-icon" />
</a>
</div>
</div>
</div>
<div class="Separator"></div>
<ul class="IndexList">
<li v-for="{ title, url } of indexList" :key="title">
<a :href="url">#{{ title }}</a>
</li>
</ul>
</template>
<style>
.TopBar {
display: grid;
grid-template-columns: 100px 1fr auto;
grid-template-rows: auto auto;
gap: 0 2em;
align-items: center;
padding: 1em;
}
.ProfileImageContainer {
grid-row: 1 / 3;
grid-column: 1;
width: 100px;
height: 100px;
margin: auto;
overflow: hidden;
border: 2px solid var(--outline-color);
border-radius: 50%;
}
.ProfileImage {
width: 100%;
height: 100%;
object-fit: cover;
}
.title {
grid-column: 2;
grid-row: 1;
margin: 0.75em 0 0;
font-size: 2.5em;
color: var(--primary-color);
}
.subtitle {
grid-column: 2;
grid-row: 2;
margin: 0.25em 0 0;
font-size: 1.5em;
color: var(--text-color);
}
.RightSideTop {
grid-column: 3;
grid-row: 1;
display: flex;
justify-content: flex-end;
}
.RightSideBottom {
grid-column: 3;
grid-row: 2;
display: flex;
justify-content: flex-end;
align-items: center;
}
.social-icons {
display: flex;
margin-left: 1em;
filter: var(--icon-filter);
}
.social-icon {
width: 24px;
height: 24px;
margin: 0 0.4em;
}
.Separator {
border-bottom: 1px solid var(--outline-color);
margin: 0.5em 1em;
}
.IndexList {
list-style: none;
margin: 1em 0;
padding: 0;
display: flex;
justify-content: center;
gap: 1.5em;
}
.IndexList li a {
text-decoration: none;
color: var(--text-color);
padding: 0 0.75em;
}
</style>

View file

@ -0,0 +1,48 @@
:root {
--background-color: #ffffff;
--primary-color: #0066cc;
--text-color: #333333;
--secondary-text-color: #666666;
--highlight-color: #3399ff;
--outline-color: rgba(0, 0, 0, 0.2);
--icon-filter: invert(0);
}
[data-theme="dark"] {
--background-color: #1a1a1a;
--primary-color: #a594f9;
--text-color: #e0e0e0;
--secondary-text-color: #cdc1ff;
--highlight-color: #e5d9f2;
--outline-color: rgba(255, 255, 255, 0.2);
--icon-filter: invert(1);
}
html {
font-family: "VT323", monospace;
background-color: var(--background-color);
color: var(--text-color);
}
a {
position: relative;
color: var(--secondary-text-color);
text-decoration: none;
}
a::after {
content: "";
position: absolute;
left: 0;
bottom: -2px;
width: 0%;
height: 1px;
background-color: var(--primary-color);
transition: width 0.3s;
}
a:hover::after {
width: 100%;
}

5
docs/blog.md Normal file
View file

@ -0,0 +1,5 @@
---
title: Blog
---
<BlogIndex />

3
docs/cv.md Normal file
View file

@ -0,0 +1,3 @@
---
title: CV
---

3
docs/impressum.md Normal file
View file

@ -0,0 +1,3 @@
---
title: Impressum
---

9
docs/index.md Normal file
View file

@ -0,0 +1,9 @@
---
home: true
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)).
Thus, I will be looking forward to showing some cool challenge solutions in the [Blogs](/blog) section.

40
docs/posts/hello.md Normal file
View file

@ -0,0 +1,40 @@
---
title: Hello
date: 2025-01-07
---
Some excerpt for the blog post teasing what it is _about_
---
[[TOC]]
## Some test blog post
with some lorem ipsum text
```python
def hello_world() -> str:
return "Hello, World!"
if __name__ == "__main__":
string = hello_world()
print(string)
```
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
```js
export default {
name: "MyComponent",
};
```
## Hello 2
some more `text` with _awesome_ **formatting**
---
some more text

33
docs/posts/hello2.md Normal file
View file

@ -0,0 +1,33 @@
---
layout: blog
title: Another Hello
date: 2025-01-07
---
Some excerpt for the blog post teasing what it is _about_
---
[[TOC]]
## Some test blog post
with some lorem ipsum text
```py
def hello_world() -> str:
return "Hello, World!"
if __name__ == "__main__":
string = hello_world()
print(string)
```
## Hello 2
some more `text` with _awesome_ _formatting_
---
some more text

View file

@ -0,0 +1,19 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve" fill="currentColor">
<g>
<path d="M510.746,110.361c-2.128-10.754-6.926-20.918-13.926-29.463c-1.422-1.794-2.909-3.39-4.535-5.009
c-12.454-12.52-29.778-19.701-47.531-19.701H67.244c-17.951,0-34.834,7-47.539,19.708c-1.608,1.604-3.099,3.216-4.575,5.067
c-6.97,8.509-11.747,18.659-13.824,29.428C0.438,114.62,0,119.002,0,123.435v265.137c0,9.224,1.874,18.206,5.589,26.745
c3.215,7.583,8.093,14.772,14.112,20.788c1.516,1.509,3.022,2.901,4.63,4.258c12.034,9.966,27.272,15.45,42.913,15.45h377.51
c15.742,0,30.965-5.505,42.967-15.56c1.604-1.298,3.091-2.661,4.578-4.148c5.818-5.812,10.442-12.49,13.766-19.854l0.438-1.05
c3.646-8.377,5.497-17.33,5.497-26.628V123.435C512,119.06,511.578,114.649,510.746,110.361z M34.823,99.104
c0.951-1.392,2.165-2.821,3.714-4.382c7.689-7.685,17.886-11.914,28.706-11.914h377.51c10.915,0,21.115,4.236,28.719,11.929
c1.313,1.327,2.567,2.8,3.661,4.272l2.887,3.88l-201.5,175.616c-6.212,5.446-14.21,8.443-22.523,8.443
c-8.231,0-16.222-2.99-22.508-8.436L32.19,102.939L34.823,99.104z M26.755,390.913c-0.109-0.722-0.134-1.524-0.134-2.341V128.925
l156.37,136.411L28.199,400.297L26.755,390.913z M464.899,423.84c-6.052,3.492-13.022,5.344-20.145,5.344H67.244
c-7.127,0-14.094-1.852-20.142-5.344l-6.328-3.668l159.936-139.379l17.528,15.246c10.514,9.128,23.922,14.16,37.761,14.16
c13.89,0,27.32-5.032,37.827-14.16l17.521-15.253L471.228,420.18L464.899,423.84z M485.372,388.572
c0,0.803-0.015,1.597-0.116,2.304l-1.386,9.472L329.012,265.409l156.36-136.418V388.572z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,3 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/>
</svg>

After

Width:  |  Height:  |  Size: 986 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

BIN
docs/public/images/me.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

2528
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"type": "module",
"devDependencies": {
"vitepress": "^1.5.0",
"@types/markdown-it": "^12.2.3",
"@types/node": "^20.11.27",
"vue": "^3.5.0-beta.3"
},
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"theme-toggles": "^4.10.1"
}
}