@nice2dev/ui-3d 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +415 -0
  2. package/dist/cjs/material/NiceMaterialEditor.js +1 -0
  3. package/dist/cjs/model/ModelEditor.js +7 -1
  4. package/dist/cjs/model/ModelEditorLeftPanel.js +2 -2
  5. package/dist/cjs/model/ModelEditorMenuBar.js +5 -5
  6. package/dist/cjs/model/ModelEditorRightPanel.js +13 -9
  7. package/dist/cjs/model/ModelEditorSubComponents.js +10 -4
  8. package/dist/cjs/model/ModelEditorTimeline.js +4 -2
  9. package/dist/cjs/model/ModelEditorToolbar.js +1 -1
  10. package/dist/cjs/model/ModelEditorViewport.js +6 -3
  11. package/dist/cjs/model/ModelViewer.js +4 -4
  12. package/dist/cjs/model/NiceArmatureEditor.js +4 -4
  13. package/dist/cjs/model/NiceMorphTargetEditor.js +3 -1
  14. package/dist/cjs/model/NiceOctree.js +7 -4
  15. package/dist/cjs/model/NicePhysicsSimulation.js +4 -4
  16. package/dist/cjs/model/NiceProceduralGeometry.js +3 -3
  17. package/dist/cjs/model/NiceTerrainEditor.js +2 -2
  18. package/dist/cjs/model/NiceWeightPainter.js +2 -2
  19. package/dist/cjs/model/NiceXRPreview.js +6 -3
  20. package/dist/cjs/model/useModelEditor.js +131 -79
  21. package/dist/cjs/model/useModelViewer.js +19 -12
  22. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  23. package/dist/cjs/particle/NiceParticleEditor.js +12 -5
  24. package/dist/cjs/rendering/NiceCascadedShadows.js +1 -1
  25. package/dist/cjs/rendering/NiceRenderExport.js +2 -2
  26. package/dist/cjs/rendering/NiceSSAO.js +2 -1
  27. package/dist/cjs/rendering/NiceSSR.js +2 -1
  28. package/dist/cjs/rendering/NiceWebGPURenderer.js +2 -1
  29. package/dist/cjs/uv/NiceUVEditor.js +3 -3
  30. package/dist/esm/material/NiceMaterialEditor.js +1 -0
  31. package/dist/esm/model/ModelEditor.js +7 -1
  32. package/dist/esm/model/ModelEditorLeftPanel.js +3 -3
  33. package/dist/esm/model/ModelEditorMenuBar.js +6 -6
  34. package/dist/esm/model/ModelEditorRightPanel.js +13 -9
  35. package/dist/esm/model/ModelEditorSubComponents.js +11 -5
  36. package/dist/esm/model/ModelEditorTimeline.js +5 -3
  37. package/dist/esm/model/ModelEditorToolbar.js +1 -1
  38. package/dist/esm/model/ModelEditorViewport.js +7 -4
  39. package/dist/esm/model/ModelViewer.js +4 -4
  40. package/dist/esm/model/NiceArmatureEditor.js +4 -4
  41. package/dist/esm/model/NiceMorphTargetEditor.js +3 -1
  42. package/dist/esm/model/NiceOctree.js +7 -4
  43. package/dist/esm/model/NicePhysicsSimulation.js +4 -4
  44. package/dist/esm/model/NiceProceduralGeometry.js +3 -3
  45. package/dist/esm/model/NiceTerrainEditor.js +2 -2
  46. package/dist/esm/model/NiceWeightPainter.js +2 -2
  47. package/dist/esm/model/NiceXRPreview.js +6 -3
  48. package/dist/esm/model/useModelEditor.js +131 -79
  49. package/dist/esm/model/useModelViewer.js +20 -13
  50. package/dist/esm/nice2dev-ui-3d.css +1 -1
  51. package/dist/esm/particle/NiceParticleEditor.js +13 -6
  52. package/dist/esm/rendering/NiceCascadedShadows.js +1 -1
  53. package/dist/esm/rendering/NiceRenderExport.js +2 -2
  54. package/dist/esm/rendering/NiceSSAO.js +2 -1
  55. package/dist/esm/rendering/NiceSSR.js +2 -1
  56. package/dist/esm/rendering/NiceWebGPURenderer.js +2 -1
  57. package/dist/esm/uv/NiceUVEditor.js +3 -3
  58. package/package.json +3 -3
@@ -68,7 +68,7 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
68
68
  }, children: [(selectedNode.object.angle * modelEditorTypes.DEG).toFixed(0), "\u00B0"] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.penumbra', 'Penumbra') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: selectedNode.object.penumbra, onChange: (val) => {
69
69
  selectedNode.object.penumbra = val;
70
70
  setMatRefresh((n) => n + 1);
71
- }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "FOV" }), jsxRuntime.jsx(ui.NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
71
+ }, style: { flex: 1 } })] })] }))] })] })), selectedNode && selectedNode.object instanceof THREE__namespace.PerspectiveCamera && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorRightPanel.camera', 'Camera')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.fov', 'FOV') }), jsxRuntime.jsx(ui.NiceSlider, { min: 10, max: 120, step: 1, value: selectedNode.object.fov, onChange: (val) => {
72
72
  const cam = selectedNode.object;
73
73
  cam.fov = val;
74
74
  cam.updateProjectionMatrix();
@@ -83,7 +83,7 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
83
83
  cam.near = val !== null && val !== void 0 ? val : 0.1;
84
84
  cam.updateProjectionMatrix();
85
85
  setMatRefresh((n) => n + 1);
86
- }, style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Far" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 1, value: selectedNode.object.far, onChange: (val) => {
86
+ }, style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.far', 'Far') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 1, value: selectedNode.object.far, onChange: (val) => {
87
87
  const cam = selectedNode.object;
88
88
  cam.far = val !== null && val !== void 0 ? val : 1000;
89
89
  cam.updateProjectionMatrix();
@@ -92,25 +92,29 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
92
92
  fontSize: 10,
93
93
  color: 'var(--nice-text-secondary, #888)',
94
94
  margin: '0 0 6px',
95
- }, children: "Upload a video and AI will extract 3D pose data to create an animation clip for the current model." }), jsxRuntime.jsx("div", { className: ModelEditor_module.aiBtnRow, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.aiBtn, variant: "ghost", size: "sm", disabled: aiBusy || !rootObjectRef.current, onClick: () => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: aiBusy ? 'Processing...' : 'Upload Video' }) }), aiStatus && jsxRuntime.jsx("div", { className: ModelEditor_module.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "palette", size: 14 }), ' ', t('modelEditorRightPanel.materials', 'Materials')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsxRuntime.jsx(ModelEditorSubComponents.MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: selectedNode ? 'No materials on this object' : 'Select a mesh to inspect' })) })] })), propTab === 'world' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), ' ', t('modelEditorRightPanel.environment', 'Environment')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.background', 'Background') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "cloud", size: 14 }), " Fog"] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.enable', 'Enable') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: "Far" }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.sceneLights', 'Scene Lights')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.showHelpers', 'Show Helpers') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.skeleton', 'Skeleton') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }), " ", t('modelEditorRightPanel.snapping', 'Snapping')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.snap', 'Snap') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.gridSize', 'Grid Size') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.modifiers', 'Modifiers')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode && selectedNode.object instanceof THREE__namespace.Mesh ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
95
+ }, children: t('modelEditorRightPanel.uploadAVideoAndAi', 'Upload a video and AI will extract 3D pose data to create an animation clip for the current model.') }), jsxRuntime.jsx("div", { className: ModelEditor_module.aiBtnRow, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.aiBtn, variant: "ghost", size: "sm", disabled: aiBusy || !rootObjectRef.current, onClick: () => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: aiBusy
96
+ ? t('modelEditorRightPanel.processing', 'Processing...')
97
+ : t('modelEditorRightPanel.uploadVideo', 'Upload Video') }) }), aiStatus && jsxRuntime.jsx("div", { className: ModelEditor_module.aiStatus, children: aiStatus })] }) })] })] })), propTab === 'material' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "palette", size: 14 }), ' ', t('modelEditorRightPanel.materials', 'Materials')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedMaterials.length > 0 ? (selectedMaterials.map((mat, i) => (jsxRuntime.jsx(ModelEditorSubComponents.MaterialCard, { material: mat, active: i === selMaterialIdx, onClick: () => setSelMaterialIdx(i), onUpdate: () => setMatRefresh((n) => n + 1) }, i)))) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: selectedNode
98
+ ? t('modelEditorRightPanel.noMaterialsOnThisObject', 'No materials on this object')
99
+ : t('modelEditorRightPanel.selectAMeshToInspect', 'Select a mesh to inspect') })) })] })), propTab === 'world' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), ' ', t('modelEditorRightPanel.environment', 'Environment')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.background', 'Background') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #888)' }, children: bgColor })] }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "cloud", size: 14 }), " ", t('modelEditorRightPanel.fog', 'Fog')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.enable', 'Enable') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: fogEnabled, onChange: () => setFogEnabled((v) => !v) })] }), fogEnabled && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: fogColor, onChange: (c) => setFogColor(c) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.near', 'Near') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogNear, onChange: (val) => setFogNear(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.far', 'Far') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, value: fogFar, onChange: (val) => setFogFar(val !== null && val !== void 0 ? val : 100), style: { width: 60 } })] })] }))] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('modelEditorRightPanel.sceneLights', 'Scene Lights')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.showHelpers', 'Show Helpers') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.grid', 'Grid') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.axes', 'Axes') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.skeleton', 'Skeleton') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v) })] })] })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }), " ", t('modelEditorRightPanel.snapping', 'Snapping')] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.panelContent, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.snap', 'Snap') }), jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapEnabled, onChange: () => setSnapEnabled((v) => !v) })] }), snapEnabled && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorRightPanel.gridSize', 'Grid Size') }), jsxRuntime.jsx(ui.NiceNumberInput, { className: ModelEditor_module.propInput, step: 0.25, min: 0.1, value: snapGrid, onChange: (val) => setSnapGrid(val !== null && val !== void 0 ? val : 1), style: { width: 60 } })] }))] })] })] })), propTab === 'modifiers' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "settings", size: 14 }), ' ', t('modelEditorRightPanel.modifiers', 'Modifiers')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: selectedNode && selectedNode.object instanceof THREE__namespace.Mesh ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
96
100
  const mesh = selectedNode.object;
