🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 14:52:07
This commit is contained in:
@@ -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
@@ -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
@@ -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 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user