gladiopeace commited on
Commit
fd20f78
·
verified ·
1 Parent(s): dcf7156

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +481 -790
index.html CHANGED
@@ -3,414 +3,248 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Parametric Kitchen Cabinet Generator</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
10
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
12
  <style>
13
- .slider-container {
14
- transition: all 0.3s ease;
15
- }
16
- .slider-container:hover {
17
- transform: translateY(-2px);
18
- }
19
- .preview-container {
20
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
21
- transition: all 0.3s ease;
22
- }
23
- .cabinet-part {
24
- display: none;
25
- position: absolute;
26
- background-color: rgba(255, 255, 255, 0.9);
27
- padding: 15px;
28
- border-radius: 8px;
29
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
30
- z-index: 10;
31
- }
32
- .progress-ring {
33
- stroke-dasharray: 314; /* 2 * π * r where r is 50 */
34
- stroke-dashoffset: 0;
35
- transition: stroke-dashoffset 0.5s ease;
36
- }
37
- #modelViewer {
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">
59
  <div class="container mx-auto px-4 py-8">
60
- <header class="mb-10 text-center">
61
- <h1 class="text-4xl font-bold text-gray-800 mb-2">Parametric Kitchen Cabinet Generator</h1>
62
- <p class="text-gray-600 max-w-2xl mx-auto">Design your perfect kitchen cabinet with real-time 3D preview and instant pricing.</p>
63
  </header>
64
 
65
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
66
- <!-- Controls Section -->
67
- <div class="lg:col-span-1 bg-white rounded-xl shadow-lg p-6">
68
- <div class="mb-8">
69
- <h2 class="text-2xl font-semibold text-gray-800 mb-4">Cabinet Configuration</h2>
70
-
71
- <!-- Cabinet Type -->
72
- <div class="mb-6 slider-container">
73
- <div class="flex justify-between items-center mb-2">
74
- <label class="text-gray-700 font-medium">Cabinet Type</label>
75
- <span id="cabinetTypeDisplay" class="text-gray-500">Wall</span>
76
- </div>
77
- <select id="cabinetType" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white">
78
- <option value="wall">Wall Cabinet</option>
79
- <option value="base">Base Cabinet</option>
80
- <option value="tall">Tall Cabinet</option>
81
- <option value="island">Island Cabinet</option>
82
- </select>
83
- </div>
84
-
85
- <!-- Dimensions -->
86
- <div class="mb-6 slider-container">
87
- <div class="flex justify-between items-center mb-2">
88
- <label class="text-gray-700 font-medium">Width (inches)</label>
89
- <span id="widthValue" class="text-gray-500">24</span>
90
- </div>
91
- <input id="widthSlider" type="range" min="12" max="48" value="24" step="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
92
- </div>
93
-
94
- <div class="mb-6 slider-container">
95
- <div class="flex justify-between items-center mb-2">
96
- <label class="text-gray-700 font-medium">Height (inches)</label>
97
- <span id="heightValue" class="text-gray-500">36</span>
98
- </div>
99
- <input id="heightSlider" type="range" min="12" max="96" value="36" step="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
100
- </div>
101
-
102
- <div class="mb-6 slider-container">
103
- <div class="flex justify-between items-center mb-2">
104
- <label class="text-gray-700 font-medium">Depth (inches)</label>
105
- <span id="depthValue" class="text-gray-500">12</span>
106
- </div>
107
- <input id="depthSlider" type="range" min="12" max="24" value="12" step="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
108
- </div>
109
-
110
- <!-- Doors -->
111
- <div class="mb-6">
112
- <label class="block text-gray-700 font-medium mb-2">Number of Doors</label>
113
- <div class="flex space-x-4">
114
- <button id="oneDoor" class="door-btn px-4 py-2 border rounded-lg flex-1 bg-indigo-100 text-indigo-700 border-indigo-300">1 Door</button>
115
- <button id="twoDoors" class="door-btn px-4 py-2 border rounded-lg flex-1 hover:bg-gray-100">2 Doors</button>
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">
140
- <label class="text-gray-700 font-medium">Material</label>
141
- <span id="materialDisplay" class="text-gray-500">Oak</span>
142
- </div>
143
- <select id="material" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white">
144
- <option value="oak">Oak</option>
145
- <option value="maple">Maple</option>
146
- <option value="cherry">Cherry</option>
147
- <option value="walnut">Walnut</option>
148
- <option value="laminate">Laminate</option>
149
- <option value="painted">Painted MDF</option>
150
- </select>
151
- </div>
152
-
153
- <!-- Color -->
154
- <div class="mb-6 slider-container">
155
- <div class="flex justify-between items-center mb-2">
156
- <label class="text-gray-700 font-medium">Color</label>
157
- <span id="colorDisplay" class="text-gray-500">Natural</span>
158
- </div>
159
- <select id="color" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white">
160
- <option value="natural">Natural</option>
161
- <option value="white">White</option>
162
- <option value="black">Black</option>
163
- <option value="gray">Gray</option>
164
- <option value="navy">Navy Blue</option>
165
- <option value="green">Forest Green</option>
166
- </select>
167
- </div>
168
-
169
- <!-- Features -->
170
- <div class="mb-6">
171
- <label class="block text-gray-700 font-medium mb-2">Features</label>
172
- <div class="space-y-2">
173
- <label class="flex items-center space-x-3">
174
- <input type="checkbox" id="softClose" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
175
- <span class="text-gray-700">Soft-Close Hinges</span>
176
- </label>
177
- <label class="flex items-center space-x-3">
178
- <input type="checkbox" id="pullOutShelves" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
179
- <span class="text-gray-700">Pull-Out Shelves</span>
180
- </label>
181
- <label class="flex items-center space-x-3">
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>
192
 
193
- <div id="priceSection" class="p-5 rounded-xl">
194
- <h3 class="text-lg font-semibold text-gray-800 mb-3">Estimated Price</h3>
195
- <div class="flex justify-between items-center mb-2">
196
- <span class="text-gray-600">Base Price</span>
197
- <span id="basePrice" class="font-medium">$300.00</span>
198
- </div>
199
- <div class="flex justify-between items-center mb-2">
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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  </div>
219
-
220
- <!-- Preview Section -->
221
- <div class="lg:col-span-2 relative">
222
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
223
- <div class="p-4 border-b border-gray-200">
224
- <div class="flex justify-between items-center">
225
- <h2 class="text-xl font-semibold text-gray-800">3D Model Preview</h2>
226
- <div class="flex space-x-2">
227
- <button id="downloadUSDZ" class="px-3 py-1 bg-indigo-100 text-indigo-700 rounded-lg text-sm flex items-center hover:bg-indigo-200">
228
- <i class="fas fa-download mr-2"></i> USDZ
229
- </button>
230
- <button id="downloadGLB" class="px-3 py-1 bg-indigo-600 text-white rounded-lg text-sm flex items-center hover:bg-indigo-700">
231
- <i class="fas fa-download mr-2"></i> GLB
232
- </button>
233
- </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>
241
- <circle class="progress-ring text-indigo-600" cx="50" cy="50" r="40" stroke-width="8" stroke="currentColor" fill="none" stroke-dashoffset="314"></circle>
242
- </svg>
243
- </div>
244
- <p class="text-gray-600">Generating 3D model...</p>
245
- </div>
246
  </div>
247
  </div>
