forked from hazem/yslootahrobotics
feat: initialize Lootah Robotics web application with 3D configurator and scroll-driven UI components
This commit is contained in:
parent
6c5f1f7f15
commit
9394b58b0e
49
package-lock.json
generated
49
package-lock.json
generated
@ -30,6 +30,7 @@
|
|||||||
"jose": "^6.2.2",
|
"jose": "^6.2.2",
|
||||||
"next": "16.2.2",
|
"next": "16.2.2",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
|
"react-country-phone-input": "^1.0.2",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"stripe": "^22.0.1",
|
"stripe": "^22.0.1",
|
||||||
@ -3935,6 +3936,12 @@
|
|||||||
"consola": "^3.2.3"
|
"consola": "^3.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||||
@ -5830,6 +5837,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.memoize": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.reduce": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.startswith": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/log-update": {
|
"node_modules/log-update": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||||
@ -6851,6 +6882,24 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-country-phone-input": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-country-phone-input/-/react-country-phone-input-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-VkCkO0O2W1fvR6xMQv2K/2rayuyr6WWGnoRX2RUFZdYWTDqh67gkEGWy1O2V1n6nJVWaqfUUPWR9o9RrQFlgaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.memoize": "^4.1.2",
|
||||||
|
"lodash.reduce": "^4.6.0",
|
||||||
|
"lodash.startswith": "^4.2.1",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
||||||
|
"react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.0.0",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
"jose": "^6.2.2",
|
"jose": "^6.2.2",
|
||||||
"next": "16.2.2",
|
"next": "16.2.2",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
|
"react-country-phone-input": "^1.0.2",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"stripe": "^22.0.1",
|
"stripe": "^22.0.1",
|
||||||
|
|||||||
BIN
prisma/lootah.db
BIN
prisma/lootah.db
Binary file not shown.
@ -56,3 +56,12 @@ model PricingItem {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ContactRequest {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
email String
|
||||||
|
phone String?
|
||||||
|
message String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ interface Order {
|
|||||||
metadata: Record<string, string>;
|
metadata: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tab = 'pricing' | 'personas' | 'orders' | 'settings';
|
type Tab = 'pricing' | 'personas' | 'orders' | 'contacts' | 'settings';
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -369,6 +369,29 @@ export default function AdminPage() {
|
|||||||
if (activeTab === 'orders') loadOrders();
|
if (activeTab === 'orders') loadOrders();
|
||||||
}, [activeTab, loadOrders]);
|
}, [activeTab, loadOrders]);
|
||||||
|
|
||||||
|
// --------------- CONTACTS ---------------
|
||||||
|
const [contacts, setContacts] = useState<{ id: string; name: string; email: string; phone: string | null; message: string; createdAt: string }[]>([]);
|
||||||
|
const [contactsLoading, setContactsLoading] = useState(false);
|
||||||
|
const [contactsError, setContactsError] = useState('');
|
||||||
|
|
||||||
|
const loadContacts = useCallback(async () => {
|
||||||
|
setContactsLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/contacts/');
|
||||||
|
if (!res.ok) throw new Error('Failed to load contacts');
|
||||||
|
const data = await res.json();
|
||||||
|
setContacts(data.contacts || []);
|
||||||
|
} catch {
|
||||||
|
setContactsError('Failed to load contacts');
|
||||||
|
} finally {
|
||||||
|
setContactsLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'contacts') loadContacts();
|
||||||
|
}, [activeTab, loadContacts]);
|
||||||
|
|
||||||
// --------------- SETTINGS ---------------
|
// --------------- SETTINGS ---------------
|
||||||
const [settings, setSettings] = useState<{ key: string; value: string }[]>([]);
|
const [settings, setSettings] = useState<{ key: string; value: string }[]>([]);
|
||||||
const [settingsLoading, setSettingsLoading] = useState(false);
|
const [settingsLoading, setSettingsLoading] = useState(false);
|
||||||
@ -495,7 +518,7 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
{/* TABS */}
|
{/* TABS */}
|
||||||
<div style={{ display: 'flex', gap: '0.25rem', marginBottom: '1.25rem', borderBottom: '1px solid rgba(0,0,0,0.06)', paddingBottom: '0.5rem' }}>
|
<div style={{ display: 'flex', gap: '0.25rem', marginBottom: '1.25rem', borderBottom: '1px solid rgba(0,0,0,0.06)', paddingBottom: '0.5rem' }}>
|
||||||
{(['pricing', 'personas', 'orders', 'settings'] as Tab[]).map((t) => (
|
{(['pricing', 'personas', 'orders', 'contacts', 'settings'] as Tab[]).map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
onClick={() => setActiveTab(t)}
|
onClick={() => setActiveTab(t)}
|
||||||
@ -758,6 +781,35 @@ export default function AdminPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ===== CONTACTS TAB ===== */}
|
||||||
|
{activeTab === 'contacts' && (
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem', marginBottom: '0.75rem', alignItems: 'center' }}>
|
||||||
|
<button onClick={loadContacts} disabled={contactsLoading} style={secondaryBtnStyle}>
|
||||||
|
{contactsLoading ? 'Loading…' : 'Refresh'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{contactsError && <p style={errorTextStyle}>{contactsError}</p>}
|
||||||
|
{!contactsLoading && contacts.length === 0 && !contactsError && (
|
||||||
|
<p style={{ color: '#94a3b8', fontSize: '0.85rem', textAlign: 'center', padding: '2rem' }}>No contact inquiries yet.</p>
|
||||||
|
)}
|
||||||
|
{contacts.length > 0 && (
|
||||||
|
<TableCard>
|
||||||
|
<TableHeader cols="1.5fr 1fr 1fr 2.5fr 1fr" labels={['Name', 'Email', 'Phone', 'Message', 'Date']} />
|
||||||
|
{contacts.map((c, i) => (
|
||||||
|
<div key={c.id} style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr 2.5fr 1fr', padding: '1rem 1.25rem', alignItems: 'flex-start', borderBottom: i < contacts.length - 1 ? '1px solid rgba(0,0,0,0.04)' : 'none', gap: '1rem' }}>
|
||||||
|
<div style={{ fontSize: '0.85rem', fontWeight: 500, color: '#1e293b' }}>{c.name}</div>
|
||||||
|
<div style={{ fontSize: '0.8rem', color: '#64748b' }}><a href={`mailto:${c.email}`} style={{ color: '#3b82f6', textDecoration: 'none' }}>{c.email}</a></div>
|
||||||
|
<div style={{ fontSize: '0.8rem', color: '#64748b' }}>{c.phone ? <a href={`tel:${c.phone}`} style={{ color: '#64748b', textDecoration: 'none' }}>{c.phone}</a> : '-'}</div>
|
||||||
|
<div style={{ fontSize: '0.8rem', color: '#475569', whiteSpace: 'pre-wrap', lineHeight: 1.5 }}>{c.message}</div>
|
||||||
|
<div style={{ fontSize: '0.75rem', color: '#94a3b8' }}>{new Date(c.createdAt).toLocaleDateString('en-AE')}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TableCard>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* SETTINGS TAB */}
|
{/* SETTINGS TAB */}
|
||||||
{activeTab === 'settings' && (
|
{activeTab === 'settings' && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
25
src/app/api/admin/contacts/route.ts
Normal file
25
src/app/api/admin/contacts/route.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
import * as jose from 'jose';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const token = cookies().get('admin_token')?.value;
|
||||||
|
if (!token) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = new TextEncoder().encode(process.env.JWT_SECRET || 'fallback-secret');
|
||||||
|
await jose.jwtVerify(token, secret);
|
||||||
|
|
||||||
|
const contacts = await prisma.contactRequest.findMany({
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ contacts });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load contacts:', error);
|
||||||
|
return NextResponse.json({ error: 'Failed to load contacts' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/app/api/contact/route.ts
Normal file
34
src/app/api/contact/route.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { prisma } from '@/lib/prisma'; // Check if this is the correct export path
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const data = await request.json();
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!data.name || !data.email || !data.message) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Name, Email, and Message are required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
const contact = await prisma.contactRequest.create({
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
email: data.email,
|
||||||
|
phone: data.phone || null,
|
||||||
|
message: data.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, contact });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to submit contact form:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to submit contact form' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,43 +1,20 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { ConfiguratorSection } from '@/components/ConfiguratorSection';
|
import { ConfiguratorSection } from '@/components/ConfiguratorSection';
|
||||||
|
import { Navbar } from '@/components/Navbar';
|
||||||
|
import { FooterAndContact } from '@/components/FooterAndContact';
|
||||||
|
|
||||||
export default function ConfigurePage() {
|
export default function ConfigurePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Back button */}
|
<Navbar />
|
||||||
<Link
|
|
||||||
href="/"
|
{/* Configurator section takes up full height minus navbar height roughly, or we just let it take its normal height */}
|
||||||
style={{
|
<div style={{ minHeight: '100vh', paddingTop: '80px' }}>
|
||||||
position: 'fixed',
|
<ConfiguratorSection />
|
||||||
top: '1rem',
|
</div>
|
||||||
right: '1rem',
|
|
||||||
zIndex: 100,
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '0.5rem',
|
|
||||||
padding: '0.5rem 1rem',
|
|
||||||
borderRadius: '2rem',
|
|
||||||
background: 'rgba(255, 255, 255, 0.9)',
|
|
||||||
backdropFilter: 'blur(12px)',
|
|
||||||
WebkitBackdropFilter: 'blur(12px)',
|
|
||||||
border: '1px solid rgba(0, 0, 0, 0.08)',
|
|
||||||
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.06)',
|
|
||||||
color: '#374151',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
fontWeight: 500,
|
|
||||||
textDecoration: 'none',
|
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<line x1="19" y1="12" x2="5" y2="12" /><polyline points="12 19 5 12 12 5" />
|
|
||||||
</svg>
|
|
||||||
Back
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<ConfiguratorSection />
|
<FooterAndContact />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -480,6 +480,27 @@ html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide overlay CTA on desktop since it takes over the screen */
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.overlay-cta-section {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific class to ensure configurator shows only on desktop */
|
||||||
|
.desktop-configurator {
|
||||||
|
display: block !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.desktop-configurator {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Small phones */
|
/* Small phones */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.overlay-section-left,
|
.overlay-section-left,
|
||||||
|
|||||||
@ -28,7 +28,18 @@ export default function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body suppressHydrationWarning>
|
<body suppressHydrationWarning>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<I18nProvider>{children}</I18nProvider>
|
<I18nProvider>
|
||||||
|
{children}
|
||||||
|
{/* WhatsApp Floating Button */}
|
||||||
|
<a href="https://wa.me/971559482728" target="_blank" rel="noopener noreferrer" aria-label="Contact us on WhatsApp" className="fixed bottom-6 z-50 flex items-center justify-center h-14 w-14 rounded-full bg-gradient-to-r from-[#25D366] to-[#128C7E] text-white shadow-lg shadow-[#25D366]/30 transition-all duration-300 ease-out hover:scale-110 hover:shadow-[#25D366]/50 hover:shadow-xl active:scale-95 group" style={{ insetInlineEnd: '1.5rem' }}>
|
||||||
|
<span className="absolute inset-0 -z-10 rounded-full bg-gradient-to-r from-[#25D366] to-[#128C7E] opacity-0 blur-xl transition-opacity duration-300 group-hover:opacity-100"></span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="tabler-icon tabler-icon-brand-whatsapp relative z-10 h-8 w-8">
|
||||||
|
<path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9"></path>
|
||||||
|
<path d="M9 10a.5 .5 0 0 0 1 0v-1a.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a.5 .5 0 0 0 0 -1h-1a.5 .5 0 0 0 0 1"></path>
|
||||||
|
</svg>
|
||||||
|
<span aria-hidden="true" className="absolute inset-0 rounded-full border border-[#25D366] bg-[#25D366]/50 animate-[ping_2.2s_cubic-bezier(0,0,0.2,1)_infinite]"></span>
|
||||||
|
</a>
|
||||||
|
</I18nProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -4,12 +4,16 @@ import { useRef } from "react";
|
|||||||
import { ClientOnly } from "@/components/ClientOnly";
|
import { ClientOnly } from "@/components/ClientOnly";
|
||||||
import { ScrollScene } from "@/components/ScrollScene";
|
import { ScrollScene } from "@/components/ScrollScene";
|
||||||
import { ScrollOverlays } from "@/components/ScrollOverlays";
|
import { ScrollOverlays } from "@/components/ScrollOverlays";
|
||||||
|
import { FooterAndContact } from "@/components/FooterAndContact";
|
||||||
|
import { Navbar } from "@/components/Navbar";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
{/* Fixed 3D scene behind everything */}
|
{/* Fixed 3D scene behind everything */}
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<ScrollScene scrollContainerRef={scrollContainerRef} />
|
<ScrollScene scrollContainerRef={scrollContainerRef} />
|
||||||
@ -28,6 +32,10 @@ export default function HomePage() {
|
|||||||
<div className="snap-section" />
|
<div className="snap-section" />
|
||||||
<div className="snap-section" />
|
<div className="snap-section" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="snap-section" style={{ scrollSnapAlign: 'start' }}>
|
||||||
|
<FooterAndContact />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/app/privacy-policy/page.tsx
Normal file
51
src/app/privacy-policy/page.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navbar } from '@/components/Navbar';
|
||||||
|
import { FooterAndContact } from '@/components/FooterAndContact';
|
||||||
|
|
||||||
|
export default function PrivacyPolicyPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<div style={{ background: '#050508', minHeight: '100vh', color: '#ffffff', fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<main style={{ maxWidth: '800px', margin: '0 auto', padding: '12rem 1.5rem 6rem', lineHeight: 1.8 }}>
|
||||||
|
<h1 style={{ fontSize: '3rem', fontWeight: 200, marginBottom: '1rem', letterSpacing: '-0.03em' }}>Privacy <span style={{ color: 'var(--color-gold)', fontWeight: 500 }}>Policy</span></h1>
|
||||||
|
<p style={{ color: '#94a3b8', fontSize: '1rem', marginBottom: '4rem' }}>Effective Date: {new Date().toLocaleDateString('en-AE')}</p>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>1. Information We Collect</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
At YS Lootah Robotics, we collect information you provide directly to us when you request information, use the G1 Customizer, or contact us. This includes your name, email address, phone number, and any other information you choose to provide in your message.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>2. How We Use Your Information</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
We use the information we collect to respond to your inquiries, deliver our robotics enterprise solutions, maintain our dashboard, and communicate with you about your custom humanoid configurations.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>3. Data Security</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
We implement robust security measures designed to protect your personal information. Your contact data is stored securely in our private databases strictly for administrative and operational purposes.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>4. Contact Us</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
If you have questions or concerns about this Privacy Policy, please reach out to us at:
|
||||||
|
<br/><br/>
|
||||||
|
<strong>YS Lootah Robotics</strong><br/>
|
||||||
|
Dubai Design District (D3)<br/>
|
||||||
|
Dubai, United Arab Emirates<br/>
|
||||||
|
Email: info@yslootahtech.com
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<FooterAndContact />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
src/app/sitemap.ts
Normal file
32
src/app/sitemap.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const baseUrl = 'https://lootahrobotics.com'; // Adjust to your actual production domain
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: `${baseUrl}`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly',
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/configure`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly',
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/privacy-policy`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'yearly',
|
||||||
|
priority: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/terms-of-service`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'yearly',
|
||||||
|
priority: 0.5,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
47
src/app/terms-of-service/page.tsx
Normal file
47
src/app/terms-of-service/page.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navbar } from '@/components/Navbar';
|
||||||
|
import { FooterAndContact } from '@/components/FooterAndContact';
|
||||||
|
|
||||||
|
export default function TermsOfServicePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<div style={{ background: '#050508', minHeight: '100vh', color: '#ffffff', fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<main style={{ maxWidth: '800px', margin: '0 auto', padding: '12rem 1.5rem 6rem', lineHeight: 1.8 }}>
|
||||||
|
<h1 style={{ fontSize: '3rem', fontWeight: 200, marginBottom: '1rem', letterSpacing: '-0.03em' }}>Terms of <span style={{ color: 'var(--color-gold)', fontWeight: 500 }}>Service</span></h1>
|
||||||
|
<p style={{ color: '#94a3b8', fontSize: '1rem', marginBottom: '4rem' }}>Effective Date: {new Date().toLocaleDateString('en-AE')}</p>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>1. Acceptance of Terms</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
By accessing and utilizing the YS Lootah Robotics web platform and the G1 Configurator, you accept and agree to be bound by the terms and provisions of this agreement.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>2. Use of the Site & Configurator</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
The 3D G1 Configurator is provided for informational and demonstrative purposes to showcase the capabilities of YS Lootah technologies. You agree to use this site strictly for lawful purposes resulting in enterprise robotics inquiries and configurations.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>3. Intellectual Property Rights</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
All original content on this website, including but not limited to text, graphics, 3D models (GLB files), logos, and software, is the exclusive property of YS Lootah Robotics and is protected by United Arab Emirates and international copyright laws.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ marginBottom: '3rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', fontWeight: 500, color: '#e2e8f0', marginBottom: '1rem' }}>4. Disclaimer of Warranties</h2>
|
||||||
|
<p style={{ color: '#cbd5e1', marginBottom: '1rem' }}>
|
||||||
|
The materials on our platform are provided "as is". We make no warranties, expressed or implied, and hereby disclaim to the fullest extent permitted by law all warranties regarding the immediate enterprise availability of the rendered concepts displayed in the Configurator.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<FooterAndContact />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
378
src/components/FooterAndContact.tsx
Normal file
378
src/components/FooterAndContact.tsx
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import PhoneInput from 'react-country-phone-input';
|
||||||
|
import 'react-country-phone-input/lib/style.css';
|
||||||
|
|
||||||
|
export function FooterAndContact() {
|
||||||
|
const [formData, setFormData] = useState({ name: '', email: '', phone: '', message: '' });
|
||||||
|
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setStatus('loading');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/contact', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
setStatus('success');
|
||||||
|
setFormData({ name: '', email: '', phone: '', message: '' });
|
||||||
|
setTimeout(() => setStatus('idle'), 4000);
|
||||||
|
} else {
|
||||||
|
setStatus('error');
|
||||||
|
setTimeout(() => setStatus('idle'), 4000);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setStatus('error');
|
||||||
|
setTimeout(() => setStatus('idle'), 4000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative', zIndex: 10, background: '#0a0a0f', color: '#ffffff', fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
|
||||||
|
{/* Premium Desktop CTA section */}
|
||||||
|
<div style={{
|
||||||
|
padding: 'clamp(4rem, 8vw, 8rem) clamp(1.5rem, 5vw, 3rem)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
background: 'linear-gradient(180deg, #11111a 0%, #0a0a0f 100%)',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{/* Subtle background glow */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 'clamp(300px, 80vw, 600px)',
|
||||||
|
height: 'clamp(200px, 50vw, 400px)',
|
||||||
|
background: 'radial-gradient(circle, rgba(196,162,101,0.08) 0%, rgba(0,0,0,0) 70%)',
|
||||||
|
pointerEvents: 'none'
|
||||||
|
}} />
|
||||||
|
|
||||||
|
<h2 style={{
|
||||||
|
fontSize: 'clamp(2rem, 6vw, 3.5rem)',
|
||||||
|
fontWeight: 200,
|
||||||
|
color: '#ffffff',
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
letterSpacing: '-0.04em',
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
Ready to Build Your <span style={{ color: 'var(--color-gold)', fontWeight: 500 }}>G1</span>?
|
||||||
|
</h2>
|
||||||
|
<p style={{
|
||||||
|
fontSize: 'clamp(1rem, 2.5vw, 1.15rem)',
|
||||||
|
color: '#94a3b8',
|
||||||
|
maxWidth: '600px',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 1.7,
|
||||||
|
marginBottom: '3.5rem',
|
||||||
|
fontWeight: 300,
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
Customize every detail. From intelligent locomotion to identity and purpose. Your enterprise-grade humanoid is just a few clicks away.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/configure/"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '1rem',
|
||||||
|
padding: 'clamp(0.8rem, 2vw, 1.25rem) clamp(1.5rem, 5vw, 3.5rem)',
|
||||||
|
borderRadius: '4rem',
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'var(--color-gold)',
|
||||||
|
border: '1px solid var(--color-gold)',
|
||||||
|
fontSize: 'clamp(0.85rem, 2vw, 1.1rem)',
|
||||||
|
fontWeight: 500,
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
textDecoration: 'none',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
transition: 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.background = 'var(--color-gold)';
|
||||||
|
e.currentTarget.style.color = '#ffffff';
|
||||||
|
e.currentTarget.style.boxShadow = '0 0 30px rgba(196, 162, 101, 0.4)';
|
||||||
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||||
|
}}
|
||||||
|
onMouseOut={(e) => {
|
||||||
|
e.currentTarget.style.background = 'transparent';
|
||||||
|
e.currentTarget.style.color = 'var(--color-gold)';
|
||||||
|
e.currentTarget.style.boxShadow = 'none';
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Configure Your G1
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" />
|
||||||
|
<polyline points="12 5 19 12 12 19" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Section */}
|
||||||
|
<section style={{ padding: 'clamp(4rem, 8vw, 6rem) 1.5rem', maxWidth: '1280px', margin: '0 auto', borderTop: '1px solid rgba(255,255,255,0.05)' }} id="contact">
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'clamp(2rem, 5vw, 4rem)', justifyContent: 'space-between' }}>
|
||||||
|
|
||||||
|
{/* Contact Info */}
|
||||||
|
<div style={{ flex: '1 1 min(400px, 100%)' }}>
|
||||||
|
<div style={{ width: '40px', height: '1px', background: 'var(--color-gold)', marginBottom: '2rem' }} />
|
||||||
|
<h3 style={{ fontSize: '0.8rem', fontWeight: 500, color: 'var(--color-gold)', letterSpacing: '0.3em', textTransform: 'uppercase', marginBottom: '1rem' }}>
|
||||||
|
Connect With Us
|
||||||
|
</h3>
|
||||||
|
<h2 style={{ fontSize: 'clamp(2rem, 5vw, 2.8rem)', fontWeight: 200, color: '#ffffff', lineHeight: 1.15, marginBottom: '2rem', letterSpacing: '-0.03em' }}>
|
||||||
|
Start your<br /><span style={{ fontWeight: 500 }}>Robotics Journey</span>
|
||||||
|
</h2>
|
||||||
|
<p style={{ fontSize: '1.05rem', color: '#94a3b8', lineHeight: 1.7, marginBottom: '4rem', fontWeight: 300 }}>
|
||||||
|
Whether you are looking to integrate the G1 into your enterprise workflows or have questions about custom developments, our team in the UAE is here to help.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style={{ padding: 'clamp(1.5rem, 4vw, 2rem)', borderRadius: '1rem', border: '1px solid rgba(255,255,255,0.05)', background: '#11111a', marginTop: '2rem' }}>
|
||||||
|
<h3 className="text-xl font-semibold text-white mb-6" style={{ letterSpacing: '0.05em' }}>Contact Information</h3>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<a className="group flex items-start gap-4" href="tel:+971 55 948 2728" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#c4a265]/10 text-[#c4a265] transition-all duration-300 group-hover:bg-[#c4a265]/20 group-hover:scale-110">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-phone h-5 w-5" aria-hidden="true"><path d="M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"></path></svg>
|
||||||
|
</div>
|
||||||
|
<div><div className="text-sm text-slate-400">Phone</div><div className="mt-1 font-medium text-white">+971 55 948 2728</div></div>
|
||||||
|
</a>
|
||||||
|
<a className="group flex items-start gap-4" href="tel:+971 4 349 9319" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#c4a265]/10 text-[#c4a265] transition-all duration-300 group-hover:bg-[#c4a265]/20 group-hover:scale-110">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-phone h-5 w-5" aria-hidden="true"><path d="M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"></path></svg>
|
||||||
|
</div>
|
||||||
|
<div><div className="text-sm text-slate-400">Phone</div><div className="mt-1 font-medium text-white">+971 4 349 9319</div></div>
|
||||||
|
</a>
|
||||||
|
<a className="group flex items-start gap-4" href="mailto:info@yslootahtech.com" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#c4a265]/10 text-[#c4a265] transition-all duration-300 group-hover:bg-[#c4a265]/20 group-hover:scale-110">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-mail h-5 w-5" aria-hidden="true"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path><rect x="2" y="4" width="20" height="16" rx="2"></rect></svg>
|
||||||
|
</div>
|
||||||
|
<div><div className="text-sm text-slate-400">Email</div><div className="mt-1 font-medium text-white">info@yslootahtech.com</div></div>
|
||||||
|
</a>
|
||||||
|
<a className="group flex items-start gap-4" href="https://maps.google.com/?q=Dubai Design District (D3), Dubai" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#c4a265]/10 text-[#c4a265] transition-all duration-300 group-hover:bg-[#c4a265]/20 group-hover:scale-110">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-map-pin h-5 w-5" aria-hidden="true"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||||||
|
</div>
|
||||||
|
<div><div className="text-sm text-slate-400">Address</div><div className="mt-1 font-medium text-white">Dubai Design District (D3)<br/>Dubai, UAE</div></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 pt-6 border-t border-[rgba(255,255,255,0.05)]">
|
||||||
|
<div className="text-sm text-slate-400 mb-4" style={{ letterSpacing: '0.05em' }}>Follow Us</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<a href="https://www.instagram.com/yslootahtech" target="_blank" rel="noopener noreferrer" className="group flex h-11 w-11 items-center justify-center rounded-xl border border-[rgba(255,255,255,0.05)] bg-[#11111a] transition-all duration-300 hover:border-[#c4a265]/50 hover:bg-[#c4a265]/10 group-hover:text-[#c4a265]" aria-label="Instagram"><svg className="h-5 w-5 text-slate-400 transition-colors group-hover:text-[#c4a265]" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"></path></svg></a>
|
||||||
|
<a href="https://www.linkedin.com/company/ys-lootah-tech" target="_blank" rel="noopener noreferrer" className="group flex h-11 w-11 items-center justify-center rounded-xl border border-[rgba(255,255,255,0.05)] bg-[#11111a] transition-all duration-300 hover:border-[#c4a265]/50 hover:bg-[#c4a265]/10 group-hover:text-[#c4a265]" aria-label="Linkedin"><svg className="h-5 w-5 text-slate-400 transition-colors group-hover:text-[#c4a265]" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path></svg></a>
|
||||||
|
<a href="https://www.facebook.com/yslootahtech" target="_blank" rel="noopener noreferrer" className="group flex h-11 w-11 items-center justify-center rounded-xl border border-[rgba(255,255,255,0.05)] bg-[#11111a] transition-all duration-300 hover:border-[#c4a265]/50 hover:bg-[#c4a265]/10 group-hover:text-[#c4a265]" aria-label="Facebook"><svg className="h-5 w-5 text-slate-400 transition-colors group-hover:text-[#c4a265]" fill="currentColor" viewBox="0 0 24 24"><path d="M9.101 24v-11.01h-3.427v-3.929h3.427v-2.897c0-3.411 2.083-5.268 5.123-5.268 1.455 0 2.707.108 3.07.157v3.56h-2.107c-1.654 0-1.974.786-1.974 1.938v2.51h3.942l-.513 3.929h-3.429V24H9.101z"></path></svg></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Form */}
|
||||||
|
<div style={{ flex: '1 1 min(450px, 100%)', padding: 'clamp(1.5rem, 5vw, 3.5rem)', borderRadius: '1rem', background: '#11111a', border: '1px solid rgba(255,255,255,0.05)', boxSizing: 'border-box' }}>
|
||||||
|
<form style={{ display: 'flex', flexDirection: 'column', gap: '1.75rem' }} onSubmit={handleSubmit}>
|
||||||
|
{status === 'success' && (
|
||||||
|
<div style={{ background: 'rgba(34, 197, 94, 0.1)', color: '#22c55e', padding: '1rem', borderRadius: '0.5rem', border: '1px solid rgba(34, 197, 94, 0.2)', textAlign: 'center', fontSize: '0.9rem' }}>
|
||||||
|
Thank you! Your message has been sent successfully.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{status === 'error' && (
|
||||||
|
<div style={{ background: 'rgba(239, 68, 68, 0.1)', color: '#ef4444', padding: '1rem', borderRadius: '0.5rem', border: '1px solid rgba(239, 68, 68, 0.2)', textAlign: 'center', fontSize: '0.9rem' }}>
|
||||||
|
An error occurred. Please try again.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
|
<label style={{ fontSize: '0.8rem', fontWeight: 500, color: '#cbd5e1', letterSpacing: '0.05em' }}>Full Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="John Doe"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({...formData, name: e.target.value})}
|
||||||
|
style={{ width: '100%', padding: '1rem 0', border: 'none', borderBottom: '1px solid rgba(255,255,255,0.1)', fontSize: '1rem', background: 'transparent', color: '#ffffff', outline: 'none', transition: 'border-color 0.3s', boxSizing: 'border-box' }}
|
||||||
|
onFocus={(e) => e.target.style.borderBottom = '1px solid var(--color-gold)'}
|
||||||
|
onBlur={(e) => e.target.style.borderBottom = '1px solid rgba(255,255,255,0.1)'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
|
<label style={{ fontSize: '0.8rem', fontWeight: 500, color: '#cbd5e1', letterSpacing: '0.05em' }}>Email Address</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder="john@company.com"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
||||||
|
style={{ width: '100%', padding: '1rem 0', border: 'none', borderBottom: '1px solid rgba(255,255,255,0.1)', fontSize: '1rem', background: 'transparent', color: '#ffffff', outline: 'none', transition: 'border-color 0.3s', boxSizing: 'border-box' }}
|
||||||
|
onFocus={(e) => e.target.style.borderBottom = '1px solid var(--color-gold)'}
|
||||||
|
onBlur={(e) => e.target.style.borderBottom = '1px solid rgba(255,255,255,0.1)'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Number with Country Code */}
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
|
<label style={{ fontSize: '0.8rem', fontWeight: 500, color: '#cbd5e1', letterSpacing: '0.05em' }}>Mobile Number</label>
|
||||||
|
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end' }}>
|
||||||
|
<PhoneInput
|
||||||
|
country={'ae'}
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={(phone) => setFormData({...formData, phone})}
|
||||||
|
containerStyle={{ width: '100%' }}
|
||||||
|
inputStyle={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '1rem 0 1rem 3.5rem',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
fontSize: '1rem',
|
||||||
|
background: 'transparent',
|
||||||
|
color: '#ffffff',
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.3s',
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}}
|
||||||
|
buttonStyle={{
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
padding: '0 0.5rem',
|
||||||
|
}}
|
||||||
|
dropdownStyle={{ background: '#11111a', color: '#fff', border: '1px solid rgba(255,255,255,0.1)' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginTop: '0.5rem' }}>
|
||||||
|
<label style={{ fontSize: '0.8rem', fontWeight: 500, color: '#cbd5e1', letterSpacing: '0.05em' }}>Message</label>
|
||||||
|
<textarea
|
||||||
|
rows={3}
|
||||||
|
required
|
||||||
|
placeholder="How can we help you?"
|
||||||
|
value={formData.message}
|
||||||
|
onChange={(e) => setFormData({...formData, message: e.target.value})}
|
||||||
|
style={{ width: '100%', padding: '1rem 0', border: 'none', borderBottom: '1px solid rgba(255,255,255,0.1)', fontSize: '1rem', background: 'transparent', color: '#ffffff', outline: 'none', transition: 'border-color 0.3s', resize: 'none', boxSizing: 'border-box' }}
|
||||||
|
onFocus={(e) => e.target.style.borderBottom = '1px solid var(--color-gold)'}
|
||||||
|
onBlur={(e) => e.target.style.borderBottom = '1px solid rgba(255,255,255,0.1)'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
disabled={status === 'loading'}
|
||||||
|
style={{
|
||||||
|
padding: '1.25rem 2.5rem',
|
||||||
|
borderRadius: '3rem',
|
||||||
|
background: status === 'loading' ? '#64748b' : '#ffffff',
|
||||||
|
color: status === 'loading' ? '#ffffff' : '#0a0a0f',
|
||||||
|
fontSize: '0.95rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
marginTop: '1.5rem',
|
||||||
|
border: 'none',
|
||||||
|
cursor: status === 'loading' ? 'not-allowed' : 'pointer',
|
||||||
|
transition: 'background-color 0.3s, transform 0.3s'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => { if(status !== 'loading') e.currentTarget.style.background = 'var(--color-gold)' }}
|
||||||
|
onMouseOut={(e) => { if(status !== 'loading') e.currentTarget.style.background = '#ffffff' }}
|
||||||
|
>
|
||||||
|
{status === 'loading' ? 'Sending...' : 'Send Message'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Redesigned Footer (Inspired by yslootahtech.com) */}
|
||||||
|
<footer style={{ background: '#050508', borderTop: '1px solid rgba(255,255,255,0.05)', padding: 'clamp(3rem, 8vw, 6rem) 1.5rem 2rem' }}>
|
||||||
|
<div style={{ maxWidth: '1280px', margin: '0 auto', display: 'flex', flexWrap: 'wrap', gap: 'clamp(2rem, 5vw, 5rem)', justifyContent: 'space-between', paddingBottom: 'clamp(2rem, 5vw, 5rem)' }}>
|
||||||
|
|
||||||
|
{/* Brand Column */}
|
||||||
|
<div style={{ flex: '1 1 min(350px, 100%)' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '2rem' }}>
|
||||||
|
<span style={{ fontSize: '1.2rem', fontWeight: 700, color: 'var(--color-gold)', letterSpacing: '0.25em', textTransform: 'uppercase' }}>
|
||||||
|
YS Lootah
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: '1.2rem', fontWeight: 300, color: '#ffffff', letterSpacing: '0.25em', textTransform: 'uppercase' }}>
|
||||||
|
Robotics
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: '1rem', color: '#94a3b8', lineHeight: 1.8, fontWeight: 300, maxWidth: '90%', marginBottom: '2rem' }}>
|
||||||
|
Innovating today for a smarter tomorrow. We are more than an automation provider; we are your trusted technology partner delivering advanced enterprise humanoid robotics.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||||
|
<a href="https://www.instagram.com/yslootahtech" target="_blank" rel="noopener noreferrer" style={{ color: '#64748b', transition: 'color 0.3s' }} onMouseOver={e=>e.currentTarget.style.color='var(--color-gold)'} onMouseOut={e=>e.currentTarget.style.color='#64748b'}>
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.linkedin.com/company/ys-lootah-tech" target="_blank" rel="noopener noreferrer" style={{ color: '#64748b', transition: 'color 0.3s' }} onMouseOver={e=>e.currentTarget.style.color='var(--color-gold)'} onMouseOut={e=>e.currentTarget.style.color='#64748b'}>
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.facebook.com/yslootahtech" target="_blank" rel="noopener noreferrer" style={{ color: '#64748b', transition: 'color 0.3s' }} onMouseOver={e=>e.currentTarget.style.color='var(--color-gold)'} onMouseOut={e=>e.currentTarget.style.color='#64748b'}>
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M9.101 24v-11.01h-3.427v-3.929h3.427v-2.897c0-3.411 2.083-5.268 5.123-5.268 1.455 0 2.707.108 3.07.157v3.56h-2.107c-1.654 0-1.974.786-1.974 1.938v2.51h3.942l-.513 3.929h-3.429V24H9.101z"></path></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<h4 style={{ fontSize: '0.85rem', fontWeight: 600, color: '#ffffff', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: '2rem' }}>Customization</h4>
|
||||||
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
|
||||||
|
{[
|
||||||
|
{ label: 'Emarati Kandura', href: '/configure/?config=eyJjIjp7InByaW1hcnkiOiIjOTZhMmI2Iiwic2Vjb25kYXJ5IjoiIzFlMjkzYiIsImFjY2VudCI6IiNmNTllMGIifSwicCI6ImVtYXJhdGkta2FuZHVyYSIsInkiOltdfQ%3D%3D' },
|
||||||
|
{ label: 'Industrial Vest', href: '/configure/?config=eyJjIjp7InByaW1hcnkiOiIjOTZhMmI2Iiwic2Vjb25kYXJ5IjoiIzFlMjkzYiIsImFjY2VudCI6IiNmNTllMGIifSwicCI6ImluZHVzdHJpYWwtdmVzdCIsInkiOltdfQ%3D%3D' },
|
||||||
|
{ label: 'Business Suit', href: '/configure/?config=eyJjIjp7InByaW1hcnkiOiIjOTZhMmI2Iiwic2Vjb25kYXJ5IjoiIzFlMjkzYiIsImFjY2VudCI6IiNmNTllMGIifSwicCI6ImJ1c2luZXNzLXN1aXQiLCJ5IjpbXX0%3D' },
|
||||||
|
{ label: 'Robot Doctor', href: '/configure/?config=eyJjIjp7InByaW1hcnkiOiIjOTZhMmI2Iiwic2Vjb25kYXJ5IjoiIzFlMjkzYiIsImFjY2VudCI6IiNmNTllMGIifSwicCI6InJvYm90LWRvY3RvciIsInkiOltdfQ%3D%3D' },
|
||||||
|
{ label: 'Security Guard', href: '/configure/?config=eyJjIjp7InByaW1hcnkiOiIjOTZhMmI2Iiwic2Vjb25kYXJ5IjoiIzFlMjkzYiIsImFjY2VudCI6IiNmNTllMGIifSwicCI6InNlY3VyaXR5LWd1YXJkIiwieSI6W119' }
|
||||||
|
].map((item, i) => (
|
||||||
|
<li key={i}>
|
||||||
|
<Link href={item.href} style={{ color: '#94a3b8', textDecoration: 'none', fontSize: '0.95rem', fontWeight: 300, transition: 'color 0.3s, padding-left 0.3s' }}
|
||||||
|
onMouseOver={e => { e.currentTarget.style.color = 'var(--color-gold)'; e.currentTarget.style.paddingLeft = '5px'; }}
|
||||||
|
onMouseOut={e => { e.currentTarget.style.color = '#94a3b8'; e.currentTarget.style.paddingLeft = '0'; }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<h4 style={{ fontSize: '0.85rem', fontWeight: 600, color: '#ffffff', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: '2rem' }}>Company</h4>
|
||||||
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
|
||||||
|
{[
|
||||||
|
{ label: 'About YS Lootah', href: 'https://yslootahtech.com/#whyus' },
|
||||||
|
{ label: 'Contact Us', href: '#contact' }
|
||||||
|
].map((item, i) => (
|
||||||
|
<li key={i}>
|
||||||
|
<Link href={item.href} style={{ color: '#94a3b8', textDecoration: 'none', fontSize: '0.95rem', fontWeight: 300, transition: 'color 0.3s, padding-left 0.3s' }}
|
||||||
|
onMouseOver={e => { e.currentTarget.style.color = 'var(--color-gold)'; e.currentTarget.style.paddingLeft = '5px'; }}
|
||||||
|
onMouseOut={e => { e.currentTarget.style.color = '#94a3b8'; e.currentTarget.style.paddingLeft = '0'; }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Copyright */}
|
||||||
|
<div style={{ maxWidth: '1280px', margin: '0 auto', paddingTop: '2.5rem', borderTop: '1px solid rgba(255,255,255,0.05)', display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'center', gap: '1rem' }}>
|
||||||
|
<p style={{ fontSize: '0.85rem', color: '#64748b', margin: 0, fontWeight: 300 }}>
|
||||||
|
© {new Date().getFullYear()} YS Lootah Robotics. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: '2rem' }}>
|
||||||
|
<Link href="/privacy-policy" style={{ fontSize: '0.85rem', color: '#64748b', textDecoration: 'none', transition: 'color 0.3s' }} onMouseOver={e=>e.currentTarget.style.color='#fff'} onMouseOut={e=>e.currentTarget.style.color='#64748b'}>Privacy Policy</Link>
|
||||||
|
<Link href="/terms-of-service" style={{ fontSize: '0.85rem', color: '#64748b', textDecoration: 'none', transition: 'color 0.3s' }} onMouseOver={e=>e.currentTarget.style.color='#fff'} onMouseOut={e=>e.currentTarget.style.color='#64748b'}>Terms of Service</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
216
src/components/Navbar.tsx
Normal file
216
src/components/Navbar.tsx
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function Navbar() {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setScrolled(window.scrollY > 20);
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ label: 'Contact', href: '#contact' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleNavClick = (e: React.MouseEvent<HTMLAnchorElement>, link: any) => {
|
||||||
|
if (link.progress !== undefined) {
|
||||||
|
e.preventDefault();
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
// We have 7 snap-sections of 100vh each. Max scroll inside the scene is 600vh.
|
||||||
|
const targetScroll = link.progress * (6 * window.innerHeight);
|
||||||
|
window.scrollTo({ top: targetScroll, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
background: scrolled ? 'rgba(255, 255, 255, 0.85)' : 'transparent',
|
||||||
|
backdropFilter: scrolled ? 'blur(16px)' : 'none',
|
||||||
|
WebkitBackdropFilter: scrolled ? 'blur(16px)' : 'none',
|
||||||
|
borderBottom: scrolled ? '1px solid rgba(0,0,0,0.05)' : '1px solid transparent',
|
||||||
|
transition: 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
|
padding: scrolled ? '1rem 2rem' : '1.5rem 2rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: '1280px', margin: '0 auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
|
|
||||||
|
{/* Logo */}
|
||||||
|
<Link href="/" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '1.1rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--color-gold)',
|
||||||
|
letterSpacing: '0.15em',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
textShadow: scrolled ? 'none' : '0 2px 10px rgba(0,0,0,0.1)'
|
||||||
|
}}>
|
||||||
|
YS Lootah
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '1.1rem',
|
||||||
|
fontWeight: 300,
|
||||||
|
color: scrolled ? '#1a1a2e' : '#1a1a2e',
|
||||||
|
letterSpacing: '0.15em',
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}}>
|
||||||
|
Robotics
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Links */}
|
||||||
|
<div className="hidden md:flex" style={{ alignItems: 'center', gap: '3rem' }}>
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.label}
|
||||||
|
href={link.href || '#'}
|
||||||
|
onClick={(e) => handleNavClick(e, link)}
|
||||||
|
style={{
|
||||||
|
color: scrolled ? '#64748b' : '#1a1a2e',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
textDecoration: 'none',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
transition: 'color 0.2s',
|
||||||
|
textShadow: scrolled ? 'none' : '0 2px 10px rgba(255,255,255,0.2)'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => e.currentTarget.style.color = 'var(--color-gold)'}
|
||||||
|
onMouseOut={(e) => e.currentTarget.style.color = scrolled ? '#64748b' : '#1a1a2e'}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Button & Hamburger */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||||
|
<Link
|
||||||
|
href="/configure/"
|
||||||
|
className="hidden md:flex"
|
||||||
|
style={{
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '0.75rem 1.5rem',
|
||||||
|
borderRadius: '2rem',
|
||||||
|
background: scrolled ? '#1a1a2e' : 'var(--color-gold)',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
textDecoration: 'none',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
transition: 'all 0.3s',
|
||||||
|
boxShadow: scrolled ? 'none' : '0 4px 14px rgba(196, 162, 101, 0.3)',
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
|
e.currentTarget.style.boxShadow = scrolled ? '0 4px 14px rgba(26, 26, 46, 0.2)' : '0 6px 20px rgba(196, 162, 101, 0.4)';
|
||||||
|
}}
|
||||||
|
onMouseOut={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
|
e.currentTarget.style.boxShadow = scrolled ? 'none' : '0 4px 14px rgba(196, 162, 101, 0.3)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Configure G1
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Mobile Menu Toggle */}
|
||||||
|
<button
|
||||||
|
className="md:hidden"
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: scrolled ? '#1a1a2e' : 'var(--color-gold)',
|
||||||
|
padding: '0.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
{mobileMenuOpen ? (
|
||||||
|
<path d="M18 6L6 18M6 6l12 12" />
|
||||||
|
) : (
|
||||||
|
<path d="M4 12h16M4 6h16M4 18h16" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Mobile Menu Dropdown */}
|
||||||
|
<div
|
||||||
|
className="md:hidden"
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: '#ffffff',
|
||||||
|
zIndex: 90,
|
||||||
|
display: mobileMenuOpen ? 'block' : 'none',
|
||||||
|
paddingTop: '6rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', padding: '0 2rem', gap: '2rem' }}>
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.label}
|
||||||
|
href={link.href || '#'}
|
||||||
|
onClick={(e) => handleNavClick(e, link)}
|
||||||
|
style={{
|
||||||
|
color: '#1a1a2e',
|
||||||
|
fontSize: '1.4rem',
|
||||||
|
fontWeight: 300,
|
||||||
|
textDecoration: 'none',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
borderBottom: '1px solid #f1f5f9',
|
||||||
|
paddingBottom: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link
|
||||||
|
href="/configure/"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '1.25rem',
|
||||||
|
borderRadius: '3rem',
|
||||||
|
background: 'var(--color-gold)',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '1.1rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
textDecoration: 'none',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
marginTop: '1rem',
|
||||||
|
boxShadow: '0 4px 14px rgba(196, 162, 101, 0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Configure Your G1
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -208,21 +208,45 @@ export function ScrollOverlays() {
|
|||||||
From traditional Emarati Kandura to industrial safety gear and professional business attire. Configure every detail to match your brand.
|
From traditional Emarati Kandura to industrial safety gear and professional business attire. Configure every detail to match your brand.
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', gap: '1rem', marginTop: '2rem' }}>
|
<div style={{ display: 'flex', gap: '1rem', marginTop: '2rem' }}>
|
||||||
{['Kandura', 'Vest', 'Suit'].map((label) => (
|
{[
|
||||||
|
{ label: 'Kandura', id: 'emarati-kandura' },
|
||||||
|
{ label: 'Vest', id: 'industrial-vest' },
|
||||||
|
{ label: 'Suit', id: 'business-suit' }
|
||||||
|
].map((item) => (
|
||||||
<div
|
<div
|
||||||
key={label}
|
key={item.id}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
import('@/store/useConfigStore').then(({ useConfigStore }) => {
|
||||||
|
useConfigStore.getState().setActivePersonaAttire(item.id);
|
||||||
|
});
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
padding: '0.5rem 1rem',
|
padding: '0.6rem 1.25rem',
|
||||||
borderRadius: '2rem',
|
borderRadius: '2rem',
|
||||||
background: 'rgba(196, 162, 101, 0.1)',
|
background: 'rgba(255, 255, 255, 0.5)',
|
||||||
border: '1px solid rgba(196, 162, 101, 0.3)',
|
border: '1px solid rgba(196, 162, 101, 0.3)',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
color: '#c4a265',
|
color: '#1a1a2e',
|
||||||
letterSpacing: '0.1em',
|
letterSpacing: '0.1em',
|
||||||
fontWeight: 500
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.background = 'var(--color-gold)';
|
||||||
|
e.currentTarget.style.color = '#ffffff';
|
||||||
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 6px 16px rgba(196, 162, 101, 0.3)';
|
||||||
|
}}
|
||||||
|
onMouseOut={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.5)';
|
||||||
|
e.currentTarget.style.color = '#1a1a2e';
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.05)';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -254,47 +278,7 @@ export function ScrollOverlays() {
|
|||||||
</div>
|
</div>
|
||||||
</OverlaySection>
|
</OverlaySection>
|
||||||
|
|
||||||
{/* Configure CTA — appears at the very end of scroll */}
|
|
||||||
<OverlaySection
|
|
||||||
progress={scrollYProgress}
|
|
||||||
startAt={0.90}
|
|
||||||
peakAt={0.95}
|
|
||||||
endAt={1.0}
|
|
||||||
align="center"
|
|
||||||
verticalAlign="center"
|
|
||||||
className="overlay-cta-section"
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.75rem', pointerEvents: 'auto' }}>
|
|
||||||
<Link
|
|
||||||
href="/configure/"
|
|
||||||
className="overlay-cta-btn"
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '0.75rem',
|
|
||||||
padding: '1.4rem 3.5rem',
|
|
||||||
borderRadius: '4rem',
|
|
||||||
background: 'linear-gradient(135deg, #c4a265, #d4b47a)',
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: '1.2rem',
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '0.12em',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
textDecoration: 'none',
|
|
||||||
boxShadow: '0 12px 40px rgba(196, 162, 101, 0.45)',
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Configure Your G1
|
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<span style={{ fontSize: '0.8rem', color: '#94a3b8', letterSpacing: '0.15em', textTransform: 'uppercase' }}>
|
|
||||||
Build & Order Your Robot
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</OverlaySection>
|
|
||||||
|
|
||||||
{/* Scroll indicator mapped to vanish rapidly when scrolled */}
|
{/* Scroll indicator mapped to vanish rapidly when scrolled */}
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import React, { Suspense, useRef, useEffect } from 'react';
|
import React, { Suspense, useRef, useEffect } from 'react';
|
||||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||||
import { Environment, ContactShadows, useGLTF, Html, useProgress } from '@react-three/drei';
|
import { Environment, ContactShadows, Html, useProgress, useGLTF } from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { RobotModel } from './RobotModel';
|
||||||
|
|
||||||
// Original camera keyframes - cinematic path with dramatic shots
|
// Original camera keyframes - cinematic path with dramatic shots
|
||||||
const CAMERA_KEYFRAMES: [number, [number, number, number]][] = [
|
const CAMERA_KEYFRAMES: [number, [number, number, number]][] = [
|
||||||
@ -225,46 +226,6 @@ function ScrollLighting() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RobotDisplay() {
|
|
||||||
const { scene } = useGLTF('/Unitree_G1.glb');
|
|
||||||
|
|
||||||
const processedScene = React.useMemo(() => {
|
|
||||||
const cloned = scene.clone();
|
|
||||||
|
|
||||||
cloned.traverse((child) => {
|
|
||||||
if (child instanceof THREE.Mesh) {
|
|
||||||
if (!child.material) {
|
|
||||||
child.material = new THREE.MeshStandardMaterial({
|
|
||||||
color: '#96a2b6',
|
|
||||||
metalness: 0.8,
|
|
||||||
roughness: 0.2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (child.material instanceof THREE.MeshStandardMaterial) {
|
|
||||||
child.material.envMapIntensity = 1.8;
|
|
||||||
child.material.needsUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const box = new THREE.Box3().setFromObject(cloned);
|
|
||||||
const center = box.getCenter(new THREE.Vector3());
|
|
||||||
const size = box.getSize(new THREE.Vector3());
|
|
||||||
const maxDim = Math.max(size.x, size.y, size.z);
|
|
||||||
const scale = 2 / maxDim;
|
|
||||||
|
|
||||||
cloned.scale.setScalar(scale);
|
|
||||||
cloned.position.set(
|
|
||||||
-center.x * scale,
|
|
||||||
-center.y * scale + 0.5,
|
|
||||||
-center.z * scale,
|
|
||||||
);
|
|
||||||
|
|
||||||
return cloned;
|
|
||||||
}, [scene]);
|
|
||||||
|
|
||||||
return <primitive object={processedScene} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Loader() {
|
function Loader() {
|
||||||
const { progress } = useProgress();
|
const { progress } = useProgress();
|
||||||
@ -299,7 +260,7 @@ function SceneContent() {
|
|||||||
<ScrollLighting />
|
<ScrollLighting />
|
||||||
<LightOrbs />
|
<LightOrbs />
|
||||||
|
|
||||||
<RobotDisplay />
|
<RobotModel />
|
||||||
<ContactShadows position={[0, -1, 0]} opacity={0.25} scale={10} blur={2} far={4} resolution={256} color="#000000" />
|
<ContactShadows position={[0, -1, 0]} opacity={0.25} scale={10} blur={2} far={4} resolution={256} color="#000000" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user