Ashik Siddike

MacBook Dual-Device Scroll

Heroes

About

An interactive scroll-driven dual-device showcase featuring a 3D folding MacBook Pro, iPhone companion app with Dynamic Island, live telemetry HUD cards, and real-time cloud sync beam.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds MacBook Dual-Device Scroll to your project and fits it to your theme.

Code

How MacBook Dual-Device Scroll is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import React, { useEffect, useRef, useState } from "react";
import { MotionValue, motion, useScroll, useTransform } from "framer-motion";
import {
  Activity,
  ShieldCheck,
  Smartphone,
  Zap,
  RefreshCw,
  Lock,
  Cpu,
  Monitor,
} from "lucide-react";

/**
 * Utility to combine Tailwind CSS classes.
 * Compatible with shadcn/ui cn helper.
 */
function cn(...classes: (string | undefined | null | false)[]) {
  return classes.filter(Boolean).join(" ");
}

export interface MacbookDualDeviceScrollProps {
  /** Desktop/Laptop screen image URL */
  src?: string;
  /** Mobile companion screen image URL */
  mobileSrc?: string;
  /** Whether to show the companion mobile phone */
  hasMobile?: boolean;
  /** Header title or custom React node */
  title?: string | React.ReactNode;
  /** Label for the mobile device */
  mobileLabel?: string;
  /** Label for the desktop device */
  desktopLabel?: string;
  /** Header badge text */
  badgeText?: string;
  /** Custom wrapper class */
  className?: string;
  /** Optional nested children */
  children?: React.ReactNode;
}

const DEFAULT_DESKTOP_IMG =
  "https://cdn.21st.dev/assets/mirror/02/02735516ba4eae3d5fe50bcf740adeaa19c7149f5ab3eca6323fc4cb3cb80e84.jpg";
const DEFAULT_MOBILE_IMG =
  "https://cdn.21st.dev/assets/mirror/d2/d27bd62f6660b25b43befc90f45d6d63485da028883ff32dca955e8161186c6a.jpg";

