diff --git a/src/App.tsx b/src/App.tsx index 2ed666c..57b0c27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import HomePage from './pages/HomePage'; import BlogPage from "@/pages/BlogPage"; import ServiceDetailPage from './pages/ServiceDetailPage'; +import MachineWalkthroughPage from "@/pages/MachineWalkthroughPage"; export default function App() { return ( @@ -12,6 +13,7 @@ export default function App() { } /> } /> + } /> ); } diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index c3892cd..43c6277 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -24,6 +24,8 @@ export default function Layout() { "href": "#contact" }, { name: "Blog", href: "/blog" }, + { name: "Machine Walkthrough", href: "/machine-walkthrough" }, + ]; diff --git a/src/pages/MachineWalkthroughPage.tsx b/src/pages/MachineWalkthroughPage.tsx new file mode 100644 index 0000000..397e6ac --- /dev/null +++ b/src/pages/MachineWalkthroughPage.tsx @@ -0,0 +1,241 @@ +import { useState } from "react"; +import { motion, AnimatePresence } from "motion/react"; +import { routes } from "@/routes"; +import { Info, MapPin, ChevronLeft } from "lucide-react"; +import { cls } from "@/lib/utils"; + +type ViewKey = 'exterior' | 'cab' | 'machinery' | 'electrical' | 'boom' | 'maintenance'; + +type Hotspot = + | { id: string; x: number; y: number; label: string; type: 'nav'; target: ViewKey } + | { id: string; x: number; y: number; label: string; type: 'info'; info: string }; + +type View = { + title: string; + imageSrc: string; + hotspots: Hotspot[]; +}; + +const VIEWS: Record = { + exterior: { + title: "P&H 4200 Exterior", + imageSrc: "https://picsum.photos/seed/1329000375/1200/800", + hotspots: [ + { id: 'enter', x: 50, y: 60, label: "Enter the Machine", target: 'cab', type: 'nav' } + ] + }, + cab: { + title: "Operator's Cab", + imageSrc: "https://picsum.photos/seed/1496099219/1200/800", + hotspots: [ + { id: 'controls', x: 40, y: 50, label: "Dual Joystick Controls", info: "Ergonomic control center with panoramic visibility.", type: 'info' }, + { id: 'display', x: 60, y: 45, label: "Diagnostic Displays", info: "Real-time operational data and payload monitoring.", type: 'info' } + ] + }, + machinery: { + title: "Machinery House", + imageSrc: "https://picsum.photos/seed/1110967395/1200/800", + hotspots: [ + { id: 'hoist', x: 30, y: 60, label: "Hoist Motors", info: "Massive hoist motors designed for continuous high-torque output.", type: 'info' }, + { id: 'swing', x: 70, y: 55, label: "Swing Transmissions", info: "Planetary gearboxes for smooth, rapid swing cycles.", type: 'info' } + ] + }, + electrical: { + title: "Electrical Room", + imageSrc: "https://picsum.photos/seed/1776604305/1200/800", + hotspots: [ + { id: 'ac', x: 50, y: 40, label: "AC Drive Systems", info: "Advanced IGBT AC drives for precise motor control.", type: 'info' }, + { id: 'panels', x: 20, y: 50, label: "Power Distribution", info: "Climate-controlled electronics bays.", type: 'info' } + ] + }, + boom: { + title: "Boom & Dipper", + imageSrc: "https://picsum.photos/seed/691466050/1200/800", + hotspots: [ + { id: 'dipper', x: 50, y: 70, label: "High-Capacity Dipper", info: "Engineered for maximum payload and optimal dig geometry.", type: 'info' }, + { id: 'crowd', x: 50, y: 30, label: "Crowd Machinery", info: "Rack and pinion crowd for positive digging force.", type: 'info' } + ] + }, + maintenance: { + title: "Maintenance Deck", + imageSrc: "https://picsum.photos/seed/429735176/1200/800", + hotspots: [ + { id: 'lube', x: 40, y: 60, label: "Auto-Lube System", info: "Centralized lubrication for all major pivot points.", type: 'info' }, + { id: 'access', x: 80, y: 50, label: "Safe Access", info: "Wide walkways and tie-off points for maintenance personnel.", type: 'info' } + ] + } +}; + +export default function MachineWalkthroughPage() { + const [currentView, setCurrentView] = useState('exterior'); + const [activeInfo, setActiveInfo] = useState(null); + const [isTransitioning, setIsTransitioning] = useState(false); + + const handleNavigate = (target: ViewKey) => { + setIsTransitioning(true); + setActiveInfo(null); + setTimeout(() => { + setCurrentView(target); + setIsTransitioning(false); + }, 800); + }; + + const view = VIEWS[currentView]; + + return ( +
+
+ + + {view.title} + {currentView === 'exterior' && ( + + )} + + + +
+ +
+ + {!isTransitioning && view.hotspots.map((hotspot) => ( + + {hotspot.type === 'nav' ? ( + + ) : ( +
+ + + + {activeInfo === hotspot.id && ( + +

{hotspot.label}

+

{hotspot.info}

+
+ )} +
+
+ )} +
+ ))} +
+
+ + + {currentView !== 'exterior' && ( + +
+ {(Object.keys(VIEWS) as ViewKey[]).filter(k => k !== 'exterior').map((key) => ( + + ))} +
+ + +
+ )} +
+ +
+ + + {currentView === 'exterior' ? 'Virtual Tour' : 'Interior Compartment'} + +

+ {view.title} +

+
+
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/routes.ts b/src/routes.ts index 36d2cfa..eca0cb6 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -7,4 +7,5 @@ export interface Route { export const routes: Route[] = [ { path: '/', label: 'Home', pageFile: 'HomePage' }, { path: '/blog', label: 'Blog', pageFile: 'BlogPage' }, + { path: '/machine-walkthrough', label: 'Machine Walkthrough', pageFile: 'MachineWalkthroughPage' }, ];