Compare commits

..

14 Commits

18 changed files with 195 additions and 79 deletions

View File

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

View File

@@ -2,11 +2,17 @@
/dist/ /dist/
*.html
*.ico *.ico
*.jpg
*.js
*.json *.json
*.md *.md
*.mjs *.mjs
*.mts
*.png *.png
*.svg *.svg
*.ts
*.vim
*.webp *.webp
*.yaml *.yaml

View File

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

View File

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

View File

@@ -37,6 +37,7 @@
"postcss-html": "^1.8.1", "postcss-html": "^1.8.1",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro": "^0.14.1",
"prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"stylelint": "^17.5.0", "stylelint": "^17.5.0",

23
pnpm-lock.yaml generated
View File

@@ -72,9 +72,12 @@ importers:
prettier-plugin-astro: prettier-plugin-astro:
specifier: ^0.14.1 specifier: ^0.14.1
version: 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: prettier-plugin-tailwindcss:
specifier: ^0.7.2 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: sharp:
specifier: ^0.34.5 specifier: ^0.34.5
version: 0.34.5 version: 0.34.5
@@ -2939,6 +2942,16 @@ packages:
resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==}
engines: {node: ^14.15.0 || >=16.0.0} 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: prettier-plugin-tailwindcss@0.7.2:
resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==}
engines: {node: '>=20.19'} engines: {node: '>=20.19'}
@@ -6885,11 +6898,17 @@ snapshots:
prettier: 3.8.1 prettier: 3.8.1
sass-formatter: 0.7.9 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: dependencies:
prettier: 3.8.1 prettier: 3.8.1
optionalDependencies: optionalDependencies:
prettier-plugin-astro: 0.14.1 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: {} prettier@3.8.1: {}

View File

@@ -1,6 +1,10 @@
/** @type {import("prettier").Config} */ /** @type {import("prettier").Config} */
export default { export default {
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"], plugins: [
"prettier-plugin-astro",
"prettier-plugin-organize-imports",
"prettier-plugin-tailwindcss",
],
overrides: [ 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; SvgComponent: SvgComponent;
size?: number;
} }
const { size = 24, SvgComponent } = Astro.props; const {
SvgComponent,
class: className,
role = "presentation",
...rest
} = Astro.props;
--- ---
<SvgComponent <SvgComponent class:list={[className, "inline h-[1em]"]} {role} {...rest} />
height={size}
width={size}
fill="currentColor"
role="presentation"
/>

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

View File

@@ -1,8 +0,0 @@
<svg role="presentation" viewBox="0 0 100 100">
<g fill="none" stroke="currentColor" stroke-width="4">
<circle cx="50" cy="50" r="48" />
<line x1="25" x2="75" y1="50" y2="50" />
<line x1="25" x2="50" y1="50" y2="25" />
<line x1="25" x2="50" y1="50" y2="75" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 292 B

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,7 +1,7 @@
--- ---
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import ExternalLink from "../components/external-link.astro"; import ButtonLink from "../components/button-link.astro";
import InlineSvg from "../components/inline-svg.astro"; import InlineLink from "../components/inline-link.astro";
import GitHubLogo from "../images/GitHub.svg"; import GitHubLogo from "../images/GitHub.svg";
import GitLabLogo from "../images/GitLab.svg"; import GitLabLogo from "../images/GitLab.svg";
import KeyoxideLogo from "../images/Keyoxide.svg"; import KeyoxideLogo from "../images/Keyoxide.svg";
@@ -23,63 +23,109 @@ import BaseLayout from "../layouts/BaseLayout.astro";
height={200} height={200}
/> />
<h1>Kai Moschcau</h1> <h1>Kai Moschcau</h1>
<p class="text-soft">
<strong>PGP fingerprint:</strong>
<code>0x DF16 F424 1770 90BB</code>
</p>
<section class="text-soft"> <section class="text-soft">
<p>Fachinformatiker für Anwendungsentwicklung</p> <p><strong>Fachinformatiker</strong> für Anwendungsentwicklung</p>
<p><strong>Software Developer &amp; Consultant</strong> @ blecon</p>
</section> </section>
<nav> <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"> <ul class="flex flex-col gap-2">
<li> <li>
<a href="/skills">Skills</a> <ButtonLink
</li> class="link-matrix"
<li>
<ExternalLink
cssClass="link-matrix"
href="https://matrix.to/#/@kmoschcau:matrix.org" href="https://matrix.to/#/@kmoschcau:matrix.org"
title="Mein Matrix-Konto" title="Mein Matrix-Konto"
external="me"
> >
<InlineSvg SvgComponent={MatrixLogo} slot="logo" /> <MatrixLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>@kmoschcau:matrix.org</span> <span>@kmoschcau:matrix.org</span>
</ExternalLink> </ButtonLink>
</li> </li>
<li> <li>
<ExternalLink <ButtonLink
cssClass="link-github" class="link-github"
href="https://github.com/kmoschcau/" href="https://github.com/kmoschcau/"
title="Mein GitHub-Konto" title="Mein GitHub-Konto"
external="me"
> >
<InlineSvg SvgComponent={GitHubLogo} slot="logo" /> <GitHubLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span> <span>kmoschcau</span>
</ExternalLink> </ButtonLink>
</li> </li>
<li> <li>
<ExternalLink <ButtonLink
cssClass="link-gitlab" class="link-gitlab"
href="https://gitlab.com/kmoschcau" href="https://gitlab.com/kmoschcau"
title="Mein GitLab-Konto" title="Mein GitLab-Konto"
external="me"
> >
<InlineSvg SvgComponent={GitLabLogo} slot="logo" /> <GitLabLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span> <span>kmoschcau</span>
</ExternalLink> </ButtonLink>
</li> </li>
<li> <li>
<ExternalLink <ButtonLink
cssClass="link-linked-in" class="link-linked-in"
href="https://www.linkedin.com/in/kmoschcau/" href="https://www.linkedin.com/in/kmoschcau/"
title="Mein LinkedIn-Konto" title="Mein LinkedIn-Konto"
external="me"
> >
<InlineSvg SvgComponent={LinkedInLogo} slot="logo" /> <LinkedInLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>kmoschcau</span> <span>kmoschcau</span>
</ExternalLink> </ButtonLink>
</li> </li>
<li> <li>
<ExternalLink <ButtonLink
cssClass="link-keyoxide" class="link-keyoxide"
href="https://keyoxide.org/8CE00E9495B5030DA9217208DF16F424177090BB" href="https://keyoxide.org/8CE00E9495B5030DA9217208DF16F424177090BB"
title="Mein Keyoxide-Eintrag" title="Mein Keyoxide-Eintrag"
external="me"
> >
<InlineSvg SvgComponent={KeyoxideLogo} slot="logo" /> <KeyoxideLogo
class="size-6"
fill="currentColor"
role="presentation"
slot="logo"
/>
<span>Keyoxide</span> <span>Keyoxide</span>
</ExternalLink> </ButtonLink>
</li> </li>
</ul> </ul>
</nav> </nav>