export const MacbookDualDeviceScroll = ({
  src = DEFAULT_DESKTOP_IMG,
  mobileSrc = DEFAULT_MOBILE_IMG,
  hasMobile = true,
  title,
  mobileLabel = "Native Mobile App",
  desktopLabel = "Web Command Center",
  badgeText = "Ecosystem Synced",
  className,
  children,
}: MacbookDualDeviceScrollProps) => {
  const ref = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start start", "end start"],
  });

  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    if (typeof window !== "undefined" && window.innerWidth < 768) {
      setIsMobile(true);
    }
  }, []);

  // 1. Title / Intro: smooth parallax exit as scroll engages
  const textTransform = useTransform(scrollYProgress, [0, 0.12], [0, -30], { clamp: true });
  const textOpacity = useTransform(scrollYProgress, [0, 0.12], [1, 0], { clamp: true });

  // Live Ecosystem HUD Header Banner
  const statusBannerOpacity = useTransform(scrollYProgress, [0.08, 0.18], [0, 1], { clamp: true });
  const statusBannerY = useTransform(scrollYProgress, [0.08, 0.18], [-14, 0], { clamp: true });

  // 2. 3D Keyboard Base & Hinge: Folds 90° on the table in realistic perspective, recedes with depth
  const keyboardOpacity = useTransform(scrollYProgress, [0, 0.12, 0.32], [1, 0.65, 0.15], { clamp: true });
  const hingeOpacity = useTransform(scrollYProgress, [0, 0.12, 0.28], [1, 0.5, 0], { clamp: true });
  const cameraNotchOpacity = useTransform(scrollYProgress, [0, 0.1, 0.24], [1, 0.4, 0], { clamp: true });

  // 3. Display Screen -> Morphs into sleek Tablet and Zooms Forward
  const tabletScale = useTransform(
    scrollYProgress,
    [0, 0.1, 0.34],
    [1.0, 1.04, isMobile ? 1.04 : 1.15],
    { clamp: true }
  );
  const tabletTranslateY = useTransform(
    scrollYProgress,
    [0, 0.1, 0.34],
    [0, 10, isMobile ? 14 : 22],
    { clamp: true }
  );
  const tabletBorderRadius = useTransform(
    scrollYProgress,
    [0, 0.26],
    [18, 26],
    { clamp: true }
  );
  const screenInnerRadius = useTransform(
    scrollYProgress,
    [0, 0.26],
    [12, 18],
    { clamp: true }
  );

  // 4. Companion Mobile Device: Zooms forward in tandem beside the Tablet
  const mobileScale = useTransform(
    scrollYProgress,
    [0, 0.1, 0.34],
    [1.0, 1.03, isMobile ? 1.02 : 1.12],
    { clamp: true }
  );
  const mobileTranslateY = useTransform(
    scrollYProgress,
    [0, 0.1, 0.34],
    [0, 8, isMobile ? 10 : 16],
    { clamp: true }
  );

  // 5. Real-Time Cloud Sync Bridge
  const syncOpacity = useTransform(scrollYProgress, [0.09, 0.22], [0, 1], { clamp: true });
  const syncScale = useTransform(scrollYProgress, [0.09, 0.22], [0.85, 1], { clamp: true });

  // 6. Floating Surrounding HUD Cards
  const leftCard1X = useTransform(scrollYProgress, [0.07, 0.22], [-70, 0], { clamp: true });
  const leftCard1Opacity = useTransform(scrollYProgress, [0.07, 0.2], [0, 1], { clamp: true });

  const leftCard2X = useTransform(scrollYProgress, [0.11, 0.26], [-60, 0], { clamp: true });
  const leftCard2Y = useTransform(scrollYProgress, [0.11, 0.26], [25, 0], { clamp: true });
  const leftCard2Opacity = useTransform(scrollYProgress, [0.11, 0.24], [0, 1], { clamp: true });

  const rightCard1X = useTransform(scrollYProgress, [0.07, 0.22], [70, 0], { clamp: true });
  const rightCard1Opacity = useTransform(scrollYProgress, [0.07, 0.2], [0, 1], { clamp: true });

  const rightCard2X = useTransform(scrollYProgress, [0.11, 0.26], [60, 0], { clamp: true });
  const rightCard2Y = useTransform(scrollYProgress, [0.11, 0.26], [25, 0], { clamp: true });
  const rightCard2Opacity = useTransform(scrollYProgress, [0.11, 0.24], [0, 1], { clamp: true });

  // 7. Hero Device Labels
  const labelOpacity = useTransform(scrollYProgress, [0.14, 0.28], [0, 1], { clamp: true });
  const labelY = useTransform(scrollYProgress, [0.14, 0.28], [10, 0], { clamp: true });

  return (
    <div
      ref={ref}
      className={cn(
        "relative h-[150vh] md:h-[175vh] w-full",
        className
      )}
    >
      <div className="sticky top-16 sm:top-20 md:top-24 flex flex-col items-center justify-start overflow-visible pt-1 md:pt-2">
        {/* Title / Hero intro */}
        <motion.div
          style={{
            translateY: textTransform,
            opacity: textOpacity,
          }}
          className="mb-2 md:mb-3 text-center max-w-4xl px-4 z-20"
        >
          {typeof title === "string" ? (
            <h2 className="text-3xl font-extrabold tracking-tight text-slate-900 md:text-5xl dark:text-white">
              {title}
            </h2>
          ) : (
            title || null
          )}
        </motion.div>

        {/* Live Ecosystem HUD Header Banner */}
        <motion.div
          style={{
            opacity: statusBannerOpacity,
            y: statusBannerY,
          }}
          className="mb-3 hidden sm:inline-flex items-center gap-2.5 rounded-full border border-sky-300/80 bg-white/95 dark:bg-slate-900/95 px-4 py-1.5 text-xs font-semibold text-slate-800 dark:text-slate-100 shadow-lg shadow-sky-500/10 backdrop-blur-md z-25"
        >
          <span className="relative flex h-2 w-2">
            <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
            <span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
          </span>
          <span className="font-bold text-sky-800 dark:text-sky-400 tracking-wide uppercase text-[10px]">
            {badgeText}
          </span>
          <span className="text-slate-300 dark:text-slate-700">•</span>
          <span className="text-slate-700 dark:text-slate-300">Central Web Console &amp; Mobile Companion Synced</span>
          <span className="text-slate-300 dark:text-slate-700">•</span>
          <span className="text-emerald-700 dark:text-emerald-400 font-mono text-[11px] font-bold">99.98% Active Telemetry</span>
        </motion.div>

        {/* Unified Ecosystem Stage */}
        <div className="relative flex items-center justify-center scale-[0.55] sm:scale-[0.66] md:scale-[0.74] lg:scale-[0.82] xl:scale-[0.90] 2xl:scale-[0.96] transition-transform origin-top">
          
          {/* Left Wing HUD Cards */}
          <div className="hidden xl:flex flex-col gap-4 w-64 mr-6 z-25 self-center">
            {/* HUD Card 1: Live Web Console */}
            <motion.div
              style={{
                x: leftCard1X,
                opacity: leftCard1Opacity,
              }}
              className="rounded-2xl border border-sky-200/90 dark:border-slate-800 bg-white/90 dark:bg-slate-900/90 p-4 shadow-xl shadow-sky-900/5 backdrop-blur-xl transition-all hover:border-sky-400"
            >
              <div className="flex items-center justify-between mb-2">
                <span className="text-[10px] font-extrabold tracking-wider text-sky-700 dark:text-sky-400 uppercase">
                  Live Operations
                </span>
                <span className="inline-flex items-center gap-1 rounded-full bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800 px-2 py-0.5 text-[10px] font-bold text-emerald-700 dark:text-emerald-300">
                  <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
                  Primary
                </span>
              </div>
              <div className="flex items-center gap-2.5">
                <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-sky-500/10 text-sky-600 dark:text-sky-400 shrink-0">
                  <Activity className="h-4 w-4" />
                </div>
                <div>
                  <h4 className="text-xs font-bold text-slate-900 dark:text-white leading-tight">
                    {desktopLabel}
                  </h4>
                  <p className="text-[11px] font-semibold text-sky-600 dark:text-sky-400 mt-0.5">
                    99.99% Cloud Uptime
                  </p>
                </div>
              </div>
              <p className="mt-2 text-[11px] text-slate-500 dark:text-slate-400 leading-snug">
                Real-time multi-tenant dashboard, high-throughput operations &amp; live telemetry.
              </p>
            </motion.div>

            {/* HUD Card 2: Enterprise Security */}
            <motion.div
              style={{
                x: leftCard2X,
                y: leftCard2Y,
                opacity: leftCard2Opacity,
              }}
              className="rounded-2xl border border-sky-200/90 dark:border-slate-800 bg-white/90 dark:bg-slate-900/90 p-4 shadow-xl shadow-sky-900/5 backdrop-blur-xl transition-all hover:border-indigo-400"
            >
              <div className="flex items-center justify-between mb-2">
                <span className="text-[10px] font-extrabold tracking-wider text-indigo-700 dark:text-indigo-400 uppercase">
                  Data Security
                </span>
                <span className="inline-flex items-center gap-1 rounded-full bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800 px-2 py-0.5 text-[10px] font-bold text-indigo-700 dark:text-indigo-300">
                  <Lock className="h-2.5 w-2.5 text-indigo-600" />
                  AES-256
                </span>
              </div>
              <div className="flex items-center gap-2.5">
                <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 shrink-0">
                  <ShieldCheck className="h-4 w-4" />
                </div>
                <div>
                  <h4 className="text-xs font-bold text-slate-900 dark:text-white leading-tight">
                    Zero-Trust Protection
                  </h4>
                  <p className="text-[11px] font-semibold text-indigo-600 dark:text-indigo-400 mt-0.5">
                    Granular RBAC &amp; 2FA
                  </p>
                </div>
              </div>
              <p className="mt-2 text-[11px] text-slate-500 dark:text-slate-400 leading-snug">
                Full cryptographic verification, continuous data backups &amp; ISO-aligned controls.
              </p>
            </motion.div>
          </div>

          {/* Center Stage: Laptop / Morphing Tablet Container */}
          <div className="relative flex flex-col items-center">
            
            {/* 1. Display Screen -> Morphs into sleek Tablet */}
            <motion.div
              style={{
                scale: tabletScale,
                translateY: tabletTranslateY,
                borderRadius: tabletBorderRadius,
                transformOrigin: "center center",
              }}
              className="relative z-20 w-[32.5rem] sm:w-[35rem] h-[21rem] sm:h-[22.5rem] bg-[#0c0d12] p-2.5 shadow-[0_20px_50px_-10px_rgba(0,0,0,0.5),0_0_0_1px_rgba(255,255,255,0.12)] border border-[#23242c]"
            >
              {/* Camera Notch */}
              <motion.div
                style={{ opacity: cameraNotchOpacity }}
                className="absolute top-2.5 left-1/2 -translate-x-1/2 z-30 flex items-center justify-center h-3 w-20 bg-[#0c0d12] rounded-b-md"
              >
                <div className="h-1.5 w-1.5 rounded-full bg-[#181920] ring-1 ring-sky-900/60" />
              </motion.div>

              {/* Screen Glass Area with Browser Header */}
              <motion.div
                style={{ borderRadius: screenInnerRadius }}
                className="relative h-full w-full overflow-hidden bg-[#111218] border border-white/5 flex flex-col"
              >
                {/* Active Software Console Header Bar */}
                <div className="h-6 w-full bg-[#16171e] border-b border-white/10 px-3 flex items-center justify-between shrink-0">
                  <div className="flex items-center gap-1.5">
                    <div className="h-2 w-2 rounded-full bg-[#ff5f56]" />
                    <div className="h-2 w-2 rounded-full bg-[#ffbd2e]" />
                    <div className="h-2 w-2 rounded-full bg-[#27c93f]" />
                  </div>
                  <div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-md bg-[#0c0d12] text-[10px] text-slate-300 font-mono">
                    <Lock className="h-2.5 w-2.5 text-emerald-400" />
                    <span>cloud.platform/console</span>
                  </div>
                  <div className="flex items-center gap-1 text-[10px] text-emerald-400 font-semibold">
                    <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
                    <span>LIVE</span>
                  </div>
                </div>

                <div className="relative flex-1 w-full overflow-hidden bg-slate-950">
                  <img
                    src={src}
                    alt={desktopLabel || "Desktop Console Preview"}
                    className="h-full w-full object-cover object-left-top"
                    loading="eager"
                  />
                  {/* Subtle glass reflection highlight */}
                  <div className="pointer-events-none absolute inset-0 bg-gradient-to-tr from-transparent via-white/5 to-white/10" />
                </div>
              </motion.div>
            </motion.div>

            {/* 2. Physical Hinge */}
            <motion.div
              style={{ opacity: hingeOpacity }}
              className="relative z-15 -mt-1 w-[28.5rem] sm:w-[31rem] h-[7px] bg-[#121318] border-t border-black border-b border-[#2a2b36] shadow-[inset_0_2px_3px_rgba(0,0,0,0.9)]"
            />

            {/* 3. Realistic 3D Perspective Keyboard Base */}
            <motion.div
              style={{
                opacity: keyboardOpacity,
              }}
              className="relative -mt-[1px] z-10 w-[36.5rem] sm:w-[39rem] pointer-events-none"
            >
              <PerspectiveLaptopBase />

              {/* Floor Shadow underneath laptop */}
              <div className="absolute -bottom-3 left-1/2 -translate-x-1/2 w-[37rem] h-8 bg-slate-950/30 blur-xl rounded-full -z-10" />
            </motion.div>

            {/* Tablet Label underneath when morphed */}
            <motion.div
              style={{
                opacity: labelOpacity,
                y: labelY,
              }}
              className="mt-4 hidden sm:inline-flex items-center gap-1.5 rounded-full border border-sky-200 dark:border-slate-800 bg-white/95 dark:bg-slate-900/95 px-3 py-1 text-[11px] font-semibold text-slate-800 dark:text-slate-100 shadow-sm backdrop-blur"
            >
              <span className="h-1.5 w-1.5 rounded-full bg-sky-500 animate-pulse" />
              {desktopLabel}
            </motion.div>
          </div>

          {/* Real-Time Sync Bridge between Laptop & Phone */}
          {hasMobile && (
            <motion.div
              style={{
                opacity: syncOpacity,
                scale: syncScale,
              }}
              className="hidden lg:flex flex-col items-center justify-center mx-1 xl:mx-3 z-30 self-center"
            >
              <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white/95 dark:bg-slate-900/95 border border-sky-300 dark:border-sky-800 shadow-md shadow-sky-500/15 backdrop-blur-md">
                <RefreshCw className="h-3 w-3 text-sky-600 dark:text-sky-400 animate-spin" style={{ animationDuration: "3s" }} />
                <span className="text-[10px] font-bold text-slate-800 dark:text-slate-200 uppercase tracking-wider">
                  Cloud Sync
                </span>
                <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
              </div>

              <div className="relative w-14 xl:w-20 h-5 flex items-center justify-center my-1">
                <div className="w-full h-[2px] bg-gradient-to-r from-sky-400 via-blue-500 to-emerald-400 shadow-[0_0_8px_rgba(56,189,248,0.8)]" />
                <div className="absolute h-2 w-2 rounded-full bg-sky-400 ring-2 ring-sky-300 animate-ping" />
              </div>

              <div className="text-[9px] font-bold text-sky-700 dark:text-sky-300 bg-sky-50/90 dark:bg-sky-950/60 px-2 py-0.5 rounded-md border border-sky-200/70 dark:border-sky-800/70 whitespace-nowrap">
                &lt; 35ms stream
              </div>
            </motion.div>
          )}

          {/* Right Stage: Companion Mobile Device Mockup */}
          {hasMobile && (
            <motion.div
              style={{
                scale: mobileScale,
                translateY: mobileTranslateY,
              }}
              className="relative z-20 flex flex-col items-center group self-center mb-2 mt-4 lg:mt-0"
            >
              <MobileDevice
                src={mobileSrc}
                label={mobileLabel}
                labelOpacity={labelOpacity}
                labelY={labelY}
              />
            </motion.div>
          )}

          {/* Right Wing HUD Cards */}
          <div className="hidden xl:flex flex-col gap-4 w-64 ml-6 z-25 self-center">
            {/* HUD Card 3: Mobile Field Companion */}
            <motion.div
              style={{
                x: rightCard1X,
                opacity: rightCard1Opacity,
              }}
              className="rounded-2xl border border-sky-200/90 dark:border-slate-800 bg-white/90 dark:bg-slate-900/90 p-4 shadow-xl shadow-sky-900/5 backdrop-blur-xl transition-all hover:border-emerald-400"
            >
              <div className="flex items-center justify-between mb-2">
                <span className="text-[10px] font-extrabold tracking-wider text-emerald-700 dark:text-emerald-400 uppercase">
                  Handheld Mobility
                </span>
                <span className="inline-flex items-center gap-1 rounded-full bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800 px-2 py-0.5 text-[10px] font-bold text-emerald-700 dark:text-emerald-300">
                  <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
                  Online
                </span>
              </div>
              <div className="flex items-center gap-2.5">
                <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 shrink-0">
                  <Smartphone className="h-4 w-4" />
                </div>
                <div>
                  <h4 className="text-xs font-bold text-slate-900 dark:text-white leading-tight">
                    {mobileLabel}
                  </h4>
                  <p className="text-[11px] font-semibold text-emerald-600 dark:text-emerald-400 mt-0.5">
                    Instant Push Alerts
                  </p>
                </div>
              </div>
              <p className="mt-2 text-[11px] text-slate-500 dark:text-slate-400 leading-snug">
                Native iOS &amp; Android connectivity with local offline caching and rapid field sync.
              </p>
            </motion.div>

            {/* HUD Card 4: Edge Telemetry */}
            <motion.div
              style={{
                x: rightCard2X,
                y: rightCard2Y,
                opacity: rightCard2Opacity,
              }}
              className="rounded-2xl border border-sky-200/90 dark:border-slate-800 bg-white/90 dark:bg-slate-900/90 p-4 shadow-xl shadow-sky-900/5 backdrop-blur-xl transition-all hover:border-amber-400"
            >
              <div className="flex items-center justify-between mb-2">
                <span className="text-[10px] font-extrabold tracking-wider text-amber-700 dark:text-amber-400 uppercase">
                  Edge Network
                </span>
                <span className="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 px-2 py-0.5 text-[10px] font-bold text-amber-700 dark:text-amber-300">
                  <Zap className="h-2.5 w-2.5 text-amber-600" />
                  &lt; 85ms
                </span>
              </div>
              <div className="flex items-center gap-2.5">
                <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-amber-500/10 text-amber-600 dark:text-amber-400 shrink-0">
                  <Cpu className="h-4 w-4" />
                </div>
                <div>
                  <h4 className="text-xs font-bold text-slate-900 dark:text-white leading-tight">
                    High-Velocity Pipeline
                  </h4>
                  <p className="text-[11px] font-semibold text-amber-600 dark:text-amber-400 mt-0.5">
                    Bi-Directional Stream
                  </p>
                </div>
              </div>
              <p className="mt-2 text-[11px] text-slate-500 dark:text-slate-400 leading-snug">
                Sub-second data replication across distributed edge clusters and cloud services.
              </p>
            </motion.div>
          </div>

        </div>

        {/* Compact Mobile & Tablet Telemetry Chips */}
        <div className="flex xl:hidden flex-wrap items-center justify-center gap-2 mt-3 px-4 max-w-2xl z-20">
          <motion.div
            style={{ opacity: leftCard1Opacity }}
            className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-white/95 dark:bg-slate-900/95 border border-sky-200/90 dark:border-slate-800 shadow-sm text-xs text-slate-800 dark:text-slate-100"
          >
            <Activity className="h-3.5 w-3.5 text-sky-600" />
            <span className="font-semibold">{desktopLabel}</span>
            <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse ml-1" />
          </motion.div>
          {hasMobile && (
            <motion.div
              style={{ opacity: rightCard1Opacity }}
              className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-white/95 dark:bg-slate-900/95 border border-sky-200/90 dark:border-slate-800 shadow-sm text-xs text-slate-800 dark:text-slate-100"
            >
              <Smartphone className="h-3.5 w-3.5 text-emerald-600" />
              <span className="font-semibold">{mobileLabel}</span>
              <span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse ml-1" />
            </motion.div>
          )}
          <motion.div
            style={{ opacity: leftCard2Opacity }}
            className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-white/95 dark:bg-slate-900/95 border border-sky-200/90 dark:border-slate-800 shadow-sm text-xs text-slate-800 dark:text-slate-100"
          >
            <ShieldCheck className="h-3.5 w-3.5 text-indigo-600" />
            <span className="font-semibold">AES-256 Cloud Encryption</span>
          </motion.div>
        </div>
      </div>

      {children}
    </div>
  );
};

