From 9d019157047901dc49ee1b329f1971b8d9736dc4 Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 10 Jul 2026 16:34:03 +0000 Subject: [PATCH] feat: apply dark mode contrast corrections to all 3D modules --- app/src/components/Warehouse3D.tsx | 27 +++++++++++----- app/src/components/three/Bar3D.tsx | 21 ++++++++++--- app/src/components/three/Bridge3D.tsx | 31 ++++++++++++------ app/src/components/three/Cylinder3D.tsx | 20 +++++++++--- app/src/components/three/Dome3D.tsx | 37 +++++++++++++--------- app/src/components/three/Dynamics3D.tsx | 17 ++++++++-- app/src/components/three/Sign3D.tsx | 42 ++++++++++++++++--------- app/src/components/three/Vault3D.tsx | 29 ++++++++++++----- 8 files changed, 158 insertions(+), 66 deletions(-) diff --git a/app/src/components/Warehouse3D.tsx b/app/src/components/Warehouse3D.tsx index 6c65dca..9ef2156 100644 --- a/app/src/components/Warehouse3D.tsx +++ b/app/src/components/Warehouse3D.tsx @@ -5,6 +5,7 @@ import { useGalpaoStore } from '../store/galpaoStore'; import { useWindStore } from '../store/appStore'; import SceneCanvas from './SceneCanvas'; import FallbackDiagram from './FallbackDiagram'; +import { useCanvasTheme } from '../lib/theme'; function pressureColor(cpe: number, cpi: number): THREE.Color { const p = cpe - cpi; @@ -72,6 +73,8 @@ function PressureArrow({ } export function WarehouseModel() { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore(); const { windAngle, cpi } = useWindStore(); @@ -286,17 +289,17 @@ export function WarehouseModel() { {/* Cumeeira */} - + {/* Bordas Laterais do Telhado (Beirais) */} - + - + {/* === RÓTULOS 3D === */} @@ -304,7 +307,7 @@ export function WarehouseModel() { @@ -316,7 +319,7 @@ export function WarehouseModel() { position={[0, height / 2, -length / 2 - 1.0]} rotation={[0, Math.PI, 0]} fontSize={Math.max(0.3, Math.min(0.6, width / 20))} - color="#1a202c" + color={isDark ? "#cbd5e1" : "#1a202c"} anchorX="center" anchorY="middle" > @@ -328,7 +331,7 @@ export function WarehouseModel() { position={[-width / 2 - 1.0, height / 2, 0]} rotation={[0, -Math.PI / 2, 0]} fontSize={Math.max(0.3, Math.min(0.6, length / 20))} - color="#1a202c" + color={isDark ? "#cbd5e1" : "#1a202c"} anchorX="center" anchorY="middle" > @@ -340,7 +343,7 @@ export function WarehouseModel() { position={[width / 2 + 1.0, height / 2, 0]} rotation={[0, Math.PI / 2, 0]} fontSize={Math.max(0.3, Math.min(0.6, length / 20))} - color="#1a202c" + color={isDark ? "#cbd5e1" : "#1a202c"} anchorX="center" anchorY="middle" > @@ -351,6 +354,8 @@ export function WarehouseModel() { } export default function Warehouse3DViewer() { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore(); const { windAngle, cpi } = useWindStore(); @@ -384,7 +389,13 @@ export default function Warehouse3DViewer() { shadow-camera-bottom={-maxDimension} /> - + diff --git a/app/src/components/three/Bar3D.tsx b/app/src/components/three/Bar3D.tsx index 96bf9ff..930a67e 100644 --- a/app/src/components/three/Bar3D.tsx +++ b/app/src/components/three/Bar3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { useCanvasTheme } from '@/lib/theme'; export interface Bar3DInput { /** Tipo de seção */ @@ -34,14 +35,18 @@ function BarModel({ alpha, cx, }: Bar3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const barRadius = barType === 'circular' ? (diameter ?? 0.05) / 2 : Math.min(width ?? 0.1, 0.08) / 2; const barThickness = barType === 'circular' ? barRadius : barRadius * 0.5; const barColor = useMemo(() => { const intensity = Math.min(1, Math.abs(cx) / 2.5); const hue = 215 - intensity * 215; - return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${45 - intensity * 10}%)`); - }, [cx]); + const l = isDark ? (55 - intensity * 10) : (45 - intensity * 10); + return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${l}%)`); + }, [cx, isDark]); // Ação do vento deve rotacionar a barra em seu próprio eixo longitudinal (roll). // O eixo longitudinal na nossa geometria é o X. @@ -87,7 +92,7 @@ function BarModel({ @@ -172,6 +177,8 @@ function SectionShape({ } export default function Bar3DViewer(input: Bar3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const { length, width, diameter } = input; const size = Math.max(length * 0.6, (width ?? diameter ?? 0.1) * 8); @@ -192,7 +199,13 @@ export default function Bar3DViewer(input: Bar3DInput) { - + diff --git a/app/src/components/three/Bridge3D.tsx b/app/src/components/three/Bridge3D.tsx index a566a4b..08a4371 100644 --- a/app/src/components/three/Bridge3D.tsx +++ b/app/src/components/three/Bridge3D.tsx @@ -4,6 +4,7 @@ import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; import { WindArrow } from './WindArrow'; +import { useCanvasTheme } from '@/lib/theme'; export interface Bridge3DInput { /** Maior vão Lₚ (m) */ @@ -34,6 +35,9 @@ function BridgeModel({ cx, alpha, }: Bridge3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const halfL = lp / 2; const halfW = width / 2; const deckThickness = Math.max(heg, 0.8); @@ -43,11 +47,14 @@ function BridgeModel({ const deckColor = useMemo(() => { const intensity = Math.min(1, Math.abs(cx) / 3); const hue = 200 - intensity * 60; - return new THREE.Color(`hsl(${hue}, ${55 + intensity * 30}%, ${50 - intensity * 8}%)`); - }, [cx]); + const l = isDark ? (60 - intensity * 8) : (50 - intensity * 8); + return new THREE.Color(`hsl(${hue}, ${55 + intensity * 30}%, ${l}%)`); + }, [cx, isDark]); // Pilar heights: posicionar 3 pilares ao longo do vão const pillarHeights = useMemo(() => [deckY - 0.5, deckY - 0.5, deckY - 0.5], [deckY]); + const barrierColor = isDark ? '#cbd5e1' : '#94a3b8'; + const pillarColor = isDark ? '#94a3b8' : '#64748b'; return ( @@ -60,11 +67,11 @@ function BridgeModel({ {/* Guarda-rodas/barreira lateral */} - + - + {/* Pilares (3 ao longo do comprimento) */} @@ -73,7 +80,7 @@ function BridgeModel({ return ( - + ); })} @@ -93,7 +100,7 @@ function BridgeModel({ @@ -103,9 +110,9 @@ function BridgeModel({ ); } - - export default function Bridge3DViewer(input: Bridge3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const { lp, deckHeight, width } = input; const dist = Math.max(lp * 0.6, deckHeight * 2); @@ -126,7 +133,13 @@ export default function Bridge3DViewer(input: Bridge3DInput) { - + diff --git a/app/src/components/three/Cylinder3D.tsx b/app/src/components/three/Cylinder3D.tsx index ce882c4..cc8b5b6 100644 --- a/app/src/components/three/Cylinder3D.tsx +++ b/app/src/components/three/Cylinder3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { useCanvasTheme } from '@/lib/theme'; export interface Cylinder3DInput { diameter: number; @@ -22,6 +23,8 @@ function cylinderColor(cpe: number, cpi: number): THREE.Color { } function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const segments = 64; const radius = diameter / 2; @@ -121,11 +124,11 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) { {/* Anéis de detalhe (bordas do cilindro) */} - + - + {/* Seta indicativa de direção do vento */} @@ -154,7 +157,7 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) { @@ -165,6 +168,9 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) { } export default function Cylinder3DViewer({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const fallback = ( - + diff --git a/app/src/components/three/Dome3D.tsx b/app/src/components/three/Dome3D.tsx index 55cb9ef..e866ae1 100644 --- a/app/src/components/three/Dome3D.tsx +++ b/app/src/components/three/Dome3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { useCanvasTheme } from '@/lib/theme'; export interface Dome3DInput { diameter: number; @@ -14,16 +15,19 @@ export interface Dome3DInput { cpeLateral: number; } -function domeColor(cpe: number, cpi: number): THREE.Color { +function domeColor(cpe: number, cpi: number, isDark: boolean): THREE.Color { const p = cpe - cpi; const intensity = Math.min(1, Math.abs(p) / 1.5); + const l = isDark ? (65 - intensity * 15) : (60 - intensity * 25); if (p > 0) { - return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 60 - intensity * 25)}%)`); + return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, l)}%)`); } - return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 60 - intensity * 20)}%)`); + return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, l + 5)}%)`); } function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cpeLateral }: Dome3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const radius = diameter / 2; const segments = 64; @@ -31,12 +35,6 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp const domeGeoms = useMemo(() => { const arr: { startTheta: number; endTheta: number; color: THREE.Color }[] = []; // Divide a circunferência completa (360°) em 6 zonas (simétricas) - // 0° a 60°: Barlavento - // 60° a 120°: Topo - // 120° a 180°: Lateral - // 180° a 240°: Lateral (espelhado) - // 240° a 300°: Topo (espelhado) - // 300° a 360°: Barlavento (espelhado) const zones = [ { fromDeg: 0, toDeg: 60, cpe: cpeBarlavento }, { fromDeg: 60, toDeg: 120, cpe: cpeTopo }, @@ -49,11 +47,11 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp arr.push({ startTheta: (z.fromDeg * Math.PI) / 180, endTheta: (z.toDeg * Math.PI) / 180, - color: domeColor(z.cpe, cpi), + color: domeColor(z.cpe, cpi, isDark), }); } return arr; - }, [cpeBarlavento, cpeTopo, cpeLateral, cpi]); + }, [cpeBarlavento, cpeTopo, cpeLateral, cpi, isDark]); // Raio da esfera da calota esférica baseada na flecha (rise) e raio da base (radius) const rSphere = useMemo(() => { @@ -71,11 +69,11 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp {/* Detalhes de anéis metálicos nas bordas */} - + - + {/* Cúpula de cobertura (Spherical Cap) segmentada */} @@ -156,7 +154,7 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp @@ -167,6 +165,9 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp } export default function Dome3DViewer(props: Dome3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const fallback = ( - + diff --git a/app/src/components/three/Dynamics3D.tsx b/app/src/components/three/Dynamics3D.tsx index e2f75bc..52dc4e1 100644 --- a/app/src/components/three/Dynamics3D.tsx +++ b/app/src/components/three/Dynamics3D.tsx @@ -4,6 +4,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { useCanvasTheme } from '@/lib/theme'; export interface Dynamics3DInput { /** Altura da estrutura (m) */ @@ -138,11 +139,13 @@ function VortexStreet({ } function DynamicsModel(props: Dynamics3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; return ( - + @@ -175,6 +178,8 @@ function DynamicsModel(props: Dynamics3DInput) { } export default function Dynamics3DViewer(props: Dynamics3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const cameraDistance = Math.max(props.height * SCALE * 2, 6); const fallback = ( @@ -201,7 +206,13 @@ export default function Dynamics3DViewer(props: Dynamics3DInput) { shadow-mapSize-height={1024} /> - + diff --git a/app/src/components/three/Sign3D.tsx b/app/src/components/three/Sign3D.tsx index 79bbb4c..6b449ce 100644 --- a/app/src/components/three/Sign3D.tsx +++ b/app/src/components/three/Sign3D.tsx @@ -4,6 +4,7 @@ import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; import { WindArrow } from './WindArrow'; +import { useCanvasTheme } from '@/lib/theme'; export interface Sign3DInput { /** Comprimento ℓ (m) */ @@ -34,6 +35,8 @@ function SignModel({ cf, applicationPoint, }: Sign3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const baseY = groundClearance; const topY = baseY + height; const halfL = length / 2; @@ -72,11 +75,11 @@ function SignModel({ <> - + - + )} @@ -84,13 +87,13 @@ function SignModel({ {/* Linha do solo (base translúcida) */} - + {/* Eixo horizontal de referência de direção do vento */} - + {/* Marca da excentricidade (Ponto de Aplicação da Resultante) */} @@ -104,7 +107,7 @@ function SignModel({ position={[0.2, (baseY + topY) / 2, applicationPoint]} rotation={[0, Math.PI / 2, 0]} fontSize={0.15} - color="#d97706" + color={isDark ? "#fcd34d" : "#d97706"} anchorX="left" anchorY="middle" > @@ -123,24 +126,24 @@ function SignModel({ {/* Linha vertical */} - + {/* Traço superior */} - + {/* Traço inferior */} - + {/* Texto da altura */} @@ -153,24 +156,24 @@ function SignModel({ {/* Linha horizontal longitudinal */} - + {/* Traço frontal */} - + {/* Traço traseiro */} - + {/* Texto do comprimento */} @@ -182,7 +185,7 @@ function SignModel({ @@ -203,6 +206,9 @@ export default function Sign3DViewer({ forceKN, applicationPoint, }: Sign3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const fallback = ( - + diff --git a/app/src/components/three/Vault3D.tsx b/app/src/components/three/Vault3D.tsx index 14612b4..dab7a86 100644 --- a/app/src/components/three/Vault3D.tsx +++ b/app/src/components/three/Vault3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { useCanvasTheme } from '@/lib/theme'; export interface Vault3DInput { span: number; @@ -12,16 +13,19 @@ export interface Vault3DInput { cpeProfile: Record; } -function vaultColor(cpe: number, cpi: number): THREE.Color { +function vaultColor(cpe: number, cpi: number, isDark: boolean): THREE.Color { const p = cpe - cpi; const intensity = Math.min(1, Math.abs(p) / 1.5); + const l = isDark ? (65 - intensity * 15) : (60 - intensity * 25); if (p > 0) { - return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 60 - intensity * 25)}%)`); + return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, l)}%)`); } - return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 60 - intensity * 20)}%)`); + return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, l + 5)}%)`); } function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; const segments = 64; const points = useMemo(() => { const pts: THREE.Vector3[] = []; @@ -66,7 +70,7 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) { {/* Casca da Abóbada */} {zones.map((zone, idx) => { - const color = vaultColor(zone.cpe, cpi); + const color = vaultColor(zone.cpe, cpi, isDark); const verts: number[] = []; for (let i = zone.startIdx; i <= zone.endIdx; i++) { verts.push(points[i].x, points[i].y, points[i].z); @@ -94,20 +98,20 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) { {/* Tímpano Traseiro (Z = 0) */} - + {/* Tímpano Frontal (Z = length) */} - + {/* Rótulo de dimensões */} @@ -118,6 +122,9 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) { } export default function Vault3DViewer({ span, length, rise, cpi, cpeProfile }: Vault3DInput) { + const theme = useCanvasTheme(); + const isDark = theme === 'dark'; + const fallback = ( - +