Skip to content

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)

  1. selectedLangIdx (number | null) - Selected language index
  2. isPlaying (boolean) - Video playback state
  3. isMuted (boolean) - Audio mute state
  4. isModalOpen (boolean) - Demo popup modal
  5. isVideoLoading (boolean) - Video load state
  6. 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/image
  • next/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-medium
  • slide-horizontal, slide-vertical
  • rotate-slow, pulse-gentle, bounce-subtle
  • wave-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)

  1. videoRefs (useRef<HTMLVideoElement[]>) - Array of video refs
  2. playingVideos (Set<number>) - Indices of playing videos

Context Integration

const { playVideo, pauseVideo, isVideoPlaying } = useVideo();

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:

  1. playingVideos Set tracks active indices
  2. Event listeners on each video
  3. Continuous sync polling (1000ms)
  4. 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

  1. mobileVideoManager Integration - All 3 components
  2. Event Broadcasting - videoStarted custom events
  3. Cross-Component Coordination - Global event listeners
  4. Fallback Error Handling - Try unmuted → Try muted
  5. Loop Prevention - Explicitly set loop=false
  6. Mobile Attributes - playsInline, webkit-playsinline
  7. State Sync - Polling + event listeners
  8. 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:

  • useMemo for 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!"