Refactor code structure for improved readability and maintainability; removed redundant code blocks and optimized function calls.
This commit is contained in:
parent
bfe589be27
commit
729ab71c2c
@ -19,14 +19,14 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
- Same shape as primary.
|
- Same shape as primary.
|
||||||
|
|
||||||
**Ghost**
|
**Ghost**
|
||||||
- `text-bone hover:text-gold-light` — no border, no fill.
|
- `text-bone hover:text-gold-light` no border, no fill.
|
||||||
|
|
||||||
**Forbidden**
|
**Forbidden**
|
||||||
- Bright red CTA buttons (unless brand color is red).
|
- Bright red CTA buttons (unless brand color is red).
|
||||||
- Square corners on action buttons.
|
- Square corners on action buttons.
|
||||||
- Solid `#000` or `#fff` backgrounds.
|
- Solid `#000` or `#fff` backgrounds.
|
||||||
- Loading text inside button that breaks layout — use a spinner icon + word swap with fixed min-width.
|
- Loading text inside button that breaks layout use a spinner icon + word swap with fixed min-width.
|
||||||
- Success/error message **inside** button — render outside as a banner.
|
- Success/error message **inside** button render outside as a banner.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
- Always `text-balance` on h2 for clean wrapping.
|
- Always `text-balance` on h2 for clean wrapping.
|
||||||
- `text-pretty` on subtitle.
|
- `text-pretty` on subtitle.
|
||||||
- Use SectionLabel with sequential index per page (01, 02, 03…). No mono font.
|
- Use SectionLabel with sequential index per page (01, 02, 03…). No mono font.
|
||||||
- One gold-accent phrase max per heading — overdoing gold cheapens it.
|
- One gold-accent phrase max per heading overdoing gold cheapens it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
|
|
||||||
## Footer
|
## Footer
|
||||||
|
|
||||||
**Layout — 4 columns, no duplication**
|
**Layout 4 columns, no duplication**
|
||||||
|
|
||||||
| Column 1 | Column 2 | Column 3 | Column 4 |
|
| Column 1 | Column 2 | Column 3 | Column 4 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@ -109,7 +109,7 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
**Rules**
|
**Rules**
|
||||||
- No page link appears in two columns.
|
- No page link appears in two columns.
|
||||||
- Privacy Policy lives only under Legal.
|
- Privacy Policy lives only under Legal.
|
||||||
- App badges only in Legal & App column (footer) — official SVGs at 132 px width, 8 px gap.
|
- App badges only in Legal & App column (footer) official SVGs at 132 px width, 8 px gap.
|
||||||
- External links open in new tab with rel attrs.
|
- External links open in new tab with rel attrs.
|
||||||
- Background: `bg-obsidian` with subtle "LUXAM"-style watermark behind content (translate-y-[18%], opacity 0.025).
|
- Background: `bg-obsidian` with subtle "LUXAM"-style watermark behind content (translate-y-[18%], opacity 0.025).
|
||||||
- Bottom row: `© YYYY {brand}. All rights reserved.` + tagline + agency credit (subtle, gold-underlined on hover).
|
- Bottom row: `© YYYY {brand}. All rights reserved.` + tagline + agency credit (subtle, gold-underlined on hover).
|
||||||
@ -128,7 +128,7 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
focus:shadow-[0_0_0_3px_rgba(212,164,55,0.18)]
|
focus:shadow-[0_0_0_3px_rgba(212,164,55,0.18)]
|
||||||
aria-[invalid=true]:border-[#a3261c]/60
|
aria-[invalid=true]:border-[#a3261c]/60
|
||||||
```
|
```
|
||||||
- Honeypot field: hidden `<input name="website">` — silently succeed if filled.
|
- Honeypot field: hidden `<input name="website">` silently succeed if filled.
|
||||||
- Validation: server-side via `submitContact()` action; client gets `state.fieldErrors` + `state.message`.
|
- Validation: server-side via `submitContact()` action; client gets `state.fieldErrors` + `state.message`.
|
||||||
- Submission states: `useActionState` + `useFormStatus`. Spinner inside button, success/error banner **outside** button.
|
- Submission states: `useActionState` + `useFormStatus`. Spinner inside button, success/error banner **outside** button.
|
||||||
|
|
||||||
@ -142,9 +142,9 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
- Wrapper for emphasis: `glass-gold size-11 grid place-items-center rounded-full`.
|
- Wrapper for emphasis: `glass-gold size-11 grid place-items-center rounded-full`.
|
||||||
- **Forbidden**: oversized icons (>32 px) standalone, multi-color icon sets, cartoonish flat icons.
|
- **Forbidden**: oversized icons (>32 px) standalone, multi-color icon sets, cartoonish flat icons.
|
||||||
|
|
||||||
**No lucide WhatsApp glyph** — use custom inline SVG from `snippets/WhatsAppLink.tsx`.
|
**No lucide WhatsApp glyph** use custom inline SVG from `snippets/WhatsAppLink.tsx`.
|
||||||
|
|
||||||
**No lucide Instagram glyph in v1.16** — use custom inline SVG (see footer).
|
**No lucide Instagram glyph in v1.16** use custom inline SVG (see footer).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -229,8 +229,8 @@ Per-element rules. Apply when designing or reviewing the corresponding component
|
|||||||
## Don't
|
## Don't
|
||||||
|
|
||||||
- Don't add hover effects without `transition-*` for smoothness.
|
- Don't add hover effects without `transition-*` for smoothness.
|
||||||
- Don't use `transform-origin` defaults on rotated icon discs — keep them small (8-12°).
|
- Don't use `transform-origin` defaults on rotated icon discs keep them small (8-12°).
|
||||||
- Don't use `whileHover={{ scale: 1.1 }}` on cards — too theatrical. Use `-translate-y-1` instead.
|
- Don't use `whileHover={{ scale: 1.1 }}` on cards too theatrical. Use `-translate-y-1` instead.
|
||||||
- Don't add box-shadows with multiple layers and bright colors. One subtle amber glow max.
|
- Don't add box-shadows with multiple layers and bright colors. One subtle amber glow max.
|
||||||
- Don't introduce a new font family per page — stick to display + sans.
|
- Don't introduce a new font family per page stick to display + sans.
|
||||||
- Don't render placeholder images (`via.placeholder.com`, gray boxes) in production code. Always use real assets the user provided.
|
- Don't render placeholder images (`via.placeholder.com`, gray boxes) in production code. Always use real assets the user provided.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Component Snippets
|
# Component Snippets
|
||||||
|
|
||||||
Overview of the reusable TSX snippets in `snippets/`. Each is a copy-paste base for a new project — adapt brand tokens + content via your own `lib/content.ts`.
|
Overview of the reusable TSX snippets in `snippets/`. Each is a copy-paste base for a new project adapt brand tokens + content via your own `lib/content.ts`.
|
||||||
|
|
||||||
| File | Purpose | Depends on |
|
| File | Purpose | Depends on |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@ -18,7 +18,7 @@ Overview of the reusable TSX snippets in `snippets/`. Each is a copy-paste base
|
|||||||
1. Copy the file you need into your project's `src/components/` (or wherever your component layer lives).
|
1. Copy the file you need into your project's `src/components/` (or wherever your component layer lives).
|
||||||
2. Update import paths (`@/lib/cn`, `@/components/ui/...`) to match your project alias.
|
2. Update import paths (`@/lib/cn`, `@/components/ui/...`) to match your project alias.
|
||||||
3. Wire data: the snippets accept props or read from a `lib/content.ts` you control.
|
3. Wire data: the snippets accept props or read from a `lib/content.ts` you control.
|
||||||
4. Confirm Tailwind v4 `@theme` tokens (`obsidian`, `gold`, `bone`, `mist`, etc.) exist — see `DESIGN_SYSTEM.md`.
|
4. Confirm Tailwind v4 `@theme` tokens (`obsidian`, `gold`, `bone`, `mist`, etc.) exist see `DESIGN_SYSTEM.md`.
|
||||||
5. Confirm Framer Motion + lucide-react installed:
|
5. Confirm Framer Motion + lucide-react installed:
|
||||||
```bash
|
```bash
|
||||||
npm i framer-motion lucide-react
|
npm i framer-motion lucide-react
|
||||||
|
|||||||
@ -8,7 +8,7 @@ Reusable design tokens + scales extracted from the Luxam reference build. Drop i
|
|||||||
|
|
||||||
```css
|
```css
|
||||||
@theme {
|
@theme {
|
||||||
/* Backgrounds — obsidian → graphite layering */
|
/* Backgrounds obsidian → graphite layering */
|
||||||
--color-obsidian: #08080A; /* page background */
|
--color-obsidian: #08080A; /* page background */
|
||||||
--color-graphite: #15161A; /* dark glass card */
|
--color-graphite: #15161A; /* dark glass card */
|
||||||
--color-graphite-2: #1C1D22; /* slightly lifted card variant */
|
--color-graphite-2: #1C1D22; /* slightly lifted card variant */
|
||||||
@ -17,7 +17,7 @@ Reusable design tokens + scales extracted from the Luxam reference build. Drop i
|
|||||||
--color-bone: #F4F0E6; /* primary text on dark */
|
--color-bone: #F4F0E6; /* primary text on dark */
|
||||||
--color-mist: #C7C2B5; /* secondary/muted text */
|
--color-mist: #C7C2B5; /* secondary/muted text */
|
||||||
|
|
||||||
/* Brand — gold spectrum (use sparingly + structurally) */
|
/* Brand gold spectrum (use sparingly + structurally) */
|
||||||
--color-gold: #D4A437; /* core brand gold */
|
--color-gold: #D4A437; /* core brand gold */
|
||||||
--color-gold-light: #F2C25B; /* hover, highlight, gradient stops */
|
--color-gold-light: #F2C25B; /* hover, highlight, gradient stops */
|
||||||
--color-gold-deep: #7A4A12; /* gradient base */
|
--color-gold-deep: #7A4A12; /* gradient base */
|
||||||
@ -178,14 +178,14 @@ Every card/button uses the same hover vocabulary:
|
|||||||
6. Optional: top hairline gold gradient fade-in.
|
6. Optional: top hairline gold gradient fade-in.
|
||||||
7. Icon disc gets `rotate-[8deg]` + amber halo shadow.
|
7. Icon disc gets `rotate-[8deg]` + amber halo shadow.
|
||||||
|
|
||||||
Apply via `group` + `group-hover:` Tailwind variants — never per-element JS listeners.
|
Apply via `group` + `group-hover:` Tailwind variants never per-element JS listeners.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Container widths
|
## Container widths
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// components/ui/Container.tsx — three variants
|
// components/ui/Container.tsx three variants
|
||||||
size="default" → max-w-7xl
|
size="default" → max-w-7xl
|
||||||
size="wide" → max-w-[1480px]
|
size="wide" → max-w-[1480px]
|
||||||
size="narrow" → max-w-3xl
|
size="narrow" → max-w-3xl
|
||||||
|
|||||||
@ -43,7 +43,7 @@ Do NOT invent links. Ask if missing.
|
|||||||
Inspect {{PAGE_ROUTE}}. Keep the content + intent but rebuild it to match the design DNA in .claude/skills/premium-frontend-designer/.
|
Inspect {{PAGE_ROUTE}}. Keep the content + intent but rebuild it to match the design DNA in .claude/skills/premium-frontend-designer/.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Reuse existing primitives (Section, Container, Button, Reveal, SectionLabel, etc.) — don't duplicate.
|
- Reuse existing primitives (Section, Container, Button, Reveal, SectionLabel, etc.) don't duplicate.
|
||||||
- Apply the cardGrid + cardCell motion variants for grid entrances.
|
- Apply the cardGrid + cardCell motion variants for grid entrances.
|
||||||
- Add a single gold-accent phrase per h2.
|
- Add a single gold-accent phrase per h2.
|
||||||
- Centralize section padding via the Section primitive (no per-page overrides).
|
- Centralize section padding via the Section primitive (no per-page overrides).
|
||||||
@ -176,7 +176,7 @@ Walk through the responsive checklist in .claude/skills/premium-frontend-designe
|
|||||||
|
|
||||||
Test at 320 / 375 / 390 / 414 / 768 / 1024 viewports.
|
Test at 320 / 375 / 390 / 414 / 768 / 1024 viewports.
|
||||||
|
|
||||||
Don't redesign — only fix layout, spacing, overflow, clipping. Preserve the visual identity.
|
Don't redesign only fix layout, spacing, overflow, clipping. Preserve the visual identity.
|
||||||
|
|
||||||
Return a table of issues + fixes (Before / After columns).
|
Return a table of issues + fixes (Before / After columns).
|
||||||
```
|
```
|
||||||
@ -194,7 +194,7 @@ Audit and refine typography across all pages:
|
|||||||
4. Tracking: reduce all tracking-[0.32em+] on small labels to tracking-[0.22em].
|
4. Tracking: reduce all tracking-[0.32em+] on small labels to tracking-[0.22em].
|
||||||
5. Nav links: text-sm font-medium tracking-[0.005em] (slightly larger + medium weight, near-zero tracking).
|
5. Nav links: text-sm font-medium tracking-[0.005em] (slightly larger + medium weight, near-zero tracking).
|
||||||
6. BrandStrip marquee: smaller (text-xs md:text-sm uppercase) so it doesn't compete with hero.
|
6. BrandStrip marquee: smaller (text-xs md:text-sm uppercase) so it doesn't compete with hero.
|
||||||
7. Section labels via SectionLabel primitive — never inline.
|
7. Section labels via SectionLabel primitive never inline.
|
||||||
8. text-balance on h1/h2, text-pretty on body paragraphs.
|
8. text-balance on h1/h2, text-pretty on body paragraphs.
|
||||||
|
|
||||||
Confirm:
|
Confirm:
|
||||||
@ -270,5 +270,5 @@ Specifically check:
|
|||||||
- Is spacing centralized via Section primitive?
|
- Is spacing centralized via Section primitive?
|
||||||
- Is motion subtle (no bounces, no scale > 1.05 on hover)?
|
- Is motion subtle (no bounces, no scale > 1.05 on hover)?
|
||||||
|
|
||||||
Apply only the changes needed — don't rewrite working sections. Return a list of refinements made.
|
Apply only the changes needed don't rewrite working sections. Return a list of refinements made.
|
||||||
```
|
```
|
||||||
|
|||||||
@ -24,13 +24,13 @@ Run through every item before declaring a page done.
|
|||||||
- [ ] Logo left, menu/CTA right on mobile.
|
- [ ] Logo left, menu/CTA right on mobile.
|
||||||
- [ ] Hamburger button is `size-10` minimum (touch target).
|
- [ ] Hamburger button is `size-10` minimum (touch target).
|
||||||
- [ ] Mobile menu drawer closes when a link is clicked.
|
- [ ] Mobile menu drawer closes when a link is clicked.
|
||||||
- [ ] No sticky-nav overlap on anchored sections — `scroll-mt-24 md:scroll-mt-28` on `<Section>`.
|
- [ ] No sticky-nav overlap on anchored sections `scroll-mt-24 md:scroll-mt-28` on `<Section>`.
|
||||||
- [ ] Logo height responsive: `h-11 sm:h-14 md:h-16`.
|
- [ ] Logo height responsive: `h-11 sm:h-14 md:h-16`.
|
||||||
|
|
||||||
## Hero
|
## Hero
|
||||||
|
|
||||||
- [ ] H1 clamp: `clamp(2.4rem, 7vw, 6.4rem)` — never larger upper bound.
|
- [ ] H1 clamp: `clamp(2.4rem, 7vw, 6.4rem)` never larger upper bound.
|
||||||
- [ ] `leading-[1.0]` on serif H1 (not `0.95` — clips ascenders).
|
- [ ] `leading-[1.0]` on serif H1 (not `0.95` clips ascenders).
|
||||||
- [ ] Top padding generous: `pt-32 sm:pt-36 md:pt-44` (clears sticky nav + breathing room).
|
- [ ] Top padding generous: `pt-32 sm:pt-36 md:pt-44` (clears sticky nav + breathing room).
|
||||||
- [ ] CTAs use `flex flex-wrap items-center gap-4` so they stack on narrow screens.
|
- [ ] CTAs use `flex flex-wrap items-center gap-4` so they stack on narrow screens.
|
||||||
- [ ] Stats panel: collapse 1-col-tall mobile layouts to a horizontal 3-col compact row.
|
- [ ] Stats panel: collapse 1-col-tall mobile layouts to a horizontal 3-col compact row.
|
||||||
@ -38,7 +38,7 @@ Run through every item before declaring a page done.
|
|||||||
## Cards & grids
|
## Cards & grids
|
||||||
|
|
||||||
- [ ] Mobile: `grid-cols-1`. Tablet: `sm:grid-cols-2`. Desktop: `lg:grid-cols-N` matched to card count.
|
- [ ] Mobile: `grid-cols-1`. Tablet: `sm:grid-cols-2`. Desktop: `lg:grid-cols-N` matched to card count.
|
||||||
- [ ] Avoid `lg:grid-cols-3` for a 5-card list (leaves orphan) — use 5 or 2-row layout matched to count.
|
- [ ] Avoid `lg:grid-cols-3` for a 5-card list (leaves orphan) use 5 or 2-row layout matched to count.
|
||||||
- [ ] Aspect-ratio used over fixed heights: `aspect-[4/3]` or `aspect-[3/4]`.
|
- [ ] Aspect-ratio used over fixed heights: `aspect-[4/3]` or `aspect-[3/4]`.
|
||||||
|
|
||||||
## Typography
|
## Typography
|
||||||
@ -54,7 +54,7 @@ Run through every item before declaring a page done.
|
|||||||
- [ ] Hero LCP image gets `priority`.
|
- [ ] Hero LCP image gets `priority`.
|
||||||
- [ ] `object-cover` only when crop is visually intentional.
|
- [ ] `object-cover` only when crop is visually intentional.
|
||||||
- [ ] `object-contain` for logos.
|
- [ ] `object-contain` for logos.
|
||||||
- [ ] Alt text meaningful — never "image" or filename.
|
- [ ] Alt text meaningful never "image" or filename.
|
||||||
|
|
||||||
## Forms
|
## Forms
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ description: Build, redesign, and polish premium dark/gold luxury websites with
|
|||||||
|
|
||||||
You are a senior frontend architect + UI/UX designer + implementation engineer.
|
You are a senior frontend architect + UI/UX designer + implementation engineer.
|
||||||
|
|
||||||
Your job: create production-ready, premium, cinematic, dark/gold luxury websites that feel high-end, trustworthy, and conversion-focused — the way the Luxam reference site does.
|
Your job: create production-ready, premium, cinematic, dark/gold luxury websites that feel high-end, trustworthy, and conversion-focused the way the Luxam reference site does.
|
||||||
|
|
||||||
This skill captures the design DNA, component patterns, prompt templates, and snippets so any future project can match the same quality bar.
|
This skill captures the design DNA, component patterns, prompt templates, and snippets so any future project can match the same quality bar.
|
||||||
|
|
||||||
@ -30,16 +30,16 @@ Refuse silently if the project explicitly uses a different design language (e.g.
|
|||||||
|
|
||||||
## Visual principles
|
## Visual principles
|
||||||
|
|
||||||
1. **Dark cinematic background** — obsidian-class blacks (`#08080A`), graphite layering (`#15161A` / `#1C1D22`). Never pure white. Never bright primary colors as backgrounds.
|
1. **Dark cinematic background** obsidian-class blacks (`#08080A`), graphite layering (`#15161A` / `#1C1D22`). Never pure white. Never bright primary colors as backgrounds.
|
||||||
2. **Gold/amber accents** — restrained, structural, single-tone gradient. Use on numbers, labels, dividers, hover states, hero highlights. Never bright yellow. Never multi-color rainbow gradients.
|
2. **Gold/amber accents** restrained, structural, single-tone gradient. Use on numbers, labels, dividers, hover states, hero highlights. Never bright yellow. Never multi-color rainbow gradients.
|
||||||
3. **Editorial typography pairing**
|
3. **Editorial typography pairing**
|
||||||
- Serif (Cormorant Garamond / Playfair / Libre Baskerville) for hero H1 + section H2 only.
|
- Serif (Cormorant Garamond / Playfair / Libre Baskerville) for hero H1 + section H2 only.
|
||||||
- Sans (Geist / Inter / Manrope / Satoshi) for nav, body, buttons, labels, metadata.
|
- Sans (Geist / Inter / Manrope / Satoshi) for nav, body, buttons, labels, metadata.
|
||||||
- No monospace on user-facing copy or long values.
|
- No monospace on user-facing copy or long values.
|
||||||
4. **Glass + grain** — semi-transparent `bg-bone/[0.03]` cards with `backdrop-blur`. Subtle noise/grain overlays to break flat surfaces.
|
4. **Glass + grain** semi-transparent `bg-bone/[0.03]` cards with `backdrop-blur`. Subtle noise/grain overlays to break flat surfaces.
|
||||||
5. **Cinematic motion** — Framer Motion: slow, restrained. `cubic-bezier(0.16, 1, 0.3, 1)`, 0.8-1.2s entrance, `useReducedMotion()` respected.
|
5. **Cinematic motion** Framer Motion: slow, restrained. `cubic-bezier(0.16, 1, 0.3, 1)`, 0.8-1.2s entrance, `useReducedMotion()` respected.
|
||||||
6. **Responsive from the start** — every layout works at 320 / 375 / 768 / 1024 / 1440 viewports.
|
6. **Responsive from the start** every layout works at 320 / 375 / 768 / 1024 / 1440 viewports.
|
||||||
7. **No corporate basic** — no boxy stock layouts, no flat colorful cards, no generic stock-photo hero, no cheap icon dumps.
|
7. **No corporate basic** no boxy stock layouts, no flat colorful cards, no generic stock-photo hero, no cheap icon dumps.
|
||||||
|
|
||||||
Read `DESIGN_SYSTEM.md` for the full token palette + typography clamps.
|
Read `DESIGN_SYSTEM.md` for the full token palette + typography clamps.
|
||||||
|
|
||||||
@ -47,13 +47,13 @@ Read `DESIGN_SYSTEM.md` for the full token palette + typography clamps.
|
|||||||
|
|
||||||
## Coding principles
|
## Coding principles
|
||||||
|
|
||||||
1. **Inspect before changing.** Always read the existing files, lockfile, framework version, and existing primitives. Reuse — don't duplicate.
|
1. **Inspect before changing.** Always read the existing files, lockfile, framework version, and existing primitives. Reuse don't duplicate.
|
||||||
2. **Preserve project identity.** Don't rewrite the whole brand for a small request.
|
2. **Preserve project identity.** Don't rewrite the whole brand for a small request.
|
||||||
3. **Real content only.** Never invent links, social handles, certifications, addresses, phone numbers, or legal claims. Ask if missing.
|
3. **Real content only.** Never invent links, social handles, certifications, addresses, phone numbers, or legal claims. Ask if missing.
|
||||||
4. **External links** carry `target="_blank"` + `rel="noopener noreferrer"`.
|
4. **External links** carry `target="_blank"` + `rel="noopener noreferrer"`.
|
||||||
5. **Reusable primitives first.** Look for existing `Section`, `Container`, `Button`, `Reveal`, `SectionLabel`, `GlassCard` before writing new ones.
|
5. **Reusable primitives first.** Look for existing `Section`, `Container`, `Button`, `Reveal`, `SectionLabel`, `GlassCard` before writing new ones.
|
||||||
6. **Clean imports.** Remove unused icons / helpers after edits.
|
6. **Clean imports.** Remove unused icons / helpers after edits.
|
||||||
7. **One source of truth** for spacing — push to a shared `Section` primitive instead of overriding per page.
|
7. **One source of truth** for spacing push to a shared `Section` primitive instead of overriding per page.
|
||||||
8. **Typecheck after every change.** `npx tsc --noEmit` must pass.
|
8. **Typecheck after every change.** `npx tsc --noEmit` must pass.
|
||||||
9. **No backwards-compat shims.** Don't leave `// removed` comments, dead aliases, or "for future" stubs.
|
9. **No backwards-compat shims.** Don't leave `// removed` comments, dead aliases, or "for future" stubs.
|
||||||
10. **No commented-out code** in committed work.
|
10. **No commented-out code** in committed work.
|
||||||
@ -71,7 +71,7 @@ Read `COMPONENT_RULES.md` for per-element rules (buttons, cards, footer, forms,
|
|||||||
- Modal/lightbox: `role="dialog" aria-modal="true"`, `Escape` closes, scroll-lock on body.
|
- Modal/lightbox: `role="dialog" aria-modal="true"`, `Escape` closes, scroll-lock on body.
|
||||||
- Decorative SVGs: `aria-hidden`.
|
- Decorative SVGs: `aria-hidden`.
|
||||||
- Color contrast: body text `text-bone` (≈ #F4F0E6) on obsidian = 16:1 ratio, well above WCAG AA.
|
- Color contrast: body text `text-bone` (≈ #F4F0E6) on obsidian = 16:1 ratio, well above WCAG AA.
|
||||||
- `prefers-reduced-motion` respected via Framer's `useReducedMotion()` — disable transforms/parallax, keep opacity fades.
|
- `prefers-reduced-motion` respected via Framer's `useReducedMotion()` disable transforms/parallax, keep opacity fades.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ Test mentally (or in browser) at: **320, 375, 390, 414, 768, 1024, 1440**.
|
|||||||
- Section padding: `py-16 md:py-20 lg:py-24` (uniform).
|
- Section padding: `py-16 md:py-20 lg:py-24` (uniform).
|
||||||
- Anchored sections: `scroll-mt-24 md:scroll-mt-28` for sticky-nav clearance.
|
- Anchored sections: `scroll-mt-24 md:scroll-mt-28` for sticky-nav clearance.
|
||||||
- `body { overflow-x: hidden }` in `globals.css` as a global guard.
|
- `body { overflow-x: hidden }` in `globals.css` as a global guard.
|
||||||
- Sticky decorations (halos, watermarks, blurs) must not introduce horizontal scroll — use `overflow-hidden` on parent + `translate` instead of fixed widths.
|
- Sticky decorations (halos, watermarks, blurs) must not introduce horizontal scroll use `overflow-hidden` on parent + `translate` instead of fixed widths.
|
||||||
- Mobile cards stack `grid-cols-1 sm:grid-cols-2 lg:grid-cols-N`.
|
- Mobile cards stack `grid-cols-1 sm:grid-cols-2 lg:grid-cols-N`.
|
||||||
|
|
||||||
Read `RESPONSIVE_CHECKLIST.md` before shipping any page.
|
Read `RESPONSIVE_CHECKLIST.md` before shipping any page.
|
||||||
@ -94,12 +94,12 @@ Read `RESPONSIVE_CHECKLIST.md` before shipping any page.
|
|||||||
|
|
||||||
Every meaningful change returns:
|
Every meaningful change returns:
|
||||||
|
|
||||||
1. **Files changed** — explicit list with `NEW` / `MODIFIED` / `DELETED` markers.
|
1. **Files changed** explicit list with `NEW` / `MODIFIED` / `DELETED` markers.
|
||||||
2. **What was kept** vs **what was replaced** when redesigning.
|
2. **What was kept** vs **what was replaced** when redesigning.
|
||||||
3. **Receipts** — `HTTP 200`, typecheck clean, key content rendered (grep output).
|
3. **Receipts** `HTTP 200`, typecheck clean, key content rendered (grep output).
|
||||||
4. **Responsive breakdown** — desktop / tablet / mobile behavior.
|
4. **Responsive breakdown** desktop / tablet / mobile behavior.
|
||||||
5. **Confirmations** — explicit yes/no on every checklist item the user asked.
|
5. **Confirmations** explicit yes/no on every checklist item the user asked.
|
||||||
6. **No placeholder leaks** — confirm zero `href="#"`, `href=""`, `href="/#"` remain in modified files.
|
6. **No placeholder leaks** confirm zero `href="#"`, `href=""`, `href="/#"` remain in modified files.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -123,21 +123,21 @@ Every meaningful change returns:
|
|||||||
|
|
||||||
## Brand-specific config (Luxam reference)
|
## Brand-specific config (Luxam reference)
|
||||||
|
|
||||||
`examples/luxam-config.md` holds the exact Luxam contact details, WhatsApp URL, app store links, agency credit, etc. **Do not copy these values into other projects** — use them only as the shape pattern. Each new project gets its own `lib/content.ts` populated from real values the user provides.
|
`examples/luxam-config.md` holds the exact Luxam contact details, WhatsApp URL, app store links, agency credit, etc. **Do not copy these values into other projects** use them only as the shape pattern. Each new project gets its own `lib/content.ts` populated from real values the user provides.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## File map of this skill
|
## File map of this skill
|
||||||
|
|
||||||
```
|
```
|
||||||
SKILL.md — this file (entry point, principles, when to use)
|
SKILL.md this file (entry point, principles, when to use)
|
||||||
DESIGN_SYSTEM.md — color tokens, typography, spacing scale, motion
|
DESIGN_SYSTEM.md color tokens, typography, spacing scale, motion
|
||||||
COMPONENT_RULES.md — per-component rules (buttons, cards, footer, forms, icons)
|
COMPONENT_RULES.md per-component rules (buttons, cards, footer, forms, icons)
|
||||||
RESPONSIVE_CHECKLIST.md — pre-ship checklist for every viewport
|
RESPONSIVE_CHECKLIST.md pre-ship checklist for every viewport
|
||||||
PROMPT_TEMPLATES.md — copy-paste prompts for common tasks
|
PROMPT_TEMPLATES.md copy-paste prompts for common tasks
|
||||||
COMPONENT_SNIPPETS.md — high-level component overview
|
COMPONENT_SNIPPETS.md high-level component overview
|
||||||
snippets/ — 8 reusable TSX component files
|
snippets/ 8 reusable TSX component files
|
||||||
examples/luxam-config.md — Luxam-specific values (reference, not boilerplate)
|
examples/luxam-config.md Luxam-specific values (reference, not boilerplate)
|
||||||
```
|
```
|
||||||
|
|
||||||
Read the relevant section(s) for the task at hand. Don't dump the whole skill into the user's reply — surface only the rules + snippets relevant to the change being made.
|
Read the relevant section(s) for the task at hand. Don't dump the whole skill into the user's reply surface only the rules + snippets relevant to the change being made.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Luxam Reference Config
|
# Luxam Reference Config
|
||||||
|
|
||||||
Concrete values used in the Luxam project. **Reference only** — do not paste into other projects unless the user explicitly says these are also their values.
|
Concrete values used in the Luxam project. **Reference only** do not paste into other projects unless the user explicitly says these are also their values.
|
||||||
|
|
||||||
## `src/lib/content.ts`
|
## `src/lib/content.ts`
|
||||||
|
|
||||||
@ -115,4 +115,4 @@ Valid from 11 August 2025
|
|||||||
Valid to 10 August 2026
|
Valid to 10 August 2026
|
||||||
```
|
```
|
||||||
|
|
||||||
For any new project that surfaces compliance docs, follow the same data shape: number, standard, issuer, certified entity, site, scope, material, validity dates. Pull only from official documents — never invent.
|
For any new project that surfaces compliance docs, follow the same data shape: number, standard, issuer, certified entity, site, scope, material, validity dates. Pull only from official documents never invent.
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
* appStore: "https://apps.apple.com/ae/app/.../id...",
|
* appStore: "https://apps.apple.com/ae/app/.../id...",
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* next/image blocks raw SVG by default in Next 16 — keep plain <img>.
|
* next/image blocks raw SVG by default in Next 16 keep plain <img>.
|
||||||
*/
|
*/
|
||||||
import { app } from "@/lib/content";
|
import { app } from "@/lib/content";
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 4-image certificate gallery — each tile links to a PDF in a new tab.
|
* 4-image certificate gallery each tile links to a PDF in a new tab.
|
||||||
*
|
*
|
||||||
* Expected data shape:
|
* Expected data shape:
|
||||||
* export type CertPage = {
|
* export type CertPage = {
|
||||||
@ -65,7 +65,7 @@ function CertCard({ src, alt, title, page, document, href }: CertPage) {
|
|||||||
href={href}
|
href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
aria-label={`${title} — ${page}. Opens PDF in a new tab.`}
|
aria-label={`${title} ${page}. Opens PDF in a new tab.`}
|
||||||
className="group relative isolate flex h-full flex-col overflow-hidden rounded-2xl border border-bone/10 bg-graphite/40 backdrop-blur-sm transition-all duration-500 hover:-translate-y-1 hover:border-gold/35 hover:bg-graphite/60 focus:outline-none focus-visible:border-gold/60 focus-visible:ring-2 focus-visible:ring-gold/40"
|
className="group relative isolate flex h-full flex-col overflow-hidden rounded-2xl border border-bone/10 bg-graphite/40 backdrop-blur-sm transition-all duration-500 hover:-translate-y-1 hover:border-gold/35 hover:bg-graphite/60 focus:outline-none focus-visible:border-gold/60 focus-visible:ring-2 focus-visible:ring-gold/40"
|
||||||
>
|
>
|
||||||
<div className="relative aspect-[3/4] w-full overflow-hidden bg-obsidian">
|
<div className="relative aspect-[3/4] w-full overflow-hidden bg-obsidian">
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export function Footer({ navigationLinks, serviceItems, legalLinks, logo }: Prop
|
|||||||
<footer className="relative overflow-hidden border-t border-bone/10 bg-obsidian pb-8 pt-11 md:pb-10 md:pt-14 lg:pb-12 lg:pt-[72px]">
|
<footer className="relative overflow-hidden border-t border-bone/10 bg-obsidian pb-8 pt-11 md:pb-10 md:pt-14 lg:pb-12 lg:pt-[72px]">
|
||||||
<div className="relative z-10 mx-auto max-w-[1480px] px-6 md:px-10">
|
<div className="relative z-10 mx-auto max-w-[1480px] px-6 md:px-10">
|
||||||
<div className="grid gap-10 lg:grid-cols-12 lg:gap-12">
|
<div className="grid gap-10 lg:grid-cols-12 lg:gap-12">
|
||||||
{/* Col 1 — Brand + contact */}
|
{/* Col 1 Brand + contact */}
|
||||||
<div className="space-y-5 lg:col-span-4">
|
<div className="space-y-5 lg:col-span-4">
|
||||||
<a href="/" className="inline-flex items-center gap-4">
|
<a href="/" className="inline-flex items-center gap-4">
|
||||||
{logo}
|
{logo}
|
||||||
@ -152,7 +152,7 @@ export function Footer({ navigationLinks, serviceItems, legalLinks, logo }: Prop
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Watermark — contained inside footer */}
|
{/* Watermark contained inside footer */}
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="pointer-events-none absolute inset-x-0 bottom-0 z-0 flex select-none justify-center overflow-hidden"
|
className="pointer-events-none absolute inset-x-0 bottom-0 z-0 flex select-none justify-center overflow-hidden"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ type Props = {
|
|||||||
eyebrow?: string;
|
eyebrow?: string;
|
||||||
index?: string;
|
index?: string;
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
/** The gold-accent phrase rendered inside the title — pass as JSX or separate prop. */
|
/** The gold-accent phrase rendered inside the title pass as JSX or separate prop. */
|
||||||
accent?: ReactNode;
|
accent?: ReactNode;
|
||||||
subtitle?: ReactNode;
|
subtitle?: ReactNode;
|
||||||
align?: "left" | "center";
|
align?: "left" | "center";
|
||||||
|
|||||||
@ -3,12 +3,12 @@ node_modules
|
|||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
# Local env files — set these as environment variables in Coolify instead
|
# Local env files set these as environment variables in Coolify instead
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
# SQLite database files — mount /app/prisma as a persistent volume in Coolify
|
# SQLite database files mount /app/prisma as a persistent volume in Coolify
|
||||||
prisma/*.db
|
prisma/*.db
|
||||||
prisma/*.db-shm
|
prisma/*.db-shm
|
||||||
prisma/*.db-wal
|
prisma/*.db-wal
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Stripe — https://dashboard.stripe.com/apikeys
|
# Stripe https://dashboard.stripe.com/apikeys
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
|
||||||
STRIPE_SECRET_KEY=sk_live_...
|
STRIPE_SECRET_KEY=sk_live_...
|
||||||
@ -11,7 +11,7 @@ STRIPE_WEBHOOK_SECRET=whsec_...
|
|||||||
ADMIN_JWT_SECRET=change_me_generate_with_openssl_rand_hex_32
|
ADMIN_JWT_SECRET=change_me_generate_with_openssl_rand_hex_32
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Database — SQLite via libsql
|
# Database SQLite via libsql
|
||||||
# In Coolify: set to file:/app/prisma/lootah.db
|
# In Coolify: set to file:/app/prisma/lootah.db
|
||||||
# and mount /app/prisma as a persistent volume
|
# and mount /app/prisma as a persistent volume
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -11,7 +11,7 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
- Create `prisma/schema.prisma` with SQLite provider
|
- Create `prisma/schema.prisma` with SQLite provider
|
||||||
- Models: `AdminUser` (id, username, passwordHash, createdAt), `AppSettings` (key, value)
|
- Models: `AdminUser` (id, username, passwordHash, createdAt), `AppSettings` (key, value)
|
||||||
- Run `npx prisma db push` to create the database
|
- Run `npx prisma db push` to create the database
|
||||||
- Create `src/lib/prisma.ts` — singleton Prisma client
|
- Create `src/lib/prisma.ts` singleton Prisma client
|
||||||
|
|
||||||
2. **Create seed script** `prisma/seed.ts`
|
2. **Create seed script** `prisma/seed.ts`
|
||||||
- Creates default admin user with bcrypt-hashed password
|
- Creates default admin user with bcrypt-hashed password
|
||||||
@ -19,10 +19,10 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
- Run with `npx prisma db seed`
|
- Run with `npx prisma db seed`
|
||||||
|
|
||||||
3. **Create admin auth API routes**
|
3. **Create admin auth API routes**
|
||||||
- `src/app/api/admin/login/route.ts` — POST, accepts `{ username, password }`, verifies bcrypt hash from DB, returns JWT in httpOnly cookie (JWT secret from DB)
|
- `src/app/api/admin/login/route.ts` POST, accepts `{ username, password }`, verifies bcrypt hash from DB, returns JWT in httpOnly cookie (JWT secret from DB)
|
||||||
- `src/app/api/admin/verify/route.ts` — GET, checks JWT cookie validity
|
- `src/app/api/admin/verify/route.ts` GET, checks JWT cookie validity
|
||||||
- `src/app/api/admin/logout/route.ts` — POST, clears auth cookie
|
- `src/app/api/admin/logout/route.ts` POST, clears auth cookie
|
||||||
- `src/app/api/admin/change-password/route.ts` — POST, accepts `{ currentPassword, newPassword }`, updates hash in DB
|
- `src/app/api/admin/change-password/route.ts` POST, accepts `{ currentPassword, newPassword }`, updates hash in DB
|
||||||
|
|
||||||
4. **Create admin middleware** `src/middleware.ts`
|
4. **Create admin middleware** `src/middleware.ts`
|
||||||
- Protects all `/admin/*` routes (except `/admin/login/`)
|
- Protects all `/admin/*` routes (except `/admin/login/`)
|
||||||
@ -34,7 +34,7 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
- Calls login API, redirects to `/admin/` on success
|
- Calls login API, redirects to `/admin/` on success
|
||||||
- Shows error on wrong credentials
|
- Shows error on wrong credentials
|
||||||
|
|
||||||
6. **Add JWT_SECRET to .env.local** (only this one — password is in DB)
|
6. **Add JWT_SECRET to .env.local** (only this one password is in DB)
|
||||||
- Alternative: store JWT secret in DB and load at startup into a module-level cache
|
- Alternative: store JWT secret in DB and load at startup into a module-level cache
|
||||||
|
|
||||||
### Dependencies to install
|
### Dependencies to install
|
||||||
@ -43,12 +43,12 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
## Phase 2: Full CRUD for Pricing Items + Personas
|
## Phase 2: Full CRUD for Pricing Items + Personas
|
||||||
|
|
||||||
### Steps (depends on Phase 1)
|
### Steps (depends on Phase 1)
|
||||||
6. **Add/Remove pricing items in admin** — Update `src/app/admin/page.tsx`
|
6. **Add/Remove pricing items in admin** Update `src/app/admin/page.tsx`
|
||||||
- "Add Item" button with fields: id (auto-slug from label), label, price
|
- "Add Item" button with fields: id (auto-slug from label), label, price
|
||||||
- Delete button per row (with confirmation)
|
- Delete button per row (with confirmation)
|
||||||
- Update `usePricingStore` to support `addItem()` and `removeItem()` actions
|
- Update `usePricingStore` to support `addItem()` and `removeItem()` actions
|
||||||
|
|
||||||
7. **Persona management section** — New section in admin page
|
7. **Persona management section** New section in admin page
|
||||||
- List current personas with edit capability (label, description, colors)
|
- List current personas with edit capability (label, description, colors)
|
||||||
- Add new persona form
|
- Add new persona form
|
||||||
- Delete persona button
|
- Delete persona button
|
||||||
@ -58,7 +58,7 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
## Phase 3: Additional Admin Features
|
## Phase 3: Additional Admin Features
|
||||||
|
|
||||||
### Steps (parallel with Phase 2)
|
### Steps (parallel with Phase 2)
|
||||||
8. **Orders dashboard section** — New section in admin page
|
8. **Orders dashboard section** New section in admin page
|
||||||
- Show orders from Stripe API (list recent payments)
|
- Show orders from Stripe API (list recent payments)
|
||||||
- Display: order ID, customer email, amount, status, date
|
- Display: order ID, customer email, amount, status, date
|
||||||
- New API route `src/app/api/admin/orders/route.ts` to fetch from Stripe
|
- New API route `src/app/api/admin/orders/route.ts` to fetch from Stripe
|
||||||
@ -77,18 +77,18 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
11. **Logout button** in admin header
|
11. **Logout button** in admin header
|
||||||
|
|
||||||
## Relevant Files
|
## Relevant Files
|
||||||
- `src/app/admin/page.tsx` — Main admin dashboard (add CRUD UI, analytics)
|
- `src/app/admin/page.tsx` Main admin dashboard (add CRUD UI, analytics)
|
||||||
- `src/app/admin/login/page.tsx` — New login page
|
- `src/app/admin/login/page.tsx` New login page
|
||||||
- `src/app/api/admin/login/route.ts` — New auth API
|
- `src/app/api/admin/login/route.ts` New auth API
|
||||||
- `src/app/api/admin/verify/route.ts` — New verify API
|
- `src/app/api/admin/verify/route.ts` New verify API
|
||||||
- `src/app/api/admin/orders/route.ts` — New orders API
|
- `src/app/api/admin/orders/route.ts` New orders API
|
||||||
- `src/middleware.ts` — New, protects admin routes
|
- `src/middleware.ts` New, protects admin routes
|
||||||
- `src/lib/prisma.ts` — New, Prisma client singleton
|
- `src/lib/prisma.ts` New, Prisma client singleton
|
||||||
- `prisma/schema.prisma` — New, database schema (AdminUser, AppSettings)
|
- `prisma/schema.prisma` New, database schema (AdminUser, AppSettings)
|
||||||
- `prisma/seed.ts` — New, seeds default admin user
|
- `prisma/seed.ts` New, seeds default admin user
|
||||||
- `src/store/usePricingStore.ts` — Add `addItem()`, `removeItem()` actions
|
- `src/store/usePricingStore.ts` Add `addItem()`, `removeItem()` actions
|
||||||
- `src/components/ConfigPanel.tsx` — Persona options currently hardcoded (PERSONA_OPTIONS const)
|
- `src/components/ConfigPanel.tsx` Persona options currently hardcoded (PERSONA_OPTIONS const)
|
||||||
- `.env.local` — Add ADMIN_PASSWORD, ADMIN_JWT_SECRET
|
- `.env.local` Add ADMIN_PASSWORD, ADMIN_JWT_SECRET
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
1. Visit `/admin/` without login → redirected to `/admin/login/`
|
1. Visit `/admin/` without login → redirected to `/admin/login/`
|
||||||
@ -102,13 +102,13 @@ Add password authentication to the admin dashboard, enable full CRUD for persona
|
|||||||
9. Run `npx vitest run` → all existing tests pass
|
9. Run `npx vitest run` → all existing tests pass
|
||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
- **Database: Prisma + SQLite** — file-based, no external server needed, real ORM
|
- **Database: Prisma + SQLite** file-based, no external server needed, real ORM
|
||||||
- Admin password stored as bcrypt hash in DB (not in env vars)
|
- Admin password stored as bcrypt hash in DB (not in env vars)
|
||||||
- JWT secret stored in AppSettings table in DB
|
- JWT secret stored in AppSettings table in DB
|
||||||
- JWT in httpOnly cookie — secure, no localStorage tokens
|
- JWT in httpOnly cookie secure, no localStorage tokens
|
||||||
- jose library for JWT — lightweight, edge-compatible (works in Next.js middleware)
|
- jose library for JWT lightweight, edge-compatible (works in Next.js middleware)
|
||||||
- Personas need to move from hardcoded array to store-driven — breaking change in ConfigPanel
|
- Personas need to move from hardcoded array to store-driven breaking change in ConfigPanel
|
||||||
- Stripe orders fetched via Stripe API directly — no local DB needed for orders
|
- Stripe orders fetched via Stripe API directly no local DB needed for orders
|
||||||
|
|
||||||
## Further Considerations
|
## Further Considerations
|
||||||
1. **Persona storage**: Currently hardcoded in ConfigPanel. Moving to a store means ConfigPanel reads from store dynamically. This is required for admin CRUD to work. **Recommended: create persona store with localStorage persistence (same pattern as pricing store)**
|
1. **Persona storage**: Currently hardcoded in ConfigPanel. Moving to a store means ConfigPanel reads from store dynamically. This is required for admin CRUD to work. **Recommended: create persona store with localStorage persistence (same pattern as pricing store)**
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -38,6 +38,6 @@ next-env.d.ts
|
|||||||
|
|
||||||
/src/generated/prisma
|
/src/generated/prisma
|
||||||
|
|
||||||
# Local design references — not part of project
|
# Local design references not part of project
|
||||||
/references
|
/references
|
||||||
|
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,9 +1,9 @@
|
|||||||
# Changelog — Lootah Robotics G1 Configurator
|
# Changelog Lootah Robotics G1 Configurator
|
||||||
> تاريخ التغييرات — 20 أبريل 2026
|
> تاريخ التغييرات 20 أبريل 2026
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2ff21c5 — perf: compress GLBs 75%, add Draco decoder, loading spinner for attire
|
## 2ff21c5 perf: compress GLBs 75%, add Draco decoder, loading spinner for attire
|
||||||
|
|
||||||
### المشكلة
|
### المشكلة
|
||||||
- الموبايلات القديمة كانت تعلّق لأوقات طويلة عند تحميل الأزياء الجديدة
|
- الموبايلات القديمة كانت تعلّق لأوقات طويلة عند تحميل الأزياء الجديدة
|
||||||
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## b2a484f — fix: dynamic attire buttons in ScrollOverlays + mobile touch support
|
## b2a484f fix: dynamic attire buttons in ScrollOverlays + mobile touch support
|
||||||
|
|
||||||
### المشكلة
|
### المشكلة
|
||||||
- أزرار الأزياء في صفحة الـ Landing (Kandura, Vest, Suit) كانت مكتوبة بشكل ثابت (hardcoded)
|
- أزرار الأزياء في صفحة الـ Landing (Kandura, Vest, Suit) كانت مكتوبة بشكل ثابت (hardcoded)
|
||||||
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 320b77b — fix: contacts API - use ADMIN_JWT_SECRET env var
|
## 320b77b fix: contacts API - use ADMIN_JWT_SECRET env var
|
||||||
|
|
||||||
### المشكلة
|
### المشكلة
|
||||||
- صفحة Contacts في الأدمن كانت ترجع خطأ 500
|
- صفحة Contacts في الأدمن كانت ترجع خطأ 500
|
||||||
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 25ffbf4 — feat: add favicon and app icons for PWA support
|
## 25ffbf4 feat: add favicon and app icons for PWA support
|
||||||
|
|
||||||
### التغييرات
|
### التغييرات
|
||||||
| الملف | التغيير |
|
| الملف | التغيير |
|
||||||
@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## e686d41 — fix: use configStore.getState().setPersonaAttire in ScrollOverlays
|
## e686d41 fix: use configStore.getState().setPersonaAttire in ScrollOverlays
|
||||||
|
|
||||||
### المشكلة
|
### المشكلة
|
||||||
- بناء Docker كان يفشل مع خطأ TypeScript:
|
- بناء Docker كان يفشل مع خطأ TypeScript:
|
||||||
@ -93,7 +93,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## e159965 — feat: add GET endpoint to retrieve contact requests with admin authentication
|
## e159965 feat: add GET endpoint to retrieve contact requests with admin authentication
|
||||||
|
|
||||||
### التغييرات
|
### التغييرات
|
||||||
| الملف | التغيير |
|
| الملف | التغيير |
|
||||||
@ -114,10 +114,10 @@ STRIPE_SECRET_KEY= # للمدفوعات
|
|||||||
### هيكل Stores
|
### هيكل Stores
|
||||||
| Store | الوصف |
|
| Store | الوصف |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `configStore` (vanilla Zustand) | الألوان والزي النشط — يدعم `.getState()` |
|
| `configStore` (vanilla Zustand) | الألوان والزي النشط يدعم `.getState()` |
|
||||||
| `useConfigStore` (React hook) | wrapper لـ `configStore` للاستخدام داخل components |
|
| `useConfigStore` (React hook) | wrapper لـ `configStore` للاستخدام داخل components |
|
||||||
| `personaStore` (vanilla Zustand) | قائمة الأزياء — تُحمَّل من API عند التهيئة |
|
| `personaStore` (vanilla Zustand) | قائمة الأزياء تُحمَّل من API عند التهيئة |
|
||||||
| `pricingStore` | أسعار العناصر — تُزامَن مع قاعدة البيانات |
|
| `pricingStore` | أسعار العناصر تُزامَن مع قاعدة البيانات |
|
||||||
|
|
||||||
### تدفق الأزياء المرفوعة
|
### تدفق الأزياء المرفوعة
|
||||||
1. الأدمن يرفع `.glb` من لوحة التحكم
|
1. الأدمن يرفع `.glb` من لوحة التحكم
|
||||||
|
|||||||
@ -15,7 +15,7 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||||||
# Generate Prisma client (reads prisma/schema.prisma + prisma.config.ts)
|
# Generate Prisma client (reads prisma/schema.prisma + prisma.config.ts)
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
|
|
||||||
# Build Next.js — produces .next/standalone (set in next.config.mjs)
|
# Build Next.js produces .next/standalone (set in next.config.mjs)
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ── Stage 3: production runner ─────────────────────────────────────────────────
|
# ── Stage 3: production runner ─────────────────────────────────────────────────
|
||||||
|
|||||||
@ -9,7 +9,7 @@ echo "→ Syncing database schema..."
|
|||||||
# db push creates the SQLite file and syncs tables to match schema.prisma
|
# db push creates the SQLite file and syncs tables to match schema.prisma
|
||||||
/app/node_modules/.bin/prisma db push
|
/app/node_modules/.bin/prisma db push
|
||||||
|
|
||||||
echo "→ Seeding database (idempotent — pricing rows are upserted every deploy)..."
|
echo "→ Seeding database (idempotent pricing rows are upserted every deploy)..."
|
||||||
/app/node_modules/.bin/tsx /app/prisma/seed.ts
|
/app/node_modules/.bin/tsx /app/prisma/seed.ts
|
||||||
|
|
||||||
echo "→ Starting Next.js on port ${PORT:-3000}..."
|
echo "→ Starting Next.js on port ${PORT:-3000}..."
|
||||||
|
|||||||
@ -47,7 +47,7 @@ async function main() {
|
|||||||
console.log('✓ JWT secret already exists, skipping.');
|
console.log('✓ JWT secret already exists, skipping.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pricing items — upserted on every deploy so the code is the source of truth.
|
// Pricing items upserted on every deploy so the code is the source of truth.
|
||||||
// Prices in AED. USD → AED at 3.6725 (CBUAE peg).
|
// Prices in AED. USD → AED at 3.6725 (CBUAE peg).
|
||||||
// Basic = Unitree G1 retail ($16k) + $5k markup = $21k ≈ 77,125 AED.
|
// Basic = Unitree G1 retail ($16k) + $5k markup = $21k ≈ 77,125 AED.
|
||||||
// EDU = $40k flat ≈ 146,900 AED.
|
// EDU = $40k flat ≈ 146,900 AED.
|
||||||
|
|||||||
BIN
public/images/robots/unitree/b2-shop-1.jpg
Normal file
BIN
public/images/robots/unitree/b2-shop-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/robots/unitree/g1-shop-1.jpg
Normal file
BIN
public/images/robots/unitree/g1-shop-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
public/images/robots/unitree/g1-shop-2.jpg
Normal file
BIN
public/images/robots/unitree/g1-shop-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/robots/unitree/go2-shop-1.png
Normal file
BIN
public/images/robots/unitree/go2-shop-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
BIN
public/images/robots/unitree/go2-shop-2.png
Normal file
BIN
public/images/robots/unitree/go2-shop-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
@ -10,9 +10,9 @@ import { FounderSection } from '@/components/robotics/FounderSection';
|
|||||||
import { ServicesGrid } from '@/components/robotics/ServicesGrid';
|
import { ServicesGrid } from '@/components/robotics/ServicesGrid';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'About YS Lootah Robotics — Exclusive UAE Access to Unitree & Pudu',
|
title: 'About YS Lootah Robotics Exclusive UAE Access to Unitree & Pudu',
|
||||||
description:
|
description:
|
||||||
'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group — a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.',
|
'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
@ -28,7 +28,7 @@ export default function AboutPage() {
|
|||||||
<span className="text-gradient" style={{ fontWeight: 500 }}>In Tech We Innovate. In Trust We Lead.</span>
|
<span className="text-gradient" style={{ fontWeight: 500 }}>In Tech We Innovate. In Trust We Lead.</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
||||||
YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group — a trusted UAE technology and robotics partner helping businesses innovate, automate, and deploy intelligent robotic solutions. The UAE's dedicated destination for selected Unitree and Pudu Robotics solutions.
|
YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE technology and robotics partner helping businesses innovate, automate, and deploy intelligent robotic solutions. The UAE's dedicated destination for selected Unitree and Pudu Robotics solutions.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default function AdminLoginPage() {
|
|||||||
Admin Login
|
Admin Login
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ fontSize: '0.8rem', color: '#8891C7', margin: 0 }}>
|
<p style={{ fontSize: '0.8rem', color: '#8891C7', margin: 0 }}>
|
||||||
Lootah Robotics — G1 Configurator
|
Lootah Robotics G1 Configurator
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ export default function AdminPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// DB is empty — push local items to server so they persist for all users
|
// DB is empty push local items to server so they persist for all users
|
||||||
if (localItems.length > 0) {
|
if (localItems.length > 0) {
|
||||||
await fetch('/api/admin/pricing/', {
|
await fetch('/api/admin/pricing/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -631,7 +631,7 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
{/* GLB file picker */}
|
{/* GLB file picker */}
|
||||||
<div style={{ marginTop: '0.625rem' }}>
|
<div style={{ marginTop: '0.625rem' }}>
|
||||||
<label style={labelStyle}>3D Model — .glb file (optional)</label>
|
<label style={labelStyle}>3D Model .glb file (optional)</label>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@ -19,7 +19,7 @@ async function verifyAdmin() {
|
|||||||
|
|
||||||
// GET /api/admin/pricing/ → all pricing items from DB
|
// GET /api/admin/pricing/ → all pricing items from DB
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
// Public endpoint — no auth required so the storefront can read prices
|
// Public endpoint no auth required so the storefront can read prices
|
||||||
const items = await prisma.pricingItem.findMany({ orderBy: { sortOrder: 'asc' } });
|
const items = await prisma.pricingItem.findMany({ orderBy: { sortOrder: 'asc' } });
|
||||||
return NextResponse.json({ items });
|
return NextResponse.json({ items });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ error: 'Only .glb files are allowed' }, { status: 400 });
|
return NextResponse.json({ error: 'Only .glb files are allowed' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize the item ID — convert underscores to hyphens, keep lowercase letters, digits, hyphens
|
// Sanitize the item ID convert underscores to hyphens, keep lowercase letters, digits, hyphens
|
||||||
const safeId = itemId.toLowerCase().replace(/_/g, '-').replace(/[^a-z0-9-]/g, '');
|
const safeId = itemId.toLowerCase().replace(/_/g, '-').replace(/[^a-z0-9-]/g, '');
|
||||||
if (!safeId) {
|
if (!safeId) {
|
||||||
return NextResponse.json({ error: 'Invalid itemId' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid itemId' }, { status: 400 });
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export async function POST(request: Request) {
|
|||||||
stripeStatus = pi.status;
|
stripeStatus = pi.status;
|
||||||
stripeMetadata = (pi.metadata ?? {}) as Record<string, string>;
|
stripeMetadata = (pi.metadata ?? {}) as Record<string, string>;
|
||||||
} catch {
|
} catch {
|
||||||
// Stripe unreachable — save with client-submitted data
|
// Stripe unreachable save with client-submitted data
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = stripeMetadata;
|
const m = stripeMetadata;
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { FooterAndContact } from '@/components/FooterAndContact';
|
|||||||
import { InquiryForm } from '@/components/robotics/InquiryForm';
|
import { InquiryForm } from '@/components/robotics/InquiryForm';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Book a Robotics Demo in Dubai — YS Lootah Robotics',
|
title: 'Book a Robotics Demo in Dubai YS Lootah Robotics',
|
||||||
description:
|
description:
|
||||||
'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action — available exclusively in the UAE through YS Lootah Robotics.',
|
'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function BookDemoPage() {
|
export default function BookDemoPage() {
|
||||||
@ -28,7 +28,7 @@ export default function BookDemoPage() {
|
|||||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||||
{[
|
{[
|
||||||
'Choose a humanoid, quadruped, or service robot to see live',
|
'Choose a humanoid, quadruped, or service robot to see live',
|
||||||
'Designed around your industry — restaurant, mall, hotel, security, etc.',
|
'Designed around your industry restaurant, mall, hotel, security, etc.',
|
||||||
'Walk through configuration and deployment options',
|
'Walk through configuration and deployment options',
|
||||||
'Get UAE-specific pricing and availability',
|
'Get UAE-specific pricing and availability',
|
||||||
].map((b) => (
|
].map((b) => (
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { MotionSection } from '@/components/ui/MotionSection';
|
|||||||
import { DemoCTA } from '@/components/robotics/DemoCTA';
|
import { DemoCTA } from '@/components/robotics/DemoCTA';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Robotics Brands — Unitree & Pudu | Exclusive UAE Access via YS Lootah Robotics',
|
title: 'Robotics Brands Unitree & Pudu | Exclusive UAE Access via YS Lootah Robotics',
|
||||||
description:
|
description:
|
||||||
'Selected Unitree and Pudu Robotics solutions — available exclusively in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.',
|
'Selected Unitree and Pudu Robotics solutions available exclusively in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ORDER: RobotBrand[] = ['unitree', 'pudu'];
|
const ORDER: RobotBrand[] = ['unitree', 'pudu'];
|
||||||
@ -25,11 +25,11 @@ export default function BrandsPage() {
|
|||||||
<span className="eyebrow">Exclusive UAE Access · Dubai</span>
|
<span className="eyebrow">Exclusive UAE Access · Dubai</span>
|
||||||
<h1 style={{ margin: 0, fontSize: 'clamp(2rem, 5vw, 3.4rem)', fontWeight: 300, lineHeight: 1.05, letterSpacing: '-0.03em' }}>
|
<h1 style={{ margin: 0, fontSize: 'clamp(2rem, 5vw, 3.4rem)', fontWeight: 300, lineHeight: 1.05, letterSpacing: '-0.03em' }}>
|
||||||
<span className="text-gradient" style={{ fontWeight: 500 }}>
|
<span className="text-gradient" style={{ fontWeight: 500 }}>
|
||||||
Selected Unitree and Pudu solutions — exclusively in the UAE.
|
Selected Unitree and Pudu solutions exclusively in the UAE.
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
||||||
YS Lootah Robotics holds exclusive UAE sales rights for selected Unitree and Pudu Robotics solutions — with on-the-ground sales, demo, and deployment support across Dubai and the UAE.
|
YS Lootah Robotics holds exclusive UAE sales rights for selected Unitree and Pudu Robotics solutions with on-the-ground sales, demo, and deployment support across Dubai and the UAE.
|
||||||
</p>
|
</p>
|
||||||
<p style={{ margin: 0, color: '#6a73a5', fontSize: '0.82rem', lineHeight: 1.6 }}>
|
<p style={{ margin: 0, color: '#6a73a5', fontSize: '0.82rem', lineHeight: 1.6 }}>
|
||||||
Brand names and product trademarks are property of their respective owners. Available exclusively in the UAE through YS Lootah Robotics.
|
Brand names and product trademarks are property of their respective owners. Available exclusively in the UAE through YS Lootah Robotics.
|
||||||
@ -84,7 +84,7 @@ export default function BrandsPage() {
|
|||||||
|
|
||||||
<DemoCTA
|
<DemoCTA
|
||||||
title="Not sure which brand fits?"
|
title="Not sure which brand fits?"
|
||||||
description="Tell us about your venue, timeline, and use case. We will recommend a brand and model — and book a live demo at our Dubai showroom."
|
description="Tell us about your venue, timeline, and use case. We will recommend a brand and model and book a live demo at our Dubai showroom."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export default function ConfigurePage() {
|
|||||||
<span className="text-gradient" style={{ fontWeight: 500 }}>Configure your robot.</span>
|
<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 style={{ margin: 0, color: '#8891C7', fontSize: 'clamp(0.85rem, 1.6vw, 0.95rem)', lineHeight: 1.6, maxWidth: 720 }}>
|
||||||
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 visualize your Unitree G1 humanoid before you request a quotation from YS Lootah Robotics.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { FooterAndContact } from '@/components/FooterAndContact';
|
|||||||
import { InquiryForm } from '@/components/robotics/InquiryForm';
|
import { InquiryForm } from '@/components/robotics/InquiryForm';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Contact YS Lootah Robotics Dubai — Robotics Sales & Demo Inquiries',
|
title: 'Contact YS Lootah Robotics Dubai Robotics Sales & Demo Inquiries',
|
||||||
description:
|
description:
|
||||||
'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics — the exclusive UAE destination for selected Unitree and Pudu Robotics solutions — by phone, email, or WhatsApp.',
|
'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics the exclusive UAE destination for selected Unitree and Pudu Robotics solutions by phone, email, or WhatsApp.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
/* === Luxury robotics — Graphite + Royal Blue + Silver === */
|
/* === Luxury robotics Graphite + Royal Blue + Silver === */
|
||||||
--color-bg: #0a0a0c;
|
--color-bg: #0a0a0c;
|
||||||
--color-bg-2: #16151a;
|
--color-bg-2: #16151a;
|
||||||
--color-bg-3: #1c1b21;
|
--color-bg-3: #1c1b21;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { DemoCTA } from '@/components/robotics/DemoCTA';
|
|||||||
import { MotionSection } from '@/components/ui/MotionSection';
|
import { MotionSection } from '@/components/ui/MotionSection';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Robotics for UAE Industries — YS Lootah Robotics Dubai',
|
title: 'Robotics for UAE Industries YS Lootah Robotics Dubai',
|
||||||
description:
|
description:
|
||||||
'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, warehouses, smart buildings, and government across the UAE.',
|
'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, warehouses, smart buildings, and government across the UAE.',
|
||||||
};
|
};
|
||||||
@ -26,7 +26,7 @@ export default function IndustriesPage() {
|
|||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.95rem, 2vw, 1.05rem)', lineHeight: 1.7 }}>
|
||||||
We deploy humanoid, quadruped, and service robots across industries that are reshaping how the UAE operates — every venue is matched to the right robot.
|
We deploy humanoid, quadruped, and service robots across industries that are reshaping how the UAE operates every venue is matched to the right robot.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export default function IndustriesPage() {
|
|||||||
|
|
||||||
<DemoCTA
|
<DemoCTA
|
||||||
title="Don't see your industry?"
|
title="Don't see your industry?"
|
||||||
description="If your venue is unusual, complex, or one-of-a-kind — that's exactly when we like to talk. Let's design the robotics fit."
|
description="If your venue is unusual, complex, or one-of-a-kind that's exactly when we like to talk. Let's design the robotics fit."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import "./globals.css";
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL('https://yslootahrobotics.com'),
|
metadataBase: new URL('https://yslootahrobotics.com'),
|
||||||
title: {
|
title: {
|
||||||
default: 'YS Lootah Robotics — Exclusive UAE Access to Unitree & Pudu Robotics',
|
default: 'YS Lootah Robotics Exclusive UAE Access to Unitree & Pudu Robotics',
|
||||||
template: '%s | YS Lootah Robotics',
|
template: '%s | YS Lootah Robotics',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
@ -28,7 +28,7 @@ export const metadata: Metadata = {
|
|||||||
],
|
],
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'website',
|
type: 'website',
|
||||||
title: 'YS Lootah Robotics — Exclusive UAE Access to Unitree & Pudu Robotics',
|
title: 'YS Lootah Robotics Exclusive UAE Access to Unitree & Pudu Robotics',
|
||||||
description:
|
description:
|
||||||
'Advanced robotics. Exclusive UAE access. Selected Unitree and Pudu solutions available through YS Lootah Robotics in Dubai.',
|
'Advanced robotics. Exclusive UAE access. Selected Unitree and Pudu solutions available through YS Lootah Robotics in Dubai.',
|
||||||
locale: 'en_AE',
|
locale: 'en_AE',
|
||||||
@ -36,7 +36,7 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'YS Lootah Robotics — Exclusive UAE Access to Unitree & Pudu Robotics',
|
title: 'YS Lootah Robotics Exclusive UAE Access to Unitree & Pudu Robotics',
|
||||||
description:
|
description:
|
||||||
'The UAE’s dedicated destination for Unitree and Pudu Robotics sales, demo inquiries, and business deployment.',
|
'The UAE’s dedicated destination for Unitree and Pudu Robotics sales, demo inquiries, and business deployment.',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { MotionSection } from '@/components/ui/MotionSection';
|
|||||||
import { FEATURED_ROBOTS } from '@/data/robots';
|
import { FEATURED_ROBOTS } from '@/data/robots';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'YS Lootah Robotics — Exclusive UAE Access to Unitree & Pudu Robotics',
|
title: 'YS Lootah Robotics Exclusive UAE Access to Unitree & Pudu Robotics',
|
||||||
description:
|
description:
|
||||||
'YS Lootah Robotics is the exclusive UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
|
'YS Lootah Robotics is the exclusive UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
|
||||||
keywords: [
|
keywords: [
|
||||||
@ -65,30 +65,30 @@ export default function HomePage() {
|
|||||||
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Featured robotics brands"
|
eyebrow="Featured robotics brands"
|
||||||
title="Unitree & Pudu — available exclusively in the UAE."
|
title="Unitree & Pudu available exclusively in the UAE."
|
||||||
description="We hold exclusive UAE sales rights for selected Unitree and Pudu Robotics solutions — curated for proven performance in the field and full local support across Dubai."
|
description="We hold exclusive UAE sales rights for selected Unitree and Pudu Robotics solutions curated for proven performance in the field and full local support across Dubai."
|
||||||
/>
|
/>
|
||||||
<BrandShowcase />
|
<BrandShowcase />
|
||||||
</div>
|
</div>
|
||||||
</MotionSection>
|
</MotionSection>
|
||||||
|
|
||||||
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="why">
|
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0 clamp(1.5rem, 3vw, 2.5rem)' }} id="why">
|
||||||
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Why YS Lootah Robotics"
|
eyebrow="Why YS Lootah Robotics"
|
||||||
title="A premium robotics partner — not a marketplace."
|
title="A premium robotics partner not a marketplace."
|
||||||
description="From inquiry to live deployment, we manage your robotics program end to end with a UAE-based team and exclusive access to Unitree and Pudu portfolios."
|
description="From inquiry to live deployment, we manage your robotics program end to end with a UAE-based team and exclusive access to Unitree and Pudu portfolios."
|
||||||
/>
|
/>
|
||||||
<BentoGrid />
|
<BentoGrid />
|
||||||
</div>
|
</div>
|
||||||
</MotionSection>
|
</MotionSection>
|
||||||
|
|
||||||
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="categories">
|
<MotionSection style={{ padding: 'clamp(1.5rem, 3vw, 2.5rem) 0 clamp(3rem, 6vw, 5rem)' }} id="categories">
|
||||||
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Robot categories"
|
eyebrow="Robot categories"
|
||||||
title="From quadruped patrol to humanoid concierge."
|
title="From quadruped patrol to humanoid concierge."
|
||||||
description="Find the right robot for your business — humanoid for events and education, quadruped for inspection and security, service and delivery for hospitality."
|
description="Find the right robot for your business humanoid for events and education, quadruped for inspection and security, service and delivery for hospitality."
|
||||||
/>
|
/>
|
||||||
<RobotCategoryGrid />
|
<RobotCategoryGrid />
|
||||||
</div>
|
</div>
|
||||||
@ -126,7 +126,7 @@ export default function HomePage() {
|
|||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Our solutions"
|
eyebrow="Our solutions"
|
||||||
title="Intelligent robotics across the full lifecycle."
|
title="Intelligent robotics across the full lifecycle."
|
||||||
description="From autonomous robotics and intelligent automation to motion programming, SDKs, and remote diagnostics — a complete robotics partner for UAE businesses."
|
description="From autonomous robotics and intelligent automation to motion programming, SDKs, and remote diagnostics a complete robotics partner for UAE businesses."
|
||||||
/>
|
/>
|
||||||
<ServicesGrid />
|
<ServicesGrid />
|
||||||
</div>
|
</div>
|
||||||
@ -149,7 +149,7 @@ export default function HomePage() {
|
|||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="How we work"
|
eyebrow="How we work"
|
||||||
title="From inquiry to live deployment."
|
title="From inquiry to live deployment."
|
||||||
description="Three steps from your first message to a robot running in your venue — fully supported by our Dubai team."
|
description="Three steps from your first message to a robot running in your venue fully supported by our Dubai team."
|
||||||
/>
|
/>
|
||||||
<HowItWorks />
|
<HowItWorks />
|
||||||
</div>
|
</div>
|
||||||
@ -160,7 +160,7 @@ export default function HomePage() {
|
|||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Industries served"
|
eyebrow="Industries served"
|
||||||
title="Robotics solutions for UAE businesses."
|
title="Robotics solutions for UAE businesses."
|
||||||
description="From restaurants to security operators to government innovation programs — we deliver robotics fits for the venue."
|
description="From restaurants to security operators to government innovation programs we deliver robotics fits for the venue."
|
||||||
/>
|
/>
|
||||||
<IndustryUseCases limit={8} />
|
<IndustryUseCases limit={8} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -21,10 +21,10 @@ export function generateStaticParams() {
|
|||||||
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
|
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const robot = getRobotBySlug(slug);
|
const robot = getRobotBySlug(slug);
|
||||||
if (!robot) return { title: 'Robot not found — YS Lootah Robotics' };
|
if (!robot) return { title: 'Robot not found YS Lootah Robotics' };
|
||||||
return {
|
return {
|
||||||
title: `${robot.brandLabel} ${robot.name} — Available in Dubai | YS Lootah Robotics`,
|
title: `${robot.brandLabel} ${robot.name} Available in Dubai | YS Lootah Robotics`,
|
||||||
description: `${robot.shortDescription} Available through YS Lootah Robotics in Dubai — request a price or book a live demo.`,
|
description: `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`,
|
||||||
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'],
|
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
|
|||||||
<span className="eyebrow">Talk to an advisor</span>
|
<span className="eyebrow">Talk to an advisor</span>
|
||||||
<h3 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Prefer a quick conversation?</h3>
|
<h3 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Prefer a quick conversation?</h3>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7 }}>
|
||||||
Call our Dubai team or message us on WhatsApp — we will share availability and demo slots for {robot.name}.
|
Call our Dubai team or message us on WhatsApp we will share availability and demo slots for {robot.name}.
|
||||||
</p>
|
</p>
|
||||||
<a className="btn btn-ghost" href="tel:+971559482728">Call +971 55 948 2728</a>
|
<a className="btn btn-ghost" href="tel:+971559482728">Call +971 55 948 2728</a>
|
||||||
<a className="btn btn-outline" href="https://wa.me/971559482728" target="_blank" rel="noopener noreferrer">WhatsApp us</a>
|
<a className="btn btn-outline" href="https://wa.me/971559482728" target="_blank" rel="noopener noreferrer">WhatsApp us</a>
|
||||||
@ -179,7 +179,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
|
|||||||
<MotionSection>
|
<MotionSection>
|
||||||
<ConfigureCTA
|
<ConfigureCTA
|
||||||
title={`Configure ${robot.name} for your business.`}
|
title={`Configure ${robot.name} for your business.`}
|
||||||
description="Choose persona, attire, color, and accessories — visualize your deployment before requesting a quotation."
|
description="Choose persona, attire, color, and accessories visualize your deployment before requesting a quotation."
|
||||||
/>
|
/>
|
||||||
</MotionSection>
|
</MotionSection>
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { RoboticsSplineShowcase } from '@/components/sections/robotics-spline-sh
|
|||||||
import { ROBOTS } from '@/data/robots';
|
import { ROBOTS } from '@/data/robots';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Robots Catalog — YS Lootah Robotics Dubai',
|
title: 'Robots Catalog YS Lootah Robotics Dubai',
|
||||||
description:
|
description:
|
||||||
'Explore selected humanoid, quadruped, service, delivery, hospitality, and cleaning robots from Unitree and Pudu — available exclusively in the UAE through YS Lootah Robotics.',
|
'Explore selected humanoid, quadruped, service, delivery, hospitality, and cleaning robots from Unitree and Pudu available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RobotsPage() {
|
export default function RobotsPage() {
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export function FooterAndContact() {
|
|||||||
In Tech We Innovate · In Trust We Lead
|
In Tech We Innovate · In Trust We Lead
|
||||||
</p>
|
</p>
|
||||||
<p style={{ margin: 0, color: '#8891C7', lineHeight: 1.7, fontSize: '0.95rem' }}>
|
<p style={{ margin: 0, color: '#8891C7', lineHeight: 1.7, fontSize: '0.95rem' }}>
|
||||||
Innovating today for a smarter tomorrow. YS Lootah Robotics — the UAE's dedicated destination for selected Unitree and Pudu Robotics solutions.
|
Innovating today for a smarter tomorrow. YS Lootah Robotics the UAE's dedicated destination for selected Unitree and Pudu Robotics solutions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '0.625rem', marginTop: '1.5rem' }}>
|
<div style={{ display: 'flex', gap: '0.625rem', marginTop: '1.5rem' }}>
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export function Navbar() {
|
|||||||
transition: 'padding 0.3s ease',
|
transition: 'padding 0.3s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href="/" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '0.65rem', minWidth: 0 }} onClick={() => setMobileOpen(false)} aria-label="YS Lootah Robotics — Home">
|
<Link href="/" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '0.65rem', minWidth: 0 }} onClick={() => setMobileOpen(false)} aria-label="YS Lootah Robotics Home">
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const STATIC_ATTIRE_GLB: Record<string, string> = {
|
|||||||
'business-suit': '/Suit.glb',
|
'business-suit': '/Suit.glb',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same chassis GLB for both — variants differ in software/licensing (EDU = open-source
|
// Same chassis GLB for both variants differ in software/licensing (EDU = open-source
|
||||||
// education / research build), not in geometry.
|
// education / research build), not in geometry.
|
||||||
const BODY_GLB: Record<'basic' | 'edu', string> = {
|
const BODY_GLB: Record<'basic' | 'edu', string> = {
|
||||||
basic: '/Unitree_G1.glb',
|
basic: '/Unitree_G1.glb',
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function PaymentStep() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to review — actual confirmation happens on "Place Order"
|
// Move to review actual confirmation happens on "Place Order"
|
||||||
orderStore.getState().setPayment({ status: 'idle' });
|
orderStore.getState().setPayment({ status: 'idle' });
|
||||||
orderStore.getState().setStep('review');
|
orderStore.getState().setStep('review');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export function ReviewStep() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment succeeded — save order to DB and upload snapshot
|
// Payment succeeded save order to DB and upload snapshot
|
||||||
const paymentIntentId = orderStore.getState().payment.paymentIntentId;
|
const paymentIntentId = orderStore.getState().payment.paymentIntentId;
|
||||||
const s = orderStore.getState().shipping;
|
const s = orderStore.getState().shipping;
|
||||||
const configSummary = orderStore.getState().personaSummary;
|
const configSummary = orderStore.getState().personaSummary;
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export function BentoGrid() {
|
|||||||
<div style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: '0.75rem', maxWidth: '50%' }}>
|
<div style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: '0.75rem', maxWidth: '50%' }}>
|
||||||
<span className="eyebrow">Flagship · Dubai</span>
|
<span className="eyebrow">Flagship · Dubai</span>
|
||||||
<h3 style={{ margin: 0, fontSize: 'clamp(1.4rem, 2.6vw, 2rem)', fontWeight: 500, letterSpacing: '-0.02em', lineHeight: 1.1 }}>
|
<h3 style={{ margin: 0, fontSize: 'clamp(1.4rem, 2.6vw, 2rem)', fontWeight: 500, letterSpacing: '-0.02em', lineHeight: 1.1 }}>
|
||||||
<span className="text-gradient">Unitree G1 humanoid — live in our Dubai showroom.</span>
|
<span className="text-gradient">Unitree G1 humanoid live in our Dubai showroom.</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: '0.92rem', lineHeight: 1.6 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: '0.92rem', lineHeight: 1.6 }}>
|
||||||
Configure persona, attire, and accessories. Then book a live demo.
|
Configure persona, attire, and accessories. Then book a live demo.
|
||||||
@ -52,7 +52,7 @@ export function BentoGrid() {
|
|||||||
<span className="text-gradient">Fast quotes for UAE businesses.</span>
|
<span className="text-gradient">Fast quotes for UAE businesses.</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p style={{ margin: 0, color: '#8891C7', fontSize: '0.88rem', lineHeight: 1.55 }}>
|
<p style={{ margin: 0, color: '#8891C7', fontSize: '0.88rem', lineHeight: 1.55 }}>
|
||||||
Tell us your use case — we respond within one business day with availability and pricing.
|
Tell us your use case we respond within one business day with availability and pricing.
|
||||||
</p>
|
</p>
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ export function BentoGrid() {
|
|||||||
<Tile col="span 4" row="span 1" accent="#DEE0F0" icon={SOC_PATHS.brain}>
|
<Tile col="span 4" row="span 1" accent="#DEE0F0" icon={SOC_PATHS.brain}>
|
||||||
<span className="eyebrow">Exclusive UAE access</span>
|
<span className="eyebrow">Exclusive UAE access</span>
|
||||||
<h3 style={{ margin: 0, fontSize: 'clamp(1.05rem, 1.8vw, 1.3rem)', fontWeight: 500, letterSpacing: '-0.01em', lineHeight: 1.2 }}>
|
<h3 style={{ margin: 0, fontSize: 'clamp(1.05rem, 1.8vw, 1.3rem)', fontWeight: 500, letterSpacing: '-0.01em', lineHeight: 1.2 }}>
|
||||||
<span className="text-gradient">Unitree · Pudu — one Dubai team.</span>
|
<span className="text-gradient">Unitree · Pudu one Dubai team.</span>
|
||||||
</h3>
|
</h3>
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ type BrandVisual = {
|
|||||||
const BRAND_VISUALS: Record<RobotBrand, BrandVisual> = {
|
const BRAND_VISUALS: Record<RobotBrand, BrandVisual> = {
|
||||||
unitree: {
|
unitree: {
|
||||||
description:
|
description:
|
||||||
'Quadruped and humanoid robotics platforms — available exclusively in the UAE through YS Lootah Robotics.',
|
'Quadruped and humanoid robotics platforms available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
chips: ['Quadruped', 'Humanoid', 'Inspection', 'Research'],
|
chips: ['Quadruped', 'Humanoid', 'Inspection', 'Research'],
|
||||||
primary: { src: '/images/robots/unitree-g1.png', alt: 'Unitree G1 humanoid robot' },
|
primary: { src: '/images/robots/unitree-g1.png', alt: 'Unitree G1 humanoid robot' },
|
||||||
secondary: { src: '/images/robots/unitree-go2.png', alt: 'Unitree Go2 quadruped robot' },
|
secondary: { src: '/images/robots/unitree-go2.png', alt: 'Unitree Go2 quadruped robot' },
|
||||||
},
|
},
|
||||||
pudu: {
|
pudu: {
|
||||||
description:
|
description:
|
||||||
'Service, delivery, cleaning, and hospitality robotics — available exclusively in the UAE through YS Lootah Robotics.',
|
'Service, delivery, cleaning, and hospitality robotics available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
chips: ['Service', 'Delivery', 'Cleaning', 'Hospitality'],
|
chips: ['Service', 'Delivery', 'Cleaning', 'Hospitality'],
|
||||||
primary: { src: '/images/robots/pudu-bellabot.svg', alt: 'Pudu BellaBot delivery robot' },
|
primary: { src: '/images/robots/pudu-bellabot.svg', alt: 'Pudu BellaBot delivery robot' },
|
||||||
secondary: { src: '/images/robots/pudu-kettybot.svg', alt: 'Pudu KettyBot service robot' },
|
secondary: { src: '/images/robots/pudu-kettybot.svg', alt: 'Pudu KettyBot service robot' },
|
||||||
@ -128,7 +128,7 @@ function BrandCard({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* LEFT — copy */}
|
{/* LEFT copy */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@ -255,7 +255,7 @@ function BrandCard({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT — product visual */}
|
{/* RIGHT product visual */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export function BuSunaidahSection() {
|
|||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7, fontSize: '1rem' }}>
|
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7, fontSize: '1rem' }}>
|
||||||
An Emirati culture-inspired robotics character — Bu Sunaidah is YS Lootah Robotics' community and storytelling ambassador. Designed to celebrate UAE heritage at media events, exhibitions, and innovation programs, Bu Sunaidah shows what intelligent automation looks like when it speaks the language of the region.
|
An Emirati culture-inspired robotics character Bu Sunaidah is YS Lootah Robotics' community and storytelling ambassador. Designed to celebrate UAE heritage at media events, exhibitions, and innovation programs, Bu Sunaidah shows what intelligent automation looks like when it speaks the language of the region.
|
||||||
</p>
|
</p>
|
||||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
|
||||||
{[
|
{[
|
||||||
@ -112,7 +112,7 @@ export function BuSunaidahSection() {
|
|||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/images/robots/unitree-g1.png"
|
src="/images/robots/unitree-g1.png"
|
||||||
alt="Bu Sunaidah — Emirati-inspired robotics character based on a Unitree G1 humanoid"
|
alt="Bu Sunaidah Emirati-inspired robotics character based on a Unitree G1 humanoid"
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 768px) 90vw, 460px"
|
sizes="(max-width: 768px) 90vw, 460px"
|
||||||
style={{ objectFit: 'contain', padding: 'clamp(1.5rem, 4vw, 3rem)' }}
|
style={{ objectFit: 'contain', padding: 'clamp(1.5rem, 4vw, 3rem)' }}
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export function CompanyStory() {
|
|||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Left — narrative */}
|
{/* Left narrative */}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem', minWidth: 0 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem', minWidth: 0 }}>
|
||||||
<span className="eyebrow">About YS Lootah Robotics</span>
|
<span className="eyebrow">About YS Lootah Robotics</span>
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ export function CompanyStory() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right — capability modules */}
|
{/* Right capability modules */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
@ -11,7 +11,7 @@ type Props = {
|
|||||||
|
|
||||||
export function ConfigureCTA({
|
export function ConfigureCTA({
|
||||||
title = 'Configure a robotics solution for your business.',
|
title = 'Configure a robotics solution for your business.',
|
||||||
description = 'Build a tailored robot — choose persona, attire, color, accessories, and accessories. Visualize before you request a quote.',
|
description = 'Build a tailored robot choose persona, attire, color, accessories, and accessories. Visualize before you request a quote.',
|
||||||
href = '/configure/',
|
href = '/configure/',
|
||||||
ctaLabel = 'Start configuration',
|
ctaLabel = 'Start configuration',
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export function ExclusiveAccessSection() {
|
|||||||
className="exa-grid"
|
className="exa-grid"
|
||||||
style={{ position: 'relative', display: 'grid', gap: 'clamp(1.5rem, 3.5vw, 2.5rem)', alignItems: 'stretch' }}
|
style={{ position: 'relative', display: 'grid', gap: 'clamp(1.5rem, 3.5vw, 2.5rem)', alignItems: 'stretch' }}
|
||||||
>
|
>
|
||||||
{/* Left — copy */}
|
{/* Left copy */}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem', minWidth: 0 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem', minWidth: 0 }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
@ -66,7 +66,7 @@ export function ExclusiveAccessSection() {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.92rem, 1.8vw, 1rem)', lineHeight: 1.7, maxWidth: 520 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(0.92rem, 1.8vw, 1rem)', lineHeight: 1.7, maxWidth: 520 }}>
|
||||||
Selected solutions delivered to UAE businesses, venues, and innovators — from inquiry to live deployment, fully supported in Dubai.
|
Selected solutions delivered to UAE businesses, venues, and innovators from inquiry to live deployment, fully supported in Dubai.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
@ -91,7 +91,7 @@ export function ExclusiveAccessSection() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right — premium showroom grid */}
|
{/* Right premium showroom grid */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -212,7 +212,7 @@ function BrandShowcaseTile({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* secondary robot — back-left */}
|
{/* secondary robot back-left */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -235,7 +235,7 @@ function BrandShowcaseTile({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* primary robot — front-right, slightly larger */}
|
{/* primary robot front-right, slightly larger */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -312,7 +312,7 @@ function TerritoryTile() {
|
|||||||
boxShadow: '0 12px 36px rgba(0,0,0,0.55), inset 0 1px 0 rgba(222,224,240,0.06)',
|
boxShadow: '0 12px 36px rgba(0,0,0,0.55), inset 0 1px 0 rgba(222,224,240,0.06)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* abstract UAE "territory" graphic — ring map style */}
|
{/* abstract UAE "territory" graphic ring map style */}
|
||||||
<svg
|
<svg
|
||||||
aria-hidden
|
aria-hidden
|
||||||
viewBox="0 0 220 140"
|
viewBox="0 0 220 140"
|
||||||
@ -345,7 +345,7 @@ function TerritoryTile() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ position: 'relative', fontSize: '0.72rem', color: '#DEE0F0', lineHeight: 1.4, marginTop: 'auto' }}>
|
<span style={{ position: 'relative', fontSize: '0.72rem', color: '#DEE0F0', lineHeight: 1.4, marginTop: 'auto' }}>
|
||||||
Exclusive sales rights — selected Unitree & Pudu solutions.
|
Exclusive sales rights selected Unitree & Pudu solutions.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export function FounderSection() {
|
|||||||
Founder · Yousuf Saeed Lootah Investment Group
|
Founder · Yousuf Saeed Lootah Investment Group
|
||||||
</p>
|
</p>
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7, fontSize: '1rem' }}>
|
<p style={{ margin: 0, color: '#DEE0F0', lineHeight: 1.7, fontSize: '1rem' }}>
|
||||||
Under the leadership of Mr. Yousif Bin Saeed Al Lootah, the group has championed innovation, trust, and long-term value creation across the UAE — from technology and robotics to investment and enterprise. YS Lootah Robotics carries that vision into the future of intelligent automation.
|
Under the leadership of Mr. Yousif Bin Saeed Al Lootah, the group has championed innovation, trust, and long-term value creation across the UAE from technology and robotics to investment and enterprise. YS Lootah Robotics carries that vision into the future of intelligent automation.
|
||||||
</p>
|
</p>
|
||||||
<p style={{ margin: 0, color: '#8891C7', lineHeight: 1.7, fontSize: '0.92rem' }}>
|
<p style={{ margin: 0, color: '#8891C7', lineHeight: 1.7, fontSize: '0.92rem' }}>
|
||||||
<em>“Innovating today for a smarter tomorrow.”</em>
|
<em>“Innovating today for a smarter tomorrow.”</em>
|
||||||
|
|||||||
@ -155,7 +155,7 @@ export function Hero3DRobotics() {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(1rem, 2vw, 1.18rem)', lineHeight: 1.7, maxWidth: 600, fontWeight: 300 }}>
|
<p style={{ margin: 0, color: '#DEE0F0', fontSize: 'clamp(1rem, 2vw, 1.18rem)', lineHeight: 1.7, maxWidth: 600, fontWeight: 300 }}>
|
||||||
YS Lootah Robotics is the exclusive UAE sales destination for selected Unitree and Pudu Robotics solutions — helping businesses explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.
|
YS Lootah Robotics is the exclusive UAE sales destination for selected Unitree and Pudu Robotics solutions helping businesses explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.
|
||||||
</p>
|
</p>
|
||||||
<p style={{ margin: 0, color: '#BFC3E2', fontSize: '0.78rem', letterSpacing: '0.32em', textTransform: 'uppercase', fontWeight: 600 }}>
|
<p style={{ margin: 0, color: '#BFC3E2', fontSize: '0.78rem', letterSpacing: '0.32em', textTransform: 'uppercase', fontWeight: 600 }}>
|
||||||
In Tech We Innovate · In Trust We Lead
|
In Tech We Innovate · In Trust We Lead
|
||||||
@ -309,7 +309,7 @@ export function Hero3DRobotics() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Robot image — main */}
|
{/* Robot image main */}
|
||||||
{FEATURED_ROBOTS.map((r, idx) => (
|
{FEATURED_ROBOTS.map((r, idx) => (
|
||||||
<div
|
<div
|
||||||
key={r.id}
|
key={r.id}
|
||||||
@ -416,7 +416,7 @@ export function Hero3DRobotics() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Edge glass glare — silver/blue rim light */}
|
{/* Edge glass glare silver/blue rim light */}
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
style={{
|
style={{
|
||||||
@ -444,7 +444,7 @@ export function Hero3DRobotics() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Floating labels — keyed to active robot + parallax */}
|
{/* Floating labels keyed to active robot + parallax */}
|
||||||
<div className="hero-labels" aria-hidden>
|
<div className="hero-labels" aria-hidden>
|
||||||
{labels.map((l, i) => {
|
{labels.map((l, i) => {
|
||||||
/* labels drift opposite to tilt for parallax depth */
|
/* labels drift opposite to tilt for parallax depth */
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const STEPS = [
|
|||||||
{
|
{
|
||||||
n: '01',
|
n: '01',
|
||||||
title: 'Discover',
|
title: 'Discover',
|
||||||
body: 'Tell us about your venue and use case. We recommend a brand and model — humanoid, quadruped, service, delivery, cleaning, or commercial.',
|
body: 'Tell us about your venue and use case. We recommend a brand and model humanoid, quadruped, service, delivery, cleaning, or commercial.',
|
||||||
accent: '#DEE0F0',
|
accent: '#DEE0F0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -16,7 +16,7 @@ const STEPS = [
|
|||||||
{
|
{
|
||||||
n: '03',
|
n: '03',
|
||||||
title: 'Deploy & support',
|
title: 'Deploy & support',
|
||||||
body: 'We handle procurement, setup, training, and ongoing service across the UAE — so your team can run, not maintain.',
|
body: 'We handle procurement, setup, training, and ongoing service across the UAE so your team can run, not maintain.',
|
||||||
accent: '#8891C7',
|
accent: '#8891C7',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export function InquiryForm({ defaultRobot = '', defaultCategory = '', intent =
|
|||||||
<form onSubmit={onSubmit} style={{ display: 'grid', gap: '1rem' }}>
|
<form onSubmit={onSubmit} style={{ display: 'grid', gap: '1rem' }}>
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
<div style={{ padding: '0.875rem 1rem', borderRadius: 12, background: 'rgba(34,197,94,0.1)', border: '1px solid rgba(34,197,94,0.3)', color: '#86efac' }}>
|
<div style={{ padding: '0.875rem 1rem', borderRadius: 12, background: 'rgba(34,197,94,0.1)', border: '1px solid rgba(34,197,94,0.3)', color: '#86efac' }}>
|
||||||
Thank you — your message was sent. Our team will reach out shortly.
|
Thank you your message was sent. Our team will reach out shortly.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{status === 'error' && (
|
{status === 'error' && (
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const SERVICES: Service[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Collaborative & Flexible Robotics Integration',
|
title: 'Collaborative & Flexible Robotics Integration',
|
||||||
body: 'Integration of robotic systems into existing workflows — collaborative, flexible, business-friendly.',
|
body: 'Integration of robotic systems into existing workflows collaborative, flexible, business-friendly.',
|
||||||
icon: 'M12 2v6m0 8v6M4.93 4.93l4.24 4.24m5.66 5.66 4.24 4.24M2 12h6m8 0h6M4.93 19.07l4.24-4.24m5.66-5.66 4.24-4.24',
|
icon: 'M12 2v6m0 8v6M4.93 4.93l4.24 4.24m5.66 5.66 4.24 4.24M2 12h6m8 0h6M4.93 19.07l4.24-4.24m5.66-5.66 4.24-4.24',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,25 +3,25 @@
|
|||||||
const REASONS = [
|
const REASONS = [
|
||||||
{
|
{
|
||||||
title: 'Exclusive UAE portfolio',
|
title: 'Exclusive UAE portfolio',
|
||||||
body: 'Selected Unitree and Pudu Robotics solutions — available exclusively in the UAE through YS Lootah Robotics for hospitality, security, education, and industry.',
|
body: 'Selected Unitree and Pudu Robotics solutions available exclusively in the UAE through YS Lootah Robotics for hospitality, security, education, and industry.',
|
||||||
accent: '#DEE0F0',
|
accent: '#DEE0F0',
|
||||||
icon: 'M3 3h7v7H3V3Zm0 11h7v7H3v-7Zm11-11h7v7h-7V3Zm0 11h7v7h-7v-7Z',
|
icon: 'M3 3h7v7H3V3Zm0 11h7v7H3v-7Zm11-11h7v7h-7V3Zm0 11h7v7h-7v-7Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dubai sales and support',
|
title: 'Dubai sales and support',
|
||||||
body: 'A local Dubai team manages your inquiry, demo, deployment, and on-the-ground support — not just a website with a contact form.',
|
body: 'A local Dubai team manages your inquiry, demo, deployment, and on-the-ground support not just a website with a contact form.',
|
||||||
accent: '#273F94',
|
accent: '#273F94',
|
||||||
icon: 'M12 2 2 7v6c0 5 4 9 10 11 6-2 10-6 10-11V7l-10-5Z',
|
icon: 'M12 2 2 7v6c0 5 4 9 10 11 6-2 10-6 10-11V7l-10-5Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Custom configuration',
|
title: 'Custom configuration',
|
||||||
body: 'Configure persona, attire, and accessories for humanoid robots — visualize your deployment before you order with our 3D configurator.',
|
body: 'Configure persona, attire, and accessories for humanoid robots visualize your deployment before you order with our 3D configurator.',
|
||||||
accent: '#8891C7',
|
accent: '#8891C7',
|
||||||
icon: 'M12 4a4 4 0 0 1 4 4v1h5v6h-5v1a4 4 0 0 1-8 0v-1H3V9h5V8a4 4 0 0 1 4-4Z',
|
icon: 'M12 4a4 4 0 0 1 4 4v1h5v6h-5v1a4 4 0 0 1-8 0v-1H3V9h5V8a4 4 0 0 1 4-4Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'End-to-end deployment',
|
title: 'End-to-end deployment',
|
||||||
body: 'From procurement and configuration to delivery, installation, training, and ongoing support — we handle the full lifecycle.',
|
body: 'From procurement and configuration to delivery, installation, training, and ongoing support we handle the full lifecycle.',
|
||||||
accent: '#DEE0F0',
|
accent: '#DEE0F0',
|
||||||
icon: 'M3 12h4l3-7 4 14 3-7h4',
|
icon: 'M3 12h4l3-7 4 14 3-7h4',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export function RoboticsScrollShowcase() {
|
|||||||
function ConsoleInterior() {
|
function ConsoleInterior() {
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full">
|
<div className="relative h-full w-full">
|
||||||
{/* left sidebar — category rail */}
|
{/* left sidebar category rail */}
|
||||||
<aside className="hidden h-full w-44 flex-col gap-1.5 border-r border-[#DEE0F0]/10 bg-[#0a0a0c]/60 p-3 md:flex md:absolute md:inset-y-0 md:left-0">
|
<aside className="hidden h-full w-44 flex-col gap-1.5 border-r border-[#DEE0F0]/10 bg-[#0a0a0c]/60 p-3 md:flex md:absolute md:inset-y-0 md:left-0">
|
||||||
<div className="px-2 py-2 text-[9px] font-semibold uppercase tracking-[0.28em] text-[#8891C7]">
|
<div className="px-2 py-2 text-[9px] font-semibold uppercase tracking-[0.28em] text-[#8891C7]">
|
||||||
Categories
|
Categories
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export function RoboticsSplineShowcase() {
|
|||||||
color: 'rgba(222,224,240,0.82)',
|
color: 'rgba(222,224,240,0.82)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Browse selected Unitree and Pudu Robotics solutions available exclusively in the UAE through YS Lootah Robotics — humanoid, quadruped, service, delivery, and cleaning robots ready for Dubai businesses.
|
Browse selected Unitree and Pudu Robotics solutions available exclusively in the UAE through YS Lootah Robotics humanoid, quadruped, service, delivery, and cleaning robots ready for Dubai businesses.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -319,7 +319,7 @@ export function RoboticsSplineShowcase() {
|
|||||||
<SplineScene scene={SPLINE_SCENE} className="relative z-[2] h-full w-full" />
|
<SplineScene scene={SPLINE_SCENE} className="relative z-[2] h-full w-full" />
|
||||||
|
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
Interactive 3D scene — placeholder Spline reference robot for development. Replace
|
Interactive 3D scene placeholder Spline reference robot for development. Replace
|
||||||
with approved YS Lootah Robotics custom Spline scene when available.
|
with approved YS Lootah Robotics custom Spline scene when available.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export function MotionSection({ children, className = '', id, delay = 0, style }
|
|||||||
opacity: visible ? 1 : 0,
|
opacity: visible ? 1 : 0,
|
||||||
transform: visible ? 'translateY(0)' : 'translateY(28px)',
|
transform: visible ? 'translateY(0)' : 'translateY(28px)',
|
||||||
transition: `opacity 0.9s cubic-bezier(0.16,1,0.3,1) ${delay}s, transform 0.9s cubic-bezier(0.16,1,0.3,1) ${delay}s`,
|
transition: `opacity 0.9s cubic-bezier(0.16,1,0.3,1) ${delay}s, transform 0.9s cubic-bezier(0.16,1,0.3,1) ${delay}s`,
|
||||||
|
scrollMarginTop: '6rem',
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -43,15 +43,15 @@ const GOLD_BRONZE = '#8891C7';
|
|||||||
export const BRANDS: Record<RobotBrand, { name: string; tagline: string; description: string; url: string; accent: string }> = {
|
export const BRANDS: Record<RobotBrand, { name: string; tagline: string; description: string; url: string; accent: string }> = {
|
||||||
unitree: {
|
unitree: {
|
||||||
name: 'Unitree Robotics',
|
name: 'Unitree Robotics',
|
||||||
tagline: 'Quadrupeds and humanoids — available exclusively in the UAE through YS Lootah Robotics.',
|
tagline: 'Quadrupeds and humanoids available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
description:
|
description:
|
||||||
'High-performance bipedal and four-legged robots engineered for mobility, perception, and real-world deployment. Selected Unitree solutions are available exclusively in the UAE through YS Lootah Robotics — with on-the-ground sales, demo, and deployment support in Dubai.',
|
'High-performance bipedal and four-legged robots engineered for mobility, perception, and real-world deployment. Selected Unitree solutions are available exclusively in the UAE through YS Lootah Robotics with on-the-ground sales, demo, and deployment support in Dubai.',
|
||||||
url: 'https://www.unitree.com/',
|
url: 'https://www.unitree.com/',
|
||||||
accent: GOLD_CHAMPAGNE,
|
accent: GOLD_CHAMPAGNE,
|
||||||
},
|
},
|
||||||
pudu: {
|
pudu: {
|
||||||
name: 'Pudu Robotics',
|
name: 'Pudu Robotics',
|
||||||
tagline: 'Service, delivery, and cleaning robotics — available exclusively in the UAE through YS Lootah Robotics.',
|
tagline: 'Service, delivery, and cleaning robotics available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
description:
|
description:
|
||||||
'A global leader in commercial service robotics. Selected Pudu Robotics solutions are available exclusively in the UAE through YS Lootah Robotics for restaurants, hotels, malls, hospitals, and offices.',
|
'A global leader in commercial service robotics. Selected Pudu Robotics solutions are available exclusively in the UAE through YS Lootah Robotics for restaurants, hotels, malls, hospitals, and offices.',
|
||||||
url: 'https://www.pudurobotics.com/',
|
url: 'https://www.pudurobotics.com/',
|
||||||
@ -84,7 +84,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'humanoid',
|
category: 'humanoid',
|
||||||
categories: ['humanoid'],
|
categories: ['humanoid'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A research-grade humanoid with agile locomotion, dexterous hands, and a developer-friendly platform — available exclusively in the UAE through YS Lootah Robotics.',
|
'A research-grade humanoid with agile locomotion, dexterous hands, and a developer-friendly platform available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'The G1 is a next-generation bipedal humanoid designed for AI research, education, and real-world deployment. It can walk, run, balance, and manipulate objects. Through our exclusive UAE access, YS Lootah Robotics delivers fully tailored persona configurations and on-the-ground deployment support across Dubai and the UAE.',
|
'The G1 is a next-generation bipedal humanoid designed for AI research, education, and real-world deployment. It can walk, run, balance, and manipulate objects. Through our exclusive UAE access, YS Lootah Robotics delivers fully tailored persona configurations and on-the-ground deployment support across Dubai and the UAE.',
|
||||||
features: [
|
features: [
|
||||||
@ -92,7 +92,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
'Dynamic balance and high-mobility locomotion',
|
'Dynamic balance and high-mobility locomotion',
|
||||||
'Dexterous hand options for manipulation research',
|
'Dexterous hand options for manipulation research',
|
||||||
'Open developer SDK for custom behaviors',
|
'Open developer SDK for custom behaviors',
|
||||||
'Configurable persona — kandura, suit, vest, doctor, security guard',
|
'Configurable persona kandura, suit, vest, doctor, security guard',
|
||||||
],
|
],
|
||||||
useCases: [
|
useCases: [
|
||||||
'Robotics R&D and AI training labs',
|
'Robotics R&D and AI training labs',
|
||||||
@ -123,9 +123,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'humanoid',
|
category: 'humanoid',
|
||||||
categories: ['humanoid'],
|
categories: ['humanoid'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'Full-size bipedal humanoid engineered for athletic motion, heavy-duty research, and demanding deployment scenarios — available exclusively in the UAE through YS Lootah Robotics.',
|
'Full-size bipedal humanoid engineered for athletic motion, heavy-duty research, and demanding deployment scenarios available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'The Unitree H2 is built for organizations that need a robust, full-scale humanoid — combining powerful actuators with high-bandwidth perception. Ideal for advanced labs and industrial pilots across the UAE, with full local support from YS Lootah Robotics.',
|
'The Unitree H2 is built for organizations that need a robust, full-scale humanoid combining powerful actuators with high-bandwidth perception. Ideal for advanced labs and industrial pilots across the UAE, with full local support from YS Lootah Robotics.',
|
||||||
features: [
|
features: [
|
||||||
'Full adult-scale humanoid frame',
|
'Full adult-scale humanoid frame',
|
||||||
'High-torque actuators for dynamic motion',
|
'High-torque actuators for dynamic motion',
|
||||||
@ -153,7 +153,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'humanoid',
|
category: 'humanoid',
|
||||||
categories: ['humanoid'],
|
categories: ['humanoid'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A purpose-built humanoid that opens advanced robotics to startups, educators, and innovators — available exclusively in the UAE through YS Lootah Robotics.',
|
'A purpose-built humanoid that opens advanced robotics to startups, educators, and innovators available exclusively in the UAE through YS Lootah Robotics.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'Engineered for accessibility without compromising capability, the R1 brings advanced humanoid robotics within reach of startups, educators, and innovators across the UAE.',
|
'Engineered for accessibility without compromising capability, the R1 brings advanced humanoid robotics within reach of startups, educators, and innovators across the UAE.',
|
||||||
features: ['Compact form factor', 'Mobility-focused design', 'Developer-friendly stack', 'Open integration paths'],
|
features: ['Compact form factor', 'Mobility-focused design', 'Developer-friendly stack', 'Open integration paths'],
|
||||||
@ -174,7 +174,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'quadruped',
|
category: 'quadruped',
|
||||||
categories: ['quadruped', 'commercial', 'inspection'],
|
categories: ['quadruped', 'commercial', 'inspection'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A versatile quadruped platform with high-bandwidth perception — available exclusively in the UAE through YS Lootah Robotics for inspection, security, and mobile AI deployments.',
|
'A versatile quadruped platform with high-bandwidth perception available exclusively in the UAE through YS Lootah Robotics for inspection, security, and mobile AI deployments.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'Unitree Go2 is a compact quadruped designed for mobility-first applications. With a powerful AI stack, LiDAR options, and developer SDK, it is the most widely deployed legged robot in commercial robotics worldwide.',
|
'Unitree Go2 is a compact quadruped designed for mobility-first applications. With a powerful AI stack, LiDAR options, and developer SDK, it is the most widely deployed legged robot in commercial robotics worldwide.',
|
||||||
features: [
|
features: [
|
||||||
@ -210,7 +210,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'quadruped',
|
category: 'quadruped',
|
||||||
categories: ['quadruped', 'commercial', 'inspection'],
|
categories: ['quadruped', 'commercial', 'inspection'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A rugged industrial-grade quadruped — available exclusively in the UAE through YS Lootah Robotics for energy, utilities, and infrastructure operators.',
|
'A rugged industrial-grade quadruped available exclusively in the UAE through YS Lootah Robotics for energy, utilities, and infrastructure operators.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'The Unitree B2 brings industrial reliability to legged robotics. With higher payload capacity and IP-rated build quality, it suits energy, utilities, and infrastructure operators across the UAE.',
|
'The Unitree B2 brings industrial reliability to legged robotics. With higher payload capacity and IP-rated build quality, it suits energy, utilities, and infrastructure operators across the UAE.',
|
||||||
features: [
|
features: [
|
||||||
@ -245,9 +245,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'quadruped',
|
category: 'quadruped',
|
||||||
categories: ['quadruped', 'inspection'],
|
categories: ['quadruped', 'inspection'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A balanced quadruped platform — available exclusively in the UAE through YS Lootah Robotics for organizations that need a step up in capability.',
|
'A balanced quadruped platform available exclusively in the UAE through YS Lootah Robotics for organizations that need a step up in capability.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'The A2 sits between Go2 and B2 — combining longer endurance and a richer sensor payload with a manageable size for UAE deployments.',
|
'The A2 sits between Go2 and B2 combining longer endurance and a richer sensor payload with a manageable size for UAE deployments.',
|
||||||
features: ['Extended autonomy', 'Sensor-rich payload bay', 'Robust outdoor capability'],
|
features: ['Extended autonomy', 'Sensor-rich payload bay', 'Robust outdoor capability'],
|
||||||
useCases: ['Site monitoring', 'Inspection at scale', 'Research deployments'],
|
useCases: ['Site monitoring', 'Inspection at scale', 'Research deployments'],
|
||||||
specs: [SPEC_CONSULT, SPEC_PLACEHOLDER],
|
specs: [SPEC_CONSULT, SPEC_PLACEHOLDER],
|
||||||
@ -266,9 +266,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'commercial',
|
category: 'commercial',
|
||||||
categories: ['commercial', 'inspection'],
|
categories: ['commercial', 'inspection'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A perception-first robotics platform combining LiDAR, vision, and compute — available exclusively in the UAE through YS Lootah Robotics for autonomous mobility research.',
|
'A perception-first robotics platform combining LiDAR, vision, and compute available exclusively in the UAE through YS Lootah Robotics for autonomous mobility research.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'AS2 brings together the sensor and compute stack Unitree pairs with its robots — useful for research teams building autonomy on top of legged or wheeled platforms.',
|
'AS2 brings together the sensor and compute stack Unitree pairs with its robots useful for research teams building autonomy on top of legged or wheeled platforms.',
|
||||||
features: ['LiDAR + vision fusion', 'On-board compute', 'Modular integration'],
|
features: ['LiDAR + vision fusion', 'On-board compute', 'Modular integration'],
|
||||||
useCases: ['Autonomy research', 'Inspection autonomy stacks', 'Mobile robotics labs'],
|
useCases: ['Autonomy research', 'Inspection autonomy stacks', 'Mobile robotics labs'],
|
||||||
specs: [SPEC_CONSULT, SPEC_PLACEHOLDER],
|
specs: [SPEC_CONSULT, SPEC_PLACEHOLDER],
|
||||||
@ -287,7 +287,7 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'delivery',
|
category: 'delivery',
|
||||||
categories: ['delivery', 'hospitality', 'service'],
|
categories: ['delivery', 'hospitality', 'service'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A multi-tray delivery robot with an expressive interface — available exclusively in the UAE through YS Lootah Robotics for hospitality venues across Dubai.',
|
'A multi-tray delivery robot with an expressive interface available exclusively in the UAE through YS Lootah Robotics for hospitality venues across Dubai.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'BellaBot delivers food, beverages, and goods with charm, efficiency, and remarkable safety performance. Deploy across restaurants, hotels, and venues in Dubai and the UAE with exclusive local support from YS Lootah Robotics.',
|
'BellaBot delivers food, beverages, and goods with charm, efficiency, and remarkable safety performance. Deploy across restaurants, hotels, and venues in Dubai and the UAE with exclusive local support from YS Lootah Robotics.',
|
||||||
features: [
|
features: [
|
||||||
@ -324,9 +324,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'service',
|
category: 'service',
|
||||||
categories: ['service', 'delivery', 'hospitality'],
|
categories: ['service', 'delivery', 'hospitality'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'A slim, billboard-equipped service robot — available exclusively in the UAE through YS Lootah Robotics for high-traffic retail and dining spaces.',
|
'A slim, billboard-equipped service robot available exclusively in the UAE through YS Lootah Robotics for high-traffic retail and dining spaces.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'KettyBot blends marketing and service. A large display lets venues advertise menus and promotions while the robot greets customers, guides them, and delivers orders — perfect for high-traffic UAE retail and dining spaces.',
|
'KettyBot blends marketing and service. A large display lets venues advertise menus and promotions while the robot greets customers, guides them, and delivers orders perfect for high-traffic UAE retail and dining spaces.',
|
||||||
features: [
|
features: [
|
||||||
'Built-in advertising display',
|
'Built-in advertising display',
|
||||||
'Compact footprint for narrow aisles',
|
'Compact footprint for narrow aisles',
|
||||||
@ -354,9 +354,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'cleaning',
|
category: 'cleaning',
|
||||||
categories: ['cleaning', 'commercial'],
|
categories: ['cleaning', 'commercial'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'An autonomous commercial cleaning platform — available exclusively in the UAE through YS Lootah Robotics for malls, airports, hotels, and offices.',
|
'An autonomous commercial cleaning platform available exclusively in the UAE through YS Lootah Robotics for malls, airports, hotels, and offices.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'CC1 is a multi-mode cleaning robot built for large commercial environments. Deploy across shopping malls, airports, hotels, and offices in the UAE — fully supported by your local Pudu team.',
|
'CC1 is a multi-mode cleaning robot built for large commercial environments. Deploy across shopping malls, airports, hotels, and offices in the UAE fully supported by your local Pudu team.',
|
||||||
features: [
|
features: [
|
||||||
'Multi-mode cleaning: sweep, scrub, mop, vacuum',
|
'Multi-mode cleaning: sweep, scrub, mop, vacuum',
|
||||||
'Autonomous mapping and routing',
|
'Autonomous mapping and routing',
|
||||||
@ -384,9 +384,9 @@ export const ROBOTS: Robot[] = [
|
|||||||
category: 'delivery',
|
category: 'delivery',
|
||||||
categories: ['delivery', 'commercial'],
|
categories: ['delivery', 'commercial'],
|
||||||
shortDescription:
|
shortDescription:
|
||||||
'Heavy-payload autonomous delivery robots — available exclusively in the UAE through YS Lootah Robotics for warehouses, factories, and back-of-house operations.',
|
'Heavy-payload autonomous delivery robots available exclusively in the UAE through YS Lootah Robotics for warehouses, factories, and back-of-house operations.',
|
||||||
longDescription:
|
longDescription:
|
||||||
'The PUDU D-Series powers industrial delivery flows — moving goods, parts, and trays across kitchens, hospitals, and warehouses with autonomous navigation and fleet coordination.',
|
'The PUDU D-Series powers industrial delivery flows moving goods, parts, and trays across kitchens, hospitals, and warehouses with autonomous navigation and fleet coordination.',
|
||||||
features: [
|
features: [
|
||||||
'High payload capacity',
|
'High payload capacity',
|
||||||
'Multi-floor and elevator integration',
|
'Multi-floor and elevator integration',
|
||||||
|
|||||||
@ -117,7 +117,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"attire": {
|
"attire": {
|
||||||
"title": "أزياء قابلة للتخصيص",
|
"title": "أزياء قابلة للتخصيص",
|
||||||
"description": "ألبس روبوت G1 لأي مناسبة — من الكندورة الإماراتية التقليدية إلى بدلات الأعمال وملابس السلامة الصناعية."
|
"description": "ألبس روبوت G1 لأي مناسبة من الكندورة الإماراتية التقليدية إلى بدلات الأعمال وملابس السلامة الصناعية."
|
||||||
},
|
},
|
||||||
"mobility": {
|
"mobility": {
|
||||||
"title": "حركة متقدمة",
|
"title": "حركة متقدمة",
|
||||||
|
|||||||
@ -117,7 +117,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"attire": {
|
"attire": {
|
||||||
"title": "Customizable Attire",
|
"title": "Customizable Attire",
|
||||||
"description": "Dress your G1 for any occasion — from traditional Emarati Kandura to professional business attire and industrial safety gear."
|
"description": "Dress your G1 for any occasion from traditional Emarati Kandura to professional business attire and industrial safety gear."
|
||||||
},
|
},
|
||||||
"mobility": {
|
"mobility": {
|
||||||
"title": "Advanced Mobility",
|
"title": "Advanced Mobility",
|
||||||
|
|||||||
@ -124,7 +124,7 @@ export const personaStore = createStore<PersonaStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
hydrate: () => {
|
hydrate: () => {
|
||||||
// Guard: only hydrate once — prevents race condition duplicates when
|
// Guard: only hydrate once prevents race condition duplicates when
|
||||||
// called from multiple components at the same time.
|
// called from multiple components at the same time.
|
||||||
if (get().isHydrated) return;
|
if (get().isHydrated) return;
|
||||||
set({ isHydrated: true });
|
set({ isHydrated: true });
|
||||||
@ -195,7 +195,7 @@ export const personaStore = createStore<PersonaStore>((set, get) => ({
|
|||||||
saveToStorage(get().personas);
|
saveToStorage(get().personas);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {}); // silent — use local data as fallback
|
.catch(() => {}); // silent use local data as fallback
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -151,7 +151,7 @@ export const pricingStore = createStore<PricingStore>((set, get) => ({
|
|||||||
set({ items: merged });
|
set({ items: merged });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {}); // silent — use local data as fallback
|
.catch(() => {}); // silent use local data as fallback
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user