Najjar 60d50e15e4
Some checks are pending
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions
feat(seo): robots.txt, richer sitemap, llms.txt, manifest, OG image, JSON-LD
- 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>
2026-07-06 13:45:38 +04:00

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>
);
}