/**
 * Realistic 3D Perspective Keyboard Base (Vector SVG)
 * Folds 90° flat on the table relative to the upright display.
 */
export const PerspectiveLaptopBase = () => {
  return (
    <svg
      viewBox="0 0 590 145"
      className="w-full h-auto drop-shadow-[0_25px_30px_rgba(0,0,0,0.45)]"
      xmlns="http://www.w3.org/2000/svg"
    >
      <defs>
        <linearGradient id="chassisGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor="#14151b" />
          <stop offset="35%" stopColor="#1b1c24" />
          <stop offset="92%" stopColor="#242531" />
          <stop offset="100%" stopColor="#181920" />
        </linearGradient>

        <linearGradient id="frontLipGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor="#3d3f4f" />
          <stop offset="50%" stopColor="#252632" />
          <stop offset="100%" stopColor="#0e0f14" />
        </linearGradient>

        <linearGradient id="wellGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor="#0a0a0e" />
          <stop offset="100%" stopColor="#111217" />
        </linearGradient>

        <linearGradient id="keyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor="#20212b" />
          <stop offset="100%" stopColor="#15161c" />
        </linearGradient>

        <linearGradient id="trackpadGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stopColor="#1c1d25" />
          <stop offset="100%" stopColor="#232430" />
        </linearGradient>
      </defs>

      {/* Main Chassis Top Surface */}
      <polygon
        points="50,0 540,0 585,134 5,134"
        fill="url(#chassisGrad)"
        stroke="#323442"
        strokeWidth="0.8"
      />

      {/* Front Aluminum Lip */}
      <path
        d="M 5,134 L 585,134 Q 588,144 578,144 L 12,144 Q 2,144 5,134 Z"
        fill="url(#frontLipGrad)"
      />

      {/* Front Centered Thumb Opening Notch */}
      <path
        d="M 260,134 L 330,134 C 328,140 322,142 318,142 L 272,142 C 268,142 262,140 260,134 Z"
        fill="#0c0d12"
        stroke="#4a4c5e"
        strokeWidth="0.5"
      />

      {/* Recessed Keyboard Well */}
      <polygon
        points="85,6 505,6 525,84 65,84"
        fill="url(#wellGrad)"
        stroke="#050508"
        strokeWidth="1"
      />

      {/* Speaker Grilles */}
      <polygon points="68,10 82,10 64,80 50,80" fill="#14151b" opacity="0.8" />
      <polygon points="508,10 522,10 540,80 526,80" fill="#14151b" opacity="0.8" />

      {/* Key Rows (Function, Numbers, QWERTY, ASDF, ZXCV, Spacebar) */}
      <g fill="url(#keyGrad)" stroke="#090a0d" strokeWidth="0.5">
        {/* Function Keys */}
        <polygon points="88,8 116,8 115,16 87,16" />
        <polygon points="120,8 148,8 147,16 119,16" />
        <polygon points="152,8 180,8 179,16 151,16" />
        <polygon points="184,8 212,8 211,16 183,16" />
        <polygon points="216,8 244,8 243,16 215,16" />
        <polygon points="248,8 276,8 275,16 247,16" />
        <polygon points="280,8 308,8 307,16 279,16" />
        <polygon points="312,8 340,8 339,16 311,16" />
        <polygon points="344,8 372,8 371,16 343,16" />
        <polygon points="376,8 404,8 403,16 375,16" />
        <polygon points="408,8 436,8 435,16 407,16" />
        <polygon points="440,8 468,8 467,16 439,16" />
        <polygon points="472,8 502,8 501,16 471,16" />
        {/* Number row */}
        <polygon points="86,18 114,18 113,28 85,28" />
        <polygon points="117,18 143,18 142,28 116,28" />
        <polygon points="146,18 172,18 171,28 145,28" />
        <polygon points="175,18 201,18 200,28 174,28" />
        <polygon points="204,18 230,18 229,28 203,28" />
        <polygon points="233,18 259,18 258,28 232,28" />
        <polygon points="262,18 288,18 287,28 261,28" />
        <polygon points="291,18 317,18 316,28 290,28" />
        <polygon points="320,18 346,18 345,28 319,28" />
        <polygon points="349,18 375,18 374,28 348,28" />
        <polygon points="378,18 404,18 403,28 377,28" />
        <polygon points="407,18 433,18 432,28 406,28" />
        <polygon points="436,18 462,18 461,28 435,28" />
        <polygon points="465,18 503,18 503,28 464,28" />
        {/* QWERTY row */}
        <polygon points="84,30 116,30 115,41 83,41" />
        <polygon points="119,30 146,30 145,41 118,41" />
        <polygon points="149,30 176,30 175,41 148,41" />
        <polygon points="179,30 206,30 205,41 178,41" />
        <polygon points="209,30 236,30 235,41 208,41" />
        <polygon points="239,30 266,30 265,41 238,41" />
        <polygon points="269,30 296,30 295,41 268,41" />
        <polygon points="299,30 326,30 325,41 298,41" />
        <polygon points="329,30 356,30 355,41 328,41" />
        <polygon points="359,30 386,30 385,41 358,41" />
        <polygon points="389,30 416,30 415,41 388,41" />
        <polygon points="419,30 446,30 445,41 418,41" />
        <polygon points="449,30 476,30 475,41 448,41" />
        <polygon points="479,30 506,30 506,41 478,41" />
        {/* ASDF row */}
        <polygon points="81,43 118,43 117,55 79,55" />
        <polygon points="121,43 149,43 148,55 120,55" />
        <polygon points="152,43 180,43 179,55 151,55" />
        <polygon points="183,43 211,43 210,55 182,55" />
        <polygon points="214,43 242,43 241,55 213,55" />
        <polygon points="245,43 273,43 272,55 244,55" />
        <polygon points="276,43 304,43 303,55 275,55" />
        <polygon points="307,43 335,43 334,55 306,55" />
        <polygon points="338,43 366,43 365,55 337,55" />
        <polygon points="369,43 397,43 396,55 368,55" />
        <polygon points="400,43 428,43 427,55 399,55" />
        <polygon points="431,43 459,43 458,55 430,55" />
        <polygon points="462,43 509,43 509,55 461,55" />
        {/* ZXCV row */}
        <polygon points="77,57 122,57 121,69 75,69" />
        <polygon points="125,57 154,57 153,69 124,69" />
        <polygon points="157,57 186,57 185,69 156,69" />
        <polygon points="189,57 218,57 217,69 188,69" />
        <polygon points="221,57 250,57 249,69 220,69" />
        <polygon points="253,57 282,57 281,69 252,69" />
        <polygon points="285,57 314,57 313,69 284,69" />
        <polygon points="317,57 346,57 345,69 316,69" />
        <polygon points="349,57 378,57 377,69 348,69" />
        <polygon points="381,57 410,57 409,69 380,69" />
        <polygon points="413,57 442,57 441,69 412,69" />
        <polygon points="445,57 474,57 473,69 444,69" />
        <polygon points="477,57 513,57 514,69 476,69" />
        {/* Spacebar row */}
        <polygon points="73,71 103,71 102,83 71,83" />
        <polygon points="106,71 133,71 132,83 105,83" />
        <polygon points="136,71 166,71 165,83 135,83" />
        <polygon points="169,71 202,71 201,83 168,83" />
        <polygon points="205,71 379,71 380,83 204,83" fill="#1b1c24" />
        <polygon points="382,71 415,71 416,83 383,83" />
        <polygon points="418,71 448,71 449,83 419,83" />
        <polygon points="451,71 475,71 476,83 452,83" />
        <polygon points="478,71 517,71 519,83 479,83" />
      </g>

      {/* Centered Glass Trackpad */}
      <polygon
        points="228,88 362,88 368,131 222,131"
        fill="url(#trackpadGrad)"
        stroke="#383949"
        strokeWidth="0.8"
      />
    </svg>
  );
};

