Files
Anelia-Wolf/src/components/motion/Curtain.tsx
T
JUSNandClaude Opus 5 a37e48e4ef Messeseite: „EN“ führt zur englischen Messeseite, nicht zur Startseite
Auf her-career.anelia-wolf.com liegt die Seite unter „/“. usePathname()
meldet dort „/“, und counterpart() leitete daraus folgerichtig die
englische STARTSEITE ab – wer am Stand gescannt und dann auf „EN“ getippt
hat, landete auf anelia-wolf.com/en/. Der Umschalter fragt jetzt den
hreflang-Verweis im Kopf des Dokuments: Der hängt an der Seite, nicht an
der Adresse, unter der sie ausgeliefert wird.

Am selben Wurzelproblem: Der Sprachwechsel ist wegen der zwei
Wurzel-Layouts ohnehin ein neues Dokument. Den macht jetzt der Browser
statt router.push(), und über die Sprachgrenze wird nicht mehr vorgeladen
– auf der Subdomain scheiterte der Vorabruf an CORS, mehrfach pro
Seitenaufbau.

Dazu zwei Sachen am Kopf der Messeseite: Das Monogramm steht hinter dem
Schlusspunkt der Begrüßung, 0,783 em hoch (die Oberlänge der Playfair
Display, im Browser gemessen) und auf der Grundlinie – und dafür nicht
mehr in der Kopfzeile, wo es sonst zweimal auf einem Bildschirm stünde.
Es steht dort dekorativ, sonst wandert der Text seines <title> über
SplitText ins aria-label der Überschrift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:17:09 +02:00

116 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { gsap, EASE } from "@/lib/gsap";
import { WolfMark } from "@/components/brand/WolfMark";
import { chrome as chromeDe, type Chrome } from "@/content/chrome";
import { crossesLangRoot } from "@/lib/i18n";
import { endTransition, setTransitionPhase, subscribe } from "./transition-store";
export interface CurtainProps {
/** The monogram (48 px). layout.tsx passes `<BrandMark/>`; falls back to `<WolfMark/>`. */
children?: ReactNode;
/** Texte der Hülle. Ohne Angabe die deutsche Fassung. */
chrome?: Chrome;
}
/**
* The page-turn sheet: fixed ivory, 1 px gold hairline on its top edge,
* monogram + destination label centred. Note the start state lives ONLY in
* GSAP (`yPercent`) – a CSS `translateY(100%)` would be read as the tween's
* base `y` and added on top, which pushed the whole cover tween one viewport
* below the fold (the reader saw a hard cut, then the sheet wiping up over the
* new page). Slides up 0.45 s (power3.inOut),
* pushes the route, keeps rising once the pathname changed, then resets.
* Hard fallback: full navigation when the route has not changed after 900 ms.
*/
export function Curtain({ children, chrome = chromeDe }: CurtainProps) {
const router = useRouter();
const pathname = usePathname();
const sheet = useRef<HTMLDivElement>(null);
const phase = useRef<"idle" | "cover" | "pushed" | "reveal">("idle");
const timer = useRef<number>(0);
const [label, setLabel] = useState<string | null>(null);
useEffect(() => {
return subscribe((s) => {
if (s.phase !== "cover" || !s.href || phase.current !== "idle") return;
const el = sheet.current;
if (!el) return;
const href = s.href;
phase.current = "cover";
setLabel(s.label ?? chrome.curtain.fallbackLabel);
gsap.killTweensOf(el);
gsap.set(el, { y: 0, yPercent: 100, autoAlpha: 1, pointerEvents: "auto" });
gsap.to(el, {
yPercent: 0,
duration: 0.45,
ease: EASE.inOut,
onComplete: () => {
phase.current = "pushed";
setTransitionPhase("pushed");
/* Sprachwechsel: den Dokumentwechsel macht der Browser, nicht der Router
(Begründung in crossesLangRoot(), src/lib/i18n.ts). Die Hülle bleibt
liegen, bis das neue Dokument da ist – sie gehört zu diesem hier. */
if (crossesLangRoot(pathname, href)) {
window.location.assign(href);
return;
}
router.push(href, { scroll: false });
window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
if (phase.current === "pushed") window.location.assign(href);
}, 900);
},
});
});
}, [router, pathname, chrome.curtain.fallbackLabel]);
// Route changed → the sheet continues upward and resets.
useEffect(() => {
if (phase.current !== "pushed") return;
const el = sheet.current;
if (!el) return;
window.clearTimeout(timer.current);
phase.current = "reveal";
setTransitionPhase("reveal");
gsap.to(el, {
yPercent: -100,
duration: 0.45,
ease: EASE.inOut,
delay: 0.05,
onComplete: () => {
gsap.set(el, { y: 0, yPercent: 100, autoAlpha: 0, pointerEvents: "none" });
phase.current = "idle";
setLabel(null);
endTransition();
},
});
}, [pathname]);
useEffect(() => () => window.clearTimeout(timer.current), []);
return (
<div
ref={sheet}
id="curtain"
aria-hidden="true"
data-theme="light"
className="pointer-events-none invisible fixed inset-0 z-[70] flex flex-col items-center justify-center gap-6 bg-ivory text-ink"
>
<span
aria-hidden="true"
className="absolute inset-x-0 top-0 h-px"
style={{ background: "var(--gold-hairline-light)" }}
/>
<span className="block h-12 text-ink [&_svg]:h-12 [&_svg]:w-auto">
{children ?? <WolfMark idPrefix="curtain" className="h-12 w-auto" />}
</span>
<span className="min-h-[1em] font-mono text-xs tracking-[0.06em] text-gold-deep">{label}</span>
</div>
);
}
export default Curtain;