# 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 ```bash # Templated site ./add-site.sh example.de --template person-dark --name "Max Mustermann" # Custom HTML site ./add-site.sh example.de --template custom ``` ### Build ```bash ./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//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: `` - Per-site override in `site.yaml`: ```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 ```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 `` inside `` (slot `{{schema_jsonld}}` in `templates/base.html`). See `docs/geo-seo-guideline.md` §3.3 for rationale. ### Conventions - `schema.type` → `@type` (the YAML key is `type`, the rendered key is `@type`). - `@context: https://schema.org` is 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 `