97
101
  const geo = mesh.geometry;
98
102
  const pos = geo.attributes.position;
99
103
  if (pos) {
100
104
  const wireGeo = new THREE__namespace.WireframeGeometry(geo);
101
105
  mesh.geometry = wireGeo;
102
- setStatusText('Applied Wireframe modifier');
106
+ setStatusText(t('modelEditorRightPanel.appliedWireframeModifier', 'Applied Wireframe modifier'));
103
107
  rebuildTree();
104
108
  }
105
- }, children: "+ Wireframe Modifier" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
109
+ }, children: t('modelEditorRightPanel.wireframeModifier', '+ Wireframe Modifier') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
106
110
  const mesh = selectedNode.object;
107
111
  mesh.geometry.computeVertexNormals();
108
- setStatusText('Recomputed normals');
112
+ setStatusText(t('modelEditorRightPanel.recomputedNormals', 'Recomputed normals'));
109
113
  }, children: t('modelEditorRightPanel.recomputeNormals', 'Recompute Normals') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
110
114
  const mesh = selectedNode.object;
111
115
  const geo = mesh.geometry;
112
116
  geo.center();
113
- setStatusText('Centered geometry');
117
+ setStatusText(t('modelEditorRightPanel.centeredGeometry', 'Centered geometry'));
114
118
  setMatRefresh((n) => n + 1);
115
119
  }, children: t('modelEditorRightPanel.centerGeometry', 'Center Geometry') }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', marginBottom: 4 }, onClick: () => {
116
120
  const mesh = selectedNode.object;
@@ -125,9 +129,9 @@ const ModelEditorRightPanel = /* @__PURE__ */ Object.assign(
125
129
  }
126
130
  idx.needsUpdate = true;
127
131
  geo.computeVertexNormals();
128
- setStatusText('Flipped normals');
132
+ setStatusText(t('modelEditorRightPanel.flippedNormals', 'Flipped normals'));
129
133
  }
130
- }, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics (Info)"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: [t('modelEditorRightPanel.physicsSimulationPreviewIsNot', 'Physics simulation preview is not yet available.'), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "Planned features:", jsxRuntime.jsx("br", {}), "\u2022 Rigid body simulation", jsxRuntime.jsx("br", {}), "\u2022 Soft body dynamics", jsxRuntime.jsx("br", {}), "\u2022 Cloth simulation", jsxRuntime.jsx("br", {}), "\u2022 Particle systems"] }) })] }))] }));
134
+ }, children: t('modelEditorRightPanel.flipNormals', 'Flip Normals') })] })) : (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10 }, children: t('modelEditorRightPanel.selectAMeshToApply', 'Select a mesh to apply modifiers') })) })] })), propTab === 'physics' && (jsxRuntime.jsxs("div", { className: ModelEditor_module.panelSection, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.panelTitle, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), ' ', t('modelEditorRightPanel.physicsInfo', 'Physics (Info)')] }), jsxRuntime.jsx("div", { className: ModelEditor_module.panelContent, children: jsxRuntime.jsxs("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, lineHeight: 1.5 }, children: [t('modelEditorRightPanel.physicsSimulationPreviewIsNot', 'Physics simulation preview is not yet available.'), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.plannedFeatures', 'Planned features:'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.rigidBodySimulation', '• Rigid body simulation'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.softBodyDynamics', '• Soft body dynamics'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.clothSimulation', '• Cloth simulation'), jsxRuntime.jsx("br", {}), t('modelEditorRightPanel.particleSystems', '• Particle systems')] }) })] }))] }));
131
135
  }), { displayName: 'ModelEditorRightPanel' });
132
136
 
133
137
  module.exports = ModelEditorRightPanel;
@@ -79,7 +79,7 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
79
79
  mat.needsUpdate = true;
80
80
  onUpdate();
81
81
  };
82
- return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.materialCard} ${active ? ModelEditor_module.materialCardActive : ''}`, onClick: onClick, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.materialHeader, children: jsxRuntime.jsx("span", { className: ModelEditor_module.materialName, children: mat.name || mat.type }) }), hasColor && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxRuntime.jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.metal', 'Metal') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
82
+ return (jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: `${ModelEditor_module.materialCard} ${active ? ModelEditor_module.materialCardActive : ''}`, onClick: onClick, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.materialHeader, children: jsxRuntime.jsx("span", { className: ModelEditor_module.materialName, children: mat.name || mat.type }) }), hasColor && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.color', 'Color') }), jsxRuntime.jsx(ui.NiceColorPicker, { value: `#${mat.color.getHexString()}`, onChange: (c) => setColor(c) }), jsxRuntime.jsxs("span", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)' }, children: ["#", mat.color.getHexString()] })] })), hasMetalness && (jsxRuntime.jsxs("div", { className: ModelEditor_module.propRow, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.propLabel, children: t('modelEditorSubComponents.metal', 'Metal') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 1, step: 0.01, value: mat.metalness, onChange: (val) => setNum('metalness', val), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: {
83
83
  fontSize: 9,
84
84
  color: 'var(--nice-text-secondary, #888)',
85
85
  width: 28,
@@ -108,9 +108,15 @@ const MaterialCard = ({ material, active, onClick, onUpdate }) => {
108
108
  mat.needsUpdate = true;
109
109
  onUpdate();
110
110
  }, options: [
111
- { value: String(THREE__namespace.FrontSide), label: 'Front' },
112
- { value: String(THREE__namespace.BackSide), label: 'Back' },
113
- { value: String(THREE__namespace.DoubleSide), label: 'Double' },
111
+ {
112
+ value: String(THREE__namespace.FrontSide),
113
+ label: t('modelEditorSubComponents.front', 'Front'),
114
+ },
115
+ { value: String(THREE__namespace.BackSide), label: t('modelEditorSubComponents.back', 'Back') },
116
+ {
117
+ value: String(THREE__namespace.DoubleSide),
118
+ label: t('modelEditorSubComponents.double', 'Double'),
119
+ },
114
120
  ] })] }), hasMap && mat.map && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.map.image != null && (jsxRuntime.jsx("img", { src: (_a = mat.map.image.src) !== null && _a !== void 0 ? _a : '', alt: "diffuse" })) }), jsxRuntime.jsxs("div", { className: ModelEditor_module.textureInfo, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.diffuseMap', 'Diffuse Map') }), jsxRuntime.jsxs("div", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #666)' }, children: [(_b = mat.map.image) === null || _b === void 0 ? void 0 : _b.width, "\u00D7", (_c = mat.map.image) === null || _c === void 0 ? void 0 : _c.height] })] })] })), hasNormalMap && mat.normalMap && (jsxRuntime.jsxs("div", { className: ModelEditor_module.textureSlot, children: [jsxRuntime.jsx("div", { className: ModelEditor_module.textureThumb, children: mat.normalMap.image != null && (jsxRuntime.jsx("img", { src: (_d = mat.normalMap.image.src) !== null && _d !== void 0 ? _d : '', alt: "normal" })) }), jsxRuntime.jsx("div", { className: ModelEditor_module.textureInfo, children: jsxRuntime.jsx("div", { className: ModelEditor_module.textureName, children: t('modelEditorSubComponents.normalMap', 'Normal Map') }) })] }))] }));
