diff --git a/README.MD b/README.MD
index ddec830..dac9338 100644
--- a/README.MD
+++ b/README.MD
@@ -14,3 +14,9 @@ Lokal laufen lassen:
### Update aller Deps
`bundle update`
+
+### Projektnotizen
+
+Wichtige, nicht offensichtliche Fakten zum Repo (Build/Deploy, CSP, SEO)
+liegen unter [`memory/`](memory/README.md). Der Ordner wird nicht auf die
+Website veröffentlicht.
diff --git a/_config.yml b/_config.yml
index 8e1a2ec..1cd1db6 100644
--- a/_config.yml
+++ b/_config.yml
@@ -57,6 +57,12 @@ exclude:
- Gemfile
- Gemfile.lock
- bin/
+ - memory/ # internal project notes, not part of the published site
+ - README.MD
+ - package.json
+ - package-lock.json
+ - scripts/
+ - purgecss.config.js
# But make sure to process the following directories and their entries.
include:
diff --git a/memory/README.md b/memory/README.md
new file mode 100644
index 0000000..1bdf1dd
--- /dev/null
+++ b/memory/README.md
@@ -0,0 +1,10 @@
+# Project notes
+
+Durable, non-obvious facts about this repo that aren't derivable from the code or git history. Keep each note short and factual; delete notes that go stale.
+
+For this project these notes live **in the repo** (here), not in the global Claude auto-memory location — add new durable findings as files in this directory
+and link them below.
+
+- [ci-and-verification.md](ci-and-verification.md) — how the site builds/deploys and how to verify changes (incl. the rtk/prettier gotcha)
+- [csp-policy.md](csp-policy.md) — the meta-tag CSP: what it allows and why, and what can't be enforced this way
+- [seo-and-metadata.md](seo-and-metadata.md) — SEO/metadata decisions (German-only, no JSON-LD, canonical/feed)
diff --git a/memory/ci-and-verification.md b/memory/ci-and-verification.md
new file mode 100644
index 0000000..dbff205
--- /dev/null
+++ b/memory/ci-and-verification.md
@@ -0,0 +1,35 @@
+# CI, build, and verification
+
+## How the site is built and deployed
+
+- Deployed via **GitHub Actions** (`.github/workflows/jekyll.yml`) running `bundle exec jekyll build`, **not** the `github-pages` gem (it is commented out in
+ the `Gemfile`). Consequence: the site is **not** restricted to the GitHub Pages plugin allowlist — any Jekyll plugin listed in the `Gemfile` + `_config.yml`
+ `plugins:` works. (The old `whitelist:` block was removed as dead config.)
+- PRs are gated by `.github/workflows/ci.yml`: build → PurgeCSS → html-proofer → pa11y (non-blocking) → prettier. `_site/` is built during CI, so anything that
+ scans the repo (like prettier) must ignore `_site/` via `.prettierignore` — do **not** pass `--ignore-path ''`, which disables it.
+
+## `.ruby-version` must be fully qualified
+
+rbenv does exact-string matching on `.ruby-version`. A bare minor like `3.4` fails with `rbenv: version '3.4' is not installed` and blocks every `ruby`/`bundle`
+command. Use the installed patch version (e.g. `3.4.9`). `ruby/setup-ruby` in CI accepts the fully-qualified value fine.
+
+## Verifying prettier: bypass the rtk proxy
+
+An `rtk` shell hook in this environment intercepts `prettier` invocations and can print a **masked/canned result** (e.g. "All files formatted correctly") while
+the real prettier is reporting failures. This caused false "prettier passes locally" claims that CI then failed on. When verifying formatting, run it through
+the proxy bypass: `rtk proxy npx prettier --check .`. Treat CI green as the ground truth.
+
+## Bundler / rubygems networking
+
+`bundle update`/`install` fetches from `index.rubygems.org` (a CNAME to Fastly). Some local resolvers (e.g. a consumer router's DNS forwarder) fail to resolve
+that specific hostname while resolving the `rubygems.org` apex fine — symptom is "Could not reach host index.rubygems.org" even though the network is up. Fix on
+the machine: point DNS at a public resolver (1.1.1.1 / 8.8.8.8). Not a repo problem.
+
+## New top-level files leak into the published site
+
+Jekyll publishes any top-level file or directory that is not in the `_config.yml` `exclude:` list. Dotfiles/dot-dirs are auto-excluded, but plain-named ones are
+NOT. This repo had `README.MD`, `package.json`, `package-lock.json`, `scripts/`, `purgecss.config.js`, and a `memory/` dir all being served publicly (e.g.
+`https://www.inoeg.de/package.json` was live) until they were added to `exclude:`.
+
+Rule: whenever you add a plain-named top-level file or directory that is repo tooling / internal docs (not site content), add it to the `exclude:` list in
+`_config.yml`, then confirm it is absent from `_site/` after a build.
diff --git a/memory/csp-policy.md b/memory/csp-policy.md
new file mode 100644
index 0000000..77a7c6e
--- /dev/null
+++ b/memory/csp-policy.md
@@ -0,0 +1,24 @@
+# Content-Security-Policy (delivered via meta tag)
+
+The site has no HTTP-header control (GitHub Pages), so the CSP is a `` in `_includes/head.html`.
+
+Current policy:
+
+```
+default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';
+font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'
+```
+
+Why each non-obvious part is there:
+
+- **`style-src 'unsafe-inline'`** — several Webflow-exported pages (`_pages/projekte.html`, `_pages/offenerbrief-epa-2025.html`) use inline `style=` attributes.
+ Removing it breaks their layout; a CSP that breaks styling is worse than none.
+- **`font-src 'self' data:`** — `webflow.css` embeds an icon font as a `data:application/x-font-ttf` URI. Without `data:` the font is blocked (was a real
+ console error after the CSP first shipped).
+- **`img-src 'self' data:`** — same reason (data-URI images in the CSS).
+
+Cannot be enforced via a meta tag (header-only — omitted to avoid console noise / false sense of security): `X-Content-Type-Options`, `X-Frame-Options`, and CSP
+`frame-ancestors`. Enforcing those needs a header-capable front (e.g. Cloudflare).
+
+Implication: **do not add inline `