import { CalendarDays, HeartPulse, Smartphone, Sparkles, Users } from "lucide-react"; import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Link, useNavigate } from "react-router-dom"; import { attemptOpenNativeApp, isAndroidPhoneBrowser } from "../../lib/mobile-app-redirect"; import "./landing-page.scss"; /** * Public marketing page shown at `/` to a logged-out visitor (see * `RequireAuth` for how it's wired in without touching the route table). * All copy comes from i18next under the `landing.*` namespace — no * hardcoded JSX text, same convention as every other page. * * Deliberately dependency-free for visuals: the hero illustration is an * inline stroke-drawn SVG in the same visual language as `lucide-react` * (the icon set already used everywhere), not an imported illustration * library or an external asset — same choice `TechStepCorrectionPopover` * made for its own hand-drawn icons. The three preview images are real * app screenshots served statically from `public/landing/` (see that * folder's README); a missing file just falls back to its `alt` text and * never breaks the build. */ export function LandingPage() { const { t } = useTranslation(); const navigate = useNavigate(); // UA-based, not `useIsMobileViewport()` — same reasoning as // `attemptOpenNativeApp`'s own doc comment: this decides whether trying // to open the *native app* makes sense at all (a platform question), not // whether the viewport is narrow (a resized desktop window shouldn't // attempt a scheme navigation just because it's a small window). const isMobile = isAndroidPhoneBrowser(); function handleOpenApp() { attemptOpenNativeApp({ onFallback: () => navigate("/telecharger") }); } const features: { icon: ReactNode; key: "planning" | "import" | "health" | "household" }[] = [ { icon: , key: "planning" }, { icon: , key: "import" }, { icon: , key: "health" }, { icon: , key: "household" }, ]; const shots: { src: string; key: "planning" | "recipe" | "cookingSession" }[] = [ { src: "/landing/planning.png", key: "planning" }, { src: "/landing/recipe.png", key: "recipe" }, { src: "/landing/cooking-session.png", key: "cookingSession" }, ]; return (

batchCooking

{t("landing.hero.title")}

{t("landing.hero.lede")}

{/* * Issue #85: on a real Android phone browser, opening/getting * the app is more useful than a web account — the web * experience isn't optimized for mobile at all (the whole * point of this chantier), and someone with the app already * installed can sign up/log in from inside it just as well. * So this becomes the *primary* action, demoting Signup/Login * to secondary rather than hiding them — some visitors * genuinely want the browser flow, e.g. to try it before * installing anything. Desktop is untouched: a scheme-open * attempt makes no sense there, so Signup stays primary and * this button doesn't render at all (see the plain `/telecharger` * link below instead, replacing the old direct-APK link). */} {isMobile && ( )} {t("landing.hero.ctaSignup")} {t("landing.hero.ctaLogin")}
{/* * Desktop only — a scheme-open attempt (the mobile button above) * has no meaning here. Routed through `/telecharger` (the same * fallback page a failed mobile attempt lands on) rather than * linking `${API_BASE_URL}/mobile/download` directly, so "get * the app" has exactly one canonical destination app-wide * instead of two slightly different mechanisms to keep in sync. */} {!isMobile && ( {t("landing.hero.mobileDownload")} )}

{t("landing.features.title")}

    {features.map(({ icon, key }) => (
  • {icon}

    {t(`landing.features.${key}.title`)}

    {t(`landing.features.${key}.body`)}

  • ))}

{t("landing.preview.title")}

{shots.map(({ src, key }) => (
{t(`landing.preview.${key}Alt`)}
{t(`landing.preview.${key}Caption`)}
))}

{t("landing.finalCta.title")}

{t("landing.finalCta.button")}
); } /** * Hero illustration — a week grid with a simmering pot, drawn in the same * 2px round-stroke `currentColor` language as the lucide icon set so it * sits visually with the rest of the app. No external asset. */ function HeroIllustration() { return ( {/* week grid */} {/* a couple of "planned" chips */} {/* simmering pot, front-right */} ); }