115
121
  };
116
122
 
@@ -11,14 +11,16 @@ const ModelEditorTimeline = /* @__PURE__ */ Object.assign(
11
11
  /* @__PURE__ */ React.memo(({ api }) => {
12
12
  const { t } = i18n.useNiceTranslation();
13
13
  const { bottomCollapsed, setBottomCollapsed, animations, activeAnimIdx, isPlaying, animTime, animDuration, animSpeed, setAnimSpeed, loopAnim, setLoopAnim, seekAnim, togglePlay, stopAnim, playClip, mergeInputRef, } = api;
14
- return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.jsxs("span", { className: ModelEditor_module.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: ["Animation (", animations.length, " clip", animations.length !== 1 ? 's' : '', ")"] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.transportBar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-left", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), "aria-label": t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) })] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.transportTime, children: [modelEditorTypes.fmtTime(animTime), " / ", modelEditorTypes.fmtTime(animDuration)] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxRuntime.jsxs("div", { className: ModelEditor_module.animListArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.animList, children: [animations.length === 0 && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.animItem} ${i === activeAnimIdx ? ModelEditor_module.animItemActive : ''}`, onClick: () => playClip(i), children: [jsxRuntime.jsx("span", { children: anim.name }), jsxRuntime.jsxs("span", { className: ModelEditor_module.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsxRuntime.jsx("div", { style: { padding: '4px 8px' }, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Add Animation File" }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineRuler, children: [animDuration > 0 &&
14
+ return (jsxRuntime.jsxs("div", { className: `${ModelEditor_module.bottomPanel} ${bottomCollapsed ? ModelEditor_module.bottomPanelCollapsed : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.bottomPanelHeader, children: [jsxRuntime.jsx("span", { role: "presentation", className: ModelEditor_module.bottomToggle, onClick: () => setBottomCollapsed((c) => !c), children: jsxRuntime.jsx(ui.NiceIcon, { name: bottomCollapsed ? 'chevron-right' : 'chevron-down', size: 14 }) }), jsxRuntime.jsx("span", { role: "button", tabIndex: 0, "aria-expanded": !bottomCollapsed, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.bottomPanelTitle, onClick: () => setBottomCollapsed((c) => !c), children: animations.length !== 1
15
+ ? t('modelEditorTimeline.animationClipsMany', 'Animation ({animations} clips)').replace('{animations}', String(animations.length))
16
+ : t('modelEditorTimeline.animationClipsOne', 'Animation ({animations} clip)').replace('{animations}', String(animations.length)) }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.transportBar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: () => seekAnim(0), title: t('modelEditorTimeline.goToStart', 'Go to start'), "aria-label": t('modelEditorTimeline.goToStart', 'Go to start'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "chevron-left", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: togglePlay, title: t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), "aria-label": t('modelEditorTimeline.playPauseSpace', 'Play/Pause (Space)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: "ghost", size: "sm", onClick: stopAnim, title: t('modelEditorTimeline.stop', 'Stop'), "aria-label": t('modelEditorTimeline.stop', 'Stop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.transportBtn, variant: loopAnim ? 'primary' : 'ghost', size: "sm", onClick: () => setLoopAnim((v) => !v), title: t('modelEditorTimeline.loop', 'Loop'), "aria-label": t('modelEditorTimeline.loop', 'Loop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) })] }), jsxRuntime.jsxs("span", { className: ModelEditor_module.transportTime, children: [modelEditorTypes.fmtTime(animTime), " / ", modelEditorTypes.fmtTime(animDuration)] }), jsxRuntime.jsx("div", { className: ModelEditor_module.menuSep }), jsxRuntime.jsx("label", { className: ModelEditor_module.menuLabel, children: t('modelEditorTimeline.speed', 'Speed') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0.1, max: 3, step: 0.1, value: animSpeed, onChange: (val) => setAnimSpeed(val), style: { width: 60 } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.menuLabel, children: [animSpeed.toFixed(1), "\u00D7"] })] }), !bottomCollapsed && (jsxRuntime.jsxs("div", { className: ModelEditor_module.animListArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.animList, children: [animations.length === 0 && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-text-secondary, #666)', fontSize: 10, padding: 8 }, children: t('modelEditorTimeline.noAnimationsLoaded', 'No animations loaded') })), animations.map((anim, i) => (jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: `${ModelEditor_module.animItem} ${i === activeAnimIdx ? ModelEditor_module.animItemActive : ''}`, onClick: () => playClip(i), children: [jsxRuntime.jsx("span", { children: anim.name }), jsxRuntime.jsxs("span", { className: ModelEditor_module.animDuration, children: [anim.duration.toFixed(1), "s"] })] }, `${anim.name}-${i}`))), jsxRuntime.jsx("div", { style: { padding: '4px 8px' }, children: jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.btnSecondary, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: t('modelEditorTimeline.addAnimationFile', '+ Add Animation File') }) })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineArea, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.timelineRuler, children: [animDuration > 0 &&
15
17
  Array.from({ length: Math.ceil(animDuration) + 1 }, (_, i) => {
16
18
  const pct = (i / animDuration) * 100;
17
19
  return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("div", { className: ModelEditor_module.rulerTick, style: { left: `${pct}%` } }), jsxRuntime.jsxs("span", { className: ModelEditor_module.rulerLabel, style: { left: `${pct}%` }, children: [i, "s"] })] }, i));
18
20
  }), animDuration > 0 && (jsxRuntime.jsx("div", { className: ModelEditor_module.playhead, style: {
19
21
  left: `${(animTime / animDuration) * 100}%`,
20
22
  height: '100%',
21
- }, children: jsxRuntime.jsx("div", { className: ModelEditor_module.playheadHandle }) }))] }), jsxRuntime.jsx("div", { className: ModelEditor_module.timelineTracks, style: { height: 140, cursor: 'pointer' }, onClick: (e) => {
23
+ }, children: jsxRuntime.jsx("div", { className: ModelEditor_module.playheadHandle }) }))] }), jsxRuntime.jsx("div", { role: "presentation", className: ModelEditor_module.timelineTracks, style: { height: 140, cursor: 'pointer' }, onClick: (e) => {
22
24
  if (animDuration <= 0) {
23
25
  return;
24
26
  }
@@ -10,7 +10,7 @@ const ModelEditorToolbar = /* @__PURE__ */ Object.assign(
10
10
  /* @__PURE__ */ React.memo(({ api }) => {
11
11
  const { t } = i18n.useNiceTranslation();
12
12
  const { transformMode, setTransformMode, snapEnabled, setSnapEnabled, gizmoSpace, setGizmoSpace, rootObjectRef, focusOnObject, setCameraPreset, deleteSelected, duplicateSelected, selectedNode, } = api;
13
- return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: t('modelEditorToolbar.translateG', 'Translate (G)'), "aria-label": t('modelEditorToolbar.translateG', 'Translate (G)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: t('modelEditorToolbar.rotateR', 'Rotate (R)'), "aria-label": t('modelEditorToolbar.rotateR', 'Rotate (R)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: t('modelEditorToolbar.scaleS', 'Scale (S)'), "aria-label": t('modelEditorToolbar.scaleS', 'Scale (S)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: `Snap to Grid (${snapEnabled ? 'ON' : 'OFF'})`, "aria-label": t('modelEditorToolbar.snapToGrid', 'Snap to Grid'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: jsxRuntime.jsx(ui.NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: t('modelEditorToolbar.focusF', 'Focus (F)'), "aria-label": t('modelEditorToolbar.focusF', 'Focus (F)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: t('modelEditorToolbar.frontViewNumpad1', 'Front view (Numpad 1)'), children: "1" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: t('modelEditorToolbar.rightViewNumpad3', 'Right view (Numpad 3)'), children: "3" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: t('modelEditorToolbar.topViewNumpad7', 'Top view (Numpad 7)'), children: "7" }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), "aria-label": t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), disabled: !selectedNode, "aria-label": t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) })] }));
13
+ return (jsxRuntime.jsxs("div", { className: ModelEditor_module.toolbar, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'translate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('translate'), title: t('modelEditorToolbar.translateG', 'Translate (G)'), "aria-label": t('modelEditorToolbar.translateG', 'Translate (G)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "move", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'rotate' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('rotate'), title: t('modelEditorToolbar.rotateR', 'Rotate (R)'), "aria-label": t('modelEditorToolbar.rotateR', 'Rotate (R)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: transformMode === 'scale' ? 'primary' : 'ghost', size: "sm", onClick: () => setTransformMode('scale'), title: t('modelEditorToolbar.scaleS', 'Scale (S)'), "aria-label": t('modelEditorToolbar.scaleS', 'Scale (S)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "hexagon", size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: snapEnabled ? 'primary' : 'ghost', size: "sm", onClick: () => setSnapEnabled((v) => !v), title: t('modelEditorToolbar.snapToGridState', 'Snap to Grid ({state})').replace('{state}', snapEnabled ? t('modelEditorToolbar.on', 'ON') : t('modelEditorToolbar.off', 'OFF')), "aria-label": t('modelEditorToolbar.snapToGrid', 'Snap to Grid'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setGizmoSpace((s) => (s === 'local' ? 'world' : 'local')), title: t('modelEditorToolbar.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), "aria-label": t('modelEditorToolbar.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), children: jsxRuntime.jsx(ui.NiceIcon, { name: gizmoSpace === 'local' ? 'target' : 'globe', size: 16 }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => rootObjectRef.current && focusOnObject(rootObjectRef.current), title: t('modelEditorToolbar.focusF', 'Focus (F)'), "aria-label": t('modelEditorToolbar.focusF', 'Focus (F)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('front'), title: t('modelEditorToolbar.frontViewNumpad1', 'Front view (Numpad 1)'), children: "1" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('right'), title: t('modelEditorToolbar.rightViewNumpad3', 'Right view (Numpad 3)'), children: "3" }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: () => setCameraPreset('top'), title: t('modelEditorToolbar.topViewNumpad7', 'Top view (Numpad 7)'), children: "7" }), jsxRuntime.jsx("div", { className: ModelEditor_module.toolSep }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: deleteSelected, title: t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), "aria-label": t('modelEditorToolbar.deleteSelectedDel', 'Delete selected (Del)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelEditor_module.toolBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, title: t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), disabled: !selectedNode, "aria-label": t('modelEditorToolbar.duplicateShiftD', 'Duplicate (Shift+D)'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "clipboard", size: 16 }) })] }));
14
14
  }), { displayName: 'ModelEditorToolbar' });
15
15
 
16
16
  module.exports = ModelEditorToolbar;
@@ -18,7 +18,10 @@ const ModelEditorViewport = /* @__PURE__ */ Object.assign(
18
18
  /* @__PURE__ */ React.memo(({ api }) => {
19
19
  const { t } = i18n.useNiceTranslation();
20
20
  const { mountRef, dragOver, handleDragOver, handleDragLeave, handleDrop, handleViewportClick, handleContextMenu, editorMode, polyCount, meshCount, boneCount, shadingMode, setShadingMode, gizmoSpace, setGizmoSpace, contextMenu, closeContextMenu, addPrimitive, addSceneLight, duplicateSelected, deleteSelected, selectedNode, rootObjectRef, focusOnObject, snapEnabled, setSnapEnabled, } = api;
21
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t(...MODE_BADGE[editorMode]) }), jsxRuntime.jsxs("span", { className: ModelEditor_module.viewportBadge, children: [polyCount.toLocaleString(), " tris \u00B7 ", meshCount, " meshes \u00B7 ", boneCount, " bones"] })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportShadingBar, children: ['wireframe', 'solid', 'material', 'rendered'].map((m) => (jsxRuntime.jsx("button", { className: `${ModelEditor_module.shadingBtn} ${shadingMode === m ? ModelEditor_module.shadingBtnActive : ''}`, onClick: (e) => {
21
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { role: "presentation", ref: mountRef, className: `${ModelEditor_module.viewportContainer} ${dragOver ? ModelEditor_module.dragOver : ''}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleViewportClick, onContextMenu: handleContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.viewportOverlay, children: [jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t(...MODE_BADGE[editorMode]) }), jsxRuntime.jsx("span", { className: ModelEditor_module.viewportBadge, children: t('modelEditorViewport.trisMeshesBones', '{polyCount} tris · {meshCount} meshes · {boneCount} bones')
22
+ .replace('{polyCount}', String(polyCount.toLocaleString()))
23
+ .replace('{meshCount}', String(meshCount))
24
+ .replace('{boneCount}', String(boneCount)) })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportShadingBar, children: ['wireframe', 'solid', 'material', 'rendered'].map((m) => (jsxRuntime.jsx("button", { className: `${ModelEditor_module.shadingBtn} ${shadingMode === m ? ModelEditor_module.shadingBtnActive : ''}`, onClick: (e) => {
22
25
  e.stopPropagation();
23
26
  setShadingMode(m);
24
27
  }, title: m.charAt(0).toUpperCase() + m.slice(1), "aria-label": m.charAt(0).toUpperCase() + m.slice(1), children: jsxRuntime.jsx(ui.NiceIcon, { name: m === 'wireframe'
@@ -30,11 +33,11 @@ const ModelEditorViewport = /* @__PURE__ */ Object.assign(
30
33
  : 'lightbulb', size: 16 }) }, m))) }), jsxRuntime.jsx("div", { className: ModelEditor_module.viewportGizmoInfo, children: jsxRuntime.jsx("button", { className: ModelEditor_module.shadingBtn, onClick: (e) => {
31
34
  e.stopPropagation();
32
35
  setGizmoSpace((s) => (s === 'local' ? 'world' : 'local'));
33
- }, title: `Orientation: ${gizmoSpace}`, "aria-label": `Orientation: ${gizmoSpace}`, children: gizmoSpace === 'local' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", t('modelEditorViewport.local', 'Local')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " ", t('modelEditorViewport.world', 'World')] })) }) }), dragOver && (jsxRuntime.jsx("div", { className: ModelEditor_module.viewportDropZone, children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.drop3dFilesHere', 'Drop 3D files here') }) }))] }), contextMenu && (jsxRuntime.jsxs("div", { className: ModelEditor_module.contextMenu, style: { top: contextMenu.y, left: contextMenu.x }, onClick: closeContextMenu, children: [jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('cube'), children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.addCube', 'Add Cube') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+A" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addPrimitive('sphere'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addSphere', 'Add Sphere') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => addSceneLight('point'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addPointLight', 'Add Point Light') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: duplicateSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.duplicate', 'Duplicate') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+D" })] }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: deleteSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.delete', 'Delete') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Del" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { className: ModelEditor_module.contextItem, onClick: () => {
36
+ }, title: t('modelEditorViewport.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), "aria-label": t('modelEditorViewport.orientation', 'Orientation: {gizmoSpace}').replace('{gizmoSpace}', String(gizmoSpace)), children: gizmoSpace === 'local' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", t('modelEditorViewport.local', 'Local')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "globe", size: 14 }), " ", t('modelEditorViewport.world', 'World')] })) }) }), dragOver && (jsxRuntime.jsx("div", { className: ModelEditor_module.viewportDropZone, children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.drop3dFilesHere', 'Drop 3D files here') }) }))] }), contextMenu && (jsxRuntime.jsxs("div", { role: "presentation", className: ModelEditor_module.contextMenu, style: { top: contextMenu.y, left: contextMenu.x }, onClick: closeContextMenu, children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: () => addPrimitive('cube'), children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.addCube', 'Add Cube') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+A" })] }), jsxRuntime.jsx("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: () => addPrimitive('sphere'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addSphere', 'Add Sphere') }) }), jsxRuntime.jsx("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: () => addSceneLight('point'), children: jsxRuntime.jsx("span", { children: t('modelEditorViewport.addPointLight', 'Add Point Light') }) }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, "aria-disabled": !selectedNode, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: duplicateSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.duplicate', 'Duplicate') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Shift+D" })] }), jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, "aria-disabled": !selectedNode, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: deleteSelected, style: !selectedNode ? { opacity: 0.4, pointerEvents: 'none' } : {}, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.delete', 'Delete') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "Del" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextSep }), jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: () => {
34
37
  if (rootObjectRef.current) {
35
38
  focusOnObject(rootObjectRef.current);
36
39
  }
