Decisions — Ralfing
Phase 1 (2026-08-14)
Project slug: ralfing
The Google listing name is 62 characters ("Rolfing | Strukturelle Integration – Komplementärtherapeut Ralf Eggenberger") and phase1
auto-derived an unusable folder name from it. ralfing is his own brand name — the domain,
the Instagram handle, the LinkedIn company, the YouTube channel, and his stated motto
("Ralfing – Für mehr Lebensqualität"). Repo and Cloudflare project use the same slug.
Category: physiotherapy-osteopathy-chiropractic
Reasoning in build/category-mapping.md. Short version: treatment series + insurance +
named conditions + credentials, none of which the massage or reflexology specs model.
Palette: adopt the Instagram petrol as the dark anchor — operator-approved
His site gave him aquamarine #7FFFD4 and apricot #FFC180 on off-white, with no dark tone;
that absence is why the current site reads washed out and fails contrast on body copy.
His Instagram was already using a deep petrol — sampled at #398489, covering 78% of the
quote-card area — which is the same hue family as the aquamarine, just dropped in value.
Taking it as primary (darkened to #1A4A4F so white text clears 9.8:1) unifies website and
Instagram instead of inventing a colour. Aquamarine and apricot carry over untouched, so
returning clients still recognise the brand.
Typography: keep Crimson Text, replace Roboto
Crimson Text at wide tracking is the one deliberate typographic decision the old site made, and
it is unused anywhere else in the portfolio — kept, at readable weight and full contrast.
Roboto was a Joomla/Cassiopeia default rather than a choice; replaced with Schibsted Grotesk,
which handles long German body copy better and avoids the portfolio's crowded Karla/Jost/Work Sans lane.
Locales: DE (primary, canonical) + EN — operator-approved
He practises in English per the EMR register, Zürich-Oerlikon has an international catchment,
and 6 of 8 Google reviews arrived in or were translated to English. The existing site is DE-only.
Photography: use his own Google Places uploads — operator-approved
The site's gallery images are Joomla _thumb.webp derivatives (17–27 KB) and are unusable at
width. The three Google Places photos are attributed to "Ralfing.ch" (his own uploads) at
4800×3204. Build with those; request originals and a model release for the recognisable client
in the current hero.
Two studios treated as navigation, not an address list
Choosing Oerlikon vs Baden is choosing Thursday/Saturday vs Friday. The current site buries both
addresses in the footer. The rebuild promotes location choice into the hero and the booking flow.
Styleguide sample content replaced by hand
render-styleguide.mjs copies styleguide-html-template.html, whose demo content is hardcoded
reflexology copy ("Reflexology", "Maternity", "Hand reflex", "A calm hour for your body to settle").
For a client-facing review artifact that reads as the wrong business. Patched
brand/styleguide.html directly with real Ralfing content.
⚠️ Re-running render-styleguide.mjs will overwrite these edits.
→ Logged as a suite issue for a spec doc: the template's sample block should be parameterised
per category, or fed from content/identity.md.
Phase 2 (2026-08-14)
Distinctiveness warning reviewed and accepted
check-distinctiveness.mjs flags primary #1A4A4F as within 48 RGB units of
silvia-jayet-reflexologie (#2a302a) — exactly on its threshold. Rejected as a false
positive after checking perceptual distance:
| | hue | saturation | lightness |
|---|---|---|---|
| Ralfing #1A4A4F | 185.7° | 50.5% | 20.6% |
| Silvia #2a302a | 120.0° | 6.7% | 17.6% |
66° apart in hue and 7.5× the saturation: one is a saturated petrol/teal, the other a
near-neutral dark olive-grey. The RGB Euclidean heuristic under-separates dark colours, where a
fixed distance covers far more perceptual ground than it does in the midtones. The petrol is also
source-derived (sampled from his Instagram, not invented), so changing it to satisfy the metric
would make the brand *less* faithful.
→ Suite observation: the check would separate dark tones better in HSL/OKLab than in RGB.
Hero layout also differs from the last five (left-text-photo-right ×3): full-bleed photograph
with a petrol scrim, text overlaid left, and a two-studio day-picker beneath the CTAs.
/impressum redirect corrected by hand
redirect-map.mjs mapped the old German /impressum to /en/imprint/ with a confidence score of
1.00 — it matched on route similarity with no locale awareness. The old site was German-only,
so every legacy URL must land on a German route. Corrected in site/public/_redirects; the two
/neuheiten* URLs scored 0.00 and were mapped by hand (announcement → /standorte/, intro post →
/strukturelle-integration/).
→ Suite issue: the matcher should prefer a same-locale target, or at minimum refuse to
cross a locale boundary at high confidence.
Dark-band link colour bug (caught in visual QA)
Links inside .band--dark inherited the starter's a { color: var(--color-primary) }, which is
the band's own background — rendering the contact list on /standorte/ invisible. Fixed with an
explicit .band--dark a:not(.btn) rule. Worth watching for in any build whose primary doubles
as a section background.
Phase 2b — operator revisions (2026-08-14)
Four changes requested after reviewing the first deploy.
Imagery expanded from 3 to 11 photographs
The first build used only the three Google Places uploads. The old site's own images turned out to be
usable after all: the gallery files are 600×600 (fine as square tiles, just not full-bleed), and four
others are 1065–1200px wide. Full-resolution originals do not exist behind the _thumb names —
every _thumb-less and _large/_medium/_orig variant returns 404, so 600×600 is genuinely all
there is for those three.
Imported with SEO filenames: praxis-oerlikon-behandlung, oederlin-areal-baden,
praxisraum-baden-eingang, ralf-eggenberger-portrait, ralf-eggenberger-portrait-quadrat,
haltungsanalyse-im-stehen, arbeit-an-schulter-und-arm, faszien-arbeit-am-fuss.
Placement: home 9 · method 5 · about 3 · services 2 · locations 2. The old site showed 8 in total.
Instagram was not used as a source in the end — it was unnecessary once the site's own library
opened up, and the site images are higher resolution and unambiguously his.
Two finds worth noting: the Oederlin building shot is the single best asset for the Baden studio (and
the signage on it confirms the spelling is *Oederlin*, not "Oderlin" as the old site writes), and the
teal portrait happens to sit almost exactly on the brand petrol — an accident that makes the palette
look deliberate.
Addresses are now the map link
Both in the footer and on the studio cards, the address block itself links to Google Maps. Previously
there was a separate "Google Maps" link beside an inert address — but the address is what people
actually tap. The explicit button stays on the cards for discoverability.
Reviews link out to Google
Each quoted review's attribution links to that specific review's Google permalink (captured from the
Places API google_maps_uri), and the footer carries a "Bewertungen auf Google" link to the full
profile. Quotes that can be checked in one click are worth considerably more than quotes that cannot.
All rel="nofollow"; still no aggregateRating markup anywhere.
OneDoc booking embedded in-page
Previously a button that sent the visitor to onedoc.ch. Now the widget itself renders on
/termin-vereinbaren/ and /en/booking/ via its iframe plus the postMessage height handshake
(origin-checked against *.onedoc.ch, widget-id matched). The locale is passed through, so the
English page loads the English widget.
Two details that mattered:
min-heighthad to be cleared when the real height arrives, or the frame can only grow and a short
widget leaves dead space beneath it.
loading="lazy"was wrong here: this iframe *is* the page's conversion. Switched to eager.
The old site did embed the same widget, but behind its cookie-consent layer — so the booking page
rendered empty until a visitor accepted cookies. Here it loads directly, with no consent gate in
front of the primary conversion.
QA note for future runs: full-page Playwright screenshots do not trigger loading="lazy"
images or cross-origin iframes below the fold — they photograph as blank boxes and look exactly
like broken images. Both "bugs" found in this round were that artifact. Verify with a scroll pass
plus naturalWidth checks before changing any code. All 26 images across the five content pages
were confirmed loading, 0 broken.
Deferred to the client (see content/operator-notes.md)
Opening hours conflict, the missing 75/90-minute prices, the EMR/ZSR number, the UID for the
impressum, domain/DNS control, and whether the OneDoc booking widget stays.