Files
cfw-autumn/apps/website/components/features.tsx
2026-04-27 16:13:15 +01:00

81 lines
3.8 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useRef, useState } from "react";
import { featuresData } from "@/app/constant";
import type { PixelAnimationHandle } from "@/lib/types";
import { FeatureIconAnimation } from "./feature-icon-animation";
function FeatureCard({ feature }: { feature: (typeof featuresData)[number] }) {
// Only mount the hover-effect video on pointer:hover devices. The video is
// already CSS-hidden on mobile via `hidden md:block`, but CSS `display:none`
// does NOT stop `<video autoPlay>` from fetching, so without this every
// mobile visitor downloads one copy per feature card (≈ 9 × the file).
const [isHoverDevice, setIsHoverDevice] = useState(false);
useEffect(() => {
setIsHoverDevice(window.matchMedia("(hover: hover)").matches);
}, []);
const iconRef = useRef<PixelAnimationHandle | null>(null);
return (
<div
onMouseEnter={() => isHoverDevice && iconRef.current?.play()}
onMouseLeave={() => isHoverDevice && iconRef.current?.reverse()}
className="group relative flex px-4 md:px-4 flex-col justify-between p-6 bg-[#0F0F0F] min-h-[200px] md:min-h-[280px] border-r border-b border-[#292929] overflow-hidden cursor-pointer"
>
{isHoverDevice && (
<div className="absolute inset-0 opacity-0 translate-y-6 md:group-hover:opacity-100 md:group-hover:translate-y-0 pointer-events-none z-0 hidden md:block">
<video
src="/images/features/pixel effect.webm"
autoPlay
loop
muted
playsInline
className="w-full h-full object-cover"
/>
</div>
)}
{/* Hover Gradient Overlay */}
<div className="absolute inset-x-0 bottom-0 h-[70%] bg-[linear-gradient(to_bottom,rgba(10,10,10,0)_0%,rgba(135,82,250,0.15)_40%,rgba(135,82,250,0.45)_70%,rgba(135,82,250,0.85)_90%)] opacity-0 md:group-hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
<div className="relative z-10 flex flex-col h-full gap-[42px] md:gap-24.5">
<feature.Icon className="w-6 h-6 text-white md:hidden" />
<div className="hidden md:block">
<FeatureIconAnimation Icon={feature.Icon} ref={iconRef} />
</div>
<div className="flex flex-col gap-2 md:gap-4.5">
<h3 className="text-white font-normal tracking-[-5%] leading-6 text-[20px] md:text-[24px] font-sans">
{feature.title}
</h3>
<p className="text-[#FFFFFF99] w-full font-light tracking-[-2%] text-[14px] md:text-[16px] font-sans leading-[18px] md:leading-[20px] pr-2 md:pr-4 text-pretty">
{feature.description}
</p>
</div>
</div>
</div>
);
}
export default function Features() {
return (
<section className="bg-[#000000] w-full">
<div className="flex flex-col px-4 md:px-4 sm:px-8 py-12 md:py-16 xl:px-22.75 items-start">
<h2 className="text-[30px] md:text-[40px] leading-[32px] md:leading-[44px] font-sans tracking-[-4%]">
<div className="text-[#FFFFFF99]">Everything you need</div>
<div className="text-white">for AI and usage billing.</div>
</h2>
<div className="mt-4 text-[16px] tracking-[-2%] font-sans font-light text-[#FFFFFF99] leading-[20px] max-w-[420px]">
Your entire billing infrastructure,{" "}
<span className="text-white">fully managed.</span>
</div>
</div>
<div className="border-t border-[#292929] w-full" />
<div className="grid grid-cols-1 gap-[-3px] md:grid-cols-2 lg:grid-cols-3 xl:px-22.75 border-[#292929] [&>*:last-child]:border-b-0 md:[&>*:nth-last-child(-n+2)]:border-b-0 lg:[&>*:nth-last-child(-n+3)]:border-b-0 *:border-l md:[&>*:nth-child(2n)]:border-l-0 lg:[&>*:nth-child(3n+1)]:border-l lg:[&>*:nth-child(3n+2)]:border-l-0 lg:[&>*:nth-child(3n)]:border-l-0">
{featuresData.map((feature, i) => (
<FeatureCard key={i} feature={feature} />
))}
{/* <div className="bg-[#0f0f0f] w-full h-full min-h-[280px] hidden lg:block border-r border-b border-[#292929]" /> */}
</div>
</section>
);
}