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

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'
description: 'With the help of ChatGPT it is fairly easy to generate custom tailored cover letters for job applications with your own CV'
keywords:

View file

@ -1,19 +1,19 @@
---
created: '2024-01-15'
title: 'Detecting System Management Interrupts (SMIs)'
pubDate: '2024-01-15'
title: 'Detecting System Management Interrupts'
description: ''
keywords:
- SMI
---
# System Management Interrutps (SMI)
## System Management Interrutps (SMIs)
- high priority interrupts caused by the hardware
- 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 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
- 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
- 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
@ -41,7 +41,7 @@ keywords:
- 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
- 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
- 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".
## 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
- 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
- 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://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"
description: ""
description: "Thoughts on Kagi.com"
keywords:
- search engine
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.
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.
@ -21,19 +18,19 @@ Google has been overflooded by SEO spam: sites that do not contain any useful in
If, for some reason, a bad site appears in the search results, I can easily block it. More relevant sites like Wikipedia or StackOverflow can be promoted to the top of the search results.
## 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
By default, since the search engine requires registration and payment, Kagi could theoretically track the user's search history. However, I have no reason to believe that Kagi is doing this. Kagi repeatedly stated that they are a small company that aims to do things differently, i.e., not maximize profit over sustainability. That is also why they give free T-shirts to the first 20k users. Although I'm not convinced this is a wise business decision, I respect their commitment to their user base.
By default, since the search engine requires registration and payment, Kagi could theoretically track the user's search history. However, I have no reason to believe that Kagi is doing this. Kagi repeatedly stated that they are a small company that aims to do things differently, i.e., not maximize profit over sustainability. That is also why they give free T-shirts to the first 20k users. Although I'm not convinced this is a wise business decision, I respect their commitment to their user base.
In recent criticism, Kagi's CEO Vlad has made questionable privacy statements. Mainly, he claimed that an Email address is not PII (Personally Identifiable Information) because the user could create single-use Email addresses. That statement is obviously regrettable, but the CEO has clarified and will be more careful in the future. Just because a CEO is more outspoken and engaging with the community (which does not happen often - if ever) and sometimes says woeful things does not mean that the company as a whole should be boycotted. It should be seen as a way to engage with the company and perhaps improve it. Kagi is the best we have right now, and I am happy to support them.
In recent criticism, Kagi's CEO Vlad has made questionable privacy statements. Mainly, he claimed that an Email address is not PII (Personally Identifiable Information) because the user could create single-use Email addresses. That statement is obviously regrettable, but the CEO has clarified and will be more careful in the future. Just because a CEO is more outspoken and engaging with the community (which does not happen often - if ever) and sometimes says woeful things does not mean that the company as a whole should be boycotted. It should be seen as a way to engage with the company and perhaps improve it. Kagi is the best we have right now, and I am happy to support them.
This entire privacy discussion boils down to a big "trust me, bro" which I am willing to give Kagi - for now.
This entire privacy discussion boils down to a big "trust me, bro" which I am willing to give Kagi - for now.
I pay for search; at least I know that Kagi does not have to sell my data to keep the lights on - unlike specific competitors.
## Conclusion
## Conclusion
Kagi is a great search engine that I can recommend to anyone who is tired of Google's SEO spam and wants to support a small company that is trying to do things differently. The search results are excellent, and the AI summaries are a nice addition. I am looking forward to seeing how Kagi will develop in the future.
Kagi is a great search engine that I can recommend to anyone who is tired of Google's SEO spam and wants to support a small company that is trying to do things differently. The search results are excellent, and the AI summaries are a nice addition. I am looking forward to seeing how Kagi will develop in the future.

View file

