WIP
This commit is contained in:
parent
0ed88f38a4
commit
44b7768d6f
31 changed files with 1146 additions and 141 deletions
|
|
@ -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 });
|
||||
|
|
|
|||
101
docs/.vitepress/theme/components/CVIndex.vue
Normal file
101
docs/.vitepress/theme/components/CVIndex.vue
Normal 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>
|
||||
70
docs/.vitepress/theme/composables/cv.data.ts
Normal file
70
docs/.vitepress/theme/composables/cv.data.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
91
docs/.vitepress/theme/layout/CVProjectCard.vue
Normal file
91
docs/.vitepress/theme/layout/CVProjectCard.vue
Normal 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>
|
||||
108
docs/.vitepress/theme/layout/CVTimelineItem.vue
Normal file
108
docs/.vitepress/theme/layout/CVTimelineItem.vue
Normal 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>
|
||||
28
docs/.vitepress/theme/layout/Footer.vue
Normal file
28
docs/.vitepress/theme/layout/Footer.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
104
docs/.vitepress/theme/styles/blogpost.css
Normal file
104
docs/.vitepress/theme/styles/blogpost.css
Normal 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;
|
||||
}
|
||||
78
docs/.vitepress/theme/styles/cards.css
Normal file
78
docs/.vitepress/theme/styles/cards.css
Normal 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;
|
||||
}
|
||||
164
docs/.vitepress/theme/styles/code.css
Normal file
164
docs/.vitepress/theme/styles/code.css
Normal 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 let’s 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);
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
---
|
||||
title: CV
|
||||
---
|
||||
|
||||
<CVPage />
|
||||
|
|
|
|||
8
docs/cv/education-1.md
Normal file
8
docs/cv/education-1.md
Normal 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
7
docs/cv/education-2.md
Normal 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
14
docs/cv/project-1.md
Normal 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
10
docs/cv/project-2.md
Normal 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
8
docs/cv/work-1.md
Normal 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
8
docs/cv/work-2.md
Normal 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
8
docs/cv/work-3.md
Normal 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
8
docs/cv/work-4.md
Normal 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
8
docs/cv/work-5.md
Normal 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
8
docs/cv/work-6.md
Normal 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."
|
||||
---
|
||||
|
|
@ -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
13
docs/legals.md
Normal 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.
|
||||
|
|
@ -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
190
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue