Compare commits

..

31 Commits

Author SHA1 Message Date
4aa64a880d style: fix linting complaints 2026-04-02 18:43:51 +02:00
614a32e0d4 feat: add PGP fingerprint 2026-04-02 18:33:53 +02:00
59525cbde5 feat: mention astro in web dev frameworks 2026-04-02 18:28:16 +02:00
264fe63e9b feat: expand descriptive paragraph 2026-04-02 18:26:20 +02:00
7d4c8e6cb1 feat: add a link to blecon.de 2026-04-02 18:22:44 +02:00
924af6e200 feat: put link to skills in separate nav 2026-04-02 17:54:58 +02:00
62339b4f33 feat: refactor inline links and use heroicons 2026-04-01 21:03:52 +02:00
d9d87daf7e feat: make InlineSvg more universal 2026-04-01 20:34:37 +02:00
320035c828 fix: pass through rest props in ButtonLink 2026-04-01 20:12:48 +02:00
92ca1f6feb refactor: rename ExternalLink to ButtonLink 2026-04-01 20:05:33 +02:00
bfd53b66ec refactor: ExternalLink component properties 2026-04-01 20:03:20 +02:00
d6aea329fe style: add prettier-plugin-organize-imports 2026-04-01 19:48:28 +02:00
d5eef87740 feat: set proper rel-attributes to external links 2026-04-01 19:16:12 +02:00
6eb4334691 feat: set up a good looking outline for links 2026-04-01 19:15:40 +02:00
4ce0421e95 chore(astro): update 2026-04-01 18:13:26 +02:00
91c8a825be feat(skills): auto-calc years experience and add back arrow 2026-03-28 22:27:02 +01:00
e5ab74ebfb feat: add a first version of the skills page 2026-03-26 22:07:00 +01:00
3e7c1d7ad3 feat: add a base layout for base HTML 2026-03-26 19:48:16 +01:00
2b988b9be9 build: update tooling configs and ignore files 2026-03-24 17:20:35 +01:00
276b8401ef chore(security): override vulnerable minimatch version 2026-03-24 17:00:20 +01:00
ef12d4883c chore: upgrade astro 2026-03-24 16:48:59 +01:00
1c04a8c2a4 chore: run pnpm update 2026-03-24 16:46:59 +01:00
7f75cd533e feat: add .ignore file 2026-03-24 16:44:38 +01:00
8ceb0839cf feat: add GitLab and LinkedIn links 2026-03-08 17:06:57 +01:00
64d4ecaf55 feat: add GitHub link 2026-03-08 16:42:54 +01:00
09bd36514c style: correct image file name casing 2026-03-08 16:42:43 +01:00
e38ad1530d feat: add Keyoxide link 2026-03-08 16:27:44 +01:00
5c252e8ff1 feat: add job title 2026-03-08 16:27:19 +01:00
6d86744166 feat: put external links in a nav 2026-03-08 16:15:01 +01:00
0b2a69a523 style(cspell): fix complaints 2026-03-08 16:14:18 +01:00
23d72ebc39 ci: add a deploy script 2026-03-08 16:11:31 +01:00
29 changed files with 1492 additions and 1313 deletions

View File

@@ -1,9 +1,20 @@
# yamllint disable-line rule:line-length
# yaml-language-server: $schema=https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json
---
version: "0.2"
import:
- "@cspell/dict-de-de/cspell-ext.json"
language: en,de
ignorePaths:
- /pnpm-lock.yaml
words:
- dockervolumes
- github
- gitlab
- grayscale
- keyoxide
- kmoschcau
- markuplint
- moschcau
- tailwindcss
- tsconfigs

1
.ignore Normal file
View File

@@ -0,0 +1 @@
pnpm-lock.yaml

View File

@@ -2,5 +2,8 @@
"extends": ["markuplint:recommended-static-html"],
"parser": {
".astro$": "@markuplint/astro-parser"
},
"rules": {
"use-list": false
}
}

3
.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
/.astro/
/dist/
/pnpm-lock.yaml

View File