248
-
249
- <div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6">
250
- <div class="bg-white p-6 rounded-xl shadow-lg">
251
- <h3 class="text-lg font-semibold text-gray-800 mb-4">Cabinet Specifications</h3>
252
- <div class="space-y-3">
253
- <div class="flex justify-between">
254
- <span class="text-gray-600">Exterior Dimensions:</span>
255
- <span id="dimensionsDisplay" class="font-medium">24" W × 36" H × 12" D</span>
256
- </div>
257
- <div class="flex justify-between">
258
- <span class="text-gray-600">Interior Capacity:</span>
259
- <span id="capacityDisplay" class="font-medium">6.0 cu.ft</span>
260
- </div>
261
- <div class="flex justify-between">
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
-
276
- <div class="bg-white p-6 rounded-xl shadow-lg">
277
- <h3 class="text-lg font-semibold text-gray-800 mb-4">Order Information</h3>
278
- <div class="space-y-3">
279
- <div class="flex justify-between">
280
- <span class="text-gray-600">Production Time:</span>
281
- <span class="font-medium">2-3 weeks</span>
282
- </div>
283
- <div class="flex justify-between">
284
- <span class="text-gray-600">Shipping:</span>
285
- <span class="font-medium">Flat rate $49.00</span>
286
- </div>
287
- <div class="flex justify-between">
288
- <span class="text-gray-600">Warranty:</span>
289
- <span class="font-medium">10 years</span>
290
- </div>
291
- </div>
292
- <button class="w-full mt-6 py-3 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition-colors">
293
- Add to Cart
294
- </button>
295
- </div>
296
  </div>
297
  </div>
298
  </div>
299
  </div>
300
 
301
  <script>
302
- // DOM Elements
303
- const widthSlider = document.getElementById('widthSlider');
304
- const heightSlider = document.getElementById('heightSlider');
305
- const depthSlider = document.getElementById('depthSlider');
306
- const widthValue = document.getElementById('widthValue');
307
- const heightValue = document.getElementById('heightValue');
308
- const depthValue = document.getElementById('depthValue');
309
- const oneDoorBtn = document.getElementById('oneDoor');
310
- const twoDoorsBtn = document.getElementById('twoDoors');
311
- const cabinetType = document.getElementById('cabinetType');
312
- const cabinetTypeDisplay = document.getElementById('cabinetTypeDisplay');
313
- const material = document.getElementById('material');
314
- const materialDisplay = document.getElementById('materialDisplay');
315
- const color = document.getElementById('color');
316
- const colorDisplay = document.getElementById('colorDisplay');
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');
328
- const modelViewer = document.getElementById('modelViewer');
329
- const loadingOverlay = document.getElementById('loadingOverlay');
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;
344
-
345
- // Initialize the 3D viewer
 
 
 
 
 
 
 
 
 
 
 
346
  function initThreeJS() {
347
  // Create scene
348
  scene = new THREE.Scene();
349
  scene.background = new THREE.Color(0xf5f3f0);
350
 
351
  // Create camera
352
- camera = new THREE.PerspectiveCamera(60, modelViewer.clientWidth / modelViewer.clientHeight, 0.1, 1000);
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);
360
 
361
  // Add lights
362
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
363
  scene.add(ambientLight);
364
 
365
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
366
- directionalLight.position.set(20, 30, 20);
367
  directionalLight.castShadow = true;
368
  directionalLight.shadow.mapSize.width = 2048;
369
  directionalLight.shadow.mapSize.height = 2048;
370
  scene.add(directionalLight);
371
 
372
- const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
373
- directionalLight2.position.set(-20, -30, -20);
374
- scene.add(directionalLight2);
 
375
 
376
  // Add controls
377
  controls = new THREE.OrbitControls(camera, renderer.domElement);
378
  controls.enableDamping = true;
379
  controls.dampingFactor = 0.05;
 
 
380
 
381
- // Create initial cabinet
382
  cabinetGroup = new THREE.Group();
383
  scene.add(cabinetGroup);
384
- updateCabinetModel();
385
 
386
- // Add a grid helper and axes helper for debugging (can be removed in production)
387
  const gridHelper = new THREE.GridHelper(100, 100);
 
388
  scene.add(gridHelper);
389
 
390
- const axesHelper = new THREE.AxesHelper(5);
391
- axesHelper.position.y = 0.1; // Slightly above ground
392
- scene.add(axesHelper);
 
 
 
 
 
 
 
393
 
394
  // Handle window resize
395
  window.addEventListener('resize', onWindowResize);
396
-
397
- // Start animation loop
398
- animate();
399
  }
400
 
401
  function onWindowResize() {
402
- camera.aspect = modelViewer.clientWidth / modelViewer.clientHeight;
403
  camera.updateProjectionMatrix();
404
- renderer.setSize(modelViewer.clientWidth, modelViewer.clientHeight);
405
  }
406
 
407
- function animate() {
408
- requestAnimationFrame(animate);
409
- if (controls) controls.update();
410
- if (renderer) renderer.render(scene, camera);
411
- }
412
-
413
- // Create cabinet geometry with detailed components
414
  function updateCabinetModel() {
415
  showLoading();
416
 
@@ -419,378 +253,285 @@
419
  cabinetGroup.remove(cabinetGroup.children[0]);
420
  }
421
 
422
- // Get current values
423
- const width = parseFloat(widthSlider.value);
424
- const height = parseFloat(heightSlider.value);
425
- const depth = parseFloat(depthSlider.value);
426
- const type = cabinetType.value;
427
- const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
428
- const colorValue = color.value;
429
- const shelvesCount = parseInt(shelvesSlider.value);
430
- const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
431
- const hasGlassDoors = glassDoors.checked;
432
- const thickness = 0.75; // Material thickness in inches
433
-
434
- // Set main material
435
- let mainMaterial = createMaterial(colorValue, false);
436
- let interiorMaterial = new THREE.MeshStandardMaterial({
437
- color: 0xF5F5F5,
438
- roughness: 0.7,
439
- metalness: 0.0
440
- });
441
- let shelfMaterial = new THREE.MeshStandardMaterial({
442
- color: 0xEAEAEA,
443
- roughness: 0.6,
444
- metalness: 0.0
445
- });
446
-
447
- // These factors help with converting inches to Three.js units
448
- // Since Three.js uses arbitrary units, we'll treat 1 unit = 1 inch
449
- const doorThickness = 0.75;
450
- const handleSize = 2.0; // Door handle size
451
- const shelfThickness = 0.5;
452
-
453
- // Create the cabinet box structure first
454
- let cabinetBox = new THREE.Group();
455
- cabinetGroup.add(cabinetBox);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
  // Bottom panel
458
- createPanel(width, thickness, depth,
459
- [0, thickness/2, 0],
460
- mainMaterial, cabinetBox);
461
 
462
- // Top panel
463
- createPanel(width, thickness, depth,
464
- [0, height - thickness/2, 0],
465
- mainMaterial, cabinetBox);
466
 
467
  // Left side
468
- createPanel(thickness, height - 2*thickness, depth,
469
- [-width/2 + thickness/2, height/2, 0],
470
- mainMaterial, cabinetBox);
471
 
472
  // Right side
473
- createPanel(thickness, height - 2*thickness, depth,
474
- [width/2 - thickness/2, height/2, 0],
475
- mainMaterial, cabinetBox);
476
 
477
  // Back panel
478
- createPanel(width - 2*thickness, height - 2*thickness, thickness,
479
- [0, height/2, -depth/2 + thickness/2],
480
- mainMaterial, cabinetBox);
481
-
482
- // Calculate interior height (accounting for drawers if they exist)
483
- const interiorHeight = height - 2*thickness - (drawersCount > 0 ? (height * 0.33) : 0);
484
 