View File

@@ -1,10 +1,11 @@
--- ---
import BackIcon from "../icons/back.svg"; import ArrowLeftCircle from "../icons/arrow-left-circle.svg";
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import { import {
getDurationInYearsBetween as between, getDurationInYearsBetween as between,
getDurationInYearsFrom as from, getDurationInYearsFrom as from,
} from "../duration-utils"; } from "../duration-utils";
import InlineLink from "../components/inline-link.astro";
const trStart = new Date(2011, 7, 1); const trStart = new Date(2011, 7, 1);
@@ -32,8 +33,12 @@ const kcStart = new Date(2026, 0, 1);
<main class="flex items-center justify-center"> <main class="flex items-center justify-center">
<article> <article>
<div class="mb-8 flex items-baseline gap-6"> <div class="mb-8 flex items-baseline gap-6">
<a href="/" title="Zurück" class="text-soft" <a
><BackIcon class="size-9" /></a href="/"
aria-label="Zurück"
title="Zurück"
class="text-soft rounded-full"
><ArrowLeftCircle role="presentation" class="size-9" /></a
> >
<h1>Skills</h1> <h1>Skills</h1>
</div> </div>
@@ -52,7 +57,7 @@ const kcStart = new Date(2026, 0, 1);
Frontend-Frameworks {from(cbtStart)}+ Jahre<br /> Frontend-Frameworks {from(cbtStart)}+ Jahre<br />
(Erweitern und Umbauen von statischen Web-App Frontends in reactive (Erweitern und Umbauen von statischen Web-App Frontends in reactive
Frontends, Neubau von reaktiven Frontends)<br /> Frontends, Neubau von reaktiven Frontends)<br />
(Vue 2, Vue 3, Svelte, SvelteKit, React) (Vue 2, Vue 3, Astro, Svelte, SvelteKit, React)
</li> </li>
<li> <li>
Java {between(...cbt) + from(kcStart)}+ Jahre<br /> Java {between(...cbt) + from(kcStart)}+ Jahre<br />
@@ -107,9 +112,10 @@ const kcStart = new Date(2026, 0, 1);
<li> <li>
Public, Private &amp; Clouds {between(cbtStart, mcB2cEnd)}+ Public, Private &amp; Clouds {between(cbtStart, mcB2cEnd)}+
Jahre<br /> Jahre<br />
(<a (<InlineLink
href="https://www.credly.com/badges/e06dda2d-a444-448d-809a-31565c3b8c8d/public_url" href="https://www.credly.com/badges/e06dda2d-a444-448d-809a-31565c3b8c8d/public_url"
>AWS Certified Solutions Architect Associate</a external="me"
>AWS Certified Solutions Architect Associate</InlineLink
>, AWS, GCP, Azure, CloudFormation, HashiCorp Terraform) >, AWS, GCP, Azure, CloudFormation, HashiCorp Terraform)
</li> </li>
<li> <li>

View File

@@ -44,6 +44,10 @@
} }
@layer components { @layer components {
.page-nav li:not(:first-child) {
@apply ms-2 border-s border-zinc-600 ps-2 dark:border-zinc-400;
}
.link-github { .link-github {
@apply bg-github hover:bg-github-hover text-white; @apply bg-github hover:bg-github-hover text-white;
} }