fix: restore website visual assets and shell
This commit is contained in:
parent
e77e16fa19
commit
7365bf56d1
|
|
@ -1,23 +1,167 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata, Viewport } from 'next';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { SiteHeader } from '../components/site-header';
|
import { SiteHeader } from '../components/site-header';
|
||||||
|
import { SiteFooter } from '../components/site-footer';
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
width: 'device-width',
|
||||||
|
initialScale: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'MotorSignal | The ride intelligence platform',
|
title: 'MotorSignal | The ride intelligence platform',
|
||||||
description: 'Motorcycle telemetry, ride history, and connected rider tools.',
|
description: 'Motorcycle telemetry, ride history, and connected rider tools.',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tailwindConfigScript = `
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
"brand": {
|
||||||
|
"bg": "#080c13",
|
||||||
|
"card": "#0f1420",
|
||||||
|
"cardAlt": "#141c2c",
|
||||||
|
"border": "#1f2a3f",
|
||||||
|
"borderLight": "#2c3b57",
|
||||||
|
"cyan": "#00f2fe",
|
||||||
|
"emerald": "#10b981",
|
||||||
|
"neonYellow": "#f59e0b",
|
||||||
|
"glow": "rgba(0, 242, 254, 0.15)"
|
||||||
|
},
|
||||||
|
"tertiary-fixed-dim": "#7bd0ff",
|
||||||
|
"surface": "#0f131c",
|
||||||
|
"on-secondary-fixed-variant": "#005227",
|
||||||
|
"primary-fixed": "#6ff6ff",
|
||||||
|
"surface-container-low": "#181c24",
|
||||||
|
"on-primary-container": "#006a70",
|
||||||
|
"on-background": "#dfe2ee",
|
||||||
|
"inverse-on-surface": "#2c3039",
|
||||||
|
"secondary-fixed": "#60ff98",
|
||||||
|
"primary": "#e0fdff",
|
||||||
|
"background": "#0f131c",
|
||||||
|
"secondary": "#f5fff3",
|
||||||
|
"primary-container": "#00f2fe",
|
||||||
|
"on-primary-fixed-variant": "#004f53",
|
||||||
|
"on-tertiary-fixed-variant": "#004c69",
|
||||||
|
"error": "#ffb4ab",
|
||||||
|
"tertiary-container": "#b2e1ff",
|
||||||
|
"surface-bright": "#353942",
|
||||||
|
"surface-container-highest": "#31353e",
|
||||||
|
"on-secondary-container": "#007239",
|
||||||
|
"surface-container": "#1c2028",
|
||||||
|
"primary-fixed-dim": "#00dce6",
|
||||||
|
"surface-variant": "#31353e",
|
||||||
|
"surface-container-high": "#262a33",
|
||||||
|
"surface-dim": "#0f131c",
|
||||||
|
"tertiary": "#f1f8ff",
|
||||||
|
"inverse-primary": "#00696f",
|
||||||
|
"on-primary-fixed": "#002022",
|
||||||
|
"tertiary-fixed": "#c4e7ff",
|
||||||
|
"on-error": "#690005",
|
||||||
|
"secondary-container": "#34ff8c",
|
||||||
|
"on-error-container": "#ffdad6",
|
||||||
|
"surface-tint": "#00dce6",
|
||||||
|
"on-tertiary-fixed": "#001e2c",
|
||||||
|
"on-tertiary-container": "#00678c",
|
||||||
|
"on-primary": "#00373a",
|
||||||
|
"on-tertiary": "#00354a",
|
||||||
|
"outline-variant": "#3a494b",
|
||||||
|
"outline": "#849495",
|
||||||
|
"on-secondary-fixed": "#00210c",
|
||||||
|
"inverse-surface": "#dfe2ee",
|
||||||
|
"surface-container-lowest": "#0a0e16",
|
||||||
|
"error-container": "#93000a",
|
||||||
|
"on-surface": "#dfe2ee",
|
||||||
|
"on-secondary": "#003919",
|
||||||
|
"secondary-fixed-dim": "#00e478",
|
||||||
|
"on-surface-variant": "#b9cacb"
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
"DEFAULT": "0.125rem",
|
||||||
|
"lg": "0.25rem",
|
||||||
|
"xl": "0.5rem",
|
||||||
|
"full": "0.75rem"
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
"space-lg": "1.5rem",
|
||||||
|
"gutter-desktop": "2rem",
|
||||||
|
"space-xl": "2rem",
|
||||||
|
"container-max": "1280px",
|
||||||
|
"gutter-tablet": "1.5rem",
|
||||||
|
"space-sm": "0.75rem",
|
||||||
|
"space-4xl": "6rem",
|
||||||
|
"space-xs": "0.5rem",
|
||||||
|
"space-2xs": "0.25rem",
|
||||||
|
"space-3xl": "4rem",
|
||||||
|
"gutter-mobile": "1rem",
|
||||||
|
"space-2xl": "3rem",
|
||||||
|
"space-md": "1rem"
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"telemetry-metric-md": ["Space Grotesk"],
|
||||||
|
"body-lg": ["Plus Jakarta Sans"],
|
||||||
|
"label-md": ["Plus Jakarta Sans"],
|
||||||
|
"code-mono": ["Space Grotesk"],
|
||||||
|
"body-sm": ["Plus Jakarta Sans"],
|
||||||
|
"label-caps": ["Space Grotesk"],
|
||||||
|
"body-md": ["Plus Jakarta Sans"],
|
||||||
|
"headline-xl": ["Space Grotesk"],
|
||||||
|
"headline-lg": ["Space Grotesk"],
|
||||||
|
"headline-sm": ["Space Grotesk"],
|
||||||
|
"display-hero-mobile": ["Space Grotesk"],
|
||||||
|
"headline-md": ["Space Grotesk"],
|
||||||
|
"headline-xl-mobile": ["Space Grotesk"],
|
||||||
|
"telemetry-metric-xl": ["Space Grotesk"],
|
||||||
|
"display-hero": ["Space Grotesk"]
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
"telemetry-metric-md": ["32px", { lineHeight: "36px", letterSpacing: "-0.03em", fontWeight: "700" }],
|
||||||
|
"body-lg": ["18px", { lineHeight: "28px", letterSpacing: "0em", fontWeight: "400" }],
|
||||||
|
"label-md": ["13px", { lineHeight: "18px", letterSpacing: "0.01em", fontWeight: "600" }],
|
||||||
|
"code-mono": ["12px", { lineHeight: "16px", letterSpacing: "0.04em", fontWeight: "500" }],
|
||||||
|
"body-sm": ["13px", { lineHeight: "20px", letterSpacing: "0.005em", fontWeight: "400" }],
|
||||||
|
"label-caps": ["11px", { lineHeight: "14px", letterSpacing: "0.12em", fontWeight: "700" }],
|
||||||
|
"body-md": ["15px", { lineHeight: "24px", letterSpacing: "0em", fontWeight: "400" }],
|
||||||
|
"headline-xl": ["40px", { lineHeight: "48px", letterSpacing: "-0.02em", fontWeight: "700" }],
|
||||||
|
"headline-lg": ["28px", { lineHeight: "36px", letterSpacing: "-0.015em", fontWeight: "600" }],
|
||||||
|
"headline-sm": ["18px", { lineHeight: "26px", letterSpacing: "-0.005em", fontWeight: "600" }],
|
||||||
|
"display-hero-mobile": ["36px", { lineHeight: "44px", letterSpacing: "-0.02em", fontWeight: "700" }],
|
||||||
|
"headline-md": ["22px", { lineHeight: "30px", letterSpacing: "-0.01em", fontWeight: "600" }],
|
||||||
|
"headline-xl-mobile": ["28px", { lineHeight: "36px", letterSpacing: "-0.01em", fontWeight: "700" }],
|
||||||
|
"telemetry-metric-xl": ["48px", { lineHeight: "48px", letterSpacing: "-0.04em", fontWeight: "700" }],
|
||||||
|
"display-hero": ["56px", { lineHeight: "64px", letterSpacing: "-0.03em", fontWeight: "700" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className="dark">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Plus+Jakarta+Sans:ital,wght@0,300..800;1,300..800&family=Space+Grotesk:wght@300..700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: tailwindConfigScript }} />
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
<footer className="site-footer">
|
<SiteFooter />
|
||||||
<span>MotorSignal</span>
|
|
||||||
<span>Ride smarter. Know more. <a href="/privacy">Privacy</a> · <a href="/terms">Terms</a></span>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
'use client';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export function SiteFooter() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
if (pathname?.includes('/manage') || pathname?.startsWith('/admin') || pathname?.startsWith('/ops')) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="flagship-footer">
|
||||||
|
<div className="flagship-container">
|
||||||
|
<div className="footer-columns-grid">
|
||||||
|
{/* Brand Column */}
|
||||||
|
<div className="footer-brand-col">
|
||||||
|
<Link className="brand" href="/" aria-label="MotorSignal home">
|
||||||
|
<span className="brand-mark">
|
||||||
|
<span className="material-symbols-outlined" style={{ fontSize: '18px', fontWeight: 'bold' }}>
|
||||||
|
show_chart
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ fontFamily: 'Space Grotesk', fontWeight: 700, letterSpacing: '0.04em' }}>MOTORSIGNAL</span>
|
||||||
|
</Link>
|
||||||
|
<p className="footer-desc">
|
||||||
|
High-performance precision telemetry and tactical ride intelligence for modern racers, track engineers,
|
||||||
|
and two-wheeled enthusiasts worldwide.
|
||||||
|
</p>
|
||||||
|
<div className="footer-status-pill">
|
||||||
|
<span className="status-dot" />
|
||||||
|
<span className="font-label-caps" style={{ color: '#b9cacb' }}>
|
||||||
|
Grid Network 99.98% Available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Product Links */}
|
||||||
|
<div className="footer-link-group">
|
||||||
|
<h4>PRODUCT</h4>
|
||||||
|
<ul>
|
||||||
|
<li><Link href="/features">Live Cockpit</Link></li>
|
||||||
|
<li><Link href="/features">Telemetry Trace</Link></li>
|
||||||
|
<li><Link href="/features">Lean Angle HUD</Link></li>
|
||||||
|
<li><Link href="/pricing">Pricing Tiers</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hardware Links */}
|
||||||
|
<div className="footer-link-group">
|
||||||
|
<h4>HARDWARE</h4>
|
||||||
|
<ul>
|
||||||
|
<li><Link href="/pricing">OBD-II CAN Link</Link></li>
|
||||||
|
<li><Link href="/features">10Hz GPS Module</Link></li>
|
||||||
|
<li><Link href="/features">6-Axis IMU Unit</Link></li>
|
||||||
|
<li><Link href="/pricing">Mount Specs</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Ecosystem Links */}
|
||||||
|
<div className="footer-link-group">
|
||||||
|
<h4>ECOSYSTEM</h4>
|
||||||
|
<ul>
|
||||||
|
<li><Link href="/group-rides">Group Rides</Link></li>
|
||||||
|
<li><Link href="/download">Mobile App</Link></li>
|
||||||
|
<li><Link href="/features">Telemetry API</Link></li>
|
||||||
|
<li><Link href="/features">Webhooks</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compliance Links */}
|
||||||
|
<div className="footer-link-group">
|
||||||
|
<h4>COMPLIANCE</h4>
|
||||||
|
<ul>
|
||||||
|
<li><Link href="/privacy">Privacy Policy</Link></li>
|
||||||
|
<li><Link href="/terms">Terms of Service</Link></li>
|
||||||
|
<li><Link href="/privacy">Security & Trust</Link></li>
|
||||||
|
<li><Link href="/features">FIM Data Standard</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Baseline Bar */}
|
||||||
|
<div className="footer-bottom-bar">
|
||||||
|
<p>© 2025 MotorSignal Technologies Inc. All machine rights reserved.</p>
|
||||||
|
<div className="footer-sys-info font-code-mono">
|
||||||
|
<span>LATENCY: 18ms</span>
|
||||||
|
<span>APEX-CORE: v3.4.1</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
interface SiteHeaderProps {
|
||||||
|
signedIn: boolean;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SiteHeaderClient({ signedIn, role }: SiteHeaderProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
if (pathname?.includes('/manage') || pathname?.startsWith('/admin') || pathname?.startsWith('/ops')) return null;
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ label: 'Live Cockpit', href: '/' },
|
||||||
|
{ label: 'Features', href: '/features' },
|
||||||
|
{ label: 'Group Rides', href: '/group-rides' },
|
||||||
|
{ label: 'Pricing', href: '/pricing' },
|
||||||
|
{ label: 'Hardware & App', href: '/pricing#hardware' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="fixed top-0 w-full z-50 bg-surface-container-lowest/80 backdrop-blur-xl border-b border-outline-variant/30 shadow-[0_1px_8px_rgba(0,0,0,0.04)]">
|
||||||
|
<div className="h-20 max-w-container-max mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between gap-space-sm sm:gap-space-md">
|
||||||
|
{/* Left: Brand + Navigation */}
|
||||||
|
<div className="flex items-center gap-space-sm sm:gap-space-lg min-w-0">
|
||||||
|
<Link className="flex items-center gap-2 sm:gap-space-sm group shrink-0" href="/" aria-label="MotorSignal home">
|
||||||
|
<div className="w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-gradient-to-tr from-primary-container via-surface-tint to-tertiary-container flex items-center justify-center shadow-[0_0_16px_rgba(0,242,254,0.35)] shrink-0">
|
||||||
|
<span className="material-symbols-outlined text-on-primary-container text-[20px] sm:text-[22px] font-bold">show_chart</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col min-w-0">
|
||||||
|
<span className="font-headline-sm text-sm sm:text-headline-sm text-on-surface tracking-tight uppercase group-hover:text-primary-container transition-colors truncate">MotorSignal</span>
|
||||||
|
<span className="font-label-caps text-[10px] sm:text-label-caps text-primary-fixed-dim uppercase tracking-widest hidden xs:block">PRO TELEMETRY</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<nav className="hidden xl:flex items-center gap-space-lg whitespace-nowrap" aria-label="Primary navigation">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const isActive =
|
||||||
|
item.href === '/' ? pathname === '/' : pathname.startsWith(item.href);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
href={item.href}
|
||||||
|
className={`transition-colors whitespace-nowrap ${
|
||||||
|
isActive
|
||||||
|
? 'text-primary-container font-headline-sm border-b-2 border-primary-container pb-0.5'
|
||||||
|
: 'font-label-md text-label-md text-on-surface-variant hover:text-on-surface'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Telemetry Grid pill + Auth actions + Mobile Hamburger */}
|
||||||
|
<div className="flex items-center gap-1.5 sm:gap-space-md whitespace-nowrap shrink-0">
|
||||||
|
{/* Telemetry Grid: Online Badge */}
|
||||||
|
<div className="hidden md:flex items-center gap-space-xs px-space-sm py-1 rounded-full bg-surface-container-low border border-outline-variant/40">
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-secondary-fixed opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-secondary-fixed-dim"></span>
|
||||||
|
</span>
|
||||||
|
<span className="font-label-caps text-label-caps text-on-surface-variant uppercase">
|
||||||
|
Telemetry Grid: Online
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{signedIn ? (
|
||||||
|
<div className="flex items-center gap-space-xs">
|
||||||
|
<Link
|
||||||
|
className="w-9 h-9 sm:w-11 sm:h-11 rounded-full bg-surface-container-high border border-outline-variant/40 flex items-center justify-center hover:ring-2 hover:ring-primary-container transition-all shrink-0"
|
||||||
|
href="/account"
|
||||||
|
title="Rider Account"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-primary-container text-[20px] sm:text-[22px]">
|
||||||
|
person
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 sm:gap-space-sm">
|
||||||
|
<Link
|
||||||
|
className="hidden sm:inline-flex items-center justify-center px-space-md py-2.5 rounded-xl bg-surface-container-low hover:bg-surface-container-high border border-outline-variant/40 text-on-surface hover:text-primary transition-all font-label-md text-label-md min-h-[44px]"
|
||||||
|
href="/login"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className="inline-flex items-center justify-center px-2.5 sm:px-space-lg py-2 sm:py-2.5 rounded-xl bg-gradient-to-r from-primary-container to-primary-fixed text-on-primary-container font-headline-sm text-xs sm:text-headline-sm hover:shadow-[0_0_24px_rgba(0,242,254,0.45)] transition-all whitespace-nowrap min-h-[38px] sm:min-h-[44px]"
|
||||||
|
href="/register"
|
||||||
|
>
|
||||||
|
<span className="hidden sm:inline">Get Started Free</span>
|
||||||
|
<span className="sm:hidden">Get Started</span>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className="sm:hidden w-9 h-9 rounded-full bg-primary flex items-center justify-center hover:ring-2 hover:ring-primary-container transition-all shrink-0"
|
||||||
|
href="/login"
|
||||||
|
title="Account"
|
||||||
|
aria-label="Sign in"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-on-primary text-[18px]">
|
||||||
|
person
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Mobile Menu Hamburger Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="xl:hidden w-9 h-9 sm:w-11 sm:h-11 rounded-xl bg-surface-container-low hover:bg-surface-container-high border border-outline-variant/40 flex items-center justify-center text-on-surface hover:text-primary transition-colors cursor-pointer shrink-0"
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
aria-label={mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}
|
||||||
|
aria-expanded={mobileMenuOpen}
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[20px] sm:text-[24px]">
|
||||||
|
{mobileMenuOpen ? 'close' : 'menu'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Drawer */}
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<div className="xl:hidden bg-surface-container-lowest/95 backdrop-blur-2xl border-b border-outline-variant/40 px-4 sm:px-gutter-desktop py-space-md shadow-2xl flex flex-col gap-space-sm animate-in slide-in-from-top-2">
|
||||||
|
<nav className="flex flex-col gap-1" aria-label="Mobile navigation">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const isActive =
|
||||||
|
item.href === '/' ? pathname === '/' : pathname.startsWith(item.href);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
href={item.href}
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className={`px-space-md py-3 rounded-xl font-label-md text-label-md transition-colors min-h-[44px] flex items-center ${
|
||||||
|
isActive
|
||||||
|
? 'bg-surface-container-high text-primary-container font-semibold border-l-4 border-primary-container'
|
||||||
|
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
<div className="pt-space-sm border-t border-outline-variant/30 flex items-center justify-between text-xs">
|
||||||
|
<div className="flex items-center gap-space-xs">
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-secondary-fixed opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-secondary-fixed-dim"></span>
|
||||||
|
</span>
|
||||||
|
<span className="font-label-caps text-label-caps text-on-surface-variant uppercase">
|
||||||
|
Telemetry Grid: Online
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{!signedIn && (
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="text-primary font-label-md text-label-md hover:underline py-1.5 px-2"
|
||||||
|
>
|
||||||
|
Sign In →
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
import Link from 'next/link';
|
import { cookies } from 'next/headers';
|
||||||
|
import { SiteHeaderClient } from './site-header-client';
|
||||||
|
|
||||||
export function SiteHeader() {
|
export async function SiteHeader() {
|
||||||
return (
|
const token = (await cookies()).get('motorsignal_access_token')?.value;
|
||||||
<header className="site-header">
|
let signedIn = false;
|
||||||
<Link className="brand" href="/" aria-label="MotorSignal home">
|
let role = 'user';
|
||||||
<span className="brand-mark">M</span>
|
if (token) {
|
||||||
<span>MotorSignal</span>
|
try {
|
||||||
</Link>
|
const response = await fetch(`${process.env.MOTORSIGNAL_API_BASE_URL ?? 'https://api.motorsignal.app'}/api/v1/me`, {
|
||||||
<nav aria-label="Primary navigation">
|
headers: { authorization: `Bearer ${token}` }, cache: 'no-store',
|
||||||
<Link href="/features">Features</Link>
|
});
|
||||||
<Link href="/pricing">Pricing</Link>
|
signedIn = response.ok;
|
||||||
<Link href="/download">Download</Link>
|
if (response.ok) role = (await response.json() as { role?: string }).role ?? 'user';
|
||||||
<Link href="/support">Support</Link>
|
} catch { /* Navigation remains available during an API outage. */ }
|
||||||
</nav>
|
}
|
||||||
<div className="header-actions">
|
|
||||||
<Link className="quiet-link" href="/login">Sign in</Link>
|
return <SiteHeaderClient signedIn={signedIn} role={role} />;
|
||||||
<Link className="button button-small" href="/register">Create account</Link>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Loading…
Reference in New Issue