Spaces:
Running
Running
Add 1 files
Browse files- index.html +390 -129
index.html
CHANGED
@@ -38,10 +38,21 @@
|
|
38 |
background-color: #f5f3f0;
|
39 |
border-radius: 8px;
|
40 |
overflow: hidden;
|
|
|
41 |
}
|
42 |
#priceSection {
|
43 |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
44 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
</style>
|
46 |
</head>
|
47 |
<body class="bg-gray-50">
|
@@ -105,6 +116,24 @@
|
|
105 |
</div>
|
106 |
</div>
|
107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
<!-- Material -->
|
109 |
<div class="mb-6 slider-container">
|
110 |
<div class="flex justify-between items-center mb-2">
|
@@ -153,6 +182,10 @@
|
|
153 |
<input type="checkbox" id="lighting" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
|
154 |
<span class="text-gray-700">LED Lighting</span>
|
155 |
</label>
|
|
|
|
|
|
|
|
|
156 |
</div>
|
157 |
</div>
|
158 |
</div>
|
@@ -167,14 +200,18 @@
|
|
167 |
<span class="text-gray-600">Material Upgrade</span>
|
168 |
<span id="materialPrice" class="font-medium">$50.00</span>
|
169 |
</div>
|
170 |
-
<div class="flex justify-between items-center mb-
|
171 |
<span class="text-gray-600">Features</span>
|
172 |
<span id="featuresPrice" class="font-medium">$75.00</span>
|
173 |
</div>
|
|
|
|
|
|
|
|
|
174 |
<div class="border-t border-gray-300 pt-4">
|
175 |
<div class="flex justify-between items-center">
|
176 |
<span class="text-xl font-bold text-gray-800">Total</span>
|
177 |
-
<span id="totalPrice" class="text-2xl font-bold text-indigo-600">$
|
178 |
</div>
|
179 |
</div>
|
180 |
</div>
|
@@ -197,7 +234,7 @@
|
|
197 |
</div>
|
198 |
</div>
|
199 |
<div id="modelViewer" class="w-full h-96 rounded-b-xl">
|
200 |
-
<div id="loadingOverlay" class="absolute inset-0 bg-gray-100 bg-opacity-70 flex flex-col items-center justify-center">
|
201 |
<div class="w-16 h-16 relative mb-4">
|
202 |
<svg class="w-full h-full" viewBox="0 0 100 100">
|
203 |
<circle class="text-gray-400" cx="50" cy="50" r="40" stroke-width="8" stroke="currentColor" fill="none"></circle>
|
@@ -225,6 +262,14 @@
|
|
225 |
<span class="text-gray-600">Material Thickness:</span>
|
226 |
<span class="font-medium">0.75"</span>
|
227 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
</div>
|
229 |
</div>
|
230 |
|
@@ -272,9 +317,11 @@
|
|
272 |
const softClose = document.getElementById('softClose');
|
273 |
const pullOutShelves = document.getElementById('pullOutShelves');
|
274 |
const lighting = document.getElementById('lighting');
|
|
|
275 |
const basePrice = document.getElementById('basePrice');
|
276 |
const materialPrice = document.getElementById('materialPrice');
|
277 |
const featuresPrice = document.getElementById('featuresPrice');
|
|
|
278 |
const totalPrice = document.getElementById('totalPrice');
|
279 |
const dimensionsDisplay = document.getElementById('dimensionsDisplay');
|
280 |
const capacityDisplay = document.getElementById('capacityDisplay');
|
@@ -283,6 +330,14 @@
|
|
283 |
const downloadUSDZ = document.getElementById('downloadUSDZ');
|
284 |
const downloadGLB = document.getElementById('downloadGLB');
|
285 |
const progressRing = document.querySelector('.progress-ring');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
|
287 |
// Three.js variables
|
288 |
let scene, camera, renderer, controls, cabinetGroup;
|
@@ -298,7 +353,7 @@
|
|
298 |
camera.position.set(30, 30, 30);
|
299 |
|
300 |
// Create renderer
|
301 |
-
renderer = new THREE.WebGLRenderer({ antialias: true });
|
302 |
renderer.setSize(modelViewer.clientWidth, modelViewer.clientHeight);
|
303 |
renderer.shadowMap.enabled = true;
|
304 |
modelViewer.appendChild(renderer.domElement);
|
@@ -343,13 +398,12 @@
|
|
343 |
|
344 |
function animate() {
|
345 |
requestAnimationFrame(animate);
|
346 |
-
controls.update();
|
347 |
-
renderer.render(scene, camera);
|
348 |
}
|
349 |
|
350 |
-
// Create cabinet geometry
|
351 |
function updateCabinetModel() {
|
352 |
-
// Simulate loading (in a real app, this would be replaced with actual model generation)
|
353 |
showLoading();
|
354 |
|
355 |
// Clear previous cabinet
|
@@ -364,151 +418,316 @@
|
|
364 |
const type = cabinetType.value;
|
365 |
const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
|
366 |
const colorValue = color.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
|
368 |
-
// Set material (simplified for demo)
|
369 |
-
let material;
|
370 |
if (colorValue === 'natural') {
|
371 |
-
|
372 |
color: 0xC19A6B,
|
373 |
-
roughness: 0.4,
|
374 |
metalness: 0.1
|
375 |
-
}
|
376 |
} else if (colorValue === 'white') {
|
377 |
-
|
378 |
color: 0xF5F5F5,
|
379 |
roughness: 0.1,
|
380 |
metalness: 0.05
|
381 |
-
}
|
382 |
} else if (colorValue === 'black') {
|
383 |
-
|
384 |
color: 0x222222,
|
385 |
roughness: 0.3,
|
386 |
metalness: 0.2
|
387 |
-
}
|
388 |
} else if (colorValue === 'gray') {
|
389 |
-
|
390 |
color: 0x808080,
|
391 |
roughness: 0.3,
|
392 |
metalness: 0.15
|
393 |
-
}
|
394 |
} else if (colorValue === 'navy') {
|
395 |
-
|
396 |
color: 0x2C3E50,
|
397 |
roughness: 0.3,
|
398 |
metalness: 0.1
|
399 |
-
}
|
400 |
} else { // green
|
401 |
-
|
402 |
color: 0x3B5E2E,
|
403 |
roughness: 0.3,
|
404 |
metalness: 0.1
|
405 |
-
}
|
406 |
}
|
407 |
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
const
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
|
424 |
-
//
|
425 |
-
const
|
426 |
-
const
|
427 |
-
|
428 |
-
|
|
|
|
|
429 |
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
434 |
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
|
|
|
|
440 |
|
441 |
-
//
|
442 |
-
|
443 |
-
|
444 |
-
const divider = new THREE.Mesh(dividerGeometry, material);
|
445 |
-
divider.position.set(0, height/2, 0);
|
446 |
-
cabinetGroup.add(divider);
|
447 |
-
}
|
448 |
|
449 |
-
//
|
450 |
-
const
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
// Left door
|
456 |
-
const leftDoorGeometry = new THREE.BoxGeometry(doorWidth, doorHeight, doorThickness);
|
457 |
-
const leftDoor = new THREE.Mesh(leftDoorGeometry, material);
|
458 |
-
leftDoor.position.set(-(width - doorWidth)/2 + doorThickness/2, height/2, depth/2 - doorThickness/2);
|
459 |
-
cabinetGroup.add(leftDoor);
|
460 |
-
|
461 |
-
// Right door
|
462 |
-
const rightDoorGeometry = new THREE.BoxGeometry(doorWidth, doorHeight, doorThickness);
|
463 |
-
const rightDoor = new THREE.Mesh(rightDoorGeometry, material);
|
464 |
-
rightDoor.position.set((width - doorWidth)/2 - doorThickness/2, height/2, depth/2 - doorThickness/2);
|
465 |
-
cabinetGroup.add(rightDoor);
|
466 |
-
} else {
|
467 |
-
doorWidth = width;
|
468 |
-
|
469 |
-
// Single door
|
470 |
-
const doorGeometry = new THREE.BoxGeometry(doorWidth, doorHeight, doorThickness);
|
471 |
-
const door = new THREE.Mesh(doorGeometry, material);
|
472 |
-
door.position.set(0, height/2, depth/2 - doorThickness/2);
|
473 |
-
cabinetGroup.add(door);
|
474 |
-
}
|
475 |
|
476 |
-
//
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
drawer.position.set(0, drawerHeight/2 + thickness, depth/4 - depth/8);
|
482 |
-
cabinetGroup.add(drawer);
|
483 |
-
|
484 |
-
// Drawer handle
|
485 |
-
const handleGeometry = new THREE.CylinderGeometry(0.2, 0.2, 2, 16);
|
486 |
-
const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
|
487 |
-
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
|
488 |
-
handle.rotation.z = Math.PI/2;
|
489 |
-
handle.position.set(0, drawerHeight + thickness - 1, depth * 0.25 + 0.4);
|
490 |
-
cabinetGroup.add(handle);
|
491 |
-
}
|
492 |
|
493 |
-
//
|
494 |
-
|
495 |
-
|
496 |
|
497 |
-
//
|
498 |
-
|
|
|
|
|
499 |
|
500 |
-
//
|
501 |
-
const
|
502 |
-
|
503 |
-
|
504 |
-
const capacity = (interiorWidth * interiorHeight * interiorDepth) / 1728; // convert to cubic feet
|
505 |
|
506 |
-
|
507 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
508 |
}
|
509 |
|
510 |
function showLoading() {
|
511 |
-
loadingOverlay.style.
|
|
|
512 |
let progress = 0;
|
513 |
const interval = setInterval(() => {
|
514 |
progress += 10;
|
@@ -517,11 +736,12 @@
|
|
517 |
if (progress >= 100) {
|
518 |
clearInterval(interval);
|
519 |
}
|
520 |
-
},
|
521 |
}
|
522 |
|
523 |
function hideLoading() {
|
524 |
-
loadingOverlay.style.
|
|
|
525 |
}
|
526 |
|
527 |
// Update price calculation
|
@@ -534,35 +754,45 @@
|
|
534 |
const hasSoftClose = softClose.checked;
|
535 |
const hasPullOutShelves = pullOutShelves.checked;
|
536 |
const hasLighting = lighting.checked;
|
|
|
|
|
|
|
537 |
|
538 |
// Calculate base price based on size and type
|
539 |
let base = 100;
|
540 |
const sizeFactor = (width * height * depth) / 10000;
|
541 |
base += sizeFactor * 100;
|
542 |
|
543 |
-
if (cabinetType.value === 'base') base *= 1.
|
544 |
-
else if (cabinetType.value === 'tall') base *= 1.
|
545 |
-
else if (cabinetType.value === 'island') base *= 1.
|
546 |
|
547 |
// Material premium
|
548 |
let matPremium = 0;
|
549 |
-
if (mat === 'cherry' || mat === 'walnut') matPremium =
|
550 |
-
else if (mat === 'maple') matPremium =
|
551 |
-
else if (mat === 'oak') matPremium =
|
552 |
-
else if (mat === 'painted') matPremium =
|
|
|
553 |
|
554 |
// Features
|
555 |
let featuresPremium = 0;
|
556 |
-
if (hasSoftClose) featuresPremium +=
|
557 |
-
if (hasPullOutShelves) featuresPremium +=
|
558 |
-
if (hasLighting) featuresPremium +=
|
559 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
560 |
|
561 |
// Update displays
|
562 |
basePrice.textContent = `$${base.toFixed(2)}`;
|
563 |
materialPrice.textContent = `$${matPremium.toFixed(2)}`;
|
564 |
featuresPrice.textContent = `$${featuresPremium.toFixed(2)}`;
|
565 |
-
|
|
|
566 |
}
|
567 |
|
568 |
// Event listeners
|
@@ -598,9 +828,35 @@
|
|
598 |
updatePrice();
|
599 |
});
|
600 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
601 |
cabinetType.addEventListener('change', () => {
|
602 |
cabinetTypeDisplay.textContent = cabinetType.options[cabinetType.selectedIndex].text;
|
603 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
604 |
// Adjust default dimensions based on cabinet type
|
605 |
if (cabinetType.value === 'wall') {
|
606 |
heightSlider.value = 36;
|
@@ -636,12 +892,17 @@
|
|
636 |
|
637 |
color.addEventListener('change', () => {
|
638 |
colorDisplay.textContent = color.options[color.selectedIndex].text;
|
|
|
639 |
updateCabinetModel();
|
640 |
});
|
641 |
|
642 |
softClose.addEventListener('change', updatePrice);
|
643 |
pullOutShelves.addEventListener('change', updatePrice);
|
644 |
lighting.addEventListener('change', updatePrice);
|
|
|
|
|
|
|
|
|
645 |
|
646 |
// Simulate download buttons (in a real app, these would generate actual files)
|
647 |
downloadUSDZ.addEventListener('click', () => {
|
@@ -649,7 +910,7 @@
|
|
649 |
});
|
650 |
|
651 |
downloadGLB.addEventListener('click', () => {
|
652 |
-
alert('In a real implementation, this would generate and download a GLB file of your cabinet design.');
|
653 |
});
|
654 |
|
655 |
// Initialize
|
|
|
38 |
background-color: #f5f3f0;
|
39 |
border-radius: 8px;
|
40 |
overflow: hidden;
|
41 |
+
position: relative;
|
42 |
}
|
43 |
#priceSection {
|
44 |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
45 |
}
|
46 |
+
.door-handle {
|
47 |
+
position: relative;
|
48 |
+
z-index: 5;
|
49 |
+
}
|
50 |
+
.shelf {
|
51 |
+
box-shadow: 0 -2px 5px rgba(0,0,0,0.1);
|
52 |
+
}
|
53 |
+
#loadingOverlay {
|
54 |
+
transition: opacity 0.3s ease;
|
55 |
+
}
|
56 |
</style>
|
57 |
</head>
|
58 |
<body class="bg-gray-50">
|
|
|
116 |
</div>
|
117 |
</div>
|
118 |
|
119 |
+
<!-- Shelves -->
|
120 |
+
<div class="mb-6 slider-container">
|
121 |
+
<div class="flex justify-between items-center mb-2">
|
122 |
+
<label class="text-gray-700 font-medium">Number of Shelves</label>
|
123 |
+
<span id="shelvesValue" class="text-gray-500">1</span>
|
124 |
+
</div>
|
125 |
+
<input id="shelvesSlider" type="range" min="0" max="5" value="1" step="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
126 |
+
</div>
|
127 |
+
|
128 |
+
<!-- Drawers (only for base cabinets) -->
|
129 |
+
<div class="mb-6 slider-container" id="drawersContainer" style="display: none;">
|
130 |
+
<div class="flex justify-between items-center mb-2">
|
131 |
+
<label class="text-gray-700 font-medium">Number of Drawers</label>
|
132 |
+
<span id="drawersValue" class="text-gray-500">0</span>
|
133 |
+
</div>
|
134 |
+
<input id="drawersSlider" type="range" min="0" max="3" value="0" step="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
135 |
+
</div>
|
136 |
+
|
137 |
<!-- Material -->
|
138 |
<div class="mb-6 slider-container">
|
139 |
<div class="flex justify-between items-center mb-2">
|
|
|
182 |
<input type="checkbox" id="lighting" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
|
183 |
<span class="text-gray-700">LED Lighting</span>
|
184 |
</label>
|
185 |
+
<label class="flex items-center space-x-3">
|
186 |
+
<input type="checkbox" id="glassDoors" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
|
187 |
+
<span class="text-gray-700">Glass Doors</span>
|
188 |
+
</label>
|
189 |
</div>
|
190 |
</div>
|
191 |
</div>
|
|
|
200 |
<span class="text-gray-600">Material Upgrade</span>
|
201 |
<span id="materialPrice" class="font-medium">$50.00</span>
|
202 |
</div>
|
203 |
+
<div class="flex justify-between items-center mb-2">
|
204 |
<span class="text-gray-600">Features</span>
|
205 |
<span id="featuresPrice" class="font-medium">$75.00</span>
|
206 |
</div>
|
207 |
+
<div class="flex justify-between items-center mb-4">
|
208 |
+
<span class="text-gray-600">Options</span>
|
209 |
+
<span id="optionsPrice" class="font-medium">$25.00</span>
|
210 |
+
</div>
|
211 |
<div class="border-t border-gray-300 pt-4">
|
212 |
<div class="flex justify-between items-center">
|
213 |
<span class="text-xl font-bold text-gray-800">Total</span>
|
214 |
+
<span id="totalPrice" class="text-2xl font-bold text-indigo-600">$450.00</span>
|
215 |
</div>
|
216 |
</div>
|
217 |
</div>
|
|
|
234 |
</div>
|
235 |
</div>
|
236 |
<div id="modelViewer" class="w-full h-96 rounded-b-xl">
|
237 |
+
<div id="loadingOverlay" class="absolute inset-0 bg-gray-100 bg-opacity-70 flex flex-col items-center justify-center opacity-0 pointer-events-none">
|
238 |
<div class="w-16 h-16 relative mb-4">
|
239 |
<svg class="w-full h-full" viewBox="0 0 100 100">
|
240 |
<circle class="text-gray-400" cx="50" cy="50" r="40" stroke-width="8" stroke="currentColor" fill="none"></circle>
|
|
|
262 |
<span class="text-gray-600">Material Thickness:</span>
|
263 |
<span class="font-medium">0.75"</span>
|
264 |
</div>
|
265 |
+
<div class="flex justify-between">
|
266 |
+
<span class="text-gray-600">Shelves:</span>
|
267 |
+
<span id="shelvesDisplay" class="font-medium">1</span>
|
268 |
+
</div>
|
269 |
+
<div class="flex justify-between" id="drawersDisplayContainer" style="display: none;">
|
270 |
+
<span class="text-gray-600">Drawers:</span>
|
271 |
+
<span id="drawersDisplay" class="font-medium">0</span>
|
272 |
+
</div>
|
273 |
</div>
|
274 |
</div>
|
275 |
|
|
|
317 |
const softClose = document.getElementById('softClose');
|
318 |
const pullOutShelves = document.getElementById('pullOutShelves');
|
319 |
const lighting = document.getElementById('lighting');
|
320 |
+
const glassDoors = document.getElementById('glassDoors');
|
321 |
const basePrice = document.getElementById('basePrice');
|
322 |
const materialPrice = document.getElementById('materialPrice');
|
323 |
const featuresPrice = document.getElementById('featuresPrice');
|
324 |
+
const optionsPrice = document.getElementById('optionsPrice');
|
325 |
const totalPrice = document.getElementById('totalPrice');
|
326 |
const dimensionsDisplay = document.getElementById('dimensionsDisplay');
|
327 |
const capacityDisplay = document.getElementById('capacityDisplay');
|
|
|
330 |
const downloadUSDZ = document.getElementById('downloadUSDZ');
|
331 |
const downloadGLB = document.getElementById('downloadGLB');
|
332 |
const progressRing = document.querySelector('.progress-ring');
|
333 |
+
const shelvesSlider = document.getElementById('shelvesSlider');
|
334 |
+
const shelvesValue = document.getElementById('shelvesValue');
|
335 |
+
const shelvesDisplay = document.getElementById('shelvesDisplay');
|
336 |
+
const drawersSlider = document.getElementById('drawersSlider');
|
337 |
+
const drawersValue = document.getElementById('drawersValue');
|
338 |
+
const drawersContainer = document.getElementById('drawersContainer');
|
339 |
+
const drawersDisplay = document.getElementById('drawersDisplay');
|
340 |
+
const drawersDisplayContainer = document.getElementById('drawersDisplayContainer');
|
341 |
|
342 |
// Three.js variables
|
343 |
let scene, camera, renderer, controls, cabinetGroup;
|
|
|
353 |
camera.position.set(30, 30, 30);
|
354 |
|
355 |
// Create renderer
|
356 |
+
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
357 |
renderer.setSize(modelViewer.clientWidth, modelViewer.clientHeight);
|
358 |
renderer.shadowMap.enabled = true;
|
359 |
modelViewer.appendChild(renderer.domElement);
|
|
|
398 |
|
399 |
function animate() {
|
400 |
requestAnimationFrame(animate);
|
401 |
+
if (controls) controls.update();
|
402 |
+
if (renderer) renderer.render(scene, camera);
|
403 |
}
|
404 |
|
405 |
+
// Create cabinet geometry with detailed components
|
406 |
function updateCabinetModel() {
|
|
|
407 |
showLoading();
|
408 |
|
409 |
// Clear previous cabinet
|
|
|
418 |
const type = cabinetType.value;
|
419 |
const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
|
420 |
const colorValue = color.value;
|
421 |
+
const shelvesCount = parseInt(shelvesSlider.value);
|
422 |
+
const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
|
423 |
+
const hasGlassDoors = glassDoors.checked;
|
424 |
+
|
425 |
+
// Set main material
|
426 |
+
let mainMaterial = createMaterial(colorValue, false);
|
427 |
+
let interiorMaterial = new THREE.MeshStandardMaterial({
|
428 |
+
color: 0xF5F5F5,
|
429 |
+
roughness: 0.7,
|
430 |
+
metalness: 0.0
|
431 |
+
});
|
432 |
+
let shelfMaterial = new THREE.MeshStandardMaterial({
|
433 |
+
color: 0xEAEAEA,
|
434 |
+
roughness: 0.6,
|
435 |
+
metalness: 0.0
|
436 |
+
});
|
437 |
+
|
438 |
+
// Cabinet dimensions
|
439 |
+
const thickness = 0.75;
|
440 |
+
const doorThickness = 1.0;
|
441 |
+
const handleSize = 0.2;
|
442 |
+
const shelfThickness = 0.5;
|
443 |
+
|
444 |
+
// Bottom panel
|
445 |
+
createPanel(width, thickness, depth,
|
446 |
+
[0, thickness/2, 0],
|
447 |
+
mainMaterial);
|
448 |
+
|
449 |
+
// Top panel
|
450 |
+
createPanel(width, thickness, depth,
|
451 |
+
[0, height - thickness/2, 0],
|
452 |
+
mainMaterial);
|
453 |
+
|
454 |
+
// Left side
|
455 |
+
createPanel(thickness, height - 2*thickness, depth,
|
456 |
+
[-width/2 + thickness/2, height/2, 0],
|
457 |
+
mainMaterial);
|
458 |
+
|
459 |
+
// Right side
|
460 |
+
createPanel(thickness, height - 2*thickness, depth,
|
461 |
+
[width/2 - thickness/2, height/2, 0],
|
462 |
+
mainMaterial);
|
463 |
+
|
464 |
+
// Back panel
|
465 |
+
createPanel(width - 2*thickness, height - 2*thickness, thickness,
|
466 |
+
[0, height/2, -depth/2 + thickness/2],
|
467 |
+
mainMaterial);
|
468 |
+
|
469 |
+
// Middle divider if two doors
|
470 |
+
if (isTwoDoors) {
|
471 |
+
createPanel(thickness, height - 2*thickness - (drawersCount > 0 ? (height * 0.33) : 0), depth,
|
472 |
+
[0, height/2 - (drawersCount > 0 ? (height * 0.33)/2 : 0), 0],
|
473 |
+
mainMaterial);
|
474 |
+
}
|
475 |
+
|
476 |
+
// Create doors
|
477 |
+
const doorHeight = height - thickness - (drawersCount > 0 ? (height * 0.33) : 0);
|
478 |
+
const doorYPosition = height/2 - (drawersCount > 0 ? (height * 0.33)/2 : 0);
|
479 |
+
|
480 |
+
if (isTwoDoors) {
|
481 |
+
const doorWidth = (width - thickness) / 2;
|
482 |
+
|
483 |
+
// Left door
|
484 |
+
createDoor(doorWidth, doorHeight, doorThickness,
|
485 |
+
[-(width - doorWidth)/2 + doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
|
486 |
+
mainMaterial, hasGlassDoors, true);
|
487 |
+
|
488 |
+
// Right door
|
489 |
+
createDoor(doorWidth, doorHeight, doorThickness,
|
490 |
+
[(width - doorWidth)/2 - doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
|
491 |
+
mainMaterial, hasGlassDoors, false);
|
492 |
+
} else {
|
493 |
+
// Single door
|
494 |
+
const doorWidth = width;
|
495 |
+
createDoor(doorWidth, doorHeight, doorThickness,
|
496 |
+
[0, doorYPosition, depth/2 - doorThickness/2],
|
497 |
+
mainMaterial, hasGlassDoors, false);
|
498 |
+
}
|
499 |
+
|
500 |
+
// Add shelves
|
501 |
+
const availableHeight = height - 2*thickness - (drawersCount > 0 ? (height * 0.33) : 0);
|
502 |
+
const shelfSpacing = availableHeight / (shelvesCount + 1);
|
503 |
+
|
504 |
+
for (let i = 0; i < shelvesCount; i++) {
|
505 |
+
const shelfY = thickness + (i + 1) * shelfSpacing - (drawersCount > 0 ? (height * 0.33)/2 : 0);
|
506 |
+
createShelf(width - 2*thickness - 0.5, shelfThickness, depth - thickness - 0.5,
|
507 |
+
[0, shelfY, -0.25],
|
508 |
+
shelfMaterial);
|
509 |
+
}
|
510 |
+
|
511 |
+
// Add drawers for base cabinets
|
512 |
+
if (type === 'base' && drawersCount > 0) {
|
513 |
+
const drawerHeight = (height * 0.33) / drawersCount;
|
514 |
+
const drawerFrontMaterial = createMaterial(colorValue, true); // Slightly different for drawer fronts
|
515 |
+
|
516 |
+
for (let i = 0; i < drawersCount; i++) {
|
517 |
+
const drawerY = thickness + i * drawerHeight + drawerHeight/2;
|
518 |
+
createDrawer(width, drawerHeight, depth * 0.7,
|
519 |
+
[0, drawerY, depth/4 - depth/8],
|
520 |
+
drawerFrontMaterial, mainMaterial);
|
521 |
+
}
|
522 |
+
}
|
523 |
+
|
524 |
+
// Adjust camera target based on cabinet size
|
525 |
+
controls.target.set(0, height/2, 0);
|
526 |
+
controls.update();
|
527 |
+
|
528 |
+
// Update capacity calculation
|
529 |
+
const interiorWidth = width - 2*thickness;
|
530 |
+
const interiorHeight = height - 2*thickness - (drawersCount > 0 ? (height * 0.33) : 0);
|
531 |
+
const interiorDepth = depth - thickness;
|
532 |
+
const capacity = (interiorWidth * interiorHeight * interiorDepth) / 1728; // convert to cubic feet
|
533 |
+
|
534 |
+
// Hide loading after a delay
|
535 |
+
setTimeout(hideLoading, 800);
|
536 |
+
|
537 |
+
// Update displays
|
538 |
+
capacityDisplay.textContent = `${capacity.toFixed(1)} cu.ft`;
|
539 |
+
dimensionsDisplay.textContent = `${width}" W × ${height}" H × ${depth}" D`;
|
540 |
+
shelvesDisplay.textContent = shelvesCount;
|
541 |
+
}
|
542 |
+
|
543 |
+
function createMaterial(colorValue, isDrawerFront = false) {
|
544 |
+
let materialOptions = {};
|
545 |
|
|
|
|
|
546 |
if (colorValue === 'natural') {
|
547 |
+
materialOptions = {
|
548 |
color: 0xC19A6B,
|
549 |
+
roughness: isDrawerFront ? 0.3 : 0.4,
|
550 |
metalness: 0.1
|
551 |
+
};
|
552 |
} else if (colorValue === 'white') {
|
553 |
+
materialOptions = {
|
554 |
color: 0xF5F5F5,
|
555 |
roughness: 0.1,
|
556 |
metalness: 0.05
|
557 |
+
};
|
558 |
} else if (colorValue === 'black') {
|
559 |
+
materialOptions = {
|
560 |
color: 0x222222,
|
561 |
roughness: 0.3,
|
562 |
metalness: 0.2
|
563 |
+
};
|
564 |
} else if (colorValue === 'gray') {
|
565 |
+
materialOptions = {
|
566 |
color: 0x808080,
|
567 |
roughness: 0.3,
|
568 |
metalness: 0.15
|
569 |
+
};
|
570 |
} else if (colorValue === 'navy') {
|
571 |
+
materialOptions = {
|
572 |
color: 0x2C3E50,
|
573 |
roughness: 0.3,
|
574 |
metalness: 0.1
|
575 |
+
};
|
576 |
} else { // green
|
577 |
+
materialOptions = {
|
578 |
color: 0x3B5E2E,
|
579 |
roughness: 0.3,
|
580 |
metalness: 0.1
|
581 |
+
};
|
582 |
}
|
583 |
|
584 |
+
return new THREE.MeshStandardMaterial(materialOptions);
|
585 |
+
}
|
586 |
+
|
587 |
+
function createPanel(width, height, depth, position, material) {
|
588 |
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
589 |
+
const panel = new THREE.Mesh(geometry, material);
|
590 |
+
panel.castShadow = true;
|
591 |
+
panel.receiveShadow = true;
|
592 |
+
panel.position.set(position[0], position[1], position[2]);
|
593 |
+
cabinetGroup.add(panel);
|
594 |
+
return panel;
|
595 |
+
}
|
596 |
+
|
597 |
+
function createDoor(width, height, thickness, position, material, hasGlass, isLeftDoor) {
|
598 |
+
const doorGroup = new THREE.Group();
|
599 |
+
doorGroup.position.set(position[0], position[1], position[2]);
|
600 |
+
|
601 |
+
// Door frame
|
602 |
+
const frameGeometry = new THREE.BoxGeometry(width, height, thickness);
|
603 |
+
const frame = new THREE.Mesh(frameGeometry, material);
|
604 |
+
frame.castShadow = true;
|
605 |
+
frame.receiveShadow = true;
|
606 |
+
doorGroup.add(frame);
|
607 |
+
|
608 |
+
// Glass panel if selected
|
609 |
+
if (hasGlass) {
|
610 |
+
const glassWidth = width * 0.8;
|
611 |
+
const glassHeight = height * 0.7;
|
612 |
+
const glassGeometry = new THREE.BoxGeometry(glassWidth, glassHeight, thickness * 0.1);
|
613 |
+
const glassMaterial = new THREE.MeshPhysicalMaterial({
|
614 |
+
color: 0x88ccff,
|
615 |
+
transmission: 0.8,
|
616 |
+
roughness: 0.1,
|
617 |
+
metalness: 0.0,
|
618 |
+
transparent: true,
|
619 |
+
opacity: 0.7,
|
620 |
+
ior: 1.5,
|
621 |
+
thickness: 0.1
|
622 |
+
});
|
623 |
+
const glass = new THREE.Mesh(glassGeometry, glassMaterial);
|
624 |
+
glass.position.z = thickness * 0.45;
|
625 |
+
doorGroup.add(glass);
|
626 |
+
}
|
627 |
|
628 |
+
// Handle
|
629 |
+
const handleGeometry = new THREE.CylinderGeometry(0.15, 0.15, 3, 16);
|
630 |
+
const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
|
631 |
+
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
|
632 |
+
handle.rotation.z = Math.PI/2;
|
633 |
+
handle.position.set(isLeftDoor ? -width/2 + 2 : width/2 - 2, 0, thickness);
|
634 |
+
doorGroup.add(handle);
|
635 |
|
636 |
+
cabinetGroup.add(doorGroup);
|
637 |
+
return doorGroup;
|
638 |
+
}
|
639 |
+
|
640 |
+
function createShelf(width, thickness, depth, position, material) {
|
641 |
+
const shelfGroup = new THREE.Group();
|
642 |
+
shelfGroup.position.set(position[0], position[1], position[2]);
|
643 |
+
|
644 |
+
// Main shelf
|
645 |
+
const shelfGeometry = new THREE.BoxGeometry(width, thickness, depth);
|
646 |
+
const shelf = new THREE.Mesh(shelfGeometry, material);
|
647 |
+
shelf.castShadow = true;
|
648 |
+
shelf.receiveShadow = true;
|
649 |
+
shelfGroup.add(shelf);
|
650 |
+
|
651 |
+
// Shelf supports
|
652 |
+
const supportGeometry = new THREE.BoxGeometry(0.5, 1, 0.5);
|
653 |
+
const supportMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
|
654 |
+
|
655 |
+
// Left support
|
656 |
+
const leftSupport = new THREE.Mesh(supportGeometry, supportMaterial);
|
657 |
+
leftSupport.position.set(-width/2 + 0.25, -thickness/2 - 0.5, -depth/2 + 0.25);
|
658 |
+
shelfGroup.add(leftSupport);
|
659 |
+
|
660 |
+
// Right support
|
661 |
+
const rightSupport = new THREE.Mesh(supportGeometry, supportMaterial);
|
662 |
+
rightSupport.position.set(width/2 - 0.25, -thickness/2 - 0.5, -depth/2 + 0.25);
|
663 |
+
shelfGroup.add(rightSupport);
|
664 |
+
|
665 |
+
if (width > 24) {
|
666 |
+
// Middle support for wider shelves
|
667 |
+
const midSupport = new THREE.Mesh(supportGeometry, supportMaterial);
|
668 |
+
midSupport.position.set(0, -thickness/2 - 0.5, -depth/2 + 0.25);
|
669 |
+
shelfGroup.add(midSupport);
|
670 |
+
}
|
671 |
|
672 |
+
cabinetGroup.add(shelfGroup);
|
673 |
+
return shelfGroup;
|
674 |
+
}
|
675 |
+
|
676 |
+
function createDrawer(width, height, depth, position, frontMaterial, sideMaterial) {
|
677 |
+
const drawerGroup = new THREE.Group();
|
678 |
+
drawerGroup.position.set(position[0], position[1], position[2]);
|
679 |
|
680 |
+
// Drawer box
|
681 |
+
const boxHeight = height * 0.9;
|
682 |
+
const boxYOffset = -height * 0.05;
|
|
|
|
|
|
|
|
|
683 |
|
684 |
+
// Front
|
685 |
+
const frontGeometry = new THREE.BoxGeometry(width * 0.9, height * 0.95, depth * 0.1);
|
686 |
+
const front = new THREE.Mesh(frontGeometry, frontMaterial);
|
687 |
+
front.position.set(0, boxYOffset, depth * 0.35);
|
688 |
+
drawerGroup.add(front);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
689 |
|
690 |
+
// Bottom
|
691 |
+
const bottomGeometry = new THREE.BoxGeometry(width * 0.85, 0.2, depth * 0.8);
|
692 |
+
const bottom = new THREE.Mesh(bottomGeometry, sideMaterial);
|
693 |
+
bottom.position.set(0, boxYOffset - boxHeight/2 + 0.1, 0);
|
694 |
+
drawerGroup.add(bottom);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
695 |
|
696 |
+
// Sides
|
697 |
+
const sideWidth = width * 0.85;
|
698 |
+
const sideGeometry = new THREE.BoxGeometry(0.2, boxHeight, depth * 0.8);
|
699 |
|
700 |
+
// Left side
|
701 |
+
const leftSide = new THREE.Mesh(sideGeometry, sideMaterial);
|
702 |
+
leftSide.position.set(-sideWidth/2, boxYOffset, 0);
|
703 |
+
drawerGroup.add(leftSide);
|
704 |
|
705 |
+
// Right side
|
706 |
+
const rightSide = new THREE.Mesh(sideGeometry, sideMaterial);
|
707 |
+
rightSide.position.set(sideWidth/2, boxYOffset, 0);
|
708 |
+
drawerGroup.add(rightSide);
|
|
|
709 |
|
710 |
+
// Back
|
711 |
+
const backGeometry = new THREE.BoxGeometry(sideWidth, boxHeight, 0.2);
|
712 |
+
const back = new THREE.Mesh(backGeometry, sideMaterial);
|
713 |
+
back.position.set(0, boxYOffset, depth * -0.3);
|
714 |
+
drawerGroup.add(back);
|
715 |
+
|
716 |
+
// Handle (simplified as a bar)
|
717 |
+
const handleGeometry = new THREE.CylinderGeometry(0.1, 0.1, 4, 16);
|
718 |
+
const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
|
719 |
+
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
|
720 |
+
handle.rotation.z = Math.PI/2;
|
721 |
+
handle.position.set(0, boxYOffset, depth * 0.35 + 0.1);
|
722 |
+
drawerGroup.add(handle);
|
723 |
+
|
724 |
+
cabinetGroup.add(drawerGroup);
|
725 |
+
return drawerGroup;
|
726 |
}
|
727 |
|
728 |
function showLoading() {
|
729 |
+
loadingOverlay.style.opacity = 1;
|
730 |
+
loadingOverlay.style.pointerEvents = 'auto';
|
731 |
let progress = 0;
|
732 |
const interval = setInterval(() => {
|
733 |
progress += 10;
|
|
|
736 |
if (progress >= 100) {
|
737 |
clearInterval(interval);
|
738 |
}
|
739 |
+
}, 30);
|
740 |
}
|
741 |
|
742 |
function hideLoading() {
|
743 |
+
loadingOverlay.style.opacity = 0;
|
744 |
+
loadingOverlay.style.pointerEvents = 'none';
|
745 |
}
|
746 |
|
747 |
// Update price calculation
|
|
|
754 |
const hasSoftClose = softClose.checked;
|
755 |
const hasPullOutShelves = pullOutShelves.checked;
|
756 |
const hasLighting = lighting.checked;
|
757 |
+
const hasGlassDoors = glassDoors.checked;
|
758 |
+
const shelvesCount = parseInt(shelvesSlider.value);
|
759 |
+
const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
|
760 |
|
761 |
// Calculate base price based on size and type
|
762 |
let base = 100;
|
763 |
const sizeFactor = (width * height * depth) / 10000;
|
764 |
base += sizeFactor * 100;
|
765 |
|
766 |
+
if (cabinetType.value === 'base') base *= 1.3;
|
767 |
+
else if (cabinetType.value === 'tall') base *= 1.4;
|
768 |
+
else if (cabinetType.value === 'island') base *= 1.6;
|
769 |
|
770 |
// Material premium
|
771 |
let matPremium = 0;
|
772 |
+
if (mat === 'cherry' || mat === 'walnut') matPremium = 120;
|
773 |
+
else if (mat === 'maple') matPremium = 80;
|
774 |
+
else if (mat === 'oak') matPremium = 40;
|
775 |
+
else if (mat === 'painted') matPremium = 60;
|
776 |
+
else if (mat === 'laminate') matPremium = 0;
|
777 |
|
778 |
// Features
|
779 |
let featuresPremium = 0;
|
780 |
+
if (hasSoftClose) featuresPremium += 40;
|
781 |
+
if (hasPullOutShelves) featuresPremium += 80;
|
782 |
+
if (hasLighting) featuresPremium += 60;
|
783 |
+
if (hasGlassDoors) featuresPremium += 90;
|
784 |
+
|
785 |
+
// Options (shelves, drawers)
|
786 |
+
let optionsPremium = shelvesCount * 20;
|
787 |
+
optionsPremium += drawersCount * 45;
|
788 |
+
if (isTwoDoors) optionsPremium += 35;
|
789 |
|
790 |
// Update displays
|
791 |
basePrice.textContent = `$${base.toFixed(2)}`;
|
792 |
materialPrice.textContent = `$${matPremium.toFixed(2)}`;
|
793 |
featuresPrice.textContent = `$${featuresPremium.toFixed(2)}`;
|
794 |
+
optionsPrice.textContent = `$${optionsPremium.toFixed(2)}`;
|
795 |
+
totalPrice.textContent = `$${(base + matPremium + featuresPremium + optionsPremium).toFixed(2)}`;
|
796 |
}
|
797 |
|
798 |
// Event listeners
|
|
|
828 |
updatePrice();
|
829 |
});
|
830 |
|
831 |
+
shelvesSlider.addEventListener('input', () => {
|
832 |
+
shelvesValue.textContent = shelvesSlider.value;
|
833 |
+
shelvesDisplay.textContent = shelvesSlider.value;
|
834 |
+
updateCabinetModel();
|
835 |
+
updatePrice();
|
836 |
+
});
|
837 |
+
|
838 |
+
drawersSlider.addEventListener('input', () => {
|
839 |
+
drawersValue.textContent = drawersSlider.value;
|
840 |
+
drawersDisplay.textContent = drawersSlider.value;
|
841 |
+
updateCabinetModel();
|
842 |
+
updatePrice();
|
843 |
+
});
|
844 |
+
|
845 |
cabinetType.addEventListener('change', () => {
|
846 |
cabinetTypeDisplay.textContent = cabinetType.options[cabinetType.selectedIndex].text;
|
847 |
|
848 |
+
// Show/hide drawers control based on cabinet type
|
849 |
+
if (cabinetType.value === 'base') {
|
850 |
+
drawersContainer.style.display = 'block';
|
851 |
+
drawersDisplayContainer.style.display = 'flex';
|
852 |
+
} else {
|
853 |
+
drawersContainer.style.display = 'none';
|
854 |
+
drawersDisplayContainer.style.display = 'none';
|
855 |
+
drawersSlider.value = 0;
|
856 |
+
drawersValue.textContent = '0';
|
857 |
+
drawersDisplay.textContent = '0';
|
858 |
+
}
|
859 |
+
|
860 |
// Adjust default dimensions based on cabinet type
|
861 |
if (cabinetType.value === 'wall') {
|
862 |
heightSlider.value = 36;
|
|
|
892 |
|
893 |
color.addEventListener('change', () => {
|
894 |
colorDisplay.textContent = color.options[color.selectedIndex].text;
|
895 |
+
updatePrice();
|
896 |
updateCabinetModel();
|
897 |
});
|
898 |
|
899 |
softClose.addEventListener('change', updatePrice);
|
900 |
pullOutShelves.addEventListener('change', updatePrice);
|
901 |
lighting.addEventListener('change', updatePrice);
|
902 |
+
glassDoors.addEventListener('change', () => {
|
903 |
+
updatePrice();
|
904 |
+
updateCabinetModel();
|
905 |
+
});
|
906 |
|
907 |
// Simulate download buttons (in a real app, these would generate actual files)
|
908 |
downloadUSDZ.addEventListener('click', () => {
|
|
|
910 |
});
|
911 |
|
912 |
downloadGLB.addEventListener('click', () => {
|
913 |
+
alert('In a real implementation, this would generate and download a GLB/GLTF file of your cabinet design.');
|
914 |
});
|
915 |
|
916 |
// Initialize
|