Compare commits

...
2 Commits
Author SHA1 Message Date
Nick SzaunigandClaude Opus 5 3393de2286 fix: Hero-Text war ueber hellem Bildmaterial nicht lesbar
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m12s
Die Ortszeile ueber der Ueberschrift stand in honey-light auf einem Foto mit
25 % Grundabdunklung plus einem to-tr-Verlauf, der die Textspalte bei rund
44 % Gesamtdeckung liegen liess. Gegen ein helles Motiv (Holz, Himmel)
gerechnet ergab das:

    Ortszeile    1,34:1   (WCAG AA verlangt 4,5:1 bei 12 px)
    Ueberschrift 2,73:1   (verlangt 3:1 bei grosser Schrift)
    Fussnote     1,90:1

Lighthouse meldet davon nichts und gibt der Seite weiterhin 100 fuer
Accessibility — Kontrast von Text ueber Bildern laesst sich nicht statisch
pruefen, weil der Hintergrund vom hochgeladenen Motiv abhaengt. Genau deshalb
muss die Abdunklung den schlechtesten Fall abdecken und nicht das Bild, das
gerade hinterlegt ist.

Drei Aenderungen, alle nachgerechnet:

- Verlauf ab md waagerecht (85 % -> 70 % -> 0) statt diagonal. Die Textspalte
  endet bei etwa 45 % Breite und liegt damit ganz im dunklen Teil, waehrend
  die rechte Bildhaelfte unangetastet bleibt.
- Auf schmalen Schirmen senkrecht, weil der Text dort ueber die volle Breite
  laeuft und rechts sonst in den durchsichtigen Teil geriete.