@ -1,5 +1,5 @@
---
created: '2024-08-25'
pubDate: '2024-08-25'
title: "Kata Containers: Custom Kernel Module in Guest"
description: 'How to build a custom kernel module for a Kata Containers guest.'
keywords:
@ -16,8 +16,8 @@ hidden: false
Kata Containers is a lightweight container runtime that leverages hardware virtualization to provide strong isolation between containers. It is compatible with the Open Container Initiative (OCI) and the Container Runtime Interface (CRI). Kata Containers uses a lightweight VM to run each container, which provides an additional layer of isolation compared to traditional container runtimes like Docker or containerd.
The official documentation is fairly lackluster here and there. For example, see [here](https://github.com/kata-containers/kata-containers/blob/main/docs/how-to/how-to-load-kernel-modules-with-kata.md). There is a lot of prerequisite knowledge assumed.
Another tutorial is [here](https://vadosware.io/post/building-custom-kernels-for-kata-containers/), which sheds some light into the building process of a custom kernel image, but leaves out custom kernel modules.
The official documentation is fairly lackluster here and there. For example, see [here](https://github.com/kata-containers/kata-containers/blob/main/docs/how-to/how-to-load-kernel-modules-with-kata.md). There is a lot of prerequisite knowledge assumed.
Another tutorial is [here](https://vadosware.io/post/building-custom-kernels-for-kata-containers/), which sheds some light into the building process of a custom kernel image, but leaves out custom kernel modules.
This article aims to provide a step-by-step guide on how to utilize a custom kernel module in a Kata Containers guest. In this example, we will include the igb_uio kernel module, which can be used with DPDK.
@ -41,7 +41,7 @@ menuconfig IGB_UIO
depends on UIO
default y
EOF
# overwrite Makefile to avoid building the module as .ko file
# overwrite Makefile to avoid building the module as .ko file
echo "# SPDX-License-Identifier: GPL-2.0" > kata-linux-6.7-$KATA_CONFIG_VERSION/drivers/igb_uio/Makefile
echo "obj-\$(CONFIG_IGB_UIO) += igb_uio.o" >> kata-linux-6.7-$KATA_CONFIG_VERSION/drivers/igb_uio/Makefile
@ -59,6 +59,6 @@ echo "CONFIG_IGB_UIO=y" >> kata-linux-6.7-$KATA_CONFIG_VERSION/.config
# build the kernel with the new module
bash build-kernel.sh -v 6.7 build
```
```
Why Kata 3.2.0, an ancient version, you might ask? Unfortunately, we were unable to get newer version to work with SEV-SNP.
Why Kata 3.2.0, an ancient version, you might ask? Unfortunately, we were unable to get newer version to work with SEV-SNP.

View file

@ -1,5 +1,5 @@
---
created: '2022-09-24'
pubDate: '2022-09-24'
title: 'Securing a Caddy endpoint with LLDAP'
description: ''
keywords:
@ -7,11 +7,6 @@ keywords:
- 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
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
@ -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
create users and groups.
<Image meta={overview} alt="LLDAP Userinterface"/>
![LLDAP Userinterface](../../assets/lldap_overview.png)
## 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'
description: ''
keywords:

View file

@ -1,5 +1,5 @@
---
created: '2023-03-12'
pubDate: '2023-03-12'
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.
keywords:
@ -17,19 +17,13 @@ keywords:
- linux kernel
- kernel
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.
<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.
@ -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.
<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_:

View file

@ -1,5 +1,5 @@
---
created: '2023-02-04'
pubDate: '2023-02-04'
title: You don't need an AIO
description: AIOs are overrated tech that's recommended too frequently without considering the downsides
keywords:
@ -9,7 +9,7 @@ keywords:
- 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)

View file

@ -1,5 +1,5 @@
---
created: '2022-08-04'
pubDate: '2022-08-04'
title: 'Free, on-demand image optimizations with Cloudflare'
description: ''
keywords:
@ -10,12 +10,8 @@ keywords:
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
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.
@ -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
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
- **(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'
description: ''
keywords:
@ -20,7 +20,7 @@ This post was originally published on the Guild Wars 2 Community on [lemmy](http
Hey fellow Tyrian Lemmings,
my first Post on Lemmy, hurray \o/ !
my first Post on Lemmy, hurray \o/ !
I have dedicated a significant amount of time to enhancing the performance of the Discretize Gear Optimizer. It now supports multi-core processing for calculations and heuristics, enabling the simultaneous calculation of large amounts of Runes, Sigils, and Food!
What is the Gear Optimizer? In short, it runs damage calculations in the game using specific scenarios to determine the ideal gear combinations.

View file

@ -1,8 +1,8 @@
---
created: '2022-05-08'
pubDate: '2022-05-08'
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.'
keywords:
keywords:
- ArrowOS
- Redmi Note 7
- Lavender

View file

@ -1,6 +1,6 @@
---
created: '2022-09-27'
updated: '2022-10-22'
pubDate: '2022-09-27'
updatedDate: '2022-10-22'
title: 'Site 2 Site Wireguard VPN with a Mikrotik Router and a Cloud VM'
description: ''
keywords:
@ -11,13 +11,6 @@ keywords:
- 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
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
@ -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
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
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
cloud wg endpoint, otherwise the cloud cant ping back home.
<div style="max-width:600px">
<Image meta={peer} alt="Mikrotik router peer setup"/>
![Mikrotik router peer setup](../../assets/site2sitewireguard/mikrotik_peer.png)
</div>
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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
---
created: '2024-08-10'
title: "Experience with MSI warranty for a monitor"
pubDate: '2024-08-10'
updatedDate: '2024-12-08'
title: "Experience with MSI's warranty for a monitor"
description: "Positive review of MSI's warranty service for a monitor."
keywords:
- warranty
@ -10,9 +11,10 @@ keywords:
- bug
- thunderfly
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:
- IPS (or rather, not OLED),
- an integrated KVM Switch with 3 Ports,
- at least 60W USB-C power delivery,
@ -20,18 +22,18 @@ I bought a new 32' 4k MSI Monitor for my birthday a couple of months ago. Since
- 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.
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
- use a vacuum cleaner
- use canned air in various gaps
- use a suction cup
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.
Would buy MSI again.
Would buy MSI again.

View file

@ -1,5 +1,5 @@
---
created: '2022-08-12'
pubDate: '2022-08-12'
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.'
keywords:
@ -24,8 +24,8 @@ advice you may find on dubious websites.
General notes:
- In chapter/section headlines, do not add the acronym.
Good: `3. Data Plane Development Kit`.
- In chapter/section headlines, do not add the acronym.
Good: `3. Data Plane Development Kit`.
Bad: `3. Data Plane Development Kit (DPDK)`
- Avoid enumerations in brackets; instead, use "such as"
- Use a spellchecker!

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.',
},
};
};

View file

@ -1,62 +0,0 @@
<script lang="ts">
import type { PageData } from './$types';
import Image from '$lib/components/CatImage.svelte';
import Link from '$lib/components/Link.svelte';
import SEO from '$components/SEO.svelte';
export let data: PageData;
$: cats = data.cats;
</script>
<SEO />
<section class="masonry">
{#each cats as cat}
<figure>
<Link
href={`https://cats.daichendt.one/${cat.images[cat.images.length - 1].href}`}
disableIcon
disablePrefetch
>
<Image
metadata={cat.images}
sizes="(max-width: 720px) 1px, (max-width: 1280px) 360px, 720px"
/>
</Link>
</figure>
{/each}
</section>
<style>
.masonry {
column-count: 3;
column-gap: 1em;
}
/* Masonry on medium-sized screens */
@media only screen and (max-width: 800px) and (min-width: 500px) {
.masonry {
column-count: 2;
}
}
/* Masonry on small screens */
@media only screen and (max-width: 500px) and (min-width: 0px) {
.masonry {
column-count: 1;
}
}
figure {
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.4);
margin: 0;
background-color: #eee;
display: inline-block;
margin: 0 0 1em;
width: 100%;
}
figure:hover {
filter: brightness(90%);
}
</style>

View file

@ -1,9 +0,0 @@
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch('https://cats.daichendt.one/list');
const asJson = await response.json();
return {
cats: response.ok && asJson,
};
};

View file

@ -1,63 +0,0 @@
<script>
import SEO from '$components/SEO.svelte';
import { mdiEmailEditOutline, mdiKey } from '@mdi/js';
import { mdiGithub } from '@mdi/js';
import Icon from 'mdi-svelte';
import Element from '$lib/components/icons/element.svelte';
import Link from '$components/Link.svelte';
import ListItem from '$components/ListItem.svelte';
</script>
<SEO />
<h1>Contact Information</h1>
<ul>
<ListItem>
<span class="listText">
<Icon path={mdiEmailEditOutline} />
E-Mail:</span
>
<span class="listValue">
<Link disableIcon disablePrefetch href="mailto:me@daichendt.one">me@daichendt.one</Link>
<span style="margin-left: 4px;">
<Icon path={mdiKey} size="1rem" />
<Link href="/pub.key" disablePrefetch>PGP key</Link>
</span>
</span>
</ListItem>
<ListItem>
<span class="listText">
<Icon path={mdiGithub} />
Github:</span
>
<span class="listValue">
<Link disableIcon href="https://github.com/AlexDaichendt">AlexDaichendt</Link></span
>
</ListItem>
<ListItem>
<span class="listText">
<span style="font-size:1rem"><Element /></span>
Element:</span
> <span class="listValue"> @alexdaichendt:matrix.org</span>
</ListItem>
</ul>
<style>
ul {
list-style-type: none;
}
@media screen and (max-width: 480px) {
ul {
padding-left: unset;
}
.listText {
display: block;
}
.listValue {
margin-left: 2rem;
}
}
</style>

View file

@ -1,8 +0,0 @@
export async function load() {
return {
seo: {
title: 'Contact',
description: 'All the communication channels for contacting Alex Daichendt ',
},
};
}

View file

@ -1,25 +0,0 @@
<script>
import SEO from '$components/SEO.svelte';
</script>
<SEO />
<h1>Impressum</h1>
<p>Information according to §5 TMG:</p>
<ul>
<li>Alexander Daichendt</li>
<li>Wiesenweg 10a</li>
<li>85464 Neufinsing</li>
<li>GERMANY</li>
</ul>
<p>Contact:</p>
<ul><li>privacy@daichendt.one</li></ul>
<style>
ul {
list-style-type: none;
}
</style>

View file

@ -1,8 +0,0 @@
export async function load() {
return {
seo: {
title: 'Impressum',
description: 'The impressum I have to include for Germany.',
},
};
}

View file

@ -1,97 +0,0 @@
<script>
import SEO from '$components/SEO.svelte';
</script>
<SEO />
<h1>Privacy Policy for AlexDaichendt</h1>
<p>
At AlexDaichendt, accessible from https://daichendt.one, one of our main priorities is the privacy
of our visitors. This Privacy Policy document contains types of information that is collected and
recorded by AlexDaichendt and how we use it.
</p>
<p>
If you have additional questions or require more information about our Privacy Policy, do not
hesitate to contact us.
</p>
<h2>Log Files</h2>
<p>
AlexDaichendt follows a standard procedure of using log files. These files log visitors when they
visit websites. All hosting companies do this and a part of hosting services' analytics. The
information collected by log files include internet protocol (IP) addresses, browser type,
Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the
number of clicks. These are not linked to any information that is personally identifiable. The
purpose of the information is for analyzing trends, administering the site, tracking users'
movement on the website, and gathering demographic information. Our Privacy Policy was created
with the help of the <a href="https://www.privacypolicygenerator.org">Privacy Policy Generator</a
>.
</p>
<h2>Privacy Policies</h2>
<p>
You may consult this list to find the Privacy Policy for each of the advertising partners of
AlexDaichendt.
</p>
<p>
Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons
that are used in their respective advertisements and links that appear on AlexDaichendt, which are
sent directly to users' browser. They automatically receive your IP address when this occurs.
These technologies are used to measure the effectiveness of their advertising campaigns and/or to
personalize the advertising content that you see on websites that you visit.
</p>
<p>
Note that AlexDaichendt has no access to or control over these cookies that are used by
third-party advertisers.
</p>
<h2>Third Party Privacy Policies</h2>
<p>
AlexDaichendt's Privacy Policy does not apply to other advertisers or websites. Thus, we are
advising you to consult the respective Privacy Policies of these third-party ad servers for more
detailed information. It may include their practices and instructions about how to opt-out of
certain options.
</p>
<p>
You can choose to disable cookies through your individual browser options. To know more detailed
information about cookie management with specific web browsers, it can be found at the browsers'
respective websites.
</p>
<h2>Children's Information</h2>
<p>
Another part of our priority is adding protection for children while using the internet. We
encourage parents and guardians to observe, participate in, and/or monitor and guide their online
activity.
</p>
<p>
AlexDaichendt does not knowingly collect any Personal Identifiable Information from children under
the age of 13. If you think that your child provided this kind of information on our website, we
strongly encourage you to contact us immediately and we will do our best efforts to promptly
remove such information from our records.
</p>
<h2>Online Privacy Policy Only</h2>
<p>
This Privacy Policy applies only to our online activities and is valid for visitors to our website
with regards to the information that they shared and/or collect in AlexDaichendt. This policy is
not applicable to any information collected offline or via channels other than this website.
</p>
<h2>Consent</h2>
<p>
By using our website, you hereby consent to our Privacy Policy and agree to its Terms and
Conditions.
</p>

View file

@ -1,9 +0,0 @@
export async function load() {
return {
seo: {
title: 'Privacy',
description:
'The privacy policy I have to include even though I do not collect any data or use shady services.',
},
};
}

View file

@ -1,22 +0,0 @@
import type { ProjectMeta, ProjectsFrontmatter } 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 projects: ProjectMeta[] = [];
const resolved = (await Promise.all(Object.values(modules).map((f) => f()))) as {
metadata: ProjectsFrontmatter;
}[];
resolved.forEach((file, index) => {
const path = Object.keys(modules)[index];
const { metadata } = file;
projects.push({ ...metadata, href: `projects/${removeExtension(path)}` });
});
return { projects };
};

