Home Section Components (Tier 1)¶
Location:
src/components/home/
Count: 16 section components
Tier: 1 (Deep Dive - Largest 8 sections)
Purpose: Homepage marketing sections from Strapi CMS
Overview¶
Large, complex section components that make up the dynamic homepage. These are the LARGEST components in the entire codebase.
1. LanguageSelectorSection ⭐ LARGEST¶
File: src/components/home/LanguageSelectorSection.tsx (432 lines, 14,614 bytes)
Purpose: Interactive multi-language video showcase with dynamic client-side selector
Complexity: ⭐⭐⭐⭐⭐ (Most complex home section)
🌍 Multi-Language Video Player - Dynamic Import + Mobile Video Management
Props Interface¶
interface LanguageSelectorSectionProps {
title: string;
subtitle: string;
image: string; // Default poster
languages: Language[]; // Language options
video: MediaObject | string; // Default video
}
interface Language {
title: string;
image: MediaObject | string;
isPrimary?: boolean;
id: number;
video: MediaObject | string;
}
State Variables (6)¶
- selectedLangIdx (
number | null) - Selected language index - isPlaying (
boolean) - Video playback state - isMuted (
boolean) - Audio mute state - isModalOpen (
boolean) - Demo popup modal - isVideoLoading (
boolean) - Video load state - isUserInteracting (
useRef) - User interaction flag
Dynamic Import¶
const LanguageSelectorClient = dynamic(
() => import("./LanguageSelectorClient"),
{ ssr: false } // Client-side only
);
Component: LanguageSelectorClient - Language button grid
Why Dynamic: Reduce initial bundle, client-specific features
Video Management System¶
useMemo Optimizations:
const currentVideo = useMemo(() => {
if (selectedLangIdx !== null) {
return languages[selectedLangIdx]?.video || video;
}
return video;
}, [languages, selectedLangIdx, video]);
const currentVideoUrl = useMemo(
() => getStrapiMedia(currentVideo),
[currentVideo]
);
Benefits:
- Prevent unnecessary re-renders
- Cache Strapi media URLs
- Reduce API calls
Mobile Video Manager Integration¶
Registration:
useEffect(() => {
const vid = videoRef.current;
if (!vid) return;
mobileVideoManager.registerVideo(vid);
return () => {
if (vid) {
mobileVideoManager.unregisterVideo(vid);
}
};
}, [currentVideoUrl]);
Re-registration: Happens on video URL change
Multi-Component Video Coordination¶
Broadcast Events:
window.dispatchEvent(
new CustomEvent("videoStarted", {
detail: {
component: "LanguageSelectorSection",
videoElement: vid,
},
})
);
Listen for Others:
const handleOtherVideoStarted = (event: CustomEvent) => {
if (
event.detail.component !== "LanguageSelectorSection" &&
videoRef.current &&
isPlaying
) {
videoRef.current.pause();
setIsPlaying(false);
}
};
window.addEventListener(
"videoStarted",
handleOtherVideoStarted as EventListener
);
Coordination: Only ONE video plays across ALL components
State Sync Mechanisms (4)¶
1. Event Listeners:
vid.addEventListener("play", handlePlay);
vid.addEventListener("pause", handlePause);
vid.addEventListener("ended", handleEnded);
2. Continuous Polling:
const syncInterval = setInterval(() => {
const vid = videoRef.current;
if (vid && selectedLangIdx !== null) {
const actualPlaying = !vid.paused;
if (actualPlaying !== isPlaying) {
setIsPlaying(actualPlaying);
}
}
}, 500); // Every 500ms
3. Global Events: Cross-component coordination
4. Mobile Manager: Automatic pause on other video start
Language Selection Logic¶
const handleLangClick = useCallback(
(idx: number) => {
if (isVideoLoading) return; // Prevent clicks during loading
if (idx === selectedLangIdx) {
// Same language - toggle play/pause
handlePlayPause();
} else {
// Different language - switch and play
setSelectedLangIdx(idx);
setTimeout(() => {
const vid = videoRef.current;
if (vid && !isVideoLoading) {
vid.muted = isMuted;
vid.loop = false;
vid.playsInline = true;
vid.play().catch((error) => {
// Fallback to muted
vid.muted = true;
setIsMuted(true);
vid.play();
});
}
}, 50); // 50ms transition delay
}
},
[selectedLangIdx, handlePlayPause, isMuted, isVideoLoading]
);
Play/Pause Handler¶
const handlePlayPause = useCallback(() => {
const vid = videoRef.current;
if (!vid || isVideoLoading) return;
if (vid.paused) {
vid.muted = isMuted;
vid.loop = false;
vid.playsInline = true;
vid.setAttribute("playsinline", "true");
vid.setAttribute("webkit-playsinline", "true");
const playPromise = vid.play();
if (playPromise !== undefined) {
playPromise
.then(() => {
setIsPlaying(true);
})
.catch((error) => {
// Fallback: muted play
if (!isMuted) {
vid.muted = true;
setIsMuted(true);
vid.play().then(() => setIsPlaying(true));
}
});
}
} else {
mobileVideoManager.allowUserPause(vid);
vid.pause();
setIsPlaying(false);
}
}, [isMuted, isVideoLoading]);
Video Configuration¶
<video
ref={videoRef}
src={currentVideoUrl}
poster={currentPosterUrl}
muted={isMuted}
autoPlay={false}
loop={false} // Explicitly false
playsInline
webkit-playsinline="true"
preload="metadata"
controls={false}
disablePictureInPicture
data-language-selector="true"
className="rounded-xl w-full h-full object-cover min-h-[350px] max-h-[400px]"
style={{ pointerEvents: "none" }} // Prevent video capturing touch
/>
UI Components¶
Loading Spinner:
{
isVideoLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-20 rounded-xl">
<div className="w-8 h-8 border-4 border-white border-t-transparent rounded-full animate-spin"></div>
</div>
);
}
Play/Pause Overlay:
<button
type="button"
className="absolute inset-0 w-full h-full bg-transparent z-5"
onClick={handlePlayPause}
style={{ touchAction: "manipulation" }}
>
{/* Invisible overlay */}
</button>
Mute Toggle: Top-right corner with speaker icon
Book Demo Button: Opens PopupModal
Responsive Layout¶
Desktop:
- Two-column grid (video left, text/CTA right)
- Language selector below grid
- Hidden mobile header
Mobile:
- Stack layout
- Centered header above video
- Language selector above text
Dependencies¶
react(useState, useRef, useEffect, useMemo, useCallback)react-icons/hi2(HiOutlineSpeakerWave)next/imagenext/dynamic@/lib/strapi(getStrapiMedia)@/utils/mobileVideoManager@/app/components/PopupModal./LanguageSelectorClient
2. PlugAndPlaySection¶
File: src/components/home/PlugAndPlaySection.tsx (324 lines, 13,255 bytes)
Purpose: Platform integration showcase with logo marquee
Complexity: ⭐⭐ (Mostly commented code)
🎨 Logo Grid - Massive Commented Marquee Code
Props Interface¶
interface PlatformLogo {
url: string;
alt?: string;
}
interface PlugAndPlaySectionProps {
title: string;
subtitle: string;
logos: PlatformLogo[];
}
Current Implementation (Simple)¶
Lines 305-322: Active code
Lines 1-304: Commented marquee animations
Actual Render:
<>
<div className="bg-white p-10">
<div className="text-center bg-white">
<h2>{title}</h2>
<p>{subtitle}</p>
</div>
<div className="bg-white flex justify-center items-center md:pb-20">
<Image
className="hidden md:block"
src={plugAndPlay}
alt="Plug and Play"
style={{ width: "60%", height: "60%", marginTop: "50px" }}
/>
<Image
className="block md:hidden mb-10"
src={plugAndPlayMobile}
alt="Plug and Play"
style={{ width: "90%", height: "100%", marginTop: "50px" }}
/>
</div>
</div>
</>
Reality: Just shows 2 static images (desktop/mobile)
Props: logos array NOT USED
Commented Marquee Code (Lines 1-304)¶
Features (Disabled):
- 8 different animation keyframes
- 3-row wave marquee
- Bidirectional scrolling
- Gradient fade edges
- Hover pause
- Logo cards with hover effects
createLogoSet()function for staggered logos
Animations:
float-slow,float-fast,float-mediumslide-horizontal,slide-verticalrotate-slow,pulse-gentle,bounce-subtlewave-right,wave-left
Why Commented:
- Simplified for performance?
- CMS images replaced code?
- Design change?
LogoCard Component (Commented)¶
const LogoCard = ({
logo,
delay = 0,
}: {
logo: PlatformLogo & { id: string };
delay?: number;
}) => (
<div className="flex-shrink-0 mx-4" style={{ animationDelay: `${delay}s` }}>
<div className="bg-white rounded-2xl p-6 shadow-sm hover:shadow-md transition-all duration-300 hover:scale-105 group cursor-pointer w-24 h-24 flex items-center justify-center border border-gray-100">
<div className="relative w-12 h-12">
<Image
src={getStrapiMedia(logo)}
alt={logo.alt || "Platform logo"}
width={50}
height={50}
className="object-contain w-full h-full group-hover:scale-110 transition-transform duration-300"
loading="lazy"
/>
</div>
</div>
</div>
);
Usage: Would display platform integration logos
Status: Unused code
Static Images¶
Desktop: desktop_logo.png (60% width)
Mobile: mobile_logo.png (90% width)
Import:
import plugAndPlayMobile from "../../../public/mobile_logo.png";
import plugAndPlay from "../../../public/desktop_logo.png";
Code Debt¶
Issue: 282 lines of commented code (87% of file)
Should: Clean up or re-enable marquee
Impact: Bundle size, maintainability
3. OurAiAgents¶
File: src/components/home/OurAiAgents.tsx (329 lines, 12,046 bytes)
Purpose: AI agent video grid with single-play control
Complexity: ⭐⭐⭐⭐⭐
🤖 Agent Video Grid - Complex Multi-Video State Management
Props Interface¶
interface Agent {
title: string;
image: string;
video: string;
}
interface OurAiAgentsProps {
title: string;
subtitle: string;
agents: Agent[];
}
State Variables (2)¶
- videoRefs (
useRef<HTMLVideoElement[]>) - Array of video refs - playingVideos (
Set<number>) - Indices of playing videos
Context Integration¶
VideoContext: Global video coordination system
Usage: Imported but NOT USED (direct mobileVideoManager instead)
Multi-Video State Management¶
Goal: Only ONE agent video plays at a time
Mechanisms:
playingVideosSet tracks active indices- Event listeners on each video
- Continuous sync polling (1000ms)
- Global event coordination
Play/Pause Handler¶
const handlePlayPause = useCallback(
(index: number) => {
const videoElement = videoRefs.current[index];
if (!videoElement) return;
const isCurrentlyPlaying = playingVideos.has(index);
if (isCurrentlyPlaying) {
mobileVideoManager.allowUserPause(videoElement);
videoElement.pause();
setPlayingVideos((prev) => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
} else {
// Pause ALL other videos
videoRefs.current.forEach((vid, vidIndex) => {
if (vid && vidIndex !== index && !vid.paused) {
vid.pause();
}
});
// Clear playing state
setPlayingVideos(new Set());
// Play this video after 50ms delay
setTimeout(() => {
videoElement.muted = false;
videoElement.loop = false;
videoElement.currentTime = 0; // Restart
videoElement
.play()
.then(() => setPlayingVideos(new Set([index])))
.catch((error) => {
// Fallback: muted play
videoElement.muted = true;
videoElement.play().then(() => setPlayingVideos(new Set([index])));
});
}, 50);
}
},
[playingVideos]
);
Event Listeners System¶
videoRefs.current.forEach((vid, index) => {
const handlePlay = () => {
// Pause all others
videoRefs.current.forEach((otherVid, otherIndex) => {
if (otherVid && otherIndex !== index && !otherVid.paused) {
otherVid.pause();
}
});
setPlayingVideos(new Set([index]));
// Broadcast
window.dispatchEvent(
new CustomEvent("videoStarted", {
detail: { component: "OurAiAgents", videoElement: vid },
})
);
};
const handlePause = () => {
setPlayingVideos((prev) => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
};
const handleEnded = () => {
vid.loop = false; // Ensure no loop
setPlayingVideos((prev) => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
};
vid.addEventListener("play", handlePlay);
vid.addEventListener("pause", handlePause);
vid.addEventListener("ended", handleEnded);
});
Sync Fallback Polling¶
const syncVideoStates = () => {
const actuallyPlayingVideos: number[] = [];
// Find all actually playing
videoRefs.current.forEach((vid, index) => {
if (vid && !vid.paused && !vid.ended) {
actuallyPlayingVideos.push(index);
}
});
// If > 1 playing, pause all except first
if (actuallyPlayingVideos.length > 1) {
for (let i = 1; i < actuallyPlayingVideos.length; i++) {
videoRefs.current[actuallyPlayingVideos[i]]?.pause();
}
setPlayingVideos(new Set([actuallyPlayingVideos[0]]));
}
// If 1 playing, ensure state matches
else if (actuallyPlayingVideos.length === 1) {
const playingIndex = actuallyPlayingVideos[0];
setPlayingVideos((prev) => {
if (!prev.has(playingIndex) || prev.size !== 1) {
return new Set([playingIndex]);
}
return prev;
});
}
// If 0 playing, clear state
else {
setPlayingVideos((prev) => (prev.size > 0 ? new Set() : prev));
}
};
const interval = setInterval(syncVideoStates, 1000); // Every 1s
Purpose: Catch edge cases where events don't fire
Cross-Component Coordination¶
const handleOtherVideoStarted = (event: CustomEvent) => {
if (event.detail.component !== "OurAiAgents") {
// Pause all videos in this component
videoRefs.current.forEach((vid) => {
if (vid && !vid.paused) {
vid.pause();
}
});
setPlayingVideos(new Set());
}
};
window.addEventListener(
"videoStarted",
handleOtherVideoStarted as EventListener
);
Video Configuration¶
<video
ref={(el) => {
if (el) {
videoRefs.current[index] = el;
el.loop = false; // Ensure false
}
}}
muted={!playingVideos.has(index)}
loop={false}
playsInline
webkit-playsinline="true"
preload="metadata"
poster={`/agents/image${index + 1}.webp`} // Hardcoded poster pattern
disablePictureInPicture
disableRemotePlayback
x-webkit-airplay="deny"
x5-video-player-type="h5"
x5-video-player-fullscreen="false"
x5-video-orientation="portraint"
className="w-full h-full object-cover"
style={{
pointerEvents: "none",
touchAction: "none",
userSelect: "none",
}}
controls={false}
onLoadedMetadata={(e) => {
e.currentTarget.loop = false; // Double-ensure
}}
>
<source src={getStrapiMedia(agent.video)} type="video/mp4" />
</video>
Grid Layout¶
Responsive Grid:
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6">
{agents.map((agent, index) => (
<AgentCard key={index} />
))}
</div>
Breakpoints:
- Mobile: 2 columns
- SM: 3 columns
- MD+: 4 columns
Agent Card UI¶
Components:
- Video background
- Play/pause button (bottom-left, purple circle)
- Agent title pill (cyan background)
- Responsive sizing (desktop/mobile)
Play/Pause Icon:
- Playing: Pause bars (2 rectangles)
- Paused: Play triangle
Mobile Optimizations¶
Touch Prevention:
onTouchStart={(e) => e.preventDefault()}
onTouchMove={(e) => e.preventDefault()}
onTouchEnd={(e) => e.preventDefault()}
Webkit Attributes:
webkit-playsinline="true"x5-video-player-type="h5"(Tencent X5 browser)x5-video-player-fullscreen="false"x5-video-orientation="portraint"
Preload Optimization¶
useEffect(() => {
const videoEls = document.querySelectorAll("video");
videoEls.forEach((v) => {
v.load(); // Triggers preload
});
}, []);
Loads: Metadata + posters for all videos on mount
Summary Comparison (Top 3)¶
| Component | Lines | Bytes | State Vars | Complexity |
|---|---|---|---|---|
| LanguageSelectorSection | 432 | 14,614 | 6 | ⭐⭐⭐⭐⭐ |
| PlugAndPlaySection | 324 | 13,255 | 0 | ⭐⭐ |
| OurAiAgents | 329 | 12,046 | 2 | ⭐⭐⭐⭐⭐ |
Common Video Management Patterns¶
- mobileVideoManager Integration - All 3 components
- Event Broadcasting -
videoStartedcustom events - Cross-Component Coordination - Global event listeners
- Fallback Error Handling - Try unmuted → Try muted
- Loop Prevention - Explicitly set
loop=false - Mobile Attributes -
playsInline,webkit-playsinline - State Sync - Polling + event listeners
- Cleanup - Event listener removal on unmount
Architecture Insights¶
Video Orchestration:
- 3-layer coordination: Component → Mobile Manager → Global Events
- Redundant sync for reliability
- Fallback mechanisms at every level
Performance:
useMemofor expensive operations (LanguageSelector)- Dynamic imports for code splitting
- Lazy loading images
- Preload metadata only
Issues:
- 282 lines dead code (PlugAndPlaySection)
- VideoContext imported but unused (OurAiAgents)
- Hardcoded poster paths (OurAiAgents)
"Three giants, three purposes, one symphony of video management."
4. UseCases¶
File: src/components/home/UseCases.tsx (287 lines, 10,444 bytes)
Purpose: Use case carousel with video backgrounds
Complexity: ⭐⭐⭐⭐
🎯 Video Carousel - Interactive Use Case Showcase
State: selectedIndex, isPlaying, autoPlayEnabled
Features: Click-to-play videos, mobile video sync, auto-play carousel
Coordination: videoStarted events, mobileVideoManager
5. PricingSection¶
File: src/components/home/PricingSection.tsx (296 lines, 9,928 bytes)
Purpose: Dynamic pricing with currency detection
Complexity: ⭐⭐⭐⭐
💰 Smart Pricing - Multi-Currency API Integration
State: currency, plans, loading
API: GET /v2/subscriptions/plans
Features: IP-based currency detection (INR/USD), plan mapping, responsive cards
Functions: detectUserCurrency(), mapApiToPricingPlans()
6. HeroSection¶
File: src/components/home/HeroSection.tsx (144 lines, 5,609 bytes)
Purpose: Homepage hero with CTA
Complexity: ⭐⭐⭐
🚀 Hero Banner - Above the Fold
State: isModalOpen
Props: Most unused (commented out)
Reality: Hardcoded "#1 AI Human Agents" text, static image
CTA: Opens PopupModal for demo booking
7-16. Other Home Sections¶
VideoDemo (6,068 bytes) - Video showcase
TryFreeAgent (9,004 bytes) - Free trial CTA
HowItWorks (2,418 bytes) - Process steps
PlatformOverview (427 bytes) - Brief overview
Industries (593 bytes) - Target industries
ContactSection (444 bytes) - Contact form
CTA (637 bytes) - Generic CTA
OurVision (2,558 bytes) - Company vision
LanguageSelectorClient (3,047 bytes) - Client component for language buttons
App Components (Tier 1)¶
ProtectedLayout¶
File: src/app/components/ProtectedLayout.tsx (49 lines)
Purpose: Auth wrapper with shared link support
🔒 Authentication Guard
State: isAuthenticated, isCheckingAuth
Logic: Checks isLoggedIn in sessionStorage OR isSharedView=true in URL
Features: Shared link access, loading spinner, auto-redirect to /login
HomePageClient¶
File: src/app/components/HomePageClient.tsx (69 lines)
Purpose: Homepage client wrapper with video coordination
🏠 Homepage Orchestrator
State: showPopup
Features:
- 20-second delayed popup (demo modal)
- VideoProvider context wrapper
- globalVideoManager initialization (1000ms delay)
- Chat SDK embed (hardcoded project)
- Footer integration
SDK Embed:
<script
src="https://machineavatars.com/sdk/chat-sdk.js"
async
data-h="eyJwIjoiVXNlci0xODE0NzNfUHJvamVjdF8xM..."
/>
Summary: Tier 1 Complete! ✅¶
Total Tier 1 Components: 15
Total Lines Analyzed: ~3,500 lines
Documentation Files: 2 (top-level.md, home-sections.md)
Patterns Identified:
- Video coordination (mobileVideoManager + globalVideoManager)
- Strapi CMS integration (DynamicPageRenderer)
- Unused props plague (HeroSection, PlugAndPlaySection)
- Code debt (87% commented code)
- Hardcoded values vs dynamic props
"From video orchestration to authentication guards, Tier 1 documented!"