Compare commits
19 Commits
276b8401ef
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
4aa64a880d
|
|||
|
614a32e0d4
|
|||
|
59525cbde5
|
|||
|
264fe63e9b
|
|||
|
7d4c8e6cb1
|
|||
|
924af6e200
|
|||
|
62339b4f33
|
|||
|
d9d87daf7e
|
|||
|
320035c828
|
|||
|
92ca1f6feb
|
|||
|
bfd53b66ec
|
|||
|
d6aea329fe
|
|||
|
d5eef87740
|
|||
|
6eb4334691
|
|||
|
4ce0421e95
|
|||
|
91c8a825be
|
|||
|
e5ab74ebfb
|
|||
|
3e7c1d7ad3
|
|||
|
2b988b9be9
|
@@ -2,5 +2,8 @@
|
||||
"extends": ["markuplint:recommended-static-html"],
|
||||
"parser": {
|
||||
".astro$": "@markuplint/astro-parser"
|
||||
},
|
||||
"rules": {
|
||||
"use-list": false
|
||||
}
|
||||
}
|
||||
|
||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
/.astro/
|
||||
/dist/
|
||||
/pnpm-lock.yaml
|
||||
@@ -1,7 +1,18 @@
|
||||
# vim: filetype=gitignore
|
||||
|
||||
/dist/
|
||||
|
||||
*.html
|
||||
*.ico
|
||||
*.jpg
|
||||
*.js
|
||||
*.json
|
||||
*.md
|
||||
*.mjs
|
||||
*.mts
|
||||
*.png
|
||||
*.svg
|
||||
*.ts
|
||||
*.vim
|
||||
*.webp
|
||||
*.yaml
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"lint:eslint": "eslint",
|
||||
"lint:markuplint": "markuplint '**/*.{astro,html}'",
|
||||
"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.2",
|
||||
"astro": "^6.0.8",
|
||||
"astro": "^6.1.2",
|
||||
"tailwindcss": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -23,6 +25,7 @@
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@markuplint/astro-parser": "^4.6.23",
|
||||
"@markuplint/ml-config": "^4.8.15",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"cspell": "^9.7.0",
|
||||
"eslint": "^10.1.0",
|
||||
@@ -34,6 +37,7 @@
|
||||
"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.5.0",
|
||||
|
||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ importers:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
astro:
|
||||
specifier: ^6.0.8
|
||||
version: 6.0.8(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
tailwindcss:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
@@ -36,6 +36,9 @@ importers:
|
||||
'@markuplint/ml-config':
|
||||
specifier: ^4.8.15
|
||||
version: 4.8.15
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.19
|
||||
version: 0.5.19(tailwindcss@4.2.2)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.57.2
|
||||
version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
|
||||
@@ -69,9 +72,12 @@ importers:
|
||||
prettier-plugin-astro:
|
||||
specifier: ^0.14.1
|
||||
version: 0.14.1
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(prettier@3.8.1)(typescript@5.9.3)
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.7.2
|
||||
version: 0.7.2(prettier-plugin-astro@0.14.1)(prettier@3.8.1)
|
||||
version: 0.7.2(prettier-plugin-astro@0.14.1)(prettier-plugin-organize-imports@4.3.0(prettier@3.8.1)(typescript@5.9.3))(prettier@3.8.1)
|
||||
sharp:
|
||||
specifier: ^0.34.5
|
||||
version: 0.34.5
|
||||
@@ -105,8 +111,8 @@ packages:
|
||||
'@astrojs/internal-helpers@0.8.0':
|
||||
resolution: {integrity: sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==}
|
||||
|
||||
'@astrojs/markdown-remark@7.0.1':
|
||||
resolution: {integrity: sha512-zAfLJmn07u9SlDNNHTpjv0RT4F8D4k54NR7ReRas8CO4OeGoqSvOuKwqCFg2/cqN3wHwdWlK/7Yv/lMXlhVIaw==}
|
||||
'@astrojs/markdown-remark@7.1.0':
|
||||
resolution: {integrity: sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ==}
|
||||
|
||||
'@astrojs/prism@4.0.1':
|
||||
resolution: {integrity: sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==}
|
||||
@@ -1170,6 +1176,11 @@ packages:
|
||||
resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@tailwindcss/typography@0.5.19':
|
||||
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
|
||||
|
||||
'@tailwindcss/vite@4.2.2':
|
||||
resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==}
|
||||
peerDependencies:
|
||||
@@ -1351,8 +1362,8 @@ packages:
|
||||
resolution: {integrity: sha512-+QDcgc7e+au6EZ0YjMmRRjNoQo5bDMlaR45aWDoFsuxQTCM9qmCHRoiKJPELgckJ8Wmr7vcfpa9eCDHBFh6G4w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
astro@6.0.8:
|
||||
resolution: {integrity: sha512-DCPeb8GKOoFWh+8whB7Qi/kKWD/6NcQ9nd1QVNzJFxgHkea3WYrNroQRq4whmBdjhkYPTLS/1gmUAl2iA2Es2g==}
|
||||
astro@6.1.2:
|
||||
resolution: {integrity: sha512-r3iIvmB6JvQxsdJLvapybKKq7Bojd1iQK6CCx5P55eRnXJIyUpHx/1UB/GdMm+em/lwaCUasxHCmIO0lCLV2uA==}
|
||||
engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2904,6 +2915,10 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.4.31
|
||||
|
||||
postcss-selector-parser@6.0.10:
|
||||
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2927,6 +2942,16 @@ packages:
|
||||
resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==}
|
||||
engines: {node: ^14.15.0 || >=16.0.0}
|
||||
|
||||
prettier-plugin-organize-imports@4.3.0:
|
||||
resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==}
|
||||
peerDependencies:
|
||||
prettier: '>=2.0'
|
||||
typescript: '>=2.9'
|
||||
vue-tsc: ^2.1.0 || 3
|
||||
peerDependenciesMeta:
|
||||
vue-tsc:
|
||||
optional: true
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.2:
|
||||
resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==}
|
||||
engines: {node: '>=20.19'}
|
||||
@@ -3654,7 +3679,7 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
'@astrojs/markdown-remark@7.0.1':
|
||||
'@astrojs/markdown-remark@7.1.0':
|
||||
dependencies:
|
||||
'@astrojs/internal-helpers': 0.8.0
|
||||
'@astrojs/prism': 4.0.1
|
||||
@@ -3669,6 +3694,7 @@ snapshots:
|
||||
remark-parse: 11.0.0
|
||||
remark-rehype: 11.1.2
|
||||
remark-smartypants: 3.0.2
|
||||
retext-smartypants: 6.2.0
|
||||
shiki: 4.0.2
|
||||
smol-toml: 1.6.1
|
||||
unified: 11.0.5
|
||||
@@ -4619,6 +4645,11 @@ snapshots:
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
|
||||
|
||||
'@tailwindcss/typography@0.5.19(tailwindcss@4.2.2)':
|
||||
dependencies:
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 4.2.2
|
||||
|
||||
'@tailwindcss/vite@4.2.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.2.2
|
||||
@@ -4853,11 +4884,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
astro@6.0.8(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
astro@6.1.2(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 3.0.1
|
||||
'@astrojs/internal-helpers': 0.8.0
|
||||
'@astrojs/markdown-remark': 7.0.1
|
||||
'@astrojs/markdown-remark': 7.1.0
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@clack/prompts': 1.1.0
|
||||
@@ -6837,6 +6868,11 @@ snapshots:
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
|
||||
postcss-selector-parser@6.0.10:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
@@ -6862,11 +6898,17 @@ snapshots:
|
||||
prettier: 3.8.1
|
||||
sass-formatter: 0.7.9
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.2(prettier-plugin-astro@0.14.1)(prettier@3.8.1):
|
||||
prettier-plugin-organize-imports@4.3.0(prettier@3.8.1)(typescript@5.9.3):
|
||||
dependencies:
|
||||
prettier: 3.8.1
|
||||
typescript: 5.9.3
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.2(prettier-plugin-astro@0.14.1)(prettier-plugin-organize-imports@4.3.0(prettier@3.8.1)(typescript@5.9.3))(prettier@3.8.1):
|
||||
dependencies:
|
||||
prettier: 3.8.1
|
||||
optionalDependencies:
|
||||
prettier-plugin-astro: 0.14.1
|
||||
prettier-plugin-organize-imports: 4.3.0(prettier@3.8.1)(typescript@5.9.3)
|
||||
|
||||
prettier@3.8.1: {}
|
||||
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
21
src/components/button-link.astro
Normal file
21
src/components/button-link.astro
Normal 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
|
||||
>
|
||||
@@ -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
|
||||
>
|
||||
24
src/components/inline-link.astro
Normal file
24
src/components/inline-link.astro
Normal 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
|
||||
>
|
||||
@@ -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
11
src/duration-utils.ts
Normal 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());
|
||||
}
|
||||
3
src/icons/arrow-left-circle.svg
Normal file
3
src/icons/arrow-left-circle.svg
Normal 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 |
3
src/icons/arrow-top-right-on-square.svg
Normal file
3
src/icons/arrow-top-right-on-square.svg
Normal 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 |
21
src/layouts/BaseLayout.astro
Normal file
21
src/layouts/BaseLayout.astro
Normal 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
7
src/link-utils.ts
Normal 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;
|
||||
}
|
||||
@@ -1,98 +1,134 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import ExternalLink from "../components/external-link.astro";
|
||||
import InlineSvg from "../components/inline-svg.astro";
|
||||
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>
|
||||
<section class="text-soft">
|
||||
<p>Fachinformatiker für Anwendungsentwicklung</p>
|
||||
</section>
|
||||
<nav>
|
||||
<ul class="flex flex-col gap-2">
|
||||
<li>
|
||||
<ExternalLink
|
||||
cssClass="link-matrix"
|
||||
href="https://matrix.to/#/@kmoschcau:matrix.org"
|
||||
title="Mein Matrix-Konto"
|
||||
>
|
||||
<InlineSvg SvgComponent={MatrixLogo} slot="logo" />
|
||||
<span>@kmoschcau:matrix.org</span>
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink
|
||||
cssClass="link-github"
|
||||
href="https://github.com/kmoschcau/"
|
||||
title="Mein GitHub-Konto"
|
||||
>
|
||||
<InlineSvg SvgComponent={GitHubLogo} slot="logo" />
|
||||
<span>kmoschcau</span>
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink
|
||||
cssClass="link-gitlab"
|
||||
href="https://gitlab.com/kmoschcau"
|
||||
title="Mein GitLab-Konto"
|
||||
>
|
||||
<InlineSvg SvgComponent={GitLabLogo} slot="logo" />
|
||||
<span>kmoschcau</span>
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink
|
||||
cssClass="link-linked-in"
|
||||
href="https://www.linkedin.com/in/kmoschcau/"
|
||||
title="Mein LinkedIn-Konto"
|
||||
>
|
||||
<InlineSvg SvgComponent={LinkedInLogo} slot="logo" />
|
||||
<span>kmoschcau</span>
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink
|
||||
cssClass="link-keyoxide"
|
||||
href="https://keyoxide.org/8CE00E9495B5030DA9217208DF16F424177090BB"
|
||||
title="Mein Keyoxide-Eintrag"
|
||||
>
|
||||
<InlineSvg SvgComponent={KeyoxideLogo} slot="logo" />
|
||||
<span>Keyoxide</span>
|
||||
</ExternalLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<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 & Consultant</strong> @ blecon</p>
|
||||
</section>
|
||||
<nav aria-label="Seitennavigation" class="page-nav">
|
||||
<ul class="flex">
|
||||
<li>
|
||||
<a href="/skills" class="font-medium underline">Skills</a>
|
||||
</li>
|
||||
<li>
|
||||
<InlineLink
|
||||
href="https://blecon.de"
|
||||
external
|
||||
class="font-medium underline">blecon.de</InlineLink
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</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
223
src/pages/skills.astro
Normal 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 & 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 & Backend-Services - {from(cbtStart)}+ Jahre<br />
|
||||
(Schnittstellenkonzeption, REST, SignalR, WebSockets)
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🛡️ IT-Sicherheit</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Sichere Anwendungsentwicklung & Design – {from(cbtStart)}+
|
||||
Jahre<br />
|
||||
(OWASP, SSDLC, Threat Modeling, Web App Security)
|
||||
</li>
|
||||
<li>
|
||||
Authentication & Authorization – {from(cbtStart)}+ Jahre<br />
|
||||
(in-App & 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 & Betrieb</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Public, Private & 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 & DevOps – {from(cbtStart)}+ Jahre<br />
|
||||
(Git, GitHub Actions, GitLab, Gitrunner, Azure DevOps, Jenkins)
|
||||
</li>
|
||||
<li>
|
||||
Containerisierung & Images – {from(cbtStart)}+ Jahre<br />
|
||||
(Docker, Docker Compose, Kubernetes, Helm, HashiCorp Packer)
|
||||
</li>
|
||||
<li>
|
||||
Deployment- & Release-Management – {from(cbtStart)}+ Jahre
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🗄️ Datenbanken & Persistence</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Relationale Datenbanken – {from(trustStart)}+ Jahre<br />
|
||||
(MySQL, Oracle DB, Microsoft SQL Server, SQLite, PostgreSQL)
|
||||
</li>
|
||||
<li>
|
||||
Datenmodellierung & Performance-Optimierung – {
|
||||
between(...cbt) + between(...bp)
|
||||
}+ Jahre
|
||||
</li>
|
||||
<li>
|
||||
Data Warehousing – {between(trustStart, migrEnd)}+ Jahre
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🧱 Architektur & Software Design</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Software-Architektur & Systemdesign – {from(cbtStart)}+ Jahre
|
||||
</li>
|
||||
<li>
|
||||
Modulare Systeme & Multitenancy-Architekturen – {
|
||||
from(cbtStart)
|
||||
}+ Jahre
|
||||
</li>
|
||||
<li>
|
||||
Clean Code, Wartbarkeit & Skalierbarkeit – {from(cbtStart)}+
|
||||
Jahre
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🔁 Methoden & Projektarbeit</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Agile Entwicklung (Scrum) – {from(trustStart)}+ Jahre
|
||||
</li>
|
||||
<li>
|
||||
Technische Projektverantwortung – {
|
||||
between(...cbt) + between(...bp) + from(kcStart)
|
||||
}+ Jahre
|
||||
</li>
|
||||
<li>
|
||||
Kundenberatung & technische Abstimmung – {from(mcB2cStart)}+
|
||||
Jahre
|
||||
</li>
|
||||
<li>
|
||||
Code Reviews & technische Qualitätssicherung – {
|
||||
from(cbtStart)
|
||||
}+ Jahre
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🧪 Weitere Technologien & Interessen</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Mentoring & Wissensvermittlung im Team – {from(mcB2cStart)}+
|
||||
Jahre
|
||||
</li>
|
||||
<li>
|
||||
Git & 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>
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@theme {
|
||||
--color-background-dark: oklch(from var(--color-violet-950) 24% 0.08 h);
|
||||
@@ -34,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 {
|
||||
@@ -43,6 +44,10 @@
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.page-nav li:not(:first-child) {
|
||||
@apply ms-2 border-s border-zinc-600 ps-2 dark:border-zinc-400;
|
||||
}
|
||||
|
||||
.link-github {
|
||||
@apply bg-github hover:bg-github-hover text-white;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user