Phones were downloading the full 1.5 MB / 2 MB hero JPEG. Now the
image macro emits a `srcset` ladder (480/768/1200/1600/2560/3840/5345 w)
with `sizes="100vw"`, so a 480 px-wide phone picks the ~19 KB variant
and 5K screens still get the native resolution.
Also drop `loading="lazy"` on the first slide — it is the LCP element
and should load eager.
Macro changes:
- Add `srcset_widths` (CSV string, since Tera macros only allow scalar
defaults) and `sizes` parameters.
- Add `lazy=true` parameter; callers can pass `lazy=false` for
above-the-fold images.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bootstrap-3 caps slide images with `max-width: 100%` only; if the
natural image is narrower than the viewport, the slide stops short
of the column edge. The macro was downscaling to 1920 px, which made
the hero shrink on >1920 px screens.
Match live's behaviour by serving the raw source image (5345 / 5621 px
wide) without resize — the source images are intentionally oversized
for this reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
text.html.twig in the live theme wraps the rendered markdown in
`<p>{{ content }}</p>`. The outer <p> auto-closes on the first inner
<p>, yielding a leading empty <p></p> with margin-bottom 10px — that's
what makes the first float-right image align with the first text line
on the services pages. Without the wrap the image starts 10px higher
than on live.
Three corrections after diffing the rebuild against the actual theme
twig templates (modular/news.html.twig, employees.html.twig,
about.html.twig):
1. section_title macro learns a `with_anchor` flag for the case where
live always wraps the h2 in `<div class="anchor" id="">` even with
an empty anchor id. Default behavior unchanged.
2. team_carousel macro drops its `.anchor` wrapper around the title.
employees.html.twig renders the h2 directly — including the wrapper
was producing an extra layout element on /team that live doesn't
have.
3. index.html's news section now calls section_title with
with_anchor=true and wraps the loaded news content in an outer <p>,
reproducing the grav markdown pipeline's behavior (the outer <p>
auto-closes on the first inner <p>, leaving one leading empty <p>
that contributes the expected 10px margin).
The praxis theme has two CSS files in css/: style.css (2000 lines, the
actual stylesheet loaded by the Twig template via assets.add('theme://
css/style.css')) and _style.css (1928 lines, a partial/backup variant
with different values). I had been copying _style.css all along —
wrong file. style.css has the correct line-heights (h1 34px, h2 28px
vs the partial's 24/24), an extra .text-bg rule, and the proper media
breakpoints. That's why paragraph spacing kept diverging from live no
matter what I touched in the body block — the differences were in the
heading rules and elsewhere in the file. Replace praxis.css with the
real source.
Stage's paragraph spacing was larger than live on text-heavy pages
(~4px per line). Root cause: when I converted the SCSS-style // comments
in praxis.css to /* */ a few iterations back, I accidentally revived
the body{} block that live's Grav asset pipeline silently drops at
build time. The block sets line-height: 24px (vs Bootstrap default
1.428 = ~20px) plus font-family Roboto and color #969595. The live
pipeline rejects the rule because the original `font-size: 14px; //13px;`
line has invalid CSS syntax — its minifier discards the whole
declaration. Browsers viewing my cleaned-up praxis.css accept the rule
and apply all properties.
Easiest way to match live exactly: remove the block. Bootstrap's body
defaults (Helvetica, line-height 1.428) now apply uniformly on stage,
matching the live render.
Surgical follow-up to the previous revert. The home-carousel indicator
dots drift because flexslider.css adds `.carousel li { margin-right: 5px }`
which bleeds into Bootstrap's carousel-indicators li. Bootstrap's
.active override removes margin on the active dot, but the inactive
keeps that 5px right margin — so when the inactive sits on the left
(slide 2 active), it pushes the active dot ~4px to the right.
The previous attempt also removed jquery.flexslider-min.js, which
empirically broke the hero/team carousels (the JS load seems to
participate in Bootstrap's data-ride auto-init timing on this custom
build). Keep the JS, drop only the CSS link plus the static file. Also
remove the now-superseded stage tweak at the bottom of praxis.css.
After the flexslider asset removal, scripts.js still tried to call
$('.testimonialSlider').flexslider(...) which threw a TypeError in the
browser console on every page. The element does not exist in this
rebuild and never will — testimonials were a feature of the original
Grav theme that we are not porting. Hero carousel and employees
carousel are both Bootstrap-3, auto-initialized via data-ride.
No functional impact on the page (every initializer before this block
already ran), just a noisy console error.
Root cause of the home carousel indicator dots drifting apart on slide
change: flexslider.css adds a rule `.carousel li { margin-right: 5px }`
that bleeds into Bootstrap's carousel because both use the `.carousel`
class. With Bootstrap's `.active { margin: 0 }` overriding the active
dot but flexslider's `margin-right: 5px` still applying to the inactive
dot, the gap between the two dots is asymmetric — 1px when active is on
the left, ~9px (1+4+4) when active is on the right.
Live's Grav asset pipeline doesn't bundle that flexslider rule (the
live HTML uses Bootstrap carousel exclusively too), so the bug is
stage-only.
We don't actually use flexslider — testimonialSlider was the only
target in scripts.js and it doesn't exist in our markup. Drop the css
link, the js script tag, and the static assets. Also revert the
previous attempt that forced active dots to inactive geometry; with
flexslider gone, Bootstrap's design (active 12px margin 0 + inactive
10px margin 1 → both outer 12px) keeps positions stable on its own,
which matches the live behavior the user observed.
Bootstrap-3's default makes the active indicator 12x12 with margin 0
while inactive is 10x10 with margin 1px. With two slides the size and
margin swap shifts dot positions on every slide change, which reads as
the gap between dots changing. Force all dots to the inactive geometry
so only the fill toggles.
The hand-edited live theme stylesheet uses // for line comments in
several places — valid in SCSS, invalid in CSS. Browsers recover but the
recovery can drop the surrounding declaration depending on context. The
body{} block at the top of the file in particular had `font-size: 14px;
//13px;` which on some browsers kills the whole rule, falling back to
Bootstrap's body defaults (Helvetica, line-height 1.428). Convert the
seven // line comments to /* */ so parsing is unambiguous.
The Sass file in sass/_praxis_theme.scss had drifted from the live
_style.css over the project's life — different line-heights (34/28 vs
24), padding overrides, restructured selectors, and a few sections that
the SCSS parser couldn't accept anyway (CSS // line-comments, missing
semicolons inherited from the original hand-written CSS).
For Phase 1 visual parity the correct source of truth is the live CSS,
not a re-edited copy. Drop compile_sass, ship the live _style.css as
/css/praxis.css straight from static/, and point base.html at it. This
restores the live vertical rhythm (paragraph spacing, banner padding)
that had been subtly off.
Two related fixes for the visual gaps reported after the previous pass:
1. Doctor portraits on /team were horizontally compressed. Cause: explicit
width="200"/height="200" attributes interacted with Bootstrap's
`img { display:block; max-width:100%; height:auto }` and `.float-right`
padding to squish the rendered box. Dropping the attributes lets the
browser use the resized image's intrinsic 200x200 dimensions, with the
padding sitting around the image instead of inside its width.
2. Vertical rhythm was a few pixels off vs live. Cause: the live Grav
markdown wraps standalone image references in <p> (markdown spec),
adding the expected paragraph margin. Our shortcode emitted bare <img>
with no wrapper, so the following <p> was visually slightly closer.
Wrap the shortcode calls in <p> in the 6 service pages and 3 doctor
blocks to restore the rhythm.
The image macro was resizing every call to its width arg, including
upscaling 270x180 service thumbnails to 600x400 and rendering 400x400
doctor portraits at full size. Live serves service thumbs at native
270x180 (CSS-floated) and doctor portraits at 200x200.
- image macro: width=0 (default) skips resize, emits raw <img> with no
width/height — lets theme CSS do the sizing, matching live pattern
- img shortcode: defaults width to 0 so callers can omit it
- service grid (home + 7 service pages): drop width=600
- doctor portraits on /team: width=200 (matches live render)
Moves hardcoded address, phone/fax/email, and Sprechzeiten table out of
content/contact.md into a dedicated contact.html template that renders
them from config.extra — sharing a single source of truth with the footer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restores the markup contracts the theme CSS expects on the home page:
- imports section_title macro (st) and calls it on all four sections
- news/about/services/team sections get bordered-icon headings via macro
- service cards use thumbnail/caption/clearfix structure (2-col grid)
- team portraits use figure/figcaption with small for position
- gray-bg (not bg-gray) on the services section
- Suppress section title prefix on home page (section.path == '/')
- Compose site_title from config.title + config.extra.doctors
- Add keywords meta tag from config.extra.keywords
The live site had the Maps iframe removed because third-party embeds
trigger cookie-consent obligations under GDPR. The Phase-1 1:1 pass
accidentally reintroduced it; this commit removes:
- the inline iframe on the home page (templates/index.html)
- the map() shortcode (templates/shortcodes/map.html)
- the {{ map() }} call on the contact page (content/contact.md)
Do not re-add without a working consent gate.
- page.html generic single-page template
- legal.md with slug=impressum to match live URL
- contact.md with practice info, hours, map embed
- map shortcode (iframe-based) usable from markdown
- Home template inlines the map iframe directly (shortcodes can't be called from templates)
Migrated all six service pages from Grav to Zola format:
- YAML frontmatter → TOML with weight and extra.anchor
- Grav image refs → {{ img() }} shortcode calls
- [fa=external-link /] → <i class="fa fa-external-link"></i>
- Linked image in HV page wrapped with <a href> HTML tag
Anchors HV, Corona, Vorsorge, Diagnostik, Kardio, Diabetologie match
the home page service grid links established in commit 9cf54f9.
- Extends base.html, uses shared image macro for resize_image
- Refactored img shortcode to forward to macros/image.html (DRY)
- News pulled via get_page from content/news.md
- Notfall section skipped (was unpublished in grav)
- Map call commented out until shortcode lands in Task 20