@@ -1,7 +1,18 @@
# vim: filetype=gitignore
/dist/
*.html
*.ico
*.jpg
*.js
*.json
*.md
*.mjs
*.mts
*.png
*.svg
*.ts
*.vim
*.webp
*.yaml

View File

@@ -31,9 +31,9 @@ Any static assets, like images, can be placed in the `public/` directory.
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `pnpm install` | Installs dependencies |
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `pnpm install` | Installs dependencies |
| `pnpm dev` | Starts local dev server at `localhost:4321` |
| `pnpm build` | Build your production site to `./dist/` |
| `pnpm preview` | Preview your build locally, before deploying |

View File

@@ -1,6 +1,6 @@
// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({

View File

@@ -1,21 +1,27 @@
import js from "@eslint/js";
import eslintPluginAstro from "eslint-plugin-astro";
import jsxA11y from "eslint-plugin-jsx-a11y";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import { defineConfig } from "eslint/config";
import jsxA11y from "eslint-plugin-jsx-a11y";
export default defineConfig([
{
ignores: ["./dist/", "**/*.html"],
},
{
files: ["**/*.{js,mjs}"],
plugins: { js },
extends: ["js/recommended"],
},
{
files: ["**/*.{astro,html}"],
plugins: {
"jsx-a11y": jsxA11y,
},
},
...eslintPluginAstro.configs.recommended,
eslintPluginPrettierRecommended,
]);

View File

