6.4 KiB
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
1. Team carousel missing on /team/
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:1838–1846 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.
2. Image-size distortion (carousel slot only)
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).
5. Nav-link URLs HTML-entity-encoded
templates/partials/navbar.html renders {{ sub.permalink }} and
{{ cpage.permalink }} without | safe. Tera autoescape converts the
slashes to /. 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-innercontaining 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/.rightanchors with FAfa-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"→ callteam_carousel(title, anchor, slides)macro with values pulled frompage.extra.- otherwise → existing
section_title+page.contentrender.
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
#####Xwith##### 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
.itemdivs, first with.active; each wraps a<blockquote><ul>…</ul><p>…</p></blockquote>. .leftand.rightcarousel-control anchors withfa-angle-left/righticons.- Carousel
<img>tags have nowidth/heightattributes (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
/sequences in<a href>values.
Risks / Notes
- The
team_carouselmacro intentionally duplicates the section-title markup rather than reusingsection_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.