Skip to content

Commit c60cd0b

Browse files
committed
STNDP-149 New landing page touch (#99)
1 parent 744293e commit c60cd0b

13 files changed

Lines changed: 1663 additions & 4860 deletions

pnpm-lock.yaml

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sites/website/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@types/react-dom": "^19.1.2",
2121
"astro": "^5.8.0",
2222
"lucide-react": "^0.525.0",
23+
"motion": "^12.23.12",
2324
"radix-ui": "^1.4.2",
2425
"react": "^19.1.0",
2526
"react-dom": "^19.1.0",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Based on https://magicui.design/docs/components/blur-fade
2+
3+
import {
4+
AnimatePresence,
5+
motion,
6+
useInView,
7+
type UseInViewOptions,
8+
type Variants,
9+
type MotionProps,
10+
} from "motion/react";
11+
import { useRef } from "react";
12+
13+
// type MarginType = UseInViewOptions["margin"];
14+
15+
interface BlurFadeProps extends MotionProps {
16+
children: React.ReactNode;
17+
className?: string;
18+
variant?: {
19+
hidden: { y: number };
20+
visible: { y: number };
21+
};
22+
// duration?: number;
23+
delay?: number;
24+
offset?: number;
25+
direction?: "up" | "down" | "left" | "right";
26+
inView?: boolean;
27+
// inViewMargin?: MarginType;
28+
blur?: string;
29+
}
30+
31+
export function BlurFade({
32+
children,
33+
className,
34+
variant,
35+
// duration = 0.25,
36+
delay = 0,
37+
offset = 8,
38+
direction = "down",
39+
inView = false,
40+
blur = "8px",
41+
...props
42+
}: BlurFadeProps) {
43+
const ref = useRef(null);
44+
const inViewResult = useInView(ref, {
45+
once: false,
46+
margin: "200px 0px -200px 0px",
47+
});
48+
const isInView = !inView || inViewResult;
49+
const defaultVariants: Variants = {
50+
hidden: {
51+
[direction === "left" || direction === "right" ? "x" : "y"]:
52+
direction === "right" || direction === "down" ? -offset : offset,
53+
opacity: 0,
54+
filter: `blur(${blur})`,
55+
},
56+
visible: {
57+
[direction === "left" || direction === "right" ? "x" : "y"]: 0,
58+
opacity: 1,
59+
filter: `blur(0px)`,
60+
},
61+
};
62+
const combinedVariants = variant || defaultVariants;
63+
return (
64+
<AnimatePresence>
65+
<motion.div
66+
ref={ref}
67+
initial="hidden"
68+
animate={isInView ? "visible" : "hidden"}
69+
exit="hidden"
70+
variants={combinedVariants}
71+
transition={{
72+
delay: 0.04 + delay,
73+
duration: 0.25,
74+
ease: "easeOut",
75+
}}
76+
className={className}
77+
{...props}
78+
>
79+
{children}
80+
</motion.div>
81+
</AnimatePresence>
82+
);
83+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { motion, type MotionProps } from "motion/react";
2+
import { cn } from "../lib/utils";
3+
4+
interface LineShadowTextProps
5+
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>,
6+
MotionProps {
7+
shadowColor?: string;
8+
as?: React.ElementType;
9+
}
10+
11+
export function LineShadowText({
12+
children,
13+
shadowColor = "black",
14+
className,
15+
as: Component = "span",
16+
...props
17+
}: LineShadowTextProps) {
18+
const MotionComponent = motion.create(Component);
19+
const content = typeof children === "string" ? children : null;
20+
21+
if (!content) {
22+
throw new Error("LineShadowText only accepts string content");
23+
}
24+
25+
return (
26+
<MotionComponent
27+
style={{ "--shadow-color": shadowColor } as React.CSSProperties}
28+
className={cn(
29+
"relative z-0 inline-flex",
30+
"after:absolute after:left-[0.04em] after:top-[0.04em] after:content-[attr(data-text)]",
31+
"after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]",
32+
"after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent",
33+
"after:animate-line-shadow",
34+
className
35+
)}
36+
data-text={content}
37+
{...props}
38+
>
39+
{content}
40+
</MotionComponent>
41+
);
42+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
motion,
3+
useInView,
4+
useScroll,
5+
useTransform,
6+
type UseInViewOptions,
7+
type MotionProps,
8+
} from "motion/react";
9+
import { useRef } from "react";
10+
11+
type MarginType = UseInViewOptions["margin"];
12+
13+
interface ScrollFadeProps extends MotionProps {
14+
children: React.ReactNode;
15+
className?: string;
16+
duration?: number;
17+
delay?: number;
18+
offset?: number;
19+
direction?: "up" | "down" | "left" | "right";
20+
inViewMargin?: MarginType;
21+
threshold?: number;
22+
parallaxIntensity?: number; // How much the section follows scroll (0 = no parallax, 1 = full parallax)
23+
enableParallax?: boolean;
24+
// Note: Fade effects removed for better performance - now only provides parallax movement
25+
}
26+
27+
export function ScrollFade({
28+
children,
29+
className,
30+
duration = 0.6,
31+
delay = 0,
32+
offset = 50,
33+
direction = "up",
34+
inViewMargin = "-100px",
35+
threshold = 0.1,
36+
parallaxIntensity = 0.3,
37+
enableParallax = false,
38+
...props
39+
}: ScrollFadeProps) {
40+
const ref = useRef(null);
41+
const isInView = useInView(ref, {
42+
once: false, // Allow repeated animations
43+
margin: inViewMargin,
44+
amount: threshold,
45+
});
46+
47+
// Track scroll progress for parallax effect
48+
const { scrollYProgress } = useScroll({
49+
target: ref,
50+
offset: ["start end", "end start"], // Better range for fade effects
51+
});
52+
53+
// Transform scroll progress to y movement
54+
const parallaxY = useTransform(
55+
scrollYProgress,
56+
[0, 0.5, 1],
57+
[offset, 0, -offset]
58+
);
59+
60+
// No opacity animation for better performance
61+
// const scrollOpacity = useTransform(
62+
// scrollYProgress,
63+
// fadeRange, // Use configurable fade range
64+
// [0, 1, 1, 1] // Fade in and stay visible (no fade out)
65+
// );
66+
67+
// Debug logging - commented out for production
68+
// useEffect(() => {
69+
// const unsubscribe = scrollYProgress.on("change", (value) => {
70+
// console.log(
71+
// "ScrollFade - scrollYProgress:",
72+
// value,
73+
// "opacity:",
74+
// scrollOpacity.get(),
75+
// "y:",
76+
// parallaxY.get()
77+
// );
78+
// });
79+
// return unsubscribe;
80+
// }, [scrollYProgress, scrollOpacity, parallaxY]);
81+
82+
const getInitialValues = () => {
83+
switch (direction) {
84+
case "down":
85+
return { y: -offset, x: 0 };
86+
case "left":
87+
return { x: offset, y: 0 };
88+
case "right":
89+
return { x: -offset, y: 0 };
90+
case "up":
91+
default:
92+
return { y: offset, x: 0 };
93+
}
94+
};
95+
96+
const initialValues = getInitialValues();
97+
98+
if (enableParallax) {
99+
// For parallax mode, use motion values directly (no fade for performance)
100+
return (
101+
<motion.div
102+
ref={ref}
103+
style={{
104+
y: parallaxY, // Only parallax movement, no opacity animation
105+
position: "relative", // Fix positioning for scroll calculation
106+
}}
107+
className={className}
108+
{...props}
109+
>
110+
{children}
111+
</motion.div>
112+
);
113+
} else {
114+
// For non-parallax mode, use regular animations
115+
return (
116+
<motion.div
117+
ref={ref}
118+
initial={{
119+
opacity: 0,
120+
...initialValues,
121+
}}
122+
animate={{
123+
opacity: isInView ? 1 : 0,
124+
x: isInView ? 0 : initialValues.x,
125+
y: isInView ? 0 : initialValues.y,
126+
}}
127+
transition={{
128+
duration,
129+
delay,
130+
ease: "easeOut",
131+
opacity: { duration: duration * 0.8 },
132+
}}
133+
className={className}
134+
{...props}
135+
>
136+
{children}
137+
</motion.div>
138+
);
139+
}
140+
}

0 commit comments

Comments
 (0)