Rollout des Toggle-Patterns auf alle 57 statischen Sites (dasbes.de + dumusst.com sind dynamic, kein index.html). 1. **Bulk-Wiring (53 Sites)** via tools/patch-theme.py: - Anti-FOUC inline IIFE im <head> (vor erstem Paint) - <link rel="stylesheet" href="/shared/css/theme.css"> - <script src="/shared/theme.js"> + toggles.js (i18n.js bleibt, hängt sich ans neue Widget) 2. **Per-Site Light-Overrides (14 Sites)** via tools/patch-light-overrides.py: - 6034, allainallain, commanderkin, hallofraumaier, heygoldi, keinefreun, lexsiebels, machesdocheinfach, matthiasbreier, orakil, osterai, patentonkel, traihard, wartebitte - Pro Site nur die failing accent-vars darkened (--green-dim, --text-faint, --warm-dim, --gold-dim, etc.) - AA 4.5:1+ auf white bg gesichert; Brand-Akzent erhalten 3. **data-theme-lock="dark" (4 Sites)** auf <html>: - kilibri, killusion, killionaer, killuminati - Aesthetisch dark-only — toggles.js blendet Theme-Button automatisch aus, Lang-Button bleibt 4. **Footer-Toggle Removal (52 Sites)** via tools/remove-footer-toggle.py: - Bestehende footer [data-i18n-toggle] Buttons entfernt — top-right widget übernimmt - Disclaimer-Information in tooltip des neuen Buttons + ai-disclosure.js footer QA: - ./build.sh: 59/59 sites built clean - contrast-audit.py --both: 0/59 dark fail, 0/59 light fail - anti-ai-lint: 0/57 sites flagged Tools committed (idempotent, für Wiederverwendung): - tools/patch-theme.py (--all wired alle Sites) - tools/patch-light-overrides.py (per-site OVERRIDES dict) - tools/remove-footer-toggle.py (4 Patterns für versch. Footer-Strukturen)
onepager
Mono-repo for 40+ vanity domain onepager sites. Single nginx container with template system and server_name-based routing.
Structure
sites/ # One folder per domain
example.de/
site.yaml # Domain config, template choice, variables
index.html # Content (generated or hand-crafted)
assets/ # Optional images, fonts
templates/ # Shared HTML templates
shared/css/ # Shared CSS (variables, responsive, animations)
nginx/ # Generated nginx.conf + generator script
build/ # Generated output (gitignored)
Usage
Add a new site
# Templated site
./add-site.sh example.de --template person-dark --name "Max Mustermann"
# Custom HTML site
./add-site.sh example.de --template custom
Build
./build.sh # build + anti-AI text lint
./build.sh --skip-lint # build only (emergencies)
Requires yq for YAML parsing and python3 for the lint step. Outputs to build/.
Anti-AI text lint
Every build runs tools/anti-ai-lint.py against build/<domain>/index.html,
flagging text fingerprints typical of LLM-generated content (vocab and structure
patterns from tools/anti-ai-blacklist.yaml). Severity warn prints a message;
fail aborts the build.
Whitelist a hit:
- HTML comment in the affected page:
<!-- anti-ai-allow: revolutionär, em-dash-3-bullet --> - Per-site override in
site.yaml:anti_ai_allow: - revolutionär - em-dash-3-bullet
The blacklist source is docs/geo-seo-guideline.md §3.6. Test the linter with
tools/test-anti-ai-lint.sh.
Deploy
Push to main — Dokploy auto-deploys. All domains must be configured in Dokploy.
Templates
| Template | Description |
|---|---|
person-dark |
Professional profile, dark theme |
person-light |
Professional profile, light/cream theme |
product-dark |
Product/service landing page, dark |
editorial |
Long-form manifesto/editorial style |
fun |
Playful/personal pages |
minimal |
Bare-bones single section |
custom |
Hand-crafted HTML, no rendering |
site.yaml
domain: example.de
aliases: [www.example.de]
template: person-dark
title: "Page Title"
description: "Meta description"
lang: de
vars:
name: "Name"
role: "Role"
initials: "AB"
tagline: "Tagline here"
accent: "#c9a84c"
accent_light: "rgba(201, 168, 76, 0.1)"
font_primary: "Inter"
font_secondary: "Newsreader"
tags: ["Tag 1", "Tag 2"]
sections:
- type: features
title: "Section Title"
items:
- title: "Item"
desc: "Description"
- type: profile
bio: "Bio text"
cta:
text: "Contact"
href: "mailto:info@example.de"
schema:
type: Person
name: "Erika Mustermann"
url: "https://example.de/"
jobTitle: "Patentanwältin"
sameAs:
- https://www.linkedin.com/in/erika-mustermann/
- https://github.com/erika-mustermann
Schema.org / JSON-LD (GEO/SEO)
Templated sites can declare a schema: block in site.yaml. render.sh emits it as <script type="application/ld+json">…</script> inside <head> (slot {{schema_jsonld}} in templates/base.html). See docs/geo-seo-guideline.md §3.3 for rationale.
Conventions
schema.type→@type(the YAML key istype, the rendered key is@type).@context: https://schema.orgis added automatically.- Nested objects use the JSON-LD form directly: write
"@type": Organization(quoted because of the@). - Array fields like
sameAs:are passed through as JSON arrays. - If
schema:is absent, no<script>tag is emitted (empty slot). - If
schema.typeis omitted, the template default applies:person-dark,person-light→Personproduct-dark→Producteditorial→Articlefun,minimal→ no default (settype:explicitly).
Supported types include Person, Organization, Article, Product, FAQPage, LocalBusiness. Schema.org accepts any type — these are just the ones we use most.
Custom sites
template: custom skips rendering, so the slot is not applied. Hand-craft the JSON-LD directly inside index.html — typically right before </head>.
Testing
./tests/schema-test.sh
Renders fixture files in tests/fixtures/ and validates that JSON-LD is well-formed, has the correct @context/@type, and that sites without schema: produce no script tag.
For Schema.org-validator checks (recommended for any new live site that uses the slot), paste the rendered <script type="application/ld+json"> block into https://validator.schema.org/.
Related
- Issue #341: Onepager Mono-Repo
- Issue #335: Container consolidation