485
- // Middle divider if two doors (only extends down to drawer section if drawers exist)
486
  if (isTwoDoors) {
487
- const dividerHeight = drawersCount > 0 ? height - 2*thickness - (height * 0.33) : height - 2*thickness;
488
- createPanel(thickness, dividerHeight, depth,
489
- [0, (drawersCount > 0 ? (height/2 - (height * 0.33)/2) : height/2), 0],
490
- mainMaterial, cabinetBox);
491
- }
492
-
493
- // Create doors
494
- const doorHeight = drawersCount > 0 ? height - thickness - (height * 0.33) : height - 2*thickness;
495
- const doorYPosition = drawersCount > 0 ? height/2 - (height * 0.33)/2 : height/2;
496
-
497
- if (isTwoDoors) {
498
- const doorWidth = (width - thickness) / 2;
499
 
500
- // Left door
501
- createDoor(doorWidth, doorHeight, doorThickness,
502
- [-(width - thickness)/2 + doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
503
- mainMaterial, hasGlassDoors, true, cabinetBox);
504
 
505
- // Right door
506
- createDoor(doorWidth, doorHeight, doorThickness,
507
- [(width - thickness)/2 - doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
508
- mainMaterial, hasGlassDoors, false, cabinetBox);
 
 
 
 
 
 
 
 
 
509
  } else {
510
- // Single door
511
- const doorWidth = width;
512
- createDoor(doorWidth, doorHeight, doorThickness,
513
- [0, doorYPosition, depth/2 - doorThickness/2],
514
- mainMaterial, hasGlassDoors, false, cabinetBox);
515
- }
516
-
517
- // Add shelves only in the upper section (above any drawers)
518
- const availableHeight = interiorHeight;
519
- const shelfSpacing = availableHeight / (shelvesCount + 1);
520
-
521
- for (let i = 0; i < shelvesCount; i++) {
522
- const shelfY = thickness + (i + 1) * shelfSpacing;
523
- if (drawersCount > 0) {
524
- // Adjust shelf position to be above the drawer section
525
- const drawerSectionHeight = height * 0.33;
526
- shelfY = thickness + (i + 1) * shelfSpacing + (height - thickness - drawerSectionHeight - availableHeight);
527
- }
528
 
529
- createShelf(width - 2*thickness - 0.5, shelfThickness, depth - thickness - 0.5,
530
- [0, shelfY, 0],
531
- shelfMaterial, cabinetBox);
532
- }
533
-
534
- // Add drawers for base cabinets
535
- if (type === 'base' && drawersCount > 0) {
536
- const drawerHeight = (height * 0.33) / drawersCount;
537
- const drawerFrontMaterial = createMaterial(colorValue, true); // Slightly different for drawer fronts
538
 
539
- for (let i = 0; i < drawersCount; i++) {
540
- const drawerY = thickness + i * drawerHeight + drawerHeight/2;
541
- createDrawer(width, drawerHeight, depth * 0.8,
542
- [0, drawerY, depth/2 - depth*0.1],
543
- drawerFrontMaterial, mainMaterial, cabinetBox);
544
- }
545
  }
 
 
 
 
546
 
547
- // Adjust camera target based on cabinet size
548
- controls.target.set(0, height/2, 0);
549
- controls.update();
550
- camera.position.set(width * 1.2, height * 1.2, depth * 1.2);
551
-
552
- // Update capacity calculation
553
- const interiorWidth = width - 2*thickness;
554
- const interiorDepth = depth - thickness;
555
- const capacity = (interiorWidth * interiorHeight * interiorDepth) / 1728; // convert to cubic feet
556
-
557
- // Hide loading after a delay
558
- setTimeout(hideLoading, 800);
559
-
560
- // Update displays
561
- capacityDisplay.textContent = `${capacity.toFixed(1)} cu.ft`;
562
- dimensionsDisplay.textContent = `${width}" W × ${height}" H × ${depth}" D`;
563
- shelvesDisplay.textContent = shelvesCount;
564
- if (drawersCount > 0) {
565
- drawersDisplay.textContent = drawersCount;
566
- }
567
  }
568
 
569
- function createMaterial(colorValue, isDrawerFront = false) {
570
- let materialOptions = {};
 
571
 
572
- if (colorValue === 'natural') {
573
- materialOptions = {
574
- color: 0xC19A6B,
575
- roughness: isDrawerFront ? 0.3 : 0.4,
576
- metalness: 0.1
577
- };
578
- } else if (colorValue === 'white') {
579
- materialOptions = {
580
- color: 0xF5F5F5,
581
- roughness: 0.1,
582
- metalness: 0.05
583
- };
584
- } else if (colorValue === 'black') {
585
- materialOptions = {
586
- color: 0x222222,
587
- roughness: 0.3,
588
- metalness: 0.2
589
- };
590
- } else if (colorValue === 'gray') {
591
- materialOptions = {
592
- color: 0x808080,
593
- roughness: 0.3,
594
- metalness: 0.15
595
- };
596
- } else if (colorValue === 'navy') {
597
- materialOptions = {
598
- color: 0x2C3E50,
599
- roughness: 0.3,
600
- metalness: 0.1
601
- };
602
- } else { // green
603
- materialOptions = {
604
- color: 0x3B5E2E,
605
- roughness: 0.3,
606
- metalness: 0.1
607
- };
608
- }
609
 
610
- return new THREE.MeshStandardMaterial(materialOptions);
 
611
  }
612
 
613
- function createPanel(width, height, depth, position, material, parentGroup) {
614
  const geometry = new THREE.BoxGeometry(width, height, depth);
615
- const panel = new THREE.Mesh(geometry, material);
616
- panel.castShadow = true;
617
- panel.receiveShadow = true;
618
- panel.position.set(position[0], position[1], position[2]);
619
- parentGroup.add(panel);
620
- return panel;
621
- }
622
-
623
- function createDoor(width, height, thickness, position, material, hasGlass, isLeftDoor, parentGroup) {
624
- const doorGroup = new THREE.Group();
625
- doorGroup.position.set(position[0], position[1], position[2]);
626
-
627
- // Door frame
628
- const frameGeometry = new THREE.BoxGeometry(width, height, thickness);
629
- const frame = new THREE.Mesh(frameGeometry, material);
630
- frame.castShadow = true;
631
- frame.receiveShadow = true;
632
- doorGroup.add(frame);
633
-
634
- // Glass panel if selected
635
- if (hasGlass) {
636
- const glassWidth = width * 0.9;
637
- const glassHeight = height * 0.8;
638
- const glassGeometry = new THREE.BoxGeometry(glassWidth, glassHeight, thickness * 0.05);
639
- const glassMaterial = new THREE.MeshPhysicalMaterial({
640
- color: 0xE6F0FF,
641
- transmission: 0.9,
642
- roughness: 0.0,
643
- metalness: 0.0,
644
- transparent: true,
645
- opacity: 0.8,
646
- ior: 1.5,
647
- thickness: 0.1
648
- });
649
- const glass = new THREE.Mesh(glassGeometry, glassMaterial);
650
- glass.position.z = thickness * 0.3;
651
- doorGroup.add(glass);
652
  }
653
 
654
- // Handle
655
- const handleGeometry = new THREE.CylinderGeometry(0.5, 0.5, 3, 16);
656
- const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
657
- const handle = new THREE.Mesh(handleGeometry, handleMaterial);
658
- handle.rotation.z = Math.PI/2;
659
- handle.position.set(isLeftDoor ? -width/2 + 2 : width/2 - 2, 0, thickness);
660
- doorGroup.add(handle);
661
-
662
- // Hinges (simple representation)
663
- const hingeGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 16);
664
- const hingeMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
665
-
666
- // Top hinge
667
- const topHinge = new THREE.Mesh(hingeGeometry, hingeMaterial);
668
- topHinge.rotation.x = Math.PI/2;
669
- topHinge.position.set(isLeftDoor ? width/2 - 1 : -width/2 + 1, height/2 - 2, -thickness/2);
670
- doorGroup.add(topHinge);
671
-
672
- // Bottom hinge
673
- const bottomHinge = new THREE.Mesh(hingeGeometry, hingeMaterial);
674
- bottomHinge.rotation.x = Math.PI/2;
675
- bottomHinge.position.set(isLeftDoor ? width/2 - 1 : -width/2 + 1, -height/2 + 2, -thickness/2);
676
- doorGroup.add(bottomHinge);
677
-
678
- parentGroup.add(doorGroup);
679
- return doorGroup;
680
  }
681
 
