446 lines
18 KiB
TypeScript
446 lines
18 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 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.9, 1]);
|
|
|
|
// 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) 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");
|
|
|
|
scene = new THREE.Scene();
|
|
camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
|
|
camera.position.set(0, 0, 5);
|
|
|
|
renderer = new THREE.WebGLRenderer({
|
|
canvas: canvasRef.current!,
|
|
alpha: true,
|
|
antialias: true,
|
|
});
|
|
renderer.setSize(500, 500);
|
|
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;
|
|
|
|
// 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));
|
|
|
|
// Performance: disable shadows
|
|
model.traverse((child: any) => {
|
|
if (child.isMesh) {
|
|
child.castShadow = false;
|
|
child.receiveShadow = false;
|
|
}
|
|
});
|
|
|
|
scene.add(model);
|
|
setModelLoaded(true);
|
|
} catch (error) {
|
|
console.error("Failed to load GLB model → using fallback", error);
|
|
|
|
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,
|
|
});
|
|
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();
|
|
};
|
|
|
|
initThreeJS();
|
|
|
|
// Cleanup
|
|
return () => {
|
|
if (animationId !== undefined) {
|
|
cancelAnimationFrame(animationId);
|
|
}
|
|
if (controls) controls.dispose();
|
|
if (renderer) renderer.dispose();
|
|
if (scene) {
|
|
scene.traverse((obj: any) => {
|
|
if (obj.geometry) obj.geometry.dispose();
|
|
if (obj.material) {
|
|
if (Array.isArray(obj.material)) {
|
|
obj.material.forEach((m: any) => m.dispose());
|
|
} else {
|
|
obj.material.dispose();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<section
|
|
ref={containerRef}
|
|
id="diagnosis"
|
|
className="relative py-20 md:py-32 lg:py-40 overflow-hidden"
|
|
>
|
|
{/* Ambient glows */}
|
|
<div className="absolute top-1/4 left-0 w-[700px] h-[700px] bg-blue-500/10 rounded-full blur-[150px] animate-pulse" />
|
|
<div className="absolute bottom-1/4 right-0 w-[600px] h-[600px] bg-purple-500/10 rounded-full 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">
|
|
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 xl:gap-24 items-center">
|
|
{/* 3D Viewer Column */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -50 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 1 }}
|
|
className="relative"
|
|
>
|
|
<div className="absolute inset-0 rounded-[3rem] blur-3xl opacity-60" />
|
|
|
|
<div className="relative rounded-3xl backdrop-blur-2xl shadow-2xl overflow-hidden">
|
|
{/* Header */}
|
|
<div className="relative 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 flex items-center justify-center">
|
|
<Brain className="w-5 h-5 text-blue-400" />
|
|
</div>
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full border-2 border-black animate-pulse" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-bold text-white">Neural Diagnostic Engine</h3>
|
|
<p className="text-xs text-zinc-500">Real-time Analysis Active</p>
|
|
</div>
|
|
</div>
|
|
<div className="px-3 py-1 rounded-full bg-green-500/10 border border-green-500/20">
|
|
<span className="text-xs font-semibold text-green-400">Live</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Canvas area */}
|
|
<div className="relative p-6 sm:p-8">
|
|
<div className="absolute top-8 left-8 w-16 h-16 border-l-2 border-t-2 border-blue-400/30 rounded-tl-2xl" />
|
|
<div className="absolute top-8 right-8 w-16 h-16 border-r-2 border-t-2 border-purple-400/30 rounded-tr-2xl" />
|
|
<div className="absolute bottom-8 left-8 w-16 h-16 border-l-2 border-b-2 border-pink-400/30 rounded-bl-2xl" />
|
|
<div className="absolute bottom-8 right-8 w-16 h-16 border-r-2 border-b-2 border-cyan-400/30 rounded-br-2xl" />
|
|
|
|
<div className="relative aspect-square flex items-center justify-center">
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="w-full h-full max-w-[500px] max-h-[500px] relative z-10"
|
|
/>
|
|
|
|
{!modelLoaded && (
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="space-y-4 text-center">
|
|
<motion.div
|
|
animate={{ rotate: 360 }}
|
|
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
>
|
|
<Brain className="w-16 h-16 text-blue-400 mx-auto" />
|
|
</motion.div>
|
|
<p className="text-sm text-zinc-400 font-semibold">
|
|
Initializing Neural Network...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{modelLoaded && (
|
|
<>
|
|
<motion.div
|
|
animate={{ y: [-250, 250] }}
|
|
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
|
className="absolute inset-x-0 h-px bg-gradient-to-r from-transparent via-blue-400 to-transparent opacity-50"
|
|
/>
|
|
<motion.div
|
|
animate={{ y: [250, -250] }}
|
|
transition={{ duration: 4, repeat: Infinity, ease: "linear", delay: 1.5 }}
|
|
className="absolute inset-x-0 h-px bg-gradient-to-r from-transparent via-purple-400 to-transparent opacity-30"
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status footer */}
|
|
<div className="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-blue-400 font-bold">{scanProgress}%</span>
|
|
</div>
|
|
<div className="h-2 bg-white/5 rounded-full overflow-hidden">
|
|
<motion.div
|
|
style={{ width: `${scanProgress}%` }}
|
|
className="h-full bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-3">
|
|
{[
|
|
{ icon: Cpu, label: "Processing", value: "98.4%", color: "text-blue-400" },
|
|
{ icon: Network, label: "Neural Load", value: "76.2%", color: "text-purple-400" },
|
|
{ icon: Activity, label: "Accuracy", value: "99.8%", color: "text-green-400" },
|
|
].map((stat, idx) => (
|
|
<motion.div
|
|
key={idx}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: idx * 0.1 }}
|
|
className="p-3 rounded-xl bg-white/[0.03] border border-white/5"
|
|
>
|
|
<stat.icon className={`w-4 h-4 ${stat.color} mb-2`} />
|
|
<p className="text-xs text-zinc-500 mb-1">{stat.label}</p>
|
|
<p className="text-sm font-bold text-white">{stat.value}</p>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="px-6 pb-6">
|
|
<p className="text-center text-xs text-zinc-600 font-medium flex items-center justify-center gap-2">
|
|
<span className="w-1 h-1 rounded-full bg-zinc-600 animate-pulse" />
|
|
Drag to rotate • Interactive 3D visualization
|
|
<span className="w-1 h-1 rounded-full bg-zinc-600 animate-pulse" />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Text / Features Column */}
|
|
<motion.div
|
|
style={{ y, opacity, scale }}
|
|
className="space-y-10 lg:space-y-12"
|
|
>
|
|
<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-6"
|
|
>
|
|
<h2 className="text-4xl sm:text-5xl md:text-6xl lg: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-blue-400 bg-clip-text text-transparent">
|
|
Synthesis
|
|
</span>
|
|
</h2>
|
|
<p className="text-base sm:text-lg lg:text-xl text-zinc-400 leading-relaxed max-w-2xl">
|
|
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> and unparalleled accuracy.
|
|
</p>
|
|
</motion.div>
|
|
|
|
{/* Feature Cards */}
|
|
<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-blue-500 to-cyan-500",
|
|
iconColor: "text-blue-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}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.6, delay: 0.3 + i * 0.1 }}
|
|
whileHover={{
|
|
y: -4,
|
|
transition: { duration: 0.2 },
|
|
}}
|
|
className="group p-5 sm:p-6 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"
|
|
>
|
|
<div className="space-y-4">
|
|
<div className="relative inline-block">
|
|
<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-12 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-6 h-6 ${item.iconColor}`} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<h3 className="text-lg font-bold text-white group-hover:text-white/90 transition-colors">
|
|
{item.title}
|
|
</h3>
|
|
<p className="text-sm text-zinc-400 leading-relaxed">{item.text}</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
|
|
{/* Stats */}
|
|
<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-4 sm:gap-6 pt-6"
|
|
>
|
|
{[
|
|
{ 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-2">
|
|
<div className="flex justify-center">
|
|
<div className="p-2 rounded-lg bg-white/5 border border-white/10">
|
|
<stat.icon className="w-4 h-4 sm:w-5 sm:h-5 text-purple-400" />
|
|
</div>
|
|
</div>
|
|
<p className="text-xl sm:text-2xl font-bold text-white">{stat.value}</p>
|
|
<p className="text-[10px] 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 gap-3 sm:gap-4 pt-4"
|
|
>
|
|
{["FDA Compliant", "HIPAA Certified", "ISO 27001"].map((badge, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center gap-2 px-3 sm:px-4 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>
|
|
);
|
|
} |