export const MobileDevice = ({
  src,
  label,
  labelOpacity,
  labelY,
}: {
  src?: string;
  label?: string;
  labelOpacity?: MotionValue<number>;
  labelY?: MotionValue<number>;
}) => {
  return (
    <div className="flex flex-col items-center">
      {/* Smartphone Chassis - Space Black Dark Finish */}
      <div className="relative h-[22.5rem] w-[11.5rem] rounded-[38px] bg-[#0c0d10] p-[8px] shadow-[0_20px_50px_-10px_rgba(0,0,0,0.4),0_0_0_1px_rgba(255,255,255,0.12)] border border-[#27282f]">
        {/* Physical side buttons */}
        <div className="absolute -left-[3px] top-16 h-8 w-[2px] rounded-l bg-[#3f4048]" />
        <div className="absolute -left-[3px] top-26 h-8 w-[2px] rounded-l bg-[#3f4048]" />
        <div className="absolute -right-[3px] top-20 h-11 w-[2px] rounded-r bg-[#3f4048]" />

        {/* Dynamic Island Pill Notch */}
        <div className="absolute top-2.5 left-1/2 -translate-x-1/2 z-30 h-3.5 w-16 rounded-full bg-black flex items-center justify-end pr-2 border border-white/5">
          <div className="h-1.5 w-1.5 rounded-full bg-[#1c1c1e] ring-1 ring-[#2c2c2e]" />
        </div>

        {/* Screen */}
        <div className="relative h-full w-full overflow-hidden rounded-[30px] bg-[#111215]">
          {/* Mobile Top Status Row */}
          <div className="absolute top-0 left-0 right-0 z-20 px-4 pt-2.5 pb-1 flex items-center justify-between text-[10px] text-white/80 font-medium pointer-events-none">
            <span className="font-semibold tracking-tight">09:41</span>
            <div className="flex items-center gap-1.5">
              <span className="text-[9px] font-bold text-white/90">5G</span>
              <div className="h-2 w-3.5 rounded-[2px] border border-white/60 p-[1px] flex items-center">
                <div className="h-full w-full bg-emerald-400 rounded-[0.5px]" />
              </div>
            </div>
          </div>

          <img
            src={src}
            alt={label || "Mobile application screen"}
            className="h-full w-full object-cover object-top transition-transform duration-500 group-hover:scale-105"
            loading="eager"
          />
          {/* Glass sheen */}
          <div className="pointer-events-none absolute inset-0 bg-gradient-to-tr from-transparent via-white/5 to-white/10" />
        </div>

        {/* Home Indicator */}
        <div className="absolute bottom-2 left-1/2 -translate-x-1/2 z-30 h-1 w-12 rounded-full bg-white/40" />
      </div>

      {/* Desk Shadow for Mobile */}
      <div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-32 h-6 bg-slate-950/25 blur-lg rounded-full -z-10" />

      {/* Mobile Label underneath when morphed */}
      {label && (
        <motion.span
          style={{
            opacity: labelOpacity,
            y: labelY,
          }}
          className="mt-4 hidden sm:inline-flex items-center gap-1.5 rounded-full border border-sky-200 dark:border-slate-800 bg-white/95 dark:bg-slate-900/95 px-3 py-1 text-[11px] font-semibold text-slate-800 dark:text-slate-100 shadow-sm backdrop-blur"
        >
          <span className="h-1.5 w-1.5 rounded-full bg-sky-500 animate-pulse" />
          {label}
        </motion.span>
      )}
    </div>
  );
};

export default MacbookDualDeviceScroll; 

More by Ashik Siddike

View all
  • Violet Flowing Silk Shader

    Violet Flowing Silk Shader

    7

More in Heroes

View all
  • SaaS Template

    SaaS Template

    2.4K
  • Responsive Hero Banner

    Responsive Hero Banner

    2K
  • Hero

    Hero

    1.2K
  • Centered Hero with Image Fan

    Centered Hero with Image Fan

    932
  • Editorial Collage Hero

    Editorial Collage Hero

    431
  • Glass Fold

    Glass Fold

    213
  • Minimalist Hero Fashion

    Minimalist Hero Fashion

    285
  • HeroSectionWithGradient

    HeroSectionWithGradient

    336