# Praxis am Lienhardplatz — Zola Rebuild Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a Zola static site that reproduces the current Grav-based praxis-am-lienhardplatz.de 1:1, deployed to the staging subdomain `preview.praxis-am-lienhardplatz.de`, without touching the live site.

**Architecture:** Zola SSG. Content as Markdown with TOML frontmatter under `content/`. Tera templates under `templates/`. Bootstrap 3 CSS/JS lifted verbatim from the Grav theme. Custom image shortcode wrapping Zola's `resize_image()`. Deploy via `rsync` to `~/public/praxis/` on `mycelium`.

**Tech Stack:** Zola v0.22.1, Tera templates, Markdown, TOML, Bootstrap 3.3.6, jQuery 2.1.4, Font Awesome 4.x (verbatim from Grav theme), Google Maps via `<iframe>` embed.

**Reference:** Design spec at `docs/specs/2026-05-20-praxis-zola-rebuild-design.md`. Read-only source snapshot at `orig/`.

**Working directory:** `/home/brummel/dev/praxis/` is the repo root.

---

## File Structure

Created in this plan:

```
/home/brummel/dev/praxis/         # repo root (git init here)
├── .gitignore                    # Task 1
├── config.toml                   # Task 3
├── README.md                     # Task 2
├── content/
│   ├── _index.md                 # Task 12
│   ├── news.md                   # Task 14
│   ├── services/
│   │   ├── _index.md             # Task 15
│   │   ├── hv.md                 # Task 16
│   │   ├── corona.md             # Task 16
│   │   ├── vorsorge.md           # Task 16
│   │   ├── diagnostik.md         # Task 16
│   │   ├── kardiologie.md        # Task 16
│   │   └── diabetologie.md       # Task 16
│   ├── team/
│   │   ├── _index.md             # Task 18
│   │   ├── doctors.md            # Task 18
│   │   ├── employees.md          # Task 18
│   │   └── jobs.md               # Task 18
│   ├── legal.md                  # Task 19
│   └── contact.md                # Task 19
├── templates/
│   ├── base.html                 # Task 9
│   ├── index.html                # Task 13
│   ├── section.html              # Task 17
│   ├── page.html                 # Task 19
│   ├── partials/
│   │   ├── head.html             # Task 10
│   │   └── navbar.html           # Task 11
│   └── shortcodes/
│       ├── img.html              # Task 8
│       └── map.html              # Task 20
├── static/
│   ├── images/                   # Task 5
│   ├── fonts/                    # Task 6
│   ├── css/                      # Task 6 (verbatim Bootstrap + theme CSS)
│   ├── js/                       # Task 5 (jQuery, Bootstrap, flexSlider, etc.)
│   └── robots.txt                # Task 21
├── sass/
│   ├── main.scss                 # Task 7 (entry point)
│   └── _praxis_theme.scss        # Task 7 (lifted from theme)
└── docs/
    ├── specs/                    # exists already
    └── plans/                    # exists already (this file lives here)
```

Not in the repo (gitignored):
- `orig/` — read-only snapshot of the Grav site, used as reference
- `.superpowers/` — brainstorming session artifacts
- `public/` — Zola build output
- `processed_images/` — Zola image cache

---

## Task 1: Repo Bootstrap

**Files:**
- Create: `/home/brummel/dev/praxis/.gitignore`
- Modify: git remote in `/home/brummel/dev/praxis/.git/config`

- [ ] **Step 1: Initialize git repo and configure remote**

```fish
cd /home/brummel/dev/praxis
git init -b main
git remote add origin http://192.168.178.103:3000/Brummel/praxis-page.git
```

- [ ] **Step 2: Write `.gitignore`**

```
# Zola build output
public/
processed_images/

# Project-local artifacts not for upstream
orig/
.superpowers/

# Editor / OS noise
*.swp
*.swo
*~
.DS_Store

# Logs / caches that might leak in
*.log
.zola/
```

- [ ] **Step 3: Verify git sees only intended files**

Run: `git status --short`
Expected: lists `docs/specs/2026-05-20-praxis-zola-rebuild-design.md` and `docs/plans/2026-05-20-praxis-zola-rebuild.md` and `.gitignore`, nothing from `orig/` or `.superpowers/`.

- [ ] **Step 4: Stage and commit**

```fish
git add .gitignore docs/specs/2026-05-20-praxis-zola-rebuild-design.md docs/plans/2026-05-20-praxis-zola-rebuild.md
git commit -m "chore: bootstrap repo with design spec and implementation plan"
```

- [ ] **Step 5: Push to Gitea**

```fish
git push -u origin main
```

Expected: push succeeds. If authentication prompts appear, the user provides credentials or has them cached.

---

## Task 2: README

**Files:**
- Create: `README.md`

- [ ] **Step 1: Write README**

```markdown
# Praxis am Lienhardplatz — Static Site

Static rebuild of the practice website, replacing the previous Grav 1.4.3 installation.

## Build

```fish
zola build       # production build → ./public/
zola serve       # dev server with auto-reload → http://127.0.0.1:1111
```

## Deploy (staging)

```fish
zola build
rsync -av --delete public/ mycelium:public/praxis/
```

Staging URL: `https://preview.praxis-am-lienhardplatz.de`

## Layout

- `content/` — Markdown pages
- `templates/` — Tera templates
- `static/` — verbatim assets (JS, CSS, fonts, images)
- `sass/` — SCSS sources (compiled by Zola)
- `docs/specs/` — design specs
- `docs/plans/` — implementation plans

The directory `orig/` (gitignored) is a read-only snapshot of the previous Grav installation, kept as a reference for content and visual parity.
```

- [ ] **Step 2: Commit**

```fish
git add README.md
git commit -m "docs: add README with build and deploy instructions"
```

---

## Task 3: Zola Skeleton

**Files:**
- Create: `config.toml`
- Create: `content/_index.md` (placeholder)

- [ ] **Step 1: Write `config.toml`**

```toml
base_url = "https://preview.praxis-am-lienhardplatz.de"
title = "Praxis am Lienhardplatz"
description = "Dr. Oliver Riemann, Dr. Angela Krey und Christian Malyga begrüßen Sie in der Hausärztlichen Gemeinschaftspraxis am Lienhardplatz in Wuppertal-Vohwinkel"
default_language = "de"
compile_sass = true
build_search_index = false
generate_feeds = false