37
- }, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.focusCamera', 'Focus Camera') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "F" })] }), jsxRuntime.jsx("div", { className: ModelEditor_module.contextItem, onClick: () => setSnapEnabled((v) => !v), children: jsxRuntime.jsxs("span", { children: [snapEnabled && jsxRuntime.jsx(ui.NiceIcon, { name: "check", size: 14 }), ' ', t('modelEditorViewport.snapToGrid', 'Snap to Grid')] }) })] }))] }));
40
+ }, children: [jsxRuntime.jsx("span", { children: t('modelEditorViewport.focusCamera', 'Focus Camera') }), jsxRuntime.jsx("span", { className: ModelEditor_module.contextKey, children: "F" })] }), jsxRuntime.jsx("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, className: ModelEditor_module.contextItem, onClick: () => setSnapEnabled((v) => !v), children: jsxRuntime.jsxs("span", { children: [snapEnabled && jsxRuntime.jsx(ui.NiceIcon, { name: "check", size: 14 }), ' ', t('modelEditorViewport.snapToGrid', 'Snap to Grid')] }) })] }))] }));
38
41
  }), { displayName: 'ModelEditorViewport' });
39
42
 
40
43
  module.exports = ModelEditorViewport;
@@ -7,6 +7,7 @@ var i18n = require('../core/i18n.js');
7
7
  var ModelViewer_module = require('./ModelViewer.module.css.js');
