Initial site: Astro + M3 Expressive, projects, blog

Personal site & blog for jeanlucmakiola.de.

- Astro 7 static output; self-hosted Inter + JetBrains Mono (Fontsource)
- Material 3 Expressive design tokens (red accent over neutral surfaces)
- Home (hero + about + selected work + latest writing)
- /work index + per-app pages (Calendula, Agendula, floret-kit) with
  build-time Gitea release tags, brand-icon link rows, features grid,
  and an at-a-glance facts panel
- Blog via Markdown content collection; RSS, sitemap, robots, OG/SEO
- Privacy-respecting Umami analytics, env-gated (off in dev)
- Dockerfile (build -> Caddy) + Caddyfile for Coolify deploy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Luc Makiola
2026-06-28 13:39:08 +02:00
commit 074199c656
35 changed files with 6954 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
---
import BaseHead from '../components/BaseHead.astro';
import Analytics from '../components/Analytics.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import { SITE } from '../consts';
// Self-hosted brand fonts (privacy-respecting — no Google CDN).
import '@fontsource-variable/inter';
import '@fontsource-variable/jetbrains-mono';
import '../styles/global.css';
interface Props {
title: string;
description?: string;
image?: string;
type?: 'website' | 'article';
/** 'narrow' caps width for long-form reading (blog, posts, tags). */
width?: 'wide' | 'narrow';
}
const { title, description, image, type, width = 'wide' } = Astro.props;
---
<!doctype html>
<html lang={SITE.lang}>
<head>
<BaseHead title={title} description={description} image={image} type={type} />
<Analytics />
</head>
<body>
<Header />
<main class:list={['container', { 'container--narrow': width === 'narrow' }]}>
<slot />
</main>
<Footer />
</body>
</html>

View File

@@ -0,0 +1,35 @@
---
import BaseLayout from './BaseLayout.astro';
import FormattedDate from '../components/FormattedDate.astro';
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>['data'];
}
const { post } = Astro.props;
const { title, description, pubDate, updatedDate, tags } = post;
---
<BaseLayout title={title} description={description} type="article" width="narrow">
<article class="prose">
<header class="post-header">
<h1>{title}</h1>
<p class="post-meta">
<FormattedDate date={pubDate} />
{updatedDate && <span> · updated <FormattedDate date={updatedDate} /></span>}
</p>
{
tags.length > 0 && (
<ul class="chip-set">
{tags.map((tag) => (
<li>
<a class="chip" href={`/tags/${tag}`}>#{tag}</a>
</li>
))}
</ul>
)
}
</header>
<slot />
</article>
</BaseLayout>