Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>3D Cabinet Generator</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> | |
<style> | |
#modelContainer { | |
width: 100%; | |
height: 500px; | |
background-color: #f5f3f0; | |
border-radius: 8px; | |
overflow: hidden; | |
position: relative; | |
} | |
.loading-overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(245, 243, 240, 0.8); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 10; | |
} | |
.progress-ring { | |
width: 60px; | |
height: 60px; | |
} | |
.progress-ring circle { | |
transition: stroke-dashoffset 0.3s; | |
} | |
.controls-overlay { | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
gap: 10px; | |
z-index: 5; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8 text-center"> | |
<h1 class="text-3xl font-bold text-gray-800">3D Cabinet Generator</h1> | |
<p class="text-gray-600 mt-2">Customize your cabinet with real-time 3D visualization</p> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Controls --> | |
<div class="bg-white rounded-lg shadow p-6"> | |
<h2 class="text-xl font-semibold mb-4">Configuration</h2> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Cabinet Type</label> | |
<select id="cabinetType" class="w-full p-2 border rounded"> | |
<option value="base">Base Cabinet</option> | |
<option value="wall">Wall Cabinet</option> | |
<option value="tall">Tall Cabinet</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Width: <span id="widthValue">24</span>"</label> | |
<input type="range" id="widthSlider" min="12" max="48" value="24" step="1" class="w-full"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Height: <span id="heightValue">36</span>"</label> | |
<input type="range" id="heightSlider" min="12" max="96" value="36" step="1" class="w-full"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Depth: <span id="depthValue">12</span>"</label> | |
<input type="range" id="depthSlider" min="12" max="24" value="12" step="1" class="w-full"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Material</label> | |
<select id="material" class="w-full p-2 border rounded"> | |
<option value="oak">Oak</option> | |
<option value="maple">Maple</option> | |
<option value="cherry">Cherry</option> | |
<option value="laminate">Laminate</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Number of Doors</label> | |
<div class="flex space-x-2"> | |
<button id="oneDoor" class="flex-1 py-2 bg-blue-100 text-blue-700 rounded">1 Door</button> | |
<button id="twoDoors" class="flex-1 py-2 bg-gray-100 text-gray-700 rounded">2 Doors</button> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Number of Shelves: <span id="shelvesValue">1</span></label> | |
<input type="range" id="shelvesSlider" min="0" max="5" value="1" class="w-full"> | |
</div> | |
<div id="drawersContainer" class="mb-4 hidden"> | |
<label class="block text-gray-700 mb-2">Number of Drawers: <span id="drawersValue">1</span></label> | |
<input type="range" id="drawersSlider" min="1" max="3" value="1" class="w-full"> | |
</div> | |
<div class="mt-6 bg-gray-50 p-4 rounded-lg"> | |
<h3 class="font-semibold mb-2">Price Estimation</h3> | |
<div class="text-2xl font-bold text-blue-600" id="totalPrice">$299.99</div> | |
</div> | |
</div> | |
<!-- 3D Preview --> | |
<div class="lg:col-span-2"> | |
<div id="modelContainer"> | |
<div id="doorControls" class="controls-overlay hidden"> | |
<button id="openBtn" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center"> | |
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path> | |
</svg> | |
Open Doors | |
</button> | |
<button id="closeBtn" class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex items-center"> | |
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path> | |
</svg> | |
Close Doors | |
</button> | |
</div> | |
<div id="loadingOverlay" class="loading-overlay"> | |
<svg class="progress-ring" viewBox="0 0 60 60"> | |
<circle class="text-gray-300" stroke="currentColor" stroke-width="4" fill="none" r="25" cx="30" cy="30"></circle> | |
<circle class="text-blue-500" stroke="currentColor" stroke-width="4" fill="none" r="25" cx="30" cy="30" stroke-dasharray="157" stroke-dashoffset="157"></circle> | |
</svg> | |
<p class="mt-2 text-gray-600">Generating 3D model...</p> | |
</div> | |
</div> | |
<div class="mt-4 flex justify-end space-x-2"> | |
<button id="downloadBtn" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center"> | |
<svg class="w-5 h-5 mr-2" fill="none stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path> | |
</svg> | |
Download 3D Model | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Three.js variables | |
let scene, camera, renderer, controls, cabinetGroup; | |
const modelContainer = document.getElementById('modelContainer'); | |
const loadingOverlay = document.getElementById('loadingOverlay'); | |
const progressRing = document.querySelector('.progress-ring circle:last-child'); | |
const doorControls = document.getElementById('doorControls'); | |
const openBtn = document.getElementById('openBtn'); | |
const closeBtn = document.getElementById('closeBtn'); | |
// Door related variables | |
let leftDoor, rightDoor, leftDoorHinge, rightDoorHinge; | |
let isAnimating = false; | |
let doorState = 'closed'; // 'closed', 'opening', 'open', 'closing' | |
// Initialize Three.js | |
function initThreeJS() { | |
// Create scene | |
scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0xf5f3f0); | |
// Create camera | |
camera = new THREE.PerspectiveCamera( | |
60, // fov | |
modelContainer.clientWidth / modelContainer.clientHeight, // aspect | |
0.1, // near | |
1000 // far | |
); | |
camera.position.set(40, 40, 40); | |
// Create renderer | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(modelContainer.clientWidth, modelContainer.clientHeight); | |
renderer.shadowMap.enabled = true; | |
modelContainer.appendChild(renderer.domElement); | |
// Add lights | |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
directionalLight.position.set(30, 50, 20); | |
directionalLight.castShadow = true; | |
directionalLight.shadow.mapSize.width = 2048; | |
directionalLight.shadow.mapSize.height = 2048; | |
scene.add(directionalLight); | |
// Add helper light from the front | |
const fillLight = new THREE.DirectionalLight(0xffffff, 0.3); | |
fillLight.position.set(-30, 30, 30); | |
scene.add(fillLight); | |
// Add controls | |
controls = new THREE.OrbitControls(camera, renderer.domElement); | |
controls.enableDamping = true; | |
controls.dampingFactor = 0.05; | |
controls.minDistance = 10; | |
controls.maxDistance = 200; | |
// Create cabinet group | |
cabinetGroup = new THREE.Group(); | |
scene.add(cabinetGroup); | |
// Add grid and axes helpers (for debugging) | |
const gridHelper = new THREE.GridHelper(100, 100); | |
gridHelper.position.y = -0.01; // Just below the cabinet | |
scene.add(gridHelper); | |
// Initial render | |
updateCabinetModel(); | |
// Animation loop | |
function animate() { | |
requestAnimationFrame(animate); | |
controls.update(); | |
renderer.render(scene, camera); | |
} | |
animate(); | |
// Handle window resize | |
window.addEventListener('resize', onWindowResize); | |
} | |
function onWindowResize() { | |
camera.aspect = modelContainer.clientWidth / modelContainer.clientHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(modelContainer.clientWidth, modelContainer.clientHeight); | |
} | |
// Create cabinet model | |
function updateCabinetModel() { | |
showLoading(); | |
// Clear previous cabinet | |
while (cabinetGroup.children.length > 0) { | |
cabinetGroup.remove(cabinetGroup.children[0]); | |
} | |
// Reset door variables | |
leftDoor = null; | |
rightDoor = null; | |
leftDoorHinge = null; | |
rightDoorHinge = null; | |
// Get parameters | |
const width = parseFloat(document.getElementById('widthSlider').value); | |
const height = parseFloat(document.getElementById('heightSlider').value); | |
const depth = parseFloat(document.getElementById('depthSlider').value); | |
const materialType = document.getElementById('material').value; | |
const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100'); | |
const shelvesCount = parseInt(document.getElementById('shelvesSlider').value); | |
const drawersCount = parseInt(document.getElementById('drawersSlider').value || 0); | |
const cabinetType = document.getElementById('cabinetType').value; | |
// Create materials | |
const materials = { | |
exterior: createMaterial(materialType), | |
interior: new THREE.MeshStandardMaterial({ color: 0xF5F5F5, roughness: 0.7 }), | |
shelf: new THREE.MeshStandardMaterial({ color: 0xEAEAEA, roughness: 0.6 }), | |
hardware: new THREE.MeshStandardMaterial({ color: 0x333333 }) | |
}; | |
// Main cabinet structure | |
createCabinetStructure(width, height, depth, materials, cabinetType, isTwoDoors); | |
// Add shelves if needed | |
if (shelvesCount > 0) { | |
const availableHeight = height - 2; // account for frame | |
const shelfSpacing = availableHeight / (shelvesCount + 1); | |
for (let i = 0; i < shelvesCount; i++) { | |
const shelfY = -height/2 + 1 + (i + 1) * shelfSpacing; | |
createShelf(width - 2.5, 0.5, depth - 2, shelfY, materials.shelf); | |
} | |
} | |
// Add drawers for base cabinets | |
if (cabinetType === 'base' && drawersCount > 0) { | |
const drawerHeight = (height / 3) / drawersCount; | |
for (let i = 0; i < drawersCount; i++) { | |
const drawerY = -height/2 + (i + 0.5) * drawerHeight; | |
createDrawer(width * 0.8, drawerHeight * 0.8, depth * 0.7, drawerY, materials.exterior, materials.interior); | |
} | |
} | |
// Update camera position based on cabinet size | |
const maxDimension = Math.max(width, height, depth); | |
camera.position.set(maxDimension * 1.5, maxDimension * 1.2, maxDimension * 1.5); | |
controls.target.set(0, height/4, 0); | |
controls.update(); | |
// Show/hide door controls | |
if (cabinetType !== 'base' || drawersCount === 0) { | |
doorControls.classList.remove('hidden'); | |
} else { | |
doorControls.classList.add('hidden'); | |
} | |
// Hide loading after a short delay to ensure smooth UI | |
setTimeout(hideLoading, 500); | |
} | |
// Create cabinet frame and panels | |
function createCabinetStructure(width, height, depth, materials, type, isTwoDoors) { | |
const thickness = 0.75; // standard material thickness | |
// Bottom panel | |
createBox(width, thickness, depth, [0, -height/2 + thickness/2, 0], materials.exterior); | |
// Top panel (not for base cabinets with countertop) | |
if (type !== 'base') { | |
createBox(width, thickness, depth, [0, height/2 - thickness/2, 0], materials.exterior); | |
} | |
// Left side | |
createBox(thickness, height - 2*thickness, depth, | |
[-width/2 + thickness/2, 0, 0], materials.exterior); | |
// Right side | |
createBox(thickness, height - 2*thickness, depth, | |
[width/2 - thickness/2, 0, 0], materials.exterior); | |
// Back panel | |
createBox(width - 2*thickness, height - 2*thickness, thickness, | |
[0, 0, -depth/2 + thickness/2], materials.exterior); | |
// Middle divider for two doors | |
if (isTwoDoors) { | |
createBox(thickness, height - 2*thickness, depth, [0, 0, 0], materials.exterior); | |
// Create door hinges (invisible objects for rotation) | |
leftDoorHinge = new THREE.Group(); | |
leftDoorHinge.position.set(-width/2 + thickness, 0, depth/2 - thickness/2); | |
cabinetGroup.add(leftDoorHinge); | |
rightDoorHinge = new THREE.Group(); | |
rightDoorHinge.position.set(width/2 - thickness, 0, depth/2 - thickness/2); | |
cabinetGroup.add(rightDoorHinge); | |
// Left door (rotates around left edge) | |
leftDoor = createBox(width/2 - thickness, height - 2*thickness, thickness, | |
[0, 0, -thickness/2], materials.exterior); | |
leftDoorHinge.add(leftDoor); | |
// Right door (rotates around right edge) | |
rightDoor = createBox(width/2 - thickness, height - 2*thickness, thickness, | |
[0, 0, -thickness/2], materials.exterior); | |
rightDoorHinge.add(rightDoor); | |
// Add handles | |
// Left handle (positioned on the right side of left door) | |
const leftHandle = createBox(0.2, 2, 1, [width/4 - thickness/2, 0, 0.75], materials.hardware); | |
leftDoorHinge.add(leftHandle); | |
// Right handle (positioned on the left side of right door) | |
const rightHandle = createBox(0.2, 2, 1, [-width/4 + thickness/2, 0, 0.75], materials.hardware); | |
rightDoorHinge.add(rightHandle); | |
} else { | |
// Single door (right side hinge) | |
rightDoorHinge = new THREE.Group(); | |
rightDoorHinge.position.set(width/2 - thickness/2, 0, depth/2 - thickness/2); | |
cabinetGroup.add(rightDoorHinge); | |
rightDoor = createBox(width - thickness, height - 2*thickness, thickness, | |
[-width/2 + thickness/2, 0, -thickness/2], materials.exterior); | |
rightDoorHinge.add(rightDoor); | |
// Handle (positioned on the left side of the door) | |
const handle = createBox(0.2, 2, 1, [-width/2 + thickness + 1, 0, 0.75], materials.hardware); | |
rightDoorHinge.add(handle); | |
} | |
} | |
function createShelf(width, thickness, depth, yPos, material) { | |
createBox(width, thickness, depth, [0, yPos, 0], material); | |
// Add shelf supports | |
createBox(0.5, 1, 0.5, [-width/2 + 0.25, yPos - 0.75, 0], material); | |
createBox(0.5, 1, 0.5, [width/2 - 0.25, yPos - 0.75, 0], material); | |
} | |
function createDrawer(width, height, depth, yPos, frontMaterial, interiorMaterial) { | |
// Drawer front | |
createBox(width * 1.1, height * 1.1, 0.5, [0, yPos, depth/2 + 0.25], frontMaterial); | |
// Drawer box | |
createBox(width, height, depth, [0, yPos, 0], interiorMaterial); | |
// Handle (simplified as a horizontal bar) | |
createBox(3, 0.5, 0.3, [0, yPos, depth/2 + 0.4], frontMaterial); | |
} | |
function createBox(width, height, depth, position, material) { | |
const geometry = new THREE.BoxGeometry(width, height, depth); | |
const mesh = new THREE.Mesh(geometry, material); | |
mesh.position.set(position[0], position[1], position[2]); | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
if (material.color.getHex() !== 0x333333) { // Don't add shadow to hardware | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
} | |
cabinetGroup.add(mesh); | |
return mesh; | |
} | |
function createMaterial(type) { | |
const materials = { | |
oak: { | |
color: 0xC19A6B, | |
roughness: 0.5 | |
}, | |
maple: { | |
color: 0xE6CEAC, | |
roughness: 0.4 | |
}, | |
cherry: { | |
color: 0x8B4513, | |
roughness: 0.3 | |
}, | |
laminate: { | |
color: 0xFFFFFF, | |
roughness: 0.1, | |
metalness: 0.1 | |
} | |
}; | |
return new THREE.MeshStandardMaterial(materials[type]); | |
} | |
// Animate doors | |
function animateDoors(targetAngle) { | |
if (isAnimating) return; | |
isAnimating = true; | |
const duration = 1000; // ms | |
const startTime = Date.now(); | |
const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100'); | |
// Determine current angles | |
const leftStartAngle = leftDoorHinge ? leftDoorHinge.rotation.y : 0; | |
const rightStartAngle = rightDoorHinge ? rightDoorHinge.rotation.y : 0; | |
// Target angles | |
let leftTarget = 0; | |
let rightTarget = 0; | |
if (targetAngle === 0) { // Closing | |
leftTarget = 0; | |
rightTarget = 0; | |
doorState = 'closing'; | |
} else { // Opening | |
if (isTwoDoors) { | |
leftTarget = -targetAngle; // Left door opens inward (negative rotation) | |
rightTarget = targetAngle; // Right door opens inward (positive rotation) | |
} else { | |
rightTarget = targetAngle; // Single door opens inward (positive rotation) | |
} | |
doorState = 'opening'; | |
} | |
const animateFrame = () => { | |
const elapsed = Date.now() - startTime; | |
const progress = Math.min(elapsed / duration, 1); | |
// Easing function | |
const easeProgress = easeOutCubic(progress); | |
if (leftDoorHinge) { | |
leftDoorHinge.rotation.y = leftStartAngle + (leftTarget - leftStartAngle) * easeProgress; | |
} | |
if (rightDoorHinge) { | |
rightDoorHinge.rotation.y = rightStartAngle + (rightTarget - rightStartAngle) * easeProgress; | |
} | |
if (progress < 1) { | |
requestAnimationFrame(animateFrame); | |
} else { | |
isAnimating = false; | |
doorState = targetAngle === 0 ? 'closed' : 'open'; | |
} | |
}; | |
animateFrame(); | |
} | |
// Easing function for smooth animation | |
function easeOutCubic(t) { | |
return 1 - Math.pow(1 - t, 3); | |
} | |
// Open/close door functions | |
function openDoors() { | |
if (doorState === 'closed' || doorState === 'closing') { | |
animateDoors(Math.PI/2); // 90 degrees in radians | |
} | |
} | |
function closeDoors() { | |
if (doorState === 'open' || doorState === 'opening') { | |
animateDoors(0); | |
} | |
} | |
function showLoading() { | |
loadingOverlay.style.display = 'flex'; | |
let progress = 0; | |
const interval = setInterval(() => { | |
progress += 10; | |
const offset = 157 * (1 - progress/100); | |
progressRing.style.strokeDashoffset = offset; | |
if (progress >= 100) { | |
clearInterval(interval); | |
} | |
}, 50); | |
} | |
function hideLoading() { | |
loadingOverlay.style.display = 'none'; | |
progressRing.style.strokeDashoffset = '157'; | |
doorState = 'closed'; // Reset door state after loading | |
} | |
// Update price calculation | |
function updatePrice() { | |
const width = parseFloat(document.getElementById('widthSlider').value); | |
const height = parseFloat(document.getElementById('heightSlider').value); | |
const depth = parseFloat(document.getElementById('depthSlider').value); | |
const material = document.getElementById('material').value; | |
const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100'); | |
const shelvesCount = parseInt(document.getElementById('shelvesSlider').value); | |
const drawersCount = parseInt(document.getElementById('drawersSlider').value || 0); | |
const cabinetType = document.getElementById('cabinetType').value; | |
// Calculate base price | |
let basePrice = 100 + (width * height * depth) / 100; | |
// Adjust by type | |
if (cabinetType === 'base') basePrice *= 1.3; | |
else if (cabinetType === 'tall') basePrice *= 1.5; | |
// Material multipliers | |
if (material === 'maple') basePrice *= 1.2; | |
else if (material === 'cherry') basePrice *= 1.4; | |
// Features | |
if (isTwoDoors) basePrice += 50; | |
if (shelvesCount > 1) basePrice += (shelvesCount - 1) * 25; | |
if (drawersCount > 0) basePrice += drawersCount * 60; | |
document.getElementById('totalPrice').textContent = `$${basePrice.toFixed(2)}`; | |
} | |
// Event listeners | |
openBtn.addEventListener('click', openDoors); | |
closeBtn.addEventListener('click', closeDoors); | |
document.getElementById('widthSlider').addEventListener('input', function() { | |
document.getElementById('widthValue').textContent = this.value; | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('heightSlider').addEventListener('input', function() { | |
document.getElementById('heightValue').textContent = this.value; | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('depthSlider').addEventListener('input', function() { | |
document.getElementById('depthValue').textContent = this.value; | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('material').addEventListener('change', function() { | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('cabinetType').addEventListener('change', function() { | |
// Adjust default dimensions | |
if (this.value === 'base') { | |
document.getElementById('heightSlider').value = 34.5; | |
document.getElementById('depthSlider').value = 24; | |
document.getElementById('heightValue').textContent = '34.5'; | |
document.getElementById('depthValue').textContent = '24'; | |
document.getElementById('drawersContainer').classList.remove('hidden'); | |
} else { | |
if (this.value === 'wall') { | |
document.getElementById('heightSlider').value = 36; | |
document.getElementById('depthSlider').value = 12; | |
} else if (this.value === 'tall') { | |
document.getElementById('heightSlider').value = 84; | |
document.getElementById('depthSlider').value = 24; | |
} | |
document.getElementById('heightValue').textContent = document.getElementById('heightSlider').value; | |
document.getElementById('depthValue').textContent = document.getElementById('depthSlider').value; | |
document.getElementById('drawersContainer').classList.add('hidden'); | |
} | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('oneDoor').addEventListener('click', function() { | |
this.classList.add('bg-blue-100', 'text-blue-700'); | |
document.getElementById('twoDoors').classList.remove('bg-blue-100', 'text-blue-700'); | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('twoDoors').addEventListener('click', function() { | |
this.classList.add('bg-blue-100', 'text-blue-700'); | |
document.getElementById('oneDoor').classList.remove('bg-blue-100', 'text-blue-700'); | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('shelvesSlider').addEventListener('input', function() { | |
document.getElementById('shelvesValue').textContent = this.value; | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('drawersSlider').addEventListener('input', function() { | |
document.getElementById('drawersValue').textContent = this.value; | |
updateCabinetModel(); | |
updatePrice(); | |
}); | |
document.getElementById('downloadBtn').addEventListener('click', function() { | |
alert('In a complete implementation, this would export your cabinet as a 3D model file.'); | |
}); | |
// Initialize | |
window.addEventListener('load', function() { | |
initThreeJS(); | |
updatePrice(); | |
// Set one door as active by default | |
document.getElementById('oneDoor').classList.add('bg-blue-100', 'text-blue-700'); | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gladiopeace/cabinet" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |