bandsbender's picture
Upload 348 files
21a686e verified
raw
history blame
18 kB
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useState, useRef, useId } from "react";
import { motion } from "motion/react";
import Image from "next/image";
import { Home, Building, Car, Sparkles } from "lucide-react";
export function ThreeColumnBentoGrid() {
return (
<div className="mx-auto my-20 w-full max-w-7xl px-4 md:px-8 bg-white">
<h2 className="text-bold text-neutral-8000 font-sans text-xl font-bold tracking-tight md:text-4xl text-[var(--color-dark-gray-secondary)]">
Product Applications
</h2>
<p className="mt-4 max-w-lg text-sm text-[var(--color-supporting-gray)]">
Discover how Ceramic Shield enhances comfort and protection across various settings.
</p>
<div className="mt-20 grid grid-flow-col grid-cols-1 grid-rows-6 gap-2 md:grid-cols-2 md:grid-rows-3 xl:grid-cols-3 xl:grid-rows-2">
<Card className="row-span-2">
<CardContent>
<CardTitle>Residential Windows</CardTitle>
<CardDescription>
Perfect for homes, providing UV protection, glare reduction, and enhanced privacy while maintaining natural light.
</CardDescription>
</CardContent>
<CardSkeletonBody>
<SkeletonOne />
</CardSkeletonBody>
</Card>
<Card className="overflow-hidden">
<CardContent>
<CardTitle>Commercial Buildings</CardTitle>
<CardDescription>
Professional-grade window film solutions for offices, retail spaces, and commercial properties.
</CardDescription>
</CardContent>
<CardSkeletonBody>
<SkeletonTwo />
</CardSkeletonBody>
</Card>
<Card>
<CardContent>
<CardTitle>Automotive</CardTitle>
<CardDescription>
Premium window tinting for luxury vehicles, providing heat rejection and UV protection.
</CardDescription>
</CardContent>
<CardSkeletonBody className="">
<SkeletonThree />
</CardSkeletonBody>
</Card>
<Card className="row-span-2">
<CardContent>
<CardTitle>Specialty Applications</CardTitle>
<CardDescription>
Custom installations for skylights, glass doors, and unique architectural features.
</CardDescription>
</CardContent>
<CardSkeletonBody className="h-full max-h-full overflow-hidden">
<SkeletonFour />
</CardSkeletonBody>
</Card>
</div>
</div>
);
}
// Skeletons
const SkeletonOne = () => {
const homes = [
{
src: "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=400&h=300&fit=crop",
type: "Modern Villa",
},
{
src: "https://images.unsplash.com/photo-1570129477492-45c003edd2be?w=400&h=300&fit=crop",
type: "Family Home",
},
{
src: "https://images.unsplash.com/photo-1449844908441-8829872d2607?w=400&h=300&fit=crop",
type: "Contemporary House",
},
{
src: "https://images.unsplash.com/photo-1605146769289-440113cc3d00?w=400&h=300&fit=crop",
type: "Luxury Residence",
},
];
const [active, setActive] = useState(homes[0]);
const intervalTime = 2000;
useEffect(() => {
const interval = setInterval(() => {
setActive((prev) => {
const currentIndex = homes.indexOf(prev);
const nextIndex = (currentIndex + 1) % homes.length;
return homes[nextIndex];
});
}, intervalTime);
return () => clearInterval(interval);
}, []);
const Highlighter = () => {
return (
<motion.div layoutId="highlighter" className="absolute inset-0">
<div className="absolute -left-px -top-px h-4 w-4 rounded-tl-lg border-l-2 border-t-2 border-[var(--color-red-primary)] bg-transparent"></div>
<div className="absolute -right-px -top-px h-4 w-4 rounded-tr-lg border-r-2 border-t-2 border-[var(--color-red-primary)] bg-transparent"></div>
<div className="absolute -bottom-px -left-px h-4 w-4 rounded-bl-lg border-b-2 border-l-2 border-[var(--color-red-primary)] bg-transparent"></div>
<div className="absolute -bottom-px -right-px h-4 w-4 rounded-br-lg border-b-2 border-r-2 border-[var(--color-red-primary)] bg-transparent"></div>
</motion.div>
);
};
const Badge = () => {
return (
<motion.span
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
type: "spring",
stiffness: 260,
damping: 20,
delay: 1,
repeat: 0,
}}
className="absolute inset-x-0 bottom-4 m-auto h-fit w-fit rounded-md border border-neutral-100 bg-white px-2 py-1 text-xs text-black flex items-center gap-1"
>
<Home className="h-3 w-3" />
<span className="font-medium">{active.type}</span>
</motion.span>
);
};
return (
<div className="p-6">
<div className="grid grid-cols-2 justify-center gap-4">
{homes.map((home, index) => (
<motion.div
key={`home-${index}-home-skeleton-one`}
className="relative"
animate={{
opacity: active.src === home.src ? 1 : 0.5,
filter: active.src === home.src ? "none" : "grayscale(100%)",
scale: active.src === home.src ? 0.95 : 1,
}}
transition={{ duration: 1 }}
>
{active.src === home.src && <Highlighter />}
{active.src === home.src && <Badge />}
<Image
key={`home-${index}`}
src={home.src}
alt="residential window"
width={100}
height={140}
className="h-[200px] w-full rounded-lg object-cover"
/>
</motion.div>
))}
</div>
</div>
);
};
export const SkeletonTwo = () => {
const Cursor = ({
className,
textClassName,
text,
}: {
className?: string;
textClassName?: string;
text?: string;
}) => {
return (
<div
className={cn(
"absolute z-30 h-4 w-4 transition-all duration-200",
className
)}
>
<Building className="h-4 w-4 text-[var(--color-red-primary)]" />
<div
className={cn(
"absolute left-3 top-3 whitespace-pre rounded-md p-1 text-[10px] text-neutral-500 transition duration-200",
textClassName
)}
>
{text ?? "Installing"}
</div>
</div>
);
};
const Container = ({
className,
children,
}: {
className?: string;
children: React.ReactNode;
}) => {
return (
<div
className={cn(
"relative z-20 w-fit rounded-lg border border-neutral-100 p-0.5 shadow-sm",
className
)}
>
<div
className={cn(
"flex h-10 items-center justify-center rounded-[5px] bg-neutral-100 px-2 text-xs text-neutral-600 shadow-lg"
)}
>
{children}
</div>
</div>
);
};
const CircleWithLine = ({ className }: { className?: string }) => {
const id = useId();
return (
<div
className={cn("flex flex-col items-center justify-center", className)}
>
<div
className={cn(
`h-3 w-3 rounded-full border border-neutral-200 bg-neutral-100 shadow-[2px_4px_16px_0px_rgba(248,248,248,0.06)_inset]`
)}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="2"
height="265"
viewBox="0 0 2 265"
fill="none"
className="h-full text-[var(--color-red-primary)]"
>
<path
d="M1 265V1"
stroke={`url(#${id})`}
strokeOpacity="0.3"
strokeWidth="1.5"
strokeLinecap="round"
/>
<defs>
<linearGradient
id={id}
x1="1.5"
y1="1"
x2="1.5"
y2="265"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="currentColor" stopOpacity="0.1" />
<stop
offset="0.530519"
stopColor="currentColor"
stopOpacity="0.8"
/>
<stop offset="1" stopColor="currentColor" stopOpacity="0.1" />
</linearGradient>
</defs>
</svg>
</div>
);
};
return (
<div className="group/bento relative flex h-40 flex-col overflow-hidden px-2 py-8">
<div className="absolute inset-0 flex h-full w-full flex-shrink-0 flex-row justify-center gap-4">
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
<CircleWithLine />
</div>
<Container className="ml-20 mt-2">Site Assessment</Container>
<Container className="ml-40 mt-4 transition duration-200 group-hover/bento:scale-[1.02] group-hover/bento:border-[var(--color-red-primary)]">
Professional Installation
</Container>
<Cursor
className="left-20 top-20 group-hover/bento:left-32"
textClassName="group-hover/bento:text-[var(--color-red-primary)]"
/>
</div>
);
};
export const SkeletonThree = () => {
const Beam = ({
className,
delay,
duration,
width = 600,
...svgProps
}: {
className?: string;
delay?: number;
duration?: number;
width?: number;
} & React.ComponentProps<typeof motion.svg>) => {
const id = useId();
return (
<motion.svg
width={width ?? "600"}
height="1"
viewBox={`0 0 ${width ?? "600"} 1`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={cn("absolute inset-x-0 w-full", className)}
{...svgProps}
>
<motion.path
d={`M0 0.5H${width ?? "600"}`}
stroke={`url(#svgGradient-${id})`}
/>
<defs>
<motion.linearGradient
id={`svgGradient-${id}`}
gradientUnits="userSpaceOnUse"
initial={{ x1: "0%", x2: "-5%", y1: 0, y2: 0 }}
animate={{ x1: "110%", x2: "105%", y1: 0, y2: 0 }}
transition={{
duration: duration ?? 2,
ease: "linear",
repeat: Infinity,
delay: delay,
repeatDelay: Math.random() * (2 - 1) + 1,
}}
>
<stop stopColor="#DC2626" stopOpacity="0" />
<stop stopColor="#DC2626" />
<stop offset="1" stopColor="#DC2626" stopOpacity="0" />
</motion.linearGradient>
</defs>
</motion.svg>
);
};
const firstImageTextVariants = {
initial: { y: 20, opacity: 0 },
animate: { y: -30, opacity: 1 },
};
return (
<motion.div className="relative pb-4">
<Beam className="left-0 top-2 w-full" delay={0.5} />
<Beam className="left-0 top-4 w-full" delay={0} />
<Beam className="left-0 top-6 w-full" delay={1} />
<Beam className="left-0 top-6 w-full" delay={3} />
<Beam className="left-0 top-10 w-full" delay={2} />
<Beam className="left-0 top-12 w-full" delay={3} />
<Beam className="left-0 top-14 w-full" delay={0.2} />
<Beam className="left-0 top-16 w-full" delay={1.2} />
<Beam className="left-0 top-20 w-full" delay={2.3} />
<div className="relative z-20 mx-auto grid max-w-[calc(100%-4rem)] grid-cols-2 justify-center gap-4">
<div className="relative rounded-lg border border-neutral-200 bg-neutral-100 p-2">
<motion.div
variants={firstImageTextVariants}
className="absolute inset-x-0 bottom-0 mx-auto w-fit rounded-md bg-white px-2 py-1 text-xs text-black opacity-0 flex items-center gap-1"
>
<Car className="h-3 w-3" />
Luxury Sedan
</motion.div>
<Image
src="https://images.unsplash.com/photo-1555215695-3004980ad54e?w=400&h=300&fit=crop"
alt="luxury car"
width={100}
height={100}
className="h-full w-full rounded-lg object-cover"
/>
</div>
<div className="relative rounded-lg border border-neutral-200 bg-neutral-100 p-2">
<motion.div
variants={firstImageTextVariants}
transition={{ delay: 0.5 }}
className="absolute inset-x-0 bottom-0 mx-auto w-fit rounded-md bg-white px-2 py-1 text-xs text-black opacity-0 flex items-center gap-1"
>
<Car className="h-3 w-3" />
SUV Premium
</motion.div>
<Image
src="https://images.unsplash.com/photo-1549317661-bd32c8ce0db2?w=400&h=300&fit=crop"
alt="luxury suv"
width={100}
height={100}
className="h-full w-full rounded-lg object-cover"
/>
</div>
</div>
</motion.div>
);
};
export const SkeletonFour = ({}: {}) => {
const [activeFeature, setActiveFeature] = useState(0);
const features = [
{ icon: Sparkles, name: "Skylights", description: "Custom solutions" },
{ icon: Building, name: "Glass Doors", description: "Architectural grade" },
{ icon: Home, name: "Conservatories", description: "Climate control" },
];
useEffect(() => {
const interval = setInterval(() => {
setActiveFeature((prev) => (prev + 1) % features.length);
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<div className="h-full w-full overflow-hidden">
<motion.div
whileHover="animate"
className="relative block h-full w-full overflow-hidden rounded-none"
>
<div className="absolute inset-0 [mask-image:linear-gradient(to_top,white,transparent)]">
<GridPattern />
</div>
<div className="flex h-full max-h-96 flex-col items-center justify-center overflow-hidden [mask-image:linear-gradient(to_top,transparent,white,transparent)]">
<div className="relative mx-auto w-full max-w-xs">
{features.map((feature, idx) => (
<motion.div
key={`feature-${idx}`}
className={cn(
"relative z-40 mx-auto mt-4 flex w-full flex-col items-center justify-center overflow-hidden rounded-md bg-white p-6 shadow-lg",
idx === activeFeature ? "opacity-100" : "opacity-40"
)}
animate={{
scale: idx === activeFeature ? 1.05 : 1,
opacity: idx === activeFeature ? 1 : 0.4,
}}
transition={{ duration: 0.5 }}
>
<feature.icon className="h-8 w-8 text-[var(--color-red-primary)] mb-2" />
<motion.p
className="text-sm font-medium text-neutral-700"
>
{feature.name}
</motion.p>
<motion.p
className="text-xs text-neutral-500 mt-1"
>
{feature.description}
</motion.p>
</motion.div>
))}
</div>
</div>
</motion.div>
</div>
);
};
// Card structure
const CardSkeletonBody = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return <div className={cn("", className)}>{children}</div>;
};
const CardContent = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return <div className={cn("p-6", className)}>{children}</div>;
};
const CardTitle = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<h3
className={cn(
"font-sans text-sm font-medium tracking-tight text-[var(--color-dark-gray-secondary)]",
className
)}
>
{children}
</h3>
);
};
const CardDescription = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<h3
className={cn(
"mt-2 max-w-xs font-sans text-sm font-normal tracking-tight text-[var(--color-supporting-gray)]",
className
)}
>
{children}
</h3>
);
};
const Card = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<motion.div
whileHover="animate"
className={cn(
"group isolate flex flex-col overflow-hidden rounded-2xl bg-white shadow-[0_1px_1px_rgba(0,0,0,0.05),0_4px_6px_rgba(34,42,53,0.04),0_24px_68px_rgba(47,48,55,0.05),0_2px_3px_rgba(0,0,0,0.04)]",
className
)}
>
{children}
</motion.div>
);
};
// Variants
const mainVariant = {
initial: { x: 0, y: 0 },
animate: { x: 20, y: -20, opacity: 0.9 },
};
const secondaryVariant = { initial: { opacity: 0 }, animate: { opacity: 1 } };
// Utils
export function GridPattern() {
const columns = 20;
const rows = 5;
return (
<div className="flex flex-shrink-0 scale-110 flex-wrap items-center justify-center gap-x-px gap-y-px bg-gray-100">
{Array.from({ length: rows }).map((_, row) =>
Array.from({ length: columns }).map((_, col) => {
const index = row * columns + col;
return (
<div
key={`${col}-${row}-grid-pattern`}
className={`flex h-10 w-10 flex-shrink-0 rounded-[2px] ${
index % 2 === 0
? "bg-gray-50"
: "bg-gray-50 shadow-[0px_0px_1px_3px_rgba(255,255,255,1)_inset]"
}`}
/>
);
})
)}
</div>
);
}