# Image processing
[imageprocessing]
# Defaults are fine; img shortcode controls per-image

[markdown]
highlight_code = false
external_links_target_blank = false
smart_punctuation = false

[extra]
# Site-wide values pulled into templates
practice_name = "Praxis am Lienhardplatz"
doctors = "Dr. Oliver Riemann, Dr. Angela Krey, Christian Malyga"
address = "Lienhardplatz 1, 42329 Wuppertal-Vohwinkel"
phone = "+49 202 123456"  # TODO replace with actual number from contact page during Task 19
```

- [ ] **Step 2: Write minimal `content/_index.md`**

```markdown
+++
title = "Home"
template = "index.html"
+++

Skeleton placeholder. Real content lands in Task 12.
```

- [ ] **Step 3: Create a minimal `templates/index.html` so build doesn't fail**

```html
<!DOCTYPE html>
<html lang="de">
<head><meta charset="utf-8"><title>{{ config.title }}</title></head>
<body>
  <h1>{{ config.title }}</h1>
  <p>{{ page.content | safe }}</p>
</body>
</html>
```

- [ ] **Step 4: Verify `zola serve` starts and serves the placeholder**

```fish
zola serve
```

Expected: console says "Web server is available at http://127.0.0.1:1111". Open browser, see "Praxis am Lienhardplatz" heading. Stop with Ctrl-C.

- [ ] **Step 5: Verify `zola build`**

```fish
zola build
ls public/
```

Expected: `public/index.html` and `public/sitemap.xml` exist. No errors.

- [ ] **Step 6: Commit**

```fish
git add config.toml content/_index.md templates/index.html
git commit -m "feat: zola skeleton with placeholder index"
```

---

## Task 4: Site Title Placeholder Phone Number

**Files:**
- Read: `orig/user/pages/10.contact/modular.md`, `orig/user/pages/10.contact/_contact/contact.md`, plus any phone references in `orig/user/pages/`

- [ ] **Step 1: Find the practice's actual phone number in the orig snapshot**

```fish
grep -rE '(0202|\+49)' orig/user/pages/ | head -20
```

Expected: locates a phone number string used on the contact or home page.

- [ ] **Step 2: Update `config.toml` `extra.phone` with the real number**

Edit `config.toml`, replace the TODO placeholder.

- [ ] **Step 3: Commit**

```fish
git add config.toml
git commit -m "chore: set practice phone number in site config"
```

---

## Task 5: Copy JS Assets

The Grav `praxis` theme inherits from `x-corporation`, so JS lives in both themes' directories. The live site loads (in order): jQuery → smoothscroll → mobile-menu → flexSlider → scripts.js → Bootstrap.

**Files:**
- Create: `static/js/jquery-2.1.4.min.js` (from `orig/user/themes/x-corporation/js/`)
- Create: `static/js/smoothscroll.js`
- Create: `static/js/mobile-menu.js`
- Create: `static/js/scripts.js`
- Create: `static/js/jquery.flexslider-min.js` (from `orig/user/themes/x-corporation/js/flexSlider/`)
- Create: `static/js/bootstrap.min.js` (from `orig/user/themes/praxis/bootstrap-3.3.6/dist/js/`)

- [ ] **Step 1: Copy JS files**

```fish
mkdir -p static/js
cp orig/user/themes/x-corporation/js/jquery-2.1.4.min.js static/js/
cp orig/user/themes/x-corporation/js/smoothscroll.js static/js/
cp orig/user/themes/x-corporation/js/mobile-menu.js static/js/
cp orig/user/themes/x-corporation/js/scripts.js static/js/
cp orig/user/themes/x-corporation/js/flexSlider/jquery.flexslider-min.js static/js/
cp orig/user/themes/praxis/bootstrap-3.3.6/dist/js/bootstrap.min.js static/js/
```

- [ ] **Step 2: Verify**

```fish
ls -la static/js/
```

Expected: six `.js` files, total a few hundred KB.

- [ ] **Step 3: Commit**

```fish
git add static/js/
git commit -m "feat: import JS assets from x-corporation and praxis themes"
```

---

## Task 6: Copy CSS, Fonts, Images

**Files:**
- Create: `static/css/bootstrap.min.css` (from `orig/user/themes/praxis/bootstrap-3.3.6/dist/css/`)
- Create: `static/css/bootstrap-theme.min.css` (from same)
- Create: `static/css/mobile-menu.css` (from `orig/user/themes/x-corporation/css/`)
- Create: `static/css/flexslider.css` (from `orig/user/themes/x-corporation/js/flexSlider/`)
- Create: `static/fonts/` populated with Font Awesome and any web fonts
- Create: `static/images/` with all imagery from `orig/`

- [ ] **Step 1: Copy CSS files**

```fish
mkdir -p static/css
cp orig/user/themes/praxis/bootstrap-3.3.6/dist/css/bootstrap.min.css static/css/
cp orig/user/themes/praxis/bootstrap-3.3.6/dist/css/bootstrap-theme.min.css static/css/
cp orig/user/themes/x-corporation/css/mobile-menu.css static/css/
cp orig/user/themes/x-corporation/js/flexSlider/flexslider.css static/css/
```

- [ ] **Step 2: Copy fonts**

```fish
mkdir -p static/fonts
cp -r orig/user/themes/x-corporation/fonts/font-awesome static/fonts/
cp orig/user/themes/praxis/fonts/montserrat.css static/fonts/  # if present
cp orig/user/themes/praxis/fonts/roboto.css static/fonts/      # if present
# Copy any actual font files (woff, ttf, etc.) referenced by these CSS files:
find orig/user/themes -type d -name "fonts" -exec cp -rn {} static/ \;
```

Expected: `static/fonts/font-awesome/` exists with `css/` and `fonts/` subdirectories.

- [ ] **Step 3: Copy images, organized by section**

```fish
mkdir -p static/images/carousel static/images/team static/images/services static/images/general