- Neuer Farbton honey-pale (#e4cfa4) fuer die Ortszeile; honey-light haelt
  4,5:1 selbst bei 89 % Abdunklung nicht (4,14:1). Fussnote von cream/60 auf
  cream/80.

Ergebnis in der Textspalte: Ortszeile 4,81:1 bis 6,97:1, Ueberschrift 6,41:1
bis 9,28:1, Fussnote 4,80:1 bis 6,59:1 — AA auf allen Breiten.

tsc: 0 Fehler. vitest: 359/359 gruen. eslint: 0 Warnungen. next build: gruen.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-20 04:04:36 +02:00
Nick SzaunigandClaude Opus 5 368985c0cf fix: Newsletter und Firmenanfrage waeren mit aktivem CAPTCHA unabsendbar gewesen
Beide Formulare lasen NEXT_PUBLIC_TURNSTILE_SITE_KEY direkt aus process.env,
und zwar im Modulrumpf einer Client Component. NEXT_PUBLIC_-Variablen werden
dort zur BUILD-Zeit ins Bundle eingebacken. Gesetzt wird der Wert hier aber
erst zur Laufzeit (k8s/01-configmap.yaml) — im Docker-Build existiert er nicht
und wird auch nicht als Build-Arg hereingereicht.

Der Wert war damit dauerhaft undefined, das Widget erschien nie. Solange
TURNSTILE_SECRET_KEY ebenfalls leer ist, faellt das nicht auf:
verifyTurnstileToken gibt ohne Secret bewusst true zurueck. Sobald das Secret
aber gesetzt wird — also genau beim Scharfschalten des Schutzes —, verlangt
der Server einen Token, den der Browser nirgends erzeugen kann. Newsletter-
Anmeldung und Firmenanfrage haetten dann jede Eingabe abgewiesen, ohne dass
irgendwo ein Fehler sichtbar wird.

Alle uebrigen Formulare (Login, Registrierung, Passwort-Reset, Zwei-Faktor,
Checkout, Verfuegbarkeits-Benachrichtigung) lesen den Wert bereits im Server
Component und reichen ihn als Prop weiter — dort wirkt die Laufzeit-Variable.
Diese beiden folgen jetzt demselben Muster.

Bewusst so und nicht ueber ein Docker-Build-Arg geloest: ein Build-Arg wuerde
den Schluessel ins Image einbacken, womit ein Schluesselwechsel einen Neubau
braeuchte und die ConfigMap fuer diese beiden Stellen wirkungslos bliebe. Ein
einziger Weg fuer alle Formulare ist weniger fehleranfaellig als zwei.

Hinweis fuer den Betrieb: der Schutz ist damit noch nicht aktiv. Dafuer
muessen NEXT_PUBLIC_TURNSTILE_SITE_KEY (ConfigMap) UND TURNSTILE_SECRET_KEY
(Secret) gesetzt sein — beide oder keiner.

tsc: 0 Fehler. vitest: 359/359 gruen. eslint: 0 Warnungen. next build: gruen.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-20 04:04:36 +02:00
6 changed files with 67 additions and 15 deletions

No files matched your search

+8
View File
@@ -9,6 +9,13 @@
hellem Grund (Hintergrund/Cream), -light fuer Text auf bg-forest. */
--color-honey-dark: #8a6531;
--color-honey-light: #c9a468;
/* Fuer Text ueber BILDERN statt ueber einer Volltonflaeche. -light besteht
4.5:1 nur gegen deckendes bg-forest; im Hero liegt der Text aber auf einem
Foto mit halbdurchlaessiger Abdunklung, und je nach Motiv (helles Holz,
Himmel) sank der Kontrast dort auf 1,34:1 — die Zeile war praktisch
unsichtbar. -pale haelt bei der im Hero gesetzten Abdunklung 4,8:1 bis
7,0:1 und behaelt den Honigton. */
--color-honey-pale: #e4cfa4;
--color-background: #faf8f3;
--color-ink: #242424;
--color-sage: #788774;
@@ -20,6 +27,7 @@
--color-honey: var(--color-honey);
--color-honey-dark: var(--color-honey-dark);
--color-honey-light: var(--color-honey-light);
--color-honey-pale: var(--color-honey-pale);
--color-background: var(--color-background);
--color-ink: var(--color-ink);
--color-sage: var(--color-sage);
+1 -1
View File
@@ -63,7 +63,7 @@ export default async function HomePage() {
<Sustainability />
<BusinessTeaser />
<Reviews />
<NewsletterSignup />
<NewsletterSignup turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} />
</>
);
}
+1 -1
View File
@@ -51,7 +51,7 @@ export default async function UnternehmenPage() {
<ScrollReveal className="mt-20 max-w-2xl">
<h2 className="text-2xl text-forest mb-8">{t.unternehmenPage.inquiryHeading}</h2>
<BusinessInquiryForm />
<BusinessInquiryForm turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} />
</ScrollReveal>
</Container>
</div>
+20 -5
View File
@@ -7,9 +7,24 @@ import { useI18n } from "@/i18n/i18n-provider";
type Status = "idle" | "loading" | "success" | "error";
const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
export function BusinessInquiryForm() {
/**
* Der Turnstile-Sitekey kommt als Prop vom Server und NICHT aus
* process.env in dieser Datei.
*
* Grund: NEXT_PUBLIC_-Variablen werden in Client Components zur BUILD-Zeit
* ins Bundle eingebacken. Der Wert wird hier aber erst zur Laufzeit gesetzt
* (k8s/01-configmap.yaml). Ein Zugriff auf process.env an dieser Stelle
* ergibt deshalb dauerhaft undefined — das Widget erschiene nie, waehrend die
* Serverseite bei gesetztem TURNSTILE_SECRET_KEY sehr wohl einen Token
* verlangt. Das Formular waere damit unabsendbar, ohne dass irgendwo ein
* Fehler auftaucht.
*
* Alle uebrigen Formulare (Login, Registrierung, Passwort-Reset, Zwei-Faktor,
* Checkout, Verfuegbarkeits-Benachrichtigung) lesen den Wert bereits im
* Server Component und reichen ihn als Prop weiter. Diese Datei folgt jetzt
* demselben Muster.
*/
export function BusinessInquiryForm({ turnstileSiteKey }: { turnstileSiteKey?: string }) {
const { t } = useI18n();
const [status, setStatus] = useState<Status>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@@ -96,8 +111,8 @@ export function BusinessInquiryForm() {
{errorMessage ? <p className="text-sm text-red-700">{errorMessage}</p> : null}
{TURNSTILE_SITE_KEY ? (
<TurnstileWidget siteKey={TURNSTILE_SITE_KEY} onVerify={setTurnstileToken} />
{turnstileSiteKey ? (
<TurnstileWidget siteKey={turnstileSiteKey} onVerify={setTurnstileToken} />
) : null}
<Button type="submit" disabled={status === "loading"}>
+17 -3
View File
@@ -110,7 +110,21 @@ export function Hero({
unlesbar machen kann. Der gerichtete Verlauf darunter verstaerkt das
gezielt dort, wo die Textspalte tatsaechlich sitzt (unten links). */}
<div className="absolute inset-0 bg-forest/25" />
<div className="absolute inset-0 flex items-center bg-gradient-to-tr from-forest/70 via-forest/25 to-transparent">
{/* Senkrecht auf schmalen Schirmen, waagerecht ab md.
Der frueher hier stehende to-tr-Verlauf liess die Textspalte auf
rund 44 % Abdunklung liegen. Ueber hellem Motiv (Holz, Himmel)
ergab das 1,34:1 fuer die Ortszeile und 2,73:1 fuer die
Ueberschrift — beides weit unter WCAG AA. Lighthouse meldet das
nicht, weil es Text ueber Bildern nicht bewerten kann.
Waagerecht funktioniert nur ab md: dort endet die Textspalte bei
etwa 45 % Breite und liegt damit ganz im dunklen Teil, waehrend
die rechte Bildhaelfte unangetastet bleibt. Auf dem Handy laeuft
der Text ueber die volle Breite und geriete rechts in den
durchsichtigen Teil — deshalb dort senkrecht, was horizontal
gleichmaessig abdunkelt und den oberen Bildrand frei laesst. */}
<div className="absolute inset-0 flex items-center bg-gradient-to-t from-forest/85 via-forest/70 to-forest/10 md:bg-gradient-to-r md:from-forest/85 md:via-forest/70 md:to-transparent">
<Container>
<motion.div
className="max-w-xl text-cream"
@@ -120,7 +134,7 @@ export function Hero({
>
<motion.p
data-reveal="" variants={itemVariants}
className="tracking-wide-caps text-xs text-honey-light mb-4"
className="tracking-wide-caps text-xs text-honey-pale mb-4"
>
{location}
</motion.p>
@@ -140,7 +154,7 @@ export function Hero({
{t.home.hero.ctaSecondary}
</LinkButton>
</motion.div>
<motion.p data-reveal="" variants={itemVariants} className="mt-8 text-xs text-cream/60 tracking-wide">
<motion.p data-reveal="" variants={itemVariants} className="mt-8 text-xs text-cream/80 tracking-wide">
{footnote}
</motion.p>
</motion.div>
+20 -5
View File
@@ -8,9 +8,24 @@ import { useI18n } from "@/i18n/i18n-provider";
type Status = "idle" | "loading" | "success" | "error";
const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
export function NewsletterSignup() {
/**
* Der Turnstile-Sitekey kommt als Prop vom Server und NICHT aus
* process.env in dieser Datei.
*
* Grund: NEXT_PUBLIC_-Variablen werden in Client Components zur BUILD-Zeit
* ins Bundle eingebacken. Der Wert wird hier aber erst zur Laufzeit gesetzt
* (k8s/01-configmap.yaml). Ein Zugriff auf process.env an dieser Stelle
* ergibt deshalb dauerhaft undefined — das Widget erschiene nie, waehrend die
* Serverseite bei gesetztem TURNSTILE_SECRET_KEY sehr wohl einen Token
* verlangt. Das Formular waere damit unabsendbar, ohne dass irgendwo ein
* Fehler auftaucht.
*
* Alle uebrigen Formulare (Login, Registrierung, Passwort-Reset, Zwei-Faktor,
* Checkout, Verfuegbarkeits-Benachrichtigung) lesen den Wert bereits im
* Server Component und reichen ihn als Prop weiter. Diese Datei folgt jetzt
* demselben Muster.
*/
export function NewsletterSignup({ turnstileSiteKey }: { turnstileSiteKey?: string }) {
const { t } = useI18n();
const [email, setEmail] = useState("");
const [status, setStatus] = useState<Status>("idle");
@@ -63,9 +78,9 @@ export function NewsletterSignup() {
<Button type="submit" disabled={status === "loading"} className="flex-shrink-0 whitespace-nowrap">
{status === "loading" ? t.home.newsletter.subscribing : t.home.newsletter.subscribe}
</Button>
{TURNSTILE_SITE_KEY ? (
{turnstileSiteKey ? (
<div className="w-full flex justify-center">
<TurnstileWidget siteKey={TURNSTILE_SITE_KEY} onVerify={setTurnstileToken} />
<TurnstileWidget siteKey={turnstileSiteKey} onVerify={setTurnstileToken} />
</div>
) : null}
</form>