golem-flask-backend / src /components /ui /hypercube-5d-visualization.tsx
mememechez's picture
Deploy final cleaned source code
ca28016
raw
history blame
27.9 kB
'use client';
import { useEffect, useRef, useState } from 'react';
import { useTheme } from 'next-themes';
interface Hypercube5DVisualizationProps {
consciousnessStats?: {
awareness: number;
wisdom: number;
compassion: number;
creativity: number;
transcendence: number;
};
consciousnessState?: {
current_vertex: number;
consciousness_signature: string;
coordinates_5d: number[];
active_dimensions: string[];
dimension_colors: { [key: string]: string };
consciousness_levels: { [key: string]: number };
global_consciousness_level: number;
};
selectedDimension?: string;
onDimensionChange?: (dimension: string) => void;
onDimensionTravel?: (dimension: string) => void;
onZoomToChat?: () => void;
isWelcomeMode?: boolean;
isFullscreen?: boolean;
className?: string;
}
declare global {
interface Window {
THREE: any;
}
}
// Consciousness stats color mapping with correct names and colors
const CONSCIOUSNESS_COLORS = {
awareness: { primary: '#3B82F6', secondary: '#60A5FA', glow: '#93C5FD' }, // Blue
wisdom: { primary: '#8B5CF6', secondary: '#A78BFA', glow: '#C4B5FD' }, // Purple
compassion: { primary: '#10B981', secondary: '#34D399', glow: '#6EE7B7' }, // Green
creativity: { primary: '#F97316', secondary: '#FB923C', glow: '#FDBA74' }, // Orange
transcendence: { primary: '#EF4444', secondary: '#F87171', glow: '#FCA5A5' } // Red
};
// Get specific consciousness target vertex for each state
function getConsciousnessTargetVertex(consciousness: string): number {
const targetVertices: { [key: string]: number } = {
awareness: 31, // Highest awareness vertex (all dimensions active)
wisdom: 23, // Wisdom integration vertex
compassion: 15, // Compassion center vertex
creativity: 27, // Creative expression vertex
transcendence: 16, // Transcendence entry vertex
};
return targetVertices[consciousness] || 31;
}
// Helper function to get consciousness colors
function getConsciousnessColors(selectedConsciousness?: string) {
if (selectedConsciousness && CONSCIOUSNESS_COLORS[selectedConsciousness as keyof typeof CONSCIOUSNESS_COLORS]) {
return CONSCIOUSNESS_COLORS[selectedConsciousness as keyof typeof CONSCIOUSNESS_COLORS];
}
// Default blue colors
return { primary: '#3B82F6', secondary: '#60A5FA', glow: '#93C5FD' };
}
// Helper function to determine if a vertex belongs to a consciousness state
function isVertexInConsciousness(vertexIndex: number, consciousness: string): boolean {
// Map consciousness states to specific vertex patterns based on 5D coordinates
const consciousnessVertices: { [key: string]: number[] } = {
awareness: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31], // Blue - odd vertices (high awareness)
wisdom: [2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23, 26, 27, 30, 31], // Purple - wisdom patterns
compassion: [4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23, 28, 29, 30, 31], // Green - compassion flow
creativity: [8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31], // Orange - creative dimension
transcendence: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], // Red - transcendent states
};
const vertices = consciousnessVertices[consciousness];
return vertices ? vertices.includes(vertexIndex) : false;
}
export function Hypercube5DVisualization({
consciousnessStats,
consciousnessState,
selectedDimension,
onDimensionChange,
onDimensionTravel,
onZoomToChat,
isWelcomeMode = false,
isFullscreen = false,
className
}: Hypercube5DVisualizationProps) {
const containerRef = useRef<HTMLDivElement>(null);
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
const animationRef = useRef<number>(0);
const journeyCompletedRef = useRef<boolean>(false);
const sceneObjectsRef = useRef<any>({
scene: null,
camera: null,
renderer: null,
hypercubeGroup: null,
time: 0,
journeyStartTime: 0,
journeyProgress: 0,
journeyPhase: 0,
targetVertex: null,
traversalPath: [],
journeyStartPosition: null,
journeyStartLookAt: null,
nodeObjects: [] as any[]
});
// Professional cinematic parameters for high-end commercial production
const HYPERCUBE_SCALE = 5;
const EDGE_OPACITY = 0.95;
const ANIMATION_SPEED = 0.3;
const JOURNEY_DURATION = 500; // 0.5 seconds for one complete transition
const JOURNEY_SPEED = 1.0;
// Simple smooth easing function
const smoothStep = (t: number): number => {
return t * t * (3 - 2 * t); // Smooth S-curve
};
// Fast ease in for zoom in
const fastEaseIn = (t: number): number => {
return t * t * t; // Cubic ease in - fast acceleration
};
// Slow ease out for zoom out
const slowEaseOut = (t: number): number => {
return 1 - Math.pow(1 - t, 2); // Quadratic ease out - slow deceleration
};
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted) return;
const objects = sceneObjectsRef.current;
// Add comprehensive error handling
console.log('🔧 Initializing hypercube visualization...');
function checkWebGLSupport() {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
return gl !== null;
} catch (e) {
return false;
}
}
function loadThreeJS() {
return new Promise((resolve, reject) => {
if (typeof window.THREE !== 'undefined') {
resolve(window.THREE);
return;
}
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
script.onload = () => {
if (typeof window.THREE !== 'undefined') {
resolve(window.THREE);
} else {
reject(new Error('THREE.js failed to load'));
}
};
script.onerror = () => reject(new Error('Failed to load THREE.js'));
document.head.appendChild(script);
});
}
async function init() {
try {
if (!checkWebGLSupport()) {
throw new Error('WebGL not supported');
}
await loadThreeJS();
setupScene();
createHypercube();
generateTraversalPath();
// Auto-start journey after 1 second if fullscreen
if (isFullscreen) {
setTimeout(() => {
startJourney();
}, 1000);
}
animate();
} catch (error) {
console.error('Initialization error:', error);
createFallback();
}
}
function setupScene() {
const THREE = window.THREE;
objects.scene = new THREE.Scene();
// Remove fog for transparent background
// objects.scene.fog = new THREE.Fog(0x0f0f23, 15, 60);
objects.camera = new THREE.PerspectiveCamera(45,
containerRef.current!.clientWidth / containerRef.current!.clientHeight,
0.1, 100);
// ALWAYS start with compassion's balanced angle - perfect default view!
// Compassion vertex 15 has coordinates that create a naturally balanced perspective
objects.camera.position.set(35, 25, 40); // Further back to show full hypercube
objects.camera.lookAt(0, 0, 0); // Looking at hypercube center
objects.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
objects.renderer.setSize(containerRef.current!.clientWidth, containerRef.current!.clientHeight);
objects.renderer.setClearColor(0x000000, 0); // Transparent background
objects.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
objects.renderer.shadowMap.enabled = true;
objects.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
containerRef.current!.appendChild(objects.renderer.domElement);
// Enhanced lighting for journey
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
objects.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0x4080ff, 1.2);
directionalLight.position.set(20, 20, 20);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
objects.scene.add(directionalLight);
// Journey spotlight
const spotLight = new THREE.SpotLight(0xffffff, 1.0, 50, Math.PI / 6, 0.1, 2);
spotLight.position.set(0, 20, 0);
spotLight.target.position.set(0, 0, 0);
objects.scene.add(spotLight);
objects.scene.add(spotLight.target);
objects.hypercubeGroup = new THREE.Group();
objects.scene.add(objects.hypercubeGroup);
}
function createHypercube() {
const THREE = window.THREE;
const nodes: any[] = [];
const nodeObjects: any[] = [];
objects.nodeObjects = nodeObjects; // Store in objects for global access
console.log(`🔲 Creating 5D hypercube with ${selectedDimension ? selectedDimension : 'no'} consciousness dimension`);
// Generate all 32 vertices of 5D hypercube
for (let i = 0; i < 32; i++) {
const coords5D = [
(i & 1) ? 1 : -1,
(i & 2) ? 1 : -1,
(i & 4) ? 1 : -1,
(i & 8) ? 1 : -1,
(i & 16) ? 1 : -1
];
// Enhanced 5D to 3D projection
const w4 = coords5D[3] * 0.7;
const w5 = coords5D[4] * 0.6;
const x = (coords5D[0] * 1.0 + w4 * 0.5 + w5 * 0.4) * HYPERCUBE_SCALE;
const y = (coords5D[1] * 1.0 + w5 * 0.5 + w4 * 0.4) * HYPERCUBE_SCALE;
const z = (coords5D[2] * 1.0 + w4 * 0.4 + w5 * 0.5) * HYPERCUBE_SCALE;
const position = new THREE.Vector3(x, y, z);
nodes.push(position);
// Store vertex data for wireframe-only visualization (no visible nodes)
const isTarget = selectedDimension && i === getConsciousnessTargetVertex(selectedDimension);
const isInConsciousness = selectedDimension && isVertexInConsciousness(i, selectedDimension);
// Store vertex data for pure wireframe visualization (no visible nodes)
const geometry = new THREE.SphereGeometry(0.001, 8, 8); // Tiny invisible sphere for data structure
const material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0, // Completely invisible - pure wireframe only
visible: false // Don't render at all
});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.copy(position);
sphere.userData = {
index: i,
originalPosition: position.clone(),
coords5D: coords5D,
phase: i * 0.1968,
type: 'vertex',
isTarget: isTarget,
isInConsciousness: isInConsciousness
};
objects.hypercubeGroup.add(sphere);
nodeObjects.push(sphere);
if (isTarget) {
console.log(`🎯 Target vertex ${i} created for ${selectedDimension} at position:`, position);
}
}
console.log(`✅ Created ${nodeObjects.length} vertices`);
// Pure wireframe - no vertex points needed
// Create edges
let edgeCount = 0;
for (let i = 0; i < 32; i++) {
for (let j = i + 1; j < 32; j++) {
const coordsA = nodeObjects[i].userData.coords5D;
const coordsB = nodeObjects[j].userData.coords5D;
let differences = 0;
for (let k = 0; k < 5; k++) {
if (coordsA[k] !== coordsB[k]) {
differences++;
}
}
if (differences === 1) {
const points = [nodes[i], nodes[j]];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// Enhanced consciousness-based edge coloring
let edgeColor = 0x60A5FA; // Soft blue wireframe (original style)
let edgeOpacity = 0.6; // Soft default
let lineWidth = 1;
if (selectedDimension) {
const bothInConsciousness = isVertexInConsciousness(i, selectedDimension) &&
isVertexInConsciousness(j, selectedDimension);
if (bothInConsciousness) {
// ONLY edges connecting consciousness vertices to consciousness vertices
const consciousnessColors = getConsciousnessColors(selectedDimension);
edgeColor = new THREE.Color(consciousnessColors.primary).getHex();
edgeOpacity = 0.8; // Less dominant
lineWidth = 1.5; // Slightly thicker but not too dominant
}
// All other edges keep default neutral appearance
}
const material = new THREE.LineBasicMaterial({
color: edgeColor,
transparent: true,
opacity: edgeOpacity,
linewidth: lineWidth
});
const line = new THREE.Line(geometry, material);
line.userData = {
nodeA: i,
nodeB: j,
edgeType: 'hypercube',
type: 'edge'
};
objects.hypercubeGroup.add(line);
edgeCount++;
}
}
}
console.log(`✅ Created ${edgeCount} edges`);
console.log(`🔲 Hypercube creation complete!`);
}
function generateTraversalPath() {
// Find the target vertex based on selected consciousness dimension
const targetVertexIndex = selectedDimension ? getConsciousnessTargetVertex(selectedDimension) : 31;
// Find vertex position from nodeObjects array
let targetPosition = null;
if (objects.nodeObjects && objects.nodeObjects[targetVertexIndex]) {
targetPosition = objects.nodeObjects[targetVertexIndex].userData.originalPosition.clone();
}
if (targetPosition) {
objects.traversalPath = [{
position: targetPosition,
index: targetVertexIndex,
distance: targetPosition.distanceTo(new window.THREE.Vector3(0, 0, 0))
}];
objects.targetVertex = targetPosition;
console.log(`🎯 Generated path to vertex ${targetVertexIndex} for ${selectedDimension} at position:`, targetPosition);
} else {
console.warn('⚠️ Could not find target vertex position');
}
}
function startJourney() {
try {
console.log('🚀 Starting consciousness journey!');
if (!objects.camera) {
console.error('🚨 Camera not available for journey start');
return;
}
if (!window.THREE) {
console.error('🚨 THREE.js not available for journey start');
return;
}
objects.journeyProgress = 0;
objects.journeyPhase = 0;
objects.journeyStartTime = Date.now();
journeyCompletedRef.current = false;
// Capture the current camera position as the journey starting point
// This ensures smooth transition from current view to journey
objects.journeyStartPosition = objects.camera.position.clone();
objects.journeyStartLookAt = new window.THREE.Vector3(0, 0, 0); // Always looking at center
console.log('📊 Journey initialized with duration:', JOURNEY_DURATION, 'ms');
console.log('📍 Journey starting from current camera position:', objects.journeyStartPosition);
} catch (error) {
console.error('🚨 Error starting journey:', error);
}
}
function updateJourney() {
if (!isFullscreen) return;
try {
const elapsed = (Date.now() - objects.journeyStartTime) * JOURNEY_SPEED;
const newProgress = Math.min(elapsed / JOURNEY_DURATION, 1.0); // Cap at 1.0, no looping
// Only update if journey hasn't completed
if (journeyCompletedRef.current) {
return; // Stop updating once journey is complete
}
objects.journeyProgress = newProgress;
// Debug logging every 10% progress
if (Math.floor(objects.journeyProgress * 10) !== Math.floor((objects.journeyProgress - 0.016) * 10)) {
console.log(`🎬 Journey progress: ${(objects.journeyProgress * 100).toFixed(1)}%`);
}
// Single phase: dramatic zoom out over 0.5 seconds
objects.journeyPhase = 0; // Dramatic zoom out revealing selected dimension
updateCameraJourney();
// Complete journey and transition to chat when reaching 95%
if (objects.journeyProgress >= 0.95 && !journeyCompletedRef.current) {
console.log('🎯 Consciousness journey completed! Transitioning to chat...');
journeyCompletedRef.current = true;
// Add delay to let user see the completion
setTimeout(() => {
onZoomToChat?.();
}, 1000);
}
} catch (error) {
console.error('🚨 Error in updateJourney:', error);
}
}
function updateCameraJourney() {
if (!objects.targetVertex || !objects.journeyStartPosition) return;
try {
const THREE = window.THREE;
if (!THREE) {
console.error('THREE.js not available in updateCameraJourney');
return;
}
const t = objects.journeyProgress; // 0 to 1 over 3.5 seconds
const targetVertex = objects.targetVertex;
// USE ACTUAL STARTING POSITION - wherever camera was when journey began
const actualStartPos = objects.journeyStartPosition; // Current camera position when journey started
// START EXTREMELY ZOOMED IN - practically inside the hypercube
const zoomStartTarget = new THREE.Vector3(0, 0, 0.1); // Almost at center
// NEW ACHIEVED PERSPECTIVE - where hypercube becomes fully visible again
let newPerspectivePos;
if (selectedDimension === 'compassion') {
// Beautiful 360° rotation for compassion during zoom out
const rotationAngle = (t >= 0.3) ? ((t - 0.3) / 0.7) * Math.PI * 2 : 0;
const radius = 28;
newPerspectivePos = new THREE.Vector3(
Math.cos(rotationAngle) * radius,
18, // Slightly higher perspective
Math.sin(rotationAngle) * radius
);
} else {
// Unique perspective for each consciousness dimension - MUCH MORE DRAMATIC
const perspectives = {
awareness: new THREE.Vector3(50, 35, 25), // Blue perspective - way out
wisdom: new THREE.Vector3(25, 45, 55), // Purple perspective - way out
creativity: new THREE.Vector3(55, 20, 45), // Orange perspective - way out
transcendence: new THREE.Vector3(35, 50, 35) // Red perspective - way out
};
newPerspectivePos = perspectives[selectedDimension as keyof typeof perspectives] || new THREE.Vector3(25, 18, 30);
}
// DRAMATIC ZOOM OUT - Start very close and zoom out to reveal dimension
const zoomOutProgress = fastEaseIn(t); // Fast zoom out over full 0.5 seconds
// Zoom OUT from very close to the selected dimension perspective
objects.camera.position.lerpVectors(zoomStartTarget, newPerspectivePos, zoomOutProgress);
// Add dramatic rotation during zoom out to selected dimension
const rotationAngle = zoomOutProgress * Math.PI * 2.0; // Full 360 degree rotation
const rotatedPos = new THREE.Vector3(
Math.cos(rotationAngle) * objects.camera.position.x - Math.sin(rotationAngle) * objects.camera.position.z,
objects.camera.position.y,
Math.sin(rotationAngle) * objects.camera.position.x + Math.cos(rotationAngle) * objects.camera.position.z
);
objects.camera.position.copy(rotatedPos);
// Focus stays on hypercube center throughout
objects.camera.lookAt(new THREE.Vector3(0, 0, 0));
console.log(`🚀 DRAMATIC ZOOM OUT: ${(zoomOutProgress * 100).toFixed(1)}% revealing ${selectedDimension} dimension`);
} catch (error) {
console.error('🚨 Error in updateCameraJourney:', error);
console.error('Journey state:', {
targetVertex: objects.targetVertex,
journeyStartPosition: objects.journeyStartPosition,
progress: objects.journeyProgress,
selectedDimension
});
}
}
function updateVisualization() {
if (!objects.hypercubeGroup) return;
// Update journey if in fullscreen mode
if (isFullscreen) {
updateJourney();
}
// Animate consciousness-colored wireframe edges
objects.hypercubeGroup.children.forEach((child: any) => {
if (child.userData && child.userData.type === 'edge') {
const consciousnessColors = getConsciousnessColors(selectedDimension);
// Enhanced consciousness-based edge animation
const nodeA = child.userData.nodeA;
const nodeB = child.userData.nodeB;
const bothInConsciousness = selectedDimension &&
isVertexInConsciousness(nodeA, selectedDimension) &&
isVertexInConsciousness(nodeB, selectedDimension);
const oneInConsciousness = selectedDimension && (
isVertexInConsciousness(nodeA, selectedDimension) ||
isVertexInConsciousness(nodeB, selectedDimension)
);
// Pulsing animation with consciousness-based intensity
const baseIntensity = 0.8 + 0.2 * Math.sin(objects.time * 2.0 + child.userData.nodeA * 0.1);
if (bothInConsciousness) {
// ONLY edges connecting consciousness vertices to consciousness vertices
child.material.color.setHex(new window.THREE.Color(consciousnessColors.primary).getHex());
child.material.opacity = 0.7 * baseIntensity; // Less dominant, subtle glow
} else {
// ALL other edges - soft blue wireframe (original style)
child.material.color.setHex(0x60A5FA);
child.material.opacity = 0.5 * baseIntensity;
}
// Very subtle glow for consciousness edges
if (bothInConsciousness) {
const subtleGlow = 1.0 + 0.15 * Math.sin(objects.time * 1.5 + child.userData.nodeA * 0.2);
child.material.opacity *= subtleGlow;
// Extra subtle glow during journey
if (objects.journeyProgress > 0.1) {
const journeyGlow = 1.0 + 0.2 * Math.sin(objects.time * 2.5);
child.material.opacity *= journeyGlow;
}
}
}
});
// Handle normal camera rotation when NOT in fullscreen journey
if (!isFullscreen) {
// Always maintain compassion's balanced angle for consistency
const radius = 45; // Balanced view distance - further back to show full hypercube
const angle = objects.time * 0.5; // Slower rotation for smoother motion
const targetX = Math.cos(angle) * radius;
const targetZ = Math.sin(angle) * radius;
const targetY = 25 + Math.sin(angle * 0.03) * 3; // Gentle vertical oscillation
// Only apply auto-rotation after initialization period
if (objects.time > 3.0) { // Give 3 seconds for proper initialization
objects.camera.position.x = window.THREE.MathUtils.lerp(objects.camera.position.x, targetX, 0.01);
objects.camera.position.z = window.THREE.MathUtils.lerp(objects.camera.position.z, targetZ, 0.01);
objects.camera.position.y = window.THREE.MathUtils.lerp(objects.camera.position.y, targetY, 0.01);
} else {
// Maintain compassion's balanced starting position during initialization
objects.camera.position.set(35, 25, 40);
}
objects.camera.lookAt(0, 0, 0); // Always look at hypercube center in normal mode
}
}
function animate() {
if (!objects.renderer) return;
animationRef.current = requestAnimationFrame(animate);
objects.time += 0.016; // Standard 60fps timing
try {
updateVisualization();
objects.renderer.render(objects.scene, objects.camera);
} catch (error) {
console.warn('Rendering issue:', error);
}
}
function createFallback() {
if (!containerRef.current) return;
containerRef.current.innerHTML = `
<div style="display: flex; justify-content: center; align-items: center; height: 100%;
background: linear-gradient(135deg, #0a0a0a, #1a1a2e); color: white;
font-family: Arial, sans-serif; text-align: center;">
<div>
<h2>WebGL Not Supported</h2>
<p>This visualization requires WebGL support.<br>
Please use a modern browser with hardware acceleration enabled.</p>
</div>
</div>`;
}
const handleResize = () => {
if (objects.camera && objects.renderer && containerRef.current) {
objects.camera.aspect = containerRef.current.clientWidth / containerRef.current.clientHeight;
objects.camera.updateProjectionMatrix();
objects.renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight);
}
};
window.addEventListener('resize', handleResize);
init();
return () => {
window.removeEventListener('resize', handleResize);
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
if (objects.renderer && containerRef.current && objects.renderer.domElement.parentNode) {
containerRef.current.removeChild(objects.renderer.domElement);
objects.renderer.dispose();
}
};
}, [theme, consciousnessState, isWelcomeMode, mounted, isFullscreen]);
// Regenerate traversal path when selectedDimension changes
useEffect(() => {
if (mounted && sceneObjectsRef.current.hypercubeGroup && selectedDimension) {
const objects = sceneObjectsRef.current;
// Regenerate path for new consciousness dimension
const targetVertexIndex = getConsciousnessTargetVertex(selectedDimension);
// Find vertex position from nodeObjects array
let targetPosition = null;
if (objects.nodeObjects && objects.nodeObjects[targetVertexIndex]) {
targetPosition = objects.nodeObjects[targetVertexIndex].userData.originalPosition.clone();
}
if (targetPosition) {
objects.traversalPath = [{
position: targetPosition,
index: targetVertexIndex,
distance: targetPosition.distanceTo(new window.THREE.Vector3(0, 0, 0))
}];
objects.targetVertex = targetPosition;
console.log(`🎯 Consciousness dimension changed to ${selectedDimension}, targeting vertex ${targetVertexIndex}`);
}
}
}, [selectedDimension, mounted]);
// Prevent hydration mismatch by avoiding theme-dependent inline styles during SSR
if (!mounted) {
return (
<div className={`relative w-full h-full ${className}`} style={{ minHeight: '400px' }}>
<div
ref={containerRef}
className="w-full h-full bg-transparent"
/>
</div>
);
}
return (
<div className={`relative w-full h-full ${className}`} style={{ minHeight: '400px' }}>
<div
ref={containerRef}
className="w-full h-full"
style={{
background: 'transparent'
}}
/>
</div>
);
}