# Carousel
cp orig/user/pages/01.home/_carousel/*.jpg static/images/carousel/ 2>/dev/null || true

# Team
cp orig/user/pages/01.home/_team/*.jpg static/images/team/ 2>/dev/null || true
cp orig/user/pages/03.team/01._doctors/*.jpg static/images/team/ 2>/dev/null || true
cp orig/user/pages/03.team/02._employees/*.jpg static/images/team/ 2>/dev/null || true

# Services
cp orig/user/pages/01.home/_services/*.jpg static/images/services/ 2>/dev/null || true
find orig/user/pages/02.services -name "*.jpg" -exec cp {} static/images/services/ \;

# Anything else under user/pages
find orig/user/pages -name "*.jpg" -o -name "*.png" 2>/dev/null | while read f; do
  base=$(basename "$f")
  cp -n "$f" static/images/general/  # -n: don't overwrite
done
```

- [ ] **Step 4: Verify**

```fish
du -sh static/
find static -type f | wc -l
```

Expected: `static/` is under 50 MB. File count is in the hundreds (mostly font glyphs and images).

- [ ] **Step 5: Commit**

```fish
git add static/
git commit -m "feat: import CSS, fonts, and images"
```

---

## Task 7: SCSS Entry Point

The Grav theme's custom CSS (`style.css`, `_style.css`) gets lifted into the SCSS sources so Zola compiles it together with the rest.

**Files:**
- Create: `sass/main.scss`
- Create: `sass/_praxis_theme.scss`

- [ ] **Step 1: Copy theme CSS as SCSS partial**

```fish
mkdir -p sass
cp orig/user/themes/praxis/css/style.css sass/_praxis_theme.scss
cp orig/user/themes/praxis/css/_style.css sass/_praxis_partial.scss 2>/dev/null || true
```

- [ ] **Step 2: Write `sass/main.scss`**

```scss
// Praxis am Lienhardplatz — theme entry point
//
// Compiled by Zola into /main.css and loaded by base.html.
// Bootstrap, mobile-menu, flexslider, and Font Awesome are loaded as separate
// <link> tags from /css/ (Task 10), so we don't import them here.

@import "praxis_theme";
```

- [ ] **Step 3: Verify Zola compiles SCSS**

```fish
zola build
ls public/main.css
```

Expected: `public/main.css` exists and is non-empty.

- [ ] **Step 4: Commit**

```fish
git add sass/
git commit -m "feat: lift praxis theme CSS into sass entry point"
```

---

## Task 8: Image Shortcode

**Files:**
- Create: `templates/shortcodes/img.html`

- [ ] **Step 1: Write the shortcode**

```html
{# Image shortcode with width-based resize and lazy loading.
   Usage in markdown:
     {{ img(src="team/Olli.jpg", width=400, alt="Dr. Riemann") }}
     {{ img(src="carousel/page1.jpg", width=1920, alt="Praxis") }}
   Class is optional, e.g. class="float-right". #}

{%- set src = src | trim_start_matches(pat="/") -%}
{%- set source_path = "static/images/" ~ src -%}
{%- set resized = resize_image(path=source_path, width=width, op="fit_width", quality=85) -%}
<img
  src="{{ resized.url }}"
  width="{{ resized.width }}"
  height="{{ resized.height }}"
  alt="{{ alt | default(value="") }}"
  {%- if class %} class="{{ class }}"{% endif %}
  loading="lazy"
>
```

- [ ] **Step 2: Sanity-test the shortcode with a sample image**

Add a temporary call in `content/_index.md` body:

```markdown
{{ img(src="team/Olli.jpg", width=400, alt="Dr. Riemann") }}
```

(Use any image actually present in `static/images/team/` — verify with `ls static/images/team/`.)

- [ ] **Step 3: Build and verify**

```fish
zola build
grep -A1 '<img' public/index.html | head -5
ls processed_images/ | head -5
```

Expected: `<img src="/processed_images/.../*.jpg" width=400 height=... loading="lazy">` appears in HTML. The `processed_images/` directory is created with resized JPEG(s).

- [ ] **Step 4: Remove the test call from `_index.md`**

Revert `content/_index.md` to its skeleton state from Task 3.

- [ ] **Step 5: Commit**

```fish
git add templates/shortcodes/img.html content/_index.md
git commit -m "feat: img shortcode with resize_image and lazy loading"
```

---

## Task 9: Base Layout Template

**Files:**
- Create: `templates/base.html`

- [ ] **Step 1: Write `templates/base.html`** (port of `partials/base.html.twig`)

```html
<!DOCTYPE HTML>
<html lang="de">
<head>
    {% include "partials/head.html" %}
</head>
<body>
    <div id="main-wrapper">
        <div id="preloader">
            <div id="status">
                <div class="status-mes"></div>
            </div>
        </div>

        <div class="uc-mobile-menu-pusher">
            <div class="content-wrapper">
                {% include "partials/navbar.html" %}

                {% block content %}{% endblock %}
            </div>
        </div>

        <div class="uc-mobile-menu uc-mobile-menu-effect">
            <button type="button" class="close" aria-hidden="true" data-toggle="offcanvas"
                    id="uc-mobile-menu-close-btn">&times;</button>
            <div>
                <div>
                    <ul id="menu">
                    {% set top = get_section(path="_index.md") %}
                    {% for p in top.subsections %}
                        {% set sub = get_section(path=p) %}
                        <li><a href="{{ sub.permalink }}">{{ sub.title }}</a></li>
                    {% endfor %}
                    </ul>
                </div>
            </div>
        </div>
    </div>

    <script src="/js/jquery-2.1.4.min.js"></script>
    <script src="/js/smoothscroll.js"></script>
    <script src="/js/mobile-menu.js"></script>
    <script src="/js/jquery.flexslider-min.js"></script>
    <script src="/js/scripts.js"></script>
    <script src="/js/bootstrap.min.js"></script>
</body>
</html>
```

Note: script load order matches the Grav theme's `head.html.twig` comment ("REIHENFOLGE IST SUPERWICHTIG!!").

- [ ] **Step 2: Commit**

```fish
git add templates/base.html
git commit -m "feat: base layout template"
```

---

## Task 10: Head Partial (with noindex for staging)

**Files:**
- Create: `templates/partials/head.html`

- [ ] **Step 1: Write `templates/partials/head.html`**

```html
<title>{% if page.title %}{{ page.title | safe }} | {% endif %}{{ config.title | safe }}</title>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

{# STAGING: keep search engines out. Remove this line at production cutover. #}
<meta name="robots" content="noindex,nofollow">

<meta name="description" content="{{ config.description | safe }}">
<meta name="author" content="{{ config.extra.practice_name | safe }}">

<link rel="stylesheet" href="/fonts/roboto.css">
<link rel="stylesheet" href="/fonts/montserrat.css">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/fonts/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/css/mobile-menu.css">
<link rel="stylesheet" href="/css/flexslider.css">
<link rel="stylesheet" href="/main.css">
```

- [ ] **Step 2: Build and curl to verify noindex appears**

```fish
zola build
grep -i 'robots' public/index.html
```

Expected: shows the `noindex,nofollow` meta tag.

- [ ] **Step 3: Commit**

```fish
git add templates/partials/head.html
git commit -m "feat: head partial with noindex meta for staging"
```

---

## Task 11: Navbar Partial

This is the most complex template — recursive nested menu with dropdown logic. The HTML output must be byte-identical to Grav's so Bootstrap 3's mobile burger keeps working.

**Files:**
- Create: `templates/partials/navbar.html`

- [ ] **Step 1: Write `templates/partials/navbar.html`** (port of `partials/navbar.html.twig`)

```html
{%- macro nav_items(section) -%}
  {%- for p in section.subsections -%}
    {%- set sub = get_section(path=p) -%}
    {%- set has_children = sub.subsections | length > 0 -%}
    {%- if has_children -%}
      <li class="dropdown m-menu-fw{% if current_url and current_url is starting_with(sub.permalink) %} active{% endif %}">
        <a href="#" data-toggle="dropdown" class="dropdown-toggle">{{ sub.title }}
          <span><i class="fa fa-angle-down"></i></span></a>
        <ul class="dropdown-menu">
          {%- for cp in sub.subsections -%}
            {%- set child = get_section(path=cp) -%}
            <li class="m-menu-content">
              <ul class="col-sm-{{ (12 / (sub.subsections | length)) | round }}">
                <li class="dropdown-header">{{ child.title }}</li>
                {%- for cpage in child.pages -%}
                  <li><a href="{{ cpage.permalink }}">{{ cpage.title }}</a></li>
                {%- endfor -%}
              </ul>
            </li>
          {%- endfor -%}
        </ul>
      </li>
    {%- else -%}
      <li class="{% if current_url and current_url is starting_with(sub.permalink) %}active{% endif %}">
        <a href="{{ sub.permalink }}">{{ sub.title }}</a>
      </li>
    {%- endif -%}
  {%- endfor -%}
{%- endmacro -%}

<nav class="navbar m-menu navbar-default navbar-fixed-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
              data-target="#navbar-collapse-1">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/">{{ config.title }}</a>
    </div>

    <div class="collapse navbar-collapse" id="navbar-collapse-1">
      <ul class="nav navbar-nav navbar-right">
        {% set top = get_section(path="_index.md") %}
        {{ self::nav_items(section=top) }}
      </ul>
    </div>
  </div>
</nav>
```

- [ ] **Step 2: Build and inspect navbar output**

```fish
zola build
sed -n '/<nav class="navbar/,/<\/nav>/p' public/index.html
```

Expected: nav structure exists. Items will only be populated once the section pages exist (Tasks 15-19). At this point the loop iterates over zero subsections — that's fine.

- [ ] **Step 3: Diff against live navbar (for later reference, save baseline)**

```fish
curl -s https://www.praxis-am-lienhardplatz.de/ | sed -n '/<nav class="navbar/,/<\/nav>/p' > /tmp/grav-navbar.html
```

This file is the reference for comparison in Task 25.

- [ ] **Step 4: Commit**

```fish
git add templates/partials/navbar.html
git commit -m "feat: navbar partial with recursive dropdown menu"
```

---

## Task 12: Home Page Content

**Files:**
- Modify: `content/_index.md`

- [ ] **Step 1: Read source data**

```fish
cat orig/user/pages/01.home/_carousel/carousel.md
cat orig/user/pages/01.home/_about/about.md
cat orig/user/pages/01.home/_services/services.md
cat orig/user/pages/01.home/_team/*.md 2>/dev/null
cat orig/user/pages/01.home/_notfall/*.md 2>/dev/null
```

- [ ] **Step 2: Write `content/_index.md`** with all home modular data

```markdown
+++
title = "Willkommen"
template = "index.html"

[extra]

# Carousel slides (was orig/.../01.home/_carousel/carousel.md → slides:)
[[extra.slides]]
heading = "Willkommen in unserer Praxis am Lienhardplatz!"
image = "carousel/page1.jpg"
alt = "Willkommen in unserer Praxis"

[[extra.slides]]
heading = "Wir freuen uns auf Ihren Besuch!"
image = "carousel/page2.jpg"
alt = "Das Praxis-Team"

# About columns (was _about/about.md → columns:)
[[extra.about_columns]]
text = """
**Auf den folgenden Seiten** wollen wir Ihnen unsere hausärztliche Gemeinschaftspraxis vorstellen.

Sie finden wichtige Informationen, erfahren alles über das Leistungsspektrum der Praxis und lernen unser Team kennen.

Als moderne, kompetente hausärztliche Gemeinschaftspraxis möchten wir Sie in einem vertrauensvollen und persönlichen Umfeld in allen Bereichen Ihrer Gesundheit begleiten. Im Mittelpunkt stehen Sie als Patientin oder Patient mit Ihren individuellen Bedürfnissen, Beschwerden und ihrer speziellen Lebenssituation.
"""

[[extra.about_columns]]
text = """
**Die Basis unseren Handelns** ist die wissenschaftlich begründete Medizin. Neben dem kompletten Spektrum der hausärztlichen Versorgung liegen besondere Tätigkeitsschwerpunkte unserer Praxis im Bereich der Diabetologie, der Kardiologie und der Hypertensiologie (Diagnostik und Behandlung der Bluthochdruckerkrankungen).

Ein besonderes Anliegen ist uns die Prävention, sowie die Motivation unserer Patientinnen und Patienten, einen gesundheitsfördernden Lebensstil zu führen.

Wir freuen uns Sie im Herzen von Vohwinkel begrüßen zu dürfen!
"""

# Service grid cards (was _services/services.md → services:)
[[extra.service_cards]]
title = "Hausärztliche Versorgung"
description = "Wir sind Ihr erster Ansprechpartner in allen gesundheitlichen Fragen!"
url = "/services#HV"
image = "services/HV.jpg"

[[extra.service_cards]]
title = "Corona-Virus"
description = "Schnelltests, PCR-Tests und Impfungen."
url = "/services#Corona"
image = "services/Corona.jpg"

[[extra.service_cards]]
title = "Vorsorge"
description = "Gesundheits-Check, Krebsvorsorge und Impfungen."
url = "/services#Vorsorge"
image = "services/Vorsorge.jpg"

[[extra.service_cards]]
title = "Diagnostik"
description = "Lungenfunktionsprüfung, EKG-Diagnostik, Ultraschall- und Laboruntersuchungen."
url = "/services#Diagnostik"
image = "services/Diagnostik.jpg"

[[extra.service_cards]]
title = "Kardiologie"
description = "Diagnostik und Therapie des Herzkreislaufsystems."
url = "/services#Kardio"
image = "services/Kardio.jpg"

[[extra.service_cards]]
title = "Diabetologie"
description = "Schulung und intensive Betreuung von Diabetikern im Rahmen des DMP für Diabetes."
url = "/services#Diabetologie"
image = "services/Diabetologie.jpg"

# Team preview (small grid of doctor portraits on the home page)
# Fill exactly with what the orig/_team/ folder has — read its actual data structure first
# and translate each portrait into a [[extra.team_preview]] entry with name, role, image.

# Notfall block — short emergency-contact paragraph for the home page bottom.
# Read orig/.../01.home/_notfall/*.md for the verbatim text.
+++

{# The body below is the "Notfall" section text. Translate the markdown verbatim
   from orig/user/pages/01.home/_notfall/*.md once you've read it. #}

## Notfall

(Body text from orig/user/pages/01.home/_notfall/ goes here verbatim.)
```

- [ ] **Step 3: Fill in `[[extra.team_preview]]` and Notfall body**

Read the actual contents of `orig/user/pages/01.home/_team/*.md` and `orig/user/pages/01.home/_notfall/*.md` and add the corresponding TOML / markdown to `_index.md`.

- [ ] **Step 4: Verify build**

```fish
zola build
```

Expected: no errors. `public/index.html` exists with placeholder template output (templating the modulars comes in Task 13).

- [ ] **Step 5: Commit**

```fish
git add content/_index.md
git commit -m "feat: home page content with modular data in extra block"
```

---

## Task 13: Home Page Template

**Files:**
- Modify: `templates/index.html` (replacing the skeleton from Task 3)

- [ ] **Step 1: Write the full home template**

```html
{% extends "base.html" %}

{% block content %}

{# ───────── Carousel ───────── #}
<div id="x-corp-carousel" class="carousel slide hero-slide" data-ride="carousel">
  <ol class="carousel-indicators">
    {% for slide in page.extra.slides %}
      <li data-target="#x-corp-carousel"
          data-slide-to="{{ loop.index0 }}"
          class="{% if loop.first %}active{% endif %}"></li>
    {% endfor %}
  </ol>
  <div class="carousel-inner" role="listbox">
    {% for slide in page.extra.slides %}
      <div class="item{% if loop.first %} active{% endif %}">
        {{ img(src=slide.image, width=1920, alt=slide.alt) }}
        <div class="carousel-caption">
          <p>{{ slide.heading }}</p>
        </div>
      </div>
    {% endfor %}
  </div>
  <a class="left carousel-control" href="#x-corp-carousel" role="button" data-slide="prev">
    <i class="fa fa-angle-left" aria-hidden="true"></i>
    <span class="sr-only">Previous</span>
  </a>
  <a class="right carousel-control" href="#x-corp-carousel" role="button" data-slide="next">
    <i class="fa fa-angle-right" aria-hidden="true"></i>
    <span class="sr-only">Next</span>
  </a>
</div>

{# ───────── News (loaded from content/news.md) ───────── #}
{% set news_page = get_page(path="news.md") %}
<section class="ptb-100" id="aktuelles">
  <div class="container">
    {{ news_page.content | safe }}
  </div>
</section>

{# ───────── About columns ───────── #}
<section class="ptb-100">
  <div class="container">
    <div class="row">
      {% for col in page.extra.about_columns %}
        <div class="col-md-6">{{ col.text | markdown | safe }}</div>
      {% endfor %}
    </div>
  </div>
</section>

{# ───────── Service grid ───────── #}
<section class="ptb-100 bg-gray">
  <div class="container">
    <h2 class="text-center">Unsere Leistungen</h2>
    <div class="row">
      {% for card in page.extra.service_cards %}
        <div class="col-md-4 col-sm-6">
          <a href="{{ card.url }}" class="service-card">
            {{ img(src=card.image, width=600, alt=card.title) }}
            <h3>{{ card.title }}</h3>
            <p>{{ card.description }}</p>
          </a>
        </div>
      {% endfor %}
    </div>
  </div>
</section>

{# ───────── Team preview ───────── #}
{% if page.extra.team_preview %}
<section class="ptb-100">
  <div class="container">
    <h2 class="text-center">Unser Team</h2>
    <div class="row">
      {% for member in page.extra.team_preview %}
        <div class="col-md-4 col-sm-6 text-center">
          {{ img(src=member.image, width=400, alt=member.name) }}
          <h4>{{ member.name }}</h4>
          <p>{{ member.role }}</p>
        </div>
      {% endfor %}
    </div>
  </div>
</section>
{% endif %}

{# ───────── Notfall (body of _index.md) ───────── #}
<section class="ptb-100 bg-gray">
  <div class="container">
    {{ page.content | safe }}
  </div>
</section>

{# ───────── Map ───────── #}
{{ map() }}

{% endblock %}
```

- [ ] **Step 2: Verify build**

```fish
zola build
```

Expected: build succeeds. Open `public/index.html` and visually scan — sections appear in order, image tags render, carousel markup is present.

Note: the `{{ map() }}` shortcode does not exist yet — replace with `{# {{ map() }} #}` (commented out) temporarily; uncomment in Task 20.

- [ ] **Step 3: Local visual check**

```fish
zola serve
```

Open `http://127.0.0.1:1111` in browser. Carousel should be visible (Bootstrap JS isn't loaded yet because navbar pulls in scripts but flexSlider/etc might error — check browser console).

- [ ] **Step 4: Commit**

```fish
git add templates/index.html
git commit -m "feat: home page template with inline modular sections"
```

---

## Task 14: News Page

**Files:**
- Create: `content/news.md`

- [ ] **Step 1: Read source**

```fish
cat orig/user/pages/01.home/_news/news.md
```

- [ ] **Step 2: Write `content/news.md`** with verbatim translation

Frontmatter becomes:

```markdown
+++
title = "Aktuelles"
template = "page.html"
weight = 5
+++

(Body markdown copied verbatim from orig/user/pages/01.home/_news/news.md,
including the date subscript, the headings, the embedded HTML table for
office hours, and the link to /services#Corona.)
```

- [ ] **Step 3: Verify**

```fish
zola build
grep -i "Aktuelles" public/news/index.html
```

Expected: news page renders standalone at `/news/` AND the home page (Task 13) embeds the content via `get_page("news.md").content`.

- [ ] **Step 4: Commit**

```fish
git add content/news.md
git commit -m "feat: news page (Aktuelles)"
```

---

## Task 15: Services Section Index

**Files:**
- Create: `content/services/_index.md`

- [ ] **Step 1: Write services section index**

```markdown
+++
title = "Leistungen"
template = "section.html"
sort_by = "weight"
weight = 20
+++

(Optional intro text for the services overview page. If the original site has no
intro for /services/, leave the body empty — the section.html template renders
all child pages stacked anyway.)
```

- [ ] **Step 2: Commit**

```fish
git add content/services/_index.md
git commit -m "feat: services section index"
```

---

## Task 16: Service Sub-Pages

**Files:**
- Create: `content/services/hv.md`
- Create: `content/services/corona.md`
- Create: `content/services/vorsorge.md`
- Create: `content/services/diagnostik.md`
- Create: `content/services/kardiologie.md`
- Create: `content/services/diabetologie.md`

- [ ] **Step 1: Migrate each service page**

For each of HV, Corona, Vorsorge, Diagnostik, Kardiologie, Diabetologie:

  - Read the source: `orig/user/pages/02.services/0N._<name>/<name>.md`
  - Create `content/services/<name>.md` with:

```markdown
+++
title = "<Title from Grav frontmatter>"
weight = <10, 20, 30, 40, 50, 60>

[extra]
anchor = "<HV | Corona | Vorsorge | Diagnostik | Kardio | Diabetologie>"
+++

<body verbatim from Grav source, with image paths rewritten:
  /home/_services/HV.jpg → use the img shortcode instead
  
example:
  {{ img(src="services/HV.jpg", width=600, alt="Hausärztliche Versorgung", class="float-right") }}
>
```

The anchor values must match what the home page service-card URLs reference
(see `content/_index.md` `[[extra.service_cards]].url` from Task 12):
HV, Corona, Vorsorge, Diagnostik, Kardio, Diabetologie.

- [ ] **Step 2: Commit**

```fish
git add content/services/
git commit -m "feat: six service sub-pages with anchors matching home grid"
```

---

## Task 17: Section Template

**Files:**
- Create: `templates/section.html`

- [ ] **Step 1: Write `templates/section.html`**

```html
{% extends "base.html" %}

{% block content %}

<section class="single-page-title">
  <div class="container text-center hidden-xs">
    <h2>{{ section.title }}</h2>
  </div>
</section>

<section class="ptb-100">
  <div class="container">
    {% if section.content %}
      <div class="row"><div class="col-md-12">{{ section.content | safe }}</div></div>
    {% endif %}

    {% for page in section.pages %}
      <article id="{{ page.extra.anchor | default(value=page.slug) }}" class="anchor-section">
        <h2 class="section-title">{{ page.title }}</h2>
        {{ page.content | safe }}
      </article>
    {% endfor %}
  </div>
</section>

{% endblock %}
```

- [ ] **Step 2: Verify services renders all sub-pages with anchors**

```fish
zola build
grep -E '<article id=' public/services/index.html
```

Expected: six `<article>` elements, IDs `HV`, `Corona`, `Vorsorge`, `Diagnostik`, `Kardio`, `Diabetologie`.

- [ ] **Step 3: Verify anchor links from home work**

In the built `public/index.html`, the service grid cards link to `/services#HV` etc. Combined with the services page having `<article id="HV">`, in-page navigation should work.

- [ ] **Step 4: Commit**

```fish
git add templates/section.html
git commit -m "feat: section template stacking subpages with anchors"
```

---

## Task 18: Team Section and Pages

**Files:**
- Create: `content/team/_index.md`
- Create: `content/team/doctors.md`
- Create: `content/team/employees.md`
- Create: `content/team/jobs.md`

- [ ] **Step 1: Write team section index**

```markdown
+++
title = "Team"
template = "section.html"
sort_by = "weight"
weight = 30
+++
```

- [ ] **Step 2: Migrate doctors page**

Source: `orig/user/pages/03.team/01._doctors/doctors.md`

```markdown
+++
title = "Ärzte"
weight = 10

[extra]
anchor = "doctors"
+++

(Body verbatim from source. Image references like /home/_team/Olli.jpg become
{{ img(src="team/Olli.jpg", width=400, alt="Dr. med. Oliver Riemann", class="float-right") }})
```

- [ ] **Step 3: Migrate employees and jobs pages** (same pattern)

- [ ] **Step 4: Verify**

```fish
zola build
ls public/team/
```

Expected: `public/team/index.html` lists all team members.

- [ ] **Step 5: Commit**

```fish
git add content/team/
git commit -m "feat: team section with doctors, employees, jobs"
```

---

## Task 19: Legal, Contact, and Page Template

**Files:**
- Create: `content/legal.md`
- Create: `content/contact.md`
- Create: `templates/page.html`

- [ ] **Step 1: Write `templates/page.html`** (port of `default.html.twig`)

```html
{% extends "base.html" %}

{% block content %}

<section class="single-page-title">
  <div class="container text-center hidden-xs">
    <h2>{{ page.title }}</h2>
  </div>
</section>

<section class="ptb-100">
  <div class="container">
    <div class="row">
      <div class="col-md-12">
        {{ page.content | safe }}
      </div>
    </div>
  </div>
</section>

{% endblock %}
```

- [ ] **Step 2: Migrate legal page**

Source: `orig/user/pages/05.legal/default.md`

```markdown
+++
title = "Impressum"
template = "page.html"
weight = 50
+++

(Body verbatim from source.)
```

- [ ] **Step 3: Migrate contact page**

Source: `orig/user/pages/10.contact/modular.md` plus any actual contact info elsewhere (the form is intentionally omitted per spec).

```markdown
+++
title = "Kontakt"
template = "page.html"
weight = 60
+++

## Praxis am Lienhardplatz

(Address, phone, email, opening hours — translate from the orig contact page
or from the practice's actual contact info. No form. Optional: embed map via
{{ map() }} once that shortcode exists (Task 20).)
```

- [ ] **Step 4: Verify**

```fish
zola build
ls public/legal/ public/contact/
```

Expected: `public/legal/index.html` and `public/contact/index.html` render with their content inside the `single-page-title` and main `container` sections.

- [ ] **Step 5: Commit**

```fish
git add content/legal.md content/contact.md templates/page.html
git commit -m "feat: legal, contact, and generic page template"
```

---

## Task 20: Map Shortcode

**Files:**
- Create: `templates/shortcodes/map.html`
- Modify: `templates/index.html` (uncomment `{{ map() }}`)
- Modify: `content/contact.md` (add `{{ map() }}` if appropriate)

- [ ] **Step 1: Look up the Google Maps embed URL for the practice**

The address is `Lienhardplatz 1, 42329 Wuppertal-Vohwinkel`. Go to Google Maps in the browser, search for the address, click "Share" → "Embed a map", copy the iframe `src` URL.

For an unattended migration without manual browsing, use a generated URL:
`https://www.google.com/maps/embed/v1/place?key=<EMBED_API_KEY>&q=Lienhardplatz+1,+42329+Wuppertal-Vohwinkel`

Without an API key, fall back to:
`https://maps.google.com/maps?q=Lienhardplatz+1,+42329+Wuppertal-Vohwinkel&t=&z=15&ie=UTF8&iwloc=&output=embed`
(legacy embed URL, no key required, less customizable but stable enough for a static address marker).

- [ ] **Step 2: Write `templates/shortcodes/map.html`**

```html
<section class="map-section">
  <iframe
    src="https://maps.google.com/maps?q=Lienhardplatz+1,+42329+Wuppertal-Vohwinkel&t=&z=15&ie=UTF8&iwloc=&output=embed"
    width="100%"
    height="450"
    style="border:0"
    loading="lazy"
    referrerpolicy="no-referrer-when-downgrade"
    title="Karte: Praxis am Lienhardplatz">
  </iframe>
</section>
```

- [ ] **Step 3: Uncomment `{{ map() }}` in `templates/index.html`** (was a comment in Task 13).

- [ ] **Step 4: Verify**

```fish
zola build
grep -A2 "map-section" public/index.html
```

Expected: iframe markup appears in the rendered home page.

- [ ] **Step 5: Commit**

```fish
git add templates/shortcodes/map.html templates/index.html content/contact.md
git commit -m "feat: map shortcode with iframe embed"
```

---

## Task 21: robots.txt

**Files:**
- Create: `static/robots.txt`

- [ ] **Step 1: Write `static/robots.txt`**

```
User-agent: *
Disallow: /
```

- [ ] **Step 2: Verify after build**

```fish
zola build
cat public/robots.txt
```

Expected: file contents exactly as above.

- [ ] **Step 3: Commit**

```fish
git add static/robots.txt
git commit -m "feat: robots.txt blocks all crawlers for staging"
```

---

## Task 22: First Deploy Test (Routing Sanity Check) — GATED

This task verifies the subdomain routes to `~/public/praxis/` before any real content lands there. It is a separate gated step: ask the user for confirmation before running the rsync.

**Files:**
- Create: `static/_hello.html` (temporary, removed in Task 24)

- [ ] **Step 1: Write `static/_hello.html`**

```html
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Routing OK</title></head>
<body><h1>Routing OK</h1>
<p>If you see this, preview.praxis-am-lienhardplatz.de routes to ~/public/praxis/.</p>
</body></html>
```

- [ ] **Step 2: Build**

```fish
zola build
ls public/_hello.html
```

- [ ] **Step 3: REQUEST USER CONFIRMATION** before deploy

Pause and ask the user:
> "Ready to push the test file to mycelium:public/praxis/ via rsync. Confirm to proceed?"

Wait for explicit "yes" / "go ahead". Do not proceed without confirmation.

- [ ] **Step 4: Deploy only the hello file**

```fish
rsync -av public/_hello.html mycelium:public/praxis/
```

- [ ] **Step 5: Verify routing**

```fish
curl -sI https://preview.praxis-am-lienhardplatz.de/_hello.html
curl -s https://preview.praxis-am-lienhardplatz.de/_hello.html | head -10
```

Expected:
  - HTTP 200 OK
  - Body contains "Routing OK"

If 404 or wrong content: subdomain DNS or Apache vhost config needs adjusting (user contacts Variomedia or fixes in customer panel). Do not proceed to Task 23 until this works.

---

## Task 23: Visual Build Audit

A sanity check before the full deploy: does the local build look like what we want?

- [ ] **Step 1: Run `zola serve`**

```fish
zola serve
```

- [ ] **Step 2: Visit every page locally**

In browser at `http://127.0.0.1:1111`:
  - `/` — carousel, news, services grid, team preview, notfall, map
  - `/services/` — all 6 service sub-sections rendered with anchors
  - `/services/#HV` — page scrolls to HV section
  - `/team/` — all team subsections
  - `/legal/` — Impressum content
  - `/contact/` — contact info + map
  - `/news/` — standalone news page

- [ ] **Step 3: Test interactive elements**

  - Carousel: indicators clickable, slides advance
  - Mobile burger menu: shrink browser to <768px width, click burger, menu opens/closes
  - Navbar dropdowns (if any): click parent, menu opens

- [ ] **Step 4: Check browser console for errors**

DevTools console should have no red errors. Yellow warnings are acceptable.

- [ ] **Step 5: Total image weight**

```fish
du -sh public/processed_images/
```

Expected: under 5 MB.

If anything is broken: fix in a follow-up commit before deploying.

---

## Task 24: Full Staging Deploy — GATED

- [ ] **Step 1: Remove the hello file from `static/`**

```fish
rm static/_hello.html
```

- [ ] **Step 2: Build**

```fish
zola build
```

- [ ] **Step 3: REQUEST USER CONFIRMATION** before deploy

Pause and ask:
> "Ready to deploy the full Zola site to mycelium:public/praxis/. This replaces everything currently in that directory (including the test hello file). Confirm to proceed?"

Wait for explicit confirmation.

- [ ] **Step 4: Deploy**

```fish
rsync -av --delete public/ mycelium:public/praxis/
```

- [ ] **Step 5: Verify root URL**

```fish
curl -sI https://preview.praxis-am-lienhardplatz.de/
curl -s https://preview.praxis-am-lienhardplatz.de/ | grep -i '<title>'
```

Expected:
  - HTTP 200
  - Title contains "Praxis am Lienhardplatz"

- [ ] **Step 6: Verify noindex is live**

```fish
curl -s https://preview.praxis-am-lienhardplatz.de/ | grep -i robots
curl -s https://preview.praxis-am-lienhardplatz.de/robots.txt
```

Expected:
  - `<meta name="robots" content="noindex,nofollow">` present
  - `robots.txt` returns `Disallow: /`

- [ ] **Step 7: Commit**

```fish
git add -A
git commit -m "chore: prepare for full staging deploy"
git push
```

---

## Task 25: Visual Parity Verification

This task closes Phase 1's main quality gate. Side-by-side comparison of the live Grav site and the new Zola staging site.

- [ ] **Step 1: Open both sites side-by-side in browser**

  - Window 1: `https://www.praxis-am-lienhardplatz.de/` (Grav, live)
  - Window 2: `https://preview.praxis-am-lienhardplatz.de/` (Zola, staging)

- [ ] **Step 2: Per-page visual check at 3 viewports**

For each page (`/`, `/services/`, `/team/`, `/legal/`, `/contact/`, `/news/`), open at three viewport widths via browser DevTools:

  - **375 px** (mobile)
  - **768 px** (tablet)
  - **1280 px** (desktop)

Look for:
  - Identical layout sections in the same order
  - Identical heading text and structure
  - Images appear in the same positions
  - Carousel/news/services-grid visible in same positions on home

Note any deviations.

- [ ] **Step 3: Navbar HTML diff**

```fish
curl -s https://www.praxis-am-lienhardplatz.de/ | sed -n '/<nav class="navbar/,/<\/nav>/p' > /tmp/grav-nav.html
curl -s https://preview.praxis-am-lienhardplatz.de/ | sed -n '/<nav class="navbar/,/<\/nav>/p' > /tmp/zola-nav.html
diff /tmp/grav-nav.html /tmp/zola-nav.html
```

Expected: minimal diff (whitespace and href URLs may differ; structural Bootstrap classes must match).

- [ ] **Step 4: Carousel interaction test**

On staging mobile + desktop:
  - Click left/right indicators → slides change
  - Wait → auto-advance after a few seconds (Bootstrap default)
  - Touch-swipe on mobile (if device available)

- [ ] **Step 5: Mobile burger test**

Set viewport to 375 px. Click burger icon. Menu drawer appears. Click items, they link to correct pages.

- [ ] **Step 6: Lighthouse run**

In browser DevTools → Lighthouse → run for staging page on Desktop and Mobile. Record Performance, Accessibility, SEO, Best Practices scores.

Compare against scores from the same Lighthouse run on the live Grav site.

Expected: Zola staging Performance score ≥ Grav score (typically substantially higher because no PHP runtime overhead and smaller images).

- [ ] **Step 7: Capture results**

Note in a follow-up message to the user:
  - Any visual deviations found
  - Lighthouse score comparison
  - Image weight reduction (compare `du -sh` of `public/processed_images/` against the 22 MB original)

---

## Task 26: Sign-Off

- [ ] **Step 1: Report to user**

Summarize for the user:
  - Staging URL is live and reachable
  - All checks from Task 25 passed (or list any open items)
  - Image weight reduction achieved
  - Lighthouse improvement

- [ ] **Step 2: Await user sign-off**

Ask:
> "Phase 1 is complete pending your sign-off. Open issues from verification:
> [list any, or 'none']. Approve as 'Phase 1 done'?"

- [ ] **Step 3: Final commit and push**

```fish
git add -A
git commit -m "docs: Phase 1 sign-off — staging matches live"
git push
```

Phase 1 ends here. Production cutover is a separate, future plan.

---

## Notes for the Implementer

- **Never touch `~/grav/praxis/` on `mycelium`.** Production deploys are explicitly out of scope. Every `rsync` in this plan targets `~/public/praxis/` and nowhere else.
- **Confirm before every deploy.** Even if the previous deploy was approved in the same session, ask again. The user's invoicing process depends on controlled production delivery.
- **Tera ≠ Twig.** Most syntax matches, but watch for: `loop.index0`, `loop.first`, `get_section()`/`get_page()` instead of `page.children`, no automatic media object on pages.
- **Image paths in Markdown:** never reference `/static/images/...` — use the `img` shortcode. Otherwise images bypass `resize_image()` and the site bloats.
- **If `zola build` fails on a Tera error**, the line number in the error is in the template file. Most issues are mismatched `{% endfor %}`/`{% endif %}` or missing `{% endblock %}`.

