Compare commits

...

10 commits

Author SHA1 Message Date
ef9ed650b3 .
Some checks are pending
🪞 Mirror to Codeberg / codeberg (push) Waiting to run
2026-04-03 03:59:57 +02:00
23f69d1854 Update checkout action to version 5 in Codeberg mirror workflow
Some checks are pending
🪞 Mirror to Codeberg / codeberg (push) Waiting to run
2026-03-30 16:44:16 +02:00
9dfd3ef840 Add links.json to store external resources and update .gitignore to include it 2026-03-30 16:39:52 +02:00
ad7fa6f966 Update content licenses and footer information; enhance contact and privacy policy pages 2026-03-30 16:04:48 +02:00
8241f5b0b3 Remove AI features setting from VSCode configuration 2026-03-30 14:46:34 +02:00
50f3d32b49 Fix repository references in Codeberg mirror workflow 2026-03-30 14:46:16 +02:00
Adrian Altner
df2e4adde1
Merge pull request #2 from adrian-altner/dependabot/npm_and_yarn/patch-minor-72c70601f8
Bump the patch-minor group with 7 updates
2026-03-30 14:23:52 +02:00
dependabot[bot]
5c9b54a5fd
Bump the patch-minor group with 7 updates
Bumps the patch-minor group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [@astrojs/check](https://github.com/withastro/astro/tree/HEAD/packages/language-tools/astro-check) | `0.9.7` | `0.9.8` |
| [@astrojs/mdx](https://github.com/withastro/astro/tree/HEAD/packages/integrations/mdx) | `5.0.2` | `5.0.3` |
| [@astrojs/node](https://github.com/withastro/astro/tree/HEAD/packages/integrations/node) | `10.0.2` | `10.0.4` |
| [@astrojs/rss](https://github.com/withastro/astro/tree/HEAD/packages/astro-rss) | `4.0.17` | `4.0.18` |
| [@astrojs/sitemap](https://github.com/withastro/astro/tree/HEAD/packages/integrations/sitemap) | `3.7.1` | `3.7.2` |
| [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) | `6.0.4` | `6.1.1` |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.4.7` | `2.4.9` |


Updates `@astrojs/check` from 0.9.7 to 0.9.8
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/language-tools/astro-check/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/check@0.9.8/packages/language-tools/astro-check)

Updates `@astrojs/mdx` from 5.0.2 to 5.0.3
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/mdx@5.0.3/packages/integrations/mdx)

Updates `@astrojs/node` from 10.0.2 to 10.0.4
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/integrations/node/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/node@10.0.4/packages/integrations/node)

Updates `@astrojs/rss` from 4.0.17 to 4.0.18
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro-rss/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/rss@4.0.18/packages/astro-rss)

Updates `@astrojs/sitemap` from 3.7.1 to 3.7.2
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/integrations/sitemap/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/sitemap@3.7.2/packages/integrations/sitemap)

Updates `astro` from 6.0.4 to 6.1.1
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@6.1.1/packages/astro)

Updates `@biomejs/biome` from 2.4.7 to 2.4.9
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.9/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@astrojs/check"
  dependency-version: 0.9.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-minor
- dependency-name: "@astrojs/mdx"
  dependency-version: 5.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-minor
- dependency-name: "@astrojs/node"
  dependency-version: 10.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-minor
- dependency-name: "@astrojs/rss"
  dependency-version: 4.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-minor
- dependency-name: "@astrojs/sitemap"
  dependency-version: 3.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-minor
- dependency-name: astro
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: patch-minor
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 12:21:15 +00:00
Adrian Altner
9291411999
Merge pull request #1 from adrian-altner/dependabot/github_actions/actions-deps-76468cb07f
Bump actions/checkout from 4 to 6 in the actions-deps group
2026-03-30 14:20:24 +02:00
dependabot[bot]
421958e0d0
Bump actions/checkout from 4 to 6 in the actions-deps group
Bumps the actions-deps group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 12:17:47 +00:00
12 changed files with 382 additions and 439 deletions

View file

@ -10,7 +10,7 @@ jobs:
codeberg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
@ -31,11 +31,11 @@ jobs:
- name: Verify SSH access
run: |
ssh -T git@codeberg.org || true
git ls-remote git@codeberg.org:adrian-altner/www.adrian-altner.com.git > /dev/null
git ls-remote git@codeberg.org:adrian-altner/adrian-altner.com.git > /dev/null
- name: Mirror to Codeberg
run: |
git remote add mirror git@codeberg.org:adrian-altner/www.adrian-altner.com.git
git remote add mirror git@codeberg.org:adrian-altner/adrian-altner.com.git
# Remove previously mirrored remote-tracking refs (e.g. refs/remotes/origin/*).
while IFS= read -r ref; do
git push mirror ":${ref}"

2
.gitignore vendored
View file

@ -14,6 +14,8 @@ pnpm-debug.log*
# local content hotfolder
src/content/*
!src/content/links/
!src/content/links/links.json
# environment variables
.env

View file

@ -5,5 +5,4 @@
"source.fixAll": "explicit"
},
"editor.defaultFormatter": "biomejs.biome",
"chat.disableAIFeatures": true
}

View file

@ -2,36 +2,46 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Prerequisites
Node.js >= 22. Package manager is pnpm (enforced via `preinstall` script). Husky pre-commit hook runs `pnpm check`.
## Commands
```bash
npm run dev # Start dev server (localhost:4321)
npm run build # astro check + build + copy-sw (use this to verify changes)
npm run check # Type check + Biome lint
npm run check:fix # Type check + Biome lint with auto-fix
npm run stylelint # Lint CSS/Astro styles
npm run stylelint:fix # Fix style issues
pnpm dev # Start dev server (localhost:4321)
pnpm build # astro check + build + copy-sw (use this to verify changes)
pnpm build:production # Production build (skips astro check, uses --mode production)
pnpm check # Type check + Biome lint
pnpm check:fix # Type check + Biome lint with auto-fix
pnpm stylelint # Lint CSS/Astro styles
pnpm stylelint:fix # Fix style issues
pnpm generate:icons # Regenerate PWA icon assets
```
There are no automated tests. Verification is done via `npm run build` (0 errors required) and the preview MCP tools.
There are no automated tests. Verification is done via `pnpm build` (0 errors required) and the preview MCP tools.
## Architecture
Astro 6 site running in SSR mode (Node.js standalone adapter) with static output for most routes. TypeScript strict mode. Path alias `@/*``src/*`.
Astro 6 site running in SSR mode (Node.js standalone adapter) with static output for most routes. Pure Astro components — no React/Vue/Svelte. TypeScript strict mode (`astro/tsconfigs/strictest`). Path alias `@/*``src/*`.
**Formatter/linter:** Biome (not ESLint/Prettier). Run `check:fix` after larger edits.
**Formatter/linter:** Biome (not ESLint/Prettier). Run `check:fix` after larger edits. Biome config disables some rules for `.astro` files (`useConst`, `useImportType`, `noUnusedVariables`, `noUnusedImports`).
**Markdown plugins:** `remarkObsidianLinks` (custom, `src/lib/remark-obsidian-links.mjs`) for `[[wiki-link]]` syntax, `rehypeExternalLinks` for `target="_blank"` on external links.
### Content Collections (`src/content.config.ts`)
Five collections defined with Zod schemas:
Seven collections defined with Zod schemas:
| Collection | Path | Notes |
|---|---|---|
| `blog` | `src/content/blog/` | Posts with series support (`seriesParent`, `seriesOrder`), tags, category ref, syndication URLs |
| `categories` | `src/content/categories/` | Referenced by blog posts |
| `notes` | `src/content/notes/` | Short-form with optional cover image |
| `links` | `src/content/links/` | Curated external links |
| `collections_photos` | `src/content/photos/collections/` | Photo collections; photos stored as JPG + JSON sidecar files in `img/` subdirs |
| Collection | Loader | Path | Notes |
|---|---|---|---|
| `blog` | glob | `src/content/blog/posts/` | Series support (`seriesParent`, `seriesOrder`), tags, category ref, syndication URLs |
| `categories` | glob | `src/content/blog/categories/` | Referenced by blog posts |
| `notes` | glob | `src/content/notes/` | Short-form with optional cover image |
| `links_json` | file | `src/content/links/links.json` | JSON file with auto-generated IDs (`json/date/slug`) |
| `projects` | glob | `src/content/projects/project/` | Portfolio items with optional URL/GitHub links |
| `projects_categories` | glob | `src/content/projects/categories/` | Referenced by projects |
| `collections_photos` | glob | `src/content/photos/collections/` | Matches `**/index.{md,mdx}`; photos as JPG + JSON sidecar files in `img/` subdirs |
### Key Routing Patterns
@ -39,22 +49,35 @@ Five collections defined with Zod schemas:
- `/og/blog/[...slug].png` — OG images generated server-side via Satori (`src/lib/og.ts`)
- `/rss/blog.xml`, `/rss/notes.xml`, `/rss/links.xml`, `/rss/photos.xml` — Separate RSS feeds per content type
- `/photos/collections/[...slug]` — Nested photo collections with breadcrumb navigation
- `/projects/[...slug]`, `/projects/category/[...slug]` — Project portfolio
- `/tags/[slug]` — Cross-content-type tag pages
- `/archives/` — Timeline of all content
- `/api/webmentions.json` — Webmention data endpoint
### Lib Utilities (`src/lib/`)
- `collections.ts` — Photo collection helpers: `collectionSlug()`, `buildCollectionPhotos()`, `buildBreadcrumbs()`, `getChildCollections()`
- `og.ts` — OG image generation using Satori (`buildArticleVNode`, `renderOgImage`)
- `webmentions.ts` — Fetch and filter webmentions from webmention.io
- `webmentions.ts` — Fetch and filter webmentions from webmention.io (includes Mastodon/Bluesky validation)
- `photo-albums.ts` — Photo album organisation utilities
- `links.ts` — Link collection helpers
- `remark-obsidian-links.mjs` — Custom remark plugin for Obsidian-style `[[links]]`
### Scripts (`scripts/`)
- `mastodon-syndicate.js` — Scans `src/content/blog` and `src/content/notes` for posts without a `syndication` field, posts to Mastodon, writes the status URL back to frontmatter. Env vars: `MASTODON_BASE_URL`, `MASTODON_ACCESS_TOKEN`, `MASTODON_VISIBILITY`, `MASTODON_DRY_RUN`, `MASTODON_LIMIT`
- `publish-posts.sh` — Full deploy orchestration: rsync content to VPS → rebuild container → send webmentions → run mastodon-syndicate
- `mastodon-syndicate.mjs` — POSSE: scans blog/notes for posts without `syndication` field, posts to Mastodon, writes status URL back to frontmatter. Env vars: `MASTODON_BASE_URL`, `MASTODON_ACCESS_TOKEN`, `MASTODON_VISIBILITY`, `MASTODON_DRY_RUN`, `MASTODON_LIMIT`
- `vision.ts` — Uses Claude Vision API to auto-generate EXIF sidecars + metadata for photo collections. Requires `ANTHROPIC_API_KEY`
- `publish-all.sh` — Full deploy orchestration: rsync content to VPS → rebuild container → send webmentions → run mastodon-syndicate. Per-collection variants: `publish-blog.sh`, `publish-notes.sh`, `publish-links.sh`, `publish-photos.sh`, `publish-projects.sh`
- `new-post.sh`, `new-note.sh` — Content scaffolding templates
- `copy-sw.js` — Post-build service worker copy
### IndieWeb / Syndication
Blog posts support POSSE via `mastodon-syndicate.js`. After posting, the Mastodon status URL is written to frontmatter as `syndication: ["https://..."]`. The `SyndicationLinks` component reads this and renders `u-syndication` microformat links. Webmentions are fetched at build time from webmention.io and displayed via `WebMentions.astro`.
Blog posts support POSSE via `mastodon-syndicate.mjs`. After posting, the Mastodon status URL is written to frontmatter as `syndication: ["https://..."]`. The `SyndicationLinks` component renders `u-syndication` microformat links. Webmentions are fetched at build time from webmention.io and displayed via `WebMentions.astro`.
### Deployment
Multi-stage container build (`Containerfile`): Node 22 with pnpm for build, Node 22 slim for runtime. Runs `node dist/server/entry.mjs` on port 4321. Orchestrated via `compose.yml`. Deploy scripts rsync content to VPS and rebuild the container.
---
@ -78,7 +101,7 @@ Blog posts support POSSE via `mastodon-syndicate.js`. After posting, the Mastodo
### 4. Verification Before Done
- Never mark a task complete without proving it works
- Run `npm run build` — must complete with 0 errors
- Run `pnpm build` — must complete with 0 errors
- Use preview MCP tools to visually verify UI changes
### 5. Demand Elegance (Balanced)

11
LICENSE
View file

@ -19,14 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## Content License
All content in `src/content/` and `public/` — including but not limited to
articles, notes, and photographs — is licensed under
[Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/).
You are free to share and adapt the content, as long as you give appropriate
credit and distribute your contributions under the same license.

View file

@ -44,13 +44,13 @@
},
"dependencies": {
"@anthropic-ai/sdk": "^0.80.0",
"@astrojs/check": "^0.9.7",
"@astrojs/mdx": "^5.0.2",
"@astrojs/node": "^10.0.2",
"@astrojs/rss": "^4.0.17",
"@astrojs/sitemap": "^3.7.1",
"@astrojs/check": "^0.9.8",
"@astrojs/mdx": "^5.0.3",
"@astrojs/node": "^10.0.4",
"@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2",
"@fontsource/exo-2": "^5.2.8",
"astro": "^6.0.4",
"astro": "^6.1.1",
"astro-embed": "^0.12.0",
"chart.js": "^4.5.1",
"consola": "^3.4.2",
@ -63,7 +63,7 @@
"typescript": "^5.9.3"
},
"devDependencies": {
"@biomejs/biome": "^2.4.7",
"@biomejs/biome": "^2.4.9",
"@types/justified-layout": "^4.1.4",
"@types/leaflet": "^1.9.21",
"@types/leaflet.markercluster": "^1.5.6",

673
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ const { dark } = Astro.props;
<footer class={`footer${dark ? " footer--dark" : ""}`}>
<div class="footer__inner">
<p class="footer__license">
© 2026 Adrian Altner · <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener noreferrer">CC BY-SA 4.0</a> · <a href="/imprint">Imprint</a> · <a href="/privacy-policy">Privacy Policy</a> · <a href="/contact">Contact</a>
© 2026 Adrian Altner
</p>
<div class="footer__icons">
<a href="https://mastodon.social/@altner" aria-label="Mastodon" target="_blank" rel="me noopener noreferrer">

View file

@ -0,0 +1,19 @@
[
{
"title": "icomoon.io",
"url": "https://icomoon.io/",
"publishDate": "2026-03-27",
"description": "Many free icons",
"tags": ["ICONS", "SVG"],
"via": "https://dev.to/",
"collection": "Design"
},
{
"title": "13 CSS Blog Cards",
"url": "https://dev.to/frontendsolutions/13-css-blog-cards-54d7",
"publishDate": "2026-03-27",
"description": "not bad as inspiration",
"tags": ["css", "html", "frontend"],
"collection": "Development"
}
]

View file

@ -13,7 +13,10 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
<p>Feedback and corrections are always welcome. I read everything, but rarely reply — please don't be discouraged if you don't hear back.</p>
<hr />
<div class="note">
<strong>Please note:</strong> Unsolicited press releases, advertising, and SEO pitches will be deleted without a response.
</div>
<h2>How to reach me</h2>
<div class="cards">
@ -26,7 +29,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
</div>
<div class="card__body">
<p class="card__label">Email</p>
<a class="card__value r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA==" data-obf-href="bWFpbHRvOmhleUBhZHJpYW4tYWx0bmVyLmNvbQ=="></a>
<span class="card__value r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA=="></span>
</div>
</div>
@ -42,12 +45,6 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
</div>
</div>
</div>
<hr />
<div class="note">
<strong>Please note:</strong> Unsolicited press releases, advertising, and SEO pitches will be deleted without a response.
</div>
</div>
</main>
</div>
@ -179,8 +176,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
.card__value {
font-size: clamp(0.8rem, 3.5vw, 1rem);
color: var(--accent);
text-underline-offset: 3px;
color: #333;
margin: 0;
white-space: nowrap;
}

View file

@ -13,8 +13,8 @@ description: Legal notice for adrian-altner.com.
<span class="r" data-obf="bmVkc2VyRCA3OTAxMA=="></span><br>
<span class="r" data-obf="eW5hbXJlRw=="></span>
**Phone:** <a class="r" data-obf="MDI0MDM1ODcgNjUxIDk0Kw==" data-obf-href="dGVsOis0OTE1Njc4NTMwNDIw"></a>
**Email:** <a class="r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA==" data-obf-href="bWFpbHRvOmhleUBhZHJpYW4tYWx0bmVyLmNvbQ=="></a>
**Phone:** <span class="r" data-obf="MDI0MDM1ODcgNjUxIDk0Kw=="></span><br>
**Email:** <span class="r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA=="></span>
---
@ -27,9 +27,9 @@ description: Legal notice for adrian-altner.com.
---
## Content License
## License
Unless otherwise noted, content published on this website is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) license.
Unless otherwise noted, content published on this website is licensed under the [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) license. The source code is licensed under the [MIT License](https://mit-license.org/).
---

View file

@ -15,7 +15,7 @@ description: Privacy policy and data protection information for adrian-altner.co
The controller responsible for data processing on this website within the meaning of the General Data Protection Regulation (GDPR) is:
<strong><span class="r" data-obf="cmVudGxBIG5haXJkQQ=="></span></strong><br>
**Email:** <a class="r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA==" data-obf-href="bWFpbHRvOmhleUBhZHJpYW4tYWx0bmVyLmNvbQ=="></a>
**Email:** <span class="r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA=="></span>
---