first publish
This commit is contained in:
commit
b8e41b4d55
147 files changed
+19367
No files matched your search
@@ -0,0 +1,107 @@
|
||||
"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 { 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");
|
||||
router.push(href, { scroll: false });
|
||||
window.clearTimeout(timer.current);
|
||||
timer.current = window.setTimeout(() => {
|
||||
if (phase.current === "pushed") window.location.assign(href);
|
||||
}, 900);
|
||||
},
|
||||
});
|
||||
});
|
||||
}, [router, 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;
|
||||
Reference in new issue
Block a user