View file

@ -1,22 +0,0 @@
<script lang="ts">
import Link from '$components/Link.svelte';
import ListItem from '$components/ListItem.svelte';
import Seo from '$components/SEO.svelte';
import type { PageData } from './$types';
export let data: PageData;
$: projects = data.projects;
</script>
<Seo />
<h1>Projects</h1>
<p>Detailed descriptions of my contributions to various projects.</p>
<ul>
{#each projects as project}
<ListItem>
<Link href={project.href}>{project.title}</Link>
</ListItem>
{/each}
</ul>

View file

@ -1,11 +0,0 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ data }) => {
return {
projects: data.projects,
seo: {
title: 'Projects',
description: 'Detailed descriptions of projects created by Alex Daichendt',
},
};
};

View file

@ -1,61 +0,0 @@
---
title: 'GW2 Gear Optimizer'
description: 'The Gear Optimizer helps Guild Wars 2 players find optimal builds for fractals, raids, and strike missions.'
keywords:
- gw2
- guild wars 2
- optimizer
layout: blog
---
<script>
import overview from "./images/gear-optimizer.png?default"
import Image from "$components/Image.svelte"
</script>
## TL;DR
{description}
- Demo / live site: [https://optimizer.discretize.eu](https://optimizer.discretize.eu)
- Code: [Github](https://github.com/discretize/discretize-gear-optimizer)
I made signifcant contributions to the frontend as well as to the calculation core by rewriting it in Rust and implementing threading.
Furthermore, I worked closely with and coordinated a small team of a few developers from all over the world ([see here](https://github.com/discretize/discretize-gear-optimizer/graphs/contributors)) to improve the project further.
<Image meta={overview} />
## Description
Interesting features for players of the video game:
- find optimal builds based on various parameters
- build templates with extensive sensible defaults that are used by players
- support for different game modes with different balancing
- Uptime input for conditional buffs
- keyboard shortcuts
- custom arbitrary modifier input to allow simulating theoretical balancing
- infusion helper: calculates the cheapest way to acquire n agony resistance
- input condition distribution
- displays the results just like in game
- share settings and results with a single link
## Most Significant Contributions
- I rewrote the frontend from scratch utilizing modern JS tooling
-> the old optimizer consisted out of one 10k LoC HTML file and an equally large js file; bundled with gulp and jquery ...
- ported the calculation core to Rust; refactored the combination generation code to support multi-threading
-> added some heuristics to reduce the amount of combinations
- state compression algorithm based on a schema so that players can share links to their builds without a server storing data
## Technical Details
The optimizer has numerous interesting technical features:
- React SPA without SSR with Vite
- Built with Material-UI 5 and Emotion for CSS-in-JS
- Statemanagement with ReduxJS
- multithreaded calculations with Rust compiled to WASM and WebWorkers
- i18n localization (Chinese and German)
- hosted on Cloudflare pages utilizing Workers and KV
- custom algorithm for lossless state compress based on a schema into base64, url-save strings

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 KiB

View file

@ -1,22 +0,0 @@
import type { PublicationMeta, PublicationFrontmatter } 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 publications: PublicationMeta[] = [];
const resolved = (await Promise.all(Object.values(modules).map((f) => f()))) as {
metadata: PublicationFrontmatter;
}[];
resolved.forEach((file, index) => {
const path = Object.keys(modules)[index];
const { metadata } = file;
publications.push({ ...metadata, href: `publications/${removeExtension(path)}` });
});
return { publications };
};

View file

@ -1,24 +0,0 @@
<script lang="ts">
import Seo from '$components/SEO.svelte';
import PublicationListItem from '$components/publications/PublicationListItem.svelte';
import type { PageData } from './$types';
export let data: PageData;
$: publications = data.publications;
</script>
<Seo />
<h1>Publications</h1>
<h2>Conference papers</h2>
<ul>
{#each publications as publication}
<PublicationListItem
title={publication.title}
authors={publication.authors}
pdf={publication.pdf}
conference={publication.conference}
/>
{/each}
</ul>

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