diff --git a/docs/specs/2026-05-20-praxis-visual-parity-design.md b/docs/specs/2026-05-20-praxis-visual-parity-design.md new file mode 100644 index 0000000..1f5d063 --- /dev/null +++ b/docs/specs/2026-05-20-praxis-visual-parity-design.md @@ -0,0 +1,193 @@ +# Praxis Visual Parity Pass — Design Spec + +**Status:** draft +**Date:** 2026-05-20 +**Phase:** 1.5 (finish-up after Phase 1 sign-off) + +## Context + +Phase 1 produced a Zola-based staging site at `http://preview.praxis-am-lienhardplatz.de/` that builds, deploys, and renders all content from the live Grav site. The user signed off Phase 1 acknowledging "MENGE Unterschiede" against the live look — accepting them as follow-up work. + +A visual diff against the live HTML (`https://www.praxis-am-lienhardplatz.de/`) revealed ten material gaps that prevent the theme CSS in `sass/_praxis_theme.scss` from applying. The theme CSS itself was lifted verbatim from the Grav theme, so making it work is mostly a question of restoring the markup contracts (class names, element nesting) it expects. + +This spec covers that visual-parity pass: closing the markup gaps so the staging site **looks** identical to the live site, while keeping the HTML clean (no Grav-era quirks reproduced). + +## Goals + +- The staging site, rendered in a browser, is visually indistinguishable from the live site on the four routes that exist on both sides: `/`, `/services`, `/team`, `/kontakt` (live) ↔ `/contact` (staging), `/impressum`. +- The theme CSS in `sass/_praxis_theme.scss` is not modified — markup conforms to it. +- Recurring patterns (section-title, single-page-title, footer) are factored into Tera macros / partials so Phase-2 modernization has a single point of change. +- Footer content (hours, contact, address) lives in `config.toml` `[extra]` as a single source of truth, shared by the footer partial and the `/contact` page. + +## Non-Goals (deferred to Phase 2) + +- Markup modernization: no semantic HTML5 rewrite, no CSS Grid, no replacement of Bootstrap 3 grid classes. +- URL escaping in templates (`/`): cosmetic in view-source, functional in browser. Phase 2. +- Content drift (e.g., hours displayed on home news section vs. footer): content is the user's responsibility, not markup parity. +- Accessibility audit, WCAG fixes, alt-text rewrite. +- Reproducing Grav HTML quirks (double `

...

` wrappers, `...` tag mismatches): we keep clean markup. + +## Reference + +Live HTML snapshots were captured to `/tmp/praxis-diff/live/` (transient — do not check in). The authoritative reference during implementation is what the live site currently serves; if in doubt, re-fetch. + +## Architecture + +### New macros + +**`templates/macros/section_title.html`** — `section_title(title, anchor="")` +Renders the bordered-icon section-title pattern used on home (4×), services (6×), team (2+ ×). When `anchor` is given, wraps the heading in `
`. + +```html +{% macro section_title(title, anchor="") %} +
+
+ {% if anchor %}
{% endif %} +

{{ title }}

+ {% if anchor %}
{% endif %} + +
+
+{% endmacro %} +``` + +**`templates/macros/page_title.html`** — `page_title(title)` +Renders the `
` banner that sits between navbar and content on every subpage. + +```html +{% macro page_title(title) %} +
+
+

{{ title }}

+
+
+{% endmacro %} +``` + +### New partial + +**`templates/partials/footer.html`** — three-column footer widget block + copyright bar, drawing all values from `config.extra`. + +Structure (matching live): +```html +
+ + +
+``` + +`base.html` includes it before ``, after the content block. + +### Modified files + +**`templates/partials/navbar.html`** +- Replace text brand with `Praxis am Lienhardplatz`. +- Add Home (`/`) as first menu item. +- Compute active state by comparing `current_path` to each link's `permalink`. + +**`templates/partials/head.html`** +- Title logic: home page emits `Praxis am Lienhardplatz - Dr. Oliver Riemann, Dr. Angela Krey, Christian Malyga` (the brand line). Subpages emit `{Page Title} | Praxis am Lienhardplatz - Dr. Oliver Riemann, Dr. Angela Krey, Christian Malyga`. +- Add ``. + +**`templates/index.html`** +- Wrap Aktuelles section with `
` and add `{{ section_title::section_title(title="Aktuelles") }}` heading. +- Add `{{ section_title::section_title(title="Willkommen in unserer Praxis am Lienhardplatz") }}` to the about block. +- Leistungen section: change class from `bg-gray` to `gray-bg`. Add `x-services` to section classes. Change column class from `col-md-4 col-sm-6` to `col-md-6`. Replace flat `` with `

