feat: update RobotCanvas lighting and background styles; enhance button designs
Some checks are pending
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions

fix: update accessory images and compatibility details in accessories data

refactor: modify industry page hero image for education section

feat: add Bu Sunaidah page with sections for media, Instagram feed, and press coverage

feat: create InstagramGlyph icon component for consistent styling

chore: initialize Bu Sunaidah press and Instagram data structures
This commit is contained in:
Najjar\NajjarV02 2026-05-21 16:15:40 +04:00
parent e500409305
commit 5271914b16
24 changed files with 1607 additions and 247 deletions

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 870.93 196.18" style="enable-background:new 0 0 870.93 196.18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M337,15.2l-27.95,112.55c-1.49,5.99-2.66,8.17-4.39,10.38c-1.7,2.18-3.85,3.89-6.34,5.08
c-2.48,1.16-4.82,1.79-10.81,1.79h-19.01L300.73,15.2H337z"/>
<path class="st0" d="M147.34,49.56l-18.76,75.56c-1.72,6.91-3.04,9.41-5.05,11.95c-1.99,2.54-4.44,4.51-7.32,5.88
c-2.87,1.34-5.55,2.06-12.46,2.06H32.81c-6.91,0-9.23-0.73-11.44-2.06c-2.2-1.37-3.68-3.34-4.4-5.88
c-0.74-2.54-0.83-5.04,0.89-11.95l18.76-75.56h36.27l-17.93,72.22c-0.69,2.77-0.65,3.76-0.35,4.79c0.28,1.01,0.87,1.79,1.77,2.35
c0.86,0.53,1.8,0.82,4.57,0.82H83.2c2.77,0,3.85-0.29,4.98-0.82c1.17-0.55,2.15-1.34,2.93-2.35c0.81-1.03,1.34-2.02,2.03-4.79
l17.93-72.22H147.34z"/>
<path class="st0" d="M585.36,145.26h-39.8l-29.42-56.89h29.82c2.77,0,3.83-0.29,4.98-0.82c0.48-0.23,0.93-0.5,1.33-0.8
c0.61-0.44,1.13-0.95,1.61-1.55c0.79-1.01,1.32-2.02,2.01-4.79l1.77-7.14c0.69-2.77,0.65-3.76,0.36-4.77
c-0.18-0.59-0.45-1.13-0.84-1.55c-0.24-0.32-0.56-0.59-0.93-0.8c-0.88-0.53-1.8-0.82-4.57-0.82h-29.82l-19.79,79.68h-36.27
l23.7-95.45h93.33c6.01,0,8.02,0.61,9.92,1.78c1.9,1.18,3.19,2.9,3.81,5.1c0.11,0.34,0.19,0.71,0.25,1.07
c0.39,1.91,0.24,4.26-1.01,9.3l-4.94,19.89c-1.25,5.04-2.27,7.39-3.61,9.3c-0.24,0.36-0.5,0.73-0.78,1.07
c-1.71,2.2-3.85,3.91-6.34,5.1c-2.48,1.16-4.79,1.78-10.81,1.78h-7.71l0,0L585.36,145.26z"/>
<path class="st0" d="M645.32,85.83h74.45l-4.27,17.18h-66.49c-2.77,0-3.83,0.29-4.98,0.82c-1.15,0.55-2.15,1.34-2.93,2.35
c-0.8,1.01-1.34,2.02-2.03,4.79l-4.19,16.86h74.45l-4.27,17.18h-90.81c-6.93,0-9.25-0.73-11.44-2.06c-2.2-1.37-3.69-3.34-4.42-5.88
c-0.72-2.54-0.83-5.04,0.89-11.95l14.48-58.32c1.49-5.99,2.66-8.17,4.39-10.37c1.71-2.2,3.85-3.91,6.34-5.1
c2.48-1.16,4.81-1.78,10.81-1.78h93.46l-4.27,17.18h-66.49c-2.77,0-3.83,0.29-4.98,0.82c-1.15,0.55-2.15,1.34-2.93,2.35
c-0.8,1.01-1.34,2.02-2.03,4.79L645.32,85.83z"/>
<path class="st0" d="M774.26,85.83h74.45l-4.27,17.18h-66.49c-2.77,0-3.85,0.29-5,0.82c-1.15,0.55-2.13,1.34-2.93,2.35
c-0.79,1.01-1.32,2.02-2.01,4.79l-4.19,16.86h74.45l-4.27,17.18h-90.83c-6.93,0-9.25-0.73-11.44-2.06c-2.2-1.37-3.69-3.34-4.4-5.88
c-0.74-2.54-0.83-5.04,0.89-11.95l14.48-58.32c1.49-5.99,2.64-8.17,4.37-10.37c1.71-2.2,3.85-3.91,6.36-5.1
c2.47-1.16,4.79-1.78,10.79-1.78h93.48l-4.27,17.18h-66.49c-2.77,0-3.85,0.29-5,0.82c-1.15,0.55-2.13,1.34-2.93,2.35
c-0.79,1.01-1.32,2.02-2.01,4.79L774.26,85.83z"/>
<g>
<path class="st1" d="M275.72,49.56l-23.7,95.45h-25.08c-6.01,0-8.01-0.63-9.92-1.79c-0.25-0.15-0.48-0.31-0.7-0.5
c-1.29-0.95-2.25-2.23-2.85-3.74c0.01-0.04,0-0.06-0.02-0.08l-20.72-54.94l-15.16,61.05H141.3L165,49.56h25.08
c5.99,0,8.02,0.61,9.92,1.78c0.25,0.15,0.48,0.31,0.7,0.5c1.3,0.97,2.25,2.23,2.84,3.78c0.01,0.02,0.01,0.02,0.03,0.04
l20.72,54.94l15.16-61.03H275.72z"/>
</g>
<g>
<polygon class="st0" points="472.48,49.56 467.98,67.7 422.16,67.7 394.43,179.37 358.16,179.37 385.89,67.7 340.08,67.7
344.58,49.56 "/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

View File

