|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const styleButtons = document.querySelectorAll('.style-btn'); |
|
|
let selectedStyle = 'realistic'; |
|
|
|
|
|
styleButtons.forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
styleButtons.forEach(b => b.classList.remove('active')); |
|
|
btn.classList.add('active'); |
|
|
selectedStyle = btn.dataset.style; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelector('[data-style="realistic"]').classList.add('active'); |
|
|
|
|
|
|
|
|
const generateBtn = document.getElementById('generate-btn'); |
|
|
const textInput = document.getElementById('text-input'); |
|
|
const progressContainer = document.getElementById('progress-container'); |
|
|
const progressBar = document.getElementById('progress-bar'); |
|
|
const progressPercent = document.getElementById('progress-percent'); |
|
|
const previewContainer = document.getElementById('preview-container'); |
|
|
const canvas = document.getElementById('3d-canvas'); |
|
|
const actionButtons = document.getElementById('action-buttons'); |
|
|
const renderTime = document.getElementById('render-time'); |
|
|
|
|
|
generateBtn.addEventListener('click', async () => { |
|
|
const text = textInput.value.trim(); |
|
|
|
|
|
if (!text) { |
|
|
showNotification('Please enter a description for your 3D design', 'warning'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
progressContainer.classList.remove('hidden'); |
|
|
generateBtn.disabled = true; |
|
|
generateBtn.classList.add('opacity-50', 'cursor-not-allowed'); |
|
|
|
|
|
|
|
|
let progress = 0; |
|
|
const startTime = Date.now(); |
|
|
|
|
|
const progressInterval = setInterval(() => { |
|
|
progress += Math.random() * 15; |
|
|
if (progress > 90) progress = 90; |
|
|
|
|
|
progressBar.style.width = `${progress}%`; |
|
|
progressPercent.textContent = `${Math.round(progress)}%`; |
|
|
}, 200); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
clearInterval(progressInterval); |
|
|
progressBar.style.width = '100%'; |
|
|
progressPercent.textContent = '100%'; |
|
|
|
|
|
|
|
|
generate3DVisualization(text, selectedStyle); |
|
|
|
|
|
|
|
|
const endTime = Date.now(); |
|
|
const renderDuration = ((endTime - startTime) / 1000).toFixed(1); |
|
|
renderTime.textContent = `Rendered in ${renderDuration}s`; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
progressContainer.classList.add('hidden'); |
|
|
generateBtn.disabled = false; |
|
|
generateBtn.classList.remove('opacity-50', 'cursor-not-allowed'); |
|
|
actionButtons.classList.remove('hidden'); |
|
|
canvas.classList.remove('hidden'); |
|
|
previewContainer.querySelector('.text-center').classList.add('hidden'); |
|
|
|
|
|
|
|
|
addToRecentCreations(text); |
|
|
}, 500); |
|
|
}, 3000); |
|
|
}); |
|
|
|
|
|
function generate3DVisualization(description, style) { |
|
|
const ctx = canvas.getContext('2d'); |
|
|
canvas.width = canvas.offsetWidth; |
|
|
canvas.height = canvas.offsetHeight; |
|
|
|
|
|
|
|
|
canvas.dataset.description = description; |
|
|
canvas.dataset.style = style; |
|
|
canvas.dataset.rotation = '0'; |
|
|
|
|
|
draw3DModel(ctx, canvas.width, canvas.height, style, 0); |
|
|
} |
|
|
|
|
|
function draw3DModel(ctx, width, height, style, rotation) { |
|
|
|
|
|
ctx.clearRect(0, 0, width, height); |
|
|
|
|
|
|
|
|
let gradient; |
|
|
switch(style) { |
|
|
case 'cartoon': |
|
|
gradient = ctx.createLinearGradient(0, 0, width, height); |
|
|
gradient.addColorStop(0, '#fef3c7'); |
|
|
gradient.addColorStop(0.5, '#fed7aa'); |
|
|
gradient.addColorStop(1, '#fbbf24'); |
|
|
break; |
|
|
case 'minimalist': |
|
|
gradient = ctx.createLinearGradient(0, 0, width, height); |
|
|
gradient.addColorStop(0, '#f3f4f6'); |
|
|
gradient.addColorStop(0.5, '#e5e7eb'); |
|
|
gradient.addColorStop(1, '#d1d5db'); |
|
|
break; |
|
|
case 'fantasy': |
|
|
gradient = ctx.createRadialGradient(width/2, height/2, 0, width/2, height/2, width/2); |
|
|
gradient.addColorStop(0, '#ddd6fe'); |
|
|
gradient.addColorStop(0.3, '#f9a8d4'); |
|
|
gradient.addColorStop(0.6, '#c084fc'); |
|
|
gradient.addColorStop(1, '#818cf8'); |
|
|
break; |
|
|
default: |
|
|
gradient = ctx.createLinearGradient(0, 0, width, height); |
|
|
gradient.addColorStop(0, '#0f172a'); |
|
|
gradient.addColorStop(0.5, '#1e293b'); |
|
|
gradient.addColorStop(1, '#334155'); |
|
|
} |
|
|
|
|
|
ctx.fillStyle = gradient; |
|
|
ctx.fillRect(0, 0, width, height); |
|
|
|
|
|
|
|
|
ctx.strokeStyle = style === 'minimalist' ? 'rgba(0, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.05)'; |
|
|
ctx.lineWidth = 1; |
|
|
const gridSize = 30; |
|
|
|
|
|
for (let x = 0; x <= width; x += gridSize) { |
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(x, 0); |
|
|
ctx.lineTo(x, height); |
|
|
ctx.stroke(); |
|
|
} |
|
|
|
|
|
for (let y = 0; y <= height; y += gridSize) { |
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(0, y); |
|
|
ctx.lineTo(width, y); |
|
|
ctx.stroke(); |
|
|
} |
|
|
|
|
|
const centerX = width / 2; |
|
|
const centerY = height / 2; |
|
|
|
|
|
|
|
|
ctx.save(); |
|
|
ctx.translate(centerX, centerY); |
|
|
|
|
|
|
|
|
ctx.rotate(rotation); |
|
|
|
|
|
|
|
|
const size = 100; |
|
|
const depth = 60; |
|
|
|
|
|
|
|
|
ctx.fillStyle = style === 'cartoon' ? 'rgba(251, 191, 36, 0.4)' : |
|
|
style === 'minimalist' ? 'rgba(156, 163, 175, 0.3)' : |
|
|
style === 'fantasy' ? 'rgba(168, 85, 247, 0.4)' : |
|
|
'rgba(14, 165, 233, 0.3)'; |
|
|
ctx.strokeStyle = style === 'cartoon' ? 'rgba(251, 191, 36, 0.8)' : |
|
|
style === 'minimalist' ? 'rgba(75, 85, 99, 0.8)' : |
|
|
style === 'fantasy' ? 'rgba(168, 85, 247, 0.8)' : |
|
|
'rgba(14, 165, 233, 0.8)'; |
|
|
ctx.lineWidth = 2; |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.rect(-size/2 - depth/3, -size/2 - depth/3, size, size); |
|
|
ctx.fill(); |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = style === 'cartoon' ? 'rgba(251, 191, 36, 0.5)' : |
|
|
style === 'minimalist' ? 'rgba(156, 163, 175, 0.4)' : |
|
|
style === 'fantasy' ? 'rgba(236, 72, 153, 0.5)' : |
|
|
'rgba(217, 70, 239, 0.4)'; |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(size/2, -size/2); |
|
|
ctx.lineTo(size/2 + depth/3, -size/2 - depth/3); |
|
|
ctx.lineTo(size/2 + depth/3, size/2 - depth/3); |
|
|
ctx.lineTo(size/2, size/2); |
|
|
ctx.closePath(); |
|
|
ctx.fill(); |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(-size/2, -size/2); |
|
|
ctx.lineTo(-size/2 - depth/3, -size/2 - depth/3); |
|
|
ctx.lineTo(size/2 - depth/3, -size/2 - depth/3); |
|
|
ctx.lineTo(size/2, -size/2); |
|
|
ctx.closePath(); |
|
|
ctx.fill(); |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = style === 'cartoon' ? 'rgba(251, 191, 36, 0.7)' : |
|
|
style === 'minimalist' ? 'rgba(156, 163, 175, 0.6)' : |
|
|
style === 'fantasy' ? 'rgba(236, 72, 153, 0.7)' : |
|
|
'rgba(14, 165, 233, 0.6)'; |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.rect(-size/2, -size/2, size, size); |
|
|
ctx.fill(); |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
if (style === 'fantasy') { |
|
|
|
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; |
|
|
for (let i = 0; i < 5; i++) { |
|
|
const sparkleX = (Math.random() - 0.5) * size * 1.5; |
|
|
const sparkleY = (Math.random() - 0.5) * size * 1.5; |
|
|
const sparkleSize = Math.random() * 3 + 1; |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.arc(sparkleX, sparkleY, sparkleSize, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
} else if (style === 'cartoon') { |
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(-20, -10, 12, 0, Math.PI * 2); |
|
|
ctx.arc(20, -10, 12, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = 'black'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(-20, -10, 6, 0, Math.PI * 2); |
|
|
ctx.arc(20, -10, 6, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
ctx.restore(); |
|
|
|
|
|
|
|
|
ctx.save(); |
|
|
ctx.translate(centerX, centerY + 100); |
|
|
ctx.scale(1, 0.3); |
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; |
|
|
ctx.beginPath(); |
|
|
ctx.ellipse(0, 0, size/2, size/4, 0, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
ctx.restore(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = style === 'minimalist' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.9)'; |
|
|
ctx.font = 'bold 14px sans-serif'; |
|
|
ctx.textAlign = 'center'; |
|
|
ctx.fillText('3D Model Generated!', centerX, height - 20); |
|
|
} |
|
|
|
|
|
function addToRecentCreations(description) { |
|
|
const recentContainer = document.getElementById('recent-creations'); |
|
|
const newItem = document.createElement('div'); |
|
|
newItem.className = 'bg-gray-800/50 rounded-lg overflow-hidden hover:scale-105 transition-transform duration-300 cursor-pointer opacity-0'; |
|
|
|
|
|
const randomSeed = Math.floor(Math.random() * 100); |
|
|
newItem.innerHTML = ` |
|
|
<img src="http://static.photos/abstract/320x240/${randomSeed}" alt="Creation" class="w-full h-40 object-cover"> |
|
|
<div class="p-3"> |
|
|
<p class="text-sm text-gray-300 truncate">${description.substring(0, 30)}${description.length > 30 ? '...' : ''}</p> |
|
|
<p class="text-xs text-gray-500">Just now</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
recentContainer.insertBefore(newItem, recentContainer.firstChild); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
newItem.classList.remove('opacity-0'); |
|
|
newItem.classList.add('opacity-100'); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
if (recentContainer.children.length > 4) { |
|
|
recentContainer.removeChild(recentContainer.lastChild); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showNotification(message, type = 'info') { |
|
|
const notification = document.createElement('div'); |
|
|
notification.className = `fixed top-20 right-4 px-6 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 z-50`; |
|
|
|
|
|
const bgColor = type === 'warning' ? 'bg-yellow-500' : type === 'error' ? 'bg-red-500' : 'bg-primary-500'; |
|
|
notification.classList.add(bgColor); |
|
|
|
|
|
notification.innerHTML = ` |
|
|
<div class="flex items-center gap-2 text-white"> |
|
|
<i data-feather="${type === 'warning' ? 'alert-triangle' : 'info'}" class="w-5 h-5"></i> |
|
|
<span>${message}</span> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
document.body.appendChild(notification); |
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
notification.classList.remove('translate-x-full'); |
|
|
notification.classList.add('translate-x-0'); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
notification.classList.add('translate-x-full'); |
|
|
setTimeout(() => { |
|
|
notification.remove(); |
|
|
}, 300); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
let animationId; |
|
|
function animateCanvas() { |
|
|
if (!canvas.classList.contains('hidden') && canvas.dataset.style) { |
|
|
const rotation = (Date.now() / 1000) % (Math.PI * 2); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
draw3DModel( |
|
|
ctx, |
|
|
canvas.width, |
|
|
canvas.height, |
|
|
canvas.dataset.style, |
|
|
rotation * 0.5 |
|
|
); |
|
|
} |
|
|
animationId = requestAnimationFrame(animateCanvas); |
|
|
} |
|
|
|
|
|
|
|
|
animateCanvas(); |
|
|
|
|
|
|
|
|
const observer = new MutationObserver((mutations) => { |
|
|
mutations.forEach((mutation) => { |
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') { |
|
|
if (canvas.classList.contains('hidden')) { |
|
|
cancelAnimationFrame(animationId); |
|
|
} else if (canvas.dataset.style) { |
|
|
animateCanvas(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
observer.observe(canvas, { attributes: true }); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => { |
|
|
anchor.addEventListener('click', function (e) { |
|
|
e.preventDefault(); |
|
|
const target = document.querySelector(this.getAttribute('href')); |
|
|
if (target) { |
|
|
target.scrollIntoView({ behavior: 'smooth' }); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const observerOptions = { |
|
|
threshold: 0.1, |
|
|
rootMargin: '0px 0px -50px 0px' |
|
|
}; |
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
entry.target.classList.add('animate-fadeIn'); |
|
|
} |
|
|
}); |
|
|
}, observerOptions); |
|
|
|
|
|
document.querySelectorAll('.section-animate').forEach(el => { |
|
|
observer.observe(el); |
|
|
}); |