feat: enhance landing page with new hero section, badges, and improved styles

This commit is contained in:
estifanos
2026-08-14 07:43:14 +00:00
parent de04ed64b3
commit cfbec948fb
3 changed files with 129 additions and 31 deletions

View File

@@ -251,51 +251,132 @@ function Header({
); );
} }
const HERO_BADGE_ICONS = {
secure: IconShieldCheck,
bilingual: IconLanguage,
tracking: IconActivity,
} as const;
function Hero({ primaryHref, isAuthed }: { primaryHref: string; isAuthed: boolean }) { function Hero({ primaryHref, isAuthed }: { primaryHref: string; isAuthed: boolean }) {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useMantineTheme(); const theme = useMantineTheme();
const badgeKeys = Object.keys(HERO_BADGE_ICONS) as (keyof typeof HERO_BADGE_ICONS)[];
return ( return (
<Box <Box
id="home" id="home"
component="section" component="section"
pos="relative"
style={{ style={{
background: theme.other?.heroGradient as string, background: theme.other?.heroGradient as string,
color: 'white', color: 'white',
overflow: 'hidden',
}} }}
> >
<Container size="xl" py={80}> {/* Decorative glow circles — same device as AuthShell's hero panel. */}
<Stack gap="md" align="center" ta="center" className="ema-landing-fade"> <Box
pos="absolute"
top={-120}
right={-100}
w={420}
h={420}
style={{ borderRadius: '50%', background: 'rgba(255,255,255,0.08)' }}
/>
<Box
pos="absolute"
bottom={-160}
left={-120}
w={380}
h={380}
style={{ borderRadius: '50%', background: 'rgba(255,255,255,0.06)' }}
/>
<Box
pos="absolute"
top="30%"
left="8%"
w={140}
h={140}
visibleFrom="md"
style={{ borderRadius: '50%', background: 'rgba(255,255,255,0.05)' }}
/>
<Container size="xl" py={{ base: 88, sm: 120, lg: 148 }} pos="relative">
<Stack gap="lg" align="center" ta="center" className="ema-landing-fade">
<Text fz="sm" fw={600} style={{ opacity: 0.85, letterSpacing: 0.5 }}> <Text fz="sm" fw={600} style={{ opacity: 0.85, letterSpacing: 0.5 }}>
{t('landing.hero.eyebrow')} {t('landing.hero.eyebrow')}
</Text> </Text>
<Title order={1} fz={{ base: 32, sm: 44 }} maw={780}> <Title order={1} fz={{ base: 34, sm: 48, lg: 56 }} lh={1.1} maw={860}>
{t('landing.hero.title')} {t('landing.hero.title')}
</Title> </Title>
<Text fz={{ base: 'md', sm: 'lg' }} maw={640} style={{ opacity: 0.92 }}> <Text fz={{ base: 'md', sm: 'xl' }} fw={500} maw={680} style={{ opacity: 0.95 }}>
{t('landing.hero.subtitle')} {t('landing.hero.subtitle')}
</Text> </Text>
<Group gap="md" mt="md" justify="center"> <Text fz={{ base: 'sm', sm: 'md' }} maw={600} style={{ opacity: 0.82 }}>
<Button {t('landing.hero.description')}
component={Link} </Text>
to={isAuthed ? primaryHref : '/signup'}
size="md" <Group gap="md" mt="sm" justify="center">
variant="white" {isAuthed ? (
color="dark" <Button
leftSection={<IconShip size={18} />} component={Link}
> to={primaryHref}
{t('landing.cta.seafarerPortal')} size="lg"
</Button> variant="white"
<Button color="dark"
component={Link} style={{ boxShadow: '0 8px 24px rgba(0,0,0,0.18)' }}
to={isAuthed ? primaryHref : '/signup'} >
size="md" {t('landing.auth.dashboard')}
variant="outline" </Button>
color="white" ) : (
leftSection={<IconAnchor size={18} />} <>
> <Button
{t('landing.cta.vesselOwnerPortal')} component={Link}
</Button> to="/signup"
size="lg"
variant="white"
color="dark"
leftSection={<IconShip size={20} />}
style={{ boxShadow: '0 8px 24px rgba(0,0,0,0.18)' }}
>
{t('landing.cta.iAmSeafarer')}
</Button>
<Button
component={Link}
to="/signup"
size="lg"
variant="outline"
color="white"
leftSection={<IconAnchor size={20} />}
>
{t('landing.cta.iAmVesselOwner')}
</Button>
</>
)}
</Group>
<Group gap="xs" mt="xl" justify="center" wrap="wrap">
{badgeKeys.map((key) => {
const Icon = HERO_BADGE_ICONS[key];
return (
<Group
key={key}
gap={6}
wrap="nowrap"
px="sm"
py={6}
style={{
borderRadius: 999,
background: 'rgba(255,255,255,0.12)',
border: '1px solid rgba(255,255,255,0.18)',
}}
>
<Icon size={14} />
<Text fz="xs" fw={600}>
{t(`landing.hero.badges.${key}`)}
</Text>
</Group>
);
})}
</Group> </Group>
</Stack> </Stack>
</Container> </Container>

View File

@@ -31,6 +31,13 @@ export const landingEn = {
eyebrow: 'Federal Democratic Republic of Ethiopia', eyebrow: 'Federal Democratic Republic of Ethiopia',
title: 'Ethiopian Maritime Authority', title: 'Ethiopian Maritime Authority',
subtitle: 'Digital Maritime Services for Seafarers, Vessel Owners and Logistics Operators', subtitle: 'Digital Maritime Services for Seafarers, Vessel Owners and Logistics Operators',
description:
'Apply, upload documents and track every application from one secure account — built for seafarers, vessel owners and logistics operators across Ethiopia.',
badges: {
secure: 'Secure & Verified',
bilingual: 'English & አማርኛ',
tracking: 'Real-Time Tracking',
},
}, },
about: { about: {
@@ -44,8 +51,8 @@ export const landingEn = {
}, },
cta: { cta: {
seafarerPortal: 'Seafarer Portal', iAmSeafarer: "I'm a Seafarer",
vesselOwnerPortal: 'Vessel Owner Portal', iAmVesselOwner: "I'm a Vessel Owner",
}, },
quickAccess: { quickAccess: {
@@ -212,6 +219,13 @@ export const landingAm: LandingCopy = {
eyebrow: 'የኢትዮጵያ ፌዴራላዊ ዲሞክራሲያዊ ሪፐብሊክ', eyebrow: 'የኢትዮጵያ ፌዴራላዊ ዲሞክራሲያዊ ሪፐብሊክ',
title: 'የኢትዮጵያ ማሪታይም ባለስልጣን', title: 'የኢትዮጵያ ማሪታይም ባለስልጣን',
subtitle: 'ለመርከበኞች፣ ለመርከብ ባለቤቶች እና ለሎጂስቲክስ ኦፕሬተሮች የተዘጋጁ ዲጂታል የባህር አገልግሎቶች', subtitle: 'ለመርከበኞች፣ ለመርከብ ባለቤቶች እና ለሎጂስቲክስ ኦፕሬተሮች የተዘጋጁ ዲጂታል የባህር አገልግሎቶች',
description:
'ከመለያዎ ማመልከቻ ያስገቡ፣ ሰነድ ይስቀሉ እንዲሁም ሁኔታ ይከታተሉ — ለመርከበኞች፣ ለመርከብ ባለቤቶችና ለሎጂስቲክስ ኦፕሬተሮች የተዘጋጀ ነው።',
badges: {
secure: 'ደህንነቱ የተጠበቀ',
bilingual: 'አማርኛና እንግሊዝኛ',
tracking: 'የቀጥታ ክትትል',
},
}, },
about: { about: {
@@ -225,8 +239,8 @@ export const landingAm: LandingCopy = {
}, },
cta: { cta: {
seafarerPortal: 'መርከበኞች ፖርታል', iAmSeafarer: 'መርከበኛ ነኝ',
vesselOwnerPortal: 'የመርከብ ባለቤቶች ፖርታል', iAmVesselOwner: 'የመርከብ ባለቤት ነኝ',
}, },
quickAccess: { quickAccess: {

View File

@@ -3,7 +3,10 @@
.ema-hover-lift, portal's --ema-surface-*) is available here, so this file .ema-hover-lift, portal's --ema-surface-*) is available here, so this file
is self-contained. */ is self-contained. */
.ema-landing { /* `scroll-behavior` only affects the element that actually scrolls — for a
full page that's `html`, not this div, so it has to live here. Scoped with
:has() so it only applies while the landing page is mounted. */
html:has(.ema-landing) {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
@@ -45,7 +48,7 @@
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.ema-landing { html:has(.ema-landing) {
scroll-behavior: auto; scroll-behavior: auto;
} }
.ema-landing .ema-landing-fade, .ema-landing .ema-landing-fade,