@@ -7,24 +7,28 @@
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint:eslint": "eslint",
"lint:markuplint": "markuplint '**/*.{astro,html}'",
"lint:stylelint": "stylelint ."
"lint:markuplint": "markuplint './{public,src}/**/*.{astro,html}'",
"lint:stylelint": "stylelint .",
"deploy": "astro build && scp -r dist/* vds:~/dockervolumes/kmoschcau_website/web/"
},
"dependencies": {
"@tailwindcss/vite": "^4.2.1",
"astro": "^5.17.1",
"tailwindcss": "^4.2.1"
"@tailwindcss/vite": "^4.2.2",
"astro": "^6.1.2",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.6",
"@astrojs/ts-plugin": "^1.10.7",
"@cspell/dict-de-de": "^4.1.2",
"@eslint/js": "^10.0.1",
"@markuplint/astro-parser": "^4.6.23",
"@markuplint/ml-config": "^4.8.15",
"@typescript-eslint/parser": "^8.56.1",
"@tailwindcss/typography": "^0.5.19",
"@typescript-eslint/parser": "^8.57.2",
"cspell": "^9.7.0",
"eslint": "^10.0.2",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-astro": "^1.6.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
@@ -33,9 +37,10 @@
"postcss-html": "^1.8.1",
"prettier": "^3.8.1",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"sharp": "^0.34.5",
"stylelint": "^17.4.0",
"stylelint": "^17.5.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^18.0.0",
"stylelint-config-standard": "^40.0.0",

2150
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
---
overrides:
minimatch@10.1.2: 10.2.3

View File

@@ -1,6 +1,10 @@
/** @type {import("prettier").Config} */
export default {
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
plugins: [
"prettier-plugin-astro",
"prettier-plugin-organize-imports",
"prettier-plugin-tailwindcss",
],
overrides: [
{

View File

@@ -0,0 +1,21 @@
---
import type { HTMLAttributes } from "astro/types";
import { getRel } from "../link-utils";
interface Props extends HTMLAttributes<"a"> {
/** Whether this link should use defaults for external links. */
external?: boolean | "me" | null | undefined;
}
const { class: className, external, target, rel, ...rest } = Astro.props;
---
<a
class:list={[
className,
"flex min-w-full items-center justify-center gap-4 rounded px-6 py-2 text-lg outline-offset-2 sm:px-24",
]}
target={target ?? (external ? "_blank" : undefined)}
rel={rel ?? getRel(external)}
{...rest}><slot name="logo" /><slot /></a
>

View File

@@ -1,22 +0,0 @@
---
import type { HTMLAttributes } from "astro/types";
interface Props extends HTMLAttributes<"a"> {
cssClass: string;
href: string;
title: string;
}
const { cssClass, href, title } = Astro.props;
---
<a
class:list={[
cssClass,
"flex min-w-full items-center justify-center gap-4 rounded px-6 py-2 text-lg sm:px-24",
]}
{href}
{title}
target="_blank"
rel="external me noreferrer"><slot name="logo" /><slot /></a
>

View File

@@ -0,0 +1,24 @@
---
import type { HTMLAttributes } from "astro/types";
import ExternalIcon from "../icons/arrow-top-right-on-square.svg";
import InlineSvg from "./inline-svg.astro";
import { getRel } from "../link-utils";
interface Props extends HTMLAttributes<"a"> {
/** Whether this link should use defaults for external links. */
external?: boolean | "me" | null | undefined;
}
const { external, target, rel, ...rest } = Astro.props;
---
<a
target={target ?? (external ? "_blank" : undefined)}
rel={rel ?? getRel(external)}
{...rest}
><slot />{
external ? (
<InlineSvg SvgComponent={ExternalIcon} class="ms-1" />
) : undefined
}</a
>

View File

@@ -1,17 +1,16 @@
---
import type { SvgComponent } from "astro/types";
import type { HTMLAttributes, SvgComponent } from "astro/types";
interface Props {
interface Props extends HTMLAttributes<"svg"> {
SvgComponent: SvgComponent;
size?: number;
}
const { size = 24, SvgComponent } = Astro.props;
const {
SvgComponent,
class: className,
role = "presentation",
...rest
} = Astro.props;
---
<SvgComponent
height={size}
width={size}
fill="currentColor"
role="presentation"
/>
<SvgComponent class:list={[className, "inline h-[1em]"]} {role} {...rest} />

11
src/duration-utils.ts Normal file
View File

@@ -0,0 +1,11 @@
const UNIX_EPOCH_YEAR = 1970;
export function getDurationInYearsBetween(start: Date, end: Date): number {
return (
new Date(end.getTime() - start.getTime()).getUTCFullYear() - UNIX_EPOCH_YEAR
);
}
export function getDurationInYearsFrom(from: Date): number {
return getDurationInYearsBetween(from, new Date());
}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 9-3 3m0 0 3 3m-3-3h7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>

After

Width:  |  Height:  |  Size: 316 B

1
src/images/GitHub.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>

After

Width:  |  Height:  |  Size: 822 B

1
src/images/GitLab.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitLab</title><path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/></svg>

After

Width:  |  Height:  |  Size: 573 B

7
src/images/Keyoxide.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m14.662 2.7734c-0.86914 8.5e-6 -1.5742 0.70509-1.5742 1.5742 0 0.52528 0.26095 1.006 0.67773 1.2949 0.12007 0.083138 0.18912 0.092562 0.20508 0.22266 0.01604 0.13001-0.16211 0.25-0.16211 0.25l-3.3242 2.5859v-0.5293c0.14724-1.4561-0.83223-2.9405-2.2754-3.2793-1.719-0.502-3.7931 1.0298-3.7931 2.6452 0 1.9182 0.00778 8.9247 0.00211 13.429-0.00211 1.6772 1.3577 3.0332 3.0332 3.0332s3.0352-1.3576 3.0352-3.0332v-0.39648c1.365 0.90898 2.7303 1.8174 4.0938 2.7285 1.1299 0.93021 2.9078 0.93859 3.9902-0.07422 1.3712-1.1518 1.323-3.4999-0.0918-4.5957-1.935-1.3219-3.8897-2.6181-5.8418-3.916l5.7734-4.4883c1.1632-0.90466 1.4954-2.5005 0.83398-3.7832-0.04938-0.095755-0.06511-0.12998-0.16797-0.16992-0.10277-0.039938-0.18425 0.00439-0.28711 0.042969-0.14138 0.053062-0.29257 0.080078-0.44531 0.080078-0.69894 0-1.2656-0.56662-1.2656-1.2656 9.4e-5 -0.094853 0.01636-0.14996-0.01172-0.20898-0.02807-0.059023-0.10233-0.097092-0.17578-0.10547-0.12191-0.013926-0.24437-0.020476-0.36719-0.019531-0.12049 8.587e-4 -0.21169-0.00952-0.26367-0.097656-0.05189-0.088142-0.02344-0.18975-0.02344-0.34961 0-0.86915-0.70504-1.5742-1.5742-1.5742z"/>
<path d="m12.806 3.085a1.0735 1.0735 0 0 1-1.0735 1.0735 1.0735 1.0735 0 0 1-1.0735-1.0735 1.0735 1.0735 0 0 1 1.0735-1.0735 1.0735 1.0735 0 0 1 1.0735 1.0735z"/>
<path d="m13.458 1.0033a0.70038 0.70038 0 0 1-0.70038 0.70038 0.70038 0.70038 0 0 1-0.70038-0.70038 0.70038 0.70038 0 0 1 0.70038-0.70038 0.70038 0.70038 0 0 1 0.70038 0.70038z"/>
<path d="m11.339 0.48902a0.48902 0.48902 0 0 1-0.48902 0.48902 0.48902 0.48902 0 0 1-0.48902-0.48902 0.48902 0.48902 0 0 1 0.48902-0.48902 0.48902 0.48902 0 0 1 0.48902 0.48902z"/>
<path d="m19.203 5.1296a0.85797 0.85797 0 0 1-0.85797 0.85797 0.85797 0.85797 0 0 1-0.85797-0.85797 0.85797 0.85797 0 0 1 0.85797-0.85797 0.85797 0.85797 0 0 1 0.85797 0.85797z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

5
src/images/LinkedIn.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em"
viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z" />
</svg>

After

Width:  |  Height:  |  Size: 698 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,21 @@
---
import "../styles/global.css";
const { title } = Astro.props;
---
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body
class="flex min-h-screen flex-col justify-center p-6 sm:px-14 md:px-24 lg:px-32"
>
<slot />
</body>
</html>

7
src/link-utils.ts Normal file
View File

@@ -0,0 +1,7 @@
export function getRel(
external: boolean | "me" | null | undefined,
): string | undefined {
return external
? "external nofollow noreferrer".concat(external === "me" ? " me" : "")
: undefined;
}

View File

@@ -1,49 +1,134 @@
---
import { Image } from "astro:assets";
import ExternalLink from "../components/external-link.astro";
import InlineSvg from "../components/inline-svg.astro";
import MatrixLogo from "../images/matrix-logo.svg";
import ButtonLink from "../components/button-link.astro";
import InlineLink from "../components/inline-link.astro";
import GitHubLogo from "../images/GitHub.svg";
import GitLabLogo from "../images/GitLab.svg";
import KeyoxideLogo from "../images/Keyoxide.svg";
import LinkedInLogo from "../images/LinkedIn.svg";
import MatrixLogo from "../images/Matrix-logo.svg";
import portrait from "../images/portrait.jpg";
import "../styles/global.css";
import BaseLayout from "../layouts/BaseLayout.astro";
---
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta name="generator" content={Astro.generator} />
<title>kmoschcau.de</title>
</head>
<body
class="flex min-h-screen flex-col justify-center p-6 sm:px-14 md:px-24 lg:px-32"
>
<main>
<article class="flex flex-col items-center gap-10">
<Image
alt="Portrait von Kai Moschcau"
class="rounded-full"
priority
src={portrait}
width={200}
height={200}
/>
<h1>Kai Moschcau</h1>
<ul class="flex flex-col gap-2">
<BaseLayout title="kmoschcau.de">
<main>
<article class="flex flex-col items-center gap-10">
<Image
alt="Portrait von Kai Moschcau"
class="rounded-full"
priority
src={portrait}
width={200}
height={200}
/>
<h1>Kai Moschcau</h1>
<p class="text-soft">
<strong>PGP fingerprint:</strong>
<code>0x DF16 F424 1770 90BB</code>
</p>
<section class="text-soft">
<p><strong>Fachinformatiker</strong> für Anwendungsentwicklung</p>
<p><strong>Software Developer &amp; Consultant</strong> @ blecon</p>
</section>
<nav aria-label="Seitennavigation" class="page-nav">
<ul class="flex">
<li>
<ExternalLink
cssClass="link-matrix"
href="https://matrix.to/#/@kmoschcau:matrix.org"
title="Mein Matrix Konto"
<a href="/skills" class="font-medium underline">Skills</a>
</li>
<li>
<InlineLink
href="https://blecon.de"
external
class="font-medium underline">blecon.de</InlineLink
>
<InlineSvg SvgComponent={MatrixLogo} slot="logo" />
<span>@kmoschcau:matrix.org</span>
</ExternalLink>
</li>
</ul>
</article>
</main>
</body>
</html>
</nav>
<nav aria-label="Externe Links">
<ul class="flex flex-col gap-2">
<li>
<ButtonLink
class="link-matrix"
href="https://matrix.to/#/@kmoschcau:matrix.org"
title="Mein Matrix-Konto"
external="me"
>
<MatrixLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>@kmoschcau:matrix.org</span>
</ButtonLink>
</li>
<li>
<ButtonLink
class="link-github"
href="https://github.com/kmoschcau/"
title="Mein GitHub-Konto"
external="me"
>
<GitHubLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span>
</ButtonLink>
</li>
<li>
<ButtonLink
class="link-gitlab"
href="https://gitlab.com/kmoschcau"
title="Mein GitLab-Konto"
external="me"
>
<GitLabLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span>
</ButtonLink>
</li>
<li>
<ButtonLink
class="link-linked-in"
href="https://www.linkedin.com/in/kmoschcau/"
title="Mein LinkedIn-Konto"
external="me"
>
<LinkedInLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span>
</ButtonLink>
</li>
<li>
<ButtonLink
class="link-keyoxide"
href="https://keyoxide.org/8CE00E9495B5030DA9217208DF16F424177090BB"
title="Mein Keyoxide-Eintrag"
external="me"
>
<KeyoxideLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>Keyoxide</span>
</ButtonLink>
</li>
</ul>
</nav>
</article>
</main>
</BaseLayout>

223
src/pages/skills.astro Normal file
View File

@@ -0,0 +1,223 @@
---
import ArrowLeftCircle from "../icons/arrow-left-circle.svg";
import BaseLayout from "../layouts/BaseLayout.astro";
import {
getDurationInYearsBetween as between,
getDurationInYearsFrom as from,
} from "../duration-utils";
import InlineLink from "../components/inline-link.astro";
const trStart = new Date(2011, 7, 1);
const trustStart = new Date(2014, 6, 1);
const migrEnd = new Date(2017, 0, 0);
const cbtStart = new Date(2018, 0, 1);
const pfmStart = new Date(2020, 0, 1);
const cbtEnd = new Date(2023, 7, 0);
const cbt = [cbtStart, cbtEnd] as const;
const mcB2cStart = new Date(2023, 10, 1);
const mcB2cEnd = new Date(2024, 5, 0);
const bpStart = new Date(2024, 5, 1);
const bpEnd = new Date(2025, 1, 0);
const bp = [bpStart, bpEnd] as const;
const kcStart = new Date(2026, 0, 1);
---
<BaseLayout title="kmoschcau.de | Skills">
<main class="flex items-center justify-center">
<article>
<div class="mb-8 flex items-baseline gap-6">
<a
href="/"
aria-label="Zurück"
title="Zurück"
class="text-soft rounded-full"
><ArrowLeftCircle role="presentation" class="size-9" /></a
>
<h1>Skills</h1>
</div>
<div class="prose prose-zinc dark:prose-invert">
<section>
<h2>💻 Softwareentwicklung &amp; Technologien</h2>
<ul>
<li>
Webentwicklung {from(cbtStart)}+ Jahre<br />
(moderne Web-UIs, Responsive Design, komplexe Komponenten, Frontend-Integration,
Web Accessibility, Web App Security)<br />
(HTML, CSS, JavaScript, Typescript, TailwindCSS, daisyUI, SASS, vite,
WebSocket, jQuery, drizzle ORM, Mocha, Chai, Jest)
</li>
<li>
Frontend-Frameworks {from(cbtStart)}+ Jahre<br />
(Erweitern und Umbauen von statischen Web-App Frontends in reactive
Frontends, Neubau von reaktiven Frontends)<br />
(Vue 2, Vue 3, Astro, Svelte, SvelteKit, React)
</li>
<li>
Java {between(...cbt) + from(kcStart)}+ Jahre<br />
(Enterprise B2B und B2C Web-Anwendungen, Web-APIs, Keycloak-Plugins,
saubere Architekturen, Web App Security)<br />
(Spring Boot, Spring MVC, Spring Security, Hibernate, JPA, Freemarker,
Thymeleaf, JSP, REST, JUnit, Mockito, Gradle, Maven)
</li>
<li>
C# - {from(pfmStart)}+ Jahre<br />
(REST- und SOAP-APIs mit hohem Durchsatz, Enterprise-Anwendungen, Web-APIs,
Backend-Systeme, saubere Architekturen, Web App Security)<br />
(.NET 8, .NET Framework 4.8, Entity Framework Core, NHibernate, ASP.NET
Core, Blazor, Razor, REST, SOAP, NUnit, Moq)
</li>
<li>
REST-APIs &amp; Backend-Services - {from(cbtStart)}+ Jahre<br />
(Schnittstellenkonzeption, REST, SignalR, WebSockets)
</li>
</ul>
</section>
<section>
<h2>🛡️ IT-Sicherheit</h2>
<ul>
<li>
Sichere Anwendungsentwicklung &amp; Design {from(cbtStart)}+
Jahre<br />
(OWASP, SSDLC, Threat Modeling, Web App Security)
</li>
<li>
Authentication &amp; Authorization {from(cbtStart)}+ Jahre<br />
(in-App &amp; extern)<br />
(OAuth2, OIDC, Keycloak)
</li>
<li>
Observability im Betrieb {from(cbtStart)}+ Jahre<br />
(Log-Analyse und statistische Auswertung, Telemetrie und Alarme)
</li>
<li>
Sicherheits-Analyse und Tests {between(...cbt)}+ Jahre<br />
(statische und Dynamische Code Analyse, manuelles Penetration Testing)<br
/>
(SonarQube, Veracode, Qualys, Zed Attack Proxy)
</li>
</ul>
</section>
<section>
<h2>☁️ Cloud, DevOps &amp; Betrieb</h2>
<ul>
<li>
Public, Private &amp; Clouds {between(cbtStart, mcB2cEnd)}+
Jahre<br />
(<InlineLink
href="https://www.credly.com/badges/e06dda2d-a444-448d-809a-31565c3b8c8d/public_url"
external="me"
>AWS Certified Solutions Architect Associate</InlineLink
>, AWS, GCP, Azure, CloudFormation, HashiCorp Terraform)
</li>
<li>
CI/CD &amp; DevOps {from(cbtStart)}+ Jahre<br />
(Git, GitHub Actions, GitLab, Gitrunner, Azure DevOps, Jenkins)
</li>
<li>
Containerisierung &amp; Images {from(cbtStart)}+ Jahre<br />
(Docker, Docker Compose, Kubernetes, Helm, HashiCorp Packer)
</li>
<li>
Deployment- &amp; Release-Management {from(cbtStart)}+ Jahre
</li>
</ul>
</section>
<section>
<h2>🗄️ Datenbanken &amp; Persistence</h2>
<ul>
<li>
Relationale Datenbanken {from(trustStart)}+ Jahre<br />
(MySQL, Oracle DB, Microsoft SQL Server, SQLite, PostgreSQL)
</li>
<li>
Datenmodellierung &amp; Performance-Optimierung {
between(...cbt) + between(...bp)
}+ Jahre
</li>
<li>
Data Warehousing {between(trustStart, migrEnd)}+ Jahre
</li>
</ul>
</section>
<section>
<h2>🧱 Architektur &amp; Software Design</h2>
<ul>
<li>
Software-Architektur &amp; Systemdesign {from(cbtStart)}+ Jahre
</li>
<li>
Modulare Systeme &amp; Multitenancy-Architekturen {
from(cbtStart)
}+ Jahre
</li>
<li>
Clean Code, Wartbarkeit &amp; Skalierbarkeit {from(cbtStart)}+
Jahre
</li>
</ul>
</section>
<section>
<h2>🔁 Methoden &amp; Projektarbeit</h2>
<ul>
<li>
Agile Entwicklung (Scrum) {from(trustStart)}+ Jahre
</li>
<li>
Technische Projektverantwortung {
between(...cbt) + between(...bp) + from(kcStart)
}+ Jahre
</li>
<li>
Kundenberatung &amp; technische Abstimmung {from(mcB2cStart)}+
Jahre
</li>
<li>
Code Reviews &amp; technische Qualitätssicherung {
from(cbtStart)
}+ Jahre
</li>
</ul>
</section>
<section>
<h2>🧪 Weitere Technologien &amp; Interessen</h2>
<ul>
<li>
Mentoring &amp; Wissensvermittlung im Team {from(mcB2cStart)}+
Jahre
</li>
<li>
Git &amp; GitHub (Open Source, Versionskontrolle) {
from(trStart)
}+ Jahre
</li>
<li>
Betriebssysteme: Linux (Daily Driver privat), Windows (Daily
Driver Arbeit)
</li>
<li>
Weitere Sprachen, Frameworks und Tools: Ruby, Rust, LaTeX, Lua,
treesitter, Shell, Python, C/C++, SSH, systemd, cron, Vim, Neovim
</li>
<li>
Interesse an 3D-Grafik: 3D Modellierung, Blender, Grundkonzepte
3D-Renderer
</li>
</ul>
</section>
</div>
</article>
</main>
</BaseLayout>

View File

@@ -1,4 +1,5 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@theme {
--color-background-dark: oklch(from var(--color-violet-950) 24% 0.08 h);
@@ -6,14 +7,24 @@
--link-bg-l: 53.5%;
--hover-darken: 0.05;
--color-fur-affinity: oklch(from #353b45 var(--link-bg-l) c h);
--color-fur-affinity-hover: oklch(
from var(--color-fur-affinity) calc(l - var(--hover-darken)) c h
--color-github: #1f2328;
--color-github-hover: oklch(
from var(--color-github) calc(l - var(--hover-darken)) c h
);
--color-mastodon: oklch(from #6364ff var(--link-bg-l) c h);
--color-mastodon-hover: oklch(
from var(--color-mastodon) calc(l - var(--hover-darken)) c h
--color-gitlab: #e24329;
--color-gitlab-hover: oklch(
from var(--color-gitlab) calc(l - var(--hover-darken)) c h
);
--color-keyoxide: #6855c3;
--color-keyoxide-hover: oklch(
from var(--color-keyoxide) calc(l - var(--hover-darken)) c h
);
--color-linked-in: #0b66c3;
--color-linked-in-hover: oklch(
from var(--color-linked-in) calc(l - var(--hover-darken)) c h
);
--color-matrix: oklch(from #0dbd8b var(--link-bg-l) c h);
@@ -24,7 +35,7 @@
@layer base {
body {
@apply dark:bg-background-dark bg-neutral-50 text-black dark:text-white;
@apply dark:bg-background-dark bg-neutral-50 text-zinc-900 dark:text-zinc-50;
}
h1 {
@@ -33,12 +44,24 @@
}
@layer components {
.link-fur-affinity {
@apply bg-fur-affinity hover:bg-fur-affinity-hover text-white;
.page-nav li:not(:first-child) {
@apply ms-2 border-s border-zinc-600 ps-2 dark:border-zinc-400;
}
.link-mastodon {
@apply bg-mastodon hover:bg-mastodon-hover text-white;
.link-github {
@apply bg-github hover:bg-github-hover text-white;
}
.link-gitlab {
@apply bg-gitlab hover:bg-gitlab-hover text-black;
}
.link-keyoxide {
@apply bg-keyoxide hover:bg-keyoxide-hover text-white;
}
.link-linked-in {
@apply bg-linked-in hover:bg-linked-in-hover text-white;
}
.link-matrix {