8
8
  var useModelViewer = require('./useModelViewer.js');
9
9
 
10
+ const NAV_MODES = ['orbit', 'fps', 'fly', 'walkthrough'];
10
11
  const NAV_META = {
11
12
  orbit: { icon: 'globe', label: 'Orbit' },
12
13
  fps: { icon: 'user', label: 'FPS' },
@@ -40,10 +41,9 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
40
41
  }
41
42
  }, [api, onFileDrop]);
42
43
  /* ── nav cycle ── */
43
- const navModes = ['orbit', 'fps', 'fly', 'walkthrough'];
44
44
  const cycleNav = React.useCallback(() => {
45
- const idx = navModes.indexOf(api.navigationMode);
46
- api.setNavigationMode(navModes[(idx + 1) % navModes.length]);
45
+ const idx = NAV_MODES.indexOf(api.navigationMode);
46
+ api.setNavigationMode(NAV_MODES[(idx + 1) % NAV_MODES.length]);
47
47
  }, [api]);
48
48
  return (jsxRuntime.jsxs("div", { className: `${ModelViewer_module.root} ${className !== null && className !== void 0 ? className : ''}`, children: [showToolbar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.toolbar, children: [jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", title: t('modelViewer.openFile', 'Open file…'), onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "folder", size: 14 }), " ", t('modelViewer.open', 'Open')] }), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: ".fbx,.glb,.gltf,.obj,.dae,.stl,.ply", style: { display: 'none' }, onChange: (e) => {
49
49
  var _a;
@@ -62,7 +62,7 @@ const NiceModelViewer = ({ className, showToolbar = true, showStatusBar = true,
62
62
  a.href = url;
63
63
  a.download = 'screenshot.png';
64
64
  a.click();
65
- }, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: "Loading model\u2026" }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && (jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: t('modelViewer.drop3dModelFileHere', 'Drop 3D model file here') }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.modelInfo', 'Model Info') }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.triangles', 'Triangles') }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.meshes', 'Meshes') }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.width', 'Width') }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.height', 'Height') }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.depth', 'Depth') }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.navigation', 'Navigation') }), navModes.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.autoRotate', 'Auto Rotate') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.animations', 'Animations') }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": t('modelViewer.playPause', 'Play/Pause'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
65
+ }, title: t('modelViewer.screenshot', 'Screenshot'), "aria-label": t('modelViewer.screenshot', 'Screenshot'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "camera", size: 16 }) }), jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.toggleFullscreen, title: t('modelViewer.fullscreen', 'Fullscreen'), "aria-label": t('modelViewer.fullscreen', 'Fullscreen'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isFullscreen ? 'collapse' : 'expand', size: 16 }) }), toolbarExtra, showInfoPanel && (jsxRuntime.jsx("div", { className: ModelViewer_module.toolRight, children: jsxRuntime.jsx(ui.NiceButton, { className: `${ModelViewer_module.toolBtn} ${panelOpen ? ModelViewer_module.active : ''}`, variant: panelOpen ? 'primary' : 'ghost', size: "sm", onClick: () => setPanelOpen(!panelOpen), title: t('modelViewer.infoPanel', 'Info panel'), "aria-label": t('modelViewer.infoPanel', 'Info panel'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "info", size: 16 }) }) }))] })), jsxRuntime.jsxs("div", { className: ModelViewer_module.viewport, ref: api.mountRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [api.loading && (jsxRuntime.jsxs("div", { className: ModelViewer_module.loadingOverlay, children: [jsxRuntime.jsx("span", { className: ModelViewer_module.loadingText, children: t('modelViewer.loadingModel', 'Loading model…') }), jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBar, children: jsxRuntime.jsx("div", { className: ModelViewer_module.loadingBarFill, style: { width: `${(api.loadProgress * 100).toFixed(0)}%` } }) })] })), api.error && (jsxRuntime.jsxs("div", { className: ModelViewer_module.errorOverlay, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "warning", size: 28 }), jsxRuntime.jsx("span", { style: { marginTop: 8 }, children: api.error })] })), dragOver && (jsxRuntime.jsx("div", { className: ModelViewer_module.dropZone, children: t('modelViewer.drop3dModelFileHere', 'Drop 3D model file here') }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.sidePanel} ${!panelOpen ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.modelInfo', 'Model Info') }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.triangles', 'Triangles') }), jsxRuntime.jsx("span", { children: api.polyCount.toLocaleString() })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.meshes', 'Meshes') }), jsxRuntime.jsx("span", { children: api.meshCount })] }), api.dimensions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.width', 'Width') }), jsxRuntime.jsx("span", { children: api.dimensions.width })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.height', 'Height') }), jsxRuntime.jsx("span", { children: api.dimensions.height })] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelRow, children: [jsxRuntime.jsx("label", { children: t('modelViewer.depth', 'Depth') }), jsxRuntime.jsx("span", { children: api.dimensions.depth })] })] }))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.navigation', 'Navigation') }), NAV_MODES.map((m) => (jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.navigationMode === m ? 'primary' : 'ghost', size: "sm", onClick: () => api.setNavigationMode(m), children: jsxRuntime.jsx(NavLabel, { mode: m }) }, m)))] }), jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.autoRotate', 'Auto Rotate') }), jsxRuntime.jsx(ui.NiceSlider, { min: 0, max: 10, step: 0.5, value: api.autoRotateSpeed, onChange: (val) => api.setAutoRotateSpeed(val), style: { width: '100%' } })] }), api.animations.length > 0 && (jsxRuntime.jsxs("div", { className: ModelViewer_module.panelSection, children: [jsxRuntime.jsx("div", { className: ModelViewer_module.panelTitle, children: t('modelViewer.animations', 'Animations') }), api.animations.map((name, i) => (jsxRuntime.jsxs(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: api.activeAnimIdx === i ? 'primary' : 'ghost', size: "sm", onClick: () => api.playAnimation(i), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", name] }, i)))] }))] }), jsxRuntime.jsxs("div", { className: `${ModelViewer_module.animBar} ${api.animations.length === 0 ? ModelViewer_module.hidden : ''}`, children: [jsxRuntime.jsx(ui.NiceButton, { className: ModelViewer_module.toolBtn, variant: "ghost", size: "sm", onClick: api.togglePlay, "aria-label": t('modelViewer.playPause', 'Play/Pause'), children: jsxRuntime.jsx(ui.NiceIcon, { name: api.isPlaying ? 'pause' : 'play', size: 16 }) }), jsxRuntime.jsx(ui.NiceSelect, { className: ModelViewer_module.animSelect, value: String(api.activeAnimIdx), onChange: (val) => api.playAnimation(parseInt(val)), options: api.animations.map((n, i) => ({ value: String(i), label: n })) }), jsxRuntime.jsx(ui.NiceSlider, { className: ModelViewer_module.animSlider, min: 0, max: api.animDuration || 1, step: 0.01, value: api.animTime, onChange: (val) => api.seekAnim(val) }), jsxRuntime.jsxs("span", { className: ModelViewer_module.toolLabel, children: [api.animTime.toFixed(1), "s / ", api.animDuration.toFixed(1), "s"] })] }), showStatusBar && (jsxRuntime.jsxs("div", { className: ModelViewer_module.statusBar, children: [jsxRuntime.jsx("span", { children: api.statusText }), jsxRuntime.jsxs("div", { className: ModelViewer_module.statusRight, children: [jsxRuntime.jsx("span", { children: jsxRuntime.jsx(NavLabel, { mode: api.navigationMode }) }), api.dimensions && (jsxRuntime.jsxs("span", { children: [api.dimensions.width, "\u00D7", api.dimensions.height, "\u00D7", api.dimensions.depth] }))] })] })), children === null || children === void 0 ? void 0 : children(api)] }));
66
66
  };