title

desc

`. Add `{{ section_title::section_title(title="Unsere Leistungen") }}`. +- Team-preview: change wrapper from `
` to `
` containing `

{name} - {position}

`. Change section class to `team ptb-100`. Heading "Ihr Ärzteteam" via macro. + +**`templates/section.html`** +- Insert `{{ page_title::page_title(title=section.title) }}` after the navbar (before content). +- Each child page rendered as `
{{ section_title::section_title(title=page.title, anchor=page.extra.anchor) }}
{{ page.content | safe }}
`. + +**`templates/page.html`** +- Same treatment as section.html for the page title banner; otherwise content goes into a `
{{ page.content | safe }}
` block. + +**`templates/base.html`** +- Add `{% include "partials/footer.html" %}` before ``. + +### Config additions + +**`config.toml`** `[extra]`: +```toml +email = "info@praxis-am-lienhardplatz.de" +address_street = "Kaiserstraße 23" +address_zip_city = "42329 Wuppertal" +keywords = "Arzt, Hausarzt, Allgemeinmedizin, Innere Medizin, Internist, Kardiologie, Praxis, Gemeinschaftspraxis, Vohwinkel, Lienhardplatz, Krey, Riemann, Malyga, Corona, COVID-19, Corona-Test" + +[[extra.opening_hours]] +label = "Montag-Freitag" +time = "08:00-12:00" + +[[extra.opening_hours]] +label = "Montag, Dienstag, Donnerstag" +time = "15:00-18:00" +``` + +Phone, fax, doctors, practice_name, address are already present in `[extra]` from Phase 1. + +### Content changes + +**`content/contact.md`** +- Remove the hardcoded address/phone/fax/email block from markdown — the contact template will render those from `config.extra` for single-source-of-truth with the footer. +- Keep Sprechzeiten and Bereitschaftsdienst text. + +For the contact page to render config values inline, the simplest approach: switch its template to a dedicated `templates/contact.html` (uses `page_title` macro + a structured layout pulling from `config.extra` + `page.content` for the Bereitschaftsdienst paragraph). The `template = "page.html"` line in frontmatter changes to `template = "contact.html"`. + +**`content/_index.md`** +- Fix the table tag mismatch: `...` → `...` in the Aktuelles news block. + +### Assets + +**`static/images/logo.png`** ← `orig/user/themes/praxis/img/Logo_Praxis_white_H110.png` (copy verbatim — PNG with alpha, no resize). + +## Per-Route Definition of Done + +After implementation, each route's rendered HTML should match the live equivalent on these checks (visual, not byte-equal): + +| Route | Live | Staging must show | +|---|---|---| +| `/` | logo+nav, carousel, Aktuelles w/ heading, Willkommen w/ heading, Leistungen 2-col w/ thumbnails, Ärzteteam w/ figures, footer | same | +| `/services` | single-page-title "Leistungen", 6 sub-sections each with section_title + anchor + image + text, footer | same | +| `/team` | single-page-title "Team", doctor/employee sub-sections w/ section_title + anchors, footer | same | +| `/contact` | single-page-title "Kontakt", address/hours pulled from config, Bereitschaftsdienst block, footer (Sprechzeiten in footer ↔ Sprechzeiten on page driven from same config keys) | same | +| `/impressum` | single-page-title "Impressum", Impressum+Datenschutz sub-sections w/ section_title + anchors, footer | same | + +A successful pass means: visually open both URLs side by side in the browser, no obvious differences in layout, typography, spacing, or color blocks. + +## Risks & Mitigations + +- **Risk:** Theme CSS may have selectors that depend on Grav-specific DOM structure we don't reproduce exactly (e.g., wrapper divs we collapsed). + **Mitigation:** Compare rendered output, not source. If a visual element is broken, grep the SCSS for the relevant selector and adapt the markup minimally. + +- **Risk:** Footer values diverging between config and `/contact` page if we forget to switch contact to template-driven rendering. + **Mitigation:** This spec explicitly converts contact.md to use `templates/contact.html`. Implementer must not leave hardcoded values in the markdown. + +- **Risk:** Active-nav-link detection in Tera using `current_path` may be off by trailing slashes. + **Mitigation:** Normalize comparison (`current_path == link.path` or both with trailing `/`); test all four nav routes after change. + +## Out of Scope + +Anything not listed above. Specifically: +- No new content. No deletions of content. +- No new pages or routes. +- No SSL/deploy automation changes (still rsync manual). +- No git workflow changes. + +## Open Questions + +None at design time. Implementation may surface CSS-selector mismatches that need a tiny markup tweak beyond the spec; record those as small commits during the implementation pass.