Add Instagram post fetching functionality and local JSON fallback
Some checks are pending
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions

- Introduced a new TypeScript file `bu-sunaidah-fetch.ts` to handle fetching the latest Instagram posts for Bu Sunaidah.
- Implemented a scraping method to attempt to retrieve posts directly from Instagram, with a fallback to a local JSON file `bu-sunaidah-posts.json`.
- Added a new JSON file containing recent Instagram post URLs and captions.
- Included a new image asset `unitree-l2.jpg` for accessories.
This commit is contained in:
Najjar\NajjarV02 2026-05-21 16:50:22 +04:00
parent 2d32a1e722
commit e7fde29a21
25 changed files with 1787 additions and 176 deletions

66
.scrape/d1t.html Normal file

File diff suppressed because one or more lines are too long

76
.scrape/dex11.html Normal file

File diff suppressed because one or more lines are too long

66
.scrape/dex25.html Normal file

File diff suppressed because one or more lines are too long

66
.scrape/dex31.html Normal file

File diff suppressed because one or more lines are too long

72
.scrape/l2.html Normal file

File diff suppressed because one or more lines are too long

70
.scrape/z1.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
Drop the approved Bu Sunaidah photo here as portrait.jpg (or .webp).

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -107,6 +107,122 @@ export default async function AccessoryDetailPage({ params }: { params: Promise<
</section> </section>
</MotionSection> </MotionSection>
{/* HIGHLIGHTS */}
{accessory.highlights && accessory.highlights.length > 0 && (
<MotionSection>
<ul className="acc-highlights" role="list">
{accessory.highlights.map((h) => (
<li key={h.label} className="acc-highlight">
<span className="acc-highlight-value">{h.value}</span>
<span className="acc-highlight-label">{h.label}</span>
{h.sub && <span className="acc-highlight-sub">{h.sub}</span>}
</li>
))}
</ul>
</MotionSection>
)}
{/* RICH FEATURE SECTIONS — full-bleed alternating */}
{accessory.featureSections && accessory.featureSections.length > 0 && (
<div className="acc-features-stack">
{accessory.featureSections.map((s, i) => {
const reverse = i % 2 === 1;
const hasImage = Boolean(s.image);
return (
<MotionSection key={i}>
<article className={`acc-feature${reverse ? ' is-reverse' : ''}${hasImage ? '' : ' is-text-only'}`}>
{hasImage && (
<div className="acc-feature-media">
<Image
src={s.image!}
alt={s.imageAlt ?? s.title}
fill
sizes="(max-width: 820px) 100vw, 50vw"
style={{ objectFit: 'cover', objectPosition: 'center' }}
/>
<span className="acc-feature-media-overlay" aria-hidden />
</div>
)}
<div className="acc-feature-body">
{s.eyebrow && <span className="eyebrow">{s.eyebrow}</span>}
<h2>{s.title}</h2>
<p>{s.body}</p>
{s.bullets && s.bullets.length > 0 && (
<ul className="acc-feature-bullets" role="list">
{s.bullets.map((b) => (
<li key={b}>
<span className="acc-feature-check"><Check size={12} strokeWidth={2.4} /></span>
{b}
</li>
))}
</ul>
)}
</div>
</article>
</MotionSection>
);
})}
</div>
)}
{/* PARAMS DIAGRAM */}
{accessory.paramsImage && (
<MotionSection>
<figure className="acc-params-figure">
<Image
src={accessory.paramsImage}
alt={`${accessory.name} dimensions and parameters diagram`}
width={2000}
height={859}
sizes="(max-width: 920px) 100vw, 1080px"
style={{ width: '100%', height: 'auto', display: 'block' }}
/>
<figcaption>{accessory.name} body dimensions and structural overview.</figcaption>
</figure>
</MotionSection>
)}
{/* SPEC GROUPS */}
{accessory.specGroups && accessory.specGroups.length > 0 && (
<MotionSection>
<section className="acc-specs">
<header>
<span className="eyebrow">Technical specifications</span>
<h2>Full parameter sheet.</h2>
</header>
<div className="acc-spec-stack">
{accessory.specGroups.map((g) => (
<div key={g.title} className="acc-spec-group">
<div className="acc-spec-head">
<h3>{g.title}</h3>
{g.variants && g.variants.length > 0 && (
<div className="acc-spec-variants" role="row">
{g.variants.map((v) => (
<span key={v} className="acc-spec-variant">{v}</span>
))}
</div>
)}
</div>
<table className="acc-spec-table">
<tbody>
{g.rows.map((r) => (
<tr key={r.label}>
<th scope="row">{r.label}</th>
<td>{r.value}</td>
{g.variants && g.variants.length > 1 && (
<td>{r.valueAlt ?? r.value}</td>
)}
</tr>
))}
</tbody>
</table>
</div>
))}
</div>
</section>
</MotionSection>
)}
{/* FEATURES + COMPATIBILITY */} {/* FEATURES + COMPATIBILITY */}
<MotionSection> <MotionSection>
<div className="acc-grid"> <div className="acc-grid">
@ -199,6 +315,30 @@ export default async function AccessoryDetailPage({ params }: { params: Promise<
</section> </section>
</MotionSection> </MotionSection>
{/* FOOTNOTES + SAFETY */}
{(accessory.footnotes?.length || accessory.safetyNotice) && (
<MotionSection>
<aside className="acc-foot">
{accessory.footnotes && accessory.footnotes.length > 0 && (
<div className="acc-foot-block">
<span className="eyebrow">Notes</span>
<ol className="acc-foot-list">
{accessory.footnotes.map((f, i) => (
<li key={i}><sup>{i + 1}</sup> {f}</li>
))}
</ol>
</div>
)}
{accessory.safetyNotice && (
<div className="acc-foot-block">
<span className="eyebrow">Safety</span>
<p>{accessory.safetyNotice}</p>
</div>
)}
</aside>
</MotionSection>
)}
{/* RELATED */} {/* RELATED */}
{related.length > 0 && ( {related.length > 0 && (
<MotionSection> <MotionSection>
@ -447,6 +587,226 @@ export default async function AccessoryDetailPage({ params }: { params: Promise<
.acc-hero-actions .cta-btn { width: 100%; justify-content: space-between; } .acc-hero-actions .cta-btn { width: 100%; justify-content: space-between; }
} }
/* HIGHLIGHTS */
.acc-highlights {
list-style: none; margin: 0; padding: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.7rem;
}
@media (min-width: 820px) { .acc-highlights { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.acc-highlight {
position: relative;
display: flex; flex-direction: column; gap: 0.35rem;
padding: 1.1rem 1.15rem;
border-radius: 18px;
border: 1px solid rgba(120, 140, 255, 0.22);
background:
radial-gradient(ellipse 80% 60% at 100% 0%, color-mix(in srgb, var(--acc) 16%, transparent), transparent 60%),
linear-gradient(135deg, rgba(80, 110, 255, 0.10), rgba(255, 255, 255, 0.03));
overflow: hidden;
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.acc-highlight::before {
content: ''; position: absolute;
top: 0; right: 16px; width: 28px; height: 1px;
background: linear-gradient(90deg, transparent, rgba(180, 195, 255, 0.55));
}
.acc-highlight:hover {
border-color: rgba(120, 140, 255, 0.55);
transform: translateY(-2px);
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45), 0 0 22px color-mix(in srgb, var(--acc) 18%, transparent);
}
.acc-highlight-value {
font-size: clamp(1.7rem, 3.2vw, 2.4rem);
font-weight: 500; letter-spacing: -0.03em; line-height: 1;
background: linear-gradient(180deg, #FFFFFF, #B5BDDB);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.acc-highlight-label {
font-size: 0.66rem; letter-spacing: 0.22em;
text-transform: uppercase; font-weight: 800; color: #DEE0F0;
}
.acc-highlight-sub {
font-size: 0.78rem; color: #8891C7; line-height: 1.3;
}
/* FEATURE SECTIONS — full-bleed alternating */
.acc-features-stack {
display: flex; flex-direction: column;
gap: clamp(2rem, 4vw, 3rem);
}
.acc-feature {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.25rem, 3vw, 2rem);
padding: clamp(1.5rem, 3vw, 2.25rem);
border-radius: 24px;
border: 1px solid rgba(74, 102, 216, 0.20);
background:
radial-gradient(ellipse 70% 80% at 0% 100%, color-mix(in srgb, var(--acc) 16%, transparent), transparent 60%),
linear-gradient(135deg, rgba(18, 17, 26, 0.95), rgba(8, 8, 12, 0.97));
box-shadow: 0 22px 50px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04);
overflow: hidden;
align-items: center;
}
@media (min-width: 820px) {
.acc-feature { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
.acc-feature.is-reverse { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
.acc-feature.is-reverse .acc-feature-media { order: 2; }
.acc-feature.is-reverse .acc-feature-body { order: 1; }
.acc-feature.is-text-only { grid-template-columns: minmax(0, 1fr); }
}
.acc-feature.is-text-only .acc-feature-media { display: none; }
.acc-feature-media {
position: relative;
aspect-ratio: 16 / 11;
border-radius: 18px;
overflow: hidden;
background: #0a0a0e;
border: 1px solid rgba(222, 224, 240, 0.08);
min-height: 200px;
}
.acc-feature-media-overlay {
position: absolute; inset: 0;
background:
linear-gradient(180deg, rgba(10, 10, 14, 0.08) 0%, rgba(10, 10, 14, 0.02) 40%, rgba(10, 10, 14, 0.55) 100%),
radial-gradient(ellipse 60% 70% at 100% 0%, color-mix(in srgb, var(--acc) 18%, transparent), transparent 65%);
pointer-events: none;
}
.acc-feature-body {
display: flex; flex-direction: column;
gap: 1rem;
padding: clamp(0.25rem, 1vw, 0.75rem);
min-width: 0;
}
.acc-feature-body h2 {
margin: 0;
font-size: clamp(1.6rem, 3.2vw, 2.4rem);
font-weight: 400; letter-spacing: -0.025em;
color: #FFFFFF;
line-height: 1.1;
}
.acc-feature-body p {
margin: 0; color: #DEE0F0;
font-size: clamp(0.95rem, 1.8vw, 1.05rem);
line-height: 1.65;
}
.acc-feature-bullets {
list-style: none; margin: 0.55rem 0 0; padding: 0;
display: flex; flex-direction: column; gap: 0.6rem;
}
.acc-feature-bullets li {
display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.65rem;
color: #DEE0F0; font-size: 0.92rem; line-height: 1.55;
}
/* PARAMS DIAGRAM */
.acc-params-figure {
margin: 0;
padding: clamp(1.2rem, 2.4vw, 1.8rem);
border-radius: 22px;
border: 1px solid rgba(74, 102, 216, 0.22);
background:
radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in srgb, var(--acc) 12%, transparent), transparent 60%),
linear-gradient(180deg, rgba(14, 13, 22, 0.95), rgba(8, 8, 12, 0.97));
display: flex; flex-direction: column; gap: 1rem;
box-shadow: 0 22px 52px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.acc-params-figure figcaption {
color: #8891C7; font-size: 0.78rem;
letter-spacing: 0.04em; text-align: center;
}
/* SPEC GROUPS */
.acc-specs { display: flex; flex-direction: column; gap: 1.25rem; }
.acc-specs header { display: flex; flex-direction: column; gap: 0.45rem; }
.acc-specs h2 {
margin: 0; font-size: clamp(1.4rem, 2.8vw, 1.85rem);
font-weight: 400; letter-spacing: -0.02em; color: #FFFFFF;
}
.acc-spec-stack {
display: flex; flex-direction: column; gap: 1rem;
}
.acc-spec-group {
border-radius: 18px;
border: 1px solid rgba(222, 224, 240, 0.10);
background: linear-gradient(180deg, rgba(20, 19, 26, 0.85), rgba(10, 10, 14, 0.94));
overflow: hidden;
}
.acc-spec-head {
display: flex; align-items: center; justify-content: space-between;
gap: 0.75rem; padding: 0.9rem 1.1rem;
border-bottom: 1px solid rgba(74, 102, 216, 0.18);
background: linear-gradient(180deg, rgba(74, 102, 216, 0.08), rgba(20, 19, 26, 0.6));
}
.acc-spec-head h3 {
margin: 0; font-size: 0.95rem; font-weight: 600;
color: #FFFFFF; letter-spacing: -0.005em;
}
.acc-spec-variants {
display: flex; gap: 0.4rem; flex-wrap: wrap;
}
.acc-spec-variant {
font-size: 0.6rem; font-weight: 800;
letter-spacing: 0.22em; text-transform: uppercase;
color: color-mix(in srgb, var(--acc) 70%, white);
padding: 0.22rem 0.55rem;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--acc) 38%, transparent);
background: rgba(10, 10, 14, 0.6);
}
.acc-spec-table {
width: 100%;
border-collapse: collapse;
font-size: 0.84rem;
}
.acc-spec-table th,
.acc-spec-table td {
padding: 0.7rem 1.1rem;
text-align: left;
vertical-align: top;
border-bottom: 1px solid rgba(222, 224, 240, 0.06);
}
.acc-spec-table tr:last-child th,
.acc-spec-table tr:last-child td { border-bottom: none; }
.acc-spec-table th {
color: #A6B2D8;
font-weight: 600;
width: 38%;
}
.acc-spec-table td {
color: #DEE0F0;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.82rem;
letter-spacing: 0.01em;
}
/* FOOTNOTES + SAFETY */
.acc-foot {
display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem;
padding: clamp(1.2rem, 2.4vw, 1.8rem);
border-radius: 18px;
border: 1px solid rgba(222, 224, 240, 0.10);
background: linear-gradient(180deg, rgba(18, 17, 24, 0.85), rgba(10, 10, 14, 0.94));
}
@media (min-width: 820px) { .acc-foot { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 1.5rem; } }
.acc-foot-block { display: flex; flex-direction: column; gap: 0.55rem; }
.acc-foot-list {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column; gap: 0.45rem;
}
.acc-foot-list li {
color: #C9CCDE; font-size: 0.82rem; line-height: 1.55;
}
.acc-foot-list sup {
color: color-mix(in srgb, var(--acc) 70%, white);
font-weight: 800;
margin-right: 0.25rem;
}
.acc-foot-block p { margin: 0; color: #C9CCDE; font-size: 0.82rem; line-height: 1.55; }
/* RELATED */ /* RELATED */
.acc-related { display: flex; flex-direction: column; gap: 1.25rem; } .acc-related { display: flex; flex-direction: column; gap: 1.25rem; }
.acc-related header { display: flex; flex-direction: column; gap: 0.45rem; } .acc-related header { display: flex; flex-direction: column; gap: 0.45rem; }

View File

@ -19,10 +19,13 @@ import { InstagramGlyph } from '@/components/icons/InstagramGlyph';
import { import {
BU_SUNAIDAH_HANDLE, BU_SUNAIDAH_HANDLE,
BU_SUNAIDAH_URL, BU_SUNAIDAH_URL,
BU_SUNAIDAH_POSTS,
BU_SUNAIDAH_REEL, BU_SUNAIDAH_REEL,
} from '@/data/bu-sunaidah'; } from '@/data/bu-sunaidah';
import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press'; import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press';
import { getLatestBuSunaidahPosts } from '@/lib/bu-sunaidah-fetch';
// Re-fetch latest Instagram posts every 30 minutes on the server.
export const revalidate = 1800;
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Bu Sunaidah · Emirati Robotics Persona | YS Lootah Robotics', title: 'Bu Sunaidah · Emirati Robotics Persona | YS Lootah Robotics',
@ -74,7 +77,8 @@ function formatDate(iso: string): string {
} }
} }
export default function BuSunaidahPage() { export default async function BuSunaidahPage() {
const posts = await getLatestBuSunaidahPosts(9);
return ( return (
<> <>
<Navbar /> <Navbar />
@ -203,9 +207,9 @@ export default function BuSunaidahPage() {
</a> </a>
</header> </header>
{BU_SUNAIDAH_POSTS.length > 0 ? ( {posts.length > 0 ? (
<div className="bs-ig-grid"> <div className="bs-ig-grid">
{BU_SUNAIDAH_POSTS.map((post) => { {posts.map((post) => {
const kind = post.kind ?? 'p'; const kind = post.kind ?? 'p';
const src = `https://www.instagram.com/${kind}/${post.shortcode}/embed/`; const src = `https://www.instagram.com/${kind}/${post.shortcode}/embed/`;
return ( return (
@ -632,10 +636,22 @@ export default function BuSunaidahPage() {
border: 1px solid rgba(222, 224, 240, 0.12); border: 1px solid rgba(222, 224, 240, 0.12);
background: rgba(14, 13, 18, 0.6); background: rgba(14, 13, 18, 0.6);
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45); box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45);
aspect-ratio: 4 / 5; aspect-ratio: 9 / 16;
min-height: 620px;
} }
/* The Instagram embed iframe ships an in-frame header (avatar +
username + "View profile") at the top. It is cross-origin so we
can't hide it from inside. Workaround: shift the iframe up by
~54px and let the rounded parent clip it. Adjust if Instagram
changes header sizing. */
.bs-ig-tile iframe { .bs-ig-tile iframe {
width: 100%; height: 100%; display: block; border: 0; position: absolute;
top: -54px;
left: 0;
width: 100%;
height: calc(100% + 54px);
display: block;
border: 0;
background: #0a0a0e; background: #0a0a0e;
} }

View File

@ -5,6 +5,7 @@ import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { CatalogClient } from './CatalogClient'; import { CatalogClient } from './CatalogClient';
import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase'; import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase';
import { RoboticsSplineShowcase } from '@/components/sections/robotics-spline-showcase';
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { ROBOTS } from '@/data/robots'; import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
@ -32,6 +33,11 @@ export default function RobotsPage() {
<Navbar /> <Navbar />
<main className="rb-main"> <main className="rb-main">
{/* SPLINE SHOWCASE — top-of-page robot + hand animation */}
<div className="rb-spline-wrap">
<RoboticsSplineShowcase />
</div>
<div className="container-wide rb-stack"> <div className="container-wide rb-stack">
{/* BREADCRUMBS */} {/* BREADCRUMBS */}
<nav aria-label="Breadcrumb" className="rb-crumbs"> <nav aria-label="Breadcrumb" className="rb-crumbs">
@ -126,6 +132,9 @@ export default function RobotsPage() {
padding-top: clamp(5rem, 9vw, 7rem); padding-top: clamp(5rem, 9vw, 7rem);
padding-bottom: clamp(3rem, 6vw, 5rem); padding-bottom: clamp(3rem, 6vw, 5rem);
} }
.rb-spline-wrap {
margin-bottom: clamp(2rem, 4vw, 3rem);
}
.rb-stack { .rb-stack {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -37,8 +37,8 @@ export function AccessoriesShowcase() {
<div <div
style={{ style={{
display: 'grid', display: 'grid',
gap: '1rem', gap: '1.25rem',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(260px, 100%), 1fr))', gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
}} }}
> >
{list.map((accessory) => ( {list.map((accessory) => (
@ -83,141 +83,209 @@ function FilterChip({
function AccessoryCard({ accessory }: { accessory: Accessory }) { function AccessoryCard({ accessory }: { accessory: Accessory }) {
const accent = accessory.accent; const accent = accessory.accent;
const detailHref = `/accessories/${accessory.slug}/`;
return ( return (
<Link <article
href={`/accessories/${accessory.slug}/`} className="card"
aria-label={`View details for ${accessory.name}`} style={{
style={{ textDecoration: 'none', color: 'inherit' }} display: 'flex',
flexDirection: 'column',
height: '100%',
color: '#FBFBFD',
}}
> >
<article <Link
className="acc-card" href={detailHref}
aria-label={`View details for ${accessory.brandLabel} ${accessory.name}`}
style={{ style={{
position: 'relative', position: 'relative',
display: 'flex', display: 'block',
flexDirection: 'column', aspectRatio: '1 / 1',
height: '100%',
borderRadius: 18,
overflow: 'hidden',
border: '1px solid rgba(222,224,240,0.10)',
background: background:
`radial-gradient(ellipse 80% 60% at 100% 0%, ${accent}1A, transparent 60%), linear-gradient(180deg, rgba(22,21,30,0.92), rgba(10,10,14,0.96))`, `radial-gradient(ellipse 70% 50% at 50% 60%, ${accent}26 0%, transparent 60%), linear-gradient(180deg, rgba(15, 12, 8,0.7), rgba(5, 5, 5,0.95))`,
boxShadow: '0 1px 0 rgba(222,224,240,0.04) inset, 0 14px 28px rgba(0,0,0,0.36)', overflow: 'hidden',
transition: 'transform 0.4s cubic-bezier(0.16,1,0.3,1), border-color 0.4s, box-shadow 0.4s', textDecoration: 'none',
}} }}
> >
<div <div
aria-hidden
style={{ style={{
position: 'relative', position: 'absolute',
aspectRatio: '16 / 11', inset: 0,
background: '#0a0a0e', backgroundImage:
borderBottom: '1px solid rgba(222,224,240,0.06)', 'linear-gradient(rgba(39, 63, 148,0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(39, 63, 148,0.05) 1px, transparent 1px)',
overflow: 'hidden', backgroundSize: '32px 32px',
maskImage: 'radial-gradient(ellipse 60% 60% at 50% 50%, #000 30%, transparent 80%)',
WebkitMaskImage: 'radial-gradient(ellipse 60% 60% at 50% 50%, #000 30%, transparent 80%)',
pointerEvents: 'none',
}}
/>
<Image
src={accessory.image}
alt={`${accessory.brandLabel} ${accessory.name}`}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{
objectFit: 'contain',
padding: '2rem',
transition: 'transform 0.6s cubic-bezier(0.16,1,0.3,1)',
}}
/>
<div
style={{
position: 'absolute',
top: '1rem',
left: '1rem',
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.4rem 0.7rem',
borderRadius: '999px',
background: 'rgba(5, 5, 5,0.7)',
border: `1px solid ${accent}55`,
fontSize: '0.7rem',
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: '#e8e0cf',
backdropFilter: 'blur(8px)',
}} }}
> >
<Image <span style={{ width: 6, height: 6, borderRadius: 999, background: accent }} />
src={accessory.image} {accessory.brandLabel}
alt={`${accessory.name} product image`}
fill
sizes="(max-width: 600px) 100vw, (max-width: 1100px) 50vw, 25vw"
style={{ objectFit: 'contain', objectPosition: 'center', padding: '0.6rem' }}
/>
<span
aria-hidden
style={{
position: 'absolute',
inset: 0,
background: `radial-gradient(ellipse 60% 70% at 50% 40%, ${accent}26, transparent 65%)`,
pointerEvents: 'none',
}}
/>
<span
style={{
position: 'absolute',
top: '0.55rem',
left: '0.55rem',
padding: '0.22rem 0.55rem',
borderRadius: 999,
fontSize: '0.55rem',
fontWeight: 800,
letterSpacing: '0.22em',
textTransform: 'uppercase',
color: accent,
background: 'rgba(10,10,14,0.7)',
border: `1px solid ${accent}55`,
backdropFilter: 'blur(8px)',
WebkitBackdropFilter: 'blur(8px)',
}}
>
{accessory.brandLabel}
</span>
<span
style={{
position: 'absolute',
top: '0.55rem',
right: '0.55rem',
padding: '0.22rem 0.55rem',
borderRadius: 999,
fontSize: '0.55rem',
fontWeight: 800,
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: '#DEE0F0',
background: 'rgba(10,10,14,0.7)',
border: '1px solid rgba(222,224,240,0.14)',
backdropFilter: 'blur(8px)',
WebkitBackdropFilter: 'blur(8px)',
}}
>
{GROUP_LABELS[accessory.group]}
</span>
</div> </div>
<div
style={{
position: 'absolute',
top: '1rem',
right: '1rem',
padding: '0.35rem 0.7rem',
borderRadius: '999px',
background: 'rgba(255,255,255,0.05)',
border: '1px solid rgba(39, 63, 148,0.2)',
fontSize: '0.66rem',
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: '#DEE0F0',
backdropFilter: 'blur(8px)',
}}
>
{GROUP_LABELS[accessory.group]}
</div>
</Link>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.55rem', padding: '1rem 1.1rem 1.1rem', flex: 1 }}> <div style={{ padding: '1.5rem', display: 'flex', flexDirection: 'column', gap: '0.75rem', flex: 1 }}>
<h3 style={{ margin: 0, fontSize: '1.02rem', fontWeight: 600, letterSpacing: '-0.005em', color: '#FFFFFF' }}> <h3 style={{ margin: 0, fontSize: '1.2rem', fontWeight: 600, letterSpacing: '-0.01em' }}>
<Link href={detailHref} style={{ color: '#FBFBFD', textDecoration: 'none' }}>
{accessory.name} {accessory.name}
</h3> </Link>
<p style={{ margin: 0, fontSize: '0.84rem', color: '#C9CCDE', lineHeight: 1.55, flex: 1 }}> </h3>
{accessory.tagline} <p style={{ margin: 0, color: '#8891C7', fontSize: '0.88rem', lineHeight: 1.55, flex: 1 }}>
</p> {accessory.tagline}
{accessory.compatibility && accessory.compatibility.length > 0 && ( </p>
<div style={{ paddingTop: '0.55rem', borderTop: '1px solid rgba(222,224,240,0.06)' }}>
<span style={{ display: 'block', fontSize: '0.55rem', fontWeight: 800, letterSpacing: '0.24em', textTransform: 'uppercase', color: '#8891C7', marginBottom: '0.35rem' }}>
Compatible with
</span>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexWrap: 'wrap', gap: '0.25rem' }}>
{accessory.compatibility.slice(0, 4).map((c) => (
<li
key={c}
style={{
fontSize: '0.66rem',
fontWeight: 600,
padding: '0.18rem 0.5rem',
borderRadius: 999,
border: '1px solid rgba(222,224,240,0.10)',
background: 'rgba(20,19,26,0.7)',
color: '#DEE0F0',
}}
>
{c}
</li>
))}
</ul>
</div>
)}
</div>
</article>
<style jsx>{` {accessory.compatibility && accessory.compatibility.length > 0 && (
:global(.acc-card:hover), <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexWrap: 'wrap', gap: '0.35rem' }}>
:global(.acc-card:focus-visible) { {accessory.compatibility.slice(0, 2).map((c) => (
transform: translateY(-3px); <li
border-color: ${accent}66 !important; key={c}
box-shadow: style={{
0 1px 0 rgba(222,224,240,0.08) inset, fontSize: '0.7rem',
0 24px 52px rgba(0,0,0,0.55), color: '#8891C7',
0 0 28px ${accent}33 !important; padding: '0.2rem 0.5rem',
} borderRadius: 999,
`}</style> border: '1px solid rgba(39, 63, 148,0.18)',
</Link> background: 'rgba(15, 12, 8,0.5)',
letterSpacing: '0.04em',
}}
>
{c}
</li>
))}
</ul>
)}
<div
style={{
marginTop: 'auto',
paddingTop: '0.75rem',
borderTop: '1px solid rgba(39, 63, 148,0.1)',
display: 'flex',
flexWrap: 'wrap',
gap: '0.4rem',
}}
>
<Link
href={detailHref}
className="card-cta card-cta-primary"
style={{
flex: '1 1 auto',
minWidth: 0,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.4rem',
padding: '0.55rem 0.75rem',
borderRadius: 10,
background: 'linear-gradient(135deg, rgba(80, 110, 255, 0.32), rgba(40, 55, 145, 0.85))',
border: '1px solid rgba(120, 140, 255, 0.55)',
color: '#FFFFFF',
fontSize: '0.72rem',
fontWeight: 700,
letterSpacing: '0.12em',
textTransform: 'uppercase',
textDecoration: 'none',
boxShadow: '0 6px 14px rgba(0, 0, 0, 0.35), 0 0 14px rgba(74, 102, 216, 0.22)',
}}
>
View details
</Link>
<Link
href={`${detailHref}#inquire`}
style={{
flex: '1 1 auto',
minWidth: 0,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0.55rem 0.75rem',
borderRadius: 10,
background: 'rgba(20, 19, 26, 0.6)',
border: '1px solid rgba(222, 224, 240, 0.14)',
color: '#DEE0F0',
fontSize: '0.72rem',
fontWeight: 600,
letterSpacing: '0.1em',
textTransform: 'uppercase',
textDecoration: 'none',
}}
>
Request price
</Link>
<Link
href="/book-demo/"
style={{
flex: '1 1 auto',
minWidth: 0,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0.55rem 0.75rem',
borderRadius: 10,
background: 'rgba(20, 19, 26, 0.6)',
border: '1px solid rgba(222, 224, 240, 0.14)',
color: '#DEE0F0',
fontSize: '0.72rem',
fontWeight: 600,
letterSpacing: '0.1em',
textTransform: 'uppercase',
textDecoration: 'none',
}}
>
Book demo
</Link>
</div>
</div>
</article>
); );
} }

View File

@ -153,14 +153,15 @@ export function RobotProductCard({ robot, priority = false }: Props) {
gap: '0.4rem', gap: '0.4rem',
padding: '0.55rem 0.75rem', padding: '0.55rem 0.75rem',
borderRadius: 10, borderRadius: 10,
background: `${robot.accent}1F`, background: 'linear-gradient(135deg, rgba(80, 110, 255, 0.32), rgba(40, 55, 145, 0.85))',
border: `1px solid ${robot.accent}66`, border: '1px solid rgba(120, 140, 255, 0.55)',
color: robot.accent, color: '#FFFFFF',
fontSize: '0.72rem', fontSize: '0.72rem',
fontWeight: 600, fontWeight: 700,
letterSpacing: '0.1em', letterSpacing: '0.12em',
textTransform: 'uppercase', textTransform: 'uppercase',
textDecoration: 'none', textDecoration: 'none',
boxShadow: '0 6px 14px rgba(0, 0, 0, 0.35), 0 0 14px rgba(74, 102, 216, 0.22)',
}} }}
> >
View details View details
@ -175,8 +176,8 @@ export function RobotProductCard({ robot, priority = false }: Props) {
justifyContent: 'center', justifyContent: 'center',
padding: '0.55rem 0.75rem', padding: '0.55rem 0.75rem',
borderRadius: 10, borderRadius: 10,
background: 'transparent', background: 'rgba(20, 19, 26, 0.6)',
border: '1px solid rgba(39, 63, 148,0.2)', border: '1px solid rgba(222, 224, 240, 0.14)',
color: '#DEE0F0', color: '#DEE0F0',
fontSize: '0.72rem', fontSize: '0.72rem',
fontWeight: 600, fontWeight: 600,
@ -197,8 +198,8 @@ export function RobotProductCard({ robot, priority = false }: Props) {
justifyContent: 'center', justifyContent: 'center',
padding: '0.55rem 0.75rem', padding: '0.55rem 0.75rem',
borderRadius: 10, borderRadius: 10,
background: 'transparent', background: 'rgba(20, 19, 26, 0.6)',
border: '1px solid rgba(39, 63, 148,0.2)', border: '1px solid rgba(222, 224, 240, 0.14)',
color: '#DEE0F0', color: '#DEE0F0',
fontSize: '0.72rem', fontSize: '0.72rem',
fontWeight: 600, fontWeight: 600,

View File

@ -8,6 +8,33 @@ export type AccessoryGroup =
| 'attachment' | 'attachment'
| 'iot-integration'; | 'iot-integration';
export interface AccessoryHighlight {
value: string;
label: string;
sub?: string;
}
export interface AccessoryFeatureSection {
eyebrow?: string;
title: string;
body: string;
bullets?: string[];
image?: string;
imageAlt?: string;
}
export interface SpecRow {
label: string;
value: string;
valueAlt?: string;
}
export interface SpecGroup {
title: string;
variants?: string[];
rows: SpecRow[];
}
export interface Accessory { export interface Accessory {
id: string; id: string;
slug: string; slug: string;
@ -22,6 +49,14 @@ export interface Accessory {
accent: string; accent: string;
officialUrl?: string; officialUrl?: string;
compatibility?: string[]; compatibility?: string[];
/* Optional rich content */
highlights?: AccessoryHighlight[];
featureSections?: AccessoryFeatureSection[];
specGroups?: SpecGroup[];
paramsImage?: string;
footnotes?: string[];
safetyNotice?: string;
} }
export const GROUP_LABELS: Record<AccessoryGroup, string> = { export const GROUP_LABELS: Record<AccessoryGroup, string> = {
@ -46,10 +81,10 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'Dex2-5 Dexterous Hand', name: 'Dex2-5 Dexterous Hand',
tagline: 'Tendon-driven 5-finger hand with 100,000+ fatigue cycles tested.', tagline: 'Gala-style tendon-driven dexterous hand — precise control for unbeatable strength.',
group: 'dexterous-hand', group: 'dexterous-hand',
description: description:
'A 5-finger, 10-DOF tendon-driven dexterous hand for G1 and R1 humanoids. Gear-tendon drive with hidden internal wiring, 365 g lightweight design, and full perceptual feedback at 1 kHz.', 'A 10-DOF (2 actuated) 5-finger tendon-driven dexterous hand for G1 and R1 humanoids. Gear-tendon drive with hidden internal wiring, 365 g lightweight design, and full perceptual feedback at 1 kHz.',
features: [ features: [
'10 DOF total (2 actuated), 5 fingers', '10 DOF total (2 actuated), 5 fingers',
'365 g, 151×70×63 mm', '365 g, 151×70×63 mm',
@ -62,6 +97,70 @@ export const ACCESSORIES: Accessory[] = [
accent: GOLD_CHAMPAGNE, accent: GOLD_CHAMPAGNE,
officialUrl: 'https://www.unitree.com/Dex2-5', officialUrl: 'https://www.unitree.com/Dex2-5',
compatibility: ['Unitree G1', 'Unitree R1'], compatibility: ['Unitree G1', 'Unitree R1'],
highlights: [
{ value: '10', label: 'Degrees of freedom', sub: '2 actuated, 5-finger' },
{ value: '365g', label: 'Weight', sub: 'Lightweight build' },
{ value: '100,000+', label: 'Fatigue cycles', sub: 'Ultra-long fatigue life' },
{ value: '100+', label: 'Drop tests passed', sub: 'Rugged impact resistance' },
],
featureSections: [
{
eyebrow: 'Durability',
title: 'Ultra-long fatigue life for greater durability.',
body: 'The entire hand has passed over 100,000 fatigue cycles, ensuring stable and reliable long-term operation.',
},
{
eyebrow: 'Impact resistance',
title: 'Rugged design — impervious to knocks and bumps.',
body: 'Completed over 100 drop tests. Superior robust design ensures the dexterous hand is no longer fragile.',
},
{
eyebrow: 'Integration',
title: 'Hidden internal wiring.',
body: 'Compatible with G1 and R1 humanoids. The “meridians” run beneath the “skin” for a clean, durable mechanical envelope.',
bullets: ['Compatible with Unitree G1', 'Compatible with Unitree R1', 'Lightweight and compact'],
},
],
specGroups: [
{
title: 'Parameters',
rows: [
{ label: 'Weight', value: '365 g' },
{ label: 'Size¹', value: '151 × 70 × 63 mm' },
{ label: 'Degrees of freedom', value: 'Thumb × 1; Index, Middle, Ring, Little: share one DOF' },
{ label: 'Transmission', value: 'Gear-tendon rope drive' },
{ label: 'Thumb joint 0', value: '0° to 42°' },
{ label: 'Thumb joint 1', value: '0° to 105°' },
{ label: 'Four-finger joint 0', value: '0° to 88°' },
{ label: 'Four-finger joint 1', value: '0° to 105°' },
{ label: 'Minimum fist clench time', value: '0.5 s' },
{ label: 'Minimum grip diameter', value: '8 mm' },
{ label: 'Fingertip force', value: 'Thumb 15 N; index/middle/ring/little total 15 N' },
{ label: 'Working voltage', value: '24 V 60 V' },
{ label: 'Static current', value: '58 V @ 0.1 A' },
{ label: 'Maximum current', value: '58 V @ 1.5 A' },
{ label: 'Communication interface', value: 'RS485' },
{ label: 'Working temperature range', value: '20 °C to 60 °C' },
{ label: 'Load (palm facing down, 5 cm round hard object)', value: '1.5 kg max' },
{ label: 'Load (palm facing left, 5 cm round hard object)', value: '1.5 kg max' },
],
},
{
title: 'Software Function',
rows: [
{ label: 'Communication rate', value: '1000 Hz' },
{ label: 'Perceptual feedback', value: 'Joint mode, position, velocity, torque, temperature, voltage and current' },
{ label: 'Control feedback', value: 'Joint mode, position, velocity, torque, stiffness coefficient, damping coefficient' },
],
},
],
footnotes: [
'Size refers to the dexterous hand in flat state.',
'Parameters listed above may vary in different application scenarios or with different model configurations.',
'Product appearance is subject to change. Refer to the final product.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
{ {
id: 'unitree-dex5-1', id: 'unitree-dex5-1',
@ -69,22 +168,133 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'Dex5-1 Dexterous Hand', name: 'Dex5-1 Dexterous Hand',
tagline: 'High-DOF dexterous hand with 94 tactile sensors for RL workflows.', tagline: 'Smart adaptability, instant responsiveness.',
group: 'dexterous-hand', group: 'dexterous-hand',
description: description:
'A 20-DOF (16 actuated) dexterous hand with 94 tactile sensors and backdrivable joints designed for reinforcement-learning workflows. ±22° four-finger lateral swing and operation from 20 °C to 60 °C.', 'A 20-DOF (16 actuated) dexterous hand with 94 tactile sensors and backdrivable joints designed for reinforcement-learning workflows. Each finger joint supports smooth backdrivability, eliminating rigidity for smoother and more convenient operations. ±22° four-finger lateral swing and operation from 20 °C to 60 °C.',
features: [ features: [
'20 DOF (16 active + 4)', '20 DOF (16 active + 4)',
'94 tactile sensors', '94 tactile sensors per hand',
'1100 g, 20 °C to 60 °C', '1100 g, 217.3 × 127.5 × 72.1 mm',
'3.5 kg palm-down / 4.5 kg palm-side load', '3.5 kg palm-down / 4.5 kg palm-side payload',
'±22° four-finger lateral swing', '±22° four-finger lateral swing',
'Backdrivable, low-damping reducers', 'Hollow-cup motors + high-precision encoders',
'Low-damping, small-clearance reducers',
'1000 Hz comm + sensor rate',
], ],
image: '/images/accessories/unitree-dex5-1.png', image: '/images/accessories/unitree-dex5-1.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.unitree.com/Dex5-1', officialUrl: 'https://www.unitree.com/Dex5-1',
compatibility: ['Unitree G1', 'Unitree H1', 'Unitree H2', 'Research arms'], compatibility: ['Unitree G1', 'Unitree H1', 'Unitree H2', 'Research arms'],
highlights: [
{ value: '20', label: 'Degrees of freedom', sub: '16 active + 4' },
{ value: '94', label: 'Tactile sensors', sub: 'Per hand' },
{ value: '±22°', label: 'Lateral swing', sub: 'Four-finger range' },
{ value: '1000 Hz', label: 'Comm rate', sub: 'Real-time control' },
],
featureSections: [
{
eyebrow: 'Reflexes & flexibility',
title: 'Ultra-fast reflexes, snake-like flexibility.',
body: 'Twenty degrees of freedom plus five independently replaceable fingers deliver responsive grasps and natural articulation. Each finger joint backdrives smoothly, eliminating rigidity for reinforcement-learning workflows.',
bullets: [
'20 degrees of freedom',
'5 fingers — each replaceable independently',
'Backdrivable joints with low damping',
],
},
{
eyebrow: 'Four-finger lateral swing',
title: '±22° lateral range for reliable curved grips.',
body: 'The four-finger lateral swing improves grip reliability and adapts more effectively to curved surfaces of grasped objects — bottles, tools, glassware, and irregular shapes.',
},
{
eyebrow: 'Sensing & feedback',
title: 'Precise feedback. Control every millimeter.',
body: 'A high-power-density hollow-cup motor pairs with a high-precision encoder and a low-damping, small-clearance reducer. A micro-gap design keeps rotation axes close to the contact surface, preventing corner interference during grasping. Tactile algorithms support secondary development.',
bullets: [
'94 tactile sensors with palm + fingertip + finger-root arrays',
'High-power-density hollow-cup motor',
'High-precision encoder',
'Micro-gap, low-damping reducers',
'Secondary-development support for tactile algorithms',
],
},
],
specGroups: [
{
title: 'Body Parameters',
variants: ['Dex5-1', 'Dex5-1P'],
rows: [
{ label: 'Weight', value: '1100 g', valueAlt: '1100 g' },
{ label: 'Size¹', value: '217.3 × 127.5 × 72.1 mm', valueAlt: '217.3 × 127.5 × 72.1 mm' },
{ label: 'Degrees of freedom', value: 'Thumb × 4, Index × 3, Middle × 3, Ring × 3, Little × 3', valueAlt: 'Thumb × 4, Index × 3, Middle × 3, Ring × 3, Little × 3' },
{ label: 'Transmission', value: '12 self-developed micro force-controlled composite joints (tactile proprioception) + 4 micro force-controlled gear joints', valueAlt: '12 self-developed micro force-controlled composite joints (tactile proprioception) + 4 micro force-controlled gear joints' },
],
},
{
title: 'Joint Angle Ranges',
rows: [
{ label: 'Thumb joint 0', value: '33.5° to 39°' },
{ label: 'Thumb joint 1', value: '0° to 100°' },
{ label: 'Thumb joint 2', value: '0° to 110°' },
{ label: 'Thumb joint 3', value: '0° to 92°' },
{ label: 'Four-finger knuckle 0', value: '22° to 22°' },
{ label: 'Four-finger knuckle 1', value: '0° to 90°' },
{ label: 'Four-finger knuckle 2', value: '0° to 95°' },
{ label: 'Four-finger knuckle 3', value: '0° to 81° (coupled with joint 2)' },
],
},
{
title: 'Performance',
rows: [
{ label: 'Four-finger lateral swing', value: '±22°' },
{ label: 'Minimum grip diameter', value: '10 mm' },
{ label: 'Fingertip repeat-positioning accuracy', value: '±1 mm' },
{ label: 'Fingertip force', value: '10 N' },
{ label: 'Working voltage', value: '24 V 60 V' },
{ label: 'Static current', value: '58 V @ 0.2 A' },
{ label: 'Maximum current', value: '58 V @ 4 A' },
{ label: 'Communication interface', value: 'USB 2.0' },
{ label: 'Working temperature range', value: '20 °C to 60 °C' },
],
},
{
title: 'Load Conditions²',
rows: [
{ label: 'Palm facing down (room temp, 5 cm round hard object)', value: '3.5 kg max' },
{ label: 'Palm facing left (room temp, 5 cm round hard object)', value: '4.5 kg max' },
],
},
{
title: 'Sensing Parameters (Dex5-1P)',
rows: [
{ label: 'Pressure sensors', value: '94 total (palm 12 + finger pads + fingertips + finger roots)' },
{ label: 'Array resolution', value: '2×5 palm; 2×3 finger pad ×5; 2×3 fingertip ×5; 2×3 finger root ×4' },
{ label: 'Perception range', value: '10 g 2500 g' },
{ label: 'Max acceptance (undamaged)', value: '20 kg' },
],
},
{
title: 'Software',
variants: ['Dex5-1', 'Dex5-1P'],
rows: [
{ label: 'Communication rate', value: '1000 Hz', valueAlt: '1000 Hz' },
{ label: 'Full packet bytes', value: 'Sender 1234 / Receiver 1270', valueAlt: 'Sender 1234 / Receiver 1270' },
{ label: 'Perceptual feedback', value: 'Joint mode, position, velocity, torque, temperature, voltage/current, IMU data', valueAlt: 'Above + sensor pressure value, sensor temperature value' },
{ label: 'Control feedback', value: 'Joint mode, position, velocity, torque, stiffness coefficient, damping coefficient', valueAlt: 'Joint mode, position, velocity, torque, stiffness coefficient, damping coefficient' },
],
},
],
paramsImage: '/images/accessories/unitree-dex5-1/params.png',
footnotes: [
'Size refers to dexterous hand in flat state.',
'Pressure data represents force from a vertically-applied 1 cm diameter cylinder; varies by application scenario.',
'Parameters may vary by scenario or configuration; subject to actual situation.',
'Product appearance subject to change; refer to the final product.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
{ {
id: 'unitree-dex3-1', id: 'unitree-dex3-1',
@ -92,22 +302,113 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'Dex3-1 Dexterous Hand', name: 'Dex3-1 Dexterous Hand',
tagline: 'Power-control 3-finger hand with hybrid force/position control.', tagline: 'Power control dexterous hand — operate everything.',
group: 'dexterous-hand', group: 'dexterous-hand',
description: description:
'A 3-finger, 7-DOF dexterous hand with 33 tactile pressure sensors and hybrid force/position control. Designed to pair with the G1 humanoid for general-purpose manipulation.', 'A 3-finger, 7-DOF dexterous hand with 33 tactile pressure sensors and hybrid force/position control. Sensitive and reliable for precise object manipulation. Pairs natively with the G1 humanoid for complex grasping and manipulation workflows.',
features: [ features: [
'7 DOF, 3 fingers', '7 DOF, 3 fingers',
'33 tactile pressure sensors', '6 motors direct drive + 1 gear drive',
'33 tactile pressure sensors per hand',
'±2 mm fingertip repeatability', '±2 mm fingertip repeatability',
'500 g payload', '710 g, 175 × 88 × 77 mm',
'1258 V operating voltage', '1258 V operating voltage',
'1 kHz communication rate', '1 kHz communication rate',
], ],
image: '/images/accessories/unitree-dex3-1.png', image: '/images/accessories/unitree-dex3-1.jpg',
accent: GOLD_BRONZE, accent: GOLD_BRONZE,
officialUrl: 'https://www.unitree.com/Dex3-1', officialUrl: 'https://www.unitree.com/Dex3-1',
compatibility: ['Unitree G1'], compatibility: ['Unitree G1'],
highlights: [
{ value: '7', label: 'Degrees of freedom', sub: '3-finger dexterous hand' },
{ value: '33', label: 'Tactile sensors', sub: '9 pressure-sensor groups' },
{ value: '710 g', label: 'Weight', sub: '175 × 88 × 77 mm' },
{ value: '±2 mm', label: 'Fingertip accuracy', sub: 'Repeat positioning' },
],
featureSections: [
{
eyebrow: 'Data collection',
title: 'Collect data — sensitive and accurate.',
body: 'Open-source data-acquisition workflow for embodied-AI research. Pairs with the G1 humanoid to perform a wide range of complex actions.',
bullets: ['Open-source links available', 'Pair with G1 humanoid robot'],
},
{
eyebrow: 'Architecture',
title: 'Three-finger dexterous hand built for manipulation.',
body: 'Dex3-1 combines 7 degrees of freedom with 6 micro brushless force-controlled joint direct drives and 1 micro brushless force-controlled gear drive. Each hand carries 33 tactile sensors. Compatible with G1 to perform complex actions in real workflows.',
bullets: ['6 motors direct drive', '1 motor gear drive', '33 tactile sensors per hand', 'Compatible with G1 humanoid'],
},
],
specGroups: [
{
title: 'Body Parameters',
rows: [
{ label: 'Weight', value: '710 g' },
{ label: 'Size¹', value: '175 × 88 × 77 mm' },
{ label: 'Degrees of freedom', value: 'Thumb × 3; Index × 2; Middle × 2' },
{ label: 'Transmission', value: '6 motors drive directly; 1 motor drives with gears' },
{ label: 'Thumb joint 0', value: '60° to 60°' },
{ label: 'Thumb joint 1', value: '35° to 60°' },
{ label: 'Thumb joint 2', value: '0° to 100°' },
{ label: 'Index joint 0', value: '0° to 90°' },
{ label: 'Index joint 1', value: '0° to 100°' },
{ label: 'Middle joint 0', value: '0° to 90°' },
{ label: 'Middle joint 1', value: '0° to 100°' },
{ label: 'Fingertip repeat-positioning accuracy', value: '±2 mm' },
{ label: 'Working voltage', value: '12 V 58 V' },
{ label: 'Static current', value: '58 V @ 0.2 A' },
{ label: 'Maximum current', value: '10 A' },
{ label: 'Communication interface', value: 'USB 2.0' },
{ label: 'Working temperature range', value: '20 °C to 60 °C' },
{ label: 'Load (palm down, 5 cm round hard object)', value: '500 g max / 400 W peak @ 3 s' },
{ label: 'Load (palm left, 5 cm round hard object)', value: '500 g max / 400 W peak @ 3 s' },
],
},
{
title: 'Sensing Parameters',
rows: [
{ label: 'Pressure sensor groups', value: '9 (33 sensors total)' },
{ label: 'Array resolution', value: '2×2 palm ×3; 2×2 finger pad ×3; 3×1 fingertip ×3' },
{ label: 'Perception range', value: '10 g 2500 g' },
{ label: 'Max acceptance (undamaged)', value: '20 kg' },
],
},
{
title: 'Software Functions',
rows: [
{ label: 'Communication rate', value: '1000 Hz' },
{ label: 'Full packet bytes', value: 'Sender 1234 / Receiver 1270' },
{ label: 'Perceptual feedback', value: 'Joint mode, position, velocity, torque, temperature, voltage/current, sensor pressure, sensor temperature, IMU data' },
{ label: 'Control feedback', value: 'Joint mode, position, velocity, torque, stiffness coefficient, damping coefficient' },
],
},
{
title: 'Miniature Brushless Force-Control Joint',
variants: ['F-1515-108', 'F-1515-214'],
rows: [
{ label: 'Weight', value: '45 g', valueAlt: '45 g' },
{ label: 'Size', value: '34.8 × 23.1 × 23.3 mm', valueAlt: '34.8 × 23.1 × 23.3 mm' },
{ label: 'Reduction ratio', value: '1:108', valueAlt: '1:214' },
{ label: 'Maximum torque (ideal)', value: '0.76 N·m', valueAlt: '1.498 N·m' },
{ label: 'Max torque (same direction as speed)', value: '0.49 N·m', valueAlt: '0.86 N·m' },
{ label: 'Max torque (opposite direction)', value: '1.37 N·m', valueAlt: '3.1 N·m' },
{ label: 'Maximum rotational speed', value: '23 rad/s', valueAlt: '11 rad/s' },
{ label: 'Maximum line current', value: '4.76 A', valueAlt: '4.76 A' },
{ label: 'Communication frequency', value: '1000 Hz', valueAlt: '1000 Hz' },
{ label: 'Communication method', value: 'High-speed Unibus', valueAlt: 'High-speed Unibus' },
{ label: 'Encoder', value: 'Rotor absolute value encoder', valueAlt: 'Rotor absolute value encoder' },
{ label: 'Working temperature', value: '20 °C to 60 °C', valueAlt: '20 °C to 60 °C' },
{ label: 'Working voltage', value: '12 V 24 V', valueAlt: '12 V 24 V' },
],
},
],
paramsImage: '/images/accessories/unitree-dex3-1/diagram.png',
footnotes: [
'Size refers to the dexterous hand in flat state.',
'Pressure data refers to force exerted by the dexterous hand when pressed by a vertical 1 cm diameter cylinder. Varies in different application scenarios.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
{ {
id: 'unitree-dex1-1', id: 'unitree-dex1-1',
@ -115,15 +416,16 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'Dex1-1 Embodied AI Gripper', name: 'Dex1-1 Embodied AI Gripper',
tagline: 'Two-finger embodied-AI gripper with optional 1080p camera.', tagline: 'Professional and refined — extra-large stroke, powerful force, ultimate control.',
group: 'dexterous-hand', group: 'dexterous-hand',
description: description:
'A two-finger gripper with 90 mm stroke, 0.1 mm resolution, and 5120 N adjustable force. Available in Standard and Advanced (with 1080p camera) variants, compatible with G1, H2, R1, or standalone use.', 'A two-finger gripper designed specifically for embodied AI applications. Extra-large 90 mm stroke, adjustable 5120 N gripping force, and 50375 mm/s adjustable speed deliver versatile pick-and-place performance. Available in Standard and Advanced (with 1080p camera) variants, compatible with G1, H2, R1, or standalone use.',
features: [ features: [
'90 mm stroke, 5120 N adjustable force', '90 mm stroke, 5120 N adjustable force',
'0.1 mm fingertip resolution', '0.1 mm fingertip position resolution',
'0.24 s full-stroke close', '0.24 s full-stroke close',
'20 kg form-fit grasp payload', '20 kg form-fit grasp payload',
'5 kg friction grasp load',
'6 Mbps RS485 communication', '6 Mbps RS485 communication',
'Advanced version includes 1080p camera', 'Advanced version includes 1080p camera',
], ],
@ -131,6 +433,74 @@ export const ACCESSORIES: Accessory[] = [
accent: GOLD_CHAMPAGNE, accent: GOLD_CHAMPAGNE,
officialUrl: 'https://www.unitree.com/Dex1-1', officialUrl: 'https://www.unitree.com/Dex1-1',
compatibility: ['Unitree G1', 'Unitree H2', 'Unitree R1', 'Standalone'], compatibility: ['Unitree G1', 'Unitree H2', 'Unitree R1', 'Standalone'],
highlights: [
{ value: '90 mm', label: 'Stroke', sub: 'Extra-large opening range' },
{ value: '120 N', label: 'Gripping force', sub: 'Adjustable 5120 N' },
{ value: '20 kg', label: 'Form-fit load', sub: '5 kg friction load' },
{ value: '0.24 s', label: 'Full-stroke close', sub: '0.1 mm fingertip resolution' },
],
featureSections: [
{
eyebrow: 'Design',
title: 'Extra-large stroke, powerful force, ultimate control.',
body: 'A two-finger gripper designed specifically for embodied AI applications. Extra-large 90 mm stroke, adjustable 5120 N gripping force, and 50375 mm/s speed cover laboratory through industrial workflows.',
bullets: ['90 mm stroke', '5120 N gripping force', '0.1 mm fingertip resolution', '80 mm jaw length'],
},
{
eyebrow: 'Industry applications',
title: 'Built for real-world deployment.',
body: 'From research labs to industrial floors, Dex1-1 adapts to a wide range of pick-and-place scenarios.',
bullets: ['Scientific research and education', 'Courier sorting', 'Industrial automation', 'New retail'],
},
],
specGroups: [
{
title: 'Gripper Parameters',
rows: [
{ label: 'Model', value: 'Dex1-1' },
{ label: 'Dimensions¹', value: '143 × 78 × 67 mm' },
{ label: 'Weight', value: '550 g' },
{ label: 'Stroke', value: '90 mm' },
{ label: 'Gripping force² (adjustable)', value: '5 120 N' },
{ label: 'Speed² (adjustable)', value: '50 375 mm/s' },
{ label: 'Fingertip position resolution', value: '0.1 mm' },
{ label: 'Grasp load³ (form-fitting)', value: '20 kg' },
{ label: 'Grasp load⁴ (friction)', value: '5 kg' },
{ label: 'Full-stroke shortest closing time²', value: '0.24 s' },
{ label: 'Jaw length', value: '80 mm' },
],
},
{
title: 'Motor Parameters',
rows: [
{ label: 'Communication protocol', value: 'High-speed 485' },
{ label: 'Communication baud rate', value: '6 Mbps' },
{ label: 'Communication frequency', value: '1000 Hz' },
{ label: 'Operating voltage', value: '24 V 64 V (24 / 48 VDC recommended)' },
{ label: 'Static current', value: '0.03 A' },
{ label: 'Peak line current', value: '3.5 A' },
{ label: 'Motor peak torque', value: '5 N·m' },
{ label: 'Motor max speed', value: '27 rad/s (with 64 V power supply)' },
{ label: 'Recommended operating environment', value: '20 °C to 60 °C' },
{ label: 'Motor reduction ratio', value: '1:25' },
{ label: 'Encoder resolution', value: '15 bit' },
{ label: 'Encoder type', value: 'Absolute encoder' },
{ label: 'Motor status feedback', value: 'Angle, angular velocity, torque, temperature' },
{ label: 'Motor control instruction', value: 'Angle, angular velocity, torque, stiffness, damping' },
],
},
],
paramsImage: '/images/accessories/unitree-dex1-1/diagram.png',
footnotes: [
'The gripper width is 78 mm when half-open, 120 mm when fully open, and 124.5 mm when fully closed (with side rails extended).',
'Listed values for speed, gripping force, and full-stroke closing time are measured under varying conditions (24 V 60 V) by adjusting motor stiffness, damping, and torque. Values may vary depending on the application.',
'Grasp load (form-fitting) refers to the maximum horizontal load the gripper can hold without losing its grip. Varies by application.',
'Grasp load (friction) refers to the maximum vertical load the gripper can hold without slipping, measured with a square steel block. Varies by application.',
'All parameters above may vary in different application scenarios or model configurations.',
'Product appearance is subject to change. Refer to the final product.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
// ============ UNITREE ROBOTIC ARMS ============ // ============ UNITREE ROBOTIC ARMS ============
@ -140,10 +510,10 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'Z1 6-Axis Manipulator', name: 'Z1 6-Axis Manipulator',
tagline: 'Onboard 6-axis manipulator for mobile robotics platforms.', tagline: 'Perfect synergy — onboard 6-axis manipulator for mobile platforms.',
group: 'robotic-arm', group: 'robotic-arm',
description: description:
'Z1 Air (4.3 kg) and Z1 Pro (4.5 kg) 6-axis robotic arms for mobile robots in logistics, retail, and research. 740 mm reach, 23 kg payload, ~0.1 mm repeatability. Designed for AlienGo and B1 mounting.', 'Z1 Air (4.3 kg) and Z1 Pro (4.5 kg) 6-axis robotic arms designed for diverse application scenarios — e-commerce logistics, new consumption, daily life, and research. 740 mm reach, 23 kg payload, and ~0.1 mm repeatability. Pairs natively with AlienGo or B1 quadruped chassis and supports joint force control with collision protection.',
features: [ features: [
'6-axis, 740 mm reach', '6-axis, 740 mm reach',
'4.3 kg (Air) / 4.5 kg (Pro)', '4.3 kg (Air) / 4.5 kg (Pro)',
@ -151,11 +521,100 @@ export const ACCESSORIES: Accessory[] = [
'~0.1 mm repeatability', '~0.1 mm repeatability',
'Joint force control + collision detection', 'Joint force control + collision detection',
'Position + force control modes', 'Position + force control modes',
'Harmonic reducer joint motors',
'Ethernet interface, Ubuntu OS',
], ],
image: '/images/accessories/unitree-z1.png', image: '/images/accessories/unitree-z1.png',
accent: GOLD_BRAND, accent: GOLD_BRAND,
officialUrl: 'https://www.unitree.com/z1', officialUrl: 'https://www.unitree.com/z1',
compatibility: ['Unitree AlienGo', 'Unitree B1', 'Mobile platforms'], compatibility: ['Unitree AlienGo', 'Unitree B1', 'Mobile platforms'],
highlights: [
{ value: '6', label: 'Axis DOF', sub: 'Force-controllable' },
{ value: '740 mm', label: 'Reach', sub: 'Z1 Air / Z1 Pro' },
{ value: '≥3 kg', label: 'Payload (Pro)', sub: '2 kg on Air' },
{ value: '~0.1 mm', label: 'Repeatability', sub: 'Position + force control' },
],
featureSections: [
{
eyebrow: 'Characteristics',
title: 'Designed for mobile manipulation.',
body: 'Compact, lightweight, and dexterous, the Z1 delivers sufficient payload and accuracy for mobile-robot pairings. Supports joint force control with built-in collision protection.',
bullets: [
'Compact and lightweight',
'Dexterous and flexible',
'Sufficient payload',
'Sufficient accuracy',
'Joint force control',
'Collision protection',
],
},
{
eyebrow: 'Open programming + extensible interface',
title: 'Open SDK, swappable end-effectors.',
body: 'The manipulator control program and control interface are opened progressively, and different actuators can be quickly replaced at the end of the manipulator for varied workflows.',
},
{
eyebrow: 'Application',
title: 'Mobile manipulation across sectors.',
body: 'Onboard manipulator for various mobile robots — e-commerce logistics, new consumption, daily-life automation, and research labs. Native AlienGo / B1 mount.',
bullets: ['Pairs with AlienGo or B1 quadruped', 'Onboard manipulator for mobile robots'],
},
],
specGroups: [
{
title: 'Joint Motor',
rows: [
{ label: 'Backlash', value: '~6 arcmin' },
{ label: 'Communication method', value: 'RS485' },
{ label: 'Maximum torque', value: '33 N·m' },
{ label: 'Encoder resolution', value: '15 bit' },
{ label: 'Weight', value: '405 g' },
{ label: 'Force control accuracy', value: '~0.2 N·m' },
{ label: 'Size', value: 'Φ65 × 52 mm' },
{ label: 'Control frequency', value: '1 kHz' },
{ label: 'Reducer', value: 'Harmonic reducer' },
{ label: 'Bearing', value: 'Industrial-grade cross roller' },
{ label: 'Reduction ratio', value: '60+' },
{ label: 'Motor sensing feedback', value: 'Torque, angle, angular velocity' },
{ label: 'Voltage', value: '24 V (recommended)' },
{ label: 'Motor control command', value: 'Torque, angle, angular velocity, stiffness, damping' },
],
},
{
title: 'Parameters',
variants: ['Z1 Air', 'Z1 Pro'],
rows: [
{ label: 'DOF', value: '6 Axis', valueAlt: '6 Axis' },
{ label: 'Weight', value: '4.3 kg', valueAlt: '4.5 kg' },
{ label: 'Payload', value: '2 kg', valueAlt: '≥3 kg' },
{ label: 'Reach', value: '740 mm', valueAlt: '740 mm' },
{ label: 'Repeatability¹', value: '~0.1 mm', valueAlt: '~0.1 mm' },
{ label: 'Power supply', value: '24 V, >20 A', valueAlt: '24 V, >20 A' },
{ label: 'Interface', value: 'Ethernet', valueAlt: 'Ethernet' },
{ label: 'Operating system', value: 'Ubuntu', valueAlt: 'Ubuntu' },
{ label: 'Power', value: 'MAX 500 W', valueAlt: 'MAX 500 W' },
{ label: 'Force feedback + collision detection', value: 'Provided', valueAlt: 'Provided' },
{ label: 'Control interface', value: 'Position + Force Control', valueAlt: 'Position + Force Control' },
],
},
{
title: 'Joint Range & Speed',
rows: [
{ label: 'J1', value: '±150° · 180°/s' },
{ label: 'J2', value: '0° to 180° · 180°/s' },
{ label: 'J3', value: '165° to 0° · 180°/s' },
{ label: 'J4', value: '±80° · 180°/s' },
{ label: 'J5', value: '±85° · 180°/s' },
{ label: 'J6', value: '±160° · 180°/s' },
],
},
],
footnotes: [
'Accuracy depends on actual test conditions; standards for manipulator testing vary, and accuracy varies under different test conditions.',
'Because each joint uses a relatively low reduction ratio, whole-machine position control stiffness is low. If the control mode is not optimized, there may be position-control error and shaking during movement.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
{ {
id: 'unitree-d1-t', id: 'unitree-d1-t',
@ -163,22 +622,78 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: 'D1-T Teleoperation Arm', name: 'D1-T Teleoperation Arm',
tagline: 'Teleoperation robot arm for embodied-AI data collection.', tagline: 'Teleoperation robot arm — empowers embodied intelligence research.',
group: 'robotic-arm', group: 'robotic-arm',
description: description:
'Dual-arm or quad-arm teleoperation kit with 2.37 kg ultra-light aluminum design. Bus servos featuring position/velocity/force control for embodied-AI data collection. 7-DOF (6-axis + gripper), 670 mm reach, 500 g payload.', 'Dual-arm (<$8,500) or quad-arm (<$16,000) teleoperation kit with 2.37 kg aluminum-alloy body and 670 mm reach. Ultra-precision force-controlled digital servo motors with position, velocity, and force control modes. Designed for embodied-AI data acquisition and continuous learning workflows.',
features: [ features: [
'7-DOF arm (6-axis + gripper)', '7-DOF arm (6 axis + gripper)',
'2.37 kg, 670 mm reach, 500 g payload', '2.37 kg aluminum alloy, 670 mm reach',
'500 g payload',
'Ultra-precision force-controlled servos', 'Ultra-precision force-controlled servos',
'Dual-arm / Quad-arm configurations', 'Dual-arm / Quad-arm kit options',
'Camera & mobile-chassis ready', 'Camera + mobile-chassis ready',
'24 V (2.5 A standard / 5 A max)', '24 V @ 2.5 A standard / 5 A max',
'RJ45 + Type-C debug interface',
], ],
image: '/images/accessories/unitree-d1-t.png', image: '/images/accessories/unitree-d1-t.jpg',
accent: GOLD_BRONZE, accent: GOLD_BRONZE,
officialUrl: 'https://www.unitree.com/D1-T', officialUrl: 'https://www.unitree.com/D1-T',
compatibility: ['Embodied AI research', 'Data collection'], compatibility: ['Embodied AI research', 'Data collection'],
highlights: [
{ value: '6 + 1', label: 'Degrees of freedom', sub: '6 axis + 1 gripper' },
{ value: '2.37 kg', label: 'Weight', sub: 'Aluminum alloy body' },
{ value: '670 mm', label: 'Reach', sub: 'With gripper' },
{ value: '500 g', label: 'Payload', sub: '24 V @ 2.5 A (MAX 5 A)' },
],
featureSections: [
{
eyebrow: 'Servo motor',
title: 'Ultra-precision force-controlled digital servo motor.',
body: 'Position control, velocity control, and force control in one motor — suitable for application development and learning tasks across diverse scenarios. Low-level and high-level interfaces expose direct motor control and advanced functions.',
},
{
eyebrow: 'Lightweight body',
title: 'Ultra-lightweight aluminum-alloy robot arm.',
body: 'Aluminum-alloy construction holds the full arm at 2.37 kg with a 670 mm arm length, keeping the platform mobile-ready and easy to transport.',
},
{
eyebrow: 'Degrees of freedom',
title: 'A “true” six-axis arm with gripper.',
body: 'Six joints plus a gripper add an extra degree of freedom for greater spatial flexibility — covering a wider envelope of motion than typical research arms.',
},
{
eyebrow: 'Hardware compatibility',
title: 'Ultra-flexible hardware integration.',
body: 'Connects to external devices — cameras, mobile robot chassis, sensors — for diverse application setups across embodied-AI workflows.',
},
],
specGroups: [
{
title: 'Parameters',
rows: [
{ label: 'Model', value: 'D1' },
{ label: 'Weight', value: '~2.37 kg' },
{ label: 'DOF', value: '6 Axis + 1 Gripper' },
{ label: 'Payload', value: '500 g' },
{ label: 'Reach (no gripper)', value: '550 mm' },
{ label: 'Reach (with gripper)', value: '670 mm' },
{ label: 'Power supply', value: '24 V @ 2.5 A (MAX 5 A)' },
{ label: 'Interface', value: 'DC5.5-2.1 power + RJ45 communication + Type-C debug' },
{ label: 'Motor type', value: 'Bus servo' },
{ label: 'Power', value: '60 W' },
],
},
{
title: 'Pricing',
rows: [
{ label: 'Dual-Arm Teleoperation Kit (Standard)', value: '< $8,500 (tax + freight excluded)' },
{ label: 'Quad-Arm Teleoperation Kit (Full)', value: '< $16,000 (tax + freight excluded)' },
],
},
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
// ============ UNITREE PERCEPTION & SENSORS ============ // ============ UNITREE PERCEPTION & SENSORS ============
@ -188,22 +703,120 @@ export const ACCESSORIES: Accessory[] = [
brand: 'unitree', brand: 'unitree',
brandLabel: 'Unitree', brandLabel: 'Unitree',
name: '4D LiDAR L2', name: '4D LiDAR L2',
tagline: '4D LiDAR with built-in IMU and open-source SLAM (POINT-LIO).', tagline: 'All new L2 — Unitree 4D LiDAR with built-in IMU and open-source SLAM.',
group: 'sensor', group: 'sensor',
description: description:
'A 4D LiDAR sensor with built-in 6-axis IMU and open POINT-LIO SLAM stack. 360°×96° ultra-wide field of view at 64,000 pts/s with 2 cm accuracy. 230 g compact form, 30 m range at 90% reflectivity.', 'A $419 4D LiDAR sensor with built-in 6-axis IMU and the open POINT-LIO SLAM stack. 360° × 96° ultra-wide field of view at 64,000 pts/s effective frequency, 30 m range @ 90% reflectivity, 4.5 mm distance resolution, and 2 cm measurement accuracy. 230 g compact form. Robust under 100 klux outdoor sunlight.',
features: [ features: [
'360°×96° ultra-wide FOV', '360° × 96° ultra-wide FOV (+7%)',
'64,000 pts/s, 2 cm accuracy', '64,000 pts/s effective frequency (+200%)',
'30 m @ 90% reflectivity', '4.5 mm distance resolution (+78%)',
'Built-in 6-axis IMU', '30 m range @ 90% reflectivity',
'230 g compact form', '2 cm measurement accuracy',
'Open POINT-LIO SLAM stack', '230 g, 75 × 75 × 65 mm',
'Built-in 3-axis acc + 3-axis gyro IMU',
'ENET UDP / TTL UART communication',
'Open POINT-LIO SLAM solution',
], ],
image: '/images/accessories/unitree-l2.png', image: '/images/accessories/unitree-l2.jpg',
accent: GOLD_CHAMPAGNE, accent: GOLD_CHAMPAGNE,
officialUrl: 'https://www.unitree.com/L2', officialUrl: 'https://www.unitree.com/L2',
compatibility: ['Unitree Go2', 'Unitree quadrupeds', 'Third-party robots'], compatibility: ['Unitree Go2', 'Unitree quadrupeds', 'Third-party robots'],
highlights: [
{ value: '360°×96°', label: 'Ultra-wide FOV', sub: '+7% scanning' },
{ value: '64,000', label: 'Points/s', sub: 'Effective frequency · +200%' },
{ value: '30 m', label: 'Scan range', sub: '@ 90% reflectivity' },
{ value: '4.5 mm', label: 'Distance resolution', sub: '+78% improvement' },
],
featureSections: [
{
eyebrow: 'SLAM',
title: 'Complete open-source SLAM solution.',
body: 'Use only L2 and its built-in IMU to map with the POINT-LIO algorithm — no other positioning sensors required. Open-source release on github.com/unitreerobotics/point_lio_unilidar.',
},
{
eyebrow: 'Indoor',
title: 'Whole-house dynamic scanning.',
body: 'L2 quickly and accurately captures 3D structural information of an entire house — supports robot positioning and autonomous navigation, plus extended functions like whole-house cleaning and organization.',
},
{
eyebrow: 'Outdoor',
title: 'Real-scene dynamic scanning under strong sun.',
body: 'Resists indoor ambient-light and outdoor strong-light interference. Achieves stable ranging and high-precision mapping under outdoor conditions of 100 klux.',
},
{
eyebrow: 'Scanning pattern',
title: 'Non-repetitive omnidirectional scanning.',
body: 'High-precision, high-density point-cloud data via omnidirectional ultra-wide-angle non-repetitive scanning — photograph-level scanning detail.',
},
],
specGroups: [
{
title: 'Headline Parameters',
rows: [
{ label: 'FOV', value: '360° × 96°' },
{ label: 'Near blind spot', value: '0.05 m' },
{ label: 'Scan range', value: '30 m @ 90% reflectivity' },
{ label: 'Effective frequency', value: '64,000 points/s' },
{ label: 'Measurement accuracy', value: '2 cm' },
{ label: 'Weight', value: '230 g' },
{ label: 'Communication interface', value: 'ENET UDP / TTL UART' },
{ label: 'Size', value: '75 × 75 × 65 mm' },
],
},
{
title: 'Comparison vs other sensors',
variants: ['Unitree L2', 'Depth Camera', 'Wide-Angle 3D Radar'],
rows: [
{ label: 'FOV', value: '360° × 96°', valueAlt: '86° × 57° (±3°)' },
{ label: 'Non-repetitive scanning', value: '✓', valueAlt: '—' },
{ label: 'Scanning method', value: 'Non-contact brushless rotating mirror', valueAlt: 'Global shutter' },
{ label: 'Scan range', value: '30 m', valueAlt: '4 5 m' },
{ label: 'Near blind area', value: '0.05 m', valueAlt: '0.1 m' },
{ label: 'IMU', value: '3-axis acc + 3-axis gyro', valueAlt: '—' },
{ label: 'Power', value: '10 W', valueAlt: '5 W' },
{ label: 'Weight', value: '230 g', valueAlt: '~100 g' },
{ label: 'Technology', value: 'Laser TOF', valueAlt: 'Infrared binocular' },
{ label: 'Price', value: '$419', valueAlt: '$359' },
],
},
{
title: 'Family Comparison',
variants: ['L2', 'L1 PM', 'L1 RM'],
rows: [
{ label: 'Price', value: '$419', valueAlt: '$249' },
{ label: 'Scan range (90% reflectivity)', value: '30 m', valueAlt: '20 m' },
{ label: 'Scan range (10% reflectivity)', value: '15 m', valueAlt: '10 m' },
{ label: 'FOV', value: '360° × 96°', valueAlt: '360° × 90°' },
{ label: 'Sampling frequency', value: '128,000 pts/s', valueAlt: '43,200 pts/s' },
{ label: 'Effective frequency', value: '64,000 pts/s', valueAlt: '21,600 pts/s' },
{ label: 'Communication', value: 'ENET UDP / TTL UART', valueAlt: 'TTL UART' },
{ label: '4D data', value: '3D position + 1D grayscale (2D mode supported)', valueAlt: '3D position + 1D grayscale' },
{ label: 'Circumferential scan frequency', value: '5.55 Hz (adjustable)', valueAlt: '11 Hz' },
{ label: 'Vertical scan frequency', value: '216 Hz', valueAlt: '180 Hz' },
{ label: 'Distance resolution', value: '4.5 mm', valueAlt: '8 mm' },
{ label: 'Measurement accuracy', value: '≤ 2.0 cm', valueAlt: '± 2.0 cm' },
],
},
{
title: 'Common Specs (all models)',
rows: [
{ label: 'Near blind area', value: '0.05 m' },
{ label: 'IMU', value: '3-axis acceleration + 3-axis gyroscope' },
{ label: 'Eye safety level', value: 'Class 1 (IEC 60825-1:2014)' },
{ label: 'Anti-glare capability', value: '> 100 klux' },
],
},
],
footnotes: [
'L2 supports negative-angle mode where FOV extends from 360° × 90° to 360° × 96°. The extra 6° has slightly shorter maximum measurement distance.',
'Reflectance values are typical; actual values depend on environment and target characteristics.',
'To ensure effective detection of objects with different reflectance, point-cloud accuracy may decrease slightly at certain positions. Farthest range is directly above the L2.',
'Stable and peak power vary with environment. At 10 °C to 30 °C, L2 automatically enters self-heating mode; point cloud data is generated only when temperature meets requirements, and peak power can reach 13 W.',
'Parameters above may vary across business scenarios and LiDAR configurations.',
],
safetyNotice:
'This product is a civilian robot. Users are requested to refrain from making dangerous modifications or using the robot in a hazardous manner. Comply with local laws and regulations and consult Unitree Robotics terms for full policies.',
}, },
// ============ PUDU DISPATCH ============ // ============ PUDU DISPATCH ============

View File

@ -0,0 +1,10 @@
[
{
"url": "https://www.instagram.com/reel/DX6pDm9Mkwe/",
"caption": "Make It in the Emirates 2026 — Abu Dhabi appearance."
},
{
"url": "https://www.instagram.com/reel/DYg5KQsC8pW/",
"caption": "Abu Dhabi Global Sustainable Security Summit activation."
}
]

View File

@ -29,9 +29,21 @@ export const BU_SUNAIDAH_URL = `https://www.instagram.com/${BU_SUNAIDAH_HANDLE}`
*/ */
export const BU_SUNAIDAH_PORTRAIT: { src: string; alt: string } | null = null; export const BU_SUNAIDAH_PORTRAIT: { src: string; alt: string } | null = null;
// TODO: populate with curated post shortcodes from @bu.sunaidah. // Curated reel shortcodes from @bu.sunaidah, sourced from publicly indexed
// Each entry renders as a 16:20 Instagram embed iframe. // Instagram URLs. Each entry renders as an official Instagram embed iframe.
export const BU_SUNAIDAH_POSTS: InstagramPost[] = []; // Captions are short factual descriptions of the event — not lifted from IG.
export const BU_SUNAIDAH_POSTS: InstagramPost[] = [
{
shortcode: 'DX6pDm9Mkwe',
kind: 'reel',
caption: 'Make It in the Emirates 2026 — Abu Dhabi appearance.',
},
{
shortcode: 'DYg5KQsC8pW',
kind: 'reel',
caption: 'Abu Dhabi Global Sustainable Security Summit activation.',
},
];
/** /**
* Bu Sunaidah video reel / showreel. * Bu Sunaidah video reel / showreel.

View File

@ -0,0 +1,105 @@
/**
* Fetch the latest Bu Sunaidah Instagram posts WITHOUT the Graph API.
*
* Honest reality:
* Instagram blocks unauthenticated scraping. There is no reliable, public
* endpoint that returns a profile's latest media without an access token
* or a logged-in session cookie. We attempt a best-effort HTML pull and
* gracefully fall back to a locally-edited JSON file.
*
* Resolution order (first hit wins):
* 1. Best-effort HTML scrape of https://www.instagram.com/bu.sunaidah/
* usually returns nothing because IG serves a login wall, but try.
* 2. `src/data/bu-sunaidah-posts.json` the editable source of truth.
* Update this file (or replace via a build step) to refresh the page.
* 3. The curated `BU_SUNAIDAH_POSTS` array (compiled-in last resort).
*
* To refresh the page: edit `src/data/bu-sunaidah-posts.json` with the
* latest 9 URLs from https://www.instagram.com/bu.sunaidah/ and rebuild
* (or rely on the page's ISR revalidate window).
*/
import { BU_SUNAIDAH_POSTS, type InstagramPost } from '@/data/bu-sunaidah';
import postsFile from '@/data/bu-sunaidah-posts.json' assert { type: 'json' };
const PROFILE_URL = 'https://www.instagram.com/bu.sunaidah/';
const BROWSER_UA =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36';
type JsonEntry = {
url?: string;
shortcode?: string;
kind?: 'p' | 'reel' | 'tv';
caption?: string;
};
function shortcodeFromUrl(raw: string): { shortcode: string; kind: 'p' | 'reel' | 'tv' } | null {
const match = raw.match(/instagram\.com\/(p|reel|tv)\/([^/?#"\\]+)/i);
if (!match) return null;
return { shortcode: match[2], kind: match[1].toLowerCase() as 'p' | 'reel' | 'tv' };
}
function uniqueByShortcode(list: InstagramPost[]): InstagramPost[] {
const seen = new Set<string>();
const out: InstagramPost[] = [];
for (const p of list) {
if (seen.has(p.shortcode)) continue;
seen.add(p.shortcode);
out.push(p);
}
return out;
}
function fromJsonFile(): InstagramPost[] {
const entries = (postsFile as JsonEntry[]) ?? [];
const out: InstagramPost[] = [];
for (const e of entries) {
if (e.shortcode) {
out.push({ shortcode: e.shortcode, kind: e.kind ?? 'p', caption: e.caption });
continue;
}
if (e.url) {
const parsed = shortcodeFromUrl(e.url);
if (parsed) out.push({ ...parsed, caption: e.caption });
}
}
return uniqueByShortcode(out);
}
async function scrapeProfile(limit: number): Promise<InstagramPost[]> {
try {
const res = await fetch(PROFILE_URL, {
headers: {
'User-Agent': BROWSER_UA,
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
},
next: { revalidate: 1800 },
});
if (!res.ok) return [];
const html = await res.text();
// Pull every /p/<code>/ and /reel/<code>/ reference out of the HTML.
const found = new Map<string, InstagramPost>();
const re = /\/(p|reel|tv)\/([A-Za-z0-9_-]{5,})\//g;
let m: RegExpExecArray | null;
while ((m = re.exec(html)) !== null) {
const shortcode = m[2];
if (found.has(shortcode)) continue;
found.set(shortcode, { shortcode, kind: m[1].toLowerCase() as 'p' | 'reel' | 'tv' });
if (found.size >= limit) break;
}
return Array.from(found.values());
} catch {
return [];
}
}
export async function getLatestBuSunaidahPosts(limit = 9): Promise<InstagramPost[]> {
const scraped = await scrapeProfile(limit);
if (scraped.length > 0) return scraped.slice(0, limit);
const fromFile = fromJsonFile();
if (fromFile.length > 0) return fromFile.slice(0, limit);
return BU_SUNAIDAH_POSTS.slice(0, limit);
}