67
67
 
68
68
  exports.NiceModelViewer = NiceModelViewer;
@@ -265,22 +265,22 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
265
265
  } }), jsxRuntime.jsx("button", { type: "button", disabled: !newBoneName.trim(), onClick: () => {
266
266
  addBone(selectedBoneId, newBoneName.trim());
267
267
  setNewBoneName('');
268
- }, children: "+ Add" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": t('armatureEditor.removeBone', 'Remove bone'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
268
+ }, children: t('armatureEditor.add', '+ Add') }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": t('armatureEditor.removeBone', 'Remove bone'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
269
269
  display: 'grid',
270
270
  gap: 4,
271
271
  fontSize: 12,
272
272
  border: '1px solid var(--nice-border, #e2e8f0)',
273
273
  borderRadius: 6,
274
274
  padding: 8,
275
- }, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.jsxs("label", { children: ["Rotation X: ", (selectedBone.localRotation.x * (180 / Math.PI)).toFixed(1), "\u00B0"] }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.x * (180 / Math.PI), onChange: (e) => {
275
+ }, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationX', 'Rotation X: {x}°').replace('{x}', String((selectedBone.localRotation.x * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.x * (180 / Math.PI), onChange: (e) => {
276
276
  const rot = selectedBone.localRotation.clone();
277
277
  rot.x = parseFloat(e.target.value) * (Math.PI / 180);
278
278
  setBoneRotation(selectedBone.id, rot);
279
- } }), jsxRuntime.jsxs("label", { children: ["Rotation Y: ", (selectedBone.localRotation.y * (180 / Math.PI)).toFixed(1), "\u00B0"] }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.y * (180 / Math.PI), onChange: (e) => {
279
+ } }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationY', 'Rotation Y: {y}°').replace('{y}', String((selectedBone.localRotation.y * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.y * (180 / Math.PI), onChange: (e) => {
280
280
  const rot = selectedBone.localRotation.clone();
281
281
  rot.y = parseFloat(e.target.value) * (Math.PI / 180);
282
282
  setBoneRotation(selectedBone.id, rot);
283
- } }), jsxRuntime.jsxs("label", { children: ["Rotation Z: ", (selectedBone.localRotation.z * (180 / Math.PI)).toFixed(1), "\u00B0"] }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.z * (180 / Math.PI), onChange: (e) => {
283
+ } }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationZ', 'Rotation Z: {z}°').replace('{z}', String((selectedBone.localRotation.z * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.z * (180 / Math.PI), onChange: (e) => {
284
284
  const rot = selectedBone.localRotation.clone();
285
285
  rot.z = parseFloat(e.target.value) * (Math.PI / 180);
286
286
  setBoneRotation(selectedBone.id, rot);
@@ -233,7 +233,9 @@ const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutori
233
233
  padding: '4px 8px',
234
234
  border: '1px solid var(--nice-border, #e2e8f0)',
235
235
  borderRadius: 4,
236
- } }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: "+ Add" })] }), animations.length > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('morphTargetEditor.animations', 'Animations') }), animations.map((anim) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }), jsxRuntime.jsx("button", { type: "button", onClick: () => (isAnimating ? stopAnimation() : startAnimation(anim)), style: { fontSize: 11 }, "aria-label": isAnimating ? 'Stop' : 'Play', children: jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeAnimation(anim.name), style: { fontSize: 11 }, "aria-label": t('morphTargetEditor.removeAnimation', 'Remove animation'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, anim.name)))] }))] }));
236
+ } }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: t('morphTargetEditor.add', '+ Add') })] }), animations.length > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('morphTargetEditor.animations', 'Animations') }), animations.map((anim) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }), jsxRuntime.jsx("button", { type: "button", onClick: () => (isAnimating ? stopAnimation() : startAnimation(anim)), style: { fontSize: 11 }, "aria-label": isAnimating
237
+ ? t('morphTargetEditor.stop', 'Stop')
238
+ : t('morphTargetEditor.play', 'Play'), children: jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeAnimation(anim.name), style: { fontSize: 11 }, "aria-label": t('morphTargetEditor.removeAnimation', 'Remove animation'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, anim.name)))] }))] }));
237
239
  };
238
240
 
239
241
  exports.NiceMorphTargetEditor = NiceMorphTargetEditor;
@@ -348,19 +348,22 @@ function useOctree(scene, configOverride) {
348
348
  const NiceOctree = ({ scene, config, showDebug: initialShowDebug, className = '', }) => {
349
349
  const { t } = i18n.useNiceTranslation();
350
350
  const octree = useOctree(scene, config);
351
+ const { setShowDebug } = octree;
351
352
  React.useEffect(() => {
352
353
  if (initialShowDebug !== undefined) {
353
- octree.setShowDebug(initialShowDebug);
354
+ setShowDebug(initialShowDebug);
354
355
  }
355
- }, [initialShowDebug]);
356
- return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }), " Spatial Index (Octree)"] }), jsxRuntime.jsxs("button", { type: "button", onClick: octree.build, style: {
356
+ }, [initialShowDebug, setShowDebug]);
357
+ return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }), ' ', t('octree.spatialIndexOctree', 'Spatial Index (Octree)')] }), jsxRuntime.jsxs("button", { type: "button", onClick: octree.build, style: {
357
358
  background: 'var(--nice-color-accent, #6366f1)',
358
359
  color: 'var(--nice-text-on-primary, #fff)',
359
360
  border: 'none',
360
361
  borderRadius: 4,
361
362
  padding: '6px 12px',
362
363
  cursor: 'pointer',
363
- }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Nodes: ", octree.nodeCount, " | Objects: ", octree.objectCount] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: octree.showDebug, onChange: (e) => octree.setShowDebug(e.target.checked) }), t('octree.showDebugWireframes', 'Show debug wireframes')] })] }));
364
+ }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('octree.nodesObjects', 'Nodes: {nodeCount} · Objects: {objectCount}')
365
+ .replace('{nodeCount}', String(octree.nodeCount))
366
+ .replace('{objectCount}', String(octree.objectCount)) }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: octree.showDebug, onChange: (e) => octree.setShowDebug(e.target.checked) }), t('octree.showDebugWireframes', 'Show debug wireframes')] })] }));
364
367
  };
365
368
 
366
369
  exports.NiceOctree = NiceOctree;
@@ -296,20 +296,20 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
296
296
  borderRadius: 4,
297
297
  padding: '5px 8px',
298
298
  cursor: 'pointer',
299
- }, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " ", t('physicsSimulation.pause', 'Pause')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('physicsSimulation.play', 'Play')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " ", t('physicsSimulation.step', 'Step')] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('physicsSimulation.reset', 'Reset')] })] }), jsxRuntime.jsxs("label", { children: ["Gravity Y: ", worldConfig.gravity.y.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
299
+ }, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " ", t('physicsSimulation.pause', 'Pause')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('physicsSimulation.play', 'Play')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " ", t('physicsSimulation.step', 'Step')] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('physicsSimulation.reset', 'Reset')] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.gravityY', 'Gravity Y: {y}').replace('{y}', String(worldConfig.gravity.y.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
300
300
  border: '1px solid var(--nice-border, #e2e8f0)',
