Skip to content

Commit 02420c8

Browse files
committed
hire-codevs
1 parent 115317a commit 02420c8

20 files changed

Lines changed: 1547 additions & 912 deletions
Lines changed: 1 addition & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1 @@
1-
"use client";
2-
3-
import {
4-
useCallback,
5-
useRef,
6-
type CSSProperties,
7-
type ReactNode,
8-
} from "react";
9-
import { animate, inView, stagger } from "framer-motion/dom";
10-
11-
import { markLandingMotionReady } from "./landing-motion-ready";
12-
13-
const VISIBLE_STYLE: CSSProperties = { opacity: 1, transform: "none" };
14-
const ALREADY_IN_VIEW_MS = 150;
15-
const CHILD_SELECTOR = "[data-progressive-child]";
16-
const EASE = [0.25, 0.46, 0.45, 0.94] as const;
17-
18-
function isIntersecting(element: HTMLElement) {
19-
const rect = element.getBoundingClientRect();
20-
return (
21-
rect.bottom > 0 &&
22-
rect.top < window.innerHeight &&
23-
rect.right > 0 &&
24-
rect.left < window.innerWidth
25-
);
26-
}
27-
28-
function prefersReducedMotion() {
29-
return (
30-
typeof window !== "undefined" &&
31-
window.matchMedia("(prefers-reduced-motion: reduce)").matches
32-
);
33-
}
34-
35-
function snapVisible(element: HTMLElement) {
36-
element.style.opacity = "1";
37-
element.style.transform = "none";
38-
element.querySelectorAll<HTMLElement>(CHILD_SELECTOR).forEach((child) => {
39-
child.style.opacity = "1";
40-
child.style.transform = "none";
41-
});
42-
}
43-
44-
function hideForEnter(element: HTMLElement, y: number) {
45-
const hidden = `translateY(${y}px)`;
46-
element.style.opacity = "0";
47-
element.style.transform = hidden;
48-
element.querySelectorAll<HTMLElement>(CHILD_SELECTOR).forEach((child) => {
49-
child.style.opacity = "0";
50-
child.style.transform = hidden;
51-
});
52-
}
53-
54-
type ProgressiveMotionProps = {
55-
children: ReactNode;
56-
className?: string;
57-
y?: number;
58-
duration?: number;
59-
amount?: number | "some" | "all";
60-
staggerChildren?: number;
61-
};
62-
63-
export default function ProgressiveMotion({
64-
children,
65-
className,
66-
y = 30,
67-
duration = 0.55,
68-
amount = 0.2,
69-
staggerChildren = 0,
70-
}: ProgressiveMotionProps) {
71-
const stopRef = useRef<(() => void) | undefined>(undefined);
72-
73-
const motionRef = useCallback(
74-
(element: HTMLDivElement | null) => {
75-
stopRef.current?.();
76-
stopRef.current = undefined;
77-
if (!element) return;
78-
79-
markLandingMotionReady();
80-
81-
let cancelled = false;
82-
let stopInView: (() => void) | undefined;
83-
const startedAt = performance.now();
84-
85-
const playEnter = () => {
86-
const children = element.querySelectorAll<HTMLElement>(CHILD_SELECTOR);
87-
88-
if (children.length > 0 && staggerChildren > 0) {
89-
element.style.opacity = "1";
90-
element.style.transform = "none";
91-
animate(
92-
children,
93-
{ opacity: 1, transform: "translateY(0px)" },
94-
{
95-
duration,
96-
ease: EASE,
97-
delay: stagger(staggerChildren),
98-
},
99-
);
100-
return;
101-
}
102-
103-
animate(
104-
element,
105-
{ opacity: 1, transform: "translateY(0px)" },
106-
{ duration, ease: EASE },
107-
);
108-
};
109-
110-
const attach = () => {
111-
if (cancelled) return;
112-
113-
if (prefersReducedMotion() || isIntersecting(element)) {
114-
snapVisible(element);
115-
return;
116-
}
117-
118-
hideForEnter(element, y);
119-
120-
stopInView = inView(
121-
element,
122-
(entry) => {
123-
const immediate =
124-
performance.now() - startedAt < ALREADY_IN_VIEW_MS ||
125-
entry.time - startedAt < ALREADY_IN_VIEW_MS;
126-
127-
if (immediate) {
128-
snapVisible(element);
129-
return;
130-
}
131-
132-
playEnter();
133-
},
134-
{ amount },
135-
);
136-
};
137-
138-
requestAnimationFrame(() => {
139-
requestAnimationFrame(attach);
140-
});
141-
142-
stopRef.current = () => {
143-
cancelled = true;
144-
stopInView?.();
145-
};
146-
},
147-
[amount, duration, staggerChildren, y],
148-
);
149-
150-
return (
151-
<div ref={motionRef} className={className} style={VISIBLE_STYLE}>
152-
{children}
153-
</div>
154-
);
155-
}
1+
export { default } from "../../_shared/ProgressiveMotion";
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
"use client";
2-
3-
export function markLandingMotionReady() {
4-
if (typeof document === "undefined") return;
5-
if (document.documentElement.dataset.landingMotion === "ready") return;
6-
document.documentElement.dataset.landingMotion = "ready";
7-
}
1+
export { markMarketingMotionReady as markLandingMotionReady } from "../../_shared/marketing-motion-ready";
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"use client";
2+
3+
import {
4+
useCallback,
5+
useRef,
6+
type CSSProperties,
7+
type ReactNode,
8+
} from "react";
9+
import { animate, inView, stagger } from "framer-motion/dom";
10+
11+
import { markMarketingMotionReady } from "./marketing-motion-ready";
12+
13+
const VISIBLE_STYLE: CSSProperties = { opacity: 1, transform: "none" };
14+
const DEFAULT_CHILD_STAGGER = 0.08;
15+
const CHILD_SELECTOR = "[data-progressive-child]";
16+
const EASE = [0.25, 0.46, 0.45, 0.94] as const;
17+
18+
function prefersReducedMotion() {
19+
return (
20+
typeof window !== "undefined" &&
21+
window.matchMedia("(prefers-reduced-motion: reduce)").matches
22+
);
23+
}
24+
25+
function snapVisible(element: HTMLElement) {
26+
element.style.opacity = "1";
27+
element.style.transform = "none";
28+
element.querySelectorAll<HTMLElement>(CHILD_SELECTOR).forEach((child) => {
29+
child.style.opacity = "1";
30+
child.style.transform = "none";
31+
});
32+
}
33+
34+
function hideForEnter(
35+
element: HTMLElement,
36+
y: number,
37+
staggerChildren: number,
38+
) {
39+
const hidden = `translateY(${y}px)`;
40+
41+
if (staggerChildren > 0) {
42+
element.style.opacity = "1";
43+
element.style.transform = "none";
44+
element.querySelectorAll<HTMLElement>(CHILD_SELECTOR).forEach((child) => {
45+
child.style.opacity = "0";
46+
child.style.transform = hidden;
47+
});
48+
return;
49+
}
50+
51+
element.style.opacity = "0";
52+
element.style.transform = hidden;
53+
}
54+
55+
function resolveStagger(
56+
element: HTMLElement,
57+
staggerChildren: number,
58+
): number {
59+
if (staggerChildren > 0) return staggerChildren;
60+
61+
const childCount = element.querySelectorAll(CHILD_SELECTOR).length;
62+
return childCount > 1 ? DEFAULT_CHILD_STAGGER : 0;
63+
}
64+
65+
type ProgressiveMotionProps = {
66+
children: ReactNode;
67+
className?: string;
68+
y?: number;
69+
duration?: number;
70+
amount?: number | "some" | "all";
71+
staggerChildren?: number;
72+
/** Play enter animation immediately on mount (hero sections). */
73+
playOnMount?: boolean;
74+
};
75+
76+
export default function ProgressiveMotion({
77+
children,
78+
className,
79+
y = 30,
80+
duration = 0.55,
81+
amount = 0.2,
82+
staggerChildren = 0,
83+
playOnMount = false,
84+
}: ProgressiveMotionProps) {
85+
const stopRef = useRef<(() => void) | undefined>(undefined);
86+
87+
const motionRef = useCallback(
88+
(element: HTMLDivElement | null) => {
89+
stopRef.current?.();
90+
stopRef.current = undefined;
91+
if (!element) return;
92+
93+
markMarketingMotionReady();
94+
95+
let cancelled = false;
96+
let stopInView: (() => void) | undefined;
97+
const effectiveStagger = resolveStagger(element, staggerChildren);
98+
99+
const playEnter = () => {
100+
const childElements =
101+
element.querySelectorAll<HTMLElement>(CHILD_SELECTOR);
102+
103+
if (childElements.length > 0 && effectiveStagger > 0) {
104+
element.style.opacity = "1";
105+
element.style.transform = "none";
106+
animate(
107+
childElements,
108+
{ opacity: 1, transform: "translateY(0px)" },
109+
{
110+
duration,
111+
ease: EASE,
112+
delay: stagger(effectiveStagger),
113+
onComplete: () => {
114+
snapVisible(element);
115+
},
116+
},
117+
);
118+
return;
119+
}
120+
121+
animate(
122+
element,
123+
{ opacity: 1, transform: "translateY(0px)" },
124+
{
125+
duration,
126+
ease: EASE,
127+
onComplete: () => {
128+
snapVisible(element);
129+
},
130+
},
131+
);
132+
};
133+
134+
const attach = () => {
135+
if (cancelled) return;
136+
137+
if (prefersReducedMotion()) {
138+
snapVisible(element);
139+
return;
140+
}
141+
142+
hideForEnter(element, y, effectiveStagger);
143+
144+
if (playOnMount) {
145+
playEnter();
146+
return;
147+
}
148+
149+
let hasEntered = false;
150+
151+
stopInView = inView(
152+
element,
153+
() => {
154+
if (hasEntered) return;
155+
hasEntered = true;
156+
stopInView?.();
157+
stopInView = undefined;
158+
playEnter();
159+
},
160+
{ amount },
161+
);
162+
};
163+
164+
requestAnimationFrame(() => {
165+
requestAnimationFrame(attach);
166+
});
167+
168+
stopRef.current = () => {
169+
cancelled = true;
170+
stopInView?.();
171+
};
172+
},
173+
[amount, duration, playOnMount, staggerChildren, y],
174+
);
175+
176+
return (
177+
<div ref={motionRef} className={className} style={VISIBLE_STYLE}>
178+
{children}
179+
</div>
180+
);
181+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use client";
2+
3+
export function markMarketingMotionReady() {
4+
if (typeof document === "undefined") return;
5+
if (document.documentElement.dataset.landingMotion === "ready") return;
6+
document.documentElement.dataset.landingMotion = "ready";
7+
}

0 commit comments

Comments
 (0)