Files
Skyheal/app/components/sections/AIDiagnosis.tsx

445 lines
19 KiB
TypeScript

"use client";
import { motion, useScroll, useTransform } from "framer-motion";
import {
Brain,
Search,
Database,
Zap,
Shield,
TrendingUp,
CheckCircle,
Activity,
Cpu,
Network,
} from "lucide-react";
import { useRef, useEffect, useState } from "react";
export default function AIDiagnosis() {
const containerRef = useRef<HTMLDivElement>(null);
const canvasContainerRef = useRef<HTMLDivElement>(null); // Ref for the wrapper of the canvas
const canvasRef = useRef<HTMLCanvasElement>(null);
const [modelLoaded, setModelLoaded] = useState(false);
const [scanProgress, setScanProgress] = useState(0);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end start"],
});
const y = useTransform(scrollYProgress, [0, 0.5], [100, 0]);
const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1]);
const scale = useTransform(scrollYProgress, [0, 0.5], [0.95, 1]); // Subtle scale
// Simulate scan progress
useEffect(() => {
const interval = setInterval(() => {
setScanProgress((prev) => (prev >= 100 ? 0 : prev + 1));
}, 50);
return () => clearInterval(interval);
}, []);
// Three.js setup
useEffect(() => {
if (typeof window === "undefined" || !canvasRef.current || !canvasContainerRef.current) return;
let animationId: number | undefined;
let scene: any;
let camera: any;
let renderer: any;
let model: any;
let controls: any;
const initThreeJS = async () => {
const THREE = await import("three");
const { GLTFLoader, OrbitControls } = await import("three-stdlib");
// Get initial dimensions from the parent container
const width = canvasContainerRef.current?.clientWidth || 500;
const height = canvasContainerRef.current?.clientHeight || 500;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
camera.position.set(0, 0, 5);
renderer = new THREE.WebGLRenderer({
canvas: canvasRef.current!,
alpha: true,
antialias: true,
});
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x000000, 0);
// Lighting
scene.add(new THREE.AmbientLight(0xffffff, 0.8));
const dirLight1 = new THREE.DirectionalLight(0x6366f1, 1.2);
dirLight1.position.set(5, 5, 5);
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0xa855f7, 0.8);
dirLight2.position.set(-5, 3, -5);
scene.add(dirLight2);
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = false;
controls.autoRotate = true;
controls.autoRotateSpeed = 1.5;
// Handle Resize - Critical for responsiveness
const handleResize = () => {
if (!canvasContainerRef.current || !camera || !renderer) return;
const newWidth = canvasContainerRef.current.clientWidth;
const newHeight = canvasContainerRef.current.clientHeight;
camera.aspect = newWidth / newHeight;
camera.updateProjectionMatrix();
renderer.setSize(newWidth, newHeight);
};
// Use ResizeObserver for robust sizing (handles container changes, not just window)
const resizeObserver = new ResizeObserver(() => handleResize());
resizeObserver.observe(canvasContainerRef.current!);
// Model loading
const loader = new GLTFLoader();
const modelURL = "/human_body.glb";
try {
const gltf = await loader.loadAsync(modelURL);
model = gltf.scene;
// Center & scale model
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const scaleFactor = 3 / maxDim;
model.scale.setScalar(scaleFactor);
model.position.sub(center.multiplyScalar(scaleFactor));
model.traverse((child: any) => {
if (child.isMesh) {
child.castShadow = false;
child.receiveShadow = false;
}
});
scene.add(model);
setModelLoaded(true);
} catch (error) {
console.warn("Using fallback geometry");
const geometry = new THREE.IcosahedronGeometry(1.5, 2);
const material = new THREE.MeshStandardMaterial({
color: 0x6366f1,
roughness: 0.3,
metalness: 0.7,
emissive: 0x3b82f6,
emissiveIntensity: 0.2,
wireframe: true, // Added wireframe for a more "tech" fallback look
});
model = new THREE.Mesh(geometry, material);
scene.add(model);
setModelLoaded(true);
}
// Animation loop
const animate = () => {
animationId = requestAnimationFrame(animate);
controls.update();
if (model) model.rotation.y += 0.002;
renderer.render(scene, camera);
};
animate();
// Clean up specifically for this closure
return () => {
resizeObserver.disconnect();
};
};
const cleanupPromise = initThreeJS();
return () => {
cleanupPromise.then((cleanup) => cleanup && cleanup());
if (animationId !== undefined) cancelAnimationFrame(animationId);
if (controls) controls.dispose();
if (renderer) renderer.dispose();
// Simplified scene disposal
if (scene) scene.clear();
};
}, []);
return (
<section
ref={containerRef}
id="diagnosis"
className="relative py-16 sm:py-20 md:py-32 lg:py-40 2xl:py-48 overflow-hidden w-full"
>
{/* Ambient glows - Optimized for mobile by reducing blur radius slightly */}
{/* <div className="absolute top-1/4 left-0 w-[300px] sm:w-[500px] lg:w-[700px] h-[300px] sm:h-[500px] lg:h-[700px] bg-cyan-500/10 rounded-full blur-[80px] sm:blur-[150px] animate-pulse" />
<div className="absolute bottom-1/4 right-0 w-[250px] sm:w-[400px] lg:w-[600px] h-[250px] sm:h-[400px] lg:h-[600px] bg-purple-500/10 rounded-full blur-[60px] sm:blur-[120px]" /> */}
{/* Subtle grid */}
<div
className="absolute inset-0 opacity-[0.02]"
style={{
backgroundImage: `linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px)`,
backgroundSize: "50px 50px",
}}
/>
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl 2xl:max-w-[1600px]">
<div className="grid lg:grid-cols-2 gap-10 sm:gap-12 lg:gap-16 xl:gap-24 2xl:gap-32 items-center">
{/* --- 3D Viewer Column --- */}
{/* Order-2 on mobile ensures text comes first if desired, but default is fine */}
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 1 }}
className="relative w-full max-w-[500px] lg:max-w-none mx-auto"
>
{/* Background Blur blob */}
<div className="absolute inset-0 rounded-[3rem] blur-3xl opacity-60 bg-gradient-to-tr from-cyan-900/20 to-purple-900/20" />
<div className="relative rounded-3xl backdrop-blur-2xl bg-black/40 border border-white/10 shadow-2xl overflow-hidden">
{/* Card Header */}
<div className="relative p-4 sm:p-6 border-b border-white/5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="relative">
<div className="w-10 h-10 rounded-xl bg-white/5 flex items-center justify-center">
<Brain className="w-5 h-5 text-cyan-400" />
</div>
<div className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-green-400 rounded-full border-2 border-black animate-pulse" />
</div>
<div>
<h3 className="text-sm font-bold text-white">Neural Engine</h3>
<p className="text-[10px] sm:text-xs text-zinc-500">Analysis Active</p>
</div>
</div>
<div className="hidden xs:flex px-3 py-1 rounded-full bg-green-500/10 border border-green-500/20">
<span className="text-[10px] sm:text-xs font-semibold text-green-400">Live Feed</span>
</div>
</div>
</div>
{/* Canvas Container - Responsive Aspect Ratio */}
<div className="relative p-4 sm:p-8">
{/* Decorative corners */}
<div className="absolute top-4 sm:top-8 left-4 sm:left-8 w-12 sm:w-16 h-12 sm:h-16 border-l-2 border-t-2 border-cyan-400/30 rounded-tl-2xl" />
<div className="absolute top-4 sm:top-8 right-4 sm:right-8 w-12 sm:w-16 h-12 sm:h-16 border-r-2 border-t-2 border-purple-400/30 rounded-tr-2xl" />
<div className="absolute bottom-4 sm:bottom-8 left-4 sm:left-8 w-12 sm:w-16 h-12 sm:h-16 border-l-2 border-b-2 border-pink-400/30 rounded-bl-2xl" />
<div className="absolute bottom-4 sm:bottom-8 right-4 sm:right-8 w-12 sm:w-16 h-12 sm:h-16 border-r-2 border-b-2 border-cyan-400/30 rounded-br-2xl" />
{/* The actual canvas wrapper */}
<div
ref={canvasContainerRef}
className="relative w-full aspect-square max-h-[400px] lg:max-h-[500px] flex items-center justify-center mx-auto"
>
<canvas
ref={canvasRef}
className="w-full h-full relative z-10 block"
/>
{/* Loading State */}
{!modelLoaded && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="space-y-4 text-center">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
>
<Brain className="w-12 h-12 sm:w-16 sm:h-16 text-cyan-400 mx-auto" />
</motion.div>
<p className="text-xs sm:text-sm text-zinc-400 font-semibold">
Initializing Network...
</p>
</div>
</div>
)}
{/* Scanning Animation Overlay */}
{modelLoaded && (
<div className="absolute inset-0 pointer-events-none overflow-hidden rounded-xl">
<motion.div
animate={{ top: ["0%", "100%", "0%"] }}
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
className="absolute left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-400 to-transparent opacity-50 shadow-[0_0_15px_rgba(34,211,238,0.5)]"
/>
</div>
)}
</div>
</div>
{/* Status footer */}
<div className="p-4 sm:p-6 space-y-4 border-t border-white/5">
<div className="space-y-2">
<div className="flex items-center justify-between text-xs">
<span className="text-zinc-400 font-semibold">Analysis Progress</span>
<span className="text-cyan-400 font-bold">{scanProgress}%</span>
</div>
<div className="h-1.5 sm:h-2 bg-white/5 rounded-full overflow-hidden">
<motion.div
style={{ width: `${scanProgress}%` }}
className="h-full bg-gradient-to-r from-cyan-500 via-purple-500 to-pink-500"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-2 sm:gap-3">
{[
{ icon: Cpu, label: "CPU Load", value: "98%", color: "text-cyan-400" },
{ icon: Network, label: "Net Load", value: "76%", color: "text-purple-400" },
{ icon: Activity, label: "Accuracy", value: "99%", color: "text-green-400" },
].map((stat, idx) => (
<div
key={idx}
className="p-2 sm:p-3 rounded-xl bg-white/[0.03] border border-white/5 text-center sm:text-left"
>
<stat.icon className={`w-3 h-3 sm:w-4 sm:h-4 ${stat.color} mb-1 sm:mb-2 mx-auto sm:mx-0`} />
<p className="hidden sm:block text-[10px] text-zinc-500 mb-0.5">{stat.label}</p>
<p className="text-xs sm:text-sm font-bold text-white">{stat.value}</p>
</div>
))}
</div>
</div>
<div className="pb-4 sm:pb-6 text-center">
<p className="text-[10px] text-zinc-600 font-medium">Interactive 3D Visualization</p>
</div>
</div>
</motion.div>
{/* --- Text / Features Column --- */}
<motion.div
style={{ y, opacity, scale }}
className="space-y-8 sm:space-y-10 lg:space-y-12 text-center lg:text-left"
>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.1 }}
className="space-y-4 sm:space-y-6"
>
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl 2xl:text-7xl font-bold leading-[1.1] tracking-tight text-white">
Neural Clinical
<br />
<span className="bg-gradient-to-r from-purple-400 via-pink-400 to-cyan-400 bg-clip-text text-transparent">
Synthesis
</span>
</h2>
<p className="text-sm sm:text-base md:text-lg lg:text-xl text-zinc-400 leading-relaxed max-w-2xl mx-auto lg:mx-0">
Enterprise-grade diagnostic assistance powered by advanced AI. Cross-reference clinical patterns against{" "}
<span className="text-white font-semibold">global medical databases</span> with{" "}
<span className="text-white font-semibold">sub-second latency</span>.
</p>
</motion.div>
{/* Feature Cards - Stack on mobile, grid on tablet+ */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.2 }}
className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6"
>
{[
{
icon: Search,
title: "Pattern Recognition",
text: "AI-powered clinical marker identification with zero-bias analysis technology.",
color: "from-cyan-500 to-cyan-500",
iconColor: "text-cyan-400",
},
{
icon: Database,
title: "Medical Database",
text: "Instant access to verified institutional medical models and research.",
color: "from-purple-500 to-pink-500",
iconColor: "text-purple-400",
},
].map((item, i) => (
<motion.div
key={i}
whileHover={{ y: -4, transition: { duration: 0.2 }}}
className="group p-5 rounded-2xl bg-white/[0.02] border border-white/5 backdrop-blur-sm hover:bg-white/[0.04] hover:border-white/10 transition-all cursor-pointer text-left"
>
<div className="flex sm:block items-start gap-4">
<div className="relative shrink-0">
<div className={`absolute inset-0 bg-gradient-to-br ${item.color} rounded-xl blur-lg opacity-0 group-hover:opacity-50 transition-opacity`} />
<div className="relative w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-gradient-to-br from-white/10 to-white/5 border border-white/10 flex items-center justify-center group-hover:border-white/20 transition-colors">
<item.icon className={`w-5 h-5 sm:w-6 sm:h-6 ${item.iconColor}`} />
</div>
</div>
<div className="space-y-1 sm:space-y-2 sm:mt-4">
<h3 className="text-base sm:text-lg font-bold text-white group-hover:text-white/90 transition-colors">
{item.title}
</h3>
<p className="text-xs sm:text-sm text-zinc-400 leading-relaxed">{item.text}</p>
</div>
</div>
</motion.div>
))}
</motion.div>
{/* Stats - Grid adjustments */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.4 }}
className="grid grid-cols-3 gap-2 sm:gap-6 pt-4 sm:pt-6 border-t border-white/5 lg:border-none"
>
{[
{ icon: Zap, value: "<500ms", label: "Query Time" },
{ icon: Shield, value: "100%", label: "Secure" },
{ icon: TrendingUp, value: "99.8%", label: "Accuracy" },
].map((stat, i) => (
<div key={i} className="text-center space-y-1 sm:space-y-2">
<div className="flex justify-center">
<div className="p-1.5 sm:p-2 rounded-lg bg-white/5 border border-white/10">
<stat.icon className="w-3.5 h-3.5 sm:w-5 sm:h-5 text-purple-400" />
</div>
</div>
<p className="text-lg sm:text-xl md:text-2xl font-bold text-white">{stat.value}</p>
<p className="text-[9px] sm:text-xs text-zinc-500 font-semibold uppercase tracking-wider">
{stat.label}
</p>
</div>
))}
</motion.div>
{/* Trust Indicators */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.5 }}
className="flex flex-wrap justify-center lg:justify-start gap-2 sm:gap-4 pt-2"
>
{["FDA Compliant", "HIPAA Certified", "ISO 27001"].map((badge, i) => (
<div
key={i}
className="flex items-center gap-1.5 sm:gap-2 px-3 py-1.5 sm:px-4 sm:py-2 rounded-full bg-white/[0.03] border border-white/10 backdrop-blur-sm"
>
<CheckCircle className="w-3 h-3 sm:w-4 sm:h-4 text-green-400" />
<span className="text-[10px] sm:text-xs text-zinc-400 font-semibold">{badge}</span>
</div>
))}
</motion.div>
</motion.div>
</div>
</div>
</section>
);
}