301
301
  borderRadius: 6,
302
302
  padding: 6,
303
303
  display: 'grid',
304
304
  gap: 4,
305
- }, children: [jsxRuntime.jsxs("legend", { style: { fontSize: 11 }, children: [selectedObject.name || 'Object', " \u2014 Physics"] }), !selectedBody ? (jsxRuntime.jsx("button", { type: "button", onClick: () => addBody(selectedObject), style: {
305
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('physicsSimulation.objectPhysics', '{name} — Physics').replace('{name}', selectedObject.name || t('physicsSimulation.object', 'Object')) }), !selectedBody ? (jsxRuntime.jsx("button", { type: "button", onClick: () => addBody(selectedObject), style: {
306
306
  background: 'var(--nice-color-accent, #6366f1)',
307
307
  color: 'var(--nice-text-on-primary, #fff)',
308
308
  border: 'none',
309
309
  borderRadius: 4,
310
310
  padding: '5px 8px',
311
311
  cursor: 'pointer',
312
- }, children: "+ Add Rigid Body" })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: ["Type:", jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: t('physicsSimulation.dynamic', 'Dynamic') }), jsxRuntime.jsx("option", { value: "static", children: t('physicsSimulation.static', 'Static') }), jsxRuntime.jsx("option", { value: "kinematic", children: t('physicsSimulation.kinematic', 'Kinematic') })] })] }), jsxRuntime.jsxs("label", { children: ["Shape:", jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: "Box" }), jsxRuntime.jsx("option", { value: "sphere", children: t('physicsSimulation.sphere', 'Sphere') }), jsxRuntime.jsx("option", { value: "capsule", children: t('physicsSimulation.capsule', 'Capsule') }), jsxRuntime.jsx("option", { value: "convexHull", children: t('physicsSimulation.convexHull', 'Convex Hull') }), jsxRuntime.jsx("option", { value: "trimesh", children: t('physicsSimulation.trimesh', 'Trimesh') })] })] }), jsxRuntime.jsxs("label", { children: ["Mass: ", selectedBody.mass.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Restitution: ", selectedBody.restitution.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Friction: ", selectedBody.friction.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), "Sensor (trigger only)"] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
312
+ }, children: t('physicsSimulation.addRigidBody', '+ Add Rigid Body') })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: [t('physicsSimulation.type', 'Type:'), jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: t('physicsSimulation.dynamic', 'Dynamic') }), jsxRuntime.jsx("option", { value: "static", children: t('physicsSimulation.static', 'Static') }), jsxRuntime.jsx("option", { value: "kinematic", children: t('physicsSimulation.kinematic', 'Kinematic') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.shape', 'Shape:'), jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: t('physicsSimulation.box', 'Box') }), jsxRuntime.jsx("option", { value: "sphere", children: t('physicsSimulation.sphere', 'Sphere') }), jsxRuntime.jsx("option", { value: "capsule", children: t('physicsSimulation.capsule', 'Capsule') }), jsxRuntime.jsx("option", { value: "convexHull", children: t('physicsSimulation.convexHull', 'Convex Hull') }), jsxRuntime.jsx("option", { value: "trimesh", children: t('physicsSimulation.trimesh', 'Trimesh') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.mass', 'Mass: {mass}').replace('{mass}', String(selectedBody.mass.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.restitution', 'Restitution: {restitution}').replace('{restitution}', String(selectedBody.restitution.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.friction', 'Friction: {friction}').replace('{friction}', String(selectedBody.friction.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), t('physicsSimulation.sensorTriggerOnly', 'Sensor (trigger only)')] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
313
313
  background: 'var(--nice-danger, #ef4444)',
314
314
  color: 'var(--nice-text-on-primary, #fff)',
315
315
  border: 'none',
@@ -317,7 +317,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
317
317
  padding: '4px 8px',
318
318
  cursor: 'pointer',
319
319
  fontSize: 11,
320
- }, children: t('physicsSimulation.removeRigidBody', 'Remove Rigid Body') })] }))] })), jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: ["Total bodies: ", bodies.length] })] }));
320
+ }, children: t('physicsSimulation.removeRigidBody', 'Remove Rigid Body') })] }))] })), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('physicsSimulation.totalBodies', 'Total bodies: {bodies}').replace('{bodies}', String(bodies.length)) })] }));
321
321
  };
322
322
 
323
323
  exports.NicePhysicsSimulation = NicePhysicsSimulation;
