migrate to astro

This commit is contained in:
Alexander Daichendt 2024-12-11 12:57:13 +01:00
parent 82150df591
commit 5e67b2bb0d
135 changed files with 5886 additions and 8330 deletions

View file

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,20 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

9
.gitignore vendored
View file

@ -1,8 +1,3 @@
.DS_Store .astro
dist
node_modules node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example

1
.npmrc
View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,14 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
Link.svelte

View file

@ -1,6 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}

View file

@ -1,26 +1,64 @@
# Alex' small website # daichendt.one
Link to the [website](https://daichendt.one) Personal website built with Astro, TailwindCSS, and MDX.
Powered by Svelte & SvelteKit ## 🚀 Getting Started
## Developing ### Prerequisites
Once you've created a project and installed dependencies with `pnpm install`, start a development server: - [Node.js](https://nodejs.org/) (v20 or higher)
- [pnpm](https://pnpm.io/) (v8 or higher)
### Installation
1. Clone the repository:
```bash ```bash
pnpm dev git clone https://github.com/AlexDaichendt/site
cd site
# or start the server and open the app in a new browser tab
pnpm dev -- --open
``` ```
## Building 2. Install dependencies:
```bash
pnpm install
```
To create a production version of your app: ### Development
Start the development server:
```bash
pnpm dev
```
### Building for Production
Build the project:
```bash ```bash
pnpm build pnpm build
``` ```
You can preview the production build with `pnpm run preview`. Preview the production build:
```bash
pnpm preview
```
## 🛠 Tech Stack
- [Astro](https://astro.build)
- [TailwindCSS](https://tailwindcss.com)
- [MDX](https://mdxjs.com)
- [Sharp](https://sharp.pixelplumbing.com) for image optimization
- [Iconify](https://iconify.design) for icons
## 📦 Key Dependencies
- `@astrojs/mdx` - MDX integration
- `@astrojs/rss` - RSS feed support
- `@astrojs/sitemap` - Sitemap generation
- `@astrojs/tailwind` - TailwindCSS integration
- `@fontsource/ubuntu` - Ubuntu font
- `astro-icon` - Icon component
- `remark-emoji` - Emoji support in markdown
## 🙏 Credits
This theme is based on the [Bear Blog](https://github.com/HermanMartinus/bearblog/) theme.

26
astro.config.mjs Normal file
View file

@ -0,0 +1,26 @@
// @ts-check
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import remarkEmoji from "remark-emoji";
import tailwind from "@astrojs/tailwind";
import icon from "astro-icon";
// https://astro.build/config
export default defineConfig({
prefetch: {
defaultStrategy: "hover",
prefetchAll: true,
},
site: "https://daichendt.one",
integrations: [
mdx({
remarkPlugins: [remarkEmoji],
}),
sitemap(),
tailwind(),
icon(),
],
});

View file

@ -1,22 +0,0 @@
import { defineMDSveXConfig as defineConfig } from 'mdsvex';
import remarkGFM from 'remark-gfm';
import remarkEmoji from 'remark-emoji';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import remarkFootnotes from 'remark-footnotes';
const config = defineConfig({
layout: {
blog: './src/lib/layouts/blog.svelte',
},
extensions: ['.svelte.md', '.md', '.svx'],
smartypants: {
dashes: 'oldschool',
},
remarkPlugins: [remarkGFM, remarkEmoji, remarkFootnotes],
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behaviour: 'append' }]],
});
export default config;

View file

@ -1,58 +1,28 @@
{ {
"name": "daichendt.one", "name": "daichendt.one-astro",
"version": "1.0.0",
"license": "MIT",
"author": {
"email": "me@daichendt.one",
"name": "Alex Daichendt"
},
"type": "module", "type": "module",
"version": "0.0.1",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "astro dev",
"build": "vite build", "build": "astro build",
"package": "svelte-kit package", "preview": "astro preview",
"preview": "vite preview", "astro": "astro"
"postinstall": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-cloudflare": "^1.0.0",
"@sveltejs/kit": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"autoprefixer": "^10.4.13",
"browserslist": "^4.21.4",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"fontaine": "^0.4.1",
"mdi-svelte": "^1.1.2",
"mdsvex": "^0.10.6",
"postcss": "^8.4.20",
"postcss-load-config": "^4.0.1",
"postcss-normalize": "^10.0.1",
"prettier": "^2.8.1",
"prettier-plugin-svelte": "^2.9.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.1.0",
"remark-emoji": "^3.0.2",
"remark-footnotes": "2.0",
"remark-gfm": "^3.0.1",
"svelte": "^3.55.0",
"svelte-check": "^3.0.1",
"svelte-preprocess": "^5.0.0",
"tslib": "^2.4.1",
"typescript": "^4.9.4",
"vite": "^4.0.3",
"vite-imagetools": "^4.0.12"
}, },
"dependencies": { "dependencies": {
"@fontsource/ubuntu-mono": "^4.5.11", "@astrojs/mdx": "^4.0.1",
"@mdi/js": "^7.1.96", "@astrojs/rss": "^4.0.10",
"imagetools-core": "^3.2.3" "@astrojs/sitemap": "^3.2.1",
"@astrojs/tailwind": "^5.1.3",
"@fontsource/ubuntu": "^5.1.0",
"@iconify-json/mdi": "^1.2.1",
"@iconify-json/simple-icons": "^1.2.14",
"astro": "^5.0.3",
"astro-icon": "^1.1.4",
"sharp": "^0.33.5",
"tailwindcss": "^3.4.16"
}, },
"browserslist": "last 2 versions" "devDependencies": {
"@tailwind-plugin/expose-colors": "^1.1.8",
"remark-emoji": "^5.0.1"
}
} }

7296
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

9
public/favicon.svg Normal file
View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

15
src/app.d.ts vendored
View file

@ -1,15 +0,0 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {
// title?: string;
// description?: string;
// keywords?: string[];
// }
}

View file

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-nu-scheme-is="light" data-nu-contrast-is="no-preference">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 551 KiB

After

Width:  |  Height:  |  Size: 551 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

@ -0,0 +1,61 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import "../styles/global.css";
import ubuntuRegularWoff2 from "@fontsource/ubuntu/files/ubuntu-latin-400-normal.woff2?url";
import ubuntuBoldWoff2 from "@fontsource/ubuntu/files/ubuntu-latin-700-normal.woff2?url";
interface Props {
title: string;
description: string;
image?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = "/blog-placeholder-1.jpg" } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<!-- Font preloads, keep them even if it throws a warning for not using them due to the system providing them -->
<link
rel="preload"
href={ubuntuRegularWoff2}
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href={ubuntuBoldWoff2}
as="font"
type="font/woff2"
crossorigin
/>
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />

View file

@ -0,0 +1,148 @@
---
---
<script>
const themeToggleBtns = document.querySelectorAll(
".theme-toggle",
) as NodeListOf<HTMLInputElement>;
const sliders = document.querySelectorAll(".slider");
// Set initial state of toggle based on previous settings
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
themeToggleBtns.forEach((btn) => (btn.checked = true));
document.documentElement.classList.add("dark");
} else {
themeToggleBtns.forEach((btn) => (btn.checked = false));
document.documentElement.classList.remove("dark");
}
// Remove no-transition class after initial load
window.addEventListener("load", () => {
setTimeout(() => {
sliders.forEach((slider) =>
slider.classList.remove("no-transition"),
);
}, 0);
});
themeToggleBtns.forEach((btn) => {
btn.addEventListener("change", function () {
// If is set in localStorage
if (localStorage.getItem("color-theme")) {
if (localStorage.getItem("color-theme") === "light") {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
themeToggleBtns.forEach((btn) => (btn.checked = true));
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
themeToggleBtns.forEach((btn) => (btn.checked = false));
}
} else {
if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
themeToggleBtns.forEach((btn) => (btn.checked = false));
} else {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
themeToggleBtns.forEach((btn) => (btn.checked = true));
}
}
});
});
</script>
<label class="switch" for="theme-toggle">
<span class="sr-only">Toggle dark mode</span>
<input
id="theme-toggle"
class="theme-toggle"
type="checkbox"
role="switch"
aria-checked="false"
/>
<span class="slider round no-transition" aria-hidden="true"></span>
</label>
<style>
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--tw-mytheme-200);
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: gold;
transition: 0.4s;
background: radial-gradient(
yellow,
orange 63%,
transparent calc(63% + 3px) 100%
);
}
input:checked + .slider {
background-color: var(--tw-mytheme-600);
}
input:checked + .slider:before {
background-color: white;
background: radial-gradient(
circle at 19% 19%,
transparent 41%,
var(--tw-mytheme-50) 43%
);
}
input:focus + .slider {
box-shadow: 0 0 5px var(--tw-mytheme-700);
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.no-transition {
transition: none !important;
}
.no-transition:before {
transition: none !important;
}
</style>

View file

@ -0,0 +1,14 @@
---
const today = new Date();
---
<footer class="bg-gray-100/60 dark:bg-mytheme-900 shadow-sm">
&copy; {today.getFullYear()} Alexander Daichendt. All rights reserved.
</footer>
<style>
footer {
padding: 2em 1em 6em 1em;
color: rgb(var(--gray));
text-align: center;
}
</style>

View file

@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View file

@ -0,0 +1,14 @@
---
import HeaderLink from "./HeaderLink.astro";
---
<header class="mb-8 w-full lg:w-[768px] max-w-[calc(100%-2em)] lg:mx-auto">
<nav>
<div class="flex gap-4">
<HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="/publications">Publications</HeaderLink>
<HeaderLink href="/contact">Contact</HeaderLink>
</div>
</nav>
</header>

View file

@ -0,0 +1,56 @@
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"a">;
const { href, class: className, ...props } = Astro.props;
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, "");
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === "/" + (subpath?.[0] || "");
---
<a
href={href}
class:list={[
className,
isActive ? "active" : "",
"p-2 hover:text-mytheme-800 hover:dark:text-mytheme-100 inline-block no-underline relative",
]}
{...props}
>
<slot />
</a>
<style>
/* Hover animation for non-active links */
a:not(.active)::after {
content: "";
position: absolute;
width: 0;
height: 4px;
bottom: 0;
left: 50%;
background-color: var(--tw-mytheme-400);
transition: all 0.3s ease-in-out;
transform: translateX(-50%);
}
a:not(.active):hover::after {
width: 100%;
}
/* Solid underline for active links */
a.active::after {
content: "";
position: absolute;
width: 100%;
height: 4px;
bottom: 0;
left: 0;
background-color: var(--tw-mytheme-400);
}
a.active {
font-weight: bold;
}
</style>

5
src/components/Li.astro Normal file
View file

@ -0,0 +1,5 @@
<li class="pt-2 mr-4" {...Astro.props}>
<div class="self-center">
<slot />
</div>
</li>

27
src/components/Link.astro Normal file
View file

@ -0,0 +1,27 @@
---
interface Props {
href: string;
disablePrefetch?: boolean;
}
const { href, disablePrefetch = false } = Astro.props;
const internal = !href.startsWith("http");
let linkProps = internal
? !disablePrefetch
? { "data-astro-prefetch": `${!disablePrefetch}` }
: {}
: {
rel: "nofollow noreferrer noopener",
target: "_blank",
};
---
<a
{...linkProps}
{href}
class="text-special text-mytheme-700 dark:text-mytheme-300 font-medium hover:bg-outline hover:text-dark no-underline"
>
<span class="underline break-words"><slot /></span></a
>

3
src/components/Ol.astro Normal file
View file

@ -0,0 +1,3 @@
<ol class="list-disc list-inside" {...Astro.props}>
<slot />
</ol>

View file

@ -0,0 +1,9 @@
---
import { Picture as AstroPicture } from "astro:assets";
---
<AstroPicture
src={Astro.props.src}
alt={Astro.props.alt}
formats={["avif", "webp"]}
/>

3
src/components/Ul.astro Normal file
View file

@ -0,0 +1,3 @@
<ul class="list-disc list-outside ml-8" {...Astro.props}>
<slot />
</ul>

6
src/consts.ts Normal file
View file

@ -0,0 +1,6 @@
// Place any global data in this file.
// You can import this data from anywhere in your site by using the `import` keyword.
export const SITE_TITLE = "Alex Daichendt";
export const SITE_DESCRIPTION =
"Alex Daichendt's personal website, blog, and portfolio.";

19
src/content.config.ts Normal file
View file

