Update content licenses and footer information; enhance contact and privacy policy pages

This commit is contained in:
Adrian Altner 2026-03-30 16:04:48 +02:00
parent 8241f5b0b3
commit ad7fa6f966
6 changed files with 61 additions and 41 deletions

View file

@ -2,36 +2,46 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 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 ## Commands
```bash ```bash
npm run dev # Start dev server (localhost:4321) pnpm dev # Start dev server (localhost:4321)
npm run build # astro check + build + copy-sw (use this to verify changes) pnpm build # astro check + build + copy-sw (use this to verify changes)
npm run check # Type check + Biome lint pnpm build:production # Production build (skips astro check, uses --mode production)
npm run check:fix # Type check + Biome lint with auto-fix pnpm check # Type check + Biome lint
npm run stylelint # Lint CSS/Astro styles pnpm check:fix # Type check + Biome lint with auto-fix
npm run stylelint:fix # Fix style issues 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 ## 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`) ### Content Collections (`src/content.config.ts`)
Five collections defined with Zod schemas: Seven collections defined with Zod schemas:
| Collection | Path | Notes | | Collection | Loader | Path | Notes |
|---|---|---| |---|---|---|---|
| `blog` | `src/content/blog/` | Posts with series support (`seriesParent`, `seriesOrder`), tags, category ref, syndication URLs | | `blog` | glob | `src/content/blog/posts/` | Series support (`seriesParent`, `seriesOrder`), tags, category ref, syndication URLs |
| `categories` | `src/content/categories/` | Referenced by blog posts | | `categories` | glob | `src/content/blog/categories/` | Referenced by blog posts |
| `notes` | `src/content/notes/` | Short-form with optional cover image | | `notes` | glob | `src/content/notes/` | Short-form with optional cover image |
| `links` | `src/content/links/` | Curated external links | | `links_json` | file | `src/content/links/links.json` | JSON file with auto-generated IDs (`json/date/slug`) |
| `collections_photos` | `src/content/photos/collections/` | Photo collections; photos stored as JPG + JSON sidecar files in `img/` subdirs | | `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 ### 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`) - `/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 - `/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 - `/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/`) ### Lib Utilities (`src/lib/`)
- `collections.ts` — Photo collection helpers: `collectionSlug()`, `buildCollectionPhotos()`, `buildBreadcrumbs()`, `getChildCollections()` - `collections.ts` — Photo collection helpers: `collectionSlug()`, `buildCollectionPhotos()`, `buildBreadcrumbs()`, `getChildCollections()`
- `og.ts` — OG image generation using Satori (`buildArticleVNode`, `renderOgImage`) - `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 - `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/`) ### 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` - `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`
- `publish-posts.sh` — Full deploy orchestration: rsync content to VPS → rebuild container → send webmentions → run mastodon-syndicate - `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 ### 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 ### 4. Verification Before Done
- Never mark a task complete without proving it works - 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 - Use preview MCP tools to visually verify UI changes
### 5. Demand Elegance (Balanced) ### 5. Demand Elegance (Balanced)

View file

@ -26,7 +26,8 @@ SOFTWARE.
All content in `src/content/` and `public/` — including but not limited to All content in `src/content/` and `public/` — including but not limited to
articles, notes, and photographs — is licensed under 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/). [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/).
You are free to share and adapt the content, as long as you give appropriate You are free to share and adapt the content for non-commercial purposes, as long
credit and distribute your contributions under the same license. as you give appropriate credit and distribute your contributions under the same
license.

View file

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

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> <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 /> <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> <h2>How to reach me</h2>
<div class="cards"> <div class="cards">
@ -26,7 +29,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
</div> </div>
<div class="card__body"> <div class="card__body">
<p class="card__label">Email</p> <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>
</div> </div>
@ -42,12 +45,6 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
</div> </div>
</div> </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> </div>
</main> </main>
</div> </div>
@ -179,8 +176,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
.card__value { .card__value {
font-size: clamp(0.8rem, 3.5vw, 1rem); font-size: clamp(0.8rem, 3.5vw, 1rem);
color: var(--accent); color: #333;
text-underline-offset: 3px;
margin: 0; margin: 0;
white-space: nowrap; 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="bmVkc2VyRCA3OTAxMA=="></span><br>
<span class="r" data-obf="eW5hbXJlRw=="></span> <span class="r" data-obf="eW5hbXJlRw=="></span>
**Phone:** <a class="r" data-obf="MDI0MDM1ODcgNjUxIDk0Kw==" data-obf-href="dGVsOis0OTE1Njc4NTMwNDIw"></a> **Phone:** <span class="r" data-obf="MDI0MDM1ODcgNjUxIDk0Kw=="></span><br>
**Email:** <a class="r" data-obf="bW9je3RvZH1yZW50bGEtbmFpcmRhe3RhfXllaA==" data-obf-href="bWFpbHRvOmhleUBhZHJpYW4tYWx0bmVyLmNvbQ=="></a> **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: 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> <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>
--- ---