@ -0,0 +1,606 @@
import type { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import Sparkles from 'lucide-react/dist/esm/icons/sparkles';
import Calendar from 'lucide-react/dist/esm/icons/calendar';
import MessageCircle from 'lucide-react/dist/esm/icons/message-circle';
import Mic from 'lucide-react/dist/esm/icons/mic';
import ChevronRight from 'lucide-react/dist/esm/icons/chevron-right';
import ExternalLink from 'lucide-react/dist/esm/icons/external-link';
import Play from 'lucide-react/dist/esm/icons/play';
import Newspaper from 'lucide-react/dist/esm/icons/newspaper';
import Quote from 'lucide-react/dist/esm/icons/quote';
import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact';
import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection';
import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton';
import { InstagramGlyph } from '@/components/icons/InstagramGlyph';
import {
BU_SUNAIDAH_HANDLE,
BU_SUNAIDAH_URL,
BU_SUNAIDAH_POSTS,
BU_SUNAIDAH_REEL,
} from '@/data/bu-sunaidah';
import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press';
export const metadata: Metadata = {
title: 'Bu Sunaidah · Emirati Robotics Persona | YS Lootah Robotics',
description:
'Meet Bu Sunaidah — the Emirati culture-inspired robotics character from YS Lootah Robotics. Media activations, exhibitions, and community storytelling across the UAE.',
alternates: { canonical: '/bu-sunaidah/' },
openGraph: {
title: 'Bu Sunaidah · Emirati Robotics Persona',
description: 'Emirati culture-inspired robotics character — UAE media, exhibitions, community.',
url: '/bu-sunaidah/',
type: 'website',
},
};
const PILLARS = [
{
icon: Sparkles,
title: 'Cultural identity',
body: 'Bu Sunaidah celebrates UAE heritage in form, voice, and presence — a familiar character built on advanced robotics.',
},
{
icon: Calendar,
title: 'Event activations',
body: 'Designed for exhibitions, brand launches, and government innovation programs — a memorable hero of the room.',
},
{
icon: Mic,
title: 'Media presence',
body: 'A modern face that media outlets, influencers, and audiences can engage with across UAE storytelling moments.',
},
{
icon: MessageCircle,
title: 'Community',
body: 'Connects with schools, makerspaces, and innovation hubs to spark interest in robotics among the next generation.',
},
];
const ACCENT = '#3a55c4';
function formatDate(iso: string): string {
try {
return new Date(iso).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric',
});
} catch {
return iso;
}
}
export default function BuSunaidahPage() {
return (
<>
<Navbar />
<main className="bs-main">
<div className="container-wide bs-stack" style={{ ['--acc' as string]: ACCENT }}>
{/* BREADCRUMB */}
<nav aria-label="Breadcrumb" className="bs-crumbs">
<ol>
<li><Link href="/">Home</Link></li>
<li aria-hidden><ChevronRight size={12} /></li>
<li aria-current="page">Bu Sunaidah</li>
</ol>
</nav>
{/* HERO — reuses the existing section component */}
<MotionSection>
<BuSunaidahSection />
</MotionSection>
{/* REEL */}
<MotionSection>
<section className="bs-block">
<header className="bs-head">
<span className="eyebrow">The reel</span>
<h2 className="bs-title">Bu Sunaidah in motion.</h2>
</header>
<div className="bs-reel">
<span className="bs-reel-glow" aria-hidden />
<span className="bs-corner bs-corner-tl" aria-hidden />
<span className="bs-corner bs-corner-tr" aria-hidden />
<span className="bs-corner bs-corner-bl" aria-hidden />
<span className="bs-corner bs-corner-br" aria-hidden />
{BU_SUNAIDAH_REEL == null ? (
<div className="bs-reel-placeholder">
<Image
src="/images/robots/unitree-g1.png"
alt="Bu Sunaidah persona — reel preview"
fill
sizes="(max-width: 900px) 100vw, 980px"
className="bs-reel-poster"
style={{ objectFit: 'contain', padding: '8%' }}
priority={false}
/>
<div className="bs-reel-overlay" aria-hidden />
<div className="bs-reel-cta">
<span className="bs-reel-play" aria-hidden>
<Play size={20} strokeWidth={1.8} />
</span>
<div>
<span className="bs-reel-eyebrow">Reel · coming soon</span>
<span className="bs-reel-caption">
A cinematic look at Bu Sunaidah across UAE activations.
</span>
</div>
</div>
</div>
) : BU_SUNAIDAH_REEL.kind === 'mp4' ? (
<video
className="bs-reel-media"
controls
preload="metadata"
poster={BU_SUNAIDAH_REEL.poster}
src={BU_SUNAIDAH_REEL.src}
/>
) : (
<iframe
className="bs-reel-media"
src={
BU_SUNAIDAH_REEL.kind === 'youtube'
? `https://www.youtube-nocookie.com/embed/${BU_SUNAIDAH_REEL.id}?rel=0&modestbranding=1`
: `https://player.vimeo.com/video/${BU_SUNAIDAH_REEL.id}?dnt=1`
}
title="Bu Sunaidah showreel"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
/>
)}
</div>
</section>
</MotionSection>
{/* PERSONA PILLARS */}
<MotionSection>
<section className="bs-block">
<header className="bs-head">
<span className="eyebrow">The persona</span>
<h2 className="bs-title">What Bu Sunaidah stands for.</h2>
</header>
<div className="bs-pillars">
{PILLARS.map((p) => {
const Icon = p.icon;
return (
<article key={p.title} className="bs-pillar">
<span className="bs-pillar-icon" aria-hidden>
<Icon size={18} strokeWidth={1.6} />
</span>
<h3>{p.title}</h3>
<p>{p.body}</p>
</article>
);
})}
</div>
</section>
</MotionSection>
{/* INSTAGRAM FEED */}
<MotionSection>
<section className="bs-block">
<header className="bs-head bs-head-row">
<div>
<span className="eyebrow">@{BU_SUNAIDAH_HANDLE}</span>
<h2 className="bs-title">Latest from Instagram.</h2>
</div>
<a
className="bs-ig-link"
href={BU_SUNAIDAH_URL}
target="_blank"
rel="noopener noreferrer"
>
<InstagramGlyph size={14} />
<span>View full profile</span>
<ExternalLink size={12} strokeWidth={2} />
</a>
</header>
{BU_SUNAIDAH_POSTS.length > 0 ? (
<div className="bs-ig-grid">
{BU_SUNAIDAH_POSTS.map((post) => {
const kind = post.kind ?? 'p';
const src = `https://www.instagram.com/${kind}/${post.shortcode}/embed/`;
return (
<div key={post.shortcode} className="bs-ig-tile">
<iframe
src={src}
allow="encrypted-media; clipboard-write"
loading="lazy"
title={post.caption ?? `Instagram post by @${BU_SUNAIDAH_HANDLE}`}
scrolling="no"
frameBorder={0}
/>
</div>
);
})}
</div>
) : (
<a
className="bs-ig-fallback"
href={BU_SUNAIDAH_URL}
target="_blank"
rel="noopener noreferrer"
aria-label={`Open @${BU_SUNAIDAH_HANDLE} on Instagram`}
>
<span className="bs-ig-fallback-glow" aria-hidden />
<span className="bs-ig-fallback-grid" aria-hidden />
<div className="bs-ig-fallback-inner">
<span className="bs-ig-fallback-mark">
<InstagramGlyph size={22} />
</span>
<div>
<span className="bs-ig-fallback-handle">@{BU_SUNAIDAH_HANDLE}</span>
<span className="bs-ig-fallback-cta">
Follow Bu Sunaidah for behind-the-scenes activations, events, and stories
<ExternalLink size={13} strokeWidth={2} />
</span>
</div>
</div>
</a>
)}
</section>
</MotionSection>
{/* PRESS & MEDIA */}
{BU_SUNAIDAH_PRESS.length > 0 && (
<MotionSection>
<section className="bs-block">
<header className="bs-head">
<span className="eyebrow">
<Newspaper size={11} strokeWidth={1.8} style={{ marginRight: 6, verticalAlign: -1 }} />
As seen in
</span>
<h2 className="bs-title">Media coverage.</h2>
</header>
<ul className="bs-press-wall" role="list">
{BU_SUNAIDAH_PRESS.map((p) => {
const inner = p.logoSrc ? (
<Image
src={p.logoSrc}
alt={`${p.outlet} logo`}
width={140}
height={48}
style={{ width: 'auto', height: '32px', objectFit: 'contain' }}
/>
) : (
<span className="bs-press-fallback">{p.outlet}</span>
);
return (
<li key={`${p.outlet}-${p.date ?? ''}`} className="bs-press-cell">
{p.url ? (
<a
href={p.url}
target="_blank"
rel="noopener noreferrer"
aria-label={`${p.outlet} — open coverage`}
>
{inner}
</a>
) : (
inner
)}
</li>
);
})}
</ul>
{BU_SUNAIDAH_PRESS.some((p) => p.quote) && (
<div className="bs-press-quotes">
{BU_SUNAIDAH_PRESS.filter((p) => p.quote).map((p) => (
<blockquote
key={`q-${p.outlet}-${p.date ?? ''}`}
className="bs-press-quote"
>
<span className="bs-press-quote-mark" aria-hidden>
<Quote size={14} strokeWidth={1.8} />
</span>
<p>{p.quote}</p>
<footer>
{p.url ? (
<a href={p.url} target="_blank" rel="noopener noreferrer">
{p.outlet}
{p.date && <span className="bs-press-date"> · {formatDate(p.date)}</span>}
</a>
) : (
<span>
{p.outlet}
{p.date && <span className="bs-press-date"> · {formatDate(p.date)}</span>}
</span>
)}
</footer>
</blockquote>
))}
</div>
)}
</section>
</MotionSection>
)}
{/* CTA */}
<MotionSection>
<section className="bs-cta">
<div className="bs-cta-glow" aria-hidden />
<div className="bs-cta-grid" aria-hidden />
<div className="bs-cta-inner">
<div className="bs-cta-text">
<span className="eyebrow">Book the persona</span>
<h2 className="bs-cta-title">Bring Bu Sunaidah to your venue.</h2>
<p>
Tell us the date, audience, and format exhibition booth, opening night, school
visit, or media moment. Our team will design the activation and confirm the
arrangement.
</p>
</div>
<div className="bs-cta-actions">
<CTAButton href="/contact/" variant="primary" size="lg" arrow="up-right">
Book Bu Sunaidah
</CTAButton>
<CTAButton
href={BU_SUNAIDAH_URL}
external
variant="secondary"
size="lg"
arrow="up-right"
ariaLabel={`Open @${BU_SUNAIDAH_HANDLE} on Instagram`}
>
Follow on Instagram
</CTAButton>
</div>
</div>
</section>
</MotionSection>
</div>
</main>
<FooterAndContact />
<style>{`
.bs-main {
padding-top: clamp(5rem, 9vw, 7rem);
padding-bottom: clamp(2rem, 5vw, 4rem);
}
.bs-stack {
display: flex;
flex-direction: column;
gap: clamp(2.5rem, 5vw, 4.25rem);
}
/* BREADCRUMB */
.bs-crumbs ol {
list-style: none; margin: 0; padding: 0;
display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase; color: #8891C7;
}
.bs-crumbs a { color: #8891C7; text-decoration: none; transition: color 0.25s; }
.bs-crumbs a:hover { color: #FFFFFF; }
.bs-crumbs li[aria-current="page"] { color: #FFFFFF; }
.bs-crumbs li[aria-hidden] { display: inline-flex; align-items: center; color: #4a4f63; }
/* BLOCKS */
.bs-block { display: flex; flex-direction: column; gap: clamp(1.2rem, 2.2vw, 1.75rem); }
.bs-head { display: flex; flex-direction: column; gap: 0.55rem; max-width: 760px; }
.bs-head-row {
flex-direction: row; max-width: none; align-items: flex-end;
justify-content: space-between; gap: 1rem; flex-wrap: wrap;
}
.bs-title {
margin: 0;
font-size: clamp(1.55rem, 3vw, 2.1rem);
font-weight: 400; letter-spacing: -0.02em; color: #FFFFFF; line-height: 1.15;
}
/* PILLARS */
.bs-pillars {
display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem;
}
@media (min-width: 640px) { .bs-pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .bs-pillars { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.bs-pillar {
display: flex; flex-direction: column; gap: 0.6rem;
padding: 1.2rem 1.2rem 1.25rem;
border-radius: 18px;
border: 1px solid rgba(222, 224, 240, 0.10);
background:
radial-gradient(ellipse 80% 60% at 100% 0%, color-mix(in srgb, var(--acc) 16%, transparent), transparent 60%),
linear-gradient(180deg, rgba(22, 21, 30, 0.9), rgba(10, 10, 14, 0.96));
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.bs-pillar:hover {
transform: translateY(-3px);
border-color: rgba(74, 102, 216, 0.42);
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.5), 0 0 24px rgba(58, 85, 196, 0.18);
}
.bs-pillar-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 40px; height: 40px; border-radius: 12px;
color: color-mix(in srgb, var(--acc) 82%, white);
border: 1px solid color-mix(in srgb, var(--acc) 35%, transparent);
background: color-mix(in srgb, var(--acc) 12%, rgba(14, 13, 18, 0.6));
}
.bs-pillar h3 {
margin: 0; font-size: 1.02rem; font-weight: 600;
color: #FFFFFF; letter-spacing: -0.005em;
}
.bs-pillar p { margin: 0; color: #C9CCDE; font-size: 0.88rem; line-height: 1.55; }
/* INSTAGRAM LINK */
.bs-ig-link {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 0.55rem 0.85rem;
border-radius: 999px;
border: 1px solid rgba(222, 224, 240, 0.22);
background: rgba(14, 13, 18, 0.55);
color: #DEE0F0;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
text-decoration: none;
backdrop-filter: blur(10px);
transition: border-color 0.25s, color 0.25s, background 0.25s;
}
.bs-ig-link:hover {
border-color: rgba(222, 224, 240, 0.55);
color: #FFFFFF;
background: rgba(58, 85, 196, 0.18);
}
/* INSTAGRAM EMBED GRID */
.bs-ig-grid {
display: grid; gap: 1rem;
grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 640px) { .bs-ig-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .bs-ig-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.bs-ig-tile {
position: relative;
border-radius: 18px;
overflow: hidden;
border: 1px solid rgba(222, 224, 240, 0.12);
background: rgba(14, 13, 18, 0.6);
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45);
aspect-ratio: 4 / 5;
}
.bs-ig-tile iframe {
width: 100%; height: 100%; display: block; border: 0;
background: #0a0a0e;
}
/* INSTAGRAM FALLBACK CARD */
.bs-ig-fallback {
position: relative;
display: block;
overflow: hidden;
border-radius: 22px;
border: 1px solid rgba(74, 102, 216, 0.28);
background:
radial-gradient(ellipse 70% 100% at 0% 0%, rgba(58, 85, 196, 0.22), transparent 55%),
radial-gradient(ellipse 60% 80% at 100% 100%, rgba(136, 145, 199, 0.16), transparent 60%),
linear-gradient(135deg, rgba(14, 13, 22, 0.95), rgba(6, 6, 10, 0.97));
text-decoration: none;
color: inherit;
box-shadow: 0 22px 50px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04);
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.35s, box-shadow 0.35s;
}
.bs-ig-fallback:hover {
transform: translateY(-3px);
border-color: rgba(74, 102, 216, 0.55);
box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6), 0 0 32px rgba(58, 85, 196, 0.28);
}
.bs-ig-fallback-glow {
position: absolute;
width: 440px; height: 440px; border-radius: 999px;
top: -180px; left: -160px;
background: radial-gradient(circle, rgba(74, 102, 216, 0.4), transparent 70%);
filter: blur(100px);
pointer-events: none;
}
.bs-ig-fallback-grid {
position: absolute; inset: 0;
background:
linear-gradient(rgba(74, 102, 216, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(74, 102, 216, 0.05) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 60% 80% at 70% 50%, #000 25%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse 60% 80% at 70% 50%, #000 25%, transparent 80%);
pointer-events: none;
}
.bs-ig-fallback-inner {
position: relative; z-index: 1;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 1rem;
align-items: center;
padding: clamp(1.4rem, 3vw, 2rem);
}
.bs-ig-fallback-mark {
display: inline-flex; align-items: center; justify-content: center;
width: 54px; height: 54px; border-radius: 16px;
color: #FFFFFF;
border: 1px solid rgba(222, 224, 240, 0.25);
background:
radial-gradient(ellipse 60% 60% at 30% 30%, rgba(255, 84, 165, 0.55), transparent 60%),
radial-gradient(ellipse 60% 60% at 70% 70%, rgba(255, 165, 84, 0.45), transparent 60%),
linear-gradient(135deg, rgba(58, 85, 196, 0.6), rgba(20, 20, 28, 0.85));
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5);
}
.bs-ig-fallback-handle {
display: block;
font-size: 0.7rem;
letter-spacing: 0.24em;
text-transform: uppercase;
color: #8891C7;
font-weight: 700;
margin-bottom: 0.2rem;
}
.bs-ig-fallback-cta {
display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.55rem;
color: #FFFFFF; font-size: 1.05rem; font-weight: 500; letter-spacing: -0.005em;
line-height: 1.4;
}
/* CTA */
.bs-cta {
position: relative; overflow: hidden;
border-radius: 26px;
border: 1px solid rgba(74, 102, 216, 0.26);
background:
radial-gradient(ellipse 60% 100% at 100% 0%, rgba(58, 85, 196, 0.22), transparent 60%),
linear-gradient(135deg, rgba(18, 16, 28, 0.95), rgba(8, 8, 12, 0.97));
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.bs-cta-glow {
position: absolute; width: 480px; height: 480px; border-radius: 999px;
top: -160px; left: -160px;
background: radial-gradient(circle, rgba(74, 102, 216, 0.45), transparent 70%);
filter: blur(100px); opacity: 0.55; pointer-events: none;
}
.bs-cta-grid {
position: absolute; inset: 0;
background:
linear-gradient(rgba(74, 102, 216, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(74, 102, 216, 0.05) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 60% 90% at 70% 50%, #000 25%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse 60% 90% at 70% 50%, #000 25%, transparent 80%);
pointer-events: none;
}
.bs-cta-inner {
position: relative; z-index: 1;
display: grid; grid-template-columns: minmax(0, 1fr);
gap: clamp(1.3rem, 3vw, 2rem);
padding: clamp(1.7rem, 3.6vw, 2.7rem);
align-items: center;
}
@media (min-width: 900px) {
.bs-cta-inner { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
}
.bs-cta-text { display: flex; flex-direction: column; gap: 0.65rem; max-width: 560px; }
.bs-cta-title {
margin: 0;
font-size: clamp(1.65rem, 3vw, 2.25rem);
font-weight: 400; letter-spacing: -0.02em; color: #FFFFFF; line-height: 1.1;
}
.bs-cta-text p { margin: 0; color: #DEE0F0; font-size: 0.95rem; line-height: 1.65; }
.bs-cta-actions {
display: flex; flex-wrap: wrap; gap: 0.55rem; justify-content: flex-start;
}
@media (min-width: 900px) {
.bs-cta-actions { justify-content: flex-end; }
}
@media (max-width: 520px) {
.bs-cta-actions .cta-btn { width: 100%; justify-content: space-between; }
}
`}</style>
</>
);
}