@ -0,0 +1,19 @@
import { glob } from "astro/loaders";
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
// Type-check frontmatter using a schema
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: image().optional(),
}),
});
export const collections = { blog };

View file

@ -1,5 +1,5 @@
--- ---
created: '2023-01-02' pubDate: '2023-01-02'
title: 'Generate cover letters with ChatGPT' title: 'Generate cover letters with ChatGPT'
description: 'With the help of ChatGPT it is fairly easy to generate custom tailored cover letters for job applications with your own CV' description: 'With the help of ChatGPT it is fairly easy to generate custom tailored cover letters for job applications with your own CV'
keywords: keywords:

View file

@ -1,19 +1,19 @@
--- ---
created: '2024-01-15' pubDate: '2024-01-15'
title: 'Detecting System Management Interrupts (SMIs)' title: 'Detecting System Management Interrupts'
description: '' description: ''
keywords: keywords:
- SMI - SMI
--- ---
# System Management Interrutps (SMI) ## System Management Interrutps (SMIs)
- high priority interrupts caused by the hardware - high priority interrupts caused by the hardware
- transparent to the operating system - transparent to the operating system
- can be used by the mainboard for power management, thermal management, or other system-level functions independent of the OS - can be used by the mainboard for power management, thermal management, or other system-level functions independent of the OS
- can take a long time to execute, causing a CPU core to be blocked from other work - can take a long time to execute, causing a CPU core to be blocked from other work
## Detecting SMIs ### Detecting SMIs
- compile a kernel with hwlat tracing capabilities; usually, a typical Linux kernel has this enabled; if not, the config can be found in the appendix - compile a kernel with hwlat tracing capabilities; usually, a typical Linux kernel has this enabled; if not, the config can be found in the appendix
- after starting the machine with a trace-capable image - after starting the machine with a trace-capable image
@ -22,7 +22,7 @@ keywords:
- there now should be a process "hwlat" running that takes up 50% of one CPU - there now should be a process "hwlat" running that takes up 50% of one CPU
- output of the hwlat tracer available `cat /sys/kernel/debug/tracing/trace` or `cat /sys/kernel/debug/tracing/trace_pipep` - output of the hwlat tracer available `cat /sys/kernel/debug/tracing/trace` or `cat /sys/kernel/debug/tracing/trace_pipep`
## Example Output ### Example Output
``` ```
# tracer: hwlat # tracer: hwlat
@ -41,7 +41,7 @@ keywords:
- inner/outer: where the latency was detected, see next section - inner/outer: where the latency was detected, see next section
### How does it work? #### How does it work?
- this hwlat process is taking timestamps in a loop - this hwlat process is taking timestamps in a loop
- if distance between two timestamps is unreasonably large (bigger than ns), there was an SMI - if distance between two timestamps is unreasonably large (bigger than ns), there was an SMI
@ -62,18 +62,18 @@ keywords:
} }
``` ```
### Further options #### Further options
- by default, only 50% CPU time is used - by default, only 50% CPU time is used
- this can be increased by echoing into `echo 9999999 > /sys/kernel/debug/tracing/hwlat_detector/width`, where the value is smaller than the set window `cat /sys/kernel/debug/tracing/hwlat_detector/window` to avoid starving the system. - this can be increased by echoing into `echo 9999999 > /sys/kernel/debug/tracing/hwlat_detector/width`, where the value is smaller than the set window `cat /sys/kernel/debug/tracing/hwlat_detector/window` to avoid starving the system.
- from my experience, this, however, is not necessary to catch SMIs. The default option is "good enough". - from my experience, this, however, is not necessary to catch SMIs. The default option is "good enough".
## Firing an SMI manually ### Firing an SMI manually
- There is a nice small kernel module [here](https://github.com/jib218/kernel-module-smi-trigger) for manually triggering an SMI to verify the setup - There is a nice small kernel module [here](https://github.com/jib218/kernel-module-smi-trigger) for manually triggering an SMI to verify the setup
- follow the instructions in the readme to compile and load the module - follow the instructions in the readme to compile and load the module
## Hardware Registers for counting SMIs ### Hardware Registers for counting SMIs
- Intel: MSR0x34, can be read out with turbostat / perf - Intel: MSR0x34, can be read out with turbostat / perf
- AMD: ls\_msi\_rx, can be used with `perf stat -e ls_smi_rx -I 60000` - AMD: ls\_msi\_rx, can be used with `perf stat -e ls_smi_rx -I 60000`
@ -81,7 +81,7 @@ However, doesn't seem to count everything; counts seem incorrect
--- ---
## Sources, Appendix ### Sources, Appendix
- https://wiki.linuxfoundation.org/realtime/documentation/howto/tools/hwlat - https://wiki.linuxfoundation.org/realtime/documentation/howto/tools/hwlat
- https://www.kernel.org/doc/html/latest/trace/hwlat_detector.html - https://www.kernel.org/doc/html/latest/trace/hwlat_detector.html

View file

@ -0,0 +1,66 @@
---
pubDate: 2024-12-21
title: Linux on a Huawei MateBook X Pro 2024
description: A guide on what is needed to get Linux running on the Huawei MateBook X Pro 2024.
heroImage: ./images/matebook.jpg
---
I recently bought a Huawei MateBook X Pro 2024. It is a beautiful laptop with a 3:2 aspect ratio display and a touchscreen. The laptop comes with Windows 11 preinstalled. However, I wanted to run Linux on it. Here is a guide on what is needed to get Linux running on the Huawei MateBook X Pro 2024.
Overall, the experience was okay, but not something I would recommend to an average user. There are a fair bit of quirks that need to be ironed out. Especially distros running older kernels will have a hard time. I am running CachyOS with the latest 6.13-rc1 kernel, more on that later.
| Hardware | PCI/USB ID | Status |
| ----------- | ------------------------------------------- | ------------------ |
| CPU | | :white_check_mark: |
| Touchpad | ps/2:7853-7853-bltp7840-00-347d | :white_check_mark: |
| Touchscreen | | :white_check_mark: |
| Keyboard | ps/2:0001-0001-at-translated-set-2-keyboard | :white_check_mark: |
| WiFi | 8086:7e40 | :white_check_mark: |
| Bluetooth | 8087:0033 | :white_check_mark: |
| iGPU | 8086:7d55 | :neutral_face: |
| Audio | 8086:7e28 | :ok: |
| Webcam | 8086:7d19 | :x: |
| Fingerprint | | :x: |
## CPU
The CPU on my SKU is an Intel Meteor Lake Core Ultra 155H. It comes with 6 performance cores, each with 2 threads, 8 efficiency cores, one thread each, and 2 LPE cores. The p and e cores share 24MB of L3 cache. The LPE cores do not have L3 cache and share 2MB L2 cache, which makes them rather slow. Below you can find the output of `lstopo`:
![lstopo](../../assets/lstopo-matebook.png)
Since thread director is not yet supported in the Linux kernel, by default, the scheduler will assign processes to the performance cores--while on battery. A scheduler like bpfland helps, but that still leaves the first, CPU core 0, alive. Disabling the cores manually is also not a good solution as the core 0 can not be deactivated. There used to be a kernel config option, `CONFIG_BOOTPARAM_HOTPLUG_CPU0` which would allow the core to be disabled at runtime, but is no longer available[^1].
Luckily, Intel is developing a tool which utilizes cgroups to enable/disable cores at runtime and moves processes away. If you care about battery life, you might want to configure `intel-lpmd`[^2].
After installing the tool, it must be enabled with `sudo systemctl enable --now intel-lpmd`. Next, enter your p cores into the config file at `/etc/intel_lpmd/intel_lpmd_config.xml`, so if you are running with SMT enabled, it would be the string `0-11` to include the 6 p-cores with 2 threads each. When you are on battery, the tool will disable the p-cores and move processes away. You can verify that it is active with `sudo systemctl status intel_lpmd.service`. For additional battery-savings, you can also disable the e-cores as the L3 cache can then be powered down. I would not recommend it tho.
## Touchpad
The touchpad worked out of the box ever since I got the laptop. I did read that older kernels might not register it.
## Touchscreen, Keyboard, Wifi, Bluetooth
No problems, as far as I can tell, all work out of the box.
## iGPU
This is a big one. Theres a problem with the default i915 driver which causes the iGPU to never go into a low power state. This is a big problem as it drains the battery rather quickly. There is an experimental Intel Xe driver, which fixes this issue. It can be enabled by adding the kernel parameters `i915.force_probe=!7d55 xe.force_probe=7d55` to the kernel command line. The driver is already in mainline, so no need to compile it yourself. However, the driver is still experimental there are several bugs. The screen might flicker from time to time showing rectangular artifacts. The 6.12 or lower Xe driver was highly unstable and caused my system to hard lock every few minutes. The 6.13-rc1 driver is much more stable, asides from the artifacts.
## Audio
The audio works out of the box. But its not great. It seems like not all speakers are fully used. It is good enough for me tho.
## Webcam
The webcam is an ipu6-based camera. Support has been trickling in over the years, but it is unusable at the moment and the forseeable future.
## Fingerprint
The fingerprint sensor is not supported at the moment. It does not even show up anywhere. One of those ingenious Goodix sensors that are not supported by the fprintd library.
---
Sources:
[^1]: https://www.kernelconfig.io/search?q=CONFIG_BOOTPARAM_HOTPLUG_CPU0&kernelversion=6.12.4&arch=x86
[^2]: https://github.com/intel/intel-lpmd

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View file

@ -1,15 +1,12 @@
--- ---
created: '2024-06-11' pubDate: '2024-06-11'
title: "Kagi.com" title: "Kagi.com"
description: "" description: "Thoughts on Kagi.com"
keywords: keywords:
- search engine - search engine
hidden: false hidden: false
heroImage: ./images/kagi_doggo_5.svg
--- ---
<script>
import fastgpt from "./images/fastgpt.png?default"
import Image from "$components/Image.svelte"
</script>
Kagi is a paid search engine providing excellent search that reminds me of what Google was like in the early 2000s. Furthermore, it provides search-enhancing features like specific filters, custom site rankings, and an LLM summary of the search results. Kagi is a paid search engine providing excellent search that reminds me of what Google was like in the early 2000s. Furthermore, it provides search-enhancing features like specific filters, custom site rankings, and an LLM summary of the search results.
In this post, I would like to share my thoughts on Kagi.com and explain why I think it is a great search engine despite recent criticism. In this post, I would like to share my thoughts on Kagi.com and explain why I think it is a great search engine despite recent criticism.
@ -23,7 +20,7 @@ If, for some reason, a bad site appears in the search results, I can easily bloc
## AI Summary ## AI Summary
Kagi's AI will summarize the search results by simply appending a `?` to the end of the search query. LLMs are prone to generating nonsense, but Kagi's AI adds citations with links to the original source. If the AI summary provided helpful information, it was accurate; if it did not, the results were still there. Kagi's AI will summarize the search results by simply appending a `?` to the end of the search query. LLMs are prone to generating nonsense, but Kagi's AI adds citations with links to the original source. If the AI summary provided helpful information, it was accurate; if it did not, the results were still there.
<Image meta={fastgpt} alt="Example search query"/> ![Example search query](../../assets/fastgpt.png)
## Privacy ## Privacy

View file

@ -1,5 +1,5 @@
--- ---
created: '2024-08-25' pubDate: '2024-08-25'
title: "Kata Containers: Custom Kernel Module in Guest" title: "Kata Containers: Custom Kernel Module in Guest"
description: 'How to build a custom kernel module for a Kata Containers guest.' description: 'How to build a custom kernel module for a Kata Containers guest.'
keywords: keywords:

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-09-24' pubDate: '2022-09-24'
title: 'Securing a Caddy endpoint with LLDAP' title: 'Securing a Caddy endpoint with LLDAP'
description: '' description: ''
keywords: keywords:
@ -7,11 +7,6 @@ keywords:
- Caddy - Caddy
--- ---
<script>
import overview from "./lldap_overview.png?default"
import Image from "$components/Image.svelte"
</script>
For my small home network, I was looking around for a solution to synchronize user For my small home network, I was looking around for a solution to synchronize user
accounts across services. I host various services like a file server or smaller web accounts across services. I host various services like a file server or smaller web
applications that are accessed by my significant other and a couple of friends. In the applications that are accessed by my significant other and a couple of friends. In the
@ -46,7 +41,7 @@ bridge for networking so that I can resolve my other services with DNS. After th
navigate to http://IP:17170 and are presented with the administration panel, where we can navigate to http://IP:17170 and are presented with the administration panel, where we can
create users and groups. create users and groups.
<Image meta={overview} alt="LLDAP Userinterface"/> ![LLDAP Userinterface](../../assets/lldap_overview.png)
## Integration with Caddy ## Integration with Caddy

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-07-27' pubDate: '2022-07-27'
title: 'How to: Run a DPDK application in an LXC container' title: 'How to: Run a DPDK application in an LXC container'
description: '' description: ''
keywords: keywords:

View file

@ -1,5 +1,5 @@
--- ---
created: '2023-03-12' pubDate: '2023-03-12'
title: '[Workaround] High idle power consumption with AMD GPUs' title: '[Workaround] High idle power consumption with AMD GPUs'
description: There is a bug with AMD gpus on Linux that causes the memory to run at full speed even when the GPU is idle for certain refresh rates and resolutions. This causes high idle power consumption. This post explains how to fix it. description: There is a bug with AMD gpus on Linux that causes the memory to run at full speed even when the GPU is idle for certain refresh rates and resolutions. This causes high idle power consumption. This post explains how to fix it.
keywords: keywords:
@ -17,19 +17,13 @@ keywords:
- linux kernel - linux kernel
- kernel - kernel
hidden: false hidden: false
updated: '2023-08-02' updatedDate: '2023-08-02'
--- ---
<script>
import hz120 from "./images/_120hz.png?default"
import hz144low from "./images/_144hzlow.png?default"
import Image from "$components/Image.svelte"
</script>
For many years, AMD GPUs have had a bug that causes the memory to run at full speed even when the GPU is idle for certain refresh rates and resolutions. For example, I am running a 34' UWQHD 3440x1440 monitor with 144Hz refresh rate. My GPU is a 6700 XT with 16GB of VRAM. When I set the resolution to 3440x1440 and the refresh rate to 144Hz, the memory frequency is stuck at 1000 MHz, or as Windows would report it 2000 Mhz. The high memory frequency consumes about 30 W of power - not doing anything at all. For many years, AMD GPUs have had a bug that causes the memory to run at full speed even when the GPU is idle for certain refresh rates and resolutions. For example, I am running a 34' UWQHD 3440x1440 monitor with 144Hz refresh rate. My GPU is a 6700 XT with 16GB of VRAM. When I set the resolution to 3440x1440 and the refresh rate to 144Hz, the memory frequency is stuck at 1000 MHz, or as Windows would report it 2000 Mhz. The high memory frequency consumes about 30 W of power - not doing anything at all.
<Image meta={hz120} alt="High power draw and high mem frequency"/> ![High power draw and high mem frequency](../../assets/_120hz.png)
This bug apparently existed on Windows as well, but was fixed some time in 2021. There were some tries to fix the issue on Linux in the past, but obviously nothing worked - as I found out now running the latest kernel 6.2. This bug apparently existed on Windows as well, but was fixed some time in 2021. There were some tries to fix the issue on Linux in the past, but obviously nothing worked - as I found out now running the latest kernel 6.2.
@ -57,7 +51,7 @@ The power draw is now as expected at around 8W. The memory downclocks to 96Mhz.
This might not seem like a lot, but in situations like this I have to think about how many GPUs were sold globally and are affected by this bug. It's is not an insignificant amount of power that is wasted on a global scale. This might not seem like a lot, but in situations like this I have to think about how many GPUs were sold globally and are affected by this bug. It's is not an insignificant amount of power that is wasted on a global scale.
<Image meta={hz144low} alt="High power draw and high mem frequency"/> ![Lower power draw with clocked down mem frequency](../../assets/_144hzlow.png)
_Update: 2021-08-02_: _Update: 2021-08-02_:

View file

@ -1,5 +1,5 @@
--- ---
created: '2023-02-04' pubDate: '2023-02-04'
title: You don't need an AIO title: You don't need an AIO
description: AIOs are overrated tech that's recommended too frequently without considering the downsides description: AIOs are overrated tech that's recommended too frequently without considering the downsides
keywords: keywords:
@ -9,7 +9,7 @@ keywords:
- CPU cooler - CPU cooler
--- ---
Recently, I have been browsing hardware forums to get in touch with the current state of computer hardware again. I am in the process of upgrading my 6-year-old rig. Up until yesterday, I had a Corsair AIO (All in one) CPU cooler with a 280mm radiator cooling my heavily overclocked CPU. Unfortunately, this AIO is not supported by Liquidctl, and the other legacy software for controlling it no longer works. So I was stuck with a piece of working hardware that I could no longer configure on my operating system of choice. I ended up buying a nice Noctua CPU cooler and ever since my PC is more enjoyable to use in every aspect. Let's get into the details, of why I think AIOs are (for most people) a bad pick. Recently, I have been browsing hardware forums to get in touch with the current state of computer hardware again. I am in the process of upgrading my 6-year-old rig. Up until yesterday, I had a Corsair AIO (All in one) CPU cooler with a 280mm radiator cooling my heavily overclocked CPU. Unfortunately, this AIO is not supported by Liquidctl, the software to control AIOs on Linux, and the other legacy software for controlling it no longer works. So I was stuck with a piece of working hardware that I could no longer configure on my operating system of choice. I ended up buying a nice Noctua CPU cooler and ever since my PC is more enjoyable to use in every aspect. Let's get into the details, of why I think AIOs are (for most people) a bad pick.
(this is not a Noctua-sponsored post) (this is not a Noctua-sponsored post)

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-08-04' pubDate: '2022-08-04'
title: 'Free, on-demand image optimizations with Cloudflare' title: 'Free, on-demand image optimizations with Cloudflare'
description: '' description: ''
keywords: keywords:
@ -10,12 +10,8 @@ keywords:
hidden: true hidden: true
--- ---
<script>
import architecture from "./_architecture.png?default"
import Image from "$components/Image.svelte"
</script>
I wanted to use responsive images for my small page of cute [cats](/cat). Since I wanted to use responsive images for my small page of cute [cats](/cat) (now removed as of 8/12/2024). Since
one of the design goals is to give my significant other, who by the way loves one of the design goals is to give my significant other, who by the way loves
cats a lot more than me, the option to add cats on the fly and also consume the cats a lot more than me, the option to add cats on the fly and also consume the
cat pictures in other services, I require dynamic image optimization. cat pictures in other services, I require dynamic image optimization.
@ -43,7 +39,7 @@ optimization program on a Raspberry Pi at my place. It is connected with a
Cloudflare tunnel to the internet. Any other device that has computing power and Cloudflare tunnel to the internet. Any other device that has computing power and
is accessible via the internet is alright. is accessible via the internet is alright.
<Image meta={architecture} /> ![Architecture](../../assets/_catapi_architecture.png)
- **(1)**: a user uploads a new image, for example via this site - **(1)**: a user uploads a new image, for example via this site
- **(2)**: the worker processes forwards the image to the image optimization server - **(2)**: the worker processes forwards the image to the image optimization server

View file

@ -1,5 +1,5 @@
--- ---
created: '2023-06-19' pubDate: '2023-06-19'
title: 'Optimzing the Guild Wars 2 Gear Optimizer' title: 'Optimzing the Guild Wars 2 Gear Optimizer'
description: '' description: ''
keywords: keywords:

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-05-08' pubDate: '2022-05-08'
title: 'How to: Arrow OS on Redmi Note 7, root, microG' title: 'How to: Arrow OS on Redmi Note 7, root, microG'
description: 'Learn how to install ArrowOS, based on Android 12 on your Redmi Note 7 (lavender) phone! Also installs root and microG for a BigTech free phone.' description: 'Learn how to install ArrowOS, based on Android 12 on your Redmi Note 7 (lavender) phone! Also installs root and microG for a BigTech free phone.'
keywords: keywords:

View file

@ -1,6 +1,6 @@
--- ---
created: '2022-09-27' pubDate: '2022-09-27'
updated: '2022-10-22' updatedDate: '2022-10-22'
title: 'Site 2 Site Wireguard VPN with a Mikrotik Router and a Cloud VM' title: 'Site 2 Site Wireguard VPN with a Mikrotik Router and a Cloud VM'
description: '' description: ''
keywords: keywords:
@ -11,13 +11,6 @@ keywords:
- vpn - vpn
--- ---
<script>
import architecture from "./images/_architecture.drawio.png?default"
import peer from "./images/mikrotik_peer.png?default"
import Image from "$components/Image.svelte"
</script>
My network consists of a server located in country A. Since the largest ISP in the country My network consists of a server located in country A. Since the largest ISP in the country
B does have terrible peering with the ISP in country A, I thought of setting up a small B does have terrible peering with the ISP in country A, I thought of setting up a small
proxy server in country A. This way, I should be able to bypass bad peering, since the proxy server in country A. This way, I should be able to bypass bad peering, since the
@ -25,7 +18,7 @@ cloud provider probably organizes good routing to both sides. Since I meant to t
Oracles free tier anyway, it seemed like a good opportunity to learn ansible properly and Oracles free tier anyway, it seemed like a good opportunity to learn ansible properly and
develop with IaC scripts to set up a reverse proxy in the cloud. develop with IaC scripts to set up a reverse proxy in the cloud.
<Image meta={architecture} alt="Architecture of the site two site wireguard setup"/> ![Architecture of the site two site wireguard setup](../../assets/site2sitewireguard/_architecture.drawio.png)
1. Create Wireguard keys. If the CLI is not an option [this 1. Create Wireguard keys. If the CLI is not an option [this
website](https://www.wireguardconfig.com/) is cool too (keys are client-sided generated) website](https://www.wireguardconfig.com/) is cool too (keys are client-sided generated)
@ -35,7 +28,7 @@ develop with IaC scripts to set up a reverse proxy in the cloud.
3. Create a new peer as follows. Important is the entry to allow the IP address of the 3. Create a new peer as follows. Important is the entry to allow the IP address of the
cloud wg endpoint, otherwise the cloud cant ping back home. cloud wg endpoint, otherwise the cloud cant ping back home.
<div style="max-width:600px"> <div style="max-width:600px">
<Image meta={peer} alt="Mikrotik router peer setup"/> ![Mikrotik router peer setup](../../assets/site2sitewireguard/mikrotik_peer.png)
</div> </div>
4. I had to adjust the firewall rules to allow communication with the tunnel network. 4. I had to adjust the firewall rules to allow communication with the tunnel network.
5. On the proxy server we use similar settings. Interestingly enough, the Mikrotik wg 5. On the proxy server we use similar settings. Interestingly enough, the Mikrotik wg

View file

@ -1,6 +1,6 @@
--- ---
created: '2022-07-26' pubDate: '2022-07-26'
updated: '2022-07-27' updatedDate: '2022-07-27'
title: 'Software recommendations for privacy conscious people' title: 'Software recommendations for privacy conscious people'
description: '' description: ''
keywords: keywords:

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-09-29' pubDate: '2022-09-29'
title: "My Bachelor's thesis journey at TUM" title: "My Bachelor's thesis journey at TUM"
description: '' description: ''
keywords: keywords:

View file

@ -1,6 +1,7 @@
--- ---
created: '2024-08-10' pubDate: '2024-08-10'
title: "Experience with MSI warranty for a monitor" updatedDate: '2024-12-08'
title: "Experience with MSI's warranty for a monitor"
description: "Positive review of MSI's warranty service for a monitor." description: "Positive review of MSI's warranty service for a monitor."
keywords: keywords:
- warranty - warranty
@ -10,6 +11,7 @@ keywords:
- bug - bug
- thunderfly - thunderfly
hidden: false hidden: false
heroImage: ./images/msi.png
--- ---
I bought a new 32' 4k MSI Monitor for my birthday a couple of months ago. Since there was not much choice when it came to my requirements: I bought a new 32' 4k MSI Monitor for my birthday a couple of months ago. Since there was not much choice when it came to my requirements:
@ -20,7 +22,7 @@ I bought a new 32' 4k MSI Monitor for my birthday a couple of months ago. Since
- 4k, 32', - 4k, 32',
I ended up buying the MSI MAG 323UPF, albeit its 'gamer' design. The device and its productivity features are awesome, and I'm very happy with them. I ended up buying the MSI MAG 323UPF, albeit its 'gamer' design. The device and its productivity features are awesome, and I'm very happy with them.
One day, a couple of dead pixels appeared. I was not sure if they were dead pixels or bugs, but they were not moving. Since they were about 8 pixels in a perfect line, I assumed it was a bug, but I do not know for sure. I was devastated. One day, a couple of dead pixels appeared. I was not sure if they were dead pixels or bugs, but they were not moving. Since they were about 8 pixels in a perfect line, I assumed it was a bug, but I do not know for sure. Searching the web it seems common occurence. I was devastated.
None of the tricks online worked: None of the tricks online worked:
- setup another light source to lure it outside - setup another light source to lure it outside
@ -30,7 +32,7 @@ None of the tricks online worked:
Maybe it was a juicy one and died in the middle of the screen. Maybe it was a juicy one and died in the middle of the screen.
I contacted MSI support, and they were very helpful. After uploading a picture, it did not take 5 minutes to receive a return label. A day later, the UPS guy picked it up at my door. The package was then delivered to Poland (I live in Germany). Once it arrived there, it only took a couple of hours until I received an email claiming that the monitor was defective, and I got a free replacement immediately. I contacted MSI support, and they were very helpful. After uploading a picture, it did not take 5 minutes to receive a return label. A day later, the UPS guy picked it up at my door. The package was then delivered to Poland (I live in Germany). Once it arrived there, it only took a couple of hours until I received an email claiming that the monitor was defective, and I got a free replacement immediately. Granted, I bribed the technician with a pack of Haribo gummy bears, but I'm sure that was not the reason for the quick replacement. A little kindness goes a long way.
Kudos to MSI for their excellent support. Whether it was a bug or a dead pixel, neither should happen on a new monitor. I'm glad they stick to their warranty without any hassle. Kudos to MSI for their excellent support. Whether it was a bug or a dead pixel, neither should happen on a new monitor. I'm glad they stick to their warranty without any hassle.

View file

@ -1,5 +1,5 @@
--- ---
created: '2022-08-12' pubDate: '2022-08-12'
title: "Do's and don't when writing a thesis" title: "Do's and don't when writing a thesis"
description: 'Useful tips for avoiding common mistakes when writing a thesis. Includes recommendations for writing, formatting, figures and Latex.' description: 'Useful tips for avoiding common mistakes when writing a thesis. Includes recommendations for writing, formatting, figures and Latex.'
keywords: keywords:

77
src/data/publications.ts Normal file
View file

@ -0,0 +1,77 @@
export const publications = [
{
authors: [
"Florian Wiedner",
"Max Helm",
"Alexander Daichendt",
"Jonas Andre",
"Georg Carle",
],
title:
"Performance evaluation of containers for low-latency packet processing in virtualized network environments",
journal: "Performance Evaluation",
volume: "166",
date: "Nov. 2024",
pages: "102442",
links: {
doi: "https://doi.org/10.1016/j.peva.2024.102442",
pdf: "http://www.net.in.tum.de/fileadmin/bibtex/publications/papers/wiedner-helm-2024-peva.pdf",
homepage: "https://wiednerf.github.io/container-in-low-latency/",
bibtex: "/publications/bibtex/WiedHelm24Container.bib",
},
},
{
authors: [
"Alexander Daichendt",
"Florian Wiedner",
"Jonas Andre",
"Georg Carle",
],
title:
"Applicability of Hardware-Supported Containers in Low-Latency Networking",
conference:
"20th International Conference on Network and Service Management (CNSM 2024)",
location: "Prague, Czech Republic",
date: "Oct. 2024",
links: {
pdf: "http://www.net.in.tum.de/fileadmin/bibtex/publications/papers/wiedner_2024_cnsm.pdf",
homepage: "https://tumi8.github.io/applicability-hwsupported-containers",
bibtex: "https://www.net.in.tum.de/publications/bibtex/Wied24CNSM.bib",
},
},
{
authors: [
"Florian Wiedner",
"Alexander Daichendt",
"Jonas Andre",
"Georg Carle",
],
title: "Control Groups Added Latency in NFVs: An Update Needed?",
conference:
"2023 IEEE Conference on Network Function Virtualization and Software Defined Networks (NFV-SDN)",
date: "Nov. 2023",
links: {
pdf: "https://www.net.in.tum.de/fileadmin/bibtex/publications/papers/wiedner_nfvsdn2023.pdf",
homepage: "https://wiednerf.github.io/cgroups-nfv/",
bibtex: "/publications/bibtex/wiedner2023containercgroups.bib",
},
},
{
authors: [
"Florian Wiedner",
"Max Helm",
"Alexander Daichendt",
"Jonas Andre",
"Georg Carle",
],
title:
"Containing Low Tail-Latencies in Packet Processing Using Lightweight Virtualization",
conference: "2023 35rd International Teletraffic Congress (ITC-35)",
date: "Oct. 2023",
links: {
pdf: "https://www.net.in.tum.de/fileadmin/bibtex/publications/papers/wiedner_itc35.pdf",
homepage: "https://wiednerf.github.io/containerized-low-latency/",
bibtex: "/publications/bibtex/wiedner2023container.bib",
},
},
];

View file

@ -0,0 +1,75 @@
---
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
import DarkModeToggle from "../components/DarkModeToggle.astro";
interface Props {
title?: string;
description?: string;
}
const { title = SITE_TITLE, description = SITE_DESCRIPTION } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<script is:inline>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
</head>
<body class="bg-white dark:bg-gray-900 text-black dark:text-white">
<!-- Mobile layout -->
<div class="lg:hidden flex flex-col min-h-screen p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="font-bold text-lg">
<a href="/">{SITE_TITLE}</a>
</h2>
<DarkModeToggle />
</div>
<Header />
<main class="flex-grow">
<slot />
</main>
</div>
<!-- Desktop layout -->
<div
class="hidden lg:grid grid-cols-[150px_1fr_60px] gap-4 min-h-screen p-4"
>
<div class="flex items-start">
<h2 class="font-bold text-xl mt-3">
<a href="/">{SITE_TITLE}</a>
</h2>
</div>
<div>
<Header />
<main
class="w-full lg:w-[768px] max-w-[calc(100%-2em)] mx-auto p-2"
>
<slot />
</main>
</div>
<div class="flex justify-end">
<DarkModeToggle />
</div>
</div>
<Footer />
</body>
</html>

View file

@ -0,0 +1,78 @@
---
import type { CollectionEntry } from "astro:content";
import FormattedDate from "../components/FormattedDate.astro";
import BaseLayout from "./BaseLayout.astro";
import { Picture } from "astro:assets";
type Props = CollectionEntry<"blog">["data"];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
---
<BaseLayout title={title} description={description}>
<article>
<div class="hero-image">
{
heroImage && (
<Picture src={heroImage} alt="" width={1020} height={510} />
)
}
</div>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on{" "}
<FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</BaseLayout>
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 768px;
max-width: calc(100% - 2em);
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>

View file

@ -1,27 +0,0 @@
<script lang="ts">
import type { ImageMetadata } from '$lib/utils/types';
export let metadata: ImageMetadata[];
export let sizes: string;
const fallback = metadata[metadata.length - 1];
const _metadata = metadata.slice(0, metadata.length - 1);
const srcset = _metadata
.map(({ href, width }) => `https://cats.daichendt.one/${href} ${width}w`)
.join(',');
</script>
{#if !fallback && !metadata}
No metadata supplied
{:else}
<img {srcset} class="image" alt="A cute kitty" {sizes} loading="lazy" />
{/if}
<style>
.image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
</style>

View file

@ -1,77 +0,0 @@
<script lang="ts">
let copied = false;
function copyToClipboard(event: MouseEvent) {
// @ts-ignore
const target = event.target?.innerText.split('\n')[0];
navigator.clipboard.writeText(target);
// select the double clicked node
let sel = document.getSelection();
let range = new Range();
range.selectNode(event.target?.firstChild);
sel?.removeAllRanges();
sel?.addRange(range);
if (!copied) {
copied = true;
setTimeout(() => (copied = false), 3000);
}
}
</script>
<code on:dblclick={copyToClipboard}>
<span class="text">
<slot />
</span>
<div class:copied class="copyWrapper">
{#if copied}
Copied
{:else}
Double click to copy
{/if}
</div>
</code>
<style>
.copied {
background-color: yellowgreen !important;
min-width: 3rem !important;
}
.copyWrapper {
background-color: var(--special-color);
margin-bottom: 0.2rem;
padding: 0.2rem;
border-radius: 3px;
position: absolute;
min-width: 10rem;
visibility: hidden;
z-index: 1;
bottom: 100%;
left: 0;
margin-left: 0px;
}
code:hover .copyWrapper {
visibility: visible;
}
code {
position: relative;
display: inline-block;
background-color: var(--light-color);
border-radius: 5px;
box-shadow: 0 0 5px var(--shadow-color);
padding: 2px;
border: 1px solid var(--border-color);
line-break: anywhere;
}
:global([data-nu-scheme-is='dark'] body code:not([class*='language-'])) {
color: var(--bg-color);
}
:global(pre[class*='language-']) {
margin: 0.5em 1rem !important;
}
:global(code[class*='language-'], pre[class*='language-']) {
font-size: 0.9rem;
}
</style>

View file

@ -1,9 +0,0 @@
<hr />
<style>
hr {
background-color: var(--special-color);
border: none;
height: 1px;
}
</style>

View file

@ -1,47 +0,0 @@
<script context="module">
const year = new Date().getFullYear();
</script>
<script>
import { mdiCopyright } from '@mdi/js';
import Icon from 'mdi-svelte';
import Link from './Link.svelte';
</script>
<footer>
<!-- container class inherited from __layout-->
<div class="container">
<p>Copyright <Icon path={mdiCopyright} size="1rem" /> {year} Alexander Daichendt</p>
<div class="footerLinks">
<Link href="/cat">Meeeeeow</Link>
<Link href="/privacy">Privacy Policy</Link>
<Link href="/impressum">Impressum</Link>
<Link href="https://github.com/AlexDaichendt/site">Source</Link>
</div>
</div>
</footer>
<style>
footer {
background-color: var(--special-bg-color);
padding: 2rem;
margin-top: 4rem;
}
@media screen and (max-width: 500px) {
.footerLinks {
flex-direction: column;
}
}
.footerLinks {
display: flex;
justify-content: space-between;
}
:global(footer div a) {
color: var(--text-soft-color) !important;
}
:global(footer div a:hover) {
color: var(--light-color) !important;
}
</style>

View file

@ -1,104 +0,0 @@
<script>
import ThemeSwitcher from './ThemeSwitcher.svelte';
import { page } from '$app/stores';
const NAV_ITEMS = [
{ href: '/', label: 'Home' },
{ href: '/blog', label: 'Blog' },
{ href: '/publications', label: 'Publications' },
{ href: '/projects', label: 'Projects' },
{ href: '/contact', label: 'Contact' },
];
</script>
<header>
<div class="header">
<a href="/">
<h1>Alex Daichendt</h1>
</a>
<ThemeSwitcher />
</div>
<nav>
<ol class="navList">
{#each NAV_ITEMS as navItem}
<li
class="navItem {$page.url.pathname === navItem.href ||
(navItem.href === '/blog' && $page.url.pathname.includes('/blog'))
? 'active'
: ''}"
>
<a href={navItem.href}>{navItem.label}</a>
</li>
{/each}
</ol>
</nav>
</header>
<style>
header {
margin-bottom: 3rem;
}
.active {
font-weight: 600;
}
.navList {
padding: 0;
}
.navItem {
display: inline;
}
.navItem:not(:last-child)::after {
content: '·';
margin-right: 0.5rem;
}
.navItem a:hover {
color: var(--text-strong-color);
}
.navItem a:after {
content: '';
position: absolute;
bottom: -2px;
right: 50%;
width: 0%;
border-bottom: 3px solid var(--outline-color);
transition: 0.3s;
}
.navItem a:before {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0%;
border-bottom: 3px solid var(--outline-color);
transition: 0.3s;
}
.navItem a:hover:after {
width: 50%;
}
.navItem a:hover:before {
width: 50%;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.header h1 {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
color: var(--special-color);
}
a {
text-decoration: none;
color: inherit;
font-size: 1.2rem;
position: relative;
}
ol {
list-style-type: none;
}
</style>

View file

@ -1,168 +0,0 @@
<script lang="ts">
/**
* the output of a vite-imagetools import, using the `meta` query for output
* format
*
* full type:
* [code](https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/output-formats.ts),
* [docs](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/guide/getting-started.md#metadata)
*/
export let meta: { src: string; width: number; format: string }[];
// if there is only one, vite-imagetools won't wrap the object in an array
if (!(meta instanceof Array)) meta = [meta];
// all images by format
let sources = new Map<string, typeof meta>();
meta.map((m) => sources.set(m.format, []));
meta.map((m) => (sources.get(m.format) ?? []).push(m));
// fallback image: first resolution of last format
let image = (sources.get([...sources.keys()].slice(-1)[0]) ?? [])[0];
/**
* `source` attribute. default: width of the first resolution specified in the
* import.
*/
export let sizes = '100vw';
/** `img` attribute */
export let alt: string;
/** `img` attribute */
export let loading: string = 'lazy';
</script>
<!--
@component
takes the output of a vite-imagetools import (using the `meta` output format)
and generates a `<picture>` with `<source>` tags and an `<img>`.
usage
- in `global.d.ts`
```typescript
declare module "*&imagetools" {
const out;
export default out;
}
```
- in svelte file
- typescript
```typescript
import Image from "$lib/Image.svelte";
import me from "$lib/assets/me.jpg?w=200;400&format=webp;png&meta&imagetools";
```
- html
```html
<span><Image meta="{me}" alt="me" /></span>
```
- it's not necessary to wrap it in a `<span>`, but i like to avoid unnested
`:global()` selectors in svelte css
- scss
```scss
span :global(img) {
border-radius: 50%;
}
```
example generated `<picture>`
```html
<picture>
<source
sizes="200px"
type="image/webp"
srcset="
/_app/assets/me-3cfc7c5f.webp 200w,
/_app/assets/me-ab564f98.webp 400w
"
/>
<source
sizes="200px"
type="image/png"
srcset="
/_app/assets/me-2bc09a6d.png 200w,
/_app/assets/me-6f16cc18.png 400w
"
/>
<img src="/_app/assets/me-2bc09a6d.png" alt="me" />
</picture>
```
notes
- from the documentation for
[`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes),
> The selected source size affects the intrinsic size of the image (the
> images display size if no CSS styling is applied). If the srcset
> attribute is absent, or contains no values with a width descriptor, then
> the sizes attribute has no effect.
there are other things that may also affect the intrinsic (and separately,
display) size of the image, but this is all we set here.
- the `&imagetools` in the usage above is to make typescript happy. there are
other workarounds, if you'd prefer a differnet one
https://github.com/JonasKruckenberg/imagetools/issues/160
- it'd be nice if we could just use a plain `<img>` tag, but in my bit of
testing that didn't seem to allow for multiple formats. i was also tempted
to just use png, but in my bit of testing the webp file was only ~10% (!)
the size of the png.
assumptions
- this counts on vite-imagetools returning metadata objects in the same order
as the query values are specified
- e.g. for `?width=100;200&format=webp;png&meta` we expect the source with
`width=100` to come before the one with `width=200`, and likewise for
`webp` and `png`
- i don't think this is guaranteed, so hopefully it doesn't change. looks
like it depends on this bit of code
https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/lib/resolve-configs.ts#L17
references
- responsive images
- mdn https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
- css-tricks https://css-tricks.com/a-guide-to-the-responsive-images-syntax-in-html/
- web
- html
- picture https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
- source https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source
- img https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
- js
- Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
- ts
- wildcard module declarations https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations
- docs
- vite-imagetools https://github.com/JonasKruckenberg/imagetools/tree/main/docs
- other
- how to generate srcset https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/output-formats.ts
- `Map` preserves insertion order https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
- you don't set elements on `Map` objects the way you do on regular objects
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
(this was really hard to figure out lol)
- vite-imagetools extensions (to make the import query string shorter)
- docs https://github.com/JonasKruckenberg/imagetools/blob/main/docs/guide/extending.md
- code (options) https://github.com/JonasKruckenberg/imagetools/blob/main/packages/vite/src/types.ts
-->
<picture>
{#each [...sources.entries()] as [format, meta]}
<source
{sizes}
type="image/{format}"
srcset={meta.map((m) => `${m.src} ${m.width}w`).join(', ')}
/>
{/each}
<img src={image.src} {alt} {loading} />
</picture>
<style>
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
</style>

View file

@ -1,53 +0,0 @@
<script lang="ts">
import { mdiLinkVariant } from '@mdi/js';
import { mdiChevronRight } from '@mdi/js';
import Icon from 'mdi-svelte';
export let href: string;
export let disableIcon = false;
export let disablePrefetch = false;
// svelte-ignore unused-export-let
export let rel = '';
const internal = !href.startsWith('http');
// external props
let props: Record<string,string|boolean> = {
rel: "nofollow noreferrer noopener",
target: "_blank"
}
if (internal) {
// internal props
if (!disablePrefetch ){
props = {
"data-sveltekit-prefetch": ""
}
}
}
</script>
<a
{...$$props}
{...props}
{href}
>
<span class="text"><slot /></span>
{#if !disableIcon && !internal}
<Icon path={internal ? mdiChevronRight : mdiLinkVariant} size="1rem" />
{/if}
</a><style>
a {
color: var(--special-color);
text-decoration: none;
font-weight: 550;
}
a:hover {
background-color: var(--outline-color);
color: var(--dark-color)
}
.text {
text-decoration: underline;
word-wrap: break-word;
}
</style>

View file

@ -1,11 +0,0 @@
<script lang="ts">
export let id: string | undefined = undefined;
</script>
<li {id}><slot /></li>
<style>
li {
margin: 0.4rem 0;
}
</style>

View file

@ -1,39 +0,0 @@
<script lang="ts">
import { mdiChevronDoubleUp } from '@mdi/js';
import Icon from 'mdi-svelte';
let y: number = 0;
$: enabled = y > 100;
function onClick() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
<svelte:window bind:scrollY={y} />
{#if enabled}
<button on:click={onClick}><Icon path={mdiChevronDoubleUp} /></button>
{/if}
<style>
button:focus {
box-shadow: 0 0 5px var(--special-shadow-color);
}
button {
border-radius: 35px;
width: 48px;
height: 48px;
position: fixed;
bottom: 32px;
right: 32px;
color: var(--light-color);
border: 1px solid var(--special-shadow-color);
background-color: var(--dark-color);
}
button:hover {
border: 1px solid var(--shadow-color);
color: var(--dark-color);
background-color: var(--light-color);
}
</style>

View file

@ -1,25 +0,0 @@
<script lang="ts">
import { page } from '$app/stores';
export let title = "Alex Daichendt's website";
export let keywords: string[] = [];
export let description: string = '';
let seo = $page.data?.seo;
if (seo) {
title = seo.title ? `${seo.title} - Alex Daichendt` : "Alex Daichendt's website";
description = seo.description;
keywords = seo.keywords || [];
}
</script>
<svelte:head
><title>{title}</title>
{#if description.length > 0}
<meta name="description" content={description} />
{/if}
<meta name="author" content="Alexander Daichendt" />
{#if keywords.length > 0}
<meta name="keywords" content={keywords.join(',')} />
{/if}
</svelte:head>

View file

@ -1,51 +0,0 @@
<script>
</script>
<div tabindex="0">
<table id="table">
<slot />
</table>
</div>
<style>
div {
border-radius: 0.5rem;
padding: 1rem;
box-shadow: 0px 0px 2px var(--shadow-color);
border: 1px solid var(--outline-color);
overflow: auto;
}
table {
border-collapse: collapse;
width: 100%;
}
:global(#table thead th) {
padding-bottom: 0.25rem;
padding-left: 1rem;
padding-right: 1rem;
border: solid;
width: 350px;
margin: auto;
border-top: none;
border-left: none;
border-right: none;
border-bottom: none;
background: linear-gradient(var(--special-color), var(--special-color)) bottom
/* left or right or else */ no-repeat;
background-size: 50% 2px;
}
:global(#table tbody tr) {
border-bottom: 1px solid var(--outline-color);
}
:global(#table tbody tr:last-child) {
border-bottom: none;
}
:global(#table tbody td) {
padding: 0.5rem;
}
</style>

View file

@ -1,138 +0,0 @@
<script lang="js">
// @ts-nocheck
import { onMount } from 'svelte';
let checked = false;
onMount(() => {
const ROOT = document.querySelector(':root');
const DARK = 'dark';
const LIGHT = 'light';
const HIGH = 'more';
const LOW = 'no-preference';
const SCHEMES = [DARK, LIGHT];
const CONTRASTS = [HIGH, LOW];
function observeContext(data) {
if (data.find((record) => !record.attributeName.endsWith('-is'))) {
setScheme();
setContrast();
}
}
const schemeMedia = matchMedia('(prefers-color-scheme: dark)');
const contrastMedia = matchMedia('(prefers-contrast: more)');
let globalScheme = schemeMedia.matches ? DARK : LIGHT;
let globalContrast = contrastMedia.matches ? HIGH : LOW;
schemeMedia.addListener((_media) => {
globalScheme = _media.matches ? DARK : LIGHT;
setScheme();
});
contrastMedia.addListener((_media) => {
globalContrast = _media.matches ? HIGH : LOW;
setContrast();
});
function setScheme() {
const setting = ROOT.dataset.nuScheme;
ROOT.dataset.nuSchemeIs =
(setting !== 'auto' && SCHEMES.includes(setting) && setting) || globalScheme;
}
function setContrast() {
const setting = ROOT.dataset.nuContrast;
ROOT.dataset.nuContrastIs =
(setting !== 'auto' && CONTRASTS.includes(setting) && setting) || globalContrast;
}
const observer = new MutationObserver((data) => observeContext(data));
observer.observe(ROOT, {
characterData: false,
attributes: true,
childList: false,
subtree: false,
});
setScheme();
// adjust the theme selector
checked = globalScheme === DARK;
setContrast();
// Switch to dark scheme
// ROOT.dataset.nuContrast = 'more';
// Increase contrast
// ROOT.dataset.nuScheme = 'dark';
});
function toggleTheme() {
const root = document.querySelector(':root');
const theme = root.dataset['nuSchemeIs'];
if (theme === 'light') {
root.dataset['nuScheme'] = 'dark';
} else {
root.dataset['nuScheme'] = 'light';
}
}
</script>
<label class="switch">
<input aria-label="Nightmode" type="checkbox" bind:checked on:change={toggleTheme} />
<span class="slider round" />
</label>
<style>
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: lightskyblue;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: '';
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: gold;
transition: 0.4s;
background: radial-gradient(yellow, orange 63%, transparent calc(63% + 3px) 100%);
}
input:checked + .slider {
background-color: var(--special-mark-color);
}
input:checked + .slider:before {
background-color: white;
background: radial-gradient(circle at 19% 19%, transparent 41%, var(--outline-color) 43%);
}
input:focus + .slider {
box-shadow: 0 0 5px var(--special-shadow-color);
}
input:checked + .slider:before {
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
</style>

View file

@ -1,22 +0,0 @@
<script lang="ts">
import type { Thumbnail } from '$lib/utils/types';
export let thumbnail: Thumbnail;
</script>
<svg width="100%" height="100%" viewBox="0 0 {thumbnail.width} {thumbnail.height}">
<filter id="blur" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="20 20" edgeMode="duplicate" />
<feComponentTransfer>
<feFuncA type="discrete" tableValues="1 1" />
</feComponentTransfer>
</filter>
<image
filter="url(#blur)"
xlink:href="data:image/jpeg;base64,{thumbnail.value}"
x="0"
y="0"
height="100%"
width="100%"
/>
</svg>

View file

@ -1,29 +0,0 @@
<svg
width="1.5rem"
height="1.5rem"
viewBox="0 0 54 54"
style="vertical-align: middle"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M19.4414 3.24C19.4414 1.4506 20.892 0 22.6814 0C34.6108 0 44.2814 9.67065 44.2814 21.6C44.2814 23.3894 42.8308 24.84 41.0414 24.84C39.252 24.84 37.8014 23.3894 37.8014 21.6C37.8014 13.2494 31.032 6.48 22.6814 6.48C20.892 6.48 19.4414 5.0294 19.4414 3.24Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M34.5586 50.76C34.5586 52.5494 33.108 54 31.3186 54C19.3893 54 9.71861 44.3294 9.71861 32.4C9.71861 30.6106 11.1692 29.16 12.9586 29.16C14.748 29.16 16.1986 30.6106 16.1986 32.4C16.1986 40.7505 22.9681 47.52 31.3186 47.52C33.108 47.52 34.5586 48.9706 34.5586 50.76Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.24 34.5601C1.4506 34.5601 -6.34076e-08 33.1095 -1.41625e-07 31.3201C-6.63074e-07 19.3907 9.67065 9.72007 21.6 9.72007C23.3894 9.72007 24.84 11.1707 24.84 12.9601C24.84 14.7495 23.3894 16.2001 21.6 16.2001C13.2495 16.2001 6.48 22.9695 6.48 31.3201C6.48 33.1095 5.0294 34.5601 3.24 34.5601Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M50.76 19.4399C52.5494 19.4399 54 20.8905 54 22.6799C54 34.6093 44.3294 44.2799 32.4 44.2799C30.6106 44.2799 29.16 42.8293 29.16 41.0399C29.16 39.2505 30.6106 37.7999 32.4 37.7999C40.7505 37.7999 47.52 31.0305 47.52 22.6799C47.52 20.8905 48.9706 19.4399 50.76 19.4399Z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,40 +0,0 @@
<script lang="ts">
export let title: string;
export let authors: string[];
export let conference: string;
export let pdf: string;
</script>
<li>
<a class="main" href={pdf}>
<span>
{#each authors as author, i}
{#if i != authors.length && i != 0}
,
{/if}
{#if author == 'Alexander Daichendt'}
<b>{author}</b>
{:else}
{author}
{/if}
{/each}
</span>
{title}
{conference}
</a>
</li>
<style>
.main {
display: flex;
flex-direction: column;
}
a {
text-decoration: none;
color: inherit;
margin-bottom: 8px;
}
</style>

View file

@ -1,14 +0,0 @@
<script lang="ts">
export let id: string | undefined;
</script>
<sup {id}><slot /></sup>
<style>
sup {
vertical-align: top;
position: relative;
top: -0.3em;
left: -0.2em;
}
</style>

View file

@ -1,76 +0,0 @@
<script context="module">
// @ts-ignore
import components from './components';
const { a, table, code, li, hr, sup } = components;
export { a, table, code, li, hr, sup };
</script>
<script>
// @ts-nocheck
import { mdiCalendar } from '@mdi/js';
import { mdiPencil } from '@mdi/js';
import Icon from 'mdi-svelte';
import Divider from '$components/Divider.svelte';
import '$lib/utils/one-dark.css';
import SEO from '$components/SEO.svelte';
// svelte-ignore unused-export-let
export let data;
// svelte-ignore unused-export-let
export let form;
export let title;
// svelte-ignore unused-export-let
export let description;
export let created;
export let updated = '';
// svelte-ignore unused-export-let
export let keywords;
// svelte-ignore unused-export-let
export let hidden = false;
</script>
<SEO {title} {description} {keywords} />
<h1>{title}</h1>
{#if updated || created}
<aside role="note">
{#if updated}
<Icon path={mdiPencil} size="0.8rem" /> updated {new Date(updated).toLocaleDateString(
'en-GB',
)};
{/if}
{#if created}
<Icon path={mdiCalendar} size="0.8rem" /> created
{new Date(created).toLocaleDateString('en-GB')}
{/if}
</aside>
{/if}
<Divider />
<slot />
<style>
aside {
font-weight: 200;
font-size: 0.8rem;
margin-bottom: 1rem;
}
:global(h1, h2, h3, h4) {
display: flex;
align-items: center;
justify-content: flex-start;
}
:global(h1 a, h2 a, h3 a, h4 a) {
text-decoration: none;
font-size: 0.7em;
margin-left: 0.5rem;
opacity: 0;
transition: opacity 0.2s ease-in-out 0.1s;
}
:global(h1:hover a, h2:hover a, h3:hover a, h4:hover a) {
opacity: 0.8;
}
:global(.icon-link::before) {
content: '🔗';
}
</style>

View file

@ -1,17 +0,0 @@
import Link from '../components/Link.svelte';
import Table from '../components/Table.svelte';
import Code from '../components/Code.svelte';
import ListItem from '../components/ListItem.svelte';
import Divider from '../components/Divider.svelte';
import sup from '../components/sup.svelte';
const components = {
a: Link,
table: Table,
code: Code,
li: ListItem,
hr: Divider,
sup,
};
export default components;

View file

@ -1,443 +0,0 @@
/**
* One Dark theme for prism.js
* Based on Atom's One Dark theme: https://github.com/atom/atom/tree/master/packages/one-dark-syntax
*/
/**
* One Dark colours (accurate as of commit 8ae45ca on 6 Sep 2018)
* From colors.less
* --mono-1: hsl(220, 14%, 71%);
* --mono-2: hsl(220, 9%, 55%);
* --mono-3: hsl(220, 10%, 40%);
* --hue-1: hsl(187, 47%, 55%);
* --hue-2: hsl(207, 82%, 66%);
* --hue-3: hsl(286, 60%, 67%);
* --hue-4: hsl(95, 38%, 62%);
* --hue-5: hsl(355, 65%, 65%);
* --hue-5-2: hsl(5, 48%, 51%);
* --hue-6: hsl(29, 54%, 61%);
* --hue-6-2: hsl(39, 67%, 69%);
* --syntax-fg: hsl(220, 14%, 71%);
* --syntax-bg: hsl(220, 13%, 18%);
* --syntax-gutter: hsl(220, 14%, 45%);
* --syntax-guide: hsla(220, 14%, 71%, 0.15);
* --syntax-accent: hsl(220, 100%, 66%);
* From syntax-variables.less
* --syntax-selection-color: hsl(220, 13%, 28%);
* --syntax-gutter-background-color-selected: hsl(220, 13%, 26%);
* --syntax-cursor-line: hsla(220, 100%, 80%, 0.04);
*/
code[class*='language-'],
pre[class*='language-'] {
background: hsl(220, 13%, 18%);
color: hsl(220, 14%, 71%);
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: 'Fira Code', 'Fira Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Selection */
code[class*='language-']::-moz-selection,
code[class*='language-'] *::-moz-selection,
pre[class*='language-'] *::-moz-selection {
background: hsl(220, 13%, 28%);
color: inherit;
text-shadow: none;
}
code[class*='language-']::selection,
code[class*='language-'] *::selection,
pre[class*='language-'] *::selection {
background: hsl(220, 13%, 28%);
color: inherit;
text-shadow: none;
}
/* Code blocks */
pre[class*='language-'] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
/* Inline code */
:not(pre) > code[class*='language-'] {
padding: 0.2em 0.3em;
border-radius: 0.3em;
white-space: normal;
}
/* Print */
@media print {
code[class*='language-'],
pre[class*='language-'] {
text-shadow: none;
}
}
.token.comment,
.token.prolog,
.token.cdata {
color: hsl(220, 10%, 40%);
}
.token.doctype,
.token.punctuation,
.token.entity {
color: hsl(220, 14%, 71%);
}
.token.attr-name,
.token.class-name,
.token.boolean,
.token.constant,
.token.number,
.token.atrule {
color: hsl(29, 54%, 61%);
}
.token.keyword {
color: hsl(286, 60%, 67%);
}
.token.property,
.token.tag,
.token.symbol,
.token.deleted,
.token.important {
color: hsl(355, 65%, 65%);
}
.token.selector,
.token.string,
.token.char,
.token.builtin,
.token.inserted,
.token.regex,
.token.attr-value,
.token.attr-value > .token.punctuation {
color: hsl(95, 38%, 62%);
}
.token.variable,
.token.operator,
.token.function {
color: hsl(207, 82%, 66%);
}
.token.url {
color: hsl(187, 47%, 55%);
}
/* HTML overrides */
.token.attr-value > .token.punctuation.attr-equals,
.token.special-attr > .token.attr-value > .token.value.css {
color: hsl(220, 14%, 71%);
}
/* CSS overrides */
.language-css .token.selector {
color: hsl(355, 65%, 65%);
}
.language-css .token.property {
color: hsl(220, 14%, 71%);
}
.language-css .token.function,
.language-css .token.url > .token.function {
color: hsl(187, 47%, 55%);
}
.language-css .token.url > .token.string.url {
color: hsl(95, 38%, 62%);
}
.language-css .token.important,
.language-css .token.atrule .token.rule {
color: hsl(286, 60%, 67%);
}
/* JS overrides */
.language-javascript .token.operator {
color: hsl(286, 60%, 67%);
}
.language-javascript
.token.template-string
> .token.interpolation
> .token.interpolation-punctuation.punctuation {
color: hsl(5, 48%, 51%);
}
/* JSON overrides */
.language-json .token.operator {
color: hsl(220, 14%, 71%);
}
.language-json .token.null.keyword {
color: hsl(29, 54%, 61%);
}
/* MD overrides */
.language-markdown .token.url,
.language-markdown .token.url > .token.operator,
.language-markdown .token.url-reference.url > .token.string {
color: hsl(220, 14%, 71%);
}
.language-markdown .token.url > .token.content {
color: hsl(207, 82%, 66%);
}
.language-markdown .token.url > .token.url,
.language-markdown .token.url-reference.url {
color: hsl(187, 47%, 55%);
}
.language-markdown .token.blockquote.punctuation,
.language-markdown .token.hr.punctuation {
color: hsl(220, 10%, 40%);
font-style: italic;
}
.language-markdown .token.code-snippet {
color: hsl(95, 38%, 62%);
}
.language-markdown .token.bold .token.content {
color: hsl(29, 54%, 61%);
}
.language-markdown .token.italic .token.content {
color: hsl(286, 60%, 67%);
}
.language-markdown .token.strike .token.content,
.language-markdown .token.strike .token.punctuation,
.language-markdown .token.list.punctuation,
.language-markdown .token.title.important > .token.punctuation {
color: hsl(355, 65%, 65%);
}
/* General */
.token.bold {
font-weight: bold;
}
.token.comment,
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.namespace {
opacity: 0.8;
}
/* Plugin overrides */
/* Selectors should have higher specificity than those in the plugins' default stylesheets */
/* Show Invisibles plugin overrides */
.token.token.tab:not(:empty):before,
.token.token.cr:before,
.token.token.lf:before,
.token.token.space:before {
color: hsla(220, 14%, 71%, 0.15);
text-shadow: none;
}
/* Toolbar plugin overrides */
/* Space out all buttons and move them away from the right edge of the code block */
div.code-toolbar > .toolbar.toolbar > .toolbar-item {
margin-right: 0.4em;
}
/* Styling the buttons */
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span {
background: hsl(220, 13%, 26%);
color: hsl(220, 9%, 55%);
padding: 0.1em 0.4em;
border-radius: 0.3em;
}
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus {
background: hsl(220, 13%, 28%);
color: hsl(220, 14%, 71%);
}
/* Line Highlight plugin overrides */
/* The highlighted line itself */
.line-highlight.line-highlight {
background: hsla(220, 100%, 80%, 0.04);
}
/* Default line numbers in Line Highlight plugin */
.line-highlight.line-highlight:before,
.line-highlight.line-highlight[data-end]:after {
background: hsl(220, 13%, 26%);
color: hsl(220, 14%, 71%);
padding: 0.1em 0.6em;
border-radius: 0.3em;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */
}
/* Hovering over a linkable line number (in the gutter area) */
/* Requires Line Numbers plugin as well */
pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before {
background-color: hsla(220, 100%, 80%, 0.04);
}
/* Line Numbers and Command Line plugins overrides */
/* Line separating gutter from coding area */
.line-numbers.line-numbers .line-numbers-rows,
.command-line .command-line-prompt {
border-right-color: hsla(220, 14%, 71%, 0.15);
}
/* Stuff in the gutter */
.line-numbers .line-numbers-rows > span:before,
.command-line .command-line-prompt > span:before {
color: hsl(220, 14%, 45%);
}
/* Match Braces plugin overrides */
/* Note: Outline colour is inherited from the braces */
.rainbow-braces .token.token.punctuation.brace-level-1,
.rainbow-braces .token.token.punctuation.brace-level-5,
.rainbow-braces .token.token.punctuation.brace-level-9 {
color: hsl(355, 65%, 65%);
}
.rainbow-braces .token.token.punctuation.brace-level-2,
.rainbow-braces .token.token.punctuation.brace-level-6,
.rainbow-braces .token.token.punctuation.brace-level-10 {
color: hsl(95, 38%, 62%);
}
.rainbow-braces .token.token.punctuation.brace-level-3,
.rainbow-braces .token.token.punctuation.brace-level-7,
.rainbow-braces .token.token.punctuation.brace-level-11 {
color: hsl(207, 82%, 66%);
}
.rainbow-braces .token.token.punctuation.brace-level-4,
.rainbow-braces .token.token.punctuation.brace-level-8,
.rainbow-braces .token.token.punctuation.brace-level-12 {
color: hsl(286, 60%, 67%);
}
/* Diff Highlight plugin overrides */
/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */
pre.diff-highlight > code .token.token.deleted:not(.prefix),
pre > code.diff-highlight .token.token.deleted:not(.prefix) {
background-color: hsla(353, 100%, 66%, 0.15);
}
pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection,
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection {
background-color: hsla(353, 95%, 66%, 0.25);
}
pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection,
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection {
background-color: hsla(353, 95%, 66%, 0.25);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix),
pre > code.diff-highlight .token.token.inserted:not(.prefix) {
background-color: hsla(137, 100%, 55%, 0.15);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection,
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection {
background-color: hsla(135, 73%, 55%, 0.25);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection,
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection {
background-color: hsla(135, 73%, 55%, 0.25);
}
/* Previewers plugin overrides */
/* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-dark-ui */
/* Border around popup */
.prism-previewer.prism-previewer:before,
.prism-previewer-gradient.prism-previewer-gradient div {
border-color: hsl(224, 13%, 17%);
}
/* Angle and time should remain as circles and are hence not included */
.prism-previewer-color.prism-previewer-color:before,
.prism-previewer-gradient.prism-previewer-gradient div,
.prism-previewer-easing.prism-previewer-easing:before {
border-radius: 0.3em;
}
/* Triangles pointing to the code */
.prism-previewer.prism-previewer:after {
border-top-color: hsl(224, 13%, 17%);
}
.prism-previewer-flipped.prism-previewer-flipped.after {
border-bottom-color: hsl(224, 13%, 17%);
}
/* Background colour within the popup */
.prism-previewer-angle.prism-previewer-angle:before,
.prism-previewer-time.prism-previewer-time:before,
.prism-previewer-easing.prism-previewer-easing {
background: hsl(219, 13%, 22%);
}
/* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */
/* For time, this is the alternate colour */
.prism-previewer-angle.prism-previewer-angle circle,
.prism-previewer-time.prism-previewer-time circle {
stroke: hsl(220, 14%, 71%);
stroke-opacity: 1;
}
/* Stroke colours of the handle, direction point, and vector itself */
.prism-previewer-easing.prism-previewer-easing circle,
.prism-previewer-easing.prism-previewer-easing path,
.prism-previewer-easing.prism-previewer-easing line {
stroke: hsl(220, 14%, 71%);
}
/* Fill colour of the handle */
.prism-previewer-easing.prism-previewer-easing circle {
fill: transparent;
}

View file

@ -1,62 +0,0 @@
export interface BlogPostFrontmatter {
created: string;
updated?: string;
title: string;
description: string;
keywords: string[];
hidden: boolean;
}
export interface BlogPostMeta extends BlogPostFrontmatter {
href: string;
}
export interface ProjectsFrontmatter {
title: string;
description: string;
keywords: string[];
inProgress: boolean;
}
export interface ProjectMeta extends ProjectsFrontmatter {
href: string;
}
export interface Skill {
name: string;
years: number;
started: number;
}
export interface ImageMetadata {
href: string;
mime: string;
width: number;
}
export interface Thumbnail {
value: string;
width: number;
height: number;
}
export interface PageData {
seo: {
title: string;
description: string;
keywords: string[];
};
}
export interface PublicationFrontmatter {
created: string;
title: string;
authors: string[];
conference: string;
pdf: string; // url to pdf
keywords: string[];
hidden: boolean;
}
export interface PublicationMeta extends PublicationFrontmatter {
href: string;
}

View file

@ -1,17 +0,0 @@
export const createLoadObserver = (handler: () => void) => {
let waiting = 0;
const onload = (el) => {
waiting++;
console.log(waiting);
el.addEventListener('load', () => {
waiting--;
console.log('waiting ' + waiting);
if (waiting === 0) {
handler();
}
});
};
return onload;
};

14
src/modules.d.ts vendored
View file

@ -1,14 +0,0 @@
declare module 'mdi-svelte' {
import { SvelteComponentTyped } from 'svelte';
export interface IconProps {
path: string;
size?: number | string;
color?: string;
flip?: boolean | string;
rotate?: number;
spin?: number | boolean;
title?: string;
}
export default class Icon extends SvelteComponentTyped<IconProps> {}
}

View file

@ -0,0 +1,34 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";
import { render } from "astro:content";
import Picture from "../../components/Picture.astro";
import Ul from "../../components/Ul.astro";
import Ol from "../../components/Ol.astro";
import Li from "../../components/Li.astro";
import Link from "../../components/Link.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<"blog">;
const post = Astro.props;
const { Content } = await render(post);
const components = {
ul: Ul,
ol: Ol,
li: Li,
img: Picture,
a: Link,
};
---
<BlogPost {...post.data}>
<Content components={components} />
</BlogPost>

View file

@ -0,0 +1,33 @@
---
import { getCollection } from "astro:content";
import FormattedDate from "../../components/FormattedDate.astro";
import BaseLayout from "../../layouts/BaseLayout.astro";
const posts = (await getCollection("blog")).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<BaseLayout>
<section class="max-w-4xl mx-auto">
<ul class="">
{
posts.map((post) => (
<li class="hover:dark:bg-gray-700 hover:bg-gray-100 p-2 rounded">
<a
href={`/blog/${post.id}`}
class="grid grid-cols-[2fr,1fr] gap-4 items-center"
>
<h6 class="text-lg font-medium">
{post.data.title}
</h6>
<span class="date text-right text-gray-600 text-sm">
<FormattedDate date={post.data.pubDate} />
</span>
</a>
</li>
))
}
</ul>
</section>
</BaseLayout>

71
src/pages/contact.astro Normal file
View file

@ -0,0 +1,71 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { Icon } from "astro-icon/components";
---
<BaseLayout title="Contact">
<ul class="space-y-2">
<li class="flex items-center space-x-3">
<span class="flex items-center">
<Icon name="mdi:email" class="w-6 h-6" />
<span class="ml-2 font-medium">E-Mail:</span>
</span>
<a
href="mailto:me@daichendt.one"
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
target="_blank"
rel="nofollow noreferrer noopener"
>
me@daichendt.one
</a>
<a
href="/pub.key"
class="ml-2 text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
target="_blank"
rel="nofollow noreferrer noopener"
>
<Icon name="mdi:key" class="w-4 h-4 inline" />
PGP key
</a>
</li>
<li class="flex items-center space-x-3">
<span class="flex items-center">
<Icon name="mdi:github" class="w-6 h-6" />
<span class="ml-2 font-medium">Github:</span>
</span>
<a
href="https://github.com/AlexDaichendt"
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
target="_blank"
rel="nofollow noreferrer noopener"
>
AlexDaichendt
</a>
</li>
<li class="flex items-center space-x-3">
<span class="flex items-center">
<Icon name="simple-icons:element" class="w-6 h-6" />
<span class="ml-2 font-medium">Element:</span>
</span>
<span class="text-gray-700 dark:text-gray-300">
@alexdaichendt:matrix.org
</span>
</li>
<li class="flex items-center space-x-3">
<span class="flex items-center">
<Icon name="simple-icons:bluesky" class="w-6 h-6" />
<span class="ml-2 font-medium">Bluesky:</span>
</span>
<a
href="https://bsky.app/profile/alexdaichendt.bsky.social"
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
target="_blank"
rel="nofollow noreferrer noopener"
>
@alexdaichendt.bsky.social
</a>
</li>
</ul>
</BaseLayout>

58
src/pages/index.astro Normal file
View file

@ -0,0 +1,58 @@
---
import Link from "../components/Link.astro";
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout>
<h1>Hi, my name is Alex!</h1>
<p>
I am a software engineer, Linux enthusiast and a friend of lightweight,
resilient systems.
</p>
<p>
My journey in the tech world has been a dynamic one. I've immersed
myself in countless projects spanning various video games and, for the
past few years, have been maintaining a small homelab, which ignited my
passion for DevOps / SRE. I am a privacy enthusiast and advocate for
non-invasive software. Occasionally, I channel my creativity into
building sleek web applications that prioritize efficiency and adhere to
web standards and best practices.
</p>
<p>
I currently work as a software engineer at <Link href="https://tv1.eu"
>TV1 GmbH</Link
>, a company that provides innovative live streaming and communication
solutions. In my free time, I am pursuing my Master's degree in computer
science at <Link href="https://www.tum.de/">TUM</Link>, where I
contribute to various research papers.
</p>
<h2 class="my-8">Skills</h2>
<div class="flex flex-wrap gap-4">
<div class="flex-[0_1_calc(50%-0.5rem)]">
<strong>Core stack:</strong><br />
Typescript/Node.js, Python, Rust, SQL, vanilla HTML/css
</div>
<div class="flex-[0_1_calc(50%-0.5rem)]">
<strong>Frontend:</strong><br />
NextJS, SvelteKit, Astro, Tailwind
</div>
<div class="flex-[0_1_calc(50%-0.5rem)]">
<strong>Ops:</strong><br />
Prometheus, Docker, Kubernetes, AWS, Github/lab actions
</div>
<div class="flex-[0_1_calc(50%-0.5rem)]">
<strong>Misc:</strong><br />
git, agile, REST, GraphQL, web performance, Linux sysadmin, Vitest
</div>
<div class="flex-[0_1_calc(50%-0.5rem)]">
<strong>Languages:</strong><br />
German (native), English
</div>
</div>
</BaseLayout>

View file

@ -0,0 +1,97 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { publications } from "../data/publications";
---
<BaseLayout title="Publications">
<h2>Conference papers</h2>
<ul class="space-y-4">
{
publications.map((pub) => (
<li class="rounded-lg border border-gray-200 dark:border-gray-700 p-4 hover:shadow-lg transition-shadow">
<div class="space-y-2">
<p class="text-lg dark:text-gray-200">
{pub.authors.join(", ")}
</p>
<p class="text-xl font-semibold dark:text-gray-100">
"{pub.title}"
</p>
<p class="text-gray-600 dark:text-gray-400">
{pub.conference ||
pub.journal +
", Volume " +
pub.volume +
", " +
pub.date +
", " +
pub.pages}
</p>
{pub.location && (
<p class="text-gray-600 dark:text-gray-400">
{pub.location}, {pub.date}
</p>
)}
{pub.links && (
<div class="flex gap-4 mt-3">
{pub.links.pdf && (
<a
href={pub.links.pdf}
class="text-mytheme-600 dark:text-mytheme-400 hover:underline flex items-center gap-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M9 2a2 2 0 00-2 2v8a2 2 0 002 2h6a2 2 0 002-2V6.414A2 2 0 0016.414 5L14 2.586A2 2 0 0012.586 2H9z" />
<path d="M3 8a2 2 0 012-2v10h8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z" />
</svg>
PDF
</a>
)}
{pub.links.homepage && (
<a
href={pub.links.homepage}
class="text-mytheme-600 dark:text-mytheme-400 hover:underline flex items-center gap-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" />
</svg>
Homepage
</a>
)}
{pub.links.bibtex && (
<a
href={pub.links.bibtex}
class="text-mytheme-600 dark:text-mytheme-400 hover:underline flex items-center gap-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z"
clip-rule="evenodd"
/>
</svg>
BibTeX
</a>
)}
</div>
)}
</div>
</li>
))
}
</ul>
</BaseLayout>

16
src/pages/rss.xml.js Normal file
View file

@ -0,0 +1,16 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.id}/`,
})),
});
}

View file

@ -1,166 +0,0 @@
<script lang="ts">
import MoveUpButton from '$components/MoveUpButton.svelte';
import Footer from '$lib/components/Footer.svelte';
import Header from '$lib/components/Header.svelte';
import '@fontsource/ubuntu-mono/400.css';
</script>
<div class="container upper">
<Header />
<main>
<slot />
</main>
</div>
<MoveUpButton />
<Footer />
<style>
@import 'normalize.css';
@import 'sanitize.css';
.upper {
min-height: calc(100vh - 4rem - 8px);
}
:global(.container) {
max-width: 50rem;
margin: 0 auto;
padding: 0 1rem;
overflow-x: hidden;
}
:global(body) {
background-color: var(--subtle-color);
color: var(--text-color);
transition: background-color 0.3s ease-in;
font-family: 'Ubuntu Mono', monospace;
line-height: 1.4;
overflow-y: scroll;
margin: 0;
margin-top: 8px;
}
:global([data-nu-scheme-is='light'][data-nu-contrast-is='no-preference'] body) {
--bg-color: rgba(255, 255, 255, 1);
--text-color: rgba(44, 50, 32, 1);
--text-soft-color: rgba(71, 76, 63, 1);
--text-strong-color: rgba(74, 74, 74, 1);
--subtle-color: rgba(248, 250, 245, 1);
--border-color: rgba(225, 234, 210, 1);
--shadow-color: rgba(181, 197, 148, 1);
--input-color: rgba(255, 255, 255, 1);
--outline-color: rgba(249, 201, 163, 1);
--mark-color: rgba(224, 146, 38, 0.08);
--special-color: rgba(224, 146, 38, 1);
--special-bg-color: rgba(229, 149, 39, 1);
--special-text-color: rgba(255, 255, 255, 1);
--special-shadow-color: rgba(149, 112, 81, 1);
--special-mark-color: rgba(255, 255, 255, 0.08);
--light-color: rgba(254, 241, 233, 1);
--dark-color: rgba(89, 66, 46, 1);
--text-color-rgb: 44, 50, 32;
--bg-color-rgb: 255, 255, 255;
--subtle-color-rgb: 248, 250, 245;
--special-color-rgb: 224, 146, 38;
--special-text-color-rgb: 255, 255, 255;
--special-bg-color-rgb: 229, 149, 39;
--shadow-color-rgb: 181, 197, 148;
--special-shadow-color-rgb: 149, 112, 81;
--outline-color-rgb: 249, 201, 163;
--dark-color-rgb: 89, 66, 46;
--light-color-rgb: 254, 241, 233;
}
:global(html[data-nu-scheme-is='dark'][data-nu-contrast-is='no-preference'] body) {
--bg-color: rgba(32, 32, 32, 1);
--text-color: rgba(216, 227, 196, 1);
--text-soft-color: rgba(184, 189, 176, 1);
--text-strong-color: rgba(187, 187, 187, 1);
--subtle-color: rgba(34, 37, 28, 1);
--border-color: rgba(44, 51, 27, 1);
--shadow-color: rgba(0, 0, 0, 1);
--input-color: rgba(28, 28, 28, 1);
--outline-color: rgba(217, 151, 82, 1);
--mark-color: rgba(170, 113, 47, 0.08);
--special-color: rgba(170, 113, 47, 1);
--special-bg-color: rgba(151, 100, 40, 1);
--special-text-color: rgba(230, 221, 216, 1);
--special-shadow-color: rgba(58, 41, 28, 1);
--special-mark-color: rgba(230, 221, 216, 0.08);
--light-color: rgba(249, 203, 168, 1);
--dark-color: rgba(68, 49, 32, 1);
--text-color-rgb: 216, 227, 196;
--bg-color-rgb: 32, 32, 32;
--subtle-color-rgb: 34, 37, 28;
--special-color-rgb: 170, 113, 47;
--special-text-color-rgb: 230, 221, 216;
--special-bg-color-rgb: 151, 100, 40;
--shadow-color-rgb: 0, 0, 0;
--special-shadow-color-rgb: 58, 41, 28;
--outline-color-rgb: 217, 151, 82;
--dark-color-rgb: 68, 49, 32;
--light-color-rgb: 249, 203, 168;
}
:global(html[data-nu-scheme-is='light'][data-nu-contrast-is='more'] body) {
--bg-color: rgba(255, 255, 255, 1);
--text-color: rgba(29, 34, 20, 1);
--text-soft-color: rgba(31, 33, 26, 1);
--text-strong-color: rgba(74, 74, 74, 1);
--subtle-color: rgba(248, 250, 245, 1);
--border-color: rgba(173, 186, 149, 1);
--shadow-color: rgba(147, 162, 117, 1);
--input-color: rgba(255, 255, 255, 1);
--outline-color: rgba(248, 179, 114, 1);
--mark-color: rgba(181, 117, 28, 0.16);
--special-color: rgba(181, 117, 28, 1);
--special-bg-color: rgba(186, 120, 29, 1);
--special-text-color: rgba(255, 255, 255, 1);
--special-shadow-color: rgba(85, 63, 44, 1);
--special-mark-color: rgba(255, 255, 255, 0.16);
--light-color: rgba(254, 241, 233, 1);
--dark-color: rgba(89, 66, 46, 1);
--text-color-rgb: 29, 34, 20;
--bg-color-rgb: 255, 255, 255;
--subtle-color-rgb: 248, 250, 245;
--special-color-rgb: 181, 117, 28;
--special-text-color-rgb: 255, 255, 255;
--special-bg-color-rgb: 186, 120, 29;
--shadow-color-rgb: 147, 162, 117;
--special-shadow-color-rgb: 85, 63, 44;
--outline-color-rgb: 248, 179, 114;
--dark-color-rgb: 89, 66, 46;
--light-color-rgb: 254, 241, 233;
}
:global(html[data-nu-scheme-is='dark'][data-nu-contrast-is='more'] body) {
--bg-color: rgba(32, 32, 32, 1);
--text-color: rgba(233, 243, 215, 1);
--text-soft-color: rgba(236, 241, 227, 1);
--text-strong-color: rgba(187, 187, 187, 1);
--subtle-color: rgba(34, 37, 28, 1);
--border-color: rgba(77, 83, 66, 1);
--shadow-color: rgba(0, 0, 0, 1);
--input-color: rgba(28, 28, 28, 1);
--outline-color: rgba(226, 157, 85, 1);
--mark-color: rgba(188, 126, 53, 0.16);
--special-color: rgba(188, 126, 53, 1);
--special-bg-color: rgba(148, 98, 39, 1);
--special-text-color: rgba(243, 238, 236, 1);
--special-shadow-color: rgba(0, 0, 0, 1);
--special-mark-color: rgba(243, 238, 236, 0.16);
--light-color: rgba(251, 223, 203, 1);
--dark-color: rgba(68, 49, 32, 1);
--text-color-rgb: 233, 243, 215;
--bg-color-rgb: 32, 32, 32;
--subtle-color-rgb: 34, 37, 28;
--special-color-rgb: 188, 126, 53;
--special-text-color-rgb: 243, 238, 236;
--special-bg-color-rgb: 148, 98, 39;
--shadow-color-rgb: 0, 0, 0;
--special-shadow-color-rgb: 0, 0, 0;
--outline-color-rgb: 226, 157, 85;
--dark-color-rgb: 68, 49, 32;
--light-color-rgb: 251, 223, 203;
}
</style>

View file

@ -1,95 +0,0 @@
<script lang="ts">
import SEO from '$components/SEO.svelte';
import ListItem from '$components/ListItem.svelte';
import Link from '$components/Link.svelte';
let SKILLS = [
{ name: 'Lightweight virtualisation (LXC, Docker)' },
{ name: 'Kubernetes' },
{ name: 'Cloud Computing / IaaS (AWS, Cloudflare)' },
{ name: 'Web development (fullstack; React, Next, Svelte, vanilla, Rust+WASM)' },
{ name: 'Scripting and automation (Bash, Python)' },
{ name: 'git and GitOps pipelines' },
{ name: 'Linux administration' },
];
const PROJECTS = [
{
name: 'Lightweight low-latency virtual networking',
href: '/blog/tumthesis',
description: 'B.Sc. thesis: Evaluate the performance of containers in low-latency networking',
},
{
name: 'This site',
href: 'https://github.com/AlexDaichendt/site',
description: 'Complete source code for this website',
},
{
name: 'Gear Optimizer (GW2)',
href: 'https://optimizer.discretize.eu',
description:
'MMO Guild Wars 2: Outputs the optimal build given numerous input paramaters; 1000 DAUs',
},
{
name: 'Discretize.eu (GW2)',
href: 'https://discretize.eu',
description: 'MMO Guild Wars 2: Advanced player guides; 3000 DAUs',
},
{
name: 'discretize-ui (GW2)',
href: 'https://github.com/discretize/discretize-ui',
description: 'MMO Guild Wars 2: UI library for mirroring in game tooltips with React',
},
{
name: 'Gw2Library (GW2)',
href: 'https://gw2library.princeps.biz/',
description:
'MMO Guild Wars 2: Build library interfacing with the optimizer; CRUD app - NextJS, AWS',
},
{
name: 'LandLord',
href: 'https://www.spigotmc.org/resources/landlord-2.44398/',
description: 'Minecraft plugin for protecting and managing areas - Java (until 2018)',
},
];
</script>
<SEO />
<h1>Hi, my name is Alex!</h1>
<p>I am a software engineer, Linux enthusiast and a friend of lightweight, resilient systems.</p>
<p>
My journey in the tech world has been a dynamic one. I've immersed myself in countless projects
spanning various video games and, for the past few years, have been maintaining a small homelab,
which ignited my passion for DevOps / SRE. I am a privacy enthusiast and advocate for non-invasive
software. Occasionally, I channel my creativity into building sleek web applications that
prioritize efficiency and adhere to web standards and best practices.
</p>
<p>
Currently, I'm pursuing a Master's degree in computer science at <Link href="https://www.tum.de/"
>TUM</Link
>, where I successfully contribute to numerous research papers.
</p>
<h2>Skills</h2>
<ul>
{#each SKILLS as skill}
<ListItem>
{skill.name}
</ListItem>
{/each}
</ul>
<h2>Highlighted Projects</h2>
<ul>
{#each PROJECTS as project}
<ListItem>
<Link href={project.href}>{project.name}</Link> - {project.description}
</ListItem>
{/each}
</ul>
<style>
</style>

View file

@ -1,8 +0,0 @@
export async function load() {
return {
seo: {
title: 'Home',
description: 'Alex Daichendt"s website, blog, and collection of thoughts on modern tech.',
},
};
}

View file

@ -1,40 +0,0 @@
<script lang="ts">
let kittyLink: string;
let importing = false;
let showSuccess = false;
function beamKitty() {
importing = true;
fetch(`https://cats.daichendt.one/import?url=${kittyLink}&optimize=true`, {
method: 'PUT',
headers: { 'X-Custom-Auth-Key': import.meta.env.VITE_CATAPI_PASSWD },
}).then((result) => {
importing = false;
if (result.status === 200) {
kittyLink = '';
showSuccess = true;
setTimeout(() => (showSuccess = false), 5000);
} else {
// display error
console.log(result);
}
});
}
</script>
<h1>Administration Area</h1>
<h2>Add a cat</h2>
<input type="text" placeholder="cat me up" bind:value={kittyLink} />
<button type="button" on:click={beamKitty} disabled={importing}> Send </button>
{#if showSuccess}
<span class="success">Successfully beamed up the kitty!</span>
{/if}
<style>
.success {
color: greenyellow;
}
</style>

View file

@ -1,24 +0,0 @@
import type { BlogPostFrontmatter, BlogPostMeta } from '$lib/utils/types';
import type { PageServerLoad } from './$types';
const removeExtension = (path: string) => path.replace(/\.[^.]*$/g, '').replace('/+page', '');
export const load: PageServerLoad = async () => {
const modulesSVX = import.meta.glob('./**/*.svx');
const modulesMD = import.meta.glob('./**/*.md');
const modules = { ...modulesMD, ...modulesSVX };
const posts: BlogPostMeta[] = [];
const resolved = (await Promise.all(Object.values(modules).map((f) => f()))) as {
metadata: BlogPostFrontmatter;
}[];
resolved.forEach((file, index) => {
const path = Object.keys(modules)[index];
const { metadata } = file;
if (!metadata?.hidden) posts.push({ ...metadata, href: `blog/${removeExtension(path)}` });
});
posts.sort((a, b) => new Date(b.created).valueOf() - new Date(a.created).valueOf());
return { posts };
};

View file

@ -1,25 +0,0 @@
<script lang="ts">
import SEO from '$components/SEO.svelte';
import Link from '$components/Link.svelte';
import ListItem from '$components/ListItem.svelte';
import type { PageData } from './$types';
export let data: PageData;
$: posts = data.posts;
</script>
<SEO />
<h1>Blog Posts</h1>
<p>Sometimes I document some of the things I do.</p>
<ul>
{#each posts as post}
<ListItem>
<Link href={post.href}
>{new Date(post.created).toLocaleDateString('en-GB')} - {post.title}</Link
>
</ListItem>
{/each}
</ul>

View file

@ -1,12 +0,0 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ data }) => {
return {
posts: data.posts,
seo: {
title: 'Blog',
description:
'My blogposts, where I occasionally document things, that I think are not accessible or badly documented.',
},
};
};

Some files were not shown because too many files have changed in this diff Show more