682
- function createShelf(width, thickness, depth, position, material, parentGroup) {
683
- const shelfGroup = new THREE.Group();
684
- shelfGroup.position.set(position[0], position[1], position[2]);
685
-
686
- // Main shelf
687
- const shelfGeometry = new THREE.BoxGeometry(width, thickness, depth);
688
- const shelf = new THREE.Mesh(shelfGeometry, material);
689
- shelf.castShadow = true;
690
- shelf.receiveShadow = true;
691
- shelfGroup.add(shelf);
692
-
693
- // Shelf supports
694
- const supportGeometry = new THREE.BoxGeometry(0.5, 1.5, 0.5);
695
- const supportMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
696
-
697
- // Left support
698
- const leftSupport = new THREE.Mesh(supportGeometry, supportMaterial);
699
- leftSupport.position.set(-width/2 + 0.25, -thickness/2 - 0.75, 0);
700
- shelfGroup.add(leftSupport);
701
-
702
- // Right support
703
- const rightSupport = new THREE.Mesh(supportGeometry, supportMaterial);
704
- rightSupport.position.set(width/2 - 0.25, -thickness/2 - 0.75, 0);
705
- shelfGroup.add(rightSupport);
706
-
707
- if (width > 24) {
708
- // Middle support for wider shelves
709
- const midSupport = new THREE.Mesh(supportGeometry, supportMaterial);
710
- midSupport.position.set(0, -thickness/2 - 0.75, 0);
711
- shelfGroup.add(midSupport);
712
- }
713
 
714
- parentGroup.add(shelfGroup);
715
- return shelfGroup;
716
  }
717
 
718
- function createDrawer(width, height, depth, position, frontMaterial, sideMaterial, parentGroup) {
719
- const drawerGroup = new THREE.Group();
720
- drawerGroup.position.set(position[0], position[1], position[2]);
721
-
722
- // Drawer body
723
- const bodyWidth = width * 0.9;
724
- const bodyHeight = height * 0.85;
725
- const bodyDepth = depth;
726
-
727
- // Front
728
- const frontGeometry = new THREE.BoxGeometry(width, height, 0.5);
729
- const front = new THREE.Mesh(frontGeometry, frontMaterial);
730
- front.position.set(0, 0, depth/2 + 0.25);
731
- drawerGroup.add(front);
732
-
733
- // Bottom
734
- const bottomGeometry = new THREE.BoxGeometry(width * 0.9, 0.2, depth);
735
- const bottom = new THREE.Mesh(bottomGeometry, sideMaterial);
736
- bottom.position.set(0, -height/2 + 0.1, 0);
737
- drawerGroup.add(bottom);
738
-
739
- // Sides
740
- const sideWidth = width * 0.9;
741
- const sideGeometry = new THREE.BoxGeometry(0.2, bodyHeight, depth);
 
 
 
 
 
 
742
 
743
- // Left side
744
- const leftSide = new THREE.Mesh(sideGeometry, sideMaterial);
745
- leftSide.position.set(-sideWidth/2, 0, 0);
746
- drawerGroup.add(leftSide);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
 
748
- // Right side
749
- const rightSide = new THREE.Mesh(sideGeometry, sideMaterial);
750
- rightSide.position.set(sideWidth/2, 0, 0);
751
- drawerGroup.add(rightSide);
752
-
753
- // Back
754
- const backGeometry = new THREE.BoxGeometry(sideWidth, bodyHeight, 0.2);
755
- const back = new THREE.Mesh(backGeometry, sideMaterial);
756
- back.position.set(0, 0, -depth/2 + 0.1);
757
- drawerGroup.add(back);
758
-
759
- // Handle (as a bar)
760
- const handleGeometry = new THREE.CylinderGeometry(0.3, 0.3, 3, 16);
761
- const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
762
- const handle = new THREE.Mesh(handleGeometry, handleMaterial);
763
- handle.rotation.z = Math.PI/2;
764
- handle.position.set(0, 0, depth/2 + 0.3);
765
- drawerGroup.add(handle);
766
-
767
- // Drawer slides
768
- const slideGeometry = new THREE.BoxGeometry(0.1, 0.5, depth * 1.2);
769
- const slideMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
770
-
771
- // Left slide
772
- const leftSlide = new THREE.Mesh(slideGeometry, slideMaterial);
773
- leftSlide.position.set(-width/2 + 0.25, -height/2 + 0.25, 0);
774
- drawerGroup.add(leftSlide);
775
-
776
- // Right slide
777
- const rightSlide = new THREE.Mesh(slideGeometry, slideMaterial);
778
- rightSlide.position.set(width/2 - 0.25, -height/2 + 0.25, 0);
779
- drawerGroup.add(rightSlide);
780
-
781
- parentGroup.add(drawerGroup);
782
- return drawerGroup;
783
  }
784
 
