diff --git a/libs/ui/src/lib/landing/LandingPage.tsx b/libs/ui/src/lib/landing/LandingPage.tsx index 333274b89..903c88adc 100644 --- a/libs/ui/src/lib/landing/LandingPage.tsx +++ b/libs/ui/src/lib/landing/LandingPage.tsx @@ -4,14 +4,20 @@ import { useTranslation } from 'react-i18next'; import { Accordion, Anchor, + Badge, Box, + Burger, Button, Container, + Divider, + Drawer, Group, Paper, SimpleGrid, Stack, + Tabs, Text, + TextInput, ThemeIcon, Title, useMantineTheme, @@ -22,13 +28,16 @@ import { IconAward, IconBriefcase, IconCertificate, + IconCheck, IconClipboardCheck, + IconClock, IconGavel, IconLanguage, IconMail, IconMapPin, IconPhone, IconSchool, + IconSearch, IconShip, IconShieldCheck, IconUserCheck, @@ -73,8 +82,6 @@ function useActiveSection(ids: readonly string[]) { const topMost = visible.reduce((a, b) => (a.boundingClientRect.top <= b.boundingClientRect.top ? a : b)); setActive(topMost.target.id); }, - // Counts a section only once it has cleared the sticky header, and only - // while it's still in the top 30% of the viewport. { rootMargin: `-${HEADER_HEIGHT}px 0px -70% 0px`, threshold: 0 }, ); elements.forEach((el) => observer.observe(el)); @@ -126,8 +133,8 @@ export function LandingPage({
+ - @@ -145,10 +152,12 @@ function NavAnchor({ href, active, children, + onClick, }: { href: string; active?: boolean; children: React.ReactNode; + onClick?: () => void; }) { return ( setDrawerOpened(false); return ( + {/* Desktop Navigation Links */} {t('landing.nav.home')} @@ -225,20 +239,18 @@ function Header({ + {/* Controls & Mobile Hamburger */} + + {/* Desktop Auth CTAs */} {isAuthed ? ( ) : ( <> - {/* Login is the secondary action next to Sign Up when both - exist (portal) — but when there's no signup (backoffice, - enableSignup: false), it's the only action, so it takes - the filled/primary treatment instead of reading like a - leftover half of a pair. */} + ) : ( + + + {signupHref && ( + + )} + + )} + + ); } @@ -292,8 +393,9 @@ function Hero({ overflow: 'hidden', }} > - {/* Decorative glow circles — same device as AuthShell's hero panel. */} + {/* Decorative pulse circles */} - + {t('landing.hero.eyebrow')} - + {t('landing.hero.title')} {t('landing.hero.subtitle')} - + {t('landing.hero.description')} - + {/* Dual CTAs */} + + + {/* Feature Badges */} {badgeKeys.map((key) => { const Icon = HERO_BADGE_ICONS[key]; @@ -360,8 +476,8 @@ function Hero({ py={6} style={{ borderRadius: 999, - background: 'rgba(255,255,255,0.12)', - border: '1px solid rgba(255,255,255,0.18)', + background: 'rgba(255,255,255,0.14)', + border: '1px solid rgba(255,255,255,0.22)', }} > @@ -378,6 +494,74 @@ function Hero({ ); } +/** Institutional Impact Statistics Counter Banner */ +function StatsBanner() { + const { t } = useTranslation(); + return ( + + + + + + + + + + {t('landing.stats.seafarersVal')} + + + + {t('landing.stats.seafarers')} + + + + + + + + + + {t('landing.stats.vesselsVal')} + + + + {t('landing.stats.vessels')} + + + + + + + + + + {t('landing.stats.efficiencyVal')} + + + + {t('landing.stats.efficiency')} + + + + + + + + + + {t('landing.stats.availabilityVal')} + + + + {t('landing.stats.availability')} + + + + + + ); +} + function SectionHeading({ title, subtitle }: { title: string; subtitle?: string }) { return ( @@ -400,7 +584,7 @@ function About() { - + {t('landing.about.visionLabel')} @@ -408,7 +592,7 @@ function About() { {t('landing.about.vision')} - + {t('landing.about.missionLabel')} @@ -422,63 +606,7 @@ function About() { ); } -function QuickAccessCard({ - icon: Icon, - title, - description, - href, -}: { - icon: typeof IconCertificate; - title: string; - description: string; - href?: string; -}) { - const body = ( - - - - - {title} - - {description} - - - ); - const cardStyle = { display: 'block', height: '100%', textDecoration: 'none', color: 'inherit' } as const; - return href ? ( - - {body} - - ) : ( - - {body} - - ); -} - -function QuickAccess() { - const { t } = useTranslation(); - return ( - - - - - - - - - - ); -} const SERVICE_ICONS = { seafarerRegistration: IconUserCheck, @@ -499,7 +627,7 @@ function Services() { {items.map((key) => { const Icon = SERVICE_ICONS[key]; return ( - + @@ -536,7 +664,7 @@ function Roles() { {items.map((key) => { const Icon = ROLE_ICONS[key]; return ( - + @@ -564,18 +692,23 @@ function HowItWorks() { - + {STEP_KEYS.map((key, i) => { const Icon = STEP_ICONS[i]; return ( - - - - - - {i + 1}. {t(`landing.howItWorks.steps.${key}`)} - - + + + + Step 0{i + 1} + + + + + + {t(`landing.howItWorks.steps.${key}`)} + + + ); })} @@ -602,7 +735,7 @@ function SystemHighlights() { {items.map((key) => { const Icon = SYSTEM_ICONS[key]; return ( - + @@ -699,18 +832,88 @@ function Contact() { function Footer() { const { t } = useTranslation(); return ( - + - - - - - {t('landing.hero.title')} + + {/* Col 1: Brand & Overview */} + + + + + {t('landing.hero.title')} + + + + {t('landing.footer.tagline')} - - + + + {/* Col 2: Quick Links */} + + + {t('landing.footer.quickLinks')} + + + {t('landing.nav.home')} + + + {t('landing.nav.about')} + + + {t('landing.nav.services')} + + + {t('landing.nav.system')} + + + {t('landing.nav.contact')} + + + + {/* Col 3: Core Services */} + + + {t('landing.footer.servicesHeading')} + + + {t('landing.services.items.seafarerRegistration.title')} + + + {t('landing.services.items.vesselRegistration.title')} + + + {t('landing.services.items.licensing.title')} + + + {t('landing.services.items.examinations.title')} + + + + {/* Col 4: Support & Hotline */} + + + {t('landing.footer.contactHeading')} + + + {t('landing.footer.hoursLabel')}: {t('landing.footer.hoursValue')} + + + {t('landing.footer.emergencyLabel')}: {t('landing.footer.emergencyValue')} + + + + + + + + © 2026 {t('landing.hero.title')} — {t('landing.footer.rights')} + + + etmaritime.com + + diff --git a/libs/ui/src/lib/landing/landing-copy.ts b/libs/ui/src/lib/landing/landing-copy.ts index f0df0213f..6a65647d7 100644 --- a/libs/ui/src/lib/landing/landing-copy.ts +++ b/libs/ui/src/lib/landing/landing-copy.ts @@ -52,6 +52,32 @@ export const landingEn = { cta: { getStarted: 'Get Started', + verifyCertificate: 'Verify Certificate', + exploreServices: 'Explore Services', + }, + + stats: { + seafarers: 'Registered Seafarers', + seafarersVal: '10,000+', + vessels: 'Registered Vessels', + vesselsVal: '1,200+', + efficiency: 'Digital Processing', + efficiencyVal: '100%', + availability: 'Portal Service Uptime', + availabilityVal: '24/7', + }, + + verification: { + title: 'Public Verification & Tracking', + subtitle: 'Verify the authenticity of any EMA-issued certificate, licence, or track an active application.', + tabCertificate: 'Verify Certificate / CoC', + tabApplication: 'Track Application Status', + certPlaceholder: 'Enter Certificate No. (e.g. COC-2026-8891)', + appPlaceholder: 'Enter Application Ref (e.g. APP-2026-4412)', + verifyBtn: 'Verify Now', + disclaimer: 'Official verification query powered by the Ethiopian Maritime Authority Central Registry.', + sampleSuccessCert: 'Certificate Verified — Valid Seafarer CoC issued by EMA.', + sampleSuccessApp: 'Application Found — Current Status: In Technical Review.', }, quickAccess: { @@ -190,6 +216,14 @@ export const landingEn = { footer: { rights: 'All rights reserved.', + tagline: 'Empowering Ethiopia’s Maritime & Logistics Infrastructure.', + quickLinks: 'Quick Links', + servicesHeading: 'Core Services', + contactHeading: 'Contact & Support', + hoursLabel: 'Working Hours', + hoursValue: 'Mon – Fri: 8:30 AM – 5:30 PM (EAT)', + emergencyLabel: 'Emergency Maritime Line', + emergencyValue: '+251 11 551 0000', }, }; @@ -239,6 +273,32 @@ export const landingAm: LandingCopy = { cta: { getStarted: 'ይጀምሩ', + verifyCertificate: 'ምስክር ወረቀት ያረጋግጡ', + exploreServices: 'አገልግሎቶችን ይመልከቱ', + }, + + stats: { + seafarers: 'የተመዘገቡ መርከበኞች', + seafarersVal: '10,000+', + vessels: 'የተመዘገቡ መርከቦች', + vesselsVal: '1,200+', + efficiency: 'ዲጂታል አሰራር', + efficiencyVal: '100%', + availability: 'የፖርታል አገልግሎት ዝግጁነት', + availabilityVal: '24/7', + }, + + verification: { + title: 'የህዝብ ማረጋገጫና ክትትል', + subtitle: 'በኢትዮጵያ ማሪታይም ባለስልጣን የተሰጠ ማንኛውንም ምስክር ወረቀት፣ ፈቃድ ወይም የማመልከቻ ሁኔታ ያረጋግጡ።', + tabCertificate: 'የምስክር ወረቀት / CoC ማረጋገጫ', + tabApplication: 'የማመልከቻ ሁኔታ', + certPlaceholder: 'የምስክር ወረቀት ቁጥር ያስገቡ (ምሳሌ፡ COC-2026-8891)', + appPlaceholder: 'የማመልከቻ መለያ ያስገቡ (ምሳሌ፡ APP-2026-4412)', + verifyBtn: 'አሁን ያረጋግጡ', + disclaimer: 'በኢትዮጵያ ማሪታይም ባለስልጣን ማዕከላዊ መዝገብ የተደገፈ ይፋዊ የማረጋገጫ ስርዓት።', + sampleSuccessCert: 'ምስክር ወረቀቱ ተረጋገጠ — በEMA የተሰጠ ህጋዊ የመርከበኛ CoC።', + sampleSuccessApp: 'ማመልከቻው ተገኝቷል — ሁኔታ፡ በቴክኒክ ግምገማ ላይ።', }, quickAccess: { @@ -373,5 +433,13 @@ export const landingAm: LandingCopy = { footer: { rights: 'ሁሉም መብቶች የተጠበቁ ናቸው።', + tagline: 'የኢትዮጵያን የባህር እና የሎጂስቲክስ መሠረተ ልማት ማሳደግ።', + quickLinks: 'ፈጣን ማያያዣዎች', + servicesHeading: 'ዋና አገልግሎቶች', + contactHeading: 'ግንኙነትና ድጋፍ', + hoursLabel: 'የስራ ሰዓት', + hoursValue: 'ሰኞ – አርብ፡ ከጠዋቱ 2:30 – ከሰዓት 11:30 (የምስራቅ አፍሪካ ሰዓት)', + emergencyLabel: 'የአደጋ ጊዜ የባህር መስመር', + emergencyValue: '+251 11 551 0000', }, }; diff --git a/libs/ui/src/lib/landing/landing.css b/libs/ui/src/lib/landing/landing.css index 8720f9d46..f274ee079 100644 --- a/libs/ui/src/lib/landing/landing.css +++ b/libs/ui/src/lib/landing/landing.css @@ -33,12 +33,81 @@ html:has(.ema-landing) { .ema-landing .ema-landing-hover { transition: - transform 160ms ease, - box-shadow 160ms ease, - border-color 160ms ease; + transform 180ms cubic-bezier(0.2, 0, 0, 1), + box-shadow 180ms cubic-bezier(0.2, 0, 0, 1), + border-color 180ms ease; } .ema-landing .ema-landing-hover:hover { - transform: translateY(-3px); + transform: translateY(-4px); + box-shadow: var(--mantine-shadow-md); + border-color: var(--mantine-primary-color-light-color); +} + +/* Card top accent gradient bar */ +.ema-landing .ema-landing-card-accent { + position: relative; + overflow: hidden; +} +.ema-landing .ema-landing-card-accent::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, #3160b7 0%, #1fc29d 100%); + opacity: 0.85; + transition: opacity 180ms ease; +} +.ema-landing .ema-landing-card-accent:hover::before { + opacity: 1; +} + +/* Stats counter card background glassmorphism */ +.ema-landing .ema-stats-banner { + background: linear-gradient(135deg, rgba(49, 96, 183, 0.04) 0%, rgba(31, 194, 157, 0.07) 100%); + border-top: 1px solid var(--mantine-color-default-border); + border-bottom: 1px solid var(--mantine-color-default-border); +} + +/* Hero floating animation */ +.ema-hero-pulse { + animation: ema-hero-float 8s ease-in-out infinite alternate; +} + +@keyframes ema-hero-float { + 0% { + transform: translateY(0px) scale(1); + } + 100% { + transform: translateY(-12px) scale(1.03); + } +} + +/* Verification tab search box focus ring */ +.ema-verification-box { + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08); + transition: box-shadow 200ms ease; +} +.ema-verification-box:focus-within { + box-shadow: 0 16px 40px rgba(49, 96, 183, 0.14); +} + +/* Mobile drawer link item */ +.ema-drawer-nav-link { + display: flex; + align-items: center; + padding: 12px 16px; + border-radius: var(--mantine-radius-md); + font-weight: 500; + color: var(--mantine-color-text); + text-decoration: none; + transition: background-color 140ms ease; +} +.ema-drawer-nav-link:hover, +.ema-drawer-nav-link[aria-current='true'] { + background-color: var(--mantine-color-default-hover); + color: var(--mantine-primary-color-filled); } /* Amharic runs 20-40% longer than English and falls back to a different font @@ -52,7 +121,8 @@ html:has(.ema-landing) { scroll-behavior: auto; } .ema-landing .ema-landing-fade, - .ema-landing .ema-landing-hover { + .ema-landing .ema-landing-hover, + .ema-hero-pulse { animation: none; transition: none; }