@@ -316,13 +316,13 @@ function useProceduralGeometry(scene, onGenerated) {
316
316
  const NiceProceduralGeometry = ({ scene, onGenerated, className = '', }) => {
317
317
  const { t } = i18n.useNiceTranslation();
318
318
  const { type, setType, treeParams, setTreeParams, rockParams, setRockParams, buildingParams, setBuildingParams, gearParams, setGearParams, generate, } = useProceduralGeometry(scene, onGenerated);
319
- return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children: t('proceduralGeometry.proceduralGeometry', 'Procedural Geometry') }), jsxRuntime.jsxs("select", { value: type, onChange: (e) => setType(e.target.value), style: { width: '100%', padding: '4px 8px' }, children: [jsxRuntime.jsx("option", { value: "tree", children: t('proceduralGeometry.tree', 'Tree') }), jsxRuntime.jsx("option", { value: "rock", children: t('proceduralGeometry.rock', 'Rock') }), jsxRuntime.jsx("option", { value: "building", children: t('proceduralGeometry.building', 'Building') }), jsxRuntime.jsx("option", { value: "pipe", children: t('proceduralGeometry.pipe', 'Pipe') }), jsxRuntime.jsx("option", { value: "gear", children: t('proceduralGeometry.gear', 'Gear') })] }), type === 'tree' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Height: ", treeParams.trunkHeight.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 1, max: 15, step: 0.5, value: treeParams.trunkHeight, onChange: (e) => setTreeParams({ ...treeParams, trunkHeight: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Branches: ", treeParams.branches, jsxRuntime.jsx("input", { type: "range", min: 1, max: 6, value: treeParams.branches, onChange: (e) => setTreeParams({ ...treeParams, branches: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Depth: ", treeParams.depth, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: treeParams.depth, onChange: (e) => setTreeParams({ ...treeParams, depth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Angle: ", treeParams.branchAngle, "\u00B0", jsxRuntime.jsx("input", { type: "range", min: 10, max: 80, value: treeParams.branchAngle, onChange: (e) => setTreeParams({ ...treeParams, branchAngle: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", treeParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: treeParams.seed, onChange: (e) => setTreeParams({ ...treeParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'rock' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Radius: ", rockParams.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 5, step: 0.1, value: rockParams.radius, onChange: (e) => setRockParams({ ...rockParams, radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Detail: ", rockParams.detail, jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: rockParams.detail, onChange: (e) => setRockParams({ ...rockParams, detail: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Noise: ", rockParams.noiseAmount.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0, max: 0.5, step: 0.01, value: rockParams.noiseAmount, onChange: (e) => setRockParams({ ...rockParams, noiseAmount: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Seed: ", rockParams.seed, jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: rockParams.seed, onChange: (e) => setRockParams({ ...rockParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'building' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Width: ", buildingParams.width, jsxRuntime.jsx("input", { type: "range", min: 4, max: 20, step: 1, value: buildingParams.width, onChange: (e) => setBuildingParams({ ...buildingParams, width: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Floors: ", buildingParams.floors, jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, value: buildingParams.floors, onChange: (e) => setBuildingParams({ ...buildingParams, floors: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Windows/Floor: ", buildingParams.windowsPerFloor, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: buildingParams.windowsPerFloor, onChange: (e) => setBuildingParams({
319
+ return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children: t('proceduralGeometry.proceduralGeometry', 'Procedural Geometry') }), jsxRuntime.jsxs("select", { value: type, onChange: (e) => setType(e.target.value), style: { width: '100%', padding: '4px 8px' }, children: [jsxRuntime.jsx("option", { value: "tree", children: t('proceduralGeometry.tree', 'Tree') }), jsxRuntime.jsx("option", { value: "rock", children: t('proceduralGeometry.rock', 'Rock') }), jsxRuntime.jsx("option", { value: "building", children: t('proceduralGeometry.building', 'Building') }), jsxRuntime.jsx("option", { value: "pipe", children: t('proceduralGeometry.pipe', 'Pipe') }), jsxRuntime.jsx("option", { value: "gear", children: t('proceduralGeometry.gear', 'Gear') })] }), type === 'tree' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.height', 'Height: {trunkHeight}').replace('{trunkHeight}', String(treeParams.trunkHeight.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 1, max: 15, step: 0.5, value: treeParams.trunkHeight, onChange: (e) => setTreeParams({ ...treeParams, trunkHeight: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.branches', 'Branches: {branches}').replace('{branches}', String(treeParams.branches)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 6, value: treeParams.branches, onChange: (e) => setTreeParams({ ...treeParams, branches: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.depth', 'Depth: {depth}').replace('{depth}', String(treeParams.depth)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: treeParams.depth, onChange: (e) => setTreeParams({ ...treeParams, depth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.angle', 'Angle: {branchAngle}°').replace('{branchAngle}', String(treeParams.branchAngle)), jsxRuntime.jsx("input", { type: "range", min: 10, max: 80, value: treeParams.branchAngle, onChange: (e) => setTreeParams({ ...treeParams, branchAngle: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.seed', 'Seed: {seed}').replace('{seed}', String(treeParams.seed)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: treeParams.seed, onChange: (e) => setTreeParams({ ...treeParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'rock' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.radius', 'Radius: {radius}').replace('{radius}', String(rockParams.radius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 5, step: 0.1, value: rockParams.radius, onChange: (e) => setRockParams({ ...rockParams, radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.detail', 'Detail: {detail}').replace('{detail}', String(rockParams.detail)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: rockParams.detail, onChange: (e) => setRockParams({ ...rockParams, detail: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.noise', 'Noise: {noiseAmount}').replace('{noiseAmount}', String(rockParams.noiseAmount.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 0.5, step: 0.01, value: rockParams.noiseAmount, onChange: (e) => setRockParams({ ...rockParams, noiseAmount: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.seed', 'Seed: {seed}').replace('{seed}', String(rockParams.seed)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: rockParams.seed, onChange: (e) => setRockParams({ ...rockParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'building' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.width', 'Width: {width}').replace('{width}', String(buildingParams.width)), jsxRuntime.jsx("input", { type: "range", min: 4, max: 20, step: 1, value: buildingParams.width, onChange: (e) => setBuildingParams({ ...buildingParams, width: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.floors', 'Floors: {floors}').replace('{floors}', String(buildingParams.floors)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, value: buildingParams.floors, onChange: (e) => setBuildingParams({ ...buildingParams, floors: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.windowsFloor', 'Windows/Floor: {windowsPerFloor}').replace('{windowsPerFloor}', String(buildingParams.windowsPerFloor)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: buildingParams.windowsPerFloor, onChange: (e) => setBuildingParams({
320
320
  ...buildingParams,
321
321
  windowsPerFloor: parseInt(e.target.value, 10),
322
- }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Roof:", jsxRuntime.jsxs("select", { value: buildingParams.roofStyle, onChange: (e) => setBuildingParams({
322
+ }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.roof', 'Roof:'), jsxRuntime.jsxs("select", { value: buildingParams.roofStyle, onChange: (e) => setBuildingParams({
323
323
  ...buildingParams,
324
324
  roofStyle: e.target.value,
325
- }), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children: t('proceduralGeometry.flat', 'Flat') }), jsxRuntime.jsx("option", { value: "gable", children: t('proceduralGeometry.gable', 'Gable') }), jsxRuntime.jsx("option", { value: "hip", children: "Hip" })] })] })] })), type === 'gear' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: ["Outer Radius: ", gearParams.outerRadius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 5, step: 0.1, value: gearParams.outerRadius, onChange: (e) => setGearParams({ ...gearParams, outerRadius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Teeth: ", gearParams.teeth, jsxRuntime.jsx("input", { type: "range", min: 4, max: 30, value: gearParams.teeth, onChange: (e) => setGearParams({ ...gearParams, teeth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Tooth Depth: ", gearParams.toothDepth.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: gearParams.toothDepth, onChange: (e) => setGearParams({ ...gearParams, toothDepth: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Thickness: ", gearParams.thickness.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 2, step: 0.1, value: gearParams.thickness, onChange: (e) => setGearParams({ ...gearParams, thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] })] })), jsxRuntime.jsxs("button", { type: "button", onClick: generate, style: {
325
+ }), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children: t('proceduralGeometry.flat', 'Flat') }), jsxRuntime.jsx("option", { value: "gable", children: t('proceduralGeometry.gable', 'Gable') }), jsxRuntime.jsx("option", { value: "hip", children: t('proceduralGeometry.hip', 'Hip') })] })] })] })), type === 'gear' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.outerRadius', 'Outer Radius: {outerRadius}').replace('{outerRadius}', String(gearParams.outerRadius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 5, step: 0.1, value: gearParams.outerRadius, onChange: (e) => setGearParams({ ...gearParams, outerRadius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.teeth', 'Teeth: {teeth}').replace('{teeth}', String(gearParams.teeth)), jsxRuntime.jsx("input", { type: "range", min: 4, max: 30, value: gearParams.teeth, onChange: (e) => setGearParams({ ...gearParams, teeth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.toothDepth', 'Tooth Depth: {toothDepth}').replace('{toothDepth}', String(gearParams.toothDepth.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: gearParams.toothDepth, onChange: (e) => setGearParams({ ...gearParams, toothDepth: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.thickness', 'Thickness: {thickness}').replace('{thickness}', String(gearParams.thickness.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 2, step: 0.1, value: gearParams.thickness, onChange: (e) => setGearParams({ ...gearParams, thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] })] })), jsxRuntime.jsxs("button", { type: "button", onClick: generate, style: {
326
326
  background: 'var(--nice-color-accent, #6366f1)',
327
327
  color: 'var(--nice-text-on-primary, #fff)',
328
328
  border: 'none',
@@ -367,7 +367,7 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
367
367
  border: '1px solid var(--nice-border, #e2e8f0)',
368
368
  background: brush.mode === m ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
369
369
  color: brush.mode === m ? 'var(--nice-color-white, #fff)' : 'inherit',
370
- }, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: ["Radius: ", brush.radius.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 20, step: 0.5, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Strength: ", brush.strength.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
370
+ }, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.radius', 'Radius: {radius}').replace('{radius}', String(brush.radius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 20, step: 0.5, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.strength', 'Strength: {strength}').replace('{strength}', String(brush.strength.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
371
371
  background: isPainting
372
372
  ? 'var(--nice-danger, #ef4444)'
373
373
  : 'var(--nice-color-accent, #6366f1)',
@@ -382,7 +382,7 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
382
382
  padding: 6,
383
383
  display: 'grid',
384
384
  gap: 4,
385
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.procedural', 'Procedural') }), jsxRuntime.jsxs("label", { children: ["Octaves: ", noiseOctaves, jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: noiseOctaves, onChange: (e) => setNoiseOctaves(parseInt(e.target.value, 10)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Persistence: ", noisePersistence.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 1, step: 0.05, value: noisePersistence, onChange: (e) => setNoisePersistence(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => generateNoise(noiseOctaves, noisePersistence), style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "mountain", size: 14 }), ' ', t('terrainEditor.generateNoise', 'Generate Noise')] }), jsxRuntime.jsxs("button", { type: "button", onClick: generateFlat, style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 14 }), " ", t('terrainEditor.flatten', 'Flatten')] })] })] }), brush.mode === 'paint' && (jsxRuntime.jsxs("fieldset", { style: {
385
+ }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.procedural', 'Procedural') }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.octaves', 'Octaves: {noiseOctaves}').replace('{noiseOctaves}', String(noiseOctaves)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: noiseOctaves, onChange: (e) => setNoiseOctaves(parseInt(e.target.value, 10)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.persistence', 'Persistence: {noisePersistence}').replace('{noisePersistence}', String(noisePersistence.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 1, step: 0.05, value: noisePersistence, onChange: (e) => setNoisePersistence(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => generateNoise(noiseOctaves, noisePersistence), style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "mountain", size: 14 }), ' ', t('terrainEditor.generateNoise', 'Generate Noise')] }), jsxRuntime.jsxs("button", { type: "button", onClick: generateFlat, style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 14 }), " ", t('terrainEditor.flatten', 'Flatten')] })] })] }), brush.mode === 'paint' && (jsxRuntime.jsxs("fieldset", { style: {
386
386
  border: '1px solid var(--nice-border, #e2e8f0)',
387
387
  borderRadius: 6,
388
388
  padding: 6,