Spaces:
Running
Running
| // Main Application Script | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize Three.js Scene | |
| initThreeJSScene(); | |
| initInteractive3DScene(); | |
| // Load Blog Posts from API | |
| loadBlogPosts(); | |
| // Theme Toggle Logic | |
| initThemeToggle(); | |
| // Smooth Scroll for Anchor Links | |
| initSmoothScroll(); | |
| // Initialize CMS Dashboard Demo | |
| initCMSDashboard(); | |
| }); | |
| // Three.js Background Animation | |
| function initThreeJSScene() { | |
| const container = document.getElementById('threejs-container'); | |
| if (!container) return; | |
| // Scene setup | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); | |
| renderer.setSize(container.clientWidth, container.clientHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| container.appendChild(renderer.domElement); | |
| // Lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(5, 5, 5); | |
| scene.add(directionalLight); | |
| // Create floating geometric objects | |
| const geometryTypes = [ | |
| new THREE.TorusGeometry(1, 0.4, 16, 100), | |
| new THREE.IcosahedronGeometry(1, 0), | |
| new THREE.OctahedronGeometry(1, 0), | |
| new THREE.TorusKnotGeometry(1, 0.3, 100, 16) | |
| ]; | |
| const material = new THREE.MeshStandardMaterial({ | |
| color: 0x3b82f6, | |
| metalness: 0.3, | |
| roughness: 0.4, | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const objects = []; | |
| for (let i = 0; i < 8; i++) { | |
| const geometry = geometryTypes[Math.floor(Math.random() * geometryTypes.length)]; | |
| const mesh = new THREE.Mesh(geometry, material.clone()); | |
| mesh.position.x = (Math.random() - 0.5) * 20; | |
| mesh.position.y = (Math.random() - 0.5) * 10; | |
| mesh.position.z = (Math.random() - 0.5) * 20; | |
| mesh.rotation.x = Math.random() * Math.PI; | |
| mesh.rotation.y = Math.random() * Math.PI; | |
| mesh.userData = { | |
| speed: 0.02 + Math.random() * 0.03, | |
| rotationSpeed: { | |
| x: (Math.random() - 0.5) * 0.02, | |
| y: (Math.random() - 0.5) * 0.02, | |
| z: (Math.random() - 0.5) * 0.02 | |
| }, | |
| floatAmplitude: 0.5 + Math.random() * 1, | |
| floatSpeed: 1 + Math.random() * 2, | |
| initialY: mesh.position.y | |
| }; | |
| mesh.material.color.setHSL(Math.random(), 0.7, 0.6); | |
| scene.add(mesh); | |
| objects.push(mesh); | |
| } | |
| camera.position.z = 15; | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| objects.forEach(mesh => { | |
| // Floating animation | |
| mesh.position.y = mesh.userData.initialY + Math.sin(Date.now() * 0.001 * mesh.userData.floatSpeed) * mesh.userData.floatAmplitude; | |
| // Rotation | |
| mesh.rotation.x += mesh.userData.rotationSpeed.x; | |
| mesh.rotation.y += mesh.userData.rotationSpeed.y; | |
| mesh.rotation.z += mesh.userData.rotationSpeed.z; | |
| // Gentle orbit | |
| mesh.position.x += Math.cos(Date.now() * 0.001 * mesh.userData.speed) * 0.02; | |
| mesh.position.z += Math.sin(Date.now() * 0.001 * mesh.userData.speed) * 0.02; | |
| }); | |
| renderer.render(scene, camera); | |
| } | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| camera.aspect = container.clientWidth / container.clientHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(container.clientWidth, container.clientHeight); | |
| }); | |
| animate(); | |
| } | |
| // Interactive 3D Scene | |
| function initInteractive3DScene() { | |
| const container = document.getElementById('interactive-3d'); | |
| if (!container) return; | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); | |
| renderer.setSize(container.clientWidth, container.clientHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| container.appendChild(renderer.domElement); | |
| // Add OrbitControls | |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| // Create a more complex 3D object | |
| const geometry = new THREE.IcosahedronGeometry(3, 2); | |
| const material = new THREE.MeshNormalMaterial({ wireframe: false }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| scene.add(mesh); | |
| // Add wireframe version | |
| const wireframe = new THREE.LineSegments( | |
| new THREE.WireframeGeometry(geometry), | |
| new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 }) | |
| ); | |
| mesh.add(wireframe); | |
| // Add particles around the object | |
| const particleGeometry = new THREE.BufferGeometry(); | |
| const particleCount = 1000; | |
| const positions = new Float32Array(particleCount * 3); | |
| for (let i = 0; i < particleCount * 3; i += 3) { | |
| const radius = 5 + Math.random() * 3; | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.random() * Math.PI; | |
| positions[i] = radius * Math.sin(phi) * Math.cos(theta); | |
| positions[i + 1] = radius * Math.cos(phi); | |
| positions[i + 2] = radius * Math.sin(phi) * Math.sin(theta); | |
| } | |
| particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| const particleMaterial = new THREE.PointsMaterial({ | |
| color: 0xa855f7, | |
| size: 0.05, | |
| transparent: true | |
| }); | |
| const particles = new THREE.Points(particleGeometry, particleMaterial); | |
| scene.add(particles); | |
| camera.position.z = 10; | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| mesh.rotation.x += 0.005; | |
| mesh.rotation.y += 0.005; | |
| particles.rotation.y += 0.001; | |
| controls.update(); | |
| renderer.render(scene, camera); | |
| } | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| camera.aspect = container.clientWidth / container.clientHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(container.clientWidth, container.clientHeight); | |
| }); | |
| animate(); | |
| } | |
| // Load Blog Posts from Mock API | |
| async function loadBlogPosts() { | |
| const container = document.getElementById('blog-posts'); | |
| if (!container) return; | |
| // Mock API URL - In a real implementation, this would be your headless CMS endpoint | |
| const mockApiUrl = 'https://jsonplaceholder.typicode.com/posts?_limit=6'; | |
| try { | |
| container.innerHTML = ` | |
| <div class="col-span-1 md:col-span-2 lg:col-span-3 text-center py-12"> | |
| <div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-600"></div> | |
| <p class="mt-4 text-gray-600 dark:text-gray-400">Loading articles from CMS...</p> | |
| </div> | |
| `; | |
| const response = await fetch(mockApiUrl); | |
| const posts = await response.json(); | |
| container.innerHTML = ''; | |
| posts.forEach((post, index) => { | |
| const colors = ['primary', 'secondary']; | |
| const color = colors[index % colors.length]; | |
| const card = document.createElement('div'); | |
| card.className = 'blog-card bg-white dark:bg-gray-800 rounded-2xl shadow-xl overflow-hidden transform transition-all duration-300 hover:shadow-2xl'; | |
| card.innerHTML = ` | |
| <div class="h-48 overflow-hidden"> | |
| <img src="https://static.photos/${color === 'primary' ? 'technology' : 'abstract'}/640x360/${index + 100}" | |
| alt="${post.title}" | |
| class="w-full h-full object-cover transform hover:scale-110 transition-transform duration-500"> | |
| </div> | |
| <div class="p-6"> | |
| <div class="flex items-center gap-2 mb-4"> | |
| <span class="px-3 py-1 text-xs font-semibold rounded-full bg-${color}-100 dark:bg-${color}-900/30 text-${color}-600 dark:text-${color}-400"> | |
| ${color === 'primary' ? 'TECHNOLOGY' : 'DESIGN'} | |
| </span> | |
| <span class="text-gray-500 dark:text-gray-400 text-sm">${Math.floor(Math.random() * 30) + 1} min read</span> | |
| </div> | |
| <h3 class="text-xl font-bold mb-3 line-clamp-2">${post.title}</h3> | |
| <p class="text-gray-600 dark:text-gray-400 mb-4 line-clamp-3">${post.body}</p> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-8 h-8 rounded-full bg-gradient-to-r from-${color}-400 to-${color}-600 flex items-center justify-center text-white text-xs"> | |
| ${post.userId} | |
| </div> | |
| <span class="text-sm text-gray-700 dark:text-gray-300">Author ${post.userId}</span> | |
| </div> | |
| <a href="#" class="text-${color}-600 dark:text-${color}-400 hover:text-${color}-700 dark:hover:text-${color}-300 font-semibold text-sm flex items-center gap-1"> | |
| Read More <i data-feather="arrow-right" class="w-4 h-4"></i> | |
| </a> | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| feather.replace(); | |
| } catch (error) { | |
| console.error('Error loading blog posts:', error); | |
| container.innerHTML = ` | |
| <div class="col-span-1 md:col-span-2 lg:col-span-3 text-center py-12"> | |
| <i data-feather="alert-circle" class="w-16 h-16 text-red-500 mx-auto"></i> | |
| <p class="mt-4 text-gray-600 dark:text-gray-400">Unable to load articles. Please check your connection.</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| } | |
| // Theme Toggle Functionality | |
| function initThemeToggle() { | |
| const themeToggles = document.querySelectorAll('[data-theme-toggle]'); | |
| if (!themeToggles.length) return; | |
| const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); | |
| const currentTheme = localStorage.getItem('theme') || (prefersDarkScheme.matches ? 'dark' : 'light'); | |
| if (currentTheme === 'dark') { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| themeToggles.forEach(themeToggle => { | |
| themeToggle.addEventListener('click', () => { | |
| const isDark = document.documentElement.classList.toggle('dark'); | |
| localStorage.setItem('theme', isDark ? 'dark' : 'light'); | |
| // Update icons | |
| const icons = document.querySelectorAll('[data-feather]'); | |
| icons.forEach(icon => { | |
| if (icon.getAttribute('data-feather') === 'moon' || icon.getAttribute('data-feather') === 'sun') { | |
| icon.setAttribute('data-feather', isDark ? 'sun' : 'moon'); | |
| } | |
| }); | |
| feather.replace(); | |
| }); | |
| }); | |
| // Also update icons on initial load | |
| setTimeout(() => { | |
| const isDark = document.documentElement.classList.contains('dark'); | |
| const icons = document.querySelectorAll('[data-feather]'); | |
| icons.forEach(icon => { | |
| if (icon.getAttribute('data-feather') === 'moon' || icon.getAttribute('data-feather') === 'sun') { | |
| icon.setAttribute('data-feather', isDark ? 'sun' : 'moon'); | |
| } | |
| }); | |
| feather.replace(); | |
| }, 100); | |
| } | |
| // Smooth Scroll Implementation | |
| function initSmoothScroll() { | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| const targetId = this.getAttribute('href'); | |
| if (targetId === '#') return; | |
| const targetElement = document.querySelector(targetId); | |
| if (targetElement) { | |
| window.scrollTo({ | |
| top: targetElement.offsetTop - 80, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| // CMS Dashboard Demo | |
| function initCMSDashboard() { | |
| // Simulate real-time updates | |
| setInterval(() => { | |
| const stats = document.querySelectorAll('.stat-value'); | |
| stats.forEach(stat => { | |
| if (stat.classList.contains('animate-pulse')) { | |
| stat.classList.remove('animate-pulse'); | |
| setTimeout(() => stat.classList.add('animate-pulse'), 10); | |
| } | |
| }); | |
| }, 3000); | |
| // Simulate API call for CMS status | |
| setTimeout(() => { | |
| const statusIndicator = document.getElementById('cms-status'); | |
| if (statusIndicator) { | |
| statusIndicator.innerHTML = ` | |
| <div class="flex items-center gap-2 text-green-500"> | |
| <div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div> | |
| <span>CMS Connected • Real-time Sync Active</span> | |
| </div> | |
| `; | |
| } | |
| }, 1500); | |
| } |