# 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/<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`:
  ```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 `<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 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 `<script>` tag is emitted (empty slot).
- If `schema.type` is omitted, the template default applies:
  - `person-dark`, `person-light` → `Person`
  - `product-dark` → `Product`
  - `editorial` → `Article`
  - `fun`, `minimal` → no default (set `type:` 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

```bash
./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