785
  function showLoading() {
786
- loadingOverlay.style.opacity = 1;
787
- loadingOverlay.style.pointerEvents = 'auto';
788
  let progress = 0;
789
- progressRing.style.strokeDashoffset = 314;
790
  const interval = setInterval(() => {
791
  progress += 10;
792
- const offset = 314 * (1 - progress/100);
793
  progressRing.style.strokeDashoffset = offset;
 
794
  if (progress >= 100) {
795
  clearInterval(interval);
796
  }
@@ -798,183 +539,133 @@
798
  }
799
 
800
  function hideLoading() {
801
- loadingOverlay.style.opacity = 0;
802
- loadingOverlay.style.pointerEvents = 'none';
 
803
  }
804
 
805
  // Update price calculation
806
  function updatePrice() {
807
- const width = parseFloat(widthSlider.value);
808
- const height = parseFloat(heightSlider.value);
809
- const depth = parseFloat(depthSlider.value);
810
- const mat = material.value;
811
- const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
812
- const hasSoftClose = softClose.checked;
813
- const hasPullOutShelves = pullOutShelves.checked;
814
- const hasLighting = lighting.checked;
815
- const hasGlassDoors = glassDoors.checked;
816
- const shelvesCount = parseInt(shelvesSlider.value);
817
- const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
818
-
819
- // Calculate base price based on size and type
820
- let base = 100;
821
- const sizeFactor = (width * height * depth) / 10000;
822
- base += sizeFactor * 100;
823
-
824
- if (cabinetType.value === 'base') base *= 1.3;
825
- else if (cabinetType.value === 'tall') base *= 1.4;
826
- else if (cabinetType.value === 'island') base *= 1.6;
827
-
828
- // Material premium
829
- let matPremium = 0;
830
- if (mat === 'cherry' || mat === 'walnut') matPremium = 120;
831
- else if (mat === 'maple') matPremium = 80;
832
- else if (mat === 'oak') matPremium = 40;
833
- else if (mat === 'painted') matPremium = 60;
834
- else if (mat === 'laminate') matPremium = 0;
835
 
836
  // Features
837
- let featuresPremium = 0;
838
- if (hasSoftClose) featuresPremium += 40;
839
- if (hasPullOutShelves) featuresPremium += 80;
840
- if (hasLighting) featuresPremium += 60;
841
- if (hasGlassDoors) featuresPremium += 90;
842
-
843
- // Options (shelves, drawers)
844
- let optionsPremium = shelvesCount * 20;
845
- optionsPremium += drawersCount * 45;
846
- if (isTwoDoors) optionsPremium += 35;
847
-
848
- // Update displays
849
- basePrice.textContent = `$${base.toFixed(2)}`;
850
- materialPrice.textContent = `$${matPremium.toFixed(2)}`;
851
- featuresPrice.textContent = `$${featuresPremium.toFixed(2)}`;
852
- optionsPrice.textContent = `$${optionsPremium.toFixed(2)}`;
853
- totalPrice.textContent = `$${(base + matPremium + featuresPremium + optionsPremium).toFixed(2)}`;
854
  }
855
 
856
  // Event listeners
857
- widthSlider.addEventListener('input', () => {
858
- widthValue.textContent = widthSlider.value;
859
- updateCabinetModel();
860
- updatePrice();
861
- });
862
 
863
- heightSlider.addEventListener('input', () => {
864
- heightValue.textContent = heightSlider.value;
865
  updateCabinetModel();
866
  updatePrice();
867
  });
868
 
869
- depthSlider.addEventListener('input', () => {
870
- depthValue.textContent = depthSlider.value;
871
  updateCabinetModel();
872
  updatePrice();
873
  });
874
 
875
- oneDoorBtn.addEventListener('click', () => {
876
- oneDoorBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
877
- twoDoorsBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
878
  updateCabinetModel();
879
  updatePrice();
880
  });
881
 
882
- twoDoorsBtn.addEventListener('click', () => {
883
- twoDoorsBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
884
- oneDoorBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
885
  updateCabinetModel();
886
  updatePrice();
887
  });
888
 
889
- shelvesSlider.addEventListener('input', () => {
890
- shelvesValue.textContent = shelvesSlider.value;
891
- shelvesDisplay.textContent = shelvesSlider.value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  updateCabinetModel();
893
  updatePrice();
894
  });
895
 
896
- drawersSlider.addEventListener('input', () => {
897
- drawersValue.textContent = drawersSlider.value;
898
- drawersDisplay.textContent = drawersSlider.value;
899
  updateCabinetModel();
900
  updatePrice();
901
  });
902
 
903
- cabinetType.addEventListener('change', () => {
904
- cabinetTypeDisplay.textContent = cabinetType.options[cabinetType.selectedIndex].text;
905
-
906
- // Show/hide drawers control based on cabinet type
907
- if (cabinetType.value === 'base') {
908
- drawersContainer.style.display = 'block';
909
- drawersDisplayContainer.style.display = 'flex';
910
- } else {
911
- drawersContainer.style.display = 'none';
912
- drawersDisplayContainer.style.display = 'none';
913
- drawersSlider.value = 0;
914
- drawersValue.textContent = '0';
915
- drawersDisplay.textContent = '0';
916
- }
917
-
918
- // Adjust default dimensions based on cabinet type
919
- if (cabinetType.value === 'wall') {
920
- heightSlider.value = 36;
921
- depthSlider.value = 12;
922
- heightValue.textContent = '36';
923
- depthValue.textContent = '12';
924
- } else if (cabinetType.value === 'base') {
925
- heightSlider.value = 34.5;
926
- depthSlider.value = 24;
927
- heightValue.textContent = '34.5';
928
- depthValue.textContent = '24';
929
- } else if (cabinetType.value === 'tall') {
930
- heightSlider.value = 84;
931
- depthSlider.value = 24;
932
- heightValue.textContent = '84';
933
- depthValue.textContent = '24';
934
- } else if (cabinetType.value === 'island') {
935
- heightSlider.value = 36;
936
- depthSlider.value = 24;
937
- heightValue.textContent = '36';
938
- depthValue.textContent = '24';
939
- }
940
-
941
  updateCabinetModel();
942
  updatePrice();
943
  });
944
 
945
- material.addEventListener('change', () => {
946
- materialDisplay.textContent = material.options[material.selectedIndex].text;
947
- updatePrice();
948
  updateCabinetModel();
949
- });
950
-
951
- color.addEventListener('change', () => {
952
- colorDisplay.textContent = color.options[color.selectedIndex].text;
953
  updatePrice();
954
- updateCabinetModel();
955
  });
956
 
957
- softClose.addEventListener('change', updatePrice);
958
- pullOutShelves.addEventListener('change', updatePrice);
959
- lighting.addEventListener('change', updatePrice);
960
- glassDoors.addEventListener('change', () => {
961
- updatePrice();
962
  updateCabinetModel();
 
963
  });
964
 
965
- // Simulate download buttons (in a real app, these would generate actual files)
966
- downloadUSDZ.addEventListener('click', () => {
967
- alert('In a real implementation, this would generate and download a USDZ file of your cabinet design.');
968
- });
969
-
970
- downloadGLB.addEventListener('click', () => {
971
- alert('In a real implementation, this would generate and download a GLB/GLTF file of your cabinet design.');
972
  });
973
 
974
  // Initialize
975
- window.addEventListener('load', () => {
976
  initThreeJS();
977
  updatePrice();
 
 
 
978
  });
979
  </script>
980
  <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>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Cabinet Generator</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
 
8
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
 
10
  <style>
11
+ #modelContainer {
12
+ width: 100%;
13
+ height: 500px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  background-color: #f5f3f0;
15
  border-radius: 8px;
16
  overflow: hidden;
17
  position: relative;
18
  }
19
+ .loading-overlay {
20
+ position: absolute;
21
+ top: 0;
22
+ left: 0;
23
+ width: 100%;
24
+ height: 100%;
25
+ background: rgba(245, 243, 240, 0.8);
26
+ display: flex;
27
+ flex-direction: column;
28
+ justify-content: center;
29
+ align-items: center;
30
+ z-index: 10;
31
  }
32
+ .progress-ring {
33
+ width: 60px;
34
+ height: 60px;
35
  }
36
+ .progress-ring circle {
37
+ transition: stroke-dashoffset 0.3s;
38
  }
39
+ .controls-overlay {
40
+ position: absolute;
41
+ bottom: 20px;
42
+ left: 50%;
43
+ transform: translateX(-50%);
44
+ display: flex;
45
+ gap: 10px;
46
+ z-index: 5;
47
  }
48
  </style>
49
  </head>
50
  <body class="bg-gray-50">
51
  <div class="container mx-auto px-4 py-8">
52
+ <header class="mb-8 text-center">
53
+ <h1 class="text-3xl font-bold text-gray-800">3D Cabinet Generator</h1>
54
+ <p class="text-gray-600 mt-2">Customize your cabinet with real-time 3D visualization</p>
55
  </header>
56
 
57
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
58
+ <!-- Controls -->
59
+ <div class="bg-white rounded-lg shadow p-6">
60
+ <h2 class="text-xl font-semibold mb-4">Configuration</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ <div class="mb-4">
63
+ <label class="block text-gray-700 mb-2">Cabinet Type</label>
64
+ <select id="cabinetType" class="w-full p-2 border rounded">
65
+ <option value="base">Base Cabinet</option>
66
+ <option value="wall">Wall Cabinet</option>
67
+ <option value="tall">Tall Cabinet</option>
68
+ </select>
69
+ </div>
70
+
71
+ <div class="mb-4">
72
+ <label class="block text-gray-700 mb-2">Width: <span id="widthValue">24</span>"</label>
73
+ <input type="range" id="widthSlider" min="12" max="48" value="24" step="1" class="w-full">
74
+ </div>
75
+
76
+ <div class="mb-4">
77
+ <label class="block text-gray-700 mb-2">Height: <span id="heightValue">36</span>"</label>
78
+ <input type="range" id="heightSlider" min="12" max="96" value="36" step="1" class="w-full">
79
+ </div>
80
+
81
+ <div class="mb-4">
82
+ <label class="block text-gray-700 mb-2">Depth: <span id="depthValue">12</span>"</label>
83
+ <input type="range" id="depthSlider" min="12" max="24" value="12" step="1" class="w-full">
84
+ </div>
85
+
86
+ <div class="mb-4">
87
+ <label class="block text-gray-700 mb-2">Material</label>
88
+ <select id="material" class="w-full p-2 border rounded">
89
+ <option value="oak">Oak</option>
90
+ <option value="maple">Maple</option>
91
+ <option value="cherry">Cherry</option>
92
+ <option value="laminate">Laminate</option>
93
+ </select>
94
+ </div>
95
+
96
+ <div class="mb-4">
97
+ <label class="block text-gray-700 mb-2">Number of Doors</label>
98
+ <div class="flex space-x-2">
99
+ <button id="oneDoor" class="flex-1 py-2 bg-blue-100 text-blue-700 rounded">1 Door</button>
100
+ <button id="twoDoors" class="flex-1 py-2 bg-gray-100 text-gray-700 rounded">2 Doors</button>
101
  </div>
102
  </div>
103
+
104
+ <div class="mb-4">
105
+ <label class="block text-gray-700 mb-2">Number of Shelves: <span id="shelvesValue">1</span></label>
106
+ <input type="range" id="shelvesSlider" min="0" max="5" value="1" class="w-full">
107
+ </div>
108
+
109
+ <div id="drawersContainer" class="mb-4 hidden">
110
+ <label class="block text-gray-700 mb-2">Number of Drawers: <span id="drawersValue">1</span></label>
111
+ <input type="range" id="drawersSlider" min="1" max="3" value="1" class="w-full">
112
+ </div>
113
+
114
+ <div class="mt-6 bg-gray-50 p-4 rounded-lg">
115
+ <h3 class="font-semibold mb-2">Price Estimation</h3>
116
+ <div class="text-2xl font-bold text-blue-600" id="totalPrice">$299.99</div>
117
+ </div>
118
  </div>
119
+
120
+ <!-- 3D Preview -->
121
+ <div class="lg:col-span-2">
122
+ <div id="modelContainer">
123
+ <div id="doorControls" class="controls-overlay hidden">
124
+ <button id="openBtn" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center">
125
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
126
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
127
+ </svg>
128
+ Open Doors
129
+ </button>
130
+ <button id="closeBtn" class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex items-center">
131
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
132
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
133
+ </svg>
134
+ Close Doors
135
+ </button>
136
  </div>
137
+ <div id="loadingOverlay" class="loading-overlay">
138
+ <svg class="progress-ring" viewBox="0 0 60 60">
139
+ <circle class="text-gray-300" stroke="currentColor" stroke-width="4" fill="none" r="25" cx="30" cy="30"></circle>
140
+ <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>
141
+ </svg>
142
+ <p class="mt-2 text-gray-600">Generating 3D model...</p>
 
 
 
 
143
  </div>
144
  </div>
145
+
146
+ <div class="mt-4 flex justify-end space-x-2">
147
+ <button id="downloadBtn" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center">
148
+ <svg class="w-5 h-5 mr-2" fill="none stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
149
+ <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>
150
+ </svg>
151
+ Download 3D Model
152
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  </div>
154
  </div>
155
  </div>
156
  </div>
157
 
158
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  // Three.js variables
160
  let scene, camera, renderer, controls, cabinetGroup;
161
+ const modelContainer = document.getElementById('modelContainer');
162
+ const loadingOverlay = document.getElementById('loadingOverlay');
163
+ const progressRing = document.querySelector('.progress-ring circle:last-child');
164
+ const doorControls = document.getElementById('doorControls');
165
+ const openBtn = document.getElementById('openBtn');
166
+ const closeBtn = document.getElementById('closeBtn');
167
+
168
+ // Door related variables
169
+ let leftDoor, rightDoor, leftDoorHinge, rightDoorHinge;
170
+ let isAnimating = false;
171
+ let doorState = 'closed'; // 'closed', 'opening', 'open', 'closing'
172
+
173
+ // Initialize Three.js
174
  function initThreeJS() {
175
  // Create scene
176
  scene = new THREE.Scene();
177
  scene.background = new THREE.Color(0xf5f3f0);
178
 
179
  // Create camera
180
+ camera = new THREE.PerspectiveCamera(
181
+ 60, // fov
182
+ modelContainer.clientWidth / modelContainer.clientHeight, // aspect
183
+ 0.1, // near
184
+ 1000 // far
185
+ );
186
+ camera.position.set(40, 40, 40);
187
 
188
  // Create renderer
189
+ renderer = new THREE.WebGLRenderer({ antialias: true });
190
+ renderer.setSize(modelContainer.clientWidth, modelContainer.clientHeight);
191
  renderer.shadowMap.enabled = true;
192
+ modelContainer.appendChild(renderer.domElement);
193
 
194
  // Add lights
195
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
196
  scene.add(ambientLight);
197
 
198
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
199
+ directionalLight.position.set(30, 50, 20);
200
  directionalLight.castShadow = true;
201
  directionalLight.shadow.mapSize.width = 2048;
202
  directionalLight.shadow.mapSize.height = 2048;
203
  scene.add(directionalLight);
204
 
205
+ // Add helper light from the front
206
+ const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
207
+ fillLight.position.set(-30, 30, 30);
208
+ scene.add(fillLight);
209
 
210
  // Add controls
211
  controls = new THREE.OrbitControls(camera, renderer.domElement);
212
  controls.enableDamping = true;
213
  controls.dampingFactor = 0.05;
214
+ controls.minDistance = 10;
215
+ controls.maxDistance = 200;
216
 
217
+ // Create cabinet group
218
  cabinetGroup = new THREE.Group();
219
  scene.add(cabinetGroup);
 
220
 
221
+ // Add grid and axes helpers (for debugging)
222
  const gridHelper = new THREE.GridHelper(100, 100);
223
+ gridHelper.position.y = -0.01; // Just below the cabinet
224
  scene.add(gridHelper);
225
 
226
+ // Initial render
227
+ updateCabinetModel();
228
+
229
+ // Animation loop
230
+ function animate() {
231
+ requestAnimationFrame(animate);
232
+ controls.update();
233
+ renderer.render(scene, camera);
234
+ }
235
+ animate();
236
 
237
  // Handle window resize
238
  window.addEventListener('resize', onWindowResize);
 
 
 
239
  }
240
 
241
  function onWindowResize() {
242
+ camera.aspect = modelContainer.clientWidth / modelContainer.clientHeight;
243
  camera.updateProjectionMatrix();
244
+ renderer.setSize(modelContainer.clientWidth, modelContainer.clientHeight);
245
  }
246
 
247
+ // Create cabinet model
 
 
 
 
 
 
248
  function updateCabinetModel() {
249
  showLoading();
250
 
 
253
  cabinetGroup.remove(cabinetGroup.children[0]);
254
  }
255
 
256
+ // Reset door variables
257
+ leftDoor = null;
258
+ rightDoor = null;
259
+ leftDoorHinge = null;
260
+ rightDoorHinge = null;
261
+
262
+ // Get parameters
263
+ const width = parseFloat(document.getElementById('widthSlider').value);
264
+ const height = parseFloat(document.getElementById('heightSlider').value);
265
+ const depth = parseFloat(document.getElementById('depthSlider').value);
266
+ const materialType = document.getElementById('material').value;
267
+ const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100');
268
+ const shelvesCount = parseInt(document.getElementById('shelvesSlider').value);
269
+ const drawersCount = parseInt(document.getElementById('drawersSlider').value || 0);
270
+ const cabinetType = document.getElementById('cabinetType').value;
271
+
272
+ // Create materials
273
+ const materials = {
274
+ exterior: createMaterial(materialType),
275
+ interior: new THREE.MeshStandardMaterial({ color: 0xF5F5F5, roughness: 0.7 }),
276
+ shelf: new THREE.MeshStandardMaterial({ color: 0xEAEAEA, roughness: 0.6 }),
277
+ hardware: new THREE.MeshStandardMaterial({ color: 0x333333 })
278
+ };
279
+
280
+ // Main cabinet structure
281
+ createCabinetStructure(width, height, depth, materials, cabinetType, isTwoDoors);
282
+
283
+ // Add shelves if needed
284
+ if (shelvesCount > 0) {
285
+ const availableHeight = height - 2; // account for frame
286
+ const shelfSpacing = availableHeight / (shelvesCount + 1);
287
+
288
+ for (let i = 0; i < shelvesCount; i++) {
289
+ const shelfY = -height/2 + 1 + (i + 1) * shelfSpacing;
290
+ createShelf(width - 2.5, 0.5, depth - 2, shelfY, materials.shelf);
291
+ }
292
+ }
293
+
294
+ // Add drawers for base cabinets
295
+ if (cabinetType === 'base' && drawersCount > 0) {
296
+ const drawerHeight = (height / 3) / drawersCount;
297
+
298
+ for (let i = 0; i < drawersCount; i++) {
299
+ const drawerY = -height/2 + (i + 0.5) * drawerHeight;
300
+ createDrawer(width * 0.8, drawerHeight * 0.8, depth * 0.7, drawerY, materials.exterior, materials.interior);
301
+ }
302
+ }
303
+
304
+ // Update camera position based on cabinet size
305
+ const maxDimension = Math.max(width, height, depth);
306
+ camera.position.set(maxDimension * 1.5, maxDimension * 1.2, maxDimension * 1.5);
307
+ controls.target.set(0, height/4, 0);
308
+ controls.update();
309
+
310
+ // Show/hide door controls
311
+ if (cabinetType !== 'base' || drawersCount === 0) {
312
+ doorControls.classList.remove('hidden');
313
+ } else {
314
+ doorControls.classList.add('hidden');
315
+ }
316
+
317
+ // Hide loading after a short delay to ensure smooth UI
318
+ setTimeout(hideLoading, 500);
319
+ }
320
+
321
+ // Create cabinet frame and panels
322
+ function createCabinetStructure(width, height, depth, materials, type, isTwoDoors) {
323
+ const thickness = 0.75; // standard material thickness
324
 
325
  // Bottom panel
326
+ createBox(width, thickness, depth, [0, -height/2 + thickness/2, 0], materials.exterior);
 
 
327
 
328
+ // Top panel (not for base cabinets with countertop)
329
+ if (type !== 'base') {
330
+ createBox(width, thickness, depth, [0, height/2 - thickness/2, 0], materials.exterior);
331
+ }
332
 
333
  // Left side
334
+ createBox(thickness, height - 2*thickness, depth,
335
+ [-width/2 + thickness/2, 0, 0], materials.exterior);
 
336
 
337
  // Right side
338
+ createBox(thickness, height - 2*thickness, depth,
339
+ [width/2 - thickness/2, 0, 0], materials.exterior);
 
340
 
341
  // Back panel
342
+ createBox(width - 2*thickness, height - 2*thickness, thickness,
343
+ [0, 0, -depth/2 + thickness/2], materials.exterior);
 
 
 
 
344
 
345
+ // Middle divider for two doors
346
  if (isTwoDoors) {
347
+ createBox(thickness, height - 2*thickness, depth, [0, 0, 0], materials.exterior);
348
+
349
+ // Create door hinges (invisible objects for rotation)
350
+ leftDoorHinge = new THREE.Group();
351
+ leftDoorHinge.position.set(-width/2 + thickness, 0, depth/2 - thickness/2);
352
+ cabinetGroup.add(leftDoorHinge);
353
+
354
+ rightDoorHinge = new THREE.Group();
355
+ rightDoorHinge.position.set(width/2 - thickness, 0, depth/2 - thickness/2);
356
+ cabinetGroup.add(rightDoorHinge);
 
 
357
 
358
+ // Left door (rotates around left edge)
359
+ leftDoor = createBox(width/2 - thickness, height - 2*thickness, thickness,
360
+ [0, 0, -thickness/2], materials.exterior);
361
+ leftDoorHinge.add(leftDoor);
362
 
363
+ // Right door (rotates around right edge)
364
+ rightDoor = createBox(width/2 - thickness, height - 2*thickness, thickness,
365
+ [0, 0, -thickness/2], materials.exterior);
366
+ rightDoorHinge.add(rightDoor);
367
+
368
+ // Add handles
369
+ // Left handle (positioned on the right side of left door)
370
+ const leftHandle = createBox(0.2, 2, 1, [width/4 - thickness/2, 0, 0.75], materials.hardware);
371
+ leftDoorHinge.add(leftHandle);
372
+
373
+ // Right handle (positioned on the left side of right door)
374
+ const rightHandle = createBox(0.2, 2, 1, [-width/4 + thickness/2, 0, 0.75], materials.hardware);
375
+ rightDoorHinge.add(rightHandle);
376
  } else {
377
+ // Single door (right side hinge)
378
+ rightDoorHinge = new THREE.Group();
379
+ rightDoorHinge.position.set(width/2 - thickness/2, 0, depth/2 - thickness/2);
380
+ cabinetGroup.add(rightDoorHinge);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
+ rightDoor = createBox(width - thickness, height - 2*thickness, thickness,
383
+ [-width/2 + thickness/2, 0, -thickness/2], materials.exterior);
384
+ rightDoorHinge.add(rightDoor);
 
 
 
 
 
 
385
 
386
+ // Handle (positioned on the left side of the door)
387
+ const handle = createBox(0.2, 2, 1, [-width/2 + thickness + 1, 0, 0.75], materials.hardware);
388
+ rightDoorHinge.add(handle);
 
 
 
389
  }
390
+ }
391
+
392
+ function createShelf(width, thickness, depth, yPos, material) {
393
+ createBox(width, thickness, depth, [0, yPos, 0], material);
394
 
395
+ // Add shelf supports
396
+ createBox(0.5, 1, 0.5, [-width/2 + 0.25, yPos - 0.75, 0], material);
397
+ createBox(0.5, 1, 0.5, [width/2 - 0.25, yPos - 0.75, 0], material);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  }
399
 
