added seeder for legacy apge and added the legacy page

This commit is contained in:
teledrisi
2026-10-05 17:32:06 +02:00
parent a5c531be00
commit 98fae066bc
30 changed files with 1488 additions and 181 deletions
+95 -21
View File
@@ -6,7 +6,7 @@
alternating the two dark surfaces the way the draft alternates #0B0B0A and
#1D1D1B:
Hero EU badge, an optional status pill, the headline, the lead,
Hero EU badge, the headline, the lead,
the CTA pair, the wide picture and the brand stripe. Two
slow washes drift behind it.
Approach "A nationwide project, delivered locally": what the project
@@ -73,6 +73,7 @@ import {
isImageGalleryBlock,
type CmsFaq,
type CmsHomePage,
type CmsLegacyPage,
type CmsNewsArticle,
type CmsPartner,
} from "./_page-kit/cms-types";
@@ -86,6 +87,7 @@ import {
type ResolvedPartner,
} from "./_page-kit/cms.server";
import { pageCopy } from "./_page-kit/copy";
import { resolveLegacyStage } from "./_page-kit/legacy-cms.server";
import { routeLocale } from "./_page-kit/locale";
import {
cx,
@@ -181,6 +183,13 @@ function toNewsCard(article: CmsNewsArticle, locale: Locale): NewsCard {
export async function loader({ params }: Route.LoaderArgs) {
const locale = routeLocale(params);
const legacyPagePromise = loadSingle<CmsLegacyPage>("legacy-page", {
locale,
populate: {
stageOne: { populate: { metrics: true, focusAreas: true } },
stageTwo: { populate: { metrics: true, focusAreas: true } },
},
});
const page = await loadSingle<CmsHomePage>("home-page", {
locale,
@@ -194,6 +203,8 @@ export async function loader({ params }: Route.LoaderArgs) {
seo: { populate: { ogImage: true } },
},
});
const legacyPage = await legacyPagePromise;
const legacyFallback = pageCopy(locale).legacy;
/* The curated relation wins; otherwise show every partner the CMS knows, so
the wall fills in as soon as partners exist - before anyone remembers to
@@ -243,8 +254,15 @@ export async function loader({ params }: Route.LoaderArgs) {
return {
locale,
legacy: {
homeEyebrow: legacyPage?.homeEyebrow ?? legacyFallback.homeEyebrow,
homeHeading: legacyPage?.homeHeading ?? legacyFallback.homeHeading,
homeLead: legacyPage?.homeLead ?? legacyFallback.homeLead,
homeLinkLabel: legacyFallback.homeLinkLabel,
stageOne: resolveLegacyStage(legacyPage?.stageOne, legacyFallback.stageOne),
stageTwo: resolveLegacyStage(legacyPage?.stageTwo, legacyFallback.stageTwo),
},
hero: {
eyebrow: page?.hero?.eyebrow ?? null,
headline: page?.hero?.headline ?? null,
subheading: page?.hero?.subheading ?? null,
media: toImage(page?.hero?.media, "large") ?? null,
@@ -334,24 +352,6 @@ function HeroCta({ href, label, variant }: HeroCtaProps) {
);
}
/**
* The hero's status line. The draft hard-codes "Now live · Expression of
* Interest open"; here the words are the CMS hero eyebrow, so the pill only
* ever announces a state an editor has actually written - and the hero simply
* has no pill until then. The dot pulses; the text does not move.
*/
function StatusPill({ label }: { label: string }) {
return (
<span className="inline-flex items-center gap-xs rounded-pill border border-[var(--border-subtle)] bg-[var(--surface-raised)] py-2xs ps-sm pe-md">
<span
aria-hidden="true"
className="sh-pulse size-[7px] shrink-0 rounded-pill bg-[var(--surface-accent)]"
/>
<span className="label-md uppercase text-[var(--text-secondary)]">{label}</span>
</span>
);
}
/**
* The approach band's pictures. The CMS alt text doubles as the visible
* caption - so the picture is described once, in the caption, and the <img>
@@ -546,6 +546,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
const { locale, hero, approach, gallery, partners, news, faqs } = loaderData;
const t = createTranslator(locale);
const copy = pageCopy(locale).home;
const legacyCopy = loaderData.legacy;
/* The hero badge sits on the page's own surface, so it follows the theme
exactly as the footer's badge does: same root cookie, same emblem. */
const root = useRouteLoaderData<{ theme?: AppTheme }>("root");
@@ -603,7 +604,6 @@ export default function Home({ loaderData }: Route.ComponentProps) {
title={headline}
lead={""}
leadWithActions
{...(hero.eyebrow ? { statusPill: <StatusPill label={hero.eyebrow} /> } : {})}
ambient={[
{ top: -220, from: "start", offset: "8%", width: 780, height: 620, durationSeconds: 18 },
{
@@ -641,6 +641,80 @@ reveal
))}
/>
{/* THE TWO EARLIER SHARP PHASES */}
<PageSection
aria-labelledby="home-legacy-heading"
className="bg-[var(--surface-sunken)]"
>
<div className="flex flex-col gap-2xl">
<div className="sh-reveal flex flex-col gap-md border-b border-[var(--border-default)] pb-lg md:max-w-[52rem]">
<p className="label-md uppercase text-[var(--text-accent)]">
{legacyCopy.homeEyebrow}
</p>
<SectionHeader
id="home-legacy-heading"
title={legacyCopy.homeHeading}
lead={legacyCopy.homeLead}
size="display"
/>
</div>
<div className="grid grid-cols-1 gap-lg lg:grid-cols-2">
{[
{
stage: legacyCopy.stageOne,
anchor: "sharp-i",
},
{
stage: legacyCopy.stageTwo,
anchor: "sharp-ii",
},
].map(({ stage, anchor }) => (
<article
key={stage.name}
className="sh-lift flex h-full flex-col overflow-hidden rounded-card border border-[var(--border-subtle)] bg-[var(--surface-card)]"
>
<div className="flex flex-1 flex-col gap-lg p-lg md:p-xl">
<div className="flex flex-wrap items-baseline justify-between gap-sm">
<h3 className="heading-h2 text-[var(--text-primary)]">
{stage.name}
</h3>
<span className="label-md text-[var(--text-accent)]">
{stage.years}
</span>
</div>
<p className="body-md text-[var(--text-secondary)]">
{stage.summary}
</p>
<dl className="grid grid-cols-3 gap-md border-y border-[var(--border-subtle)] py-md">
{stage.metrics.slice(0, 3).map((metric) => (
<div key={metric.label} className="flex flex-col gap-2xs">
<dt className="label-md text-[var(--text-secondary)]">
{metric.label}
</dt>
<dd className="heading-h2 text-[var(--text-accent)]">
{metric.value}
</dd>
</div>
))}
</dl>
<Button
as={Link}
to={`${sectionPath("legacy", locale)}#${anchor}`}
prefetch="intent"
variant="ghost"
className="self-start"
iconEnd={<ArrowIcon />}
>
{legacyCopy.homeLinkLabel}
</Button>
</div>
</article>
))}
</div>
</div>
</PageSection>
{/* WHAT THE PROJECT IS, AND WHO DELIVERS IT */}
{/* `bleed` drops the 80rem Container so the band's content - the three
pictures especially - runs the full width of the viewport, the way