diff --git a/src/app/page.tsx b/src/app/page.tsx index 237dfbe..98d5d58 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,4 @@ +import { Landing } from "@/app/sections/landing"; import { MagneticHoverShowcase } from "@/components/magnetic-hover-showcase"; import { ScrollProgressIndicator } from "@/components/scroll-progress-indicator"; @@ -7,20 +8,7 @@ export default function Home() {
-
-
-

Welcome

-

- Scroll down to explore the sections and test the progress indicator. -

-

- Watch the dots on the right... -

-
-
+
diff --git a/src/app/sections/landing/index.tsx b/src/app/sections/landing/index.tsx new file mode 100644 index 0000000..e1bd3b1 --- /dev/null +++ b/src/app/sections/landing/index.tsx @@ -0,0 +1,118 @@ +import { LandingClient } from "./landing-client"; + +const letterSegments: Record = { + l: [ + [0, 0, 2, 0], + [2, 0, 2, 10], + [2, 10, 0, 10], + [0, 10, 0, 0], + ], + u: [ + [0, 3, 0, 10], + [0, 10, 4, 10], + [4, 10, 4, 3], + [4, 3, 0, 3], + [2, 3, 2, 8], + ], + i: [ + [0, 3, 2, 3], + [2, 3, 2, 10], + [2, 10, 0, 10], + [0, 10, 0, 3], + [0, 0.5, 2, 0.5], + [2, 0.5, 2, 2.5], + [2, 2.5, 0, 2.5], + [0, 2.5, 0, 0.5], + ], + s: [ + [0, 3, 5, 3], + [0, 10, 5, 10], + [0, 3, 0, 7.67], + [0, 7.67, 0, 10], + [5, 3, 5, 5.33], + [5, 5.33, 5, 10], + [2, 5.33, 5, 5.33], + [0, 7.67, 3, 7.67], + ], + d: [ + [2, 0, 4, 0], + [4, 0, 4, 10], + [0, 3, 0, 10], + [0, 10, 4, 10], + [0, 3, 2, 3], + [2, 3, 2, 0], + [2, 5, 2, 8], + ], + r: [ + [0, 3, 4, 3], + [2, 5.33, 2, 10], + [2, 10, 0, 10], + [0, 10, 0, 3], + [4, 3, 4, 5.33], + [2, 5.33, 4, 5.33], + ], + a: [ + [0, 3, 0, 5.33], + [0, 5.33, 0, 10], + [0, 10, 5, 10], + [5, 10, 5, 3], + [5, 3, 0, 3], + [0, 5.33, 3, 5.33], + [2, 7.67, 3, 7.67], + ], + v: [ + [0, 3, 1.5, 10], + [5, 3, 3.5, 10], + [1.5, 10, 3.5, 10], + [2, 3, 2.5, 8], + [3, 3, 2.5, 8], + [0, 3, 2, 3], + [3, 3, 5, 3], + ], + e: [ + [5, 3, 5, 10], + [0, 3, 5, 3], + [0, 3, 0, 10], + [5, 10, 0, 10], + [5, 7.67, 2, 7.67], + [2, 5.33, 3, 5.33], + ], +}; + +const letterLayout: [string, number][] = [ + ["l", 0], + ["u", 2.5], + ["i", 7], + ["s", 9.5], + ["d", 15], + ["r", 19.5], + ["a", 24], + ["l", 29.5], + ["v", 32], + ["e", 37.5], + ["s", 43], +]; + +const allPaths = letterLayout.reduce<{ d: string; key: string }[]>( + (paths, [letterKey, xOffset]) => { + const letter = letterSegments[letterKey]; + if (letter) { + letter.forEach(([x1, y1, x2, y2]) => { + paths.push({ + d: `M${x1 + xOffset},${y1} L${x2 + xOffset},${y2}`, + key: `seg-${paths.length}`, + }); + }); + } + return paths; + }, + [], +); + +export const Landing = () => { + return ( +
+ +
+ ); +}; diff --git a/src/app/sections/landing/landing-client.tsx b/src/app/sections/landing/landing-client.tsx new file mode 100644 index 0000000..11de950 --- /dev/null +++ b/src/app/sections/landing/landing-client.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { type MotionValue, m, useScroll, useTransform } from "motion/react"; +import { useRef } from "react"; +import { springGentle } from "@/lib/motion"; + +const STROKE_WIDTH = 0.05; + +const LogotypePath = ({ + d, + scrollProgress, +}: { + d: string; + scrollProgress: MotionValue; +}) => { + const strokeDashoffset = useTransform(scrollProgress, [0, 0.6], [0, 1]); + + return ( + + ); +}; + +type Props = { + paths: { d: string; key: string }[]; +}; + +export const LandingClient = ({ paths }: Props) => { + const containerRef = useRef(null); + + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ["start start", "end start"], + }); + + return ( +
+ + {paths.map(({ d, key }) => ( + + ))} + +
+ ); +}; diff --git a/src/components/scroll-progress-indicator/radial-reveal.tsx b/src/components/scroll-progress-indicator/radial-reveal.tsx index 5bd1a67..ded97ad 100644 --- a/src/components/scroll-progress-indicator/radial-reveal.tsx +++ b/src/components/scroll-progress-indicator/radial-reveal.tsx @@ -36,6 +36,21 @@ export const RadialReveal = ({ targetSection, origin, onComplete }: Props) => { clone.style.transform = `translateY(-${clampedScrollY}px)`; clone.style.pointerEvents = "none"; + // Reset scroll-animated elements to their initial visual state + // Format: data-scroll-animated="property1:value1;property2:value2" + clone.querySelectorAll("[data-scroll-animated]").forEach((el) => { + const htmlEl = el as HTMLElement | SVGElement; + const resetValues = htmlEl.dataset.scrollAnimated; + if (resetValues) { + resetValues.split(";").forEach((pair) => { + const [property, value] = pair.split(":"); + if (property && value !== undefined) { + htmlEl.style.setProperty(property, value); + } + }); + } + }); + overlayRef.current.innerHTML = ""; overlayRef.current.appendChild(clone); setIsReady(true);