400
+ function createDrawer(width, height, depth, yPos, frontMaterial, interiorMaterial) {
401
+ // Drawer front
402
+ createBox(width * 1.1, height * 1.1, 0.5, [0, yPos, depth/2 + 0.25], frontMaterial);
403
 
404
+ // Drawer box
405
+ createBox(width, height, depth, [0, yPos, 0], interiorMaterial);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
+ // Handle (simplified as a horizontal bar)
408
+ createBox(3, 0.5, 0.3, [0, yPos, depth/2 + 0.4], frontMaterial);
409
  }
410
 
411
+ function createBox(width, height, depth, position, material) {
412
  const geometry = new THREE.BoxGeometry(width, height, depth);
413
+ const mesh = new THREE.Mesh(geometry, material);
414
+ mesh.position.set(position[0], position[1], position[2]);
415
+ mesh.castShadow = true;
416
+ mesh.receiveShadow = true;
417
+
418
+ if (material.color.getHex() !== 0x333333) { // Don't add shadow to hardware
419
+ mesh.castShadow = true;
420
+ mesh.receiveShadow = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  }
422
 
423
+ cabinetGroup.add(mesh);
424
+ return mesh;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  }
426
 
427
+ function createMaterial(type) {
428
+ const materials = {
429
+ oak: {
430
+ color: 0xC19A6B,
431
+ roughness: 0.5
432
+ },
433
+ maple: {
434
+ color: 0xE6CEAC,
435
+ roughness: 0.4
436
+ },
437
+ cherry: {
438
+ color: 0x8B4513,
439
+ roughness: 0.3
440
+ },
441
+ laminate: {
442
+ color: 0xFFFFFF,
443
+ roughness: 0.1,
444
+ metalness: 0.1
445
+ }
446
+ };
 
 
 
 
 
 
 
 
 
 
 
447
 
448
+ return new THREE.MeshStandardMaterial(materials[type]);
 
449
  }
