import React, { useState, useCallback, useRef, useEffect } from ‚react‘;
import {
Trash2,
Box,
Layers,
Magnet,
Layout,
Maximize,
Compass,
PenTool,
Plus,
Move,
Hand,
Archive,
ArrowUpRight,
GripHorizontal,
DoorOpen,
AlignVerticalSpaceAround,
Save,
FolderOpen,
Copy,
CheckCircle2,
Library,
Armchair,
Monitor,
BookmarkPlus,
FilePlus,
ZoomIn,
Download,
Upload,
Grid3X3,
Columns,
Undo2,
Redo2,
Scan
} from ‚lucide-react‘;
const App = () => {
const plateThickness = 1.6; // 16mm Fixum
// Fix: Tailwind Script Injection für Umgebungen, in denen es fehlt
useEffect(() => {
if (!document.getElementById(‚tailwind-script‘)) {
const script = document.createElement(’script‘);
script.id = ‚tailwind-script‘;
script.src = „https://cdn.tailwindcss.com“;
document.head.appendChild(script);
}
}, []);
// Preset-Definitionen für den Katalog (Statisch)
const STATIC_PRESETS = [
{
name: ‚Sideboard „Klassik“‚,
icon:
,
config: {
type: ‚corpus‘, w: 120, d: 40, h: 80, shelves: 1,
shelfPositions: [],
fronts: { 0: ‚door_left‘, 1: ‚door_right‘ },
verticals: { 0: 1, 1: 1 }
}
},
{
name: ‚Kommode „Organizer“‚,
icon:
,
config: {
type: ‚corpus‘, w: 60, d: 45, h: 90, shelves: 3,
shelfPositions: [],
fronts: { 0: ‚drawer‘, 1: ‚drawer‘, 2: ‚drawer‘, 3: ‚drawer‘ },
verticals: {}
}
},
{
name: ‚TV-Lowboard‘,
icon:
,
config: {
type: ‚corpus‘, w: 160, d: 50, h: 45, shelves: 0,
shelfPositions: [],
fronts: { 0: ‚flap_down‘ },
verticals: { 0: 2 }
}
},
{
name: ‚Highboard „Vitrine“‚,
icon:
,
config: {
type: ‚corpus‘, w: 80, d: 35, h: 140, shelves: 3,
shelfPositions: [],
fronts: { 0: ‚drawer‘, 1: ‚door_left‘, 2: ’none‘, 3: ‚door_left‘ },
verticals: {}
}
}
];
// — STATE MANAGEMENT —
// Zustand für die Objekte im Raum
const [items, setItems] = useState([
{
id: 1,
type: ‚corpus‘,
x: 0, y: 0, z: 0,
w: 80, d: 40, h: 80,
shelves: 2,
shelfPositions: [],
drawerOpen: 0,
frontOpen: 0,
fronts: {},
verticals: {}
}
]);
const [activeId, setActiveId] = useState(1);
// Active Item Helper (WICHTIG: Muss vor der Verwendung definiert sein)
const activeItem = items.find(i => i.id === activeId);
const [isSnappingEnabled, setIsSnappingEnabled] = useState(true);
// Undo/Redo History State
const [history, setHistory] = useState({ past: [], future: [] });
// Viewport Settings
const [showFloor, setShowFloor] = useState(true);
const [blueprintView, setBlueprintView] = useState(‚front‘); // ‚front‘ | ‚top‘ | ’side‘
// Collection & Custom Presets State
const [collectionName, setCollectionName] = useState(‚Meine Erste Kollektion‘);
const [savedCollections, setSavedCollections] = useState([]);
const [customPresets, setCustomPresets] = useState([]);
const [showLoadMenu, setShowLoadMenu] = useState(false);
const [saveStatus, setSaveStatus] = useState(null);
// UI Resize State
const [blueprintHeight, setBlueprintHeight] = useState(350);
const isResizingUI = useRef(false);
// 3D Viewport Rotation & Zoom State
const [viewRotation, setViewRotation] = useState({ x: 62, z: -35 });
const [zoom, setZoom] = useState(0.8);
const [isRotating, setIsRotating] = useState(false);
const lastMousePos = useRef({ x: 0, y: 0 });
// Dragging State
const [dragState, setDragState] = useState(null);
const svgRef = useRef(null);
const fileInputRef = useRef(null);
const snapDistance = 4; // Snapping-Radius
const gridStep = 5; // Raster für Items
// Load Data from LocalStorage on mount
useEffect(() => {
try {
const savedCols = localStorage.getItem(‚bebobel_collections‘);
if (savedCols) setSavedCollections(JSON.parse(savedCols));
const savedPresets = localStorage.getItem(‚bebobel_presets‘);
if (savedPresets) setCustomPresets(JSON.parse(savedPresets));
} catch (e) {
console.error(„Fehler beim Laden der Daten“, e);
}
}, []);
// — HELPERS —
const getShelfPositions = (item) => {
if (!item) return [];
const innerH = item.h – 2 * plateThickness;
const count = parseInt(item.shelves || 0);
if (item.shelfPositions && item.shelfPositions.length === count) return item.shelfPositions;
const gap = innerH / (count + 1);
return Array.from({ length: count }, (_, i) => (i + 1) * gap);
};
const getCompartments = (item) => {
if (!item) return [];
const innerH = item.h – 2 * plateThickness;
const positions = getShelfPositions(item);
const boundaries = [0, …positions, innerH];
const compartments = [];
for (let i = 0; i < boundaries.length - 1; i++) {
const bottom = boundaries[i];
const top = boundaries[i+1];
let yStart = bottom + (i === 0 ? 0 : plateThickness/2);
let yEnd = top - (i === boundaries.length - 2 ? 0 : plateThickness/2);
compartments.push({ index: i, yRelative: yStart, h: Math.max(0.1, yEnd - yStart), centerYRelative: yStart + (yEnd - yStart) / 2 });
}
return compartments;
};
const getBlueprintCoords = (item) => {
if (blueprintView === ‚front‘) {
return { x: item.x – item.w/2, y: -item.y – item.h, w: item.w, h: item.h, labelX: item.w, labelY: item.h };
} else if (blueprintView === ‚top‘) {
return { x: item.x – item.w/2, y: item.z – item.d/2, w: item.w, h: item.d, labelX: item.w, labelY: item.d };
} else if (blueprintView === ’side‘) {
return { x: item.z – item.d/2, y: -item.y – item.h, w: item.d, h: item.h, labelX: item.d, labelY: item.h };
}
return { x: 0, y: 0, w: 10, h: 10 };
};
// — HISTORY —
const saveToHistory = useCallback(() => {
setHistory(curr => {
const newPast = […curr.past, items];
if (newPast.length > 10) newPast.shift();
return {
past: newPast,
future: []
};
});
}, [items]);
const undo = () => {
if (history.past.length === 0) return;
const previous = history.past[history.past.length – 1];
const newPast = history.past.slice(0, -1);
setHistory({
past: newPast,
future: [items, …history.future]
});
setItems(previous);
if (!previous.find(i => i.id === activeId)) {
setActiveId(previous[0]?.id || 0);
}
};
const redo = () => {
if (history.future.length === 0) return;
const next = history.future[0];
const newFuture = history.future.slice(1);
setHistory({
past: […history.past, items],
future: newFuture
});
setItems(next);
if (!next.find(i => i.id === activeId)) {
setActiveId(next[0]?.id || 0);
}
};
// — ACTIONS —
const createNewScene = () => {
saveToHistory();
let baseName = ‚Neues Projekt‘;
let uniqueName = baseName;
let counter = 1;
while (savedCollections.some(c => c.name === uniqueName)) {
uniqueName = `${baseName} ${counter}`;
counter++;
}
const initialItem = {
id: 1, type: ‚corpus‘, x: 0, y: 0, z: 0, w: 80, d: 40, h: 80,
shelves: 2, shelfPositions: [], drawerOpen: 0, frontOpen: 0, fronts: {}, verticals: {}
};
setItems([initialItem]);
setActiveId(1);
setCollectionName(uniqueName);
const newCollection = {
name: uniqueName, items: [initialItem], date: new Date().toISOString()
};
const newCollections = […savedCollections, newCollection];
setSavedCollections(newCollections);
localStorage.setItem(‚bebobel_collections‘, JSON.stringify(newCollections));
setSaveStatus(’saved‘);
setTimeout(() => setSaveStatus(null), 2000);
};
const saveCollection = () => {
const newCollection = {
name: collectionName, items: items, date: new Date().toISOString()
};
const existingIndex = savedCollections.findIndex(c => c.name === collectionName);
let newCollections = […savedCollections];
if (existingIndex >= 0) newCollections[existingIndex] = newCollection;
else newCollections.push(newCollection);
setSavedCollections(newCollections);
localStorage.setItem(‚bebobel_collections‘, JSON.stringify(newCollections));
setSaveStatus(’saved‘);
setTimeout(() => setSaveStatus(null), 2000);
};
const handleExport = () => {
const data = {
name: collectionName, items: items, date: new Date().toISOString(), appVersion: ‚bebobel-1.0‘
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: ‚application/json‘ });
const url = URL.createObjectURL(blob);
const link = document.createElement(‚a‘);
link.href = url;
link.download = `${collectionName.replace(/[^a-z0-9]/gi, ‚_‘).toLowerCase()}.bebobel.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const handleImport = (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const imported = JSON.parse(event.target.result);
if (imported.items && Array.isArray(imported.items)) {
saveToHistory();
setItems(imported.items);
setCollectionName(imported.name || „Importiertes Projekt“);
setActiveId(imported.items[0]?.id || 1);
setShowLoadMenu(false);
} else {
alert(„Ungültiges Dateiformat.“);
}
} catch (error) {
console.error(„Import Error“, error);
alert(„Fehler beim Laden der Datei.“);
}
};
reader.readAsText(file);
e.target.value = “;
};
const addToCatalog = () => {
const item = items.find(i => i.id === activeId);
if (!item) return;
const newPreset = {
name: `Custom ${item.w}x${item.h}`,
config: {
…item, id: undefined, x: 0, y: 0, z: 0,
shelfPositions: […(item.shelfPositions || [])], fronts: { …item.fronts }, verticals: { …item.verticals }
}
};
const updatedPresets = […customPresets, newPreset];
setCustomPresets(updatedPresets);
localStorage.setItem(‚bebobel_presets‘, JSON.stringify(updatedPresets));
};
const loadCollection = (collection) => {
saveToHistory();
setItems(collection.items);
setCollectionName(collection.name);
setActiveId(collection.items[0]?.id || 0);
setShowLoadMenu(false);
};
const duplicateItem = (id) => {
saveToHistory();
const itemToClone = items.find(i => i.id === id);
if (!itemToClone) return;
const newId = Math.max(…items.map(i => i.id), 0) + 1;
const newItem = {
…itemToClone, id: newId, x: itemToClone.x + 20, y: itemToClone.y,
fronts: { …itemToClone.fronts }, verticals: { …itemToClone.verticals },
shelfPositions: [ …itemToClone.shelfPositions ]
};
setItems([…items, newItem]);
setActiveId(newId);
};
const spawnItem = (type) => {
saveToHistory();
const newId = Math.max(…items.map(i => i.id), 0) + 1;
let newItem = {
id: newId, type: type, x: 0, y: 0, z: 0,
w: 60, d: 40, h: type === ‚vertical‘ ? 72 : (type === ‚corpus‘ ? 60 : 1.6),
shelves: type === ‚corpus‘ ? 0 : undefined,
shelfPositions: [], drawerOpen: 0, frontOpen: 0, fronts: type === ‚corpus‘ ? {} : undefined, verticals: type === ‚corpus‘ ? {} : undefined
};
setItems([…items, newItem]);
setActiveId(newId);
};
const spawnPreset = (presetConfig) => {
saveToHistory();
const newId = Math.max(…items.map(i => i.id), 0) + 1;
let newItem = {
…presetConfig, id: newId, x: 0, y: 0, z: 0,
drawerOpen: 0, frontOpen: 0,
shelfPositions: presetConfig.shelfPositions ? […presetConfig.shelfPositions] : [],
fronts: presetConfig.fronts ? {…presetConfig.fronts} : {},
verticals: presetConfig.verticals ? {…presetConfig.verticals} : {}
};
setItems([…items, newItem]);
setActiveId(newId);
};
const updateActiveItem = (key, value) => {
const parsed = (key === ‚fronts‘ || key === ‚verticals‘) ? value : parseFloat(value);
const val = ((key !== ‚fronts‘ && key !== ‚verticals‘) && isNaN(parsed)) ? 0 : parsed;
setItems(prev => {
let extraUpdates = {};
if (key === ’shelves‘) extraUpdates = { shelfPositions: [] };
const updated = prev.map(i => i.id === activeId ? { …i, …extraUpdates, [key]: val } : i);
const active = updated.find(i => i.id === activeId);
if (!active) return prev;
const snapped = applySnapping(active, updated);
return updated.map(i => i.id === activeId ? snapped : i);
});
};
const updateFrontConfig = (index, type) => {
saveToHistory();
const active = items.find(i => i.id === activeId);
if (!active) return;
const newFronts = { …active.fronts, [index]: type };
updateActiveItem(‚fronts‘, newFronts);
};
const updateVerticalConfig = (index, count) => {
saveToHistory();
const active = items.find(i => i.id === activeId);
if (!active) return;
const newVerticals = { …active.verticals, [index]: parseInt(count) };
updateActiveItem(‚verticals‘, newVerticals);
};
const removeItem = (id) => {
saveToHistory();
if (items.length <= 1) return;
const newItems = items.filter(i => i.id !== id);
setItems(newItems);
setActiveId(newItems[0].id);
};
// — EVENTS —
const startResizingUI = (e) => {
isResizingUI.current = true;
document.body.style.cursor = ‚row-resize‘;
};
const stopResizingUI = () => {
isResizingUI.current = false;
document.body.style.cursor = ‚default‘;
};
const handleMouseMoveGlobal = useCallback((e) => {
if (isResizingUI.current) {
const newHeight = window.innerHeight – e.clientY – 40;
setBlueprintHeight(Math.max(150, Math.min(window.innerHeight – 250, newHeight)));
}
if (isRotating) {
const deltaX = e.clientX – lastMousePos.current.x;
const deltaY = e.clientY – lastMousePos.current.y;
setViewRotation(prev => ({
x: Math.max(10, Math.min(90, prev.x – deltaY * 0.5)),
z: prev.z + deltaX * 0.5
}));
lastMousePos.current = { x: e.clientX, y: e.clientY };
}
}, [isRotating]);
useEffect(() => {
window.addEventListener(‚mousemove‘, handleMouseMoveGlobal);
window.addEventListener(‚mouseup‘, () => {
stopResizingUI();
setIsRotating(false);
setDragState(null);
});
return () => {
window.removeEventListener(‚mousemove‘, handleMouseMoveGlobal);
};
}, [handleMouseMoveGlobal]);
const handleMouseDown3D = (e) => {
if (e.target.closest(‚.no-orbit‘)) return;
setIsRotating(true);
lastMousePos.current = { x: e.clientX, y: e.clientY };
};
const handleWheel = (e) => {
const scaleAmount = -e.deltaY * 0.001;
setZoom(prev => Math.min(Math.max(0.3, prev + scaleAmount), 2.5));
};
const applySnapping = useCallback((current, all) => {
if (!isSnappingEnabled) return current;
let snappedItem = { …current };
all.forEach(other => {
if (other.id === current.id) return;
if (Math.abs(current.y – (other.y + other.h)) < snapDistance) snappedItem.y = other.y + other.h;
else if (Math.abs((current.y + current.h) - other.y) < snapDistance) snappedItem.y = other.y - current.h;
if (Math.abs(current.x - other.x) < snapDistance) snappedItem.x = other.x;
if (Math.abs(current.z - other.z) < snapDistance) snappedItem.z = other.z;
});
return snappedItem;
}, [isSnappingEnabled]);
const handleSVGMouseMove = (e) => {
if (!dragState || !svgRef.current) return;
const svg = svgRef.current;
const CTM = svg.getScreenCTM();
if (!CTM) return;
const mouseX = (e.clientX – CTM.e) / CTM.a;
const mouseY = (e.clientY – CTM.f) / CTM.d;
if (dragState.type === ‚item‘) {
setItems(prev => {
const active = prev.find(i => i.id === dragState.itemId);
if (!active) return prev;
let newX = active.x;
let newY = active.y;
let newZ = active.z;
// Update logic based on view plane
if (blueprintView === ‚front‘) {
newX = Math.round(mouseX / gridStep) * gridStep;
newY = Math.round(-mouseY / gridStep) * gridStep; // SVG Y is down
} else if (blueprintView === ‚top‘) {
newX = Math.round(mouseX / gridStep) * gridStep;
newZ = Math.round(mouseY / gridStep) * gridStep;
} else if (blueprintView === ’side‘) {
newZ = Math.round(mouseX / gridStep) * gridStep;
newY = Math.round(-mouseY / gridStep) * gridStep;
}
const updated = prev.map(i => i.id === dragState.itemId ? { …i, x: newX, y: newY, z: newZ } : i);
const newActive = updated.find(i => i.id === dragState.itemId);
const snapped = applySnapping(newActive, updated);
return updated.map(i => i.id === dragState.itemId ? snapped : i);
});
} else if (dragState.type === ’shelf‘) {
// Shelf dragging only works in Front view properly for now (height)
if (blueprintView !== ‚front‘) return;
const worldMouseY = -mouseY; // Only relevant for Front view
setItems(prev => {
const item = prev.find(i => i.id === dragState.itemId);
if (!item) return prev;
const innerH = item.h – 2 * plateThickness;
let newRelY = worldMouseY – item.y – plateThickness;
newRelY = Math.round(newRelY);
newRelY = Math.max(2, Math.min(innerH – 2, newRelY));
const positions = […getShelfPositions(item)];
positions[dragState.shelfIndex] = newRelY;
positions.sort((a, b) => a – b);
return prev.map(i => i.id === dragState.itemId ? { …i, shelfPositions: positions } : i);
});
}
};
// 3D Render
const renderBlock = (w, d, h, x, y, z, isActive, id, colorClass = „bg-white“, isDrawerFront = false, key, style = {}) => {
const scale = 2.2;
// Safety
w = w||0; d=d||0; h=h||0; x=x||0; y=y||0; z=z||0;
const borderColor = isActive ? ‚border-gray-500‘ : ‚border-black/20‘;
const sideColor = isActive ? ‚bg-gray-300‘ : ‚bg-gray-100‘;
return (
{ e.stopPropagation(); if (id) setActiveId(id); }}
className={`absolute transition-all duration-300 transform-gpu cursor-pointer no-orbit ${isActive ? ‚z-50‘ : ‚z-0‘}`}
style={{
width: `${w * scale}px`, height: `${d * scale}px`,
transform: `translate3d(${(x – w/2) * scale}px, ${(z – d/2) * scale}px, ${y * scale}px)`,
transformStyle: ‚preserve-3d‘,
…style
}}
>
{/* Top Face */}
{/* Bottom Face */}
{/* Front Face (South) */}
{/* Back Face (North) */}
{/* Right Face (East) */}
{/* Left Face (West) */}
);
};
return (
{/* Header */}
{/* Sidebar */}
{/* Hauptbereich */}
{/* 3D Viewport */}
{/* Viewport Overlay */}
{showFloor && (
)}
{showFloor && items.map(item => {
const scale = 2.2;
const shadowOpacity = Math.max(0.1, 0.4 – (item.y / 200));
const blur = Math.max(8, item.y / 3);
return
;
})}
{items.map((item) => {
const isActive = activeId === item.id;
if (item.type === ‚corpus‘) {
const { w, d, h, x, y, z, drawerOpen, frontOpen, fronts, verticals } = item;
const t = plateThickness;
const internalElements = [];
const openPercent = (drawerOpen || 0) / 100;
const frontOpenPercent = (frontOpen || 0) / 100;
const shelfPositions = getShelfPositions(item);
shelfPositions.forEach((pos, i) => {
const absoluteY = y + t + pos;
internalElements.push(renderBlock(w – 2*t, d – t, t, x, absoluteY, z + t/2, isActive, item.id, „bg-gray-100“, false, `shelf-${item.id}-${i}`));
});
const compartments = getCompartments(item);
compartments.forEach((comp, i) => {
const frontType = fronts?.[i];
const numVerticals = verticals?.[i] || 0;
const cH = comp.h;
const cY = y + t + comp.yRelative;
if (numVerticals > 0) {
const innerW = w – 2*t;
const sectionWidth = (innerW – numVerticals * t) / (numVerticals + 1);
const startX = x – innerW/2;
for (let v = 0; v < numVerticals; v++) {
const vX = startX + (v + 1) * sectionWidth + v * t + t/2;
internalElements.push(renderBlock(t, d - t, cH, vX, cY, z + t/2, isActive, item.id, "bg-gray-100", false, `vertical-${item.id}-${i}-${v}`));
}
}
if (frontType && frontType !== 'none') {
const fW = w - 0.4;
const fH = cH - 0.4;
const fD = 1.6; // FIX: fD muss HIER definiert werden, BEVOR es in fZ benutzt wird
const fZ = z + d/2 + fD/2;
const rot = (frontOpenPercent * 90);
if (frontType === 'drawer') {
const drawerFrontH = cH - 0.2;
const openOffset = openPercent * (d - 5);
const boxSideH = drawerFrontH * 0.7;
const boxD = d - t;
const drawerY = cY;
const numDrawers = numVerticals + 1;
const innerW = w - 2*t;
const drawerSectionW = (innerW - numVerticals * t) / numDrawers;
const startX = x - innerW/2;
for (let dIdx = 0; dIdx < numDrawers; dIdx++) {
const currentDrawerX = startX + dIdx * (drawerSectionW + t) + drawerSectionW/2;
const currentBoxW = drawerSectionW - 0.5;
const currentFrontW = drawerSectionW - 0.4;
if (openPercent > 0.01) {
internalElements.push(renderBlock(currentBoxW – 1, boxD – 1, 0.5, currentDrawerX, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-50“, false, `drawer-base-${item.id}-${i}-${dIdx}`));
internalElements.push(renderBlock(0.5, boxD – 1, boxSideH, currentDrawerX – currentBoxW/2 + 0.5, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-100“, false, `drawer-side-l-${item.id}-${i}-${dIdx}`));
internalElements.push(renderBlock(0.5, boxD – 1, boxSideH, currentDrawerX + currentBoxW/2 – 0.5, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-100“, false, `drawer-side-r-${item.id}-${i}-${dIdx}`));
internalElements.push(renderBlock(currentBoxW – 1, 0.5, boxSideH, currentDrawerX, drawerY + 0.5, z – boxD/2 + 0.5 + openOffset, isActive, item.id, „bg-gray-200“, false, `drawer-back-${item.id}-${i}-${dIdx}`));
}
internalElements.push(renderBlock(currentFrontW, 0.8, drawerFrontH, currentDrawerX, drawerY, z + d/2 + openOffset, isActive, item.id, „bg-white“, true, `drawer-front-${item.id}-${i}-${dIdx}`));
}
} else {
let styleOverride = {};
if (frontType === ‚door_left‘) styleOverride = { transformOrigin: ‚0% 50%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateZ(${rot}deg)` };
else if (frontType === ‚door_right‘) styleOverride = { transformOrigin: ‚100% 50%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateZ(${-rot}deg)` };
else if (frontType === ‚flap_down‘) styleOverride = { transformOrigin: ‚50% 0%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateX(${-rot}deg)` };
else if (frontType === ‚flap_up‘) styleOverride = { transformOrigin: `50% 0% ${(fH * 2.2)}px`, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateX(${rot}deg)` };
internalElements.push(
);
}
}
});
return (
{renderBlock(w, d, t, x, y, z, isActive, item.id, „bg-white“, false, `corpus-base-${item.id}`)}
{renderBlock(w, d, t, x, y + h – t, z, isActive, item.id, „bg-white“, false, `corpus-top-${item.id}`)}
{renderBlock(t, d, h – 2*t, x – w/2 + t/2, y + t, z, isActive, item.id, „bg-gray-50“, false, `corpus-side-l-${item.id}`)}
{renderBlock(t, d, h – 2*t, x + w/2 – t/2, y + t, z, isActive, item.id, „bg-gray-50“, false, `corpus-side-r-${item.id}`)}
{renderBlock(w – 2*t, t, h – 2*t, x, y + t, z – d/2 + t/2, isActive, item.id, „bg-gray-200“, false, `corpus-back-${item.id}`)}
{internalElements}
);
}
const plateH = item.type === ‚vertical‘ ? item.h : plateThickness;
return (
{/* Position Indicator Arrows for Single Items */}
{isActive && (
<>
>
)}
{renderBlock(item.w, item.d, plateH, item.x, item.y, item.z, isActive, item.id, „bg-white“, false, `plate-${item.id}`)}
);
})}
{/* 2D Blueprint */}
{/* … Blueprint Header … */}
Raster: {gridStep} cm Active
{/* Axis Indicator */}
{blueprintView === ‚front‘ ? ‚X / Y (Front)‘ : blueprintView === ‚top‘ ? ‚X / Z (Top)‘ : ‚Z / Y (Side)‘}
);
};
export default App;