/* ========================================================================== SHARP LEGACY - /en/legacy and /ar/legacy -------------------------------------------------------------------------- The full programme record stays available from the CMS. The page introduces each phase with a short story, its own gallery, and three highlights. ========================================================================== */ import { useRef, useState } from "react"; import { Link } from "react-router"; import { ArrowIcon, Button } from "~/components/ui/Button"; import { createTranslator, sectionPath, type Translator } from "~/i18n"; import { buildBreadcrumbJsonLd, buildMeta } from "~/lib/seo"; import type { Route } from "./+types/legacy"; import type { CmsLegacyPage } from "./_page-kit/cms-types"; import { loadSingle, toImage } from "./_page-kit/cms.server"; import type { LegacyGalleryImage, LegacyStageCopy } from "./_page-kit/copy"; import { pageCopy } from "./_page-kit/copy"; import { resolveLegacyStage } from "./_page-kit/legacy-cms.server"; import { routeLocale } from "./_page-kit/locale"; import { cx, PageHero, PageSection, SectionHeader } from "./_page-kit/sections"; import { RichText } from "./_page-kit/rich-text"; import "./_page-kit/motion.css"; const STAGES = [ { id: "sharp-i", band: true, }, { id: "sharp-ii", band: false, }, ] as const; /** Keep the short page narrative in step with the editor's CMS description. */ function storyPreview(markdown: string) { const [introduction = "", ...rawChapters] = markdown .trim() .replace(/\r\n?/g, "\n") .split(/\n(?=###\s)/); const chapters = rawChapters.flatMap((raw) => { const match = /^###\s+([^\n]+)\n([\s\S]*)$/.exec(raw.trim()); return match ? [{ heading: match[1]!, body: match[2]!.trim() }] : []; }); const featuredIndexes = new Set( chapters.length > 3 ? [1, 2, chapters.length - 1] : chapters.map((_, index) => index), ); const firstSentences = (value: string, count: number) => value.trim().split(/(?<=[.!?؟])\s+/u).slice(0, count).join(" "); return { introduction: firstSentences(introduction, 2), highlights: chapters .filter((_, index) => featuredIndexes.has(index)) .map(({ heading, body }) => ({ heading, preview: firstSentences(body.split(/\n\s*\n/)[0] ?? body, 2), })), }; } export function loader({ params }: Route.LoaderArgs) { const locale = routeLocale(params); return loadSingle("legacy-page", { locale, populate: { hero: { populate: { media: true, ctas: true } }, stageOne: { populate: { metrics: true, focusAreas: true, images: true } }, stageTwo: { populate: { metrics: true, focusAreas: true, images: true } }, seo: { populate: { ogImage: true } }, }, }).then((page) => { const fallback = pageCopy(locale).legacy; return { locale, hero: { eyebrow: page?.hero?.eyebrow ?? null, title: page?.hero?.headline ?? null, lead: page?.hero?.subheading ?? null, media: toImage(page?.hero?.media, "large") ?? null, }, stageOne: resolveLegacyStage(page?.stageOne, fallback.stageOne), stageTwo: resolveLegacyStage(page?.stageTwo, fallback.stageTwo), closingHeading: page?.closingHeading ?? null, closingBody: page?.closingBody ?? null, seo: { title: page?.seo?.metaTitle ?? null, description: page?.seo?.metaDescription ?? null, image: toImage(page?.seo?.ogImage, "large") ?? null, noIndex: page?.seo?.noIndex ?? false, }, }; }); } export function meta({ loaderData, matches }: Route.MetaArgs) { const root = matches[0].loaderData; const locale = loaderData?.locale ?? root.locale; const t = createTranslator(locale); const copy = pageCopy(locale).legacy; const seo = loaderData?.seo; return buildMeta({ locale, pathname: root.pathname, siteUrl: root.siteUrl, title: seo?.title ?? t("nav.sections.legacy"), description: seo?.description ?? loaderData?.hero.lead ?? copy.heroLead, ...(seo?.image ? { image: { url: seo.image.src, alt: seo.image.alt } } : {}), noIndex: seo?.noIndex ?? false, jsonLd: buildBreadcrumbJsonLd(root.siteUrl, [ { name: t("nav.sections.home"), pathname: sectionPath("home", locale) }, { name: t("nav.sections.about"), pathname: sectionPath("about", locale) }, { name: t("nav.sections.legacy"), pathname: sectionPath("legacy", locale) }, ]), }); } function LegacyImageGallery({ images, stageName, heading, empty, t, }: { images: readonly LegacyGalleryImage[]; stageName: string; heading: string; empty: string; t: Translator; }) { const trackRef = useRef(null); const [activeIndex, setActiveIndex] = useState(0); const scrollToImage = (index: number) => { trackRef.current?.children.item(index)?.scrollIntoView({ behavior: "auto", block: "nearest", inline: "start", }); setActiveIndex(index); }; const updateActiveImage = () => { const track = trackRef.current; if (!track) return; const bounds = track.getBoundingClientRect(); const center = (bounds.left + bounds.right) / 2; let closestIndex = 0; let closestDistance = Number.POSITIVE_INFINITY; Array.from(track.children).forEach((slide, index) => { const rect = slide.getBoundingClientRect(); const distance = Math.abs((rect.left + rect.right) / 2 - center); if (distance < closestDistance) { closestIndex = index; closestDistance = distance; } }); setActiveIndex((current) => current === closestIndex ? current : closestIndex); }; if (images.length === 0) { return (

{empty}

); } if (images.length === 1) { const image = images[0]!; return (
{image.alt} {image.caption ? (
{image.caption}
) : null}
); } return (
    {images.map((image, index) => (
  • {image.alt} {image.caption ? (
    {image.caption}
    ) : null}
  • ))}

{t("legacy.imageCount", { current: activeIndex + 1, total: images.length })}

); } function StageSection({ stage, id, band, t, }: { stage: LegacyStageCopy; id: string; band: boolean; t: Translator; }) { const story = storyPreview(stage.description); return (
{story.introduction ? (

{story.introduction}

) : null}
{stage.metrics.slice(0, 3).map((metric) => (
{metric.label}
{metric.value}
))}

{stage.imageHeading}

{story.highlights.length > 0 ? (
{story.highlights.map((highlight) => (

{highlight.heading}

{highlight.preview}

))}
) : null}
{t("legacy.fullRecord", { programme: stage.name })}
{stage.focusAreas.length > 0 ? (

{stage.focusHeading}

    {stage.focusAreas.map((area) => (
  • {area}
  • ))}
) : null}
); } export default function Legacy({ loaderData }: Route.ComponentProps) { const locale = loaderData.locale; const t = createTranslator(locale); const copy = pageCopy(locale).legacy; const stageEntries = [ { ...STAGES[0], stage: loaderData.stageOne }, { ...STAGES[1], stage: loaderData.stageTwo }, ] as const; return ( <> {stageEntries.map(({ stage, ...config }) => ( ))}

{(loaderData.closingBody ?? copy.closingParagraphs.join(" ")) .trim() .split(/(?<=[.!?؟])\s+/u) .slice(0, 2) .join(" ")}

); }