- Add app/robots.ts (allow crawl, disallow admin/api, sitemap URL) - Expand sitemap to industries/[slug], accessories/[slug] and more static routes - Add public/llms.txt (AI summary), site.webmanifest, 1200x630 og.jpg - Layout: default OG/Twitter image, manifest, robots meta, Organization + WebSite JSON-LD - Robot pages: canonical, OG image, Product + BreadcrumbList JSON-LD; home canonical Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
248 lines
11 KiB
TypeScript
248 lines
11 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import { notFound } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { Navbar } from '@/components/Navbar';
|
|
import { FooterAndContact } from '@/components/FooterAndContact';
|
|
import { InquiryForm } from '@/components/robotics/InquiryForm';
|
|
import { RobotProductCard } from '@/components/robotics/RobotProductCard';
|
|
import { DemoCTA } from '@/components/robotics/DemoCTA';
|
|
import { ConfigureCTA } from '@/components/robotics/ConfigureCTA';
|
|
import { ProductSpecTable } from '@/components/robotics/ProductSpecTable';
|
|
import { ProductGallery } from '@/components/robotics/ProductGallery';
|
|
import { MotionSection } from '@/components/ui/MotionSection';
|
|
import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots';
|
|
import JsonLd from '@/components/JsonLd';
|
|
import { canonical, abs, productJsonLd, breadcrumbJsonLd } from '@/lib/seo';
|
|
|
|
type Params = { slug: string };
|
|
|
|
export function generateStaticParams() {
|
|
return ROBOTS.map((r) => ({ slug: r.slug }));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const robot = getRobotBySlug(slug);
|
|
if (!robot) return { title: 'Robot not found YS Lootah Robotics' };
|
|
const url = canonical(`/robots/${robot.slug}`);
|
|
const img = abs(robot.image);
|
|
const title = `${robot.brandLabel} ${robot.name} Available in Dubai | YS Lootah Robotics`;
|
|
const description = `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`;
|
|
return {
|
|
title,
|
|
description,
|
|
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'],
|
|
alternates: { canonical: url },
|
|
openGraph: { type: 'website', url, title, description, images: [{ url: img, alt: `${robot.brandLabel} ${robot.name}` }] },
|
|
twitter: { card: 'summary_large_image', title, description, images: [img] },
|
|
};
|
|
}
|
|
|
|
export default async function RobotDetailPage({ params }: { params: Promise<Params> }) {
|
|
const { slug } = await params;
|
|
const robot = getRobotBySlug(slug);
|
|
if (!robot) notFound();
|
|
|
|
const brand = BRANDS[robot.brand];
|
|
const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3);
|
|
|
|
const productLd = productJsonLd({
|
|
name: `${robot.brandLabel} ${robot.name}`,
|
|
description: robot.shortDescription,
|
|
image: robot.image,
|
|
brandName: brand.name,
|
|
path: `/robots/${robot.slug}`,
|
|
category: CATEGORY_LABELS[robot.category],
|
|
});
|
|
const crumbLd = breadcrumbJsonLd([
|
|
{ name: 'Home', path: '/' },
|
|
{ name: 'Robots', path: '/robots' },
|
|
{ name: robot.name, path: `/robots/${robot.slug}` },
|
|
]);
|
|
|
|
return (
|
|
<>
|
|
<JsonLd data={[productLd, crumbLd]} />
|
|
<Navbar />
|
|
|
|
<main style={{ paddingTop: 'clamp(6rem, 10vw, 8rem)', paddingBottom: 'clamp(4rem, 8vw, 6rem)' }}>
|
|
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(3rem, 6vw, 5rem)' }}>
|
|
<nav style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', fontSize: '0.78rem', letterSpacing: '0.14em', textTransform: 'uppercase', color: '#a8a8a8' }}>
|
|
<Link href="/" style={{ color: '#a8a8a8', textDecoration: 'none' }}>Home</Link>
|
|
<span>/</span>
|
|
<Link href="/robots/" style={{ color: '#a8a8a8', textDecoration: 'none' }}>Robots</Link>
|
|
<span>/</span>
|
|
<span style={{ color: '#e7e7e7' }}>{robot.name}</span>
|
|
</nav>
|
|
|
|
<section
|
|
style={{
|
|
display: 'grid',
|
|
gap: 'clamp(2rem, 5vw, 3rem)',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<div style={{ position: 'relative' }}>
|
|
<ProductGallery
|
|
images={[{ src: robot.image, alt: `${robot.brandLabel} ${robot.name}` }]}
|
|
accent={robot.accent}
|
|
/>
|
|
{robot.imageType === 'placeholder' && (
|
|
<span
|
|
style={{
|
|
position: 'absolute',
|
|
bottom: '0.75rem',
|
|
left: '0.75rem',
|
|
padding: '0.3rem 0.55rem',
|
|
borderRadius: 999,
|
|
background: 'rgba(5, 5, 5,0.6)',
|
|
border: '1px solid rgba(94, 94, 94, 0.2)',
|
|
color: '#a8a8a8',
|
|
fontSize: '0.62rem',
|
|
letterSpacing: '0.2em',
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
Brand visual placeholder
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
|
<span className="eyebrow" style={{ color: robot.accent }}>
|
|
<span style={{ width: 6, height: 6, borderRadius: 999, background: robot.accent }} /> {brand.name} · {CATEGORY_LABELS[robot.category]}
|
|
</span>
|
|
<h1 style={{ margin: 0, fontSize: 'clamp(2rem, 5vw, 3.4rem)', fontWeight: 300, lineHeight: 1.05, letterSpacing: '-0.03em' }}>
|
|
<span className="text-gradient" style={{ fontWeight: 500 }}>
|
|
{robot.name}
|
|
</span>
|
|
</h1>
|
|
<p style={{ margin: 0, color: '#e7e7e7', fontSize: '1.05rem', lineHeight: 1.5 }}>{robot.tagline}</p>
|
|
<p style={{ margin: 0, color: '#e7e7e7', fontSize: '1rem', lineHeight: 1.7 }}>{robot.longDescription}</p>
|
|
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.625rem' }}>
|
|
<a className="btn btn-primary" href="#inquire">Request quotation</a>
|
|
<a className="btn btn-ghost" href="/book-demo/">Book a live demo</a>
|
|
{robot.configureSlug && (
|
|
<a className="btn btn-outline" href={robot.configureSlug}>Configure for your business</a>
|
|
)}
|
|
{robot.officialUrl && (
|
|
<a className="btn btn-ghost" href={robot.officialUrl} target="_blank" rel="noopener noreferrer">
|
|
Brand site
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<MotionSection>
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gap: '1.5rem',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
|
|
}}
|
|
>
|
|
<Block title="Key features">
|
|
<ul style={{ margin: 0, paddingLeft: '1.1rem', display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
|
|
{robot.features.map((f) => (
|
|
<li key={f} style={{ color: '#e7e7e7', lineHeight: 1.6 }}>{f}</li>
|
|
))}
|
|
</ul>
|
|
</Block>
|
|
<Block title="Use cases">
|
|
<ul style={{ margin: 0, paddingLeft: '1.1rem', display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
|
|
{robot.useCases.map((u) => (
|
|
<li key={u} style={{ color: '#e7e7e7', lineHeight: 1.6 }}>{u}</li>
|
|
))}
|
|
</ul>
|
|
</Block>
|
|
<Block title="Technical specs">
|
|
<ProductSpecTable specs={robot.specs} />
|
|
</Block>
|
|
</div>
|
|
</MotionSection>
|
|
|
|
<MotionSection id="inquire">
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gap: '1.5rem',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(300px, 100%), 1fr))',
|
|
}}
|
|
>
|
|
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)' }}>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '1.5rem' }}>
|
|
<span className="eyebrow">Inquiry</span>
|
|
<h2 style={{ margin: 0, fontSize: 'clamp(1.5rem, 3vw, 2rem)', fontWeight: 400, letterSpacing: '-0.02em' }}>
|
|
<span className="text-gradient" style={{ fontWeight: 500 }}>
|
|
Request a quotation for {robot.name}.
|
|
</span>
|
|
</h2>
|
|
<p style={{ margin: 0, color: '#e7e7e7', lineHeight: 1.7 }}>
|
|
Tell us about your venue, timeline, and use case. We will respond with availability, configuration options, and pricing for the UAE.
|
|
</p>
|
|
</div>
|
|
<InquiryForm defaultRobot={`${robot.brandLabel} ${robot.name}`} defaultCategory={robot.category} intent="quotation" />
|
|
</div>
|
|
|
|
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
<span className="eyebrow">Talk to an advisor</span>
|
|
<h3 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Prefer a quick conversation?</h3>
|
|
<p style={{ margin: 0, color: '#e7e7e7', lineHeight: 1.7 }}>
|
|
Call our Dubai team or message us on WhatsApp we will share availability and demo slots for {robot.name}.
|
|
</p>
|
|
<a className="btn btn-ghost" href="tel:+971559482728">Call +971 55 948 2728</a>
|
|
<a className="btn btn-outline" href="https://wa.me/971559482728" target="_blank" rel="noopener noreferrer">WhatsApp us</a>
|
|
<a className="btn btn-ghost" href="mailto:info@yslootahrobotics.com">Email info@yslootahrobotics.com</a>
|
|
</div>
|
|
</div>
|
|
</MotionSection>
|
|
|
|
<MotionSection>
|
|
<ConfigureCTA />
|
|
</MotionSection>
|
|
|
|
{related.length > 0 && (
|
|
<MotionSection>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
|
<div>
|
|
<span className="eyebrow">Related robots</span>
|
|
<h2 style={{ margin: '0.5rem 0 0', fontSize: 'clamp(1.5rem, 3vw, 2rem)', fontWeight: 400 }}>
|
|
<span className="text-gradient" style={{ fontWeight: 500 }}>You might also consider…</span>
|
|
</h2>
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gap: '1.25rem',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
|
|
}}
|
|
>
|
|
{related.map((r) => (
|
|
<RobotProductCard key={r.id} robot={r} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</MotionSection>
|
|
)}
|
|
|
|
<DemoCTA title="Ready to bring this robot to your venue?" />
|
|
</div>
|
|
</main>
|
|
|
|
<FooterAndContact />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Block({ title, children }: { title: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="card" style={{ padding: '1.5rem' }}>
|
|
<div className="eyebrow" style={{ marginBottom: '1rem' }}>{title}</div>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|