Compare commits
5
Commits
38cc2025de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d39e857a | ||
|
|
78aec0e08d | ||
|
|
81358b0815 | ||
|
|
1d9d620d67 | ||
|
|
bcd7bd2c7b |
+1
-1
@@ -29,6 +29,6 @@ Hospitality, restaurants and cafes, hotels and resorts, shopping malls, healthca
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
- Email: info@yslootahrobotics.com
|
- Email: info@yslootahrobotics.com
|
||||||
- Phone: +971 55 948 2728, +971 4 349 9319
|
- Phone: +971 55 948 2728, +971 4 289 6095
|
||||||
- WhatsApp: https://wa.me/971559482728
|
- WhatsApp: https://wa.me/971559482728
|
||||||
- Location: Office 408, City Bay Business Center, Dubai, United Arab Emirates
|
- Location: Office 408, City Bay Business Center, Dubai, United Arab Emirates
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { pageMetadata } from '@/lib/seo';
|
|||||||
|
|
||||||
// The page itself is a client component and cannot export metadata.
|
// The page itself is a client component and cannot export metadata.
|
||||||
export const metadata: Metadata = pageMetadata({
|
export const metadata: Metadata = pageMetadata({
|
||||||
title: 'Unitree G1 3D Configurator',
|
title: 'Configure Your Robot and Request a UAE Quotation',
|
||||||
description:
|
description:
|
||||||
'Build your Unitree G1 in 3D. Choose persona, attire, colours and accessories, preview it live, then request a UAE quotation from YS Lootah Robotics.',
|
'Build your Unitree G1 in 3D. Choose persona, attire, colours and accessories, preview it live, then request a UAE quotation from YS Lootah Robotics.',
|
||||||
path: '/configure/',
|
path: '/configure/',
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function ContactPage() {
|
|||||||
|
|
||||||
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||||
<ContactRow label="Phone" value="+971 55 948 2728" href="tel:+971559482728" />
|
<ContactRow label="Phone" value="+971 55 948 2728" href="tel:+971559482728" />
|
||||||
<ContactRow label="Office" value="+971 4 349 9319" href="tel:+97143499319" />
|
<ContactRow label="Office" value="+971 4 289 6095" href="tel:+97142896095" />
|
||||||
<ContactRow label="Email" value="info@yslootahrobotics.com" href="mailto:info@yslootahrobotics.com" />
|
<ContactRow label="Email" value="info@yslootahrobotics.com" href="mailto:info@yslootahrobotics.com" />
|
||||||
<ContactRow label="WhatsApp" value="+971 55 948 2728" href="https://wa.me/971559482728" external />
|
<ContactRow label="WhatsApp" value="+971 55 948 2728" href="https://wa.me/971559482728" external />
|
||||||
<ContactRow label="Address" value="Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE" href="https://maps.google.com/?q=Showroom+No+03+Sheikh+Zayed+Rd+Trade+Center+Second+DIFC+Dubai" external />
|
<ContactRow label="Address" value="Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE" href="https://maps.google.com/?q=Showroom+No+03+Sheikh+Zayed+Rd+Trade+Center+Second+DIFC+Dubai" external />
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ import { Navbar } from '@/components/Navbar';
|
|||||||
import { FooterAndContact } from '@/components/FooterAndContact';
|
import { FooterAndContact } from '@/components/FooterAndContact';
|
||||||
import { MotionSection } from '@/components/ui/MotionSection';
|
import { MotionSection } from '@/components/ui/MotionSection';
|
||||||
import { CTAButton } from '@/components/ui/CTAButton';
|
import { CTAButton } from '@/components/ui/CTAButton';
|
||||||
import { INDUSTRIES, getIndustryBySlug } from '@/data/industries';
|
import { INDUSTRIES, UPCOMING_INDUSTRIES, getIndustryBySlug, getUpcomingIndustryBySlug, type Industry } from '@/data/industries';
|
||||||
|
import { ROBOTS } from '@/data/robots';
|
||||||
import { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages';
|
import { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages';
|
||||||
import { pageMetadata } from '@/lib/seo';
|
import { displayName, pageMetadata } from '@/lib/seo';
|
||||||
|
|
||||||
type Params = { slug: string };
|
type Params = { slug: string };
|
||||||
|
|
||||||
@@ -50,11 +51,21 @@ const ICONS: Record<IndustrySolution['iconKey'], React.ComponentType<{ size?: nu
|
|||||||
export const dynamicParams = false;
|
export const dynamicParams = false;
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return INDUSTRIES.map((i) => ({ slug: i.slug }));
|
return [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({ slug: i.slug }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
|
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
const soon = getUpcomingIndustryBySlug(slug);
|
||||||
|
if (soon?.upcoming) {
|
||||||
|
return pageMetadata({
|
||||||
|
title: soon.upcoming.seoTitle,
|
||||||
|
description: soon.upcoming.seoDescription,
|
||||||
|
path: `/industries/${soon.slug}`,
|
||||||
|
image: soon.image,
|
||||||
|
imageAlt: soon.imageAlt,
|
||||||
|
});
|
||||||
|
}
|
||||||
const industry = getIndustryBySlug(slug);
|
const industry = getIndustryBySlug(slug);
|
||||||
if (!industry) return {};
|
if (!industry) return {};
|
||||||
const content = INDUSTRY_PAGES[industry.id];
|
const content = INDUSTRY_PAGES[industry.id];
|
||||||
@@ -77,6 +88,8 @@ const TRUST_DEFAULT = [
|
|||||||
|
|
||||||
export default async function IndustryDetailPage({ params }: { params: Promise<Params> }) {
|
export default async function IndustryDetailPage({ params }: { params: Promise<Params> }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
const upcoming = getUpcomingIndustryBySlug(slug);
|
||||||
|
if (upcoming) return <ComingSoonIndustry industry={upcoming} />;
|
||||||
const industry = getIndustryBySlug(slug);
|
const industry = getIndustryBySlug(slug);
|
||||||
if (!industry) notFound();
|
if (!industry) notFound();
|
||||||
const content = INDUSTRY_PAGES[industry.id];
|
const content = INDUSTRY_PAGES[industry.id];
|
||||||
@@ -441,7 +454,160 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
|
|||||||
|
|
||||||
<FooterAndContact />
|
<FooterAndContact />
|
||||||
|
|
||||||
<style>{`
|
<style>{IP_STYLES}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short page for a sector whose full content is not written yet. Same layout
|
||||||
|
// and styles as the full page, built from the card copy and matching robots.
|
||||||
|
function ComingSoonIndustry({ industry }: { industry: Industry }) {
|
||||||
|
const accent = industry.accent;
|
||||||
|
const robots = ROBOTS.filter((r) => r.categories.some((c) => industry.matchCategories.includes(c))).slice(0, 6);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<main className="ip-main">
|
||||||
|
<div className="container-wide ip-stack">
|
||||||
|
<nav aria-label="Breadcrumb" className="ip-breadcrumbs">
|
||||||
|
<ol>
|
||||||
|
<li><Link href="/">Home</Link></li>
|
||||||
|
<li aria-hidden><ChevronRight size={12} /></li>
|
||||||
|
<li><Link href="/industries/">Industries</Link></li>
|
||||||
|
<li aria-hidden><ChevronRight size={12} /></li>
|
||||||
|
<li aria-current="page">{industry.name}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<MotionSection>
|
||||||
|
<section className="ip-hero" style={{ ['--acc' as string]: accent }}>
|
||||||
|
<div className="ip-hero-glow" aria-hidden />
|
||||||
|
<div className="ip-hero-glow-b" aria-hidden />
|
||||||
|
<div className="ip-hero-grid" aria-hidden />
|
||||||
|
|
||||||
|
<div className="ip-hero-inner">
|
||||||
|
<div className="ip-hero-copy">
|
||||||
|
<span className="eyebrow ip-hero-eyebrow">
|
||||||
|
<span className="ip-dot" />
|
||||||
|
Industry Solution · Coming soon
|
||||||
|
</span>
|
||||||
|
<h1 className="ip-hero-title">
|
||||||
|
<span className="text-gradient">Robots for {industry.name}</span>
|
||||||
|
</h1>
|
||||||
|
<p className="ip-hero-sub">
|
||||||
|
{industry.solution} A full guide for this sector is coming soon. Our Dubai team can already
|
||||||
|
advise on robots and arrange a demo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="ip-hero-actions">
|
||||||
|
<CTAButton href="/book-demo/" variant="primary" size="lg" arrow="up-right">
|
||||||
|
Book a Demo
|
||||||
|
</CTAButton>
|
||||||
|
<CTAButton href="/contact/" variant="secondary" size="lg" arrow="right">
|
||||||
|
Talk to Our Team
|
||||||
|
</CTAButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside className="ip-hero-visual">
|
||||||
|
<Image
|
||||||
|
src={industry.image}
|
||||||
|
alt={industry.imageAlt}
|
||||||
|
fill
|
||||||
|
sizes="(max-width: 920px) 100vw, 520px"
|
||||||
|
className="ip-hero-visual-img"
|
||||||
|
style={{ objectFit: 'cover', objectPosition: 'center' }}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<span className="ip-hero-visual-overlay" aria-hidden />
|
||||||
|
<div className="ip-hero-visual-meta">
|
||||||
|
<span className="ip-mono ip-live">
|
||||||
|
<span className="ip-live-dot" />Coming soon · UAE
|
||||||
|
</span>
|
||||||
|
<span className="ip-mono">Dubai Showroom · Live Demo</span>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</MotionSection>
|
||||||
|
|
||||||
|
<MotionSection>
|
||||||
|
<section className="ip-block">
|
||||||
|
<header className="ip-block-head">
|
||||||
|
<span className="eyebrow">At a glance</span>
|
||||||
|
<h2 className="ip-block-title">What robots change for {industry.name.toLowerCase()}.</h2>
|
||||||
|
</header>
|
||||||
|
<ul className="ip-problems" role="list">
|
||||||
|
{[industry.problem, industry.solution, industry.benefit].map((p, i) => (
|
||||||
|
<li key={i} className="ip-problem">
|
||||||
|
<span className="ip-problem-num">{String(i + 1).padStart(2, '0')}</span>
|
||||||
|
<p>{p}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</MotionSection>
|
||||||
|
|
||||||
|
{robots.length > 0 && (
|
||||||
|
<MotionSection>
|
||||||
|
<section className="ip-block" style={{ ['--acc' as string]: accent }}>
|
||||||
|
<header className="ip-block-head">
|
||||||
|
<span className="eyebrow">Suggested robots</span>
|
||||||
|
<h2 className="ip-block-title">Robots we would start with.</h2>
|
||||||
|
</header>
|
||||||
|
<div className="ip-robots">
|
||||||
|
{robots.map((r) => (
|
||||||
|
<article key={r.slug} className="ip-robot">
|
||||||
|
<div className="ip-robot-head">
|
||||||
|
<span className="ip-robot-brand">{r.brandLabel}</span>
|
||||||
|
<span className="ip-robot-arrow"><ArrowRight size={14} strokeWidth={1.8} /></span>
|
||||||
|
</div>
|
||||||
|
<h3>{displayName(r.brandLabel, r.name)}</h3>
|
||||||
|
<p>{r.tagline}</p>
|
||||||
|
<Link href={`/robots/${r.slug}/`} className="ip-robot-link">
|
||||||
|
View robot <ArrowRight size={13} strokeWidth={2} />
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</MotionSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<MotionSection>
|
||||||
|
<section className="ip-cta">
|
||||||
|
<div className="ip-cta-glow" aria-hidden />
|
||||||
|
<div className="ip-cta-grid" aria-hidden />
|
||||||
|
<div className="ip-cta-inner">
|
||||||
|
<div className="ip-cta-text">
|
||||||
|
<span className="eyebrow">Next step</span>
|
||||||
|
<h2 className="ip-cta-title">Planning robots for {industry.name.toLowerCase()}?</h2>
|
||||||
|
<p>Tell us about your venue and goals. Our Dubai team will recommend robots and arrange a live demo or quotation.</p>
|
||||||
|
</div>
|
||||||
|
<div className="ip-cta-actions">
|
||||||
|
<CTAButton href="/book-demo/" variant="primary" size="lg" arrow="up-right">
|
||||||
|
Book a Live Demo
|
||||||
|
</CTAButton>
|
||||||
|
<CTAButton href="/contact/" variant="secondary" size="lg" arrow="right">
|
||||||
|
Request UAE Quotation
|
||||||
|
</CTAButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</MotionSection>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<FooterAndContact />
|
||||||
|
|
||||||
|
<style>{IP_STYLES}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IP_STYLES = `
|
||||||
.ip-main {
|
.ip-main {
|
||||||
padding-top: clamp(5rem, 9vw, 7rem);
|
padding-top: clamp(5rem, 9vw, 7rem);
|
||||||
padding-bottom: clamp(2rem, 5vw, 4rem);
|
padding-bottom: clamp(2rem, 5vw, 4rem);
|
||||||
@@ -1098,7 +1264,4 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
|
|||||||
.ip-cta-actions .cta-btn { width: 100%; justify-content: space-between; }
|
.ip-cta-actions .cta-btn { width: 100%; justify-content: space-between; }
|
||||||
.ip-hero-actions .cta-btn { width: 100%; justify-content: space-between; }
|
.ip-hero-actions .cta-btn { width: 100%; justify-content: space-between; }
|
||||||
}
|
}
|
||||||
`}</style>
|
`;
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
+9
-4
@@ -16,11 +16,17 @@ import { FounderSection } from '@/components/robotics/FounderSection';
|
|||||||
import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection';
|
import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection';
|
||||||
import { MotionSection } from '@/components/ui/MotionSection';
|
import { MotionSection } from '@/components/ui/MotionSection';
|
||||||
import { FEATURED_ROBOTS } from '@/data/robots';
|
import { FEATURED_ROBOTS } from '@/data/robots';
|
||||||
|
import { pageMetadata } from '@/lib/seo';
|
||||||
|
|
||||||
|
// `absolute` keeps the layout template from adding the brand a second time.
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics',
|
...pageMetadata({
|
||||||
description:
|
title: 'Robot Shop and Configurator in the UAE',
|
||||||
'YS Lootah Robotics is the UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
|
description:
|
||||||
|
'YS Lootah Robotics is the UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
|
||||||
|
path: '/',
|
||||||
|
}),
|
||||||
|
title: { absolute: 'Robot Shop and Configurator in the UAE | YS Lootah Robotics' },
|
||||||
keywords: [
|
keywords: [
|
||||||
'YS Lootah Robotics',
|
'YS Lootah Robotics',
|
||||||
'robotics Dubai',
|
'robotics Dubai',
|
||||||
@@ -35,7 +41,6 @@ export const metadata: Metadata = {
|
|||||||
'robotics configuration UAE',
|
'robotics configuration UAE',
|
||||||
'robot sales and demo UAE',
|
'robot sales and demo UAE',
|
||||||
],
|
],
|
||||||
alternates: { canonical: '/' },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { MetadataRoute } from 'next';
|
import { MetadataRoute } from 'next';
|
||||||
import { ROBOTS } from '@/data/robots';
|
import { ROBOTS } from '@/data/robots';
|
||||||
import { INDUSTRIES } from '@/data/industries';
|
import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries';
|
||||||
import { ACCESSORIES } from '@/data/accessories';
|
import { ACCESSORIES } from '@/data/accessories';
|
||||||
import { SITE_URL } from '@/lib/seo';
|
import { SITE_URL } from '@/lib/seo';
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const industryRoutes: MetadataRoute.Sitemap = INDUSTRIES.map((i) => ({
|
const industryRoutes: MetadataRoute.Sitemap = [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({
|
||||||
url: u(`/industries/${i.slug}/`),
|
url: u(`/industries/${i.slug}/`),
|
||||||
lastModified: now,
|
lastModified: now,
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import { PaymentStep } from './checkout/PaymentStep';
|
|||||||
import { ReviewStep } from './checkout/ReviewStep';
|
import { ReviewStep } from './checkout/ReviewStep';
|
||||||
import { ConfirmationStep } from './checkout/ConfirmationStep';
|
import { ConfirmationStep } from './checkout/ConfirmationStep';
|
||||||
|
|
||||||
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
|
// Without a publishable key (local builds), loadStripe throws on every page that
|
||||||
|
// imports this module. Elements accepts null and the payment step stays empty.
|
||||||
|
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
|
||||||
|
const stripePromise = publishableKey ? loadStripe(publishableKey) : null;
|
||||||
|
|
||||||
const STEPS: { id: CheckoutStep; label: string }[] = [
|
const STEPS: { id: CheckoutStep; label: string }[] = [
|
||||||
{ id: 'shipping', label: 'Shipping' },
|
{ id: 'shipping', label: 'Shipping' },
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { CTAButton } from '@/components/ui/CTAButton';
|
|||||||
|
|
||||||
const CONTACT = {
|
const CONTACT = {
|
||||||
phonePrimary: '+971 55 948 2728',
|
phonePrimary: '+971 55 948 2728',
|
||||||
phoneSecondary: '+971 4 349 9319',
|
phoneSecondary: '+971 4 289 6095',
|
||||||
email: 'info@yslootahrobotics.com',
|
email: 'info@yslootahrobotics.com',
|
||||||
address: 'Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE',
|
address: 'Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE',
|
||||||
whatsapp: 'https://wa.me/971559482728',
|
whatsapp: 'https://wa.me/971559482728',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ACCESSORIES, GROUP_LABELS, type AccessoryGroup, type Accessory } from '@/data/accessories';
|
import { ACCESSORIES, GROUP_LABELS, type AccessoryGroup, type Accessory } from '@/data/accessories';
|
||||||
|
import { displayName } from '@/lib/seo';
|
||||||
|
|
||||||
type Filter = AccessoryGroup | 'all';
|
type Filter = AccessoryGroup | 'all';
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ function AccessoryCard({ accessory }: { accessory: Accessory }) {
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={detailHref}
|
href={detailHref}
|
||||||
aria-label={`View details for ${accessory.brandLabel} ${accessory.name}`}
|
aria-label={`View details for ${displayName(accessory.brandLabel, accessory.name)}`}
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
@@ -123,7 +124,7 @@ function AccessoryCard({ accessory }: { accessory: Accessory }) {
|
|||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
src={accessory.image}
|
src={accessory.image}
|
||||||
alt={`${accessory.brandLabel} ${accessory.name}`}
|
alt={displayName(accessory.brandLabel, accessory.name)}
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Link from 'next/link';
|
|||||||
import { PremiumButton } from '@/components/ui/PremiumButton';
|
import { PremiumButton } from '@/components/ui/PremiumButton';
|
||||||
import { CursorSpotlight } from '@/components/ui/CursorSpotlight';
|
import { CursorSpotlight } from '@/components/ui/CursorSpotlight';
|
||||||
import { FEATURED_ROBOTS } from '@/data/robots';
|
import { FEATURED_ROBOTS } from '@/data/robots';
|
||||||
|
import { displayName } from '@/lib/seo';
|
||||||
|
|
||||||
const HERO_STATS = [
|
const HERO_STATS = [
|
||||||
{ value: 'UAE', label: 'Access' },
|
{ value: 'UAE', label: 'Access' },
|
||||||
@@ -341,7 +342,7 @@ export function Hero3DRobotics() {
|
|||||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||||
<Image
|
<Image
|
||||||
src={r.image}
|
src={r.image}
|
||||||
alt={`${r.brandLabel} ${r.name}`}
|
alt={displayName(r.brandLabel, r.name)}
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 768px) 90vw, 620px"
|
sizes="(max-width: 768px) 90vw, 620px"
|
||||||
style={{ objectFit: 'contain', objectPosition: 'center bottom' }}
|
style={{ objectFit: 'contain', objectPosition: 'center bottom' }}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import Sparkles from 'lucide-react/dist/esm/icons/sparkles';
|
|||||||
import Landmark from 'lucide-react/dist/esm/icons/landmark';
|
import Landmark from 'lucide-react/dist/esm/icons/landmark';
|
||||||
import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';
|
import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
import type { LucideIcon } from 'lucide-react';
|
||||||
import { INDUSTRIES, type Industry } from '@/data/industries';
|
import { INDUSTRIES, UPCOMING_INDUSTRIES, type Industry } from '@/data/industries';
|
||||||
|
|
||||||
const ease = [0.16, 1, 0.3, 1] as const;
|
const ease = [0.16, 1, 0.3, 1] as const;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ const STATS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export function IndustryUseCases({ limit }: { limit?: number }) {
|
export function IndustryUseCases({ limit }: { limit?: number }) {
|
||||||
const list = limit ? INDUSTRIES.slice(0, limit) : INDUSTRIES;
|
const list = limit ? INDUSTRIES.slice(0, limit) : [...INDUSTRIES, ...UPCOMING_INDUSTRIES];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="iu" aria-label="Industries served">
|
<section className="iu" aria-label="Industries served">
|
||||||
@@ -232,7 +232,7 @@ function IndustryCard({ industry, delay }: { industry: Industry; delay: number }
|
|||||||
<h3 className="ic-title">{industry.name}</h3>
|
<h3 className="ic-title">{industry.name}</h3>
|
||||||
<p className="ic-desc">{blurb}</p>
|
<p className="ic-desc">{blurb}</p>
|
||||||
<span className="ic-cta">
|
<span className="ic-cta">
|
||||||
<span>Learn more</span>
|
<span>{industry.upcoming ? 'Coming soon' : 'Learn more'}</span>
|
||||||
<span className="ic-cta-arrow" aria-hidden>
|
<span className="ic-cta-arrow" aria-hidden>
|
||||||
<ArrowRight size={14} strokeWidth={2} />
|
<ArrowRight size={14} strokeWidth={2} />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Robot, CATEGORY_LABELS } from '@/data/robots';
|
import { Robot, CATEGORY_LABELS } from '@/data/robots';
|
||||||
|
import { displayName } from '@/lib/seo';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
robot: Robot;
|
robot: Robot;
|
||||||
@@ -24,7 +25,7 @@ export function RobotProductCard({ robot, priority = false }: Props) {
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={detailHref}
|
href={detailHref}
|
||||||
aria-label={`View details for ${robot.brandLabel} ${robot.name}`}
|
aria-label={`View details for ${displayName(robot.brandLabel, robot.name)}`}
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
@@ -50,7 +51,7 @@ export function RobotProductCard({ robot, priority = false }: Props) {
|
|||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
src={robot.image}
|
src={robot.image}
|
||||||
alt={`${robot.brandLabel} ${robot.name}`}
|
alt={displayName(robot.brandLabel, robot.name)}
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
+24
-3
@@ -12,6 +12,8 @@ export interface Industry {
|
|||||||
icon: 'building' | 'utensils' | 'hotel' | 'shopping-bag' | 'heart-pulse' | 'graduation-cap' | 'shield' | 'warehouse' | 'sparkles' | 'landmark';
|
icon: 'building' | 'utensils' | 'hotel' | 'shopping-bag' | 'heart-pulse' | 'graduation-cap' | 'shield' | 'warehouse' | 'sparkles' | 'landmark';
|
||||||
image: string;
|
image: string;
|
||||||
imageAlt: string;
|
imageAlt: string;
|
||||||
|
/** Sector with a short coming-soon page instead of a full INDUSTRY_PAGES entry. */
|
||||||
|
upcoming?: { seoTitle: string; seoDescription: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INDUSTRIES: Industry[] = [
|
export const INDUSTRIES: Industry[] = [
|
||||||
@@ -121,9 +123,9 @@ export const INDUSTRIES: Industry[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sectors with no detail page content yet (no entry in INDUSTRY_PAGES). They are
|
// Sectors with no full detail page content yet (no entry in INDUSTRY_PAGES).
|
||||||
// kept out of INDUSTRIES so no card, sitemap entry or route points at an empty
|
// Each renders a short coming-soon page from its `upcoming` copy. Move an entry
|
||||||
// page. Move an entry back up once its content is written.
|
// up into INDUSTRIES once its INDUSTRY_PAGES content is written.
|
||||||
export const UPCOMING_INDUSTRIES: Industry[] = [
|
export const UPCOMING_INDUSTRIES: Industry[] = [
|
||||||
{
|
{
|
||||||
id: 'events',
|
id: 'events',
|
||||||
@@ -137,6 +139,11 @@ export const UPCOMING_INDUSTRIES: Industry[] = [
|
|||||||
icon: 'sparkles',
|
icon: 'sparkles',
|
||||||
image: '/industries/cards/events.webp',
|
image: '/industries/cards/events.webp',
|
||||||
imageAlt: 'Robotics activation for UAE live events and brand experiences',
|
imageAlt: 'Robotics activation for UAE live events and brand experiences',
|
||||||
|
upcoming: {
|
||||||
|
seoTitle: 'Robots for Events and Exhibitions in the UAE',
|
||||||
|
seoDescription:
|
||||||
|
'Humanoid and quadruped robot showcases for UAE events, exhibitions and brand activations. Book a demo with YS Lootah Robotics in Dubai.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'smart-buildings',
|
id: 'smart-buildings',
|
||||||
@@ -150,6 +157,11 @@ export const UPCOMING_INDUSTRIES: Industry[] = [
|
|||||||
icon: 'building',
|
icon: 'building',
|
||||||
image: '/industries/cards/smart-buildings.webp',
|
image: '/industries/cards/smart-buildings.webp',
|
||||||
imageAlt: 'Cleaning robot in a modern UAE smart office building',
|
imageAlt: 'Cleaning robot in a modern UAE smart office building',
|
||||||
|
upcoming: {
|
||||||
|
seoTitle: 'Robots for Smart Buildings in the UAE',
|
||||||
|
seoDescription:
|
||||||
|
'Cleaning, delivery and patrol robots working as one fleet in UAE smart buildings. Talk to YS Lootah Robotics about a deployment in Dubai.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'government',
|
id: 'government',
|
||||||
@@ -163,9 +175,18 @@ export const UPCOMING_INDUSTRIES: Industry[] = [
|
|||||||
icon: 'landmark',
|
icon: 'landmark',
|
||||||
image: '/industries/unitree/utility-room.webp',
|
image: '/industries/unitree/utility-room.webp',
|
||||||
imageAlt: 'Quadruped robots deployed for UAE civic and emergency services',
|
imageAlt: 'Quadruped robots deployed for UAE civic and emergency services',
|
||||||
|
upcoming: {
|
||||||
|
seoTitle: 'Robots for Government and Public Sector in the UAE',
|
||||||
|
seoDescription:
|
||||||
|
'Humanoid, quadruped and service robots for UAE government and public sector programs, with local deployment and support from YS Lootah Robotics.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getIndustryBySlug(slug: string): Industry | undefined {
|
export function getIndustryBySlug(slug: string): Industry | undefined {
|
||||||
return INDUSTRIES.find((i) => i.slug === slug);
|
return INDUSTRIES.find((i) => i.slug === slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUpcomingIndustryBySlug(slug: string): Industry | undefined {
|
||||||
|
return UPCOMING_INDUSTRIES.find((i) => i.slug === slug);
|
||||||
|
}
|
||||||
|
|||||||
+9
-1
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { ROBOTS } from '@/data/robots';
|
import { ROBOTS } from '@/data/robots';
|
||||||
import { ACCESSORIES } from '@/data/accessories';
|
import { ACCESSORIES } from '@/data/accessories';
|
||||||
import { INDUSTRIES } from '@/data/industries';
|
import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries';
|
||||||
import { INDUSTRY_PAGES } from '@/data/industry-pages';
|
import { INDUSTRY_PAGES } from '@/data/industry-pages';
|
||||||
import { canonical, displayName, pageMetadata, siteName } from './seo';
|
import { canonical, displayName, pageMetadata, siteName } from './seo';
|
||||||
|
|
||||||
@@ -55,4 +55,12 @@ describe('industries', () => {
|
|||||||
expect(page.seoTitle).not.toContain(siteName);
|
expect(page.seoTitle).not.toContain(siteName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('gives every upcoming sector its own coming-soon title', () => {
|
||||||
|
for (const industry of UPCOMING_INDUSTRIES) {
|
||||||
|
expect(industry.upcoming?.seoTitle, industry.slug).toBeTruthy();
|
||||||
|
expect(industry.upcoming?.seoTitle).not.toContain(siteName);
|
||||||
|
expect(INDUSTRIES.some((i) => i.slug === industry.slug), industry.slug).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ export function pageMetadata({ title, description, path, image, imageAlt }: Page
|
|||||||
export const business = {
|
export const business = {
|
||||||
legalName: 'YS Lootah Robotics',
|
legalName: 'YS Lootah Robotics',
|
||||||
parent: 'Yousuf Saeed Lootah Investment Group',
|
parent: 'Yousuf Saeed Lootah Investment Group',
|
||||||
phones: ['+971559482728', '+97143499319'],
|
phones: ['+971559482728', '+97142896095'],
|
||||||
email: 'info@yslootahrobotics.com',
|
email: 'info@yslootahrobotics.com',
|
||||||
whatsapp: 'https://wa.me/971559482728',
|
whatsapp: 'https://wa.me/971559482728',
|
||||||
street: 'Showroom No 03, Sheikh Zayed Rd, Trade Center Second, DIFC',
|
street: 'Showroom No 03, Sheikh Zayed Rd, Trade Center Second, DIFC',
|
||||||
|
|||||||
Reference in New Issue
Block a user