cabinet / index.html
gladiopeace's picture
Add 1 files
60bd933 verified
raw
history blame
49 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parametric Kitchen Cabinet Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
<style>
.slider-container {
transition: all 0.3s ease;
}
.slider-container:hover {
transform: translateY(-2px);
}
.preview-container {
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.cabinet-part {
display: none;
position: absolute;
background-color: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 10;
}
.progress-ring {
stroke-dasharray: 314; /* 2 * π * r where r is 50 */
stroke-dashoffset: 0;
transition: stroke-dashoffset 0.5s ease;
}
#modelViewer {
background-color: #f5f3f0;
border-radius: 8px;
overflow: hidden;
position: relative;
}
#priceSection {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.door-handle {
position: relative;
z-index: 5;
}
.shelf {
box-shadow: 0 -2px 5px rgba(0,0,0,0.1);
}
#loadingOverlay {
transition: opacity 0.3s ease;
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<header class="mb-10 text-center">
<h1 class="text-4xl font-bold text-gray-800 mb-2">Parametric Kitchen Cabinet Generator</h1>
<p class="text-gray-600 max-w-2xl mx-auto">Design your perfect kitchen cabinet with real-time 3D preview and instant pricing.</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Controls Section -->
<div class="lg:col-span-1 bg-white rounded-xl shadow-lg p-6">
<div class="mb-8">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Cabinet Configuration</h2>
<!-- Cabinet Type -->
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Cabinet Type</label>
<span id="cabinetTypeDisplay" class="text-gray-500">Wall</span>
</div>
<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">
<option value="wall">Wall Cabinet</option>
<option value="base">Base Cabinet</option>
<option value="tall">Tall Cabinet</option>
<option value="island">Island Cabinet</option>
</select>
</div>
<!-- Dimensions -->
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Width (inches)</label>
<span id="widthValue" class="text-gray-500">24</span>
</div>
<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">
</div>
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Height (inches)</label>
<span id="heightValue" class="text-gray-500">36</span>
</div>
<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">
</div>
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Depth (inches)</label>
<span id="depthValue" class="text-gray-500">12</span>
</div>
<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">
</div>
<!-- Doors -->
<div class="mb-6">
<label class="block text-gray-700 font-medium mb-2">Number of Doors</label>
<div class="flex space-x-4">
<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>
<button id="twoDoors" class="door-btn px-4 py-2 border rounded-lg flex-1 hover:bg-gray-100">2 Doors</button>
</div>
</div>
<!-- Shelves -->
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Number of Shelves</label>
<span id="shelvesValue" class="text-gray-500">1</span>
</div>
<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">
</div>
<!-- Drawers (only for base cabinets) -->
<div class="mb-6 slider-container" id="drawersContainer" style="display: none;">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Number of Drawers</label>
<span id="drawersValue" class="text-gray-500">0</span>
</div>
<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">
</div>
<!-- Material -->
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Material</label>
<span id="materialDisplay" class="text-gray-500">Oak</span>
</div>
<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">
<option value="oak">Oak</option>
<option value="maple">Maple</option>
<option value="cherry">Cherry</option>
<option value="walnut">Walnut</option>
<option value="laminate">Laminate</option>
<option value="painted">Painted MDF</option>
</select>
</div>
<!-- Color -->
<div class="mb-6 slider-container">
<div class="flex justify-between items-center mb-2">
<label class="text-gray-700 font-medium">Color</label>
<span id="colorDisplay" class="text-gray-500">Natural</span>
</div>
<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">
<option value="natural">Natural</option>
<option value="white">White</option>
<option value="black">Black</option>
<option value="gray">Gray</option>
<option value="navy">Navy Blue</option>
<option value="green">Forest Green</option>
</select>
</div>
<!-- Features -->
<div class="mb-6">
<label class="block text-gray-700 font-medium mb-2">Features</label>
<div class="space-y-2">
<label class="flex items-center space-x-3">
<input type="checkbox" id="softClose" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
<span class="text-gray-700">Soft-Close Hinges</span>
</label>
<label class="flex items-center space-x-3">
<input type="checkbox" id="pullOutShelves" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
<span class="text-gray-700">Pull-Out Shelves</span>
</label>
<label class="flex items-center space-x-3">
<input type="checkbox" id="lighting" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
<span class="text-gray-700">LED Lighting</span>
</label>
<label class="flex items-center space-x-3">
<input type="checkbox" id="glassDoors" class="form-checkbox h-5 w-5 text-indigo-600 rounded">
<span class="text-gray-700">Glass Doors</span>
</label>
</div>
</div>
</div>
<div id="priceSection" class="p-5 rounded-xl">
<h3 class="text-lg font-semibold text-gray-800 mb-3">Estimated Price</h3>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-600">Base Price</span>
<span id="basePrice" class="font-medium">$300.00</span>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-600">Material Upgrade</span>
<span id="materialPrice" class="font-medium">$50.00</span>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-600">Features</span>
<span id="featuresPrice" class="font-medium">$75.00</span>
</div>
<div class="flex justify-between items-center mb-4">
<span class="text-gray-600">Options</span>
<span id="optionsPrice" class="font-medium">$25.00</span>
</div>
<div class="border-t border-gray-300 pt-4">
<div class="flex justify-between items-center">
<span class="text-xl font-bold text-gray-800">Total</span>
<span id="totalPrice" class="text-2xl font-bold text-indigo-600">$450.00</span>
</div>
</div>
</div>
</div>
<!-- Preview Section -->
<div class="lg:col-span-2 relative">
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<div class="p-4 border-b border-gray-200">
<div class="flex justify-between items-center">
<h2 class="text-xl font-semibold text-gray-800">3D Model Preview</h2>
<div class="flex space-x-2">
<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">
<i class="fas fa-download mr-2"></i> USDZ
</button>
<button id="downloadGLB" class="px-3 py-1 bg-indigo-600 text-white rounded-lg text-sm flex items-center hover:bg-indigo-700">
<i class="fas fa-download mr-2"></i> GLB
</button>
</div>
</div>
</div>
<div id="modelViewer" class="w-full h-96 rounded-b-xl">
<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">
<div class="w-16 h-16 relative mb-4">
<svg class="w-full h-full" viewBox="0 0 100 100">
<circle class="text-gray-400" cx="50" cy="50" r="40" stroke-width="8" stroke="currentColor" fill="none"></circle>
<circle class="progress-ring text-indigo-600" cx="50" cy="50" r="40" stroke-width="8" stroke="currentColor" fill="none" stroke-dashoffset="314"></circle>
</svg>
</div>
<p class="text-gray-600">Generating 3D model...</p>
</div>
</div>
</div>
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-white p-6 rounded-xl shadow-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Cabinet Specifications</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">Exterior Dimensions:</span>
<span id="dimensionsDisplay" class="font-medium">24" W × 36" H × 12" D</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Interior Capacity:</span>
<span id="capacityDisplay" class="font-medium">6.0 cu.ft</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Material Thickness:</span>
<span class="font-medium">0.75"</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Shelves:</span>
<span id="shelvesDisplay" class="font-medium">1</span>
</div>
<div class="flex justify-between" id="drawersDisplayContainer" style="display: none;">
<span class="text-gray-600">Drawers:</span>
<span id="drawersDisplay" class="font-medium">0</span>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Order Information</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">Production Time:</span>
<span class="font-medium">2-3 weeks</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Shipping:</span>
<span class="font-medium">Flat rate $49.00</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Warranty:</span>
<span class="font-medium">10 years</span>
</div>
</div>
<button class="w-full mt-6 py-3 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition-colors">
Add to Cart
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const widthSlider = document.getElementById('widthSlider');
const heightSlider = document.getElementById('heightSlider');
const depthSlider = document.getElementById('depthSlider');
const widthValue = document.getElementById('widthValue');
const heightValue = document.getElementById('heightValue');
const depthValue = document.getElementById('depthValue');
const oneDoorBtn = document.getElementById('oneDoor');
const twoDoorsBtn = document.getElementById('twoDoors');
const cabinetType = document.getElementById('cabinetType');
const cabinetTypeDisplay = document.getElementById('cabinetTypeDisplay');
const material = document.getElementById('material');
const materialDisplay = document.getElementById('materialDisplay');
const color = document.getElementById('color');
const colorDisplay = document.getElementById('colorDisplay');
const softClose = document.getElementById('softClose');
const pullOutShelves = document.getElementById('pullOutShelves');
const lighting = document.getElementById('lighting');
const glassDoors = document.getElementById('glassDoors');
const basePrice = document.getElementById('basePrice');
const materialPrice = document.getElementById('materialPrice');
const featuresPrice = document.getElementById('featuresPrice');
const optionsPrice = document.getElementById('optionsPrice');
const totalPrice = document.getElementById('totalPrice');
const dimensionsDisplay = document.getElementById('dimensionsDisplay');
const capacityDisplay = document.getElementById('capacityDisplay');
const modelViewer = document.getElementById('modelViewer');
const loadingOverlay = document.getElementById('loadingOverlay');
const downloadUSDZ = document.getElementById('downloadUSDZ');
const downloadGLB = document.getElementById('downloadGLB');
const progressRing = document.querySelector('.progress-ring');
const shelvesSlider = document.getElementById('shelvesSlider');
const shelvesValue = document.getElementById('shelvesValue');
const shelvesDisplay = document.getElementById('shelvesDisplay');
const drawersSlider = document.getElementById('drawersSlider');
const drawersValue = document.getElementById('drawersValue');
const drawersContainer = document.getElementById('drawersContainer');
const drawersDisplay = document.getElementById('drawersDisplay');
const drawersDisplayContainer = document.getElementById('drawersDisplayContainer');
// Three.js variables
let scene, camera, renderer, controls, cabinetGroup;
// Initialize the 3D viewer
function initThreeJS() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf5f3f0);
// Create camera
camera = new THREE.PerspectiveCamera(60, modelViewer.clientWidth / modelViewer.clientHeight, 0.1, 1000);
camera.position.set(30, 30, 30);
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(modelViewer.clientWidth, modelViewer.clientHeight);
renderer.shadowMap.enabled = true;
modelViewer.appendChild(renderer.domElement);
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(20, 30, 20);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight2.position.set(-20, -30, -20);
scene.add(directionalLight2);
// Add controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Create initial cabinet
cabinetGroup = new THREE.Group();
scene.add(cabinetGroup);
updateCabinetModel();
// Add a grid helper and axes helper for debugging (can be removed in production)
const gridHelper = new THREE.GridHelper(100, 100);
scene.add(gridHelper);
const axesHelper = new THREE.AxesHelper(5);
axesHelper.position.y = 0.1; // Slightly above ground
scene.add(axesHelper);
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Start animation loop
animate();
}
function onWindowResize() {
camera.aspect = modelViewer.clientWidth / modelViewer.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(modelViewer.clientWidth, modelViewer.clientHeight);
}
function animate() {
requestAnimationFrame(animate);
if (controls) controls.update();
if (renderer) renderer.render(scene, camera);
}
// Create cabinet geometry with detailed components
function updateCabinetModel() {
showLoading();
// Clear previous cabinet
while (cabinetGroup.children.length > 0) {
cabinetGroup.remove(cabinetGroup.children[0]);
}
// Get current values
const width = parseFloat(widthSlider.value);
const height = parseFloat(heightSlider.value);
const depth = parseFloat(depthSlider.value);
const type = cabinetType.value;
const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
const colorValue = color.value;
const shelvesCount = parseInt(shelvesSlider.value);
const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
const hasGlassDoors = glassDoors.checked;
const thickness = 0.75; // Material thickness in inches
// Set main material
let mainMaterial = createMaterial(colorValue, false);
let interiorMaterial = new THREE.MeshStandardMaterial({
color: 0xF5F5F5,
roughness: 0.7,
metalness: 0.0
});
let shelfMaterial = new THREE.MeshStandardMaterial({
color: 0xEAEAEA,
roughness: 0.6,
metalness: 0.0
});
// These factors help with converting inches to Three.js units
// Since Three.js uses arbitrary units, we'll treat 1 unit = 1 inch
const doorThickness = 0.75;
const handleSize = 2.0; // Door handle size
const shelfThickness = 0.5;
// Create the cabinet box structure first
let cabinetBox = new THREE.Group();
cabinetGroup.add(cabinetBox);
// Bottom panel
createPanel(width, thickness, depth,
[0, thickness/2, 0],
mainMaterial, cabinetBox);
// Top panel
createPanel(width, thickness, depth,
[0, height - thickness/2, 0],
mainMaterial, cabinetBox);
// Left side
createPanel(thickness, height - 2*thickness, depth,
[-width/2 + thickness/2, height/2, 0],
mainMaterial, cabinetBox);
// Right side
createPanel(thickness, height - 2*thickness, depth,
[width/2 - thickness/2, height/2, 0],
mainMaterial, cabinetBox);
// Back panel
createPanel(width - 2*thickness, height - 2*thickness, thickness,
[0, height/2, -depth/2 + thickness/2],
mainMaterial, cabinetBox);
// Calculate interior height (accounting for drawers if they exist)
const interiorHeight = height - 2*thickness - (drawersCount > 0 ? (height * 0.33) : 0);
// Middle divider if two doors (only extends down to drawer section if drawers exist)
if (isTwoDoors) {
const dividerHeight = drawersCount > 0 ? height - 2*thickness - (height * 0.33) : height - 2*thickness;
createPanel(thickness, dividerHeight, depth,
[0, (drawersCount > 0 ? (height/2 - (height * 0.33)/2) : height/2), 0],
mainMaterial, cabinetBox);
}
// Create doors
const doorHeight = drawersCount > 0 ? height - thickness - (height * 0.33) : height - 2*thickness;
const doorYPosition = drawersCount > 0 ? height/2 - (height * 0.33)/2 : height/2;
if (isTwoDoors) {
const doorWidth = (width - thickness) / 2;
// Left door
createDoor(doorWidth, doorHeight, doorThickness,
[-(width - thickness)/2 + doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
mainMaterial, hasGlassDoors, true, cabinetBox);
// Right door
createDoor(doorWidth, doorHeight, doorThickness,
[(width - thickness)/2 - doorThickness/2, doorYPosition, depth/2 - doorThickness/2],
mainMaterial, hasGlassDoors, false, cabinetBox);
} else {
// Single door
const doorWidth = width;
createDoor(doorWidth, doorHeight, doorThickness,
[0, doorYPosition, depth/2 - doorThickness/2],
mainMaterial, hasGlassDoors, false, cabinetBox);
}
// Add shelves only in the upper section (above any drawers)
const availableHeight = interiorHeight;
const shelfSpacing = availableHeight / (shelvesCount + 1);
for (let i = 0; i < shelvesCount; i++) {
const shelfY = thickness + (i + 1) * shelfSpacing;
if (drawersCount > 0) {
// Adjust shelf position to be above the drawer section
const drawerSectionHeight = height * 0.33;
shelfY = thickness + (i + 1) * shelfSpacing + (height - thickness - drawerSectionHeight - availableHeight);
}
createShelf(width - 2*thickness - 0.5, shelfThickness, depth - thickness - 0.5,
[0, shelfY, 0],
shelfMaterial, cabinetBox);
}
// Add drawers for base cabinets
if (type === 'base' && drawersCount > 0) {
const drawerHeight = (height * 0.33) / drawersCount;
const drawerFrontMaterial = createMaterial(colorValue, true); // Slightly different for drawer fronts
for (let i = 0; i < drawersCount; i++) {
const drawerY = thickness + i * drawerHeight + drawerHeight/2;
createDrawer(width, drawerHeight, depth * 0.8,
[0, drawerY, depth/2 - depth*0.1],
drawerFrontMaterial, mainMaterial, cabinetBox);
}
}
// Adjust camera target based on cabinet size
controls.target.set(0, height/2, 0);
controls.update();
camera.position.set(width * 1.2, height * 1.2, depth * 1.2);
// Update capacity calculation
const interiorWidth = width - 2*thickness;
const interiorDepth = depth - thickness;
const capacity = (interiorWidth * interiorHeight * interiorDepth) / 1728; // convert to cubic feet
// Hide loading after a delay
setTimeout(hideLoading, 800);
// Update displays
capacityDisplay.textContent = `${capacity.toFixed(1)} cu.ft`;
dimensionsDisplay.textContent = `${width}" W × ${height}" H × ${depth}" D`;
shelvesDisplay.textContent = shelvesCount;
if (drawersCount > 0) {
drawersDisplay.textContent = drawersCount;
}
}
function createMaterial(colorValue, isDrawerFront = false) {
let materialOptions = {};
if (colorValue === 'natural') {
materialOptions = {
color: 0xC19A6B,
roughness: isDrawerFront ? 0.3 : 0.4,
metalness: 0.1
};
} else if (colorValue === 'white') {
materialOptions = {
color: 0xF5F5F5,
roughness: 0.1,
metalness: 0.05
};
} else if (colorValue === 'black') {
materialOptions = {
color: 0x222222,
roughness: 0.3,
metalness: 0.2
};
} else if (colorValue === 'gray') {
materialOptions = {
color: 0x808080,
roughness: 0.3,
metalness: 0.15
};
} else if (colorValue === 'navy') {
materialOptions = {
color: 0x2C3E50,
roughness: 0.3,
metalness: 0.1
};
} else { // green
materialOptions = {
color: 0x3B5E2E,
roughness: 0.3,
metalness: 0.1
};
}
return new THREE.MeshStandardMaterial(materialOptions);
}
function createPanel(width, height, depth, position, material, parentGroup) {
const geometry = new THREE.BoxGeometry(width, height, depth);
const panel = new THREE.Mesh(geometry, material);
panel.castShadow = true;
panel.receiveShadow = true;
panel.position.set(position[0], position[1], position[2]);
parentGroup.add(panel);
return panel;
}
function createDoor(width, height, thickness, position, material, hasGlass, isLeftDoor, parentGroup) {
const doorGroup = new THREE.Group();
doorGroup.position.set(position[0], position[1], position[2]);
// Door frame
const frameGeometry = new THREE.BoxGeometry(width, height, thickness);
const frame = new THREE.Mesh(frameGeometry, material);
frame.castShadow = true;
frame.receiveShadow = true;
doorGroup.add(frame);
// Glass panel if selected
if (hasGlass) {
const glassWidth = width * 0.9;
const glassHeight = height * 0.8;
const glassGeometry = new THREE.BoxGeometry(glassWidth, glassHeight, thickness * 0.05);
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xE6F0FF,
transmission: 0.9,
roughness: 0.0,
metalness: 0.0,
transparent: true,
opacity: 0.8,
ior: 1.5,
thickness: 0.1
});
const glass = new THREE.Mesh(glassGeometry, glassMaterial);
glass.position.z = thickness * 0.3;
doorGroup.add(glass);
}
// Handle
const handleGeometry = new THREE.CylinderGeometry(0.5, 0.5, 3, 16);
const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
handle.rotation.z = Math.PI/2;
handle.position.set(isLeftDoor ? -width/2 + 2 : width/2 - 2, 0, thickness);
doorGroup.add(handle);
// Hinges (simple representation)
const hingeGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 16);
const hingeMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
// Top hinge
const topHinge = new THREE.Mesh(hingeGeometry, hingeMaterial);
topHinge.rotation.x = Math.PI/2;
topHinge.position.set(isLeftDoor ? width/2 - 1 : -width/2 + 1, height/2 - 2, -thickness/2);
doorGroup.add(topHinge);
// Bottom hinge
const bottomHinge = new THREE.Mesh(hingeGeometry, hingeMaterial);
bottomHinge.rotation.x = Math.PI/2;
bottomHinge.position.set(isLeftDoor ? width/2 - 1 : -width/2 + 1, -height/2 + 2, -thickness/2);
doorGroup.add(bottomHinge);
parentGroup.add(doorGroup);
return doorGroup;
}
function createShelf(width, thickness, depth, position, material, parentGroup) {
const shelfGroup = new THREE.Group();
shelfGroup.position.set(position[0], position[1], position[2]);
// Main shelf
const shelfGeometry = new THREE.BoxGeometry(width, thickness, depth);
const shelf = new THREE.Mesh(shelfGeometry, material);
shelf.castShadow = true;
shelf.receiveShadow = true;
shelfGroup.add(shelf);
// Shelf supports
const supportGeometry = new THREE.BoxGeometry(0.5, 1.5, 0.5);
const supportMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
// Left support
const leftSupport = new THREE.Mesh(supportGeometry, supportMaterial);
leftSupport.position.set(-width/2 + 0.25, -thickness/2 - 0.75, 0);
shelfGroup.add(leftSupport);
// Right support
const rightSupport = new THREE.Mesh(supportGeometry, supportMaterial);
rightSupport.position.set(width/2 - 0.25, -thickness/2 - 0.75, 0);
shelfGroup.add(rightSupport);
if (width > 24) {
// Middle support for wider shelves
const midSupport = new THREE.Mesh(supportGeometry, supportMaterial);
midSupport.position.set(0, -thickness/2 - 0.75, 0);
shelfGroup.add(midSupport);
}
parentGroup.add(shelfGroup);
return shelfGroup;
}
function createDrawer(width, height, depth, position, frontMaterial, sideMaterial, parentGroup) {
const drawerGroup = new THREE.Group();
drawerGroup.position.set(position[0], position[1], position[2]);
// Drawer body
const bodyWidth = width * 0.9;
const bodyHeight = height * 0.85;
const bodyDepth = depth;
// Front
const frontGeometry = new THREE.BoxGeometry(width, height, 0.5);
const front = new THREE.Mesh(frontGeometry, frontMaterial);
front.position.set(0, 0, depth/2 + 0.25);
drawerGroup.add(front);
// Bottom
const bottomGeometry = new THREE.BoxGeometry(width * 0.9, 0.2, depth);
const bottom = new THREE.Mesh(bottomGeometry, sideMaterial);
bottom.position.set(0, -height/2 + 0.1, 0);
drawerGroup.add(bottom);
// Sides
const sideWidth = width * 0.9;
const sideGeometry = new THREE.BoxGeometry(0.2, bodyHeight, depth);
// Left side
const leftSide = new THREE.Mesh(sideGeometry, sideMaterial);
leftSide.position.set(-sideWidth/2, 0, 0);
drawerGroup.add(leftSide);
// Right side
const rightSide = new THREE.Mesh(sideGeometry, sideMaterial);
rightSide.position.set(sideWidth/2, 0, 0);
drawerGroup.add(rightSide);
// Back
const backGeometry = new THREE.BoxGeometry(sideWidth, bodyHeight, 0.2);
const back = new THREE.Mesh(backGeometry, sideMaterial);
back.position.set(0, 0, -depth/2 + 0.1);
drawerGroup.add(back);
// Handle (as a bar)
const handleGeometry = new THREE.CylinderGeometry(0.3, 0.3, 3, 16);
const handleMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
handle.rotation.z = Math.PI/2;
handle.position.set(0, 0, depth/2 + 0.3);
drawerGroup.add(handle);
// Drawer slides
const slideGeometry = new THREE.BoxGeometry(0.1, 0.5, depth * 1.2);
const slideMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
// Left slide
const leftSlide = new THREE.Mesh(slideGeometry, slideMaterial);
leftSlide.position.set(-width/2 + 0.25, -height/2 + 0.25, 0);
drawerGroup.add(leftSlide);
// Right slide
const rightSlide = new THREE.Mesh(slideGeometry, slideMaterial);
rightSlide.position.set(width/2 - 0.25, -height/2 + 0.25, 0);
drawerGroup.add(rightSlide);
parentGroup.add(drawerGroup);
return drawerGroup;
}
function showLoading() {
loadingOverlay.style.opacity = 1;
loadingOverlay.style.pointerEvents = 'auto';
let progress = 0;
progressRing.style.strokeDashoffset = 314;
const interval = setInterval(() => {
progress += 10;
const offset = 314 * (1 - progress/100);
progressRing.style.strokeDashoffset = offset;
if (progress >= 100) {
clearInterval(interval);
}
}, 50);
}
function hideLoading() {
loadingOverlay.style.opacity = 0;
loadingOverlay.style.pointerEvents = 'none';
}
// Update price calculation
function updatePrice() {
const width = parseFloat(widthSlider.value);
const height = parseFloat(heightSlider.value);
const depth = parseFloat(depthSlider.value);
const mat = material.value;
const isTwoDoors = twoDoorsBtn.classList.contains('bg-indigo-100');
const hasSoftClose = softClose.checked;
const hasPullOutShelves = pullOutShelves.checked;
const hasLighting = lighting.checked;
const hasGlassDoors = glassDoors.checked;
const shelvesCount = parseInt(shelvesSlider.value);
const drawersCount = cabinetType.value === 'base' ? parseInt(drawersSlider.value) : 0;
// Calculate base price based on size and type
let base = 100;
const sizeFactor = (width * height * depth) / 10000;
base += sizeFactor * 100;
if (cabinetType.value === 'base') base *= 1.3;
else if (cabinetType.value === 'tall') base *= 1.4;
else if (cabinetType.value === 'island') base *= 1.6;
// Material premium
let matPremium = 0;
if (mat === 'cherry' || mat === 'walnut') matPremium = 120;
else if (mat === 'maple') matPremium = 80;
else if (mat === 'oak') matPremium = 40;
else if (mat === 'painted') matPremium = 60;
else if (mat === 'laminate') matPremium = 0;
// Features
let featuresPremium = 0;
if (hasSoftClose) featuresPremium += 40;
if (hasPullOutShelves) featuresPremium += 80;
if (hasLighting) featuresPremium += 60;
if (hasGlassDoors) featuresPremium += 90;
// Options (shelves, drawers)
let optionsPremium = shelvesCount * 20;
optionsPremium += drawersCount * 45;
if (isTwoDoors) optionsPremium += 35;
// Update displays
basePrice.textContent = `$${base.toFixed(2)}`;
materialPrice.textContent = `$${matPremium.toFixed(2)}`;
featuresPrice.textContent = `$${featuresPremium.toFixed(2)}`;
optionsPrice.textContent = `$${optionsPremium.toFixed(2)}`;
totalPrice.textContent = `$${(base + matPremium + featuresPremium + optionsPremium).toFixed(2)}`;
}
// Event listeners
widthSlider.addEventListener('input', () => {
widthValue.textContent = widthSlider.value;
updateCabinetModel();
updatePrice();
});
heightSlider.addEventListener('input', () => {
heightValue.textContent = heightSlider.value;
updateCabinetModel();
updatePrice();
});
depthSlider.addEventListener('input', () => {
depthValue.textContent = depthSlider.value;
updateCabinetModel();
updatePrice();
});
oneDoorBtn.addEventListener('click', () => {
oneDoorBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
twoDoorsBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
updateCabinetModel();
updatePrice();
});
twoDoorsBtn.addEventListener('click', () => {
twoDoorsBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
oneDoorBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300');
updateCabinetModel();
updatePrice();
});
shelvesSlider.addEventListener('input', () => {
shelvesValue.textContent = shelvesSlider.value;
shelvesDisplay.textContent = shelvesSlider.value;
updateCabinetModel();
updatePrice();
});
drawersSlider.addEventListener('input', () => {
drawersValue.textContent = drawersSlider.value;
drawersDisplay.textContent = drawersSlider.value;
updateCabinetModel();
updatePrice();
});
cabinetType.addEventListener('change', () => {
cabinetTypeDisplay.textContent = cabinetType.options[cabinetType.selectedIndex].text;
// Show/hide drawers control based on cabinet type
if (cabinetType.value === 'base') {
drawersContainer.style.display = 'block';
drawersDisplayContainer.style.display = 'flex';
} else {
drawersContainer.style.display = 'none';
drawersDisplayContainer.style.display = 'none';
drawersSlider.value = 0;
drawersValue.textContent = '0';
drawersDisplay.textContent = '0';
}
// Adjust default dimensions based on cabinet type
if (cabinetType.value === 'wall') {
heightSlider.value = 36;
depthSlider.value = 12;
heightValue.textContent = '36';
depthValue.textContent = '12';
} else if (cabinetType.value === 'base') {
heightSlider.value = 34.5;
depthSlider.value = 24;
heightValue.textContent = '34.5';
depthValue.textContent = '24';
} else if (cabinetType.value === 'tall') {
heightSlider.value = 84;
depthSlider.value = 24;
heightValue.textContent = '84';
depthValue.textContent = '24';
} else if (cabinetType.value === 'island') {
heightSlider.value = 36;
depthSlider.value = 24;
heightValue.textContent = '36';
depthValue.textContent = '24';
}
updateCabinetModel();
updatePrice();
});
material.addEventListener('change', () => {
materialDisplay.textContent = material.options[material.selectedIndex].text;
updatePrice();
updateCabinetModel();
});
color.addEventListener('change', () => {
colorDisplay.textContent = color.options[color.selectedIndex].text;
updatePrice();
updateCabinetModel();
});
softClose.addEventListener('change', updatePrice);
pullOutShelves.addEventListener('change', updatePrice);
lighting.addEventListener('change', updatePrice);
glassDoors.addEventListener('change', () => {
updatePrice();
updateCabinetModel();
});
// Simulate download buttons (in a real app, these would generate actual files)
downloadUSDZ.addEventListener('click', () => {
alert('In a real implementation, this would generate and download a USDZ file of your cabinet design.');
});
downloadGLB.addEventListener('click', () => {
alert('In a real implementation, this would generate and download a GLB/GLTF file of your cabinet design.');
});
// Initialize
window.addEventListener('load', () => {
initThreeJS();
updatePrice();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gladiopeace/cabinet" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>