450
 
451
+ // Animate doors
452
+ function animateDoors(targetAngle) {
453
+ if (isAnimating) return;
454
+
455
+ isAnimating = true;
456
+ const duration = 1000; // ms
457
+ const startTime = Date.now();
458
+ const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100');
459
+
460
+ // Determine current angles
461
+ const leftStartAngle = leftDoorHinge ? leftDoorHinge.rotation.y : 0;
462
+ const rightStartAngle = rightDoorHinge ? rightDoorHinge.rotation.y : 0;
463
+
464
+ // Target angles
465
+ let leftTarget = 0;
466
+ let rightTarget = 0;
467
+
468
+ if (targetAngle === 0) { // Closing
469
+ leftTarget = 0;
470
+ rightTarget = 0;
471
+ doorState = 'closing';
472
+ } else { // Opening
473
+ if (isTwoDoors) {
474
+ leftTarget = -targetAngle; // Left door opens inward (negative rotation)
475
+ rightTarget = targetAngle; // Right door opens inward (positive rotation)
476
+ } else {
477
+ rightTarget = targetAngle; // Single door opens inward (positive rotation)
478
+ }
479
+ doorState = 'opening';
480
+ }
481
 
482
+ const animateFrame = () => {
483
+ const elapsed = Date.now() - startTime;
484
+ const progress = Math.min(elapsed / duration, 1);
485
+
486
+ // Easing function
487
+ const easeProgress = easeOutCubic(progress);
488
+
489
+ if (leftDoorHinge) {
490
+ leftDoorHinge.rotation.y = leftStartAngle + (leftTarget - leftStartAngle) * easeProgress;
491
+ }
492
+
493
+ if (rightDoorHinge) {
494
+ rightDoorHinge.rotation.y = rightStartAngle + (rightTarget - rightStartAngle) * easeProgress;
495
+ }
496
+
497
+ if (progress < 1) {
498
+ requestAnimationFrame(animateFrame);
499
+ } else {
500
+ isAnimating = false;
501
+ doorState = targetAngle === 0 ? 'closed' : 'open';
502
+ }
503
+ };
504
 
505
+ animateFrame();
506
+ }
507
+
508
+ // Easing function for smooth animation
509
+ function easeOutCubic(t) {
510
+ return 1 - Math.pow(1 - t, 3);
511
+ }
512
+
513
+ // Open/close door functions
514
+ function openDoors() {
515
+ if (doorState === 'closed' || doorState === 'closing') {
516
+ animateDoors(Math.PI/2); // 90 degrees in radians
517
+ }
518
+ }
519
+
520
+ function closeDoors() {
521
+ if (doorState === 'open' || doorState === 'opening') {
522
+ animateDoors(0);
523
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  }
525
 
526
  function showLoading() {
527
+ loadingOverlay.style.display = 'flex';
 
528
  let progress = 0;
529
+
530
  const interval = setInterval(() => {
531
  progress += 10;
532
+ const offset = 157 * (1 - progress/100);
533
  progressRing.style.strokeDashoffset = offset;
534
+
535
  if (progress >= 100) {
536
  clearInterval(interval);
537
  }
 
539
  }
540
 
541
  function hideLoading() {
542
+ loadingOverlay.style.display = 'none';
543
+ progressRing.style.strokeDashoffset = '157';
544
+ doorState = 'closed'; // Reset door state after loading
545
  }
546
 
547
  // Update price calculation
548
  function updatePrice() {
549
+ const width = parseFloat(document.getElementById('widthSlider').value);
550
+ const height = parseFloat(document.getElementById('heightSlider').value);
551
+ const depth = parseFloat(document.getElementById('depthSlider').value);
552
+ const material = document.getElementById('material').value;
553
+ const isTwoDoors = document.getElementById('twoDoors').classList.contains('bg-blue-100');
554
+ const shelvesCount = parseInt(document.getElementById('shelvesSlider').value);
555
+ const drawersCount = parseInt(document.getElementById('drawersSlider').value || 0);
556
+ const cabinetType = document.getElementById('cabinetType').value;
557
+
558
+ // Calculate base price
559
+ let basePrice = 100 + (width * height * depth) / 100;
560
+
561
+ // Adjust by type
562
+ if (cabinetType === 'base') basePrice *= 1.3;
563
+ else if (cabinetType === 'tall') basePrice *= 1.5;
564
+
565
+ // Material multipliers
566
+ if (material === 'maple') basePrice *= 1.2;
567
+ else if (material === 'cherry') basePrice *= 1.4;
 
 
 
 
 
 
 
 
 
568
 
569
  // Features
570
+ if (isTwoDoors) basePrice += 50;
571
+ if (shelvesCount > 1) basePrice += (shelvesCount - 1) * 25;
572
+ if (drawersCount > 0) basePrice += drawersCount * 60;
573
+
574
+ document.getElementById('totalPrice').textContent = `$${basePrice.toFixed(2)}`;
 
 
 
 
 
 
 
 
 
 
 
 
575
  }
576
 
577
  // Event listeners
578
+ openBtn.addEventListener('click', openDoors);
579
+ closeBtn.addEventListener('click', closeDoors);
 
 
 
580
 
581
+ document.getElementById('widthSlider').addEventListener('input', function() {
582
+ document.getElementById('widthValue').textContent = this.value;
583
  updateCabinetModel();
584
  updatePrice();
585
  });
586
 
587
+ document.getElementById('heightSlider').addEventListener('input', function() {
588
+ document.getElementById('heightValue').textContent = this.value;
589
  updateCabinetModel();
590
  updatePrice();
591
  });
592
 
593
+ document.getElementById('depthSlider').addEventListener('input', function() {
594
+ document.getElementById('depthValue').textContent = this.value;
 
595
  updateCabinetModel();
596
  updatePrice();
597
  });
598
 
599
+ document.getElementById('material').addEventListener('change', function() {
 
 
600
  updateCabinetModel();
601
  updatePrice();
602
  });
603
 
604
+ document.getElementById('cabinetType').addEventListener('change', function() {
605
+ // Adjust default dimensions
606
+ if (this.value === 'base') {
607
+ document.getElementById('heightSlider').value = 34.5;
608
+ document.getElementById('depthSlider').value = 24;
609
+ document.getElementById('heightValue').textContent = '34.5';
610
+ document.getElementById('depthValue').textContent = '24';
611
+
612
+ document.getElementById('drawersContainer').classList.remove('hidden');
613
+ } else {
614
+ if (this.value === 'wall') {
615
+ document.getElementById('heightSlider').value = 36;
616
+ document.getElementById('depthSlider').value = 12;
617
+ } else if (this.value === 'tall') {
618
+ document.getElementById('heightSlider').value = 84;
619
+ document.getElementById('depthSlider').value = 24;
620
+ }
621
+
622
+ document.getElementById('heightValue').textContent = document.getElementById('heightSlider').value;
623
+ document.getElementById('depthValue').textContent = document.getElementById('depthSlider').value;
624
+
625
+ document.getElementById('drawersContainer').classList.add('hidden');
626
+ }
627
+
628
  updateCabinetModel();
629
  updatePrice();
630
  });
631
 
632
+ document.getElementById('oneDoor').addEventListener('click', function() {
633
+ this.classList.add('bg-blue-100', 'text-blue-700');
634
+ document.getElementById('twoDoors').classList.remove('bg-blue-100', 'text-blue-700');
635
  updateCabinetModel();
636
  updatePrice();
637
  });
638
 
639
+ document.getElementById('twoDoors').addEventListener('click', function() {
640
+ this.classList.add('bg-blue-100', 'text-blue-700');
641
+ document.getElementById('oneDoor').classList.remove('bg-blue-100', 'text-blue-700');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  updateCabinetModel();
643
  updatePrice();
644
  });
645
 
646
+ document.getElementById('shelvesSlider').addEventListener('input', function() {
647
+ document.getElementById('shelvesValue').textContent = this.value;
 
648
  updateCabinetModel();
 
 
 
 
649
  updatePrice();
 
650
  });
651
 
652
+ document.getElementById('drawersSlider').addEventListener('input', function() {
653
+ document.getElementById('drawersValue').textContent = this.value;
 
 
 
654
  updateCabinetModel();
655
+ updatePrice();
656
  });
657
 
658
+ document.getElementById('downloadBtn').addEventListener('click', function() {
659
+ alert('In a complete implementation, this would export your cabinet as a 3D model file.');
 
 
 
 
 
660
  });
661
 
662
  // Initialize
663
+ window.addEventListener('load', function() {
664
  initThreeJS();
665
  updatePrice();
666
+
667
+ // Set one door as active by default
668
+ document.getElementById('oneDoor').classList.add('bg-blue-100', 'text-blue-700');
669
  });
670
  </script>
671
  <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>