added seeder for legacy apge and added the legacy page
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user