fix(viewer3d): harmonize ViewCube container background and ISO button for light mode

This commit is contained in:
2026-09-21 01:48:01 +00:00
parent 94d41c0e85
commit 3882e8c338
+52 -26
View File
@@ -13,6 +13,7 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
const sceneRef = useRef<THREE.Scene | null>(null);
const cameraRef = useRef<THREE.OrthographicCamera | null>(null);
const cubeRef = useRef<THREE.Mesh | null>(null);
const wireframeRef = useRef<THREE.LineSegments | null>(null);
const raycasterRef = useRef<THREE.Raycaster>(new THREE.Raycaster());
const mouseRef = useRef<THREE.Vector2>(new THREE.Vector2());
@@ -39,7 +40,7 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
camera.lookAt(0, 0, 0);
cameraRef.current = camera;
// 2. Renderer
// 2. Renderer Transparente
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
@@ -49,24 +50,24 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
containerRef.current.innerHTML = '';
containerRef.current.appendChild(renderer.domElement);
// 3. Helper to create high-res Canvas Texture for each face (SteelXR Style)
const createFaceTexture = (text: string) => {
// 3. Helper to create high-res Canvas Texture for each face
const createFaceTexture = (text: string, isDark: boolean) => {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
if (ctx) {
// Background: Deep Dark Navy Blue (#0f172a / #1e293b)
ctx.fillStyle = '#0f172a';
// Face Background: Azul petróleo equilibrado no claro (#1e293b) e escuro (#0f172a)
ctx.fillStyle = isDark ? '#0f172a' : '#1e293b';
ctx.fillRect(0, 0, 256, 256);
// Neon Cyan Border (#22d3ee)
ctx.strokeStyle = '#22d3ee';
// Neon Cyan Border (#0891b2 no claro / #22d3ee no escuro)
ctx.strokeStyle = isDark ? '#22d3ee' : '#06b6d4';
ctx.lineWidth = 14;
ctx.strokeRect(7, 7, 242, 242);
// Inner soft cyan glow
ctx.strokeStyle = 'rgba(34, 211, 238, 0.4)';
ctx.strokeStyle = isDark ? 'rgba(34, 211, 238, 0.4)' : 'rgba(6, 182, 212, 0.3)';
ctx.lineWidth = 6;
ctx.strokeRect(18, 18, 220, 220);
@@ -75,8 +76,8 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
ctx.font = 'bold 54px system-ui, -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = '#06b6d4';
ctx.shadowBlur = 12;
ctx.shadowColor = isDark ? '#06b6d4' : '#0891b2';
ctx.shadowBlur = isDark ? 12 : 8;
ctx.fillText(text, 128, 128);
}
const texture = new THREE.CanvasTexture(canvas);
@@ -84,15 +85,16 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
return texture;
};
// Faces Order in Three.js BoxGeometry:
// 0: Right (DIR), 1: Left (ESQ), 2: Top (TOPO), 3: Bottom (BASE), 4: Front (FRENTE), 5: Back (ATRÁS)
const isDarkMode = document.documentElement.classList.contains('dark');
// Faces: 0: Right (DIR), 1: Left (ESQ), 2: Top (TOPO), 3: Bottom (BASE), 4: Front (FRENTE), 5: Back (ATRÁS)
const materials = [
new THREE.MeshBasicMaterial({ map: createFaceTexture('DIR') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('ESQ') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('TOPO') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('BASE') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('FRENTE') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('ATRÁS') }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('DIR', isDarkMode) }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('ESQ', isDarkMode) }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('TOPO', isDarkMode) }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('BASE', isDarkMode) }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('FRENTE', isDarkMode) }),
new THREE.MeshBasicMaterial({ map: createFaceTexture('ATRÁS', isDarkMode) }),
];
const geometry = new THREE.BoxGeometry(32, 32, 32);
@@ -102,9 +104,32 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
// Glowing Neon Edges on the Cube
const edges = new THREE.EdgesGeometry(geometry);
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x22d3ee, linewidth: 2 });
const lineMaterial = new THREE.LineBasicMaterial({
color: isDarkMode ? 0x22d3ee : 0x06b6d4,
linewidth: 2,
});
const wireframe = new THREE.LineSegments(edges, lineMaterial);
cube.add(wireframe);
wireframeRef.current = wireframe;
// Função de atualização quando o tema mudar
const updateThemeMaterials = () => {
const dark = document.documentElement.classList.contains('dark');
const labels = ['DIR', 'ESQ', 'TOPO', 'BASE', 'FRENTE', 'ATRÁS'];
materials.forEach((mat, idx) => {
if (mat.map) mat.map.dispose();
mat.map = createFaceTexture(labels[idx], dark);
mat.needsUpdate = true;
});
if (wireframeRef.current && wireframeRef.current.material) {
(wireframeRef.current.material as THREE.LineBasicMaterial).color.set(dark ? 0x22d3ee : 0x06b6d4);
}
};
const themeObserver = new MutationObserver(() => {
updateThemeMaterials();
});
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
// 4. Animation Loop: Sincroniza com a câmera principal em tempo real
let animId: number;
@@ -165,6 +190,7 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
return () => {
cancelAnimationFrame(animId);
dom.removeEventListener('click', handleCanvasClick);
themeObserver.disconnect();
materials.forEach((m) => {
if (m.map) m.map.dispose();
m.dispose();
@@ -176,21 +202,21 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
return (
<div className="relative select-none flex flex-col items-center">
{/* Container Reduzido (~70% do anterior) com estilo SteelXR */}
<div className="relative w-24 h-24 p-1 rounded-2xl bg-slate-950/85 backdrop-blur-xl border border-cyan-500/40 shadow-xl shadow-cyan-950/60 flex items-center justify-center cursor-pointer group">
{/* Glow sutil */}
<div className="absolute inset-0 bg-gradient-to-tr from-cyan-500/10 via-transparent to-blue-500/10 rounded-2xl pointer-events-none" />
{/* Container Reduzido (~70%) Harmonizado para Modo Claro e Escuro */}
<div className="relative w-24 h-24 p-1 rounded-2xl bg-white/90 dark:bg-slate-950/85 backdrop-blur-xl border border-slate-200 dark:border-cyan-500/40 shadow-lg dark:shadow-xl dark:shadow-cyan-950/60 flex items-center justify-center cursor-pointer group transition-all">
{/* Glow sutil harmonizado */}
<div className="absolute inset-0 bg-gradient-to-tr from-cyan-500/5 dark:from-cyan-500/10 via-transparent to-blue-500/5 dark:to-blue-500/10 rounded-2xl pointer-events-none" />
{/* Canvas WebGL 3D do Cubo Sincronizado */}
<div ref={containerRef} className="w-[84px] h-[84px] flex items-center justify-center" />
{/* Botão ISO no canto inferior */}
{/* Botão ISO no canto inferior harmonizado com o tema */}
<button
onClick={(e) => {
e.stopPropagation();
onSelectView('iso');
}}
className="absolute bottom-1 right-1 px-1 py-0.5 rounded bg-slate-900/90 hover:bg-cyan-600 text-cyan-300 hover:text-white border border-cyan-500/30 transition-all text-[9px] font-mono font-bold flex items-center gap-0.5 shadow cursor-pointer"
className="absolute bottom-1 right-1 px-1 py-0.5 rounded bg-slate-100 hover:bg-cyan-600 text-slate-700 hover:text-white border border-slate-300 hover:border-cyan-500 dark:bg-slate-900/90 dark:text-cyan-300 dark:border-cyan-500/30 transition-all text-[9px] font-mono font-bold flex items-center gap-0.5 shadow-sm cursor-pointer"
title="Restaurar Vista Isométrica 3D"
>
<RotateCcw className="w-2.5 h-2.5" />
@@ -199,7 +225,7 @@ export const ViewCube: React.FC<ViewCubeProps> = ({ onSelectView, mainCameraRef
</div>
{/* Rótulo inferior */}
<span className="text-[8px] font-mono font-bold tracking-widest uppercase text-cyan-700 dark:text-cyan-400 mt-0.5">
<span className="text-[8px] font-mono font-bold tracking-widest uppercase text-cyan-800 dark:text-cyan-400 mt-0.5">
Cubo 3D
</span>
</div>