GRV-Summit-Site/components/brand/RiftTopographyLayer.tsx
“kirukib” 3693495dd0
Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
Add site-wide topography patterns and refine section styling.
Use mainwhite.svg on white sections with curvy green transitions into flat green bands, improve text and button contrast, and deploy via OpenNext on Cloudflare Workers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 20:34:36 +03:00

212 lines
6.5 KiB
TypeScript

"use client";
import type { RiftPageProfile } from "@/content/rift-page-profiles";
import {
CONTOUR_MAJOR,
CONTOUR_MINOR,
RIFT_CHANNEL_INNER,
RIFT_CHANNEL_OUTER_LEFT,
RIFT_CHANNEL_OUTER_RIGHT,
RIFT_VIEWBOX,
contourOpacity,
getChannelTransform,
} from "@/lib/rift-topography-paths";
import { mixHex } from "@/lib/rift-colors";
import { cn } from "@/lib/utils";
const GREEN = "#1a5c38";
const GOLD = "#ffb300";
const BLUE = "#1f3d7e";
type Props = {
variant: "hero" | "ambient" | "header";
profile: RiftPageProfile;
className?: string;
scrollProgress?: number;
scrollY?: number;
reduceMotion?: boolean;
/** hero intro state */
introPhase?: "intro" | "settled" | "static";
};
function strokesFromProgress(p: number, accentMode: RiftPageProfile["accentMode"]) {
const t = Math.max(0, Math.min(1, p));
let primary = GREEN;
let accent = GOLD;
if (accentMode === "gold") {
primary = mixHex(GOLD, GREEN, 0.35);
accent = GOLD;
} else if (accentMode === "mixed") {
if (t < 0.33) {
primary = mixHex(GREEN, GOLD, t / 0.33);
accent = mixHex(GOLD, BLUE, t / 0.33);
} else if (t < 0.66) {
primary = mixHex(GOLD, BLUE, (t - 0.33) / 0.33);
accent = mixHex(BLUE, GREEN, (t - 0.33) / 0.33);
} else {
primary = mixHex(BLUE, GREEN, (t - 0.66) / 0.34);
accent = mixHex(GREEN, GOLD, (t - 0.66) / 0.34);
}
}
return { primary, accent };
}
export function RiftTopographyLayer({
variant,
profile,
className,
scrollProgress = 0,
scrollY = 0,
reduceMotion = false,
introPhase = "static",
}: Props) {
const { primary, accent } = strokesFromProgress(scrollProgress, profile.accentMode);
const drawOffset = reduceMotion ? 0 : Math.max(0, 1 - scrollProgress * 1.12 - 0.06);
const majorOp = contourOpacity(profile.contourDensity, "major");
const minorOp = contourOpacity(profile.contourDensity, "minor");
const channelTransform = getChannelTransform(profile.channelBias);
const parallaxY = reduceMotion ? 0 : scrollY * 0.04;
const isHero = variant === "hero";
const sceneClass = cn(
isHero && "rift-hero-scene",
isHero && introPhase === "intro" && "rift-hero-intro",
isHero && introPhase === "settled" && "rift-hero-settled",
isHero && introPhase === "static" && "rift-hero-static",
!isHero && profile.profileClass,
profile.enablePulse && !reduceMotion && "rift-ambient-pulse"
);
const strokeTransition =
"stroke 0.85s ease, opacity 0.85s ease, stroke-dashoffset 0.25s ease-out";
const drawStyle = {
strokeDasharray: 1,
strokeDashoffset: drawOffset,
transition: strokeTransition,
} as const;
const showMinor =
profile.contourDensity !== "low" || profile.id === "partners";
return (
<div
className={cn("absolute inset-0 overflow-hidden", sceneClass, className)}
style={{
opacity: variant === "ambient" ? profile.ambientOpacity : 1,
transform: variant === "ambient" ? `translate3d(0, ${parallaxY}px, 0)` : undefined,
}}
aria-hidden
>
{isHero && (
<>
<div
className="absolute inset-0 bg-gradient-to-b from-[#fbfdfb] via-white to-[#f0f5f2]"
aria-hidden
/>
<div
className="absolute inset-0 opacity-70"
style={{
background:
"radial-gradient(ellipse 55% 45% at 50% 48%, rgba(255,179,0,0.12) 0%, transparent 70%)",
}}
aria-hidden
/>
<div className="rift-crack-line pointer-events-none absolute left-1/2 top-[46%] z-10 h-px w-[min(88%,680px)] -translate-x-1/2" />
<div className="rift-floor-glow pointer-events-none absolute inset-0" />
<div className="rift-channel-open pointer-events-none absolute inset-0" aria-hidden />
</>
)}
<svg
className="rift-line-draw absolute inset-0 h-full w-full"
viewBox={`0 0 ${RIFT_VIEWBOX.w} ${RIFT_VIEWBOX.h}`}
preserveAspectRatio="xMidYMid slice"
>
<g
className="rift-contour-major"
fill="none"
stroke={primary}
strokeLinecap="round"
style={{ transform: channelTransform, transformOrigin: "center" }}
>
{CONTOUR_MAJOR.map((d, i) => (
<path
key={`m-${i}`}
pathLength={1}
d={d}
strokeWidth={0.7 + (i % 2) * 0.2}
opacity={majorOp}
className="rift-contour-path"
style={{ ...drawStyle, animationDelay: isHero ? `${i * 0.35}s` : undefined }}
/>
))}
</g>
{showMinor && (
<g
className="rift-contour-minor"
fill="none"
stroke={primary}
strokeLinecap="round"
opacity={minorOp * 1.1}
style={{ transform: channelTransform, transformOrigin: "center" }}
>
{CONTOUR_MINOR.map((d, i) => (
<path
key={`n-${i}`}
pathLength={1}
d={d}
strokeWidth={0.45}
className="rift-contour-path rift-terrace-line"
style={drawStyle}
/>
))}
</g>
)}
<g
className="rift-channel-group"
fill="none"
strokeLinecap="round"
style={{ transform: channelTransform, transformOrigin: "center" }}
>
<path
pathLength={1}
d={RIFT_CHANNEL_OUTER_LEFT}
stroke={accent}
strokeWidth={1.4}
className="rift-channel-outer rift-channel-left"
opacity={0.55}
style={drawStyle}
/>
<path
pathLength={1}
d={RIFT_CHANNEL_OUTER_RIGHT}
stroke={accent}
strokeWidth={1.4}
className="rift-channel-outer rift-channel-right"
opacity={0.55}
style={drawStyle}
/>
{RIFT_CHANNEL_INNER.map((d, i) => (
<path
key={`c-${i}`}
pathLength={1}
d={d}
stroke={profile.accentMode === "gold" ? GOLD : primary}
strokeWidth={0.55}
opacity={0.35}
className="rift-channel-inner"
style={{ ...drawStyle, animationDelay: `${0.2 + i * 0.15}s` }}
/>
))}
</g>
</svg>
{isHero && (
<div className="rift-contour-morph pointer-events-none absolute inset-0" aria-hidden />
)}
</div>
);
}