🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 14:52:07

This commit is contained in:
2026-05-30 14:52:07 +00:00
parent 46ae916a4a
commit 37bac18534
3 changed files with 101 additions and 12 deletions
+76
View File
@@ -0,0 +1,76 @@
import { ShieldCheck, Ruler, Scissors, ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
export function LandingContent() {
return (
<div className="w-full max-w-4xl mx-auto space-y-12 py-12">
{/* Hero Section */}
<section className="text-center space-y-6 px-4">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight text-foreground">
Controle total da qualidade em obras metálicas e civis <br className="hidden md:block" />
<span className="text-primary">direto no canteiro, em Realidade Aumentada.</span>
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
visualize. Meça. inspire confiança.
</p>
</section>
{/* Benefits Section */}
<section className="grid md:grid-cols-3 gap-6 px-4">
<div className="rounded-xl border bg-card p-6 space-y-4 hover:border-primary/50 transition-colors">
<div className="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center">
<ShieldCheck className="h-6 w-6 text-primary" />
</div>
<h3 className="text-lg font-semibold text-foreground">
Inspeção por Checklist Digital com Gravação de Voz
</h3>
<p className="text-sm text-muted-foreground">
Elimine planilhas de papel. Faça inspeções de qualidade diretamente no modelo 3D, com aprovação ou reprovação de cada item e anotações por voz tudo rastreável e auditável.
</p>
</div>
<div className="rounded-xl border bg-card p-6 space-y-4 hover:border-primary/50 transition-colors">
<div className="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center">
<Ruler className="h-6 w-6 text-primary" />
</div>
<h3 className="text-lg font-semibold text-foreground">
Medições Precisas em AR com Snap Inteligente
</h3>
<p className="text-sm text-muted-foreground">
O controlador XR identifica automaticamente vértices, arestas e furos nos modelos IFC. O resultado são medições em milímetros reais, sem depender de trena ou cálculos manuais.
</p>
</div>
<div className="rounded-xl border bg-card p-6 space-y-4 hover:border-primary/50 transition-colors">
<div className="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center">
<Scissors className="h-6 w-6 text-primary" />
</div>
<h3 className="text-lg font-semibold text-foreground">
Cortes de Seção Interativos para Análise Estrutural
</h3>
<p className="text-sm text-muted-foreground">
Visualize o interior de estruturas metálicas com cortes dinâmicos nos eixos X, Y e Z sem desmontar nada. Ideal para validar projetos complexos antes da montagem.
</p>
</div>
</section>
{/* Final CTA Section */}
<section className="px-4 text-center space-y-6">
<div className="rounded-2xl border border-border bg-gradient-to-b from-card to-muted/20 p-8 md:p-12">
<p className="text-muted-foreground max-w-2xl mx-auto text-base md:text-lg leading-relaxed">
Atrasos por erros de medição, retrabalho por falhas de comunicação e perdas por não conformidade são o pesadelo de qualquer gestão de produção metalmecânica ou obra civil. O TrackSteelXR coloca o modelo 3D IFC no canteiro em AR para que engenheiros e gestores tomem decisões precisas no momento certo. <span className="font-semibold text-foreground">Menos retrabalho, mais produtividade, entregas dentro do prazo.</span>
</p>
<div className="mt-8 flex justify-center gap-4">
<Button size="lg" className="gap-2">
Começar Agora
<ArrowRight className="h-4 w-4" />
</Button>
<Button size="lg" variant="outline" className="gap-2">
Ver Demonstração
</Button>
</div>
</div>
</section>
</div>
);
}
+9 -2
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef, useCallback, useState } from 'react'; import { useEffect, useRef, useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Upload, Glasses, CheckCircle, XCircle, Loader2, Box, Package, Users } from 'lucide-react'; import { Upload, Glasses, CheckCircle, XCircle, Loader2, Box, Package, Users, Info } from 'lucide-react';
import { LandingContent } from '@/components/LandingContent';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useModelStore } from '@/stores/useModelStore'; import { useModelStore } from '@/stores/useModelStore';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -20,6 +21,7 @@ const Index = () => {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [loadingDemoIFC, setLoadingDemoIFC] = useState(false); const [loadingDemoIFC, setLoadingDemoIFC] = useState(false);
const [loadingDemoCotovelo, setLoadingDemoCotovelo] = useState(false); const [loadingDemoCotovelo, setLoadingDemoCotovelo] = useState(false);
const [showLanding, setShowLanding] = useState(false);
const [converting, setConverting] = useState(false); const [converting, setConverting] = useState(false);
const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore(); const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore();
@@ -209,18 +211,23 @@ const Index = () => {
{/* Logo area */} {/* Logo area */}
<div className="mb-12 text-center"> <div className="mb-8 text-center">
<div className="mb-4 flex items-center justify-center gap-3"> <div className="mb-4 flex items-center justify-center gap-3">
<Box className="h-10 w-10 text-primary" /> <Box className="h-10 w-10 text-primary" />
<h1 className="text-3xl font-bold tracking-tight text-foreground md:text-4xl"> <h1 className="text-3xl font-bold tracking-tight text-foreground md:text-4xl">
TrackSteel<span className="text-primary">XR</span> TrackSteel<span className="text-primary">XR</span>
</h1> </h1>
<Button variant="ghost" size="icon" className="ml-2" onClick={() => setShowLanding(!showLanding)} title="Sobre">
<Info className="h-5 w-5" />
</Button>
</div> </div>
<p className="font-mono text-sm uppercase tracking-widest text-muted-foreground"> <p className="font-mono text-sm uppercase tracking-widest text-muted-foreground">
Inspeção de Qualidade Industrial Inspeção de Qualidade Industrial
</p> </p>
</div> </div>
{showLanding && <LandingContent />}
{/* Main card */} {/* Main card */}
<div className="w-full max-w-md space-y-4"> <div className="w-full max-w-md space-y-4">
{/* Import button */} {/* Import button */}
+16 -10
View File
@@ -531,6 +531,7 @@ function HologramModel({ model, presentationState, isPresenter, onPresentationCh
const { scene } = useGLTF(model.url); const { scene } = useGLTF(model.url);
const clone = useMemo(() => scene.clone(), [scene]); const clone = useMemo(() => scene.clone(), [scene]);
const presentationGroupRef = useRef<THREE.Group>(null); const presentationGroupRef = useRef<THREE.Group>(null);
const [presenterGroup, setPresenterGroup] = useState<THREE.Group | null>(null);
const baseScaleRef = useRef<number>(1); const baseScaleRef = useRef<number>(1);
const centerRef = useRef<THREE.Vector3>(new THREE.Vector3()); const centerRef = useRef<THREE.Vector3>(new THREE.Vector3());
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
@@ -590,13 +591,14 @@ function HologramModel({ model, presentationState, isPresenter, onPresentationCh
// Aplica transformações de Rotação e Escala compartilhadas no grupo externo de apresentação // Aplica transformações de Rotação e Escala compartilhadas no grupo externo de apresentação
useFrame(() => { useFrame(() => {
if (presentationGroupRef.current && presentationState && !isDragging) { const targetGroup = presentationGroupRef.current || presenterGroup;
if (targetGroup && presentationState && !isDragging) {
const { rotation, scale } = presentationState; const { rotation, scale } = presentationState;
if (rotation) { if (rotation) {
presentationGroupRef.current.rotation.set(rotation[0], rotation[1], rotation[2]); targetGroup.rotation.set(rotation[0], rotation[1], rotation[2]);
} }
if (scale) { if (scale) {
presentationGroupRef.current.scale.setScalar(baseScaleRef.current * scale); targetGroup.scale.setScalar(baseScaleRef.current * scale);
} }
} }
}); });
@@ -666,20 +668,20 @@ function HologramModel({ model, presentationState, isPresenter, onPresentationCh
return ( return (
<> <>
{isPresenter && ( {isPresenter && presenterGroup && (
<TransformControls <TransformControls
object={presentationGroupRef} object={presenterGroup}
mode="rotate" mode="rotate"
size={0.6} size={0.6}
onMouseDown={() => setIsDragging(true)} onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)} onMouseUp={() => setIsDragging(false)}
onChange={() => { onChange={() => {
if (isDragging && presentationGroupRef.current) { if (isDragging && presenterGroup) {
onPresentationChange({ onPresentationChange({
rotation: [ rotation: [
presentationGroupRef.current.rotation.x, presenterGroup.rotation.x,
presentationGroupRef.current.rotation.y, presenterGroup.rotation.y,
presentationGroupRef.current.rotation.z presenterGroup.rotation.z
] ]
}); });
} }
@@ -687,7 +689,11 @@ function HologramModel({ model, presentationState, isPresenter, onPresentationCh
/> />
)} )}
{/* Grupo Externo de Apresentação Compartilhada */} {/* Grupo Externo de Apresentação Compartilhada */}
<group ref={presentationGroupRef}> <group ref={(node) => {
// @ts-ignore
presentationGroupRef.current = node;
setPresenterGroup(node);
}}>
{/* Ignoramos fineTuning.posX/Y/Z para ancorar perfeitamente à mesa */} {/* Ignoramos fineTuning.posX/Y/Z para ancorar perfeitamente à mesa */}
<group> <group>
{/* Grupo de Rotação Fina e Escala Fina */} {/* Grupo de Rotação Fina e Escala Fina */}