Skip to content

Commit 98d38ea

Browse files
feat(HomeClient): enhance featured project interaction with auto-timer and user engagement tracking
1 parent f48d67a commit 98d38ea

File tree

2 files changed

+130
-15
lines changed

2 files changed

+130
-15
lines changed

src/app/globals.css

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,6 +3237,68 @@ textarea {
32373237
}
32383238
}
32393239

3240+
@media (max-width: 600px) {
3241+
.hero-name {
3242+
font-size: 20px;
3243+
letter-spacing: 0.06em;
3244+
}
3245+
3246+
.hero-role,
3247+
.hero-hint {
3248+
font-size: 10px;
3249+
letter-spacing: 0.08em;
3250+
}
3251+
3252+
.section-eyebrow {
3253+
font-size: 10px;
3254+
letter-spacing: 0.18em;
3255+
}
3256+
3257+
.section-title {
3258+
font-size: clamp(20px, 6vw, 26px);
3259+
}
3260+
3261+
.section-subtitle {
3262+
font-size: 12px;
3263+
}
3264+
3265+
.markdown-p,
3266+
.markdown-li,
3267+
.about-card p,
3268+
.about-card li {
3269+
font-size: 12px;
3270+
line-height: 1.55;
3271+
}
3272+
3273+
.mobile-menu-toggle,
3274+
.dock-button,
3275+
.featured-card,
3276+
.capability-card,
3277+
.contact-card,
3278+
.about-card,
3279+
.project-card {
3280+
box-shadow: none;
3281+
}
3282+
3283+
.sidebar-backdrop {
3284+
-webkit-backdrop-filter: none;
3285+
backdrop-filter: none;
3286+
}
3287+
3288+
.hero-mobile-keys {
3289+
animation: none;
3290+
}
3291+
3292+
.hero-shell,
3293+
.section {
3294+
background: var(--content-bg);
3295+
}
3296+
3297+
body::after {
3298+
opacity: 0.04;
3299+
}
3300+
}
3301+
32403302
@media (prefers-reduced-motion: reduce) {
32413303
* {
32423304
animation-duration: 0.01ms !important;

src/components/HomeClient.tsx

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,11 @@ export default function HomeClient({
15731573

15741574
const featuredCount = featuredProjects.length;
15751575
const [activeFeatured, setActiveFeatured] = useState(0);
1576+
const featuredAutoTimerRef = useRef<number | null>(null);
1577+
const featuredAutoScheduleRef = useRef<((delay: number) => void) | null>(
1578+
null,
1579+
);
1580+
const featuredLastInteractionRef = useRef(0);
15761581
const clampFeaturedIndex = useCallback(
15771582
(index: number) => {
15781583
if (featuredCount <= 0) {
@@ -1594,22 +1599,55 @@ export default function HomeClient({
15941599
},
15951600
[clampFeaturedIndex],
15961601
);
1602+
const registerFeaturedInteraction = useCallback(() => {
1603+
featuredLastInteractionRef.current = Date.now();
1604+
if (featuredAutoScheduleRef.current) {
1605+
featuredAutoScheduleRef.current(12000);
1606+
}
1607+
}, []);
15971608
const featuredAutoDir = useRef(1);
15981609
useEffect(() => {
15991610
if (reducedMotion || featuredCount <= 1) {
16001611
return;
16011612
}
1602-
const timer = window.setInterval(() => {
1603-
setActiveFeatured((prev) => {
1604-
if (prev >= featuredCount - 1) {
1605-
featuredAutoDir.current = -1;
1606-
} else if (prev <= 0) {
1607-
featuredAutoDir.current = 1;
1613+
let active = true;
1614+
const clearTimer = () => {
1615+
if (featuredAutoTimerRef.current !== null) {
1616+
window.clearTimeout(featuredAutoTimerRef.current);
1617+
featuredAutoTimerRef.current = null;
1618+
}
1619+
};
1620+
const schedule = (delay: number) => {
1621+
clearTimer();
1622+
featuredAutoTimerRef.current = window.setTimeout(() => {
1623+
if (!active) {
1624+
return;
16081625
}
1609-
return clampFeaturedIndex(prev + featuredAutoDir.current);
1610-
});
1611-
}, 5200);
1612-
return () => window.clearInterval(timer);
1626+
const now = Date.now();
1627+
const sinceInteraction =
1628+
now - (featuredLastInteractionRef.current || 0);
1629+
if (sinceInteraction < 12000) {
1630+
schedule(12000 - sinceInteraction);
1631+
return;
1632+
}
1633+
setActiveFeatured((prev) => {
1634+
if (prev >= featuredCount - 1) {
1635+
featuredAutoDir.current = -1;
1636+
} else if (prev <= 0) {
1637+
featuredAutoDir.current = 1;
1638+
}
1639+
return clampFeaturedIndex(prev + featuredAutoDir.current);
1640+
});
1641+
schedule(5200);
1642+
}, delay);
1643+
};
1644+
featuredAutoScheduleRef.current = schedule;
1645+
schedule(5200);
1646+
return () => {
1647+
active = false;
1648+
featuredAutoScheduleRef.current = null;
1649+
clearTimer();
1650+
};
16131651
}, [clampFeaturedIndex, featuredCount, reducedMotion]);
16141652
const safeActiveFeatured =
16151653
featuredCount > 0 ? clampFeaturedIndex(activeFeatured) : 0;
@@ -2412,7 +2450,10 @@ export default function HomeClient({
24122450
name="featured-slider"
24132451
id={`featured-slide-${index + 1}`}
24142452
checked={safeActiveFeatured === index}
2415-
onChange={() => setFeaturedIndex(index)}
2453+
onChange={() => {
2454+
registerFeaturedInteraction();
2455+
setFeaturedIndex(index);
2456+
}}
24162457
aria-label={`${t.sections.featured.title} ${index + 1}`}
24172458
/>
24182459
))}
@@ -2437,7 +2478,10 @@ export default function HomeClient({
24372478
aria-label={`${t.sections.featured.title} ${
24382479
activeFeatured
24392480
}/${featuredCount}`}
2440-
onClick={() => shiftFeaturedIndex(-1)}
2481+
onClick={() => {
2482+
registerFeaturedInteraction();
2483+
shiftFeaturedIndex(-1);
2484+
}}
24412485
/>
24422486
) : null}
24432487
{featuredProjects[activeFeatured + 1] ? (
@@ -2447,7 +2491,10 @@ export default function HomeClient({
24472491
aria-label={`${t.sections.featured.title} ${
24482492
activeFeatured + 2
24492493
}/${featuredCount}`}
2450-
onClick={() => shiftFeaturedIndex(1)}
2494+
onClick={() => {
2495+
registerFeaturedInteraction();
2496+
shiftFeaturedIndex(1);
2497+
}}
24512498
/>
24522499
) : null}
24532500
</div>
@@ -2458,7 +2505,10 @@ export default function HomeClient({
24582505
aria-label={`${t.sections.featured.title} ${
24592506
safeActiveFeatured + 1
24602507
}/${featuredCount}`}
2461-
onClick={() => shiftFeaturedIndex(-1)}
2508+
onClick={() => {
2509+
registerFeaturedInteraction();
2510+
shiftFeaturedIndex(-1);
2511+
}}
24622512
disabled={safeActiveFeatured <= 0}>
24632513
<svg viewBox="0 0 24 24" aria-hidden="true">
24642514
<path
@@ -2485,7 +2535,10 @@ export default function HomeClient({
24852535
aria-label={`${t.sections.featured.title} ${
24862536
safeActiveFeatured + 1
24872537
}/${featuredCount}`}
2488-
onClick={() => shiftFeaturedIndex(1)}
2538+
onClick={() => {
2539+
registerFeaturedInteraction();
2540+
shiftFeaturedIndex(1);
2541+
}}
24892542
disabled={safeActiveFeatured >= featuredCount - 1}>
24902543
<svg viewBox="0 0 24 24" aria-hidden="true">
24912544
<path

0 commit comments

Comments
 (0)