View File

@ -3,48 +3,660 @@
import { ConfiguratorSection } from '@/components/ConfiguratorSection'; import { ConfiguratorSection } from '@/components/ConfiguratorSection';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { CTAButton } from '@/components/ui/CTAButton';
import Palette from 'lucide-react/dist/esm/icons/palette';
import Shirt from 'lucide-react/dist/esm/icons/shirt';
import Wand2 from 'lucide-react/dist/esm/icons/wand-sparkles';
import Eye from 'lucide-react/dist/esm/icons/eye';
import Send from 'lucide-react/dist/esm/icons/send';
import Headphones from 'lucide-react/dist/esm/icons/headphones';
import MapPin from 'lucide-react/dist/esm/icons/map-pin';
import Clock from 'lucide-react/dist/esm/icons/clock';
import Boxes from 'lucide-react/dist/esm/icons/boxes';
import CheckCircle2 from 'lucide-react/dist/esm/icons/check-circle-2';
const STATS = [
{ value: '5+', label: 'Personas' },
{ value: '10+', label: 'Attires' },
{ value: 'UAE', label: 'Local support' },
{ value: 'Dubai', label: 'Showroom' },
];
const STEPS = [
{
n: '01',
Icon: Shirt,
title: 'Pick persona & attire',
body: 'Choose role and outfit — kandura, business suit, doctor, security guard, hospitality vest, and more.',
},
{
n: '02',
Icon: Palette,
title: 'Customize colors & accessories',
body: 'Adjust accent colors and add accessories. Preview live in 3D as you build the configuration.',
},
{
n: '03',
Icon: Send,
title: 'Request UAE quotation',
body: 'Save and share, then submit to YS Lootah Robotics for pricing, lead time, and a live demo.',
},
];
const POST_CONFIG = [
{
Icon: Eye,
title: 'Share your build',
body: 'Copy a unique link that captures every choice. Send it to your team or stakeholders for review.',
},
{
Icon: Wand2,
title: 'Tailored quotation',
body: 'Our Dubai team confirms availability, lead time, training, and on-site deployment scope.',
},
{
Icon: Headphones,
title: 'Live demo',
body: 'Book a Dubai showroom slot or an on-site visit to see the robot in your space before you commit.',
},
];
const FAQ = [
{
q: 'Which robot can I configure here?',
a: 'The configurator focuses on the Unitree G1 humanoid — the most personalized robot in our catalog. Quadrupeds, delivery, and cleaning robots are configured through a direct consultation with our Dubai team.',
},
{
q: 'How long does delivery take in the UAE?',
a: 'Standard lead time runs 610 weeks from confirmed order, depending on persona kit and accessory availability. Demo units in Dubai are available immediately for live testing.',
},
{
q: 'Can the persona be fully custom?',
a: 'Yes. Beyond the presets, we tailor attire, branding, voice persona, and behaviors per venue. Custom configurations are scoped with our team after you share your initial build.',
},
{
q: 'Is training and support included?',
a: 'Every deployment includes on-site setup, operator training, and post-deployment support from our UAE-based team. Service contracts cover updates, repairs, and consumables.',
},
];
export default function ConfigurePage() { export default function ConfigurePage() {
return ( return (
<> <>
<Navbar /> <Navbar />
<header <main className="cfg-main">
style={{ {/* HERO */}
position: 'relative', <section className="cfg-hero" aria-labelledby="cfg-hero-title">
paddingTop: 'clamp(6rem, 10vw, 8rem)', <div className="cfg-hero-glow" aria-hidden />
paddingBottom: 'clamp(1.5rem, 3vw, 2.5rem)', <div className="cfg-hero-glow-b" aria-hidden />
paddingLeft: 'clamp(1rem, 4vw, 2rem)', <div className="cfg-hero-grid" aria-hidden />
paddingRight: 'clamp(1rem, 4vw, 2rem)',
background: <div className="container-wide cfg-hero-inner">
'radial-gradient(ellipse 60% 80% at 50% 0%, rgba(39, 63, 148,0.12), transparent 60%), linear-gradient(180deg, #050505 0%, #030303 100%)', <div className="cfg-hero-copy">
borderBottom: '1px solid rgba(39, 63, 148,0.12)', <span className="eyebrow cfg-hero-eyebrow">
}} <span className="cfg-dot" />
> Configurator · Unitree G1 · Dubai
<div style={{ maxWidth: 1320, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}> </span>
<span className="eyebrow">Configurator · Unitree G1 humanoid · Dubai</span> <h1 id="cfg-hero-title" className="cfg-hero-title">
<h1 <span className="text-gradient">Configure your robot.</span>
style={{
margin: 0,
fontSize: 'clamp(1.6rem, 3.6vw, 2.4rem)',
fontWeight: 300,
letterSpacing: '-0.02em',
lineHeight: 1.1,
}}
>
<span className="text-gradient" style={{ fontWeight: 500 }}>Configure your robot.</span>
</h1> </h1>
<p style={{ margin: 0, color: '#8891C7', fontSize: 'clamp(0.85rem, 1.6vw, 0.95rem)', lineHeight: 1.6, maxWidth: 720 }}> <p className="cfg-hero-sub">
Choose persona, attire, colors, and accessories visualize your Unitree G1 humanoid before you request a quotation from YS Lootah Robotics. Choose persona, attire, colors, and accessories. Preview your Unitree G1 humanoid live in 3D, save your build, then request a UAE quotation from YS Lootah Robotics.
</p> </p>
<ul className="cfg-hero-checks" role="list">
{[
'Live 3D preview as you build',
'Shareable configuration link',
'Dubai-based sales, support, and deployment',
].map((c) => (
<li key={c}>
<CheckCircle2 size={14} strokeWidth={2.2} />
{c}
</li>
))}
</ul>
<div className="cfg-hero-actions">
<CTAButton href="#configurator" variant="primary" size="lg" arrow="right">
Start Configuring
</CTAButton>
<CTAButton href="/book-demo/" variant="secondary" size="lg" arrow="right">
Book a Live Demo
</CTAButton>
</div> </div>
</div>
<aside className="cfg-hero-stats" aria-label="Configurator stats">
{STATS.map((s) => (
<div key={s.label} className="cfg-stat">
<span className="cfg-stat-value">{s.value}</span>
<span className="cfg-stat-label">{s.label}</span>
</div>
))}
</aside>
</div>
</section>
{/* PROCESS STEPS */}
<section className="cfg-steps container-wide" aria-label="Configuration process">
<header className="cfg-section-head">
<span className="eyebrow">How it works</span>
<h2>Three steps from idea to UAE quotation.</h2>
</header>
<ol className="cfg-steps-grid" role="list">
{STEPS.map(({ n, Icon, title, body }) => (
<li key={n} className="cfg-step">
<span className="cfg-step-num">{n}</span>
<span className="cfg-step-icon"><Icon size={20} strokeWidth={1.6} /></span>
<h3>{title}</h3>
<p>{body}</p>
</li>
))}
</ol>
</section>
{/* CONFIGURATOR FRAME */}
<section id="configurator" className="cfg-frame container-wide" aria-label="3D configurator">
<header className="cfg-section-head cfg-frame-head">
<div>
<span className="eyebrow">Live 3D · Build it your way</span>
<h2>Customize the Unitree G1 humanoid.</h2>
</div>
<a href="/robots/unitree-g1/" className="cfg-frame-spec">
View G1 spec sheet
<span aria-hidden></span>
</a>
</header> </header>
<div style={{ minHeight: '100vh', background: '#ffffff' }}> <div className="cfg-frame-shell">
<div className="cfg-frame-bar">
<span className="cfg-mono"><span className="cfg-live-dot" />Live preview</span>
<span className="cfg-mono cfg-mono-mute">Unitree G1 · Dubai showroom</span>
</div>
<ConfiguratorSection /> <ConfiguratorSection />
</div> </div>
</section>
{/* POST-CONFIG */}
<section className="cfg-post container-wide" aria-label="What happens after configuring">
<header className="cfg-section-head">
<span className="eyebrow">After you configure</span>
<h2>What happens next.</h2>
</header>
<div className="cfg-post-grid">
{POST_CONFIG.map(({ Icon, title, body }) => (
<article key={title} className="cfg-post-card">
<span className="cfg-post-icon"><Icon size={18} strokeWidth={1.6} /></span>
<h3>{title}</h3>
<p>{body}</p>
</article>
))}
</div>
</section>
{/* PILLARS */}
<section className="cfg-pillars container-wide" aria-label="Service pillars">
<ul className="cfg-pillars-grid" role="list">
<li><MapPin size={14} strokeWidth={1.7} /><div><span>UAE-ready</span><small>Deployment across the UAE</small></div></li>
<li><Headphones size={14} strokeWidth={1.7} /><div><span>Dubai support</span><small>Local robotics team</small></div></li>
<li><Boxes size={14} strokeWidth={1.7} /><div><span>Tailored configuration</span><small>Persona, attire, behaviour</small></div></li>
<li><Clock size={14} strokeWidth={1.7} /><div><span>610 week lead time</span><small>From confirmed order</small></div></li>
</ul>
</section>
{/* FAQ */}
<section className="cfg-faq container-wide" aria-label="Configuration FAQ">
<header className="cfg-section-head">
<span className="eyebrow">Questions</span>
<h2>Configurator FAQ.</h2>
</header>
<div className="cfg-faq-grid">
{FAQ.map(({ q, a }) => (
<details key={q} className="cfg-faq-item">
<summary>{q}<span aria-hidden className="cfg-faq-icon">+</span></summary>
<p>{a}</p>
</details>
))}
</div>
</section>
{/* FINAL CTA */}
<section className="cfg-cta container-wide" aria-label="Get started">
<div className="cfg-cta-glow" aria-hidden />
<div className="cfg-cta-grid-bg" aria-hidden />
<div className="cfg-cta-inner">
<div className="cfg-cta-text">
<span className="eyebrow">Next step</span>
<h2>Ready to bring your G1 to life in Dubai?</h2>
<p>
Send us your configuration. Our Dubai team will confirm availability, lead time, and book a live demo at our showroom or your venue.
</p>
</div>
<div className="cfg-cta-actions">
<CTAButton href="/contact/" variant="primary" size="lg" arrow="up-right">
Request UAE Quotation
</CTAButton>
<CTAButton href="/book-demo/" variant="secondary" size="lg" arrow="right">
Book a Live Demo
</CTAButton>
<CTAButton
href="https://wa.me/971559482728"
external
variant="ghost"
size="lg"
arrow="up-right"
ariaLabel="Chat with YS Lootah Robotics on WhatsApp"
>
WhatsApp Us
</CTAButton>
</div>
</div>
</section>
</main>
<FooterAndContact /> <FooterAndContact />
<style jsx>{`
.cfg-main {
padding-top: clamp(5rem, 9vw, 7rem);
display: flex;
flex-direction: column;
gap: clamp(2.5rem, 5vw, 4.25rem);
padding-bottom: clamp(3rem, 6vw, 5rem);
}
.cfg-section-head {
display: flex;
flex-direction: column;
gap: 0.55rem;
max-width: 760px;
}
.cfg-section-head h2 {
margin: 0;
font-size: clamp(1.5rem, 3vw, 2.1rem);
font-weight: 400;
letter-spacing: -0.02em;
color: #FFFFFF;
line-height: 1.15;
}
/* HERO */
.cfg-hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid rgba(74, 102, 216, 0.16);
background:
radial-gradient(ellipse 70% 100% at 0% 0%, rgba(58, 85, 196, 0.18), transparent 55%),
radial-gradient(ellipse 60% 80% at 100% 100%, rgba(136, 145, 199, 0.14), transparent 60%),
linear-gradient(135deg, rgba(14, 13, 22, 0.95), rgba(6, 6, 10, 0.97));
}
.cfg-hero-glow {
position: absolute; width: 580px; height: 580px; border-radius: 999px;
top: -220px; right: -180px;
background: radial-gradient(circle, rgba(120, 140, 255, 0.34), transparent 70%);
filter: blur(120px); opacity: 0.55; pointer-events: none;
}
.cfg-hero-glow-b {
position: absolute; width: 460px; height: 460px; border-radius: 999px;
bottom: -200px; left: -150px;
background: radial-gradient(circle, rgba(74, 102, 216, 0.32), transparent 70%);
filter: blur(110px); opacity: 0.45; pointer-events: none;
}
.cfg-hero-grid {
position: absolute; inset: 0;
background:
linear-gradient(rgba(74, 102, 216, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(74, 102, 216, 0.05) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 70% 90% at 30% 40%, #000 25%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse 70% 90% at 30% 40%, #000 25%, transparent 80%);
pointer-events: none;
}
.cfg-hero-inner {
position: relative;
z-index: 1;
padding: clamp(2rem, 4vw, 3.25rem) clamp(1rem, 4vw, 2rem);
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.75rem, 3.5vw, 2.75rem);
align-items: center;
}
.cfg-hero-copy { display: flex; flex-direction: column; gap: 1rem; max-width: 640px; }
.cfg-hero-eyebrow {
display: inline-flex; align-items: center; gap: 0.55rem;
width: fit-content;
}
.cfg-dot {
width: 7px; height: 7px; border-radius: 999px; background: #DEE0F0;
box-shadow: 0 0 14px rgba(222, 224, 240, 0.85);
}
.cfg-hero-title {
margin: 0;
font-size: clamp(2rem, 5vw, 3.2rem);
font-weight: 300;
letter-spacing: -0.03em;
line-height: 1.05;
color: #FFFFFF;
}
.cfg-hero-title :global(.text-gradient) { font-weight: 500; }
.cfg-hero-sub {
margin: 0;
color: #DEE0F0;
font-size: clamp(0.95rem, 1.8vw, 1.1rem);
line-height: 1.65;
max-width: 560px;
}
.cfg-hero-checks {
list-style: none; margin: 0.4rem 0 0; padding: 0;
display: flex; flex-direction: column; gap: 0.55rem;
}
.cfg-hero-checks li {
display: inline-flex; align-items: center; gap: 0.55rem;
color: #DEE0F0; font-size: 0.92rem; line-height: 1.5;
}
.cfg-hero-checks li :global(svg) { color: #7FD6D0; flex: none; }
.cfg-hero-actions {
display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.65rem;
}
/* HERO STATS */
.cfg-hero-stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.6rem;
}
.cfg-stat {
position: relative;
display: flex; flex-direction: column; gap: 0.3rem;
padding: 0.95rem 1rem;
border-radius: 16px;
border: 1px solid rgba(120, 140, 255, 0.22);
background:
linear-gradient(135deg, rgba(80, 110, 255, 0.12), rgba(255, 255, 255, 0.03));
overflow: hidden;
min-height: 86px;
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.cfg-stat::before {
content: ''; position: absolute;
top: 0; right: 14px; width: 24px; height: 1px;
background: linear-gradient(90deg, transparent, rgba(180, 195, 255, 0.55));
}
.cfg-stat:hover {
border-color: rgba(120, 140, 255, 0.45);
transform: translateY(-1px);
box-shadow: 0 10px 22px rgba(0, 0, 0, 0.38), 0 0 18px rgba(74, 102, 216, 0.18);
}
.cfg-stat-value {
font-size: clamp(1.4rem, 2.4vw, 1.85rem);
font-weight: 500; letter-spacing: -0.025em; line-height: 1;
background: linear-gradient(180deg, #FFFFFF, #B5BDDB);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.cfg-stat-label {
font-size: 0.64rem; letter-spacing: 0.22em;
text-transform: uppercase; font-weight: 700; color: #A6B2D8;
}
@media (min-width: 920px) {
.cfg-hero-inner { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
.cfg-hero-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* STEPS */
.cfg-steps { display: flex; flex-direction: column; gap: clamp(1.25rem, 2.5vw, 1.75rem); }
.cfg-steps-grid {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem;
counter-reset: step;
}
@media (min-width: 720px) { .cfg-steps-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.1rem; } }
.cfg-step {
position: relative;
display: flex; flex-direction: column; gap: 0.6rem;
padding: 1.2rem 1.2rem 1.3rem;
border-radius: 18px;
border: 1px solid rgba(222, 224, 240, 0.10);
background:
radial-gradient(ellipse 80% 60% at 100% 0%, rgba(74, 102, 216, 0.14), transparent 60%),
linear-gradient(180deg, rgba(22, 21, 30, 0.92), rgba(10, 10, 14, 0.96));
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.cfg-step:hover {
border-color: rgba(74, 102, 216, 0.42);
transform: translateY(-3px);
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.5), 0 0 22px rgba(58, 85, 196, 0.18);
}
.cfg-step-num {
position: absolute; top: 0.85rem; right: 1rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.7rem; letter-spacing: 0.22em; font-weight: 800; color: #8891C7;
}
.cfg-step-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 40px; height: 40px; border-radius: 12px;
color: #DEE0F0;
background: rgba(74, 102, 216, 0.16);
border: 1px solid rgba(74, 102, 216, 0.38);
}
.cfg-step h3 { margin: 0; font-size: 1.02rem; font-weight: 600; color: #FFFFFF; letter-spacing: -0.005em; }
.cfg-step p { margin: 0; color: #C9CCDE; font-size: 0.9rem; line-height: 1.6; }
/* CONFIGURATOR FRAME */
.cfg-frame { display: flex; flex-direction: column; gap: clamp(1.25rem, 2.5vw, 1.75rem); scroll-margin-top: clamp(80px, 12vh, 120px); }
.cfg-frame-head {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
max-width: none;
gap: 1rem;
}
.cfg-frame-spec {
display: inline-flex; align-items: center; gap: 0.45rem;
font-size: 0.78rem; font-weight: 600; letter-spacing: 0.06em;
text-transform: uppercase;
color: #DEE0F0;
text-decoration: none;
padding: 0.6rem 0.95rem;
border-radius: 999px;
border: 1px solid rgba(222, 224, 240, 0.18);
background: rgba(20, 19, 26, 0.7);
transition: color 0.3s, border-color 0.3s, gap 0.3s;
}
.cfg-frame-spec:hover { color: #FFFFFF; border-color: rgba(74, 102, 216, 0.5); gap: 0.65rem; }
.cfg-frame-shell {
border-radius: 24px;
overflow: hidden;
border: 1px solid rgba(74, 102, 216, 0.22);
box-shadow:
0 30px 80px rgba(0, 0, 0, 0.55),
inset 0 1px 0 rgba(255, 255, 255, 0.04),
0 0 28px rgba(58, 85, 196, 0.18);
background: linear-gradient(180deg, rgba(10, 10, 14, 0.95), rgba(6, 6, 10, 0.98));
}
.cfg-frame-bar {
display: flex; align-items: center; justify-content: space-between;
padding: 0.7rem 1rem;
border-bottom: 1px solid rgba(74, 102, 216, 0.18);
background: linear-gradient(180deg, rgba(14, 13, 22, 0.95), rgba(8, 8, 12, 0.97));
}
.cfg-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.62rem; letter-spacing: 0.28em; font-weight: 800; color: #DEE0F0;
display: inline-flex; align-items: center; gap: 0.45rem;
}
.cfg-mono-mute { color: #8891C7; }
.cfg-live-dot {
width: 7px; height: 7px; border-radius: 999px; background: #7FD6D0;
box-shadow: 0 0 10px rgba(127, 214, 208, 0.7);
}
/* POST CONFIG */
.cfg-post { display: flex; flex-direction: column; gap: clamp(1.25rem, 2.5vw, 1.75rem); }
.cfg-post-grid {
display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem;
}
@media (min-width: 720px) { .cfg-post-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.1rem; } }
.cfg-post-card {
display: flex; flex-direction: column; gap: 0.55rem;
padding: 1.2rem 1.2rem 1.25rem;
border-radius: 18px;
border: 1px solid rgba(222, 224, 240, 0.10);
background:
radial-gradient(ellipse 80% 60% at 100% 0%, rgba(127, 214, 208, 0.10), transparent 60%),
linear-gradient(180deg, rgba(22, 21, 30, 0.9), rgba(10, 10, 14, 0.96));
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.cfg-post-card:hover {
border-color: rgba(127, 214, 208, 0.38);
transform: translateY(-3px);
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.5), 0 0 24px rgba(127, 214, 208, 0.14);
}
.cfg-post-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 36px; height: 36px; border-radius: 10px;
color: #7FD6D0;
background: rgba(127, 214, 208, 0.16);
border: 1px solid rgba(127, 214, 208, 0.38);
}
.cfg-post-card h3 { margin: 0; font-size: 1rem; font-weight: 600; color: #FFFFFF; letter-spacing: -0.005em; }
.cfg-post-card p { margin: 0; color: #C9CCDE; font-size: 0.88rem; line-height: 1.55; }
/* PILLARS */
.cfg-pillars-grid {
list-style: none; margin: 0; padding: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.6rem;
}
@media (min-width: 820px) { .cfg-pillars-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.7rem; } }
.cfg-pillars-grid li {
display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 0.7rem;
padding: 0.95rem 1.05rem;
border-radius: 16px;
border: 1px solid rgba(222, 224, 240, 0.10);
background:
radial-gradient(ellipse 80% 60% at 100% 0%, rgba(74, 102, 216, 0.12), transparent 60%),
linear-gradient(180deg, rgba(22, 21, 30, 0.92), rgba(10, 10, 14, 0.96));
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.cfg-pillars-grid li:hover {
border-color: rgba(74, 102, 216, 0.42);
transform: translateY(-2px);
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45), 0 0 18px rgba(58, 85, 196, 0.16);
}
.cfg-pillars-grid li :global(svg) {
color: #DEE0F0;
background: rgba(74, 102, 216, 0.16);
border: 1px solid rgba(74, 102, 216, 0.38);
border-radius: 10px;
padding: 8px;
width: 32px; height: 32px;
box-sizing: content-box;
}
.cfg-pillars-grid li div { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.cfg-pillars-grid li span { color: #FFFFFF; font-size: 0.82rem; font-weight: 600; letter-spacing: -0.005em; }
.cfg-pillars-grid li small { color: #8891C7; font-size: 0.7rem; }
/* FAQ */
.cfg-faq { display: flex; flex-direction: column; gap: clamp(1rem, 2.5vw, 1.5rem); }
.cfg-faq-grid {
display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.6rem;
}
@media (min-width: 820px) { .cfg-faq-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; } }
.cfg-faq-item {
position: relative;
padding: 1rem 1.15rem;
border-radius: 14px;
border: 1px solid rgba(222, 224, 240, 0.10);
background: linear-gradient(135deg, rgba(20, 19, 26, 0.85), rgba(10, 10, 14, 0.94));
transition: border-color 0.3s;
}
.cfg-faq-item[open] { border-color: rgba(74, 102, 216, 0.42); }
.cfg-faq-item summary {
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
cursor: pointer; list-style: none;
font-size: 0.95rem; font-weight: 600; color: #FFFFFF;
letter-spacing: -0.005em;
}
.cfg-faq-item summary::-webkit-details-marker { display: none; }
.cfg-faq-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 26px; height: 26px;
border-radius: 999px;
border: 1px solid rgba(222, 224, 240, 0.18);
color: #DEE0F0;
font-size: 0.95rem;
transition: transform 0.3s, background 0.3s;
flex: none;
}
.cfg-faq-item[open] .cfg-faq-icon { transform: rotate(45deg); background: rgba(74, 102, 216, 0.2); }
.cfg-faq-item p {
margin: 0.75rem 0 0;
color: #C9CCDE;
font-size: 0.9rem;
line-height: 1.65;
}
/* FINAL CTA */
.cfg-cta {
position: relative;
overflow: hidden;
border-radius: 26px;
border: 1px solid rgba(74, 102, 216, 0.26);
background:
radial-gradient(ellipse 60% 100% at 100% 0%, rgba(58, 85, 196, 0.22), transparent 60%),
linear-gradient(135deg, rgba(18, 16, 28, 0.95), rgba(8, 8, 12, 0.97));
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.cfg-cta-glow {
position: absolute; width: 480px; height: 480px; border-radius: 999px;
top: -160px; left: -160px;
background: radial-gradient(circle, rgba(74, 102, 216, 0.45), transparent 70%);
filter: blur(100px); opacity: 0.55; pointer-events: none;
}
.cfg-cta-grid-bg {
position: absolute; inset: 0;
background:
linear-gradient(rgba(74, 102, 216, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(74, 102, 216, 0.05) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 60% 90% at 70% 50%, #000 25%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse 60% 90% at 70% 50%, #000 25%, transparent 80%);
pointer-events: none;
}
.cfg-cta-inner {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.3rem, 3vw, 2rem);
padding: clamp(1.75rem, 3.6vw, 2.75rem);
align-items: center;
}
@media (min-width: 900px) {
.cfg-cta-inner { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
}
.cfg-cta-text { display: flex; flex-direction: column; gap: 0.65rem; max-width: 560px; }
.cfg-cta-text h2 {
margin: 0;
font-size: clamp(1.65rem, 3vw, 2.25rem);
font-weight: 400; letter-spacing: -0.02em; color: #FFFFFF; line-height: 1.1;
}
.cfg-cta-text p { margin: 0; color: #DEE0F0; font-size: 0.95rem; line-height: 1.65; }
.cfg-cta-actions {
display: flex; flex-wrap: wrap; gap: 0.55rem; justify-content: flex-start;
}
@media (min-width: 900px) {
.cfg-cta-actions { justify-content: flex-end; }
}
@media (max-width: 520px) {
.cfg-cta-actions :global(.cta-btn) { width: 100%; justify-content: space-between; }
.cfg-hero-actions :global(.cta-btn) { width: 100%; justify-content: space-between; }
}
`}</style>
</> </>
); );
} }

View File

@ -19,7 +19,7 @@ export const metadata: Metadata = {
const HERO_COLLAGE: { src: string; alt: string }[] = [ const HERO_COLLAGE: { src: string; alt: string }[] = [
{ src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' }, { src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' },
{ src: '/industries/cards/security-surveillance.jpg', alt: 'Quadruped robot on security patrol' }, { src: '/industries/cards/security-surveillance.jpg', alt: 'Quadruped robot on security patrol' },
{ src: '/industries/cards/education.jpg', alt: 'Humanoid robot engaging with students in a classroom' }, { src: '/images/robots/unitree-h1.jpg', alt: 'Humanoid robot engaging with students in a classroom' },
{ src: '/industries/cards/healthcare.webp', alt: 'Delivery robot moving through a hospital corridor' }, { src: '/industries/cards/healthcare.webp', alt: 'Delivery robot moving through a hospital corridor' },
]; ];

View File

@ -15,19 +15,16 @@ export function ConfigPanel() {
const activePersona = useConfigStore((s) => s.activePersonaAttire); const activePersona = useConfigStore((s) => s.activePersonaAttire);
const activeBody = useConfigStore((s) => s.activeBody); const activeBody = useConfigStore((s) => s.activeBody);
const personas = usePersonaStore((s) => s.personas); const personas = usePersonaStore((s) => s.personas);
// Track which persona is loading (waiting for GLB to download)
const [loadingPersona, setLoadingPersona] = useState<string | null>(null); const [loadingPersona, setLoadingPersona] = useState<string | null>(null);
const colorsSectionRef = useRef<HTMLElement>(null); const colorsSectionRef = useRef<HTMLElement>(null);
const personaSectionRef = useRef<HTMLElement>(null); const personaSectionRef = useRef<HTMLElement>(null);
// Clear loading state once activePersona matches what we requested
const prevActivePersona = useRef(activePersona); const prevActivePersona = useRef(activePersona);
useEffect(() => { useEffect(() => {
if (activePersona !== prevActivePersona.current) { if (activePersona !== prevActivePersona.current) {
prevActivePersona.current = activePersona; prevActivePersona.current = activePersona;
} }
// After 6s max, clear loading state even if something went wrong
if (loadingPersona !== null) { if (loadingPersona !== null) {
const t = setTimeout(() => setLoadingPersona(null), 6000); const t = setTimeout(() => setLoadingPersona(null), 6000);
return () => clearTimeout(t); return () => clearTimeout(t);
@ -47,14 +44,12 @@ export function ConfigPanel() {
}, []); }, []);
const handlePersonaSelect = useCallback((attire: string) => { const handlePersonaSelect = useCallback((attire: string) => {
// Only show loading for dynamic (uploaded) attire that has a GLB to download
const persona = personaStore.getState().personas.find((p) => p.id === attire); const persona = personaStore.getState().personas.find((p) => p.id === attire);
const isStatic = ['none', 'emarati-kandura', 'industrial-vest', 'business-suit'].includes(attire); const isStatic = ['none', 'emarati-kandura', 'industrial-vest', 'business-suit'].includes(attire);
if (!isStatic && persona?.modelPath) { if (!isStatic && persona?.modelPath) {
setLoadingPersona(attire); setLoadingPersona(attire);
} }
configStore.getState().setPersonaAttire(attire); configStore.getState().setPersonaAttire(attire);
// Clear loading once the spin animation would have completed (~800ms)
setTimeout(() => setLoadingPersona(null), 3000); setTimeout(() => setLoadingPersona(null), 3000);
}, []); }, []);
@ -64,31 +59,17 @@ export function ConfigPanel() {
}, []); }, []);
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '1.4rem' }}>
{/* --- COLORS SECTION --- */} {/* COLORS */}
<section <section ref={colorsSectionRef} id="section-colors" style={sectionWrap}>
ref={colorsSectionRef}
id="section-colors"
style={{
borderRadius: '0.5rem',
padding: '0.75rem',
}}
>
<h3 style={sectionTitleStyle}>Colors</h3> <h3 style={sectionTitleStyle}>Colors</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
<ColorInput label="Primary" value={activeColors.primary} onChange={(v) => handleColorChange('primary', v)} /> <ColorInput label="Primary" value={activeColors.primary} onChange={(v) => handleColorChange('primary', v)} />
</div> </div>
</section> </section>
{/* --- PERSONA SECTION --- */} {/* PERSONA ATTIRE */}
<section <section ref={personaSectionRef} id="section-persona" style={sectionWrap}>
ref={personaSectionRef}
id="section-persona"
style={{
borderRadius: '0.5rem',
padding: '0.75rem',
}}
>
<h3 style={sectionTitleStyle}>Persona Attire</h3> <h3 style={sectionTitleStyle}>Persona Attire</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
{personas.map((persona) => { {personas.map((persona) => {
@ -97,85 +78,85 @@ export function ConfigPanel() {
<button <button
key={persona.id} key={persona.id}
onClick={() => handlePersonaSelect(persona.id)} onClick={() => handlePersonaSelect(persona.id)}
aria-pressed={isActive}
className={`cfg-persona-btn${isActive ? ' is-active' : ''}`}
style={{ style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '0.75rem', gap: '0.75rem',
padding: '0.6rem 0.75rem', padding: '0.65rem 0.75rem',
borderRadius: '0.5rem', borderRadius: '12px',
border: isActive border: isActive
? '1px solid rgba(59, 130, 246, 0.5)' ? '1px solid rgba(120, 140, 255, 0.55)'
: '1px solid rgba(0, 0, 0, 0.06)', : '1px solid rgba(222, 224, 240, 0.10)',
background: isActive background: isActive
? 'rgba(59, 130, 246, 0.06)' ? 'linear-gradient(135deg, rgba(80, 110, 255, 0.16), rgba(28, 27, 38, 0.85))'
: 'rgba(248, 248, 246, 0.4)', : 'linear-gradient(135deg, rgba(22, 21, 30, 0.78), rgba(14, 13, 20, 0.92))',
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.25s ease', transition: 'all 0.25s ease',
textAlign: 'left', textAlign: 'left',
width: '100%', width: '100%',
boxShadow: isActive
? '0 8px 22px rgba(0, 0, 0, 0.45), 0 0 18px rgba(74, 102, 216, 0.22)'
: '0 4px 10px rgba(0, 0, 0, 0.3)',
}} }}
aria-pressed={isActive}
> >
{/* Color preview swatch */} {/* swatch */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', flexShrink: 0 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', flexShrink: 0 }}>
<div style={{ <div style={{
width: '36px', width: '36px', height: '12px',
height: '12px',
borderRadius: '3px 3px 0 0', borderRadius: '3px 3px 0 0',
backgroundColor: persona.colors.torso, backgroundColor: persona.colors.torso,
border: '1px solid rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.12)',
borderBottom: 'none', borderBottom: 'none',
}} /> }} />
<div style={{ <div style={{
width: '36px', width: '36px', height: '12px',
height: '12px',
borderRadius: '0 0 3px 3px', borderRadius: '0 0 3px 3px',
backgroundColor: persona.colors.legs, backgroundColor: persona.colors.legs,
border: '1px solid rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.12)',
borderTop: 'none', borderTop: 'none',
}} /> }} />
</div> </div>
{/* Text */}
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ <div style={{
fontSize: '0.8rem', fontSize: '0.85rem',
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 500,
color: isActive ? '#374151' : '#8891C7', color: isActive ? '#FFFFFF' : '#DEE0F0',
marginBottom: '2px', marginBottom: '2px',
letterSpacing: '-0.005em',
}}> }}>
{persona.label} {persona.label}
</div> </div>
<div style={{ <div style={{
fontSize: '0.65rem', fontSize: '0.7rem',
color: '#6a73a5', color: isActive ? '#A6B2D8' : '#8891C7',
lineHeight: 1.3, lineHeight: 1.35,
}}> }}>
{persona.description} {persona.description}
</div> </div>
</div> </div>
{/* Active checkmark or loading spinner */}
{isActive && loadingPersona === persona.id ? ( {isActive && loadingPersona === persona.id ? (
<div style={{ <div style={{
width: '20px', height: '20px', borderRadius: '50%', width: '22px', height: '22px', borderRadius: '50%',
border: '2px solid rgba(59,130,246,0.2)', border: '2px solid rgba(120, 140, 255, 0.22)',
borderTopColor: '#3b82f6', borderTopColor: '#7FD6D0',
animation: 'spin 0.8s linear infinite', animation: 'spin 0.8s linear infinite',
flexShrink: 0, flexShrink: 0,
}} /> }} />
) : isActive ? ( ) : isActive ? (
<div style={{ <div style={{
width: '20px', width: '22px', height: '22px', borderRadius: '50%',
height: '20px', background: 'rgba(127, 214, 208, 0.18)',
borderRadius: '50%', border: '1px solid rgba(127, 214, 208, 0.4)',
background: 'rgba(59, 130, 246, 0.2)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
flexShrink: 0, flexShrink: 0,
}}> }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#2563eb" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#7FD6D0" strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" /> <polyline points="20 6 9 17 4 12" />
</svg> </svg>
</div> </div>
@ -186,14 +167,8 @@ export function ConfigPanel() {
</div> </div>
</section> </section>
{/* --- ROBOT BODY SECTION --- */} {/* ROBOT BODY */}
<section <section id="section-body" style={sectionWrap}>
id="section-body"
style={{
borderRadius: '0.5rem',
padding: '0.75rem',
}}
>
<h3 style={sectionTitleStyle}>Robot Body</h3> <h3 style={sectionTitleStyle}>Robot Body</h3>
<div style={{ display: 'flex', gap: '0.5rem' }}> <div style={{ display: 'flex', gap: '0.5rem' }}>
{BODY_OPTIONS.map((opt) => { {BODY_OPTIONS.map((opt) => {
@ -205,31 +180,35 @@ export function ConfigPanel() {
aria-pressed={isActive} aria-pressed={isActive}
style={{ style={{
flex: 1, flex: 1,
padding: '0.6rem 0.75rem', padding: '0.7rem 0.8rem',
borderRadius: '0.5rem', borderRadius: '12px',
border: isActive border: isActive
? '1px solid rgba(59, 130, 246, 0.5)' ? '1px solid rgba(120, 140, 255, 0.55)'
: '1px solid rgba(0, 0, 0, 0.06)', : '1px solid rgba(222, 224, 240, 0.10)',
background: isActive background: isActive
? 'rgba(59, 130, 246, 0.06)' ? 'linear-gradient(135deg, rgba(80, 110, 255, 0.16), rgba(28, 27, 38, 0.85))'
: 'rgba(248, 248, 246, 0.4)', : 'linear-gradient(135deg, rgba(22, 21, 30, 0.78), rgba(14, 13, 20, 0.92))',
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.25s ease', transition: 'all 0.25s ease',
textAlign: 'left', textAlign: 'left',
boxShadow: isActive
? '0 8px 22px rgba(0, 0, 0, 0.45), 0 0 18px rgba(74, 102, 216, 0.22)'
: '0 4px 10px rgba(0, 0, 0, 0.28)',
}} }}
> >
<div style={{ <div style={{
fontSize: '0.8rem', fontSize: '0.85rem',
fontWeight: isActive ? 600 : 500, fontWeight: isActive ? 600 : 500,
color: isActive ? '#374151' : '#6a73a5', color: isActive ? '#FFFFFF' : '#DEE0F0',
marginBottom: '2px', marginBottom: '3px',
letterSpacing: '-0.005em',
}}> }}>
{opt.label} {opt.label}
</div> </div>
<div style={{ <div style={{
fontSize: '0.65rem', fontSize: '0.68rem',
color: '#8891C7', color: '#8891C7',
lineHeight: 1.3, lineHeight: 1.35,
}}> }}>
{opt.description} {opt.description}
</div> </div>
@ -239,22 +218,32 @@ export function ConfigPanel() {
</div> </div>
</section> </section>
{/* --- PRICING --- */} {/* PRICING */}
<PricingEngine /> <PricingEngine />
{/* --- RESET --- */} {/* RESET */}
<button <button
onClick={handleReset} onClick={handleReset}
style={{ style={{
padding: '0.6rem', padding: '0.7rem',
borderRadius: '0.375rem', borderRadius: '10px',
border: '1px solid rgba(239, 68, 68, 0.2)', border: '1px solid rgba(239, 68, 68, 0.28)',
background: 'rgba(239, 68, 68, 0.05)', background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.10), rgba(20, 12, 14, 0.8))',
color: '#ef4444', color: '#FCA5A5',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.8rem', fontSize: '0.78rem',
fontWeight: 600,
letterSpacing: '0.04em',
transition: 'all 0.2s ease', transition: 'all 0.2s ease',
}} }}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.55)';
e.currentTarget.style.color = '#FECACA';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.28)';
e.currentTarget.style.color = '#FCA5A5';
}}
> >
Reset Configuration Reset Configuration
</button> </button>
@ -264,16 +253,26 @@ export function ConfigPanel() {
function ColorInput({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) { function ColorInput({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
return ( return (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}> <div
style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
padding: '0.6rem 0.7rem',
borderRadius: '12px',
border: '1px solid rgba(222, 224, 240, 0.10)',
background: 'linear-gradient(135deg, rgba(22, 21, 30, 0.78), rgba(14, 13, 20, 0.92))',
}}
>
<input <input
type="color" type="color"
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
style={{ style={{
width: '32px', width: '36px',
height: '32px', height: '36px',
border: '2px solid rgba(0, 0, 0, 0.08)', border: '1px solid rgba(120, 140, 255, 0.38)',
borderRadius: '0.375rem', borderRadius: '8px',
background: 'transparent', background: 'transparent',
cursor: 'pointer', cursor: 'pointer',
padding: 0, padding: 0,
@ -281,21 +280,28 @@ function ColorInput({ label, value, onChange }: { label: string; value: string;
aria-label={`${label} color`} aria-label={`${label} color`}
/> />
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<div style={{ fontSize: '0.8rem', color: '#374151', marginBottom: '2px' }}>{label}</div> <div style={{ fontSize: '0.82rem', color: '#FFFFFF', fontWeight: 600, marginBottom: '2px', letterSpacing: '-0.005em' }}>{label}</div>
<div style={{ fontSize: '0.7rem', color: '#6a73a5', fontFamily: 'monospace' }}>{value}</div> <div style={{ fontSize: '0.7rem', color: '#8891C7', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace', letterSpacing: '0.08em' }}>{value.toUpperCase()}</div>
</div> </div>
</div> </div>
); );
} }
const sectionWrap: React.CSSProperties = {
borderRadius: '14px',
padding: '0.9rem 0.95rem 1rem',
border: '1px solid rgba(222, 224, 240, 0.08)',
background: 'linear-gradient(180deg, rgba(20, 19, 26, 0.55), rgba(10, 10, 14, 0.75))',
};
const sectionTitleStyle: React.CSSProperties = { const sectionTitleStyle: React.CSSProperties = {
fontSize: '0.75rem', fontSize: '0.65rem',
fontWeight: 500, fontWeight: 800,
color: '#8891C7', color: '#8891C7',
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: '0.05em', letterSpacing: '0.24em',
margin: 0, margin: 0,
paddingBottom: '0.5rem', paddingBottom: '0.6rem',
borderBottom: '1px solid rgba(0, 0, 0, 0.06)', borderBottom: '1px solid rgba(74, 102, 216, 0.18)',
marginBottom: '0.75rem', marginBottom: '0.85rem',
}; };

View File

@ -16,13 +16,15 @@ export function ConfiguratorSection() {
return ( return (
<section <section
id="configurator" id="configurator"
className="snap-section" className="snap-section cfg-shell"
style={{ style={{
position: 'relative', position: 'relative',
width: '100%', width: '100%',
height: '100vh', height: '100vh',
background: '#ffffff', background:
'radial-gradient(ellipse 70% 100% at 0% 0%, rgba(58, 85, 196, 0.10), transparent 55%), radial-gradient(ellipse 60% 80% at 100% 100%, rgba(136, 145, 199, 0.08), transparent 60%), linear-gradient(135deg, rgba(14, 13, 22, 0.98), rgba(6, 6, 10, 0.99))',
}} }}
aria-label={t('app.title')}
> >
<div <div
className={`layout-container${panelExpanded ? ' layout-expanded' : ''}`} className={`layout-container${panelExpanded ? ' layout-expanded' : ''}`}
@ -34,15 +36,16 @@ export function ConfiguratorSection() {
}} }}
> >
<aside <aside
className={`glass-panel-responsive${panelExpanded ? ' panel-expanded' : ''}`} className={`glass-panel-responsive cfg-aside${panelExpanded ? ' panel-expanded' : ''}`}
style={{ style={{
width: '420px', width: '420px',
height: '100%', height: '100%',
background: 'rgba(255, 255, 255, 0.9)', background:
'linear-gradient(180deg, rgba(22, 21, 30, 0.92), rgba(10, 10, 14, 0.96))',
backdropFilter: 'blur(20px)', backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)',
borderRight: '1px solid #e8e0cf', borderRight: '1px solid rgba(74, 102, 216, 0.18)',
boxShadow: '4px 0 30px rgba(0, 0, 0, 0.04)', boxShadow: '4px 0 30px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.04)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
position: 'relative', position: 'relative',
@ -69,12 +72,11 @@ export function ConfiguratorSection() {
userSelect: 'none', userSelect: 'none',
}} }}
> >
{/* Drag indicator tap to expand/collapse */}
<div <div
style={{ style={{
width: '40px', width: '40px',
height: '4px', height: '4px',
backgroundColor: '#e8e0cf', backgroundColor: 'rgba(222, 224, 240, 0.18)',
borderRadius: '2px', borderRadius: '2px',
transition: 'width 0.2s, background-color 0.2s', transition: 'width 0.2s, background-color 0.2s',
}} }}
@ -84,18 +86,44 @@ export function ConfiguratorSection() {
<header <header
style={{ style={{
padding: '1.25rem 1.5rem', padding: '1rem 1.5rem 1.25rem',
borderBottom: '1px solid #e8e0cf', borderBottom: '1px solid rgba(74, 102, 216, 0.16)',
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
display: 'flex',
flexDirection: 'column',
gap: '0.4rem',
}} }}
> >
<span
style={{
fontSize: '0.58rem',
letterSpacing: '0.28em',
textTransform: 'uppercase',
color: '#8891C7',
fontWeight: 800,
display: 'inline-flex',
alignItems: 'center',
gap: '0.4rem',
}}
>
<span
aria-hidden
style={{
width: 6, height: 6, borderRadius: 999,
background: '#7FD6D0',
boxShadow: '0 0 8px rgba(127, 214, 208, 0.7)',
}}
/>
Live build · Unitree G1
</span>
<h2 <h2
style={{ style={{
fontSize: '1rem', fontSize: '1.05rem',
fontWeight: 600, fontWeight: 600,
color: '#0a0a0c', color: '#FFFFFF',
margin: 0, margin: 0,
letterSpacing: '-0.01em',
}} }}
> >
{t('panel.title')} {t('panel.title')}
@ -105,7 +133,7 @@ export function ConfiguratorSection() {
<div <div
style={{ style={{
flex: 1, flex: 1,
padding: '1.5rem', padding: '1.25rem 1.25rem 1.5rem',
overflowY: 'visible', overflowY: 'visible',
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
@ -118,7 +146,7 @@ export function ConfiguratorSection() {
</aside> </aside>
<main <main
className="canvas-area" className="canvas-area cfg-canvas"
style={{ style={{
flex: 1, flex: 1,
display: 'flex', display: 'flex',
@ -127,10 +155,26 @@ export function ConfiguratorSection() {
position: 'relative', position: 'relative',
width: '100%', width: '100%',
height: '100%', height: '100%',
background:
'radial-gradient(ellipse 80% 60% at 50% 40%, rgba(74, 102, 216, 0.12), transparent 65%), radial-gradient(ellipse 60% 70% at 50% 90%, rgba(255, 255, 255, 0.04), transparent 70%), linear-gradient(180deg, rgba(8, 8, 14, 0.98), rgba(4, 4, 8, 1))',
}} }}
role="main" role="main"
aria-label={t('app.title')} aria-label={t('app.title')}
> >
<div
aria-hidden
style={{
position: 'absolute',
inset: 0,
backgroundImage:
'linear-gradient(rgba(74, 102, 216, 0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(74, 102, 216, 0.06) 1px, transparent 1px)',
backgroundSize: '56px 56px',
maskImage: 'radial-gradient(ellipse 70% 70% at 50% 50%, #000 30%, transparent 80%)',
WebkitMaskImage: 'radial-gradient(ellipse 70% 70% at 50% 50%, #000 30%, transparent 80%)',
pointerEvents: 'none',
}}
/>
{!isHydrated && ( {!isHydrated && (
<div <div
style={{ style={{
@ -140,7 +184,8 @@ export function ConfiguratorSection() {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundColor: '#ffffff', background:
'radial-gradient(ellipse 60% 50% at 50% 50%, rgba(58, 85, 196, 0.18), transparent 70%), rgba(8, 8, 14, 0.95)',
zIndex: 10, zIndex: 10,
}} }}
role="status" role="status"
@ -148,22 +193,31 @@ export function ConfiguratorSection() {
> >
<div <div
style={{ style={{
width: '48px', width: '52px',
height: '48px', height: '52px',
border: '3px solid #e8e0cf', border: '3px solid rgba(74, 102, 216, 0.18)',
borderTopColor: '#3b82f6', borderTopColor: '#7FD6D0',
borderRadius: '50%', borderRadius: '50%',
animation: 'spin 1s linear infinite', animation: 'spin 0.9s linear infinite',
}} }}
/> />
<p style={{ marginTop: '1rem', fontSize: '0.875rem', color: '#8891C7' }}> <p
style={{
marginTop: '1.1rem',
fontSize: '0.7rem',
color: '#8891C7',
letterSpacing: '0.22em',
textTransform: 'uppercase',
fontWeight: 700,
}}
>
{t('loading.configuration')} {t('loading.configuration')}
</p> </p>
</div> </div>
)} )}
{isHydrated && ( {isHydrated && (
<div style={{ width: '100%', height: '100%' }}> <div style={{ width: '100%', height: '100%', position: 'relative', zIndex: 1 }}>
<ClientOnly> <ClientOnly>
<RobotCanvas /> <RobotCanvas />
</ClientOnly> </ClientOnly>
@ -173,6 +227,16 @@ export function ConfiguratorSection() {
</div> </div>
<CheckoutOverlay /> <CheckoutOverlay />
<style jsx>{`
:global(.cfg-canvas button[aria-label="Capture 3D scene snapshot"]:hover),
:global(.cfg-canvas button[aria-label="Share configuration link"]:hover) {
border-color: rgba(74, 102, 216, 0.7) !important;
background: rgba(28, 27, 38, 0.88) !important;
color: #FFFFFF !important;
transform: translateY(-1px) !important;
}
`}</style>
</section> </section>
); );
} }

View File

@ -10,6 +10,7 @@ const NAV_LINKS = [
{ label: 'Robots', href: '/robots/' }, { label: 'Robots', href: '/robots/' },
{ label: 'Brands', href: '/brands/' }, { label: 'Brands', href: '/brands/' },
{ label: 'Industries', href: '/industries/' }, { label: 'Industries', href: '/industries/' },
{ label: 'Bu Sunaidah', href: '/bu-sunaidah/' },
{ label: 'About', href: '/about/' }, { label: 'About', href: '/about/' },
{ label: 'Contact', href: '/contact/' }, { label: 'Contact', href: '/contact/' },
{ label: 'Book Demo', href: '/book-demo/' }, { label: 'Book Demo', href: '/book-demo/' },

View File

@ -61,66 +61,88 @@ export function PricingEngine() {
<div style={{ <div style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '0.5rem', gap: '0.55rem',
padding: '0.75rem', padding: '1rem 1rem 1.05rem',
borderRadius: '0.5rem', borderRadius: '14px',
background: 'rgba(59, 130, 246, 0.03)', background:
border: '1px solid rgba(59, 130, 246, 0.06)', 'radial-gradient(ellipse 80% 60% at 100% 0%, rgba(74, 102, 216, 0.18), transparent 60%), linear-gradient(180deg, rgba(28, 27, 38, 0.92), rgba(12, 12, 18, 0.96))',
border: '1px solid rgba(74, 102, 216, 0.28)',
boxShadow: '0 14px 30px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.04)',
}}> }}>
<h3 style={{ <h3 style={{
fontSize: '0.75rem', fontSize: '0.62rem',
fontWeight: 500, fontWeight: 800,
color: '#8891C7', color: '#8891C7',
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: '0.05em', letterSpacing: '0.26em',
margin: 0, margin: 0,
paddingBottom: '0.5rem', paddingBottom: '0.55rem',
borderBottom: '1px solid rgba(0, 0, 0, 0.06)', borderBottom: '1px solid rgba(74, 102, 216, 0.22)',
}}> }}>
Price Breakdown Price Breakdown
</h3> </h3>
{/* Line items */} <div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
<PriceLine label={baseLabel} price={basePrice} /> <PriceLine label={baseLabel} price={basePrice} />
{personaPrice > 0 && <PriceLine label={personaLabel} price={personaPrice} />} {personaPrice > 0 && <PriceLine label={personaLabel} price={personaPrice} />}
{colorPrice > 0 && <PriceLine label="Custom Color" price={colorPrice} />} {colorPrice > 0 && <PriceLine label="Custom Color" price={colorPrice} />}
</div> </div>
{/* Total */}
<div <div
aria-live="polite" aria-live="polite"
aria-atomic="true" aria-atomic="true"
style={{ style={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'baseline',
paddingTop: '0.5rem', paddingTop: '0.65rem',
borderTop: '1px solid rgba(0, 0, 0, 0.06)', borderTop: '1px solid rgba(74, 102, 216, 0.22)',
marginTop: '0.25rem', marginTop: '0.2rem',
}}
>
<span style={{ fontSize: '0.7rem', fontWeight: 700, color: '#A6B2D8', letterSpacing: '0.18em', textTransform: 'uppercase' }}>Total</span>
<span
style={{
fontSize: '1.15rem',
fontWeight: 700,
background: 'linear-gradient(180deg, #FFFFFF, #B5BDDB)',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
color: 'transparent',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
letterSpacing: '0.02em',
}} }}
> >
<span style={{ fontSize: '0.8rem', fontWeight: 600, color: '#374151' }}>Total</span>
<span style={{ fontSize: '0.9rem', fontWeight: 700, color: '#0a0a0c', fontFamily: 'monospace' }}>
AED {formatAED(total)} AED {formatAED(total)}
</span> </span>
</div> </div>
{/* Proceed button */}
<button <button
onClick={handleProceed} onClick={handleProceed}
style={{ style={{
marginTop: '0.5rem', marginTop: '0.65rem',
padding: '0.7rem', padding: '0.85rem',
borderRadius: '0.375rem', borderRadius: '12px',
border: '1px solid rgba(59, 130, 246, 0.5)', border: '1px solid rgba(120, 140, 255, 0.55)',
background: 'rgba(59, 130, 246, 0.08)', background:
color: '#2563eb', 'linear-gradient(135deg, rgba(80, 110, 255, 0.32), rgba(40, 55, 145, 0.85))',
color: '#FFFFFF',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.8rem', fontSize: '0.78rem',
fontWeight: 600, fontWeight: 700,
transition: 'all 0.2s ease', letterSpacing: '0.16em',
textTransform: 'uppercase',
transition: 'all 0.25s ease',
textAlign: 'center', textAlign: 'center',
boxShadow: '0 10px 22px rgba(0, 0, 0, 0.45), 0 0 18px rgba(74, 102, 216, 0.32)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-1px)';
e.currentTarget.style.boxShadow = '0 14px 30px rgba(0, 0, 0, 0.55), 0 0 24px rgba(74, 102, 216, 0.45)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 10px 22px rgba(0, 0, 0, 0.45), 0 0 18px rgba(74, 102, 216, 0.32)';
}} }}
> >
Proceed to Order Proceed to Order
@ -136,8 +158,8 @@ function PriceLine({ label, price }: { label: string; price: number }) {
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
}}> }}>
<span style={{ fontSize: '0.75rem', color: '#8891C7' }}>{label}</span> <span style={{ fontSize: '0.78rem', color: '#C9CCDE' }}>{label}</span>
<span style={{ fontSize: '0.75rem', color: '#374151', fontFamily: 'monospace' }}> <span style={{ fontSize: '0.78rem', color: '#DEE0F0', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace', letterSpacing: '0.02em' }}>
AED {formatAED(price)} AED {formatAED(price)}
</span> </span>
</div> </div>

View File

@ -49,16 +49,16 @@ function SceneContent({ onCapture }: { onCapture: (gl: WebGLRenderer, scene: Sce
return ( return (
<> <>
<SceneCapture onCapture={onCapture} /> <SceneCapture onCapture={onCapture} />
<Environment preset="city" /> <Environment preset="studio" />
<ambientLight intensity={0.8} /> <ambientLight intensity={1.1} />
<directionalLight position={[5, 5, 5]} intensity={1.8} color="#ffffff" castShadow shadow-mapSize={[1024, 1024]} /> <directionalLight position={[5, 5, 5]} intensity={2.4} color="#ffffff" castShadow shadow-mapSize={[1024, 1024]} />
<directionalLight position={[-4, 3, 2]} intensity={0.8} color="#e8f0ff" /> <directionalLight position={[-4, 3, 2]} intensity={1.3} color="#dfeaff" />
<directionalLight position={[0, 3, -5]} intensity={0.8} color="#8891C7" /> <directionalLight position={[0, 3, -5]} intensity={1.1} color="#A6B2D8" />
<spotLight position={[0, 8, 0]} intensity={1.0} angle={0.6} penumbra={0.5} color="#ffffff" /> <spotLight position={[0, 8, 0]} intensity={1.6} angle={0.6} penumbra={0.5} color="#ffffff" />
<directionalLight position={[0, 2, 5]} intensity={0.7} color="#ffffff" /> <directionalLight position={[0, 2, 5]} intensity={1.1} color="#ffffff" />
<RobotModel /> <RobotModel />
<OrbitControls enablePan enableZoom enableRotate minDistance={2} maxDistance={10} minPolarAngle={0} maxPolarAngle={Math.PI} dampingFactor={0.05} enableDamping /> <OrbitControls enablePan enableZoom enableRotate minDistance={2} maxDistance={10} minPolarAngle={0} maxPolarAngle={Math.PI} dampingFactor={0.05} enableDamping />
<ContactShadows position={[0, -1, 0]} opacity={0.25} scale={10} blur={2} far={4} resolution={256} color="#000000" /> <ContactShadows position={[0, -1, 0]} opacity={0.55} scale={10} blur={2.4} far={4} resolution={256} color="#000000" />
</> </>
); );
} }
@ -138,7 +138,10 @@ export function RobotCanvas() {
dpr={[1, 1.5]} dpr={[1, 1.5]}
camera={{ position: [0, 1, 5], fov: 50 }} camera={{ position: [0, 1, 5], fov: 50 }}
gl={{ antialias: true, powerPreference: 'high-performance' }} gl={{ antialias: true, powerPreference: 'high-performance' }}
style={{ background: 'linear-gradient(180deg, #e8e8e4 0%, #f0f0ec 50%, #e8e8e4 100%)' }} style={{
background:
'radial-gradient(ellipse 38% 18% at 50% 80%, rgba(200, 210, 240, 0.22), transparent 70%), radial-gradient(ellipse 55% 55% at 50% 50%, rgba(58, 85, 196, 0.22), transparent 65%), linear-gradient(180deg, #0e0e16 0%, #060609 100%)',
}}
> >
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<SceneContent onCapture={handleCapture} /> <SceneContent onCapture={handleCapture} />
@ -148,13 +151,21 @@ export function RobotCanvas() {
<button <button
onClick={handleSnapshot} onClick={handleSnapshot}
disabled={isCapturing} disabled={isCapturing}
style={{ ...btnBase, left: '1rem', backgroundColor: 'rgba(255,255,255,0.8)', border: '1px solid #e8e0cf', color: '#0a0a0c' }} style={{
...btnBase,
left: '1rem',
backgroundColor: 'rgba(20, 19, 26, 0.78)',
border: '1px solid rgba(74, 102, 216, 0.36)',
color: '#DEE0F0',
boxShadow: '0 8px 22px rgba(0, 0, 0, 0.45)',
letterSpacing: '0.04em',
}}
aria-label="Capture 3D scene snapshot" aria-label="Capture 3D scene snapshot"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
{isCapturing ? 'Capturing...' : 'Snapshot'} {isCapturing ? 'Capturing' : 'Snapshot'}
</button> </button>
<button <button
@ -162,9 +173,11 @@ export function RobotCanvas() {
style={{ style={{
...btnBase, ...btnBase,
left: '8.5rem', left: '8.5rem',
backgroundColor: shareStatus === 'copied' ? 'rgba(34,197,94,0.1)' : 'rgba(255,255,255,0.8)', backgroundColor: shareStatus === 'copied' ? 'rgba(127, 214, 208, 0.18)' : 'rgba(20, 19, 26, 0.78)',
border: `1px solid ${shareStatus === 'copied' ? '#86efac' : '#e8e0cf'}`, border: `1px solid ${shareStatus === 'copied' ? 'rgba(127, 214, 208, 0.6)' : 'rgba(74, 102, 216, 0.36)'}`,
color: shareStatus === 'copied' ? '#16a34a' : '#0a0a0c', color: shareStatus === 'copied' ? '#7FD6D0' : '#DEE0F0',
boxShadow: '0 8px 22px rgba(0, 0, 0, 0.45)',
letterSpacing: '0.04em',
}} }}
aria-label="Share configuration link" aria-label="Share configuration link"
> >

View File

@ -0,0 +1,22 @@
type Props = {
size?: number;
className?: string;
title?: string;
};
export function InstagramGlyph({ size = 16, className, title }: Props) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="currentColor"
className={className}
aria-hidden={title ? undefined : 'true'}
role={title ? 'img' : undefined}
>
{title ? <title>{title}</title> : null}
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
</svg>
);
}

View File

@ -248,7 +248,7 @@ export const ACCESSORIES: Accessory[] = [
'Multi-robot management', 'Multi-robot management',
'Pudu Link integration', 'Pudu Link integration',
], ],
image: '/images/accessories/pudu-4g-watch.png', image: '/images/accessories/pudu-pager.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['All Pudu service robots'], compatibility: ['All Pudu service robots'],
@ -273,7 +273,7 @@ export const ACCESSORIES: Accessory[] = [
'Status indicators', 'Status indicators',
'Pudu Link cloud integration', 'Pudu Link cloud integration',
], ],
image: '/images/accessories/pudu-docking-station.png', image: '/images/robots/pudu-sh1.png',
accent: GOLD_CHAMPAGNE, accent: GOLD_CHAMPAGNE,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['CC1', 'T300', 'FlashBot Max', 'BellaBot Pro', 'PuduBot 2'], compatibility: ['CC1', 'T300', 'FlashBot Max', 'BellaBot Pro', 'PuduBot 2'],
@ -390,7 +390,7 @@ export const ACCESSORIES: Accessory[] = [
'Quick swap', 'Quick swap',
'Multiple finishes', 'Multiple finishes',
], ],
image: '/images/accessories/pudu-tray.png', image: '/images/robots/pudu-pudubot2.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['BellaBot', 'BellaBot Pro', 'KettyBot Pro', 'PuduBot 2'], compatibility: ['BellaBot', 'BellaBot Pro', 'KettyBot Pro', 'PuduBot 2'],
@ -436,7 +436,7 @@ export const ACCESSORIES: Accessory[] = [
'Tool-less install', 'Tool-less install',
'IoT route planning', 'IoT route planning',
], ],
image: '/images/accessories/pudu-towing-device.png', image: '/images/robots/pudu-d7.png',
accent: GOLD_BRONZE, accent: GOLD_BRONZE,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['PUDU T300'], compatibility: ['PUDU T300'],
@ -459,7 +459,7 @@ export const ACCESSORIES: Accessory[] = [
'Safe motion sensors', 'Safe motion sensors',
'Tool-less install', 'Tool-less install',
], ],
image: '/images/accessories/pudu-lifting-rack.png', image: '/images/robots/pudu-d9.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['PUDU T300'], compatibility: ['PUDU T300'],
@ -528,7 +528,7 @@ export const ACCESSORIES: Accessory[] = [
'Multi-compartment layout', 'Multi-compartment layout',
'Compatible with FlashBot family', 'Compatible with FlashBot family',
], ],
image: '/images/accessories/pudu-flashbot-vending-machine.png', image: '/images/robots/pudu-flashbot-max.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['FlashBot', 'FlashBot Max'], compatibility: ['FlashBot', 'FlashBot Max'],
@ -599,7 +599,7 @@ export const ACCESSORIES: Accessory[] = [
'Hospitality + corporate ready', 'Hospitality + corporate ready',
'Pudu Link cloud', 'Pudu Link cloud',
], ],
image: '/images/accessories/pudu-telephone-system.png', image: '/images/robots/pudu-kettybot.png',
accent: GOLD_BRONZE, accent: GOLD_BRONZE,
officialUrl: 'https://www.pudurobotics.com/en/accessory', officialUrl: 'https://www.pudurobotics.com/en/accessory',
compatibility: ['FlashBot', 'FlashBot Max', 'SwiftBot'], compatibility: ['FlashBot', 'FlashBot Max', 'SwiftBot'],

View File

@ -0,0 +1,19 @@
/**
* Bu Sunaidah press & media coverage.
*
* Each entry shows up as:
* - a logo in the desaturated logo wall (if `logoSrc` provided)
* - a pull-quote card below the wall (if `quote` provided)
*
* The entire press section is hidden when the array is empty.
*/
export type PressOutlet = {
outlet: string;
logoSrc?: string; // e.g. '/press/khaleej-times.svg'
url?: string; // article or coverage link
quote?: string; // optional short pull-quote
date?: string; // ISO 8601, e.g. '2025-09-12'
};
// TODO: populate as media coverage lands.
export const BU_SUNAIDAH_PRESS: PressOutlet[] = [];

39
src/data/bu-sunaidah.ts Normal file
View File

@ -0,0 +1,39 @@
/**
* Bu Sunaidah Instagram posts.
*
* Add post shortcodes (the part after /p/ or /reel/ in the URL).
* Example: https://www.instagram.com/p/ABC123/ → 'ABC123'
*
* The page renders each as an official Instagram embed iframe.
* Leave the array empty to show the "follow on Instagram" CTA fallback.
*/
export type InstagramPost = {
shortcode: string;
kind?: 'p' | 'reel' | 'tv';
caption?: string;
};
export const BU_SUNAIDAH_HANDLE = 'bu.sunaidah';
export const BU_SUNAIDAH_URL = `https://www.instagram.com/${BU_SUNAIDAH_HANDLE}`;
// TODO: populate with curated post shortcodes from @bu.sunaidah.
// Each entry renders as a 16:20 Instagram embed iframe.
export const BU_SUNAIDAH_POSTS: InstagramPost[] = [];
/**
* Bu Sunaidah video reel / showreel.
*
* Set to a config object to render the reel above the persona pillars.
* Leave as null to render a polished "reel coming soon" placeholder card.
*
* Examples:
* { kind: 'youtube', id: 'dQw4w9WgXcQ' }
* { kind: 'vimeo', id: '76979871' }
* { kind: 'mp4', src: '/video/bu-sunaidah-reel.mp4', poster: '/video/poster.webp' }
*/
export type ReelSource =
| { kind: 'youtube'; id: string; poster?: string }
| { kind: 'vimeo'; id: string; poster?: string }
| { kind: 'mp4'; src: string; poster?: string };
export const BU_SUNAIDAH_REEL: ReelSource | null = null;

View File

@ -486,7 +486,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
'Humanoid and quadruped robots that bring AI, programming, and modern robotics into UAE schools, universities, and innovation labs.', 'Humanoid and quadruped robots that bring AI, programming, and modern robotics into UAE schools, universities, and innovation labs.',
heroTags: ['STEM labs', 'Robotics clubs', 'Innovation programs', 'Research'], heroTags: ['STEM labs', 'Robotics clubs', 'Innovation programs', 'Research'],
heroImage: { heroImage: {
src: '/industries/cards/education.jpg', src: '/images/robots/unitree-h1.jpg',
alt: 'Humanoid research robot in a UAE STEM lab', alt: 'Humanoid research robot in a UAE STEM lab',
}, },
proofPoints: [ proofPoints: [