Files
praxis-page/docs/specs/2026-05-21-praxis-visual-parity-2-design.md
T

6.4 KiB
Raw Blame History

Visual Parity Pass 2 — Design Spec

Date: 2026-05-21 Phase: 1.6 (regression follow-up to Phase 1.5) Status: approved (auto-mode, in-session execution)

Goal

Close the remaining visual gaps reported by the editor after Phase 1.5 deploy: the team carousel that was silently dropped during the Grav→Zola content port, the image-size distortion on the employee block, and a small set of markdown parsing / template autoescape glitches.

Non-Goals

  • No Phase 2 modernization (typography, mobile, accessibility) — still 1:1 parity.
  • No CSS edits — the live theme CSS is the contract; we change markup to match it.
  • No production cutover. Staging only.

Reference

  • Live source-of-truth (read-only): /home/brummel/dev/praxis/orig/
  • Current Zola build output: /home/brummel/dev/praxis/public/
  • Phase 1.5 spec: docs/specs/2026-05-20-praxis-visual-parity-design.md
  • Phase 1.5 plan: docs/plans/2026-05-20-praxis-visual-parity.md

Findings

Live (orig/user/themes/praxis/templates/modular/employees.html.twig) renders a Bootstrap-3 carousel: <section class="employees"><div id="employeesSlider" class="carousel slide" data-ride="carousel"> with one .item per slide (blockquote / ul / li-img + li.name + p.description), plus .left/.right carousel-control anchors. Source data is a slides list in the employees.md front matter. CSS rules in orig/user/themes/praxis/css/_style.css:18381846 force img { width:160px; height:200px } for items inside #employeesSlider .carousel-inner.

Current Zola output for /team/ employees section is just sequential <img class="float-right"> blocks with <strong>name</strong> paragraphs — the carousel container, controls, and item wrapping are entirely missing.

templates/macros/image.html resizes with op="fit_width". For 311×400 originals at width=200, that produces 200×257 — the wrong aspect for the 160×200 CSS slot. The live site avoids this by serving the raw image and letting CSS pin it to 160×200. Doctor portraits (400×400) and services thumbnails (600×400) are visually fine; the bug is isolated to the carousel images.

3. <h5> headings not parsed in content/team/doctors.md

Markdown source has #####Qualifikation with no space after the hashes. Grav's markdown parser was tolerant; pulldown-cmark (Zola) is not — output is <p>#####Qualifikation</p>. Six occurrences across three doctor blocks.

4. </th> closing tag mismatch in doctors.md tables

<td>…</th> pairs in the three doctor schedule tables (six total). Browsers recover, but the markup is wrong and inconsistent with the live (which uses <td>…</td> everywhere).

templates/partials/navbar.html renders {{ sub.permalink }} and {{ cpage.permalink }} without | safe. Tera autoescape converts the slashes to &#x2F;. Functional but markup-ugly and not parity. Live uses relative paths (/services/).

Architecture

New: templates/macros/team_carousel.html

Renders a <section class="employees"> block:

  • Reuses the section-title structure from section_title.html (or inlines it identically — anchor optional).
  • Renders <div id="employeesSlider" class="carousel slide" data-ride="carousel"> with .carousel-inner containing one <div class="item{% if loop.first %} active{% endif %}"> per slide.
  • Each item: <blockquote><ul><li><img src="/images/team/{{ slide.image }}" alt="{{ slide.name }}"></li><li class="name">{{ slide.name }}</li></ul><p>{{ slide.description }}</p></blockquote>.
  • Carousel controls: .left / .right anchors with FA fa-angle-left/right.

Images are served raw from static/images/team/ — no width/height attribute, no resize_image. The CSS slot (#employeesSlider .carousel-inner .item img) forces the final 160×200 dimensions, exactly as the live site does it.

Modified: templates/section.html

When iterating section.pages, branch on page.extra.layout:

  • "carousel" → call team_carousel(title, anchor, slides) macro with values pulled from page.extra.
  • otherwise → existing section_title + page.content render.

Modified: content/team/employees.md

Front matter holds the carousel data; body is empty. Schema:

+++
title = "Unser Praxis-Team"
weight = 20

[extra]
anchor = "employees"
layout = "carousel"

[[extra.slides]]
name = "Frau Berscheidt"
image = "Berscheidt.jpg"
description = "Medizinische Fachangestellte, Diabetes-Assistentin"

# … repeated for Bolz, Frohne, Merhof, Rodrigues (5 total, matching live order)
+++

Modified: content/team/doctors.md

  • Replace each #####X with ##### X (six occurrences).
  • Replace each </th> with </td> inside <td>…</th> pairs (six occurrences).

Modified: templates/partials/navbar.html

Replace {{ sub.permalink }} and {{ cpage.permalink }} with the equivalent relative-path form: {{ sub.permalink | replace(from=config.base_url, to='') | safe }} (and same for cpage). Mobile-menu list inside the same file gets the same treatment.

Per-Route Definition of Done

  • /team/:
    • <section class="employees"> present, contains <div id="employeesSlider" class="carousel slide" data-ride="carousel">.
    • Exactly five .item divs, first with .active; each wraps a <blockquote><ul>…</ul><p>…</p></blockquote>.
    • .left and .right carousel-control anchors with fa-angle-left/right icons.
    • Carousel <img> tags have no width/height attributes (CSS does the sizing).
    • Doctor block: <h5>Qualifikation</h5> and <h5>Tätigkeitsschwerpunkte</h5> (etc.) replace the previous <p>#####…</p> literals.
    • Doctor schedule tables: zero </th> tags in the rendered HTML.
  • Site-wide nav: no &#x2F; sequences in <a href> values.

Risks / Notes

  • The team_carousel macro intentionally duplicates the section-title markup rather than reusing section_title.html, because the live theme wraps it in <section class="employees"> and the empty-anchor branch is different. Keeping it inline avoids a fragile macro-of-macro chain.
  • page.extra.layout == "carousel" is the dispatch key. If we later add other layouts (map, news, etc. — the live theme has those as modular templates), the same dispatch extends naturally.
  • Image-macro stays unchanged; the carousel deliberately bypasses it.