diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts
index 92400f9ae..da2e6234b 100644
--- a/apps/backoffice/src/app/i18n/locales/am.ts
+++ b/apps/backoffice/src/app/i18n/locales/am.ts
@@ -1,6 +1,9 @@
+import { landingAm } from "@ema-platform/ui";
import type { Translations } from "./en";
export const am: Translations = {
+ landing: landingAm,
+
app: {
name: "ኢማ አስተዳደር",
shortName: "ኢማ",
diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts
index 338a88e68..9b51214c2 100644
--- a/apps/backoffice/src/app/i18n/locales/en.ts
+++ b/apps/backoffice/src/app/i18n/locales/en.ts
@@ -1,4 +1,8 @@
+import { landingEn } from '@ema-platform/ui';
+
export const en = {
+ landing: landingEn,
+
app: {
name: 'EMA Admin',
shortName: 'EMA',
diff --git a/apps/backoffice/src/app/router/LandingRoute.tsx b/apps/backoffice/src/app/router/LandingRoute.tsx
new file mode 100644
index 000000000..776bf01f8
--- /dev/null
+++ b/apps/backoffice/src/app/router/LandingRoute.tsx
@@ -0,0 +1,13 @@
+import Cookies from 'js-cookie';
+import { LandingPage } from '@ema-platform/ui';
+import { authStorage } from '@ema-platform/auth';
+
+/**
+ * Public `/` — mounts the shared landing page. Backoffice has no /signup
+ * (enableSignup: false) and no /verify route, so those props are omitted.
+ */
+export function LandingRoute() {
+ const token = authStorage.getToken() ?? Cookies.get('auth-token');
+
+ return ;
+}
diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx
index a5a2c5c5b..e04280c63 100644
--- a/apps/backoffice/src/app/router/index.tsx
+++ b/apps/backoffice/src/app/router/index.tsx
@@ -15,6 +15,7 @@ import {
import { AuthLayout } from '../layouts/AuthLayout';
import { BackofficeLayout } from '../layouts/BackofficeLayout';
import { ProtectedRoute } from './ProtectedRoute';
+import { LandingRoute } from './LandingRoute';
import { DashboardPage } from '../features/dashboard/pages/DashboardPage';
import UserManagementPage from '../features/user-management/UserManagementPage';
import { ProfilePage } from '../features/profile/pages/ProfilePage';
@@ -61,7 +62,7 @@ const router = createBrowserRouter([
],
},
{ path: '/um/*', element: },
- { path: '/', element: },
+ { path: '/', element: },
{ path: '/profile-setup', element: },
{
element: ,
@@ -69,7 +70,6 @@ const router = createBrowserRouter([
{
element: ,
children: [
- { index: true, element: },
{ path: 'dashboard', element: },
{ path: 'vessel-registration-head-dashboard', element: guard([P.VIEW_VESSEL_REGISTRY], ) },
{ path: 'logistics-head-dashboard', element: guard(APPLICATION_QUEUE, ) },
diff --git a/apps/backoffice/src/main.tsx b/apps/backoffice/src/main.tsx
index 5c3165aa8..5d991e0df 100644
--- a/apps/backoffice/src/main.tsx
+++ b/apps/backoffice/src/main.tsx
@@ -39,7 +39,7 @@ window.__USER_MANAGEMENT_BRANDING__ = {
organizationName: 'Ethiopian Maritime Authority',
logoSrc: '/assets/emaLogo.jpg',
logoAlt: 'EMA Logo',
- homePath: '/',
+ homePath: '/dashboard',
moduleBasePath: '/user-management',
backToAppPath: '/dashboard',
backToAppLabel: 'Back to dashboard',
diff --git a/apps/portal/src/app/components/LandingRoute.tsx b/apps/portal/src/app/components/LandingRoute.tsx
new file mode 100644
index 000000000..16f7050c4
--- /dev/null
+++ b/apps/portal/src/app/components/LandingRoute.tsx
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie';
+import { LandingPage } from '@ema-platform/ui';
+import { authStorage } from '@ema-platform/auth';
+
+/**
+ * Public `/` — mounts the shared landing page with portal-specific routes.
+ * Auth state is read the same way ProtectedRoute does (token cookie or
+ * storage fallback) so the header can show "Go to dashboard" instead of
+ * Login/Sign Up without gating the route itself.
+ */
+export function LandingRoute() {
+ const token = authStorage.getToken() ?? Cookies.get('auth-token');
+
+ return ;
+}
diff --git a/apps/portal/src/app/i18n/locales/am.ts b/apps/portal/src/app/i18n/locales/am.ts
index 5de71b937..4a0bea10f 100644
--- a/apps/portal/src/app/i18n/locales/am.ts
+++ b/apps/portal/src/app/i18n/locales/am.ts
@@ -1,6 +1,9 @@
+import { landingAm } from '@ema-platform/ui';
import type { Translations } from './en';
export const am: Translations = {
+ landing: landingAm,
+
app: {
name: 'ኢባባ ፖርታል',
authority: 'የኢትዮጵያ ባሕር ጉዳዮች ባለሥልጣን',
diff --git a/apps/portal/src/app/i18n/locales/en.ts b/apps/portal/src/app/i18n/locales/en.ts
index f05f83645..175d3918e 100644
--- a/apps/portal/src/app/i18n/locales/en.ts
+++ b/apps/portal/src/app/i18n/locales/en.ts
@@ -1,4 +1,8 @@
+import { landingEn } from '@ema-platform/ui';
+
export const en = {
+ landing: landingEn,
+
app: {
name: 'EMA Portal',
authority: 'Ethiopian Maritime Authority',
diff --git a/apps/portal/src/app/providers/AppProviders.tsx b/apps/portal/src/app/providers/AppProviders.tsx
index f21fab6b8..886fd7138 100644
--- a/apps/portal/src/app/providers/AppProviders.tsx
+++ b/apps/portal/src/app/providers/AppProviders.tsx
@@ -1,8 +1,10 @@
+import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthBootstrap, AuthConfigProvider } from '@ema-platform/auth';
import type { ReactNode } from 'react';
import { store } from '../store';
+import { i18n } from '../i18n/config';
import { MantineThemeProvider } from './MantineThemeProvider';
import { ErrorBoundary } from '../components/ErrorBoundary';
@@ -26,9 +28,11 @@ export function AppProviders({ children }: { children: ReactNode }) {
enableForgotPassword: true,
}}
>
-
- {children}
-
+
+
+ {children}
+
+
diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx
index e6ec53024..d58a12a16 100644
--- a/apps/portal/src/app/router.tsx
+++ b/apps/portal/src/app/router.tsx
@@ -1,8 +1,7 @@
import { createBrowserRouter, Navigate } from "react-router-dom";
-import { I18nextProvider } from "react-i18next";
-import { i18n } from "./i18n/config";
import { PortalLayout } from "./layouts/PortalLayout";
import { ProtectedRoute } from "./components/ProtectedRoute";
+import { LandingRoute } from "./components/LandingRoute";
// Auth (standalone pages, no portal chrome)
import {
@@ -58,6 +57,9 @@ import { WaiverPage } from "./features/waiver/pages/WaiverPage";
import { VesselRegistrationStatusPage } from "./features/vessel-registration/pages/VesselRegistrationStatusPage";
export const router = createBrowserRouter([
+ // Public landing page — institutional overview + role-based entry points.
+ { path: "/", element: },
+
// Public auth pages
{ path: "/login", element: },
{ path: "/signup", element: },
@@ -100,17 +102,14 @@ export const router = createBrowserRouter([
{
element: (
-
- {/* Asks what the applicant operates as before the rest of the
- portal, which is filtered by that answer. */}
-
-
-
-
+ {/* Asks what the applicant operates as before the rest of the
+ portal, which is filtered by that answer. */}
+
+
+
),
children: [
- { path: "/", element: },
{ path: "/dashboard", element: },
{ path: "/onboarding/operations", element: },
@@ -409,5 +408,7 @@ export const router = createBrowserRouter([
element: ,
},
- { path: "*", element: },
+ // A typo'd URL should not maroon a signed-in user on the marketing page —
+ // ProtectedRoute sends anonymous visitors on to /login exactly as before.
+ { path: "*", element: },
]);
diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts
index e713c8ee3..17f6e386a 100644
--- a/libs/ui/src/index.ts
+++ b/libs/ui/src/index.ts
@@ -21,3 +21,5 @@ export * from "./lib/input/phone";
export * from "./lib/data/AdvancedTable";
export * from "./lib/feedback/use-error-handler";
export * from "./lib/data/useServerTable";
+export * from "./lib/landing/LandingPage";
+export * from "./lib/landing/landing-copy";
diff --git a/libs/ui/src/lib/landing/LandingPage.tsx b/libs/ui/src/lib/landing/LandingPage.tsx
new file mode 100644
index 000000000..f746244c3
--- /dev/null
+++ b/libs/ui/src/lib/landing/LandingPage.tsx
@@ -0,0 +1,589 @@
+import { Link } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import {
+ Accordion,
+ Anchor,
+ Box,
+ Button,
+ Container,
+ Group,
+ Paper,
+ SimpleGrid,
+ Stack,
+ Text,
+ ThemeIcon,
+ Title,
+ useMantineTheme,
+} from '@mantine/core';
+import {
+ IconAnchor,
+ IconAward,
+ IconBriefcase,
+ IconCertificate,
+ IconClipboardCheck,
+ IconFileCertificate,
+ IconGavel,
+ IconMail,
+ IconMapPin,
+ IconPhone,
+ IconSchool,
+ IconShip,
+ IconShieldCheck,
+ IconUserCheck,
+ IconUsers,
+} from '@tabler/icons-react';
+import { LanguageSwitcher } from '../layout/LanguageSwitcher';
+import { ColorSchemeToggle } from '../layout/ColorSchemeToggle';
+import { EmptyState } from '../feedback/EmptyState';
+import './landing.css';
+
+export interface LandingPageProps {
+ /** Primary CTA target. Each app passes '/dashboard' when a session token
+ * exists, else '/login'. Authenticated header state is derived from this
+ * (anything other than '/login' counts as signed in) rather than a
+ * separate prop, since libs/ui cannot import @ema-platform/auth. */
+ primaryHref: string;
+ /** Renders the "Create account" CTA only when set. Portal passes '/signup'; backoffice omits it (enableSignup: false). */
+ signupHref?: string;
+ /** Renders the Certificate Verification nav item + quick-access tile only when set. Portal passes '/verify'; backoffice omits it. */
+ verifyHref?: string;
+ supportedLanguages?: readonly string[];
+}
+
+const HEADER_HEIGHT = 72;
+
+export function LandingPage({
+ primaryHref,
+ signupHref,
+ verifyHref,
+ supportedLanguages = ['en', 'am'],
+}: LandingPageProps) {
+ const { t } = useTranslation();
+ const isAuthed = primaryHref !== '/login';
+
+ return (
+
+ {
+ e.currentTarget.style.top = '8px';
+ }}
+ onBlur={(e) => {
+ e.currentTarget.style.top = '-60px';
+ }}
+ >
+ {t('landing.meta.skipToContent')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function NavAnchor({ href, children }: { href: string; children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function Header({
+ isAuthed,
+ primaryHref,
+ signupHref,
+ verifyHref,
+ supportedLanguages,
+}: {
+ isAuthed: boolean;
+ primaryHref: string;
+ signupHref?: string;
+ verifyHref?: string;
+ supportedLanguages: readonly string[];
+}) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+ {t('app.name')}
+
+
+
+
+
+ {t('landing.nav.home')}
+ {t('landing.nav.about')}
+ {t('landing.nav.services')}
+ {t('landing.nav.notices')}
+ {verifyHref && (
+
+ {t('landing.nav.verify')}
+
+ )}
+ {t('landing.nav.contact')}
+
+
+
+
+
+ {isAuthed ? (
+
+ ) : (
+ <>
+
+ {signupHref && (
+
+ )}
+ >
+ )}
+
+
+
+
+ );
+}
+
+function Hero({ primaryHref, isAuthed }: { primaryHref: string; isAuthed: boolean }) {
+ const { t } = useTranslation();
+ const theme = useMantineTheme();
+
+ return (
+
+
+
+
+ {t('landing.hero.eyebrow')}
+
+
+ {t('landing.hero.title')}
+
+
+ {t('landing.hero.subtitle')}
+
+
+ }
+ >
+ {t('landing.cta.seafarerPortal')}
+
+ }
+ >
+ {t('landing.cta.vesselOwnerPortal')}
+
+
+
+
+
+ );
+}
+
+function SectionHeading({ title, subtitle }: { title: string; subtitle?: string }) {
+ return (
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ );
+}
+
+function About() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+
+ {t('landing.about.visionLabel')}
+
+ {t('landing.about.vision')}
+
+
+
+
+
+ {t('landing.about.missionLabel')}
+
+ {t('landing.about.mission')}
+
+
+
+
+
+ );
+}
+
+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({ verifyHref }: { verifyHref?: string }) {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const SERVICE_ICONS = {
+ seafarerRegistration: IconUserCheck,
+ vesselRegistration: IconShip,
+ licensing: IconBriefcase,
+ examinations: IconSchool,
+ certificateVerification: IconCertificate,
+ waivers: IconShieldCheck,
+} as const;
+
+function Services() {
+ const { t } = useTranslation();
+ const items = Object.keys(SERVICE_ICONS) as (keyof typeof SERVICE_ICONS)[];
+ return (
+
+
+
+
+ {items.map((key) => {
+ const Icon = SERVICE_ICONS[key];
+ return (
+
+
+
+
+
+ {t(`landing.services.items.${key}.title`)}
+
+ {t(`landing.services.items.${key}.description`)}
+
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+const ROLE_ICONS = {
+ seafarers: IconShip,
+ vesselOwners: IconAnchor,
+ agents: IconBriefcase,
+ reviewers: IconGavel,
+} as const;
+
+function Roles() {
+ const { t } = useTranslation();
+ const items = Object.keys(ROLE_ICONS) as (keyof typeof ROLE_ICONS)[];
+ return (
+
+
+
+
+ {items.map((key) => {
+ const Icon = ROLE_ICONS[key];
+ return (
+
+
+
+
+
+ {t(`landing.roles.${key}.title`)}
+
+ {t(`landing.roles.${key}.description`)}
+
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+const STEP_KEYS = ['selectRole', 'createAccount', 'submitApplication', 'trackStatus', 'receiveApproval'] as const;
+const STEP_ICONS = [IconUsers, IconUserCheck, IconClipboardCheck, IconAward, IconCertificate];
+
+function HowItWorks() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+ {STEP_KEYS.map((key, i) => {
+ const Icon = STEP_ICONS[i];
+ return (
+
+
+
+
+
+ {i + 1}. {t(`landing.howItWorks.steps.${key}`)}
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+function Notices() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+const FAQ_KEYS = ['whatIsPortal', 'whoCanUse', 'howToApply', 'howToVerify', 'isFree'] as const;
+
+function Faq() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+ {FAQ_KEYS.map((key) => (
+
+ {t(`landing.faq.items.${key}.question`)}
+ {t(`landing.faq.items.${key}.answer`)}
+
+ ))}
+
+
+
+ );
+}
+
+function Contact() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {t('landing.contact.addressLabel')}
+
+
+ {t('landing.contact.address')}
+
+
+
+
+
+
+
+
+
+ {t('landing.contact.phoneLabel')}
+
+
+ +251 011 515 0299
+
+
+
+
+
+
+
+
+
+ {t('landing.contact.websiteLabel')}
+
+
+ etmaritime.com
+
+
+
+
+
+
+
+ );
+}
+
+function Footer() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+ {t('landing.hero.title')}
+
+
+
+ © 2026 {t('landing.hero.title')} — {t('landing.footer.rights')}
+
+
+
+
+ );
+}
diff --git a/libs/ui/src/lib/landing/landing-copy.ts b/libs/ui/src/lib/landing/landing-copy.ts
new file mode 100644
index 000000000..8b17e1b36
--- /dev/null
+++ b/libs/ui/src/lib/landing/landing-copy.ts
@@ -0,0 +1,360 @@
+/**
+ * Copy for the public landing page (`LandingPage.tsx`), shared by both apps.
+ * Written once here; each app's `en.ts`/`am.ts` re-exports it under the
+ * `landing` key so it flows through that app's own i18next instance.
+ *
+ * `landingAm: LandingCopy` enforces English/Amharic parity right here — a
+ * missing Amharic key is a compile error, same contract as each app's own
+ * `Translations = typeof en`.
+ */
+
+export const landingEn = {
+ meta: {
+ skipToContent: 'Skip to content',
+ },
+
+ nav: {
+ home: 'Home',
+ about: 'About EMA',
+ services: 'Services',
+ notices: 'Maritime Notices',
+ verify: 'Certificate Verification',
+ contact: 'Contact',
+ },
+
+ auth: {
+ login: 'Login',
+ signup: 'Sign Up',
+ dashboard: 'Go to Dashboard',
+ },
+
+ hero: {
+ eyebrow: 'Federal Democratic Republic of Ethiopia',
+ title: 'Ethiopian Maritime Authority',
+ subtitle: 'Digital Maritime Services for Seafarers, Vessel Owners and Logistics Operators',
+ },
+
+ about: {
+ title: 'About EMA',
+ visionLabel: 'Vision',
+ vision:
+ 'To make Ethiopia the leading logistics performer in Africa and one of the five supplying seafarer nations in the world by 2030.',
+ missionLabel: 'Mission',
+ mission:
+ "Transform Ethiopia's logistics system and unlock the blue economy — strengthening legal frameworks, building infrastructure, and ensuring vessel safety and seafarer qualification.",
+ },
+
+ cta: {
+ seafarerPortal: 'Seafarer Portal',
+ vesselOwnerPortal: 'Vessel Owner Portal',
+ },
+
+ quickAccess: {
+ title: 'Quick Access',
+ subtitle: 'The most requested services, one click away.',
+ verify: {
+ title: 'Certificate Verification',
+ description: 'Confirm a seafarer certificate or licence is genuine and current.',
+ },
+ vesselRegistration: {
+ title: 'Vessel Registration',
+ description: 'Register a vessel or transfer ownership.',
+ },
+ notices: {
+ title: 'Marine Notices',
+ description: 'Official notices to seafarers, owners and operators.',
+ },
+ },
+
+ services: {
+ title: 'Maritime Services',
+ subtitle: 'Every licence, certificate and registration EMA issues, in one digital system.',
+ items: {
+ seafarerRegistration: {
+ title: 'Seafarer Registration & Certification',
+ description:
+ "Register as a seafarer, apply for a Certificate of Competency or Proficiency, and manage your seaman's book online.",
+ },
+ vesselRegistration: {
+ title: 'Vessel Registration',
+ description:
+ 'Register inland or sea-going vessels and manage ownership transfers with full document tracking.',
+ },
+ licensing: {
+ title: 'Operator Licensing',
+ description:
+ 'Apply for freight forwarder, shipping agent, combined and multimodal transport operator licences.',
+ },
+ examinations: {
+ title: 'Examinations',
+ description: 'Sit competency examinations and track your results as part of certification.',
+ },
+ certificateVerification: {
+ title: 'Certificate Verification',
+ description: 'Let employers, port authorities and the public verify any EMA-issued certificate instantly.',
+ },
+ waivers: {
+ title: 'Waivers',
+ description: 'Apply for a maritime waiver where standard requirements do not apply.',
+ },
+ },
+ },
+
+ roles: {
+ title: 'Built for Every User',
+ subtitle: 'One portal, tailored to what you do.',
+ seafarers: {
+ title: 'Seafarers',
+ description: 'Register, certify, and manage your sea service records.',
+ },
+ vesselOwners: {
+ title: 'Vessel Owners',
+ description: 'Register vessels and manage ownership and licensing.',
+ },
+ agents: {
+ title: 'Agents & Logistics Operators',
+ description: 'Apply for and renew operator licences for freight and shipping.',
+ },
+ reviewers: {
+ title: 'EMA Reviewers',
+ description: 'Review, verify and approve applications from the backoffice.',
+ },
+ },
+
+ howItWorks: {
+ title: 'How It Works',
+ subtitle: 'From application to approval, in five steps.',
+ steps: {
+ selectRole: 'Select role',
+ createAccount: 'Create account',
+ submitApplication: 'Submit application',
+ trackStatus: 'Track status',
+ receiveApproval: 'Receive approval',
+ },
+ },
+
+ notices: {
+ title: 'Maritime Notices',
+ subtitle: 'Official notices to seafarers, vessel owners and operators.',
+ emptyTitle: 'No notices published yet',
+ emptyDescription: 'Check back here for official updates from EMA.',
+ },
+
+ faq: {
+ title: 'Frequently Asked Questions',
+ items: {
+ whatIsPortal: {
+ question: 'What is the EMA portal?',
+ answer:
+ "The EMA portal is the Ethiopian Maritime Authority's official digital system for seafarer registration, vessel registration, licensing, and certificate verification.",
+ },
+ whoCanUse: {
+ question: 'Who can use this portal?',
+ answer:
+ 'Seafarers, vessel owners, shipping agents, freight forwarders and logistics operators can all apply for and manage their licences here.',
+ },
+ howToApply: {
+ question: 'How do I apply for a licence or certificate?',
+ answer:
+ 'Create an account, select your role, and follow the application wizard for the licence or certificate you need. You can track its status at any time.',
+ },
+ howToVerify: {
+ question: 'How do I verify a certificate?',
+ answer:
+ 'Use Certificate Verification and enter the certificate reference or scan its QR code. No account is required.',
+ },
+ isFree: {
+ question: 'Is registration free?',
+ answer:
+ 'Creating a portal account is free. Statutory fees apply to specific licences and certificates, and are shown before you submit an application.',
+ },
+ },
+ },
+
+ contact: {
+ title: 'Contact EMA',
+ subtitle: 'Reach the Ethiopian Maritime Authority head office.',
+ addressLabel: 'Address',
+ address: 'Meskel Square, behind Hyatt Regency Hotel, Sunshine Building No. 4, Addis Ababa, Ethiopia',
+ phoneLabel: 'Phone',
+ websiteLabel: 'Website',
+ },
+
+ footer: {
+ rights: 'All rights reserved.',
+ },
+};
+
+export type LandingCopy = typeof landingEn;
+
+export const landingAm: LandingCopy = {
+ meta: {
+ skipToContent: 'ወደ ዋናው ይዘት ዝለል',
+ },
+
+ nav: {
+ home: 'ዋና ገጽ',
+ about: 'ስለ ባለስልጣኑ',
+ services: 'አገልግሎቶች',
+ notices: 'የባህር ማስታወቂያዎች',
+ verify: 'የምስክር ወረቀት ማረጋገጫ',
+ contact: 'እኛን ያግኙ',
+ },
+
+ auth: {
+ login: 'ግባ',
+ signup: 'ይመዝገቡ',
+ dashboard: 'ወደ ዳሽቦርድ ይሂዱ',
+ },
+
+ hero: {
+ eyebrow: 'የኢትዮጵያ ፌዴራላዊ ዲሞክራሲያዊ ሪፐብሊክ',
+ title: 'የኢትዮጵያ ማሪታይም ባለስልጣን',
+ subtitle: 'ለመርከበኞች፣ ለመርከብ ባለቤቶች እና ለሎጂስቲክስ ኦፕሬተሮች የተዘጋጁ ዲጂታል የባህር አገልግሎቶች',
+ },
+
+ about: {
+ title: 'ስለ ባለስልጣኑ',
+ visionLabel: 'ራዕይ',
+ vision:
+ 'በ2030 ኢትዮጵያ በአፍሪካ ግንባር ቀደም የሎጂስቲክስ አገልግሎት ሰጪ እንድትሆን፣ እንዲሁም ከዓለም አምስት መርከበኞችን ወደ ውጭ ከሚልኩ ሀገራት አንዷ እንድትሆን ማድረግ።',
+ missionLabel: 'ተልዕኮ',
+ mission:
+ 'የኢትዮጵያን የሎጂስቲክስ ስርዓት መለወጥ እንዲሁም ከባህር ኢኮኖሚ ተጠቃሚ መሆን — በህግ ማዕቀፎች ማጠናከር፣ መሠረተ ልማት በመገንባትና የመርከብ ደህንነትንና የመርከበኛ ብቃትን በማረጋገጥ።',
+ },
+
+ cta: {
+ seafarerPortal: 'የመርከበኞች ፖርታል',
+ vesselOwnerPortal: 'የመርከብ ባለቤቶች ፖርታል',
+ },
+
+ quickAccess: {
+ title: 'ፈጣን መዳረሻ',
+ subtitle: 'በብዛት የሚፈለጉ አገልግሎቶች፣ በአንድ ቦታ።',
+ verify: {
+ title: 'የምስክር ወረቀት ማረጋገጫ',
+ description: 'የመርከበኛ ምስክር ወረቀት ወይም ፈቃድ ትክክለኛ እና የሚሰራ መሆኑን ያረጋግጡ።',
+ },
+ vesselRegistration: {
+ title: 'የመርከብ ምዝገባ',
+ description: 'መርከብ ይመዝገቡ ወይም ባለቤትነት ያስተላልፉ።',
+ },
+ notices: {
+ title: 'የባህር ማስታወቂያዎች',
+ description: 'ለመርከበኞች፣ ለመርከብ ባለቤቶችና ለኦፕሬተሮች የተሰጡ ማስታወቂያዎች።',
+ },
+ },
+
+ services: {
+ title: 'የባህር አገልግሎቶች',
+ subtitle: 'ባለስልጣኑ የሚሰጣቸው ሁሉም ፈቃድ፣ ምስክር ወረቀትና ምዝገባ በአንድ ዲጂታል ስርዓት ውስጥ።',
+ items: {
+ seafarerRegistration: {
+ title: 'የመርከበኞች ምዝገባና ማረጋገጫ',
+ description:
+ 'እንደ መርከበኛ ይመዝገቡ፣ ለብቃት ወይም ችሎታ ማረጋገጫ ምስክር ወረቀት ያመልክቱ፣ እንዲሁም የመርከበኛ መዝገብ መጽሐፍዎን በመስመር ላይ ያስተዳድሩ።',
+ },
+ vesselRegistration: {
+ title: 'የመርከብ ምዝገባ',
+ description: 'የውስጥ ውሃ ወይም የባህር ማዶ መርከቦችን ይመዝገቡ፣ የባለቤትነት ዝውውርንም ሙሉ በሙሉ በሰነድ ክትትል ያስተዳድሩ።',
+ },
+ licensing: {
+ title: 'የኦፕሬተር ፈቃድ',
+ description: 'ለጭነት አስተላላፊ፣ ለመርከብ ወኪል፣ ለተቀናጀ እና ለብዙ-ዘዴ ትራንስፖርት ኦፕሬተር ፈቃድ ያመልክቱ።',
+ },
+ examinations: {
+ title: 'ፈተናዎች',
+ description: 'የብቃት ፈተናዎችን ይውሰዱ እንዲሁም ውጤቶችዎን እንደ ማረጋገጫ ሂደት አካል ይከታተሉ።',
+ },
+ certificateVerification: {
+ title: 'የምስክር ወረቀት ማረጋገጫ',
+ description: 'ቀጣሪዎች፣ የወደብ ባለስልጣናትና ህዝብ ማንኛውንም በባለስልጣኑ የተሰጠ ምስክር ወረቀት ወዲያውኑ እንዲያረጋግጡ ያስችላል።',
+ },
+ waivers: {
+ title: 'ነፃ ፈቃዶች',
+ description: 'መደበኛ መስፈርቶች በማይሟሉበት ጊዜ ለባህር ትራንስፖርት ነፃ ፈቃድ ያመልክቱ።',
+ },
+ },
+ },
+
+ roles: {
+ title: 'ለሁሉም ተጠቃሚ የተዘጋጀ',
+ subtitle: 'አንድ ፖርታል፣ ለሚናዎ የተዘጋጀ።',
+ seafarers: {
+ title: 'መርከበኞች',
+ description: 'ይመዝገቡ፣ ይረጋገጡ እንዲሁም የባህር አገልግሎት መዝገብዎን ያስተዳድሩ።',
+ },
+ vesselOwners: {
+ title: 'የመርከብ ባለቤቶች',
+ description: 'መርከብ ይመዝገቡ እንዲሁም ባለቤትነትና ፈቃድ ያስተዳድሩ።',
+ },
+ agents: {
+ title: 'ወኪሎችና ሎጂስቲክስ ኦፕሬተሮች',
+ description: 'ለጭነትና ለመላኪያ ፈቃድ ያመልክቱ እንዲሁም ያድሱ።',
+ },
+ reviewers: {
+ title: 'የባለስልጣኑ ገምጋሚዎች',
+ description: 'ማመልከቻዎችን ይገመግሙ፣ ያረጋግጡ እንዲሁም ይፍቀዱ።',
+ },
+ },
+
+ howItWorks: {
+ title: 'እንዴት እንደሚሰራ',
+ subtitle: 'ከማመልከቻ እስከ ፈቃድ፣ በአምስት ደረጃዎች።',
+ steps: {
+ selectRole: 'ሚና ይምረጡ',
+ createAccount: 'መለያ ይፍጠሩ',
+ submitApplication: 'ማመልከቻ ያስገቡ',
+ trackStatus: 'ሁኔታ ይከታተሉ',
+ receiveApproval: 'ፍቃድ ይቀበሉ',
+ },
+ },
+
+ notices: {
+ title: 'የባህር ማስታወቂያዎች',
+ subtitle: 'ለመርከበኞች፣ ለመርከብ ባለቤቶችና ለኦፕሬተሮች የተሰጡ ማስታወቂያዎች።',
+ emptyTitle: 'እስካሁን የወጣ ማስታወቂያ የለም',
+ emptyDescription: 'አዲስ መረጃ ሲኖር እዚህ ያገኙታል።',
+ },
+
+ faq: {
+ title: 'ተደጋጋሚ ጥያቄዎች',
+ items: {
+ whatIsPortal: {
+ question: 'EMA ፖርታል ምንድን ነው?',
+ answer:
+ 'EMA ፖርታል የኢትዮጵያ ማሪታይም ባለስልጣን ለመርከበኛ ምዝገባ፣ ለመርከብ ምዝገባ፣ ፈቃድና ለምስክር ወረቀት ማረጋገጫ የሚጠቀምበት ዲጂታል ስርዓት ነው።',
+ },
+ whoCanUse: {
+ question: 'ይህን ፖርታል ማን ሊጠቀም ይችላል?',
+ answer: 'መርከበኞች፣ የመርከብ ባለቤቶች፣ ወኪሎችና ሎጂስቲክስ ኦፕሬተሮች ሁሉም እዚህ ፈቃዳቸውን ማመልከትና ማስተዳደር ይችላሉ።',
+ },
+ howToApply: {
+ question: 'ለፈቃድ ወይም ለምስክር ወረቀት እንዴት አመለክታለሁ?',
+ answer:
+ 'መለያ ይፍጠሩ፣ ሚናዎን ይምረጡ፣ እንዲሁም የሚያስፈልግዎትን ፈቃድ ወይም ምስክር ወረቀት ደረጃዎች ይከተሉ። ሁኔታውን በማንኛውም ጊዜ መከታተል ይችላሉ።',
+ },
+ howToVerify: {
+ question: 'ምስክር ወረቀት እንዴት አረጋግጣለሁ?',
+ answer: 'የምስክር ወረቀት ማረጋገጫን ይክፈቱና የምስክር ወረቀቱን ቁጥር ያስገቡ ወይም QR ኮዱን ያንብቡ። መለያ አያስፈልግም።',
+ },
+ isFree: {
+ question: 'ምዝገባ ነፃ ነው?',
+ answer: 'የፖርታል መለያ መክፈት ነፃ ነው። ለተወሰኑ ፈቃዶችና ምስክር ወረቀቶች የመንግስት ክፍያ ይኖራል፣ ማመልከቻ ከማስገባትዎ በፊት ይታያል።',
+ },
+ },
+ },
+
+ contact: {
+ title: 'ባለስልጣኑን ያግኙ',
+ subtitle: 'የኢትዮጵያ ማሪታይም ባለስልጣን ዋና መሥሪያ ቤትን ያግኙ።',
+ addressLabel: 'አድራሻ',
+ address: 'መስቀል አደባባይ፣ ከHyatt Regency ሆቴል ጀርባ፣ Sunshine ህንፃ ቁጥር 4፣ አዲስ አበባ፣ ኢትዮጵያ',
+ phoneLabel: 'ስልክ',
+ websiteLabel: 'ድረ ገጽ',
+ },
+
+ footer: {
+ rights: 'ሁሉም መብቶች የተጠበቁ ናቸው።',
+ },
+};
diff --git a/libs/ui/src/lib/landing/landing.css b/libs/ui/src/lib/landing/landing.css
new file mode 100644
index 000000000..e1173d306
--- /dev/null
+++ b/libs/ui/src/lib/landing/landing.css
@@ -0,0 +1,56 @@
+/* Public landing page — scoped under .ema-landing so nothing leaks into the
+ rest of either app. Neither app's global CSS (portal's .ema-page-enter /
+ .ema-hover-lift, portal's --ema-surface-*) is available here, so this file
+ is self-contained. */
+
+.ema-landing {
+ scroll-behavior: smooth;
+}
+
+.ema-landing section[id] {
+ /* Offsets anchor jumps by the sticky header height so the heading isn't
+ hidden underneath it. Keep in sync with the header's fixed height. */
+ scroll-margin-top: 72px;
+}
+
+.ema-landing .ema-landing-fade {
+ animation: ema-landing-fade-up 360ms cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+@keyframes ema-landing-fade-up {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.ema-landing .ema-landing-hover {
+ transition:
+ transform 160ms ease,
+ box-shadow 160ms ease,
+ border-color 160ms ease;
+}
+.ema-landing .ema-landing-hover:hover {
+ transform: translateY(-3px);
+}
+
+/* Amharic runs 20-40% longer than English and falls back to a different font
+ (Inter has no Ethiopic glyphs), so it needs more vertical room. */
+.ema-landing:lang(am) {
+ line-height: 1.7;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .ema-landing {
+ scroll-behavior: auto;
+ }
+ .ema-landing .ema-landing-fade,
+ .ema-landing .ema-landing-hover {
+ animation: none;
+ transition: none;
+ }
+}
diff --git a/libs/ui/src/lib/layout/ColorSchemeToggle.tsx b/libs/ui/src/lib/layout/ColorSchemeToggle.tsx
index e7bcd0ae8..c31109394 100644
--- a/libs/ui/src/lib/layout/ColorSchemeToggle.tsx
+++ b/libs/ui/src/lib/layout/ColorSchemeToggle.tsx
@@ -27,10 +27,10 @@ export function ColorSchemeToggle() {
border: 'none',
}}
onMouseEnter={(e) => {
- e.currentTarget.style.background = 'var(--mantine-color-primary-light)';
- e.currentTarget.style.color = 'var(--mantine-color-primary-6)';
+ e.currentTarget.style.background = 'var(--mantine-primary-color-light)';
+ e.currentTarget.style.color = 'var(--mantine-primary-color-6)';
e.currentTarget.style.boxShadow =
- '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px var(--mantine-color-primary-2)';
+ '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px var(--mantine-primary-color-2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'var(--mantine-color-body)';
diff --git a/libs/ui/src/lib/layout/LanguageSwitcher.tsx b/libs/ui/src/lib/layout/LanguageSwitcher.tsx
index f1b2ce481..b1ec6305d 100644
--- a/libs/ui/src/lib/layout/LanguageSwitcher.tsx
+++ b/libs/ui/src/lib/layout/LanguageSwitcher.tsx
@@ -38,10 +38,10 @@ export function LanguageSwitcher({ supportedLanguages, variant = 'icon' }: Langu
border: 'none',
}}
onMouseEnter={(e) => {
- e.currentTarget.style.background = 'var(--mantine-color-primary-light)';
- e.currentTarget.style.color = 'var(--mantine-color-primary-6)';
+ e.currentTarget.style.background = 'var(--mantine-primary-color-light)';
+ e.currentTarget.style.color = 'var(--mantine-primary-color-6)';
e.currentTarget.style.boxShadow =
- '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px var(--mantine-color-primary-2)';
+ '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px var(--mantine-primary-color-2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'var(--mantine-color-body)';