Land the third design drop, seed the CMS, and retire the figures band

Three pieces of one session's work. They are in one commit because home.tsx
and _page-kit/copy.ts carry hunks from all three and splitting them by hand
would have meant committing intermediate states of those two files that were
never built or served.

THE THIRD DESIGN DROP

Only Home.dc.html and SiteFooter.dc.html differ from the second drop; the
other nine page drafts are byte-identical to what already ships.

The home page stops opening on a launch announcement dated to one day in
June. New headline, lead and primary call to action ("See open calls"), and
the launch band becomes "A nationwide project, delivered locally" -
launchEyebrow/Heading/Lead renamed approach*, LaunchShots renamed
GalleryShots, since the band is no longer about an event. Its Arabic reuses
the About page's already-approved wording for the coverage line and the body
heading; the new lead and headline are ar-draft like the rest of the Arabic
home copy and still want client sign-off.

Five motion classes in _page-kit/motion.css, tokens only, each with its RTL
form where the property has no logical one: sh-wipe (the headline uncovered
from the inline start), sh-pan (the hero picture on a view timeline), sh-zoom
(a picture closing in inside its frame), sh-rule-top (an accent rule drawn
across a hovered tile) and sh-marquee (the logo wall). sh-wipe fills
`backwards`, not `both`, so the clip is gone at rest - inset(0) would shave a
descender or an Arabic mark off a tight heading line - and it is removed
outright under prefers-reduced-motion, because a clip does not fade a
headline, it hides it.

The marquee carries each logo twice and slides exactly half the track, so its
spacing is a margin on the tile rather than a gap on the row: with a row gap
the seam lands half a gap short. The second half is aria-hidden with no link,
so the wall is neither read out nor tabbed through twice; it pauses on hover
and focus (WCAG 2.2.2) and unwraps to a plain row under reduced motion. Below
eight partners the wall stays a grid, because a half-track narrower than its
frame drifts a short row across empty space instead of looping.

SEEDING THE CMS

`pnpm seed` writes content through the document service. It opens no port, so
it runs happily beside `pnpm dev:cms`, and every write is matched on a slug
and written over the same document, so a second run changes nothing but the
fields the seed owns.

src/seed/approved.ts is transcribed from docs/ in both languages and is safe
to publish: site settings, the three consortium members, the topic taxonomy,
the five activity streams with their implementing partners, the launch
article in full, and the four pages. What docs/ withholds is not written -
no statistics, no project email, no Facebook URL, no consortium addresses,
all of which the source writes as bracketed placeholders. Two fields are
judgement calls and say so at the line: activityStatus, which the schema
requires and the source never states, and the launch article's year, which is
derived - the source says "Tuesday, June 30" and within December 2025 to
December 2027 that is 2026 and no other year.

src/seed/demo.ts is invented and kept apart three ways: opt-in behind --demo,
refused outright when NODE_ENV=production, and tagged in the data itself with
a demo- slug prefix that shows in every URL and is how --demo --clean finds
them again, with nothing remembered between runs.

Two things the Strapi 5 document service does not do by itself, both of which
leave Arabic rows behind that the next run then collides with on the unique
slug: delete({ documentId }) with no locale removes the DEFAULT locale only,
not the document, and findMany({ locale }) answers for one locale, so a
document whose English half has gone is invisible to a search in English. The
first --clean left six Arabic orphans and failed the next run partway.
deleteMatching now pools documentIds across both locales and deletes with
locale: '*'.

Knex aborts its pending pool acquisitions on teardown, and each abort rejects
a promise nothing awaits, so it arrives as an unhandled rejection rather than
as an error a try/catch would see. scripts/seed.js absorbs exactly that, for
the length of the shutdown only: a seed that worked must not report itself as
a seed that failed.

THE FIGURES BAND, AND THE BAND THAT REPLACED IT

The third draft deletes the figures band, so shared.stat and blocks.stats-grid
are deleted with it, home-page.stats is dropped, and blocks.stats-grid comes
out of all six dynamic zones. On the web side that takes CmsStat, StatsRow,
CountUpValue, StatStrip and Card.Stat - the last two were already dead, but
they existed only to draw figures. No approved document carries a number, so
there is now nowhere for one to be typed in, which is the point.

The "nationwide project, delivered locally" band was half editable and half
not: its pictures came from an image-gallery block down the Blocks dynamic
zone, and its words came from bundled fallback copy with no CMS field at all.
The new page.band-intro component (eyebrow, heading, lead, images) on
home-page.approach puts the whole band in one place in the admin. The page
still falls back to the bundled copy when the fields are empty, and still
reads an image-gallery block after the component's own images, so a gallery
built in Blocks keeps rendering.

VERIFIED

pnpm typecheck and both builds pass. Both rule greps are clean: no raw hex or
tier-1 token in components/ or routes/, no physical-direction utility in
apps/web/app.

Against a seeded CMS on the built server: / redirects 302 with
Vary: Accept-Language, and all ten sections plus the detail routes return 200
in both locales with lang/dir correct in the first bytes. The home page draws
the marquee from twelve partners, the hero pan, the three hovered card rules
and the status pill, and carries no figures band.

The seed is idempotent - two identical --demo runs give identical counts,
balanced across locales - and a clean/re-seed cycle leaves nothing behind.
The approach band was proved editable end to end: heading, eyebrow, lead and
images changed through the CMS all came through on /en while /ar stayed on
its own Arabic, then the seeded copy was restored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-20 14:16:53 +02:00
co-authored by Claude Opus 5
parent 2ed3a17914
commit 2e920a96e7
30 changed files with 2994 additions and 484 deletions
+38 -48
View File
@@ -2,14 +2,17 @@
PAGE COPY - EN / AR
----------------------------------------------------------------------------
SOURCE
Home page Figma frame 253:4799 "Home-en" is the source of truth.
The headline (I253:4800;228:2609), the launch heading
(I361:10853;361:11147), the one-paragraph launch lead
with its four bold runs (I361:10853;349:8838) and the
three captions are transcribed from that frame, NOT
from docs/SHARPER Home Page .docx - the design condenses
the draft, and the draft itself is headed "Placeholder
text for design purposes - not final copy".
Home page The Claude Design draft "Home page redesign with news
and FAQs" (third drop) is the source of truth. Its
headline, lead, primary call to action and the
"nationwide project, delivered locally" band are
transcribed from that draft, NOT from docs/SHARPER Home
Page .docx - the design condenses the draft, and the
draft itself is headed "Placeholder text for design
purposes - not final copy". Every claim in it is
nonetheless in docs/: the consortium, the partner
groups it works with, the EU funding and the December
2027 end date.
Everything else docs/SHARPER_Website_Draft_Section_EN_AR.docx
(sections 1, 2 and 9), which carries both columns, so
no string below is a machine translation of the other
@@ -32,8 +35,9 @@
Figures. The draft marks every number "TBD" and says they must not be
published until confirmed, so there is no statistic anywhere in this file.
Figma hides the home page's figures band outright (frame 253:4835 is
hidden="true"), so the band renders only once the CMS `stats` component
supplies real numbers, and nothing stands in for it until then.
hidden="true") and the third design drop deletes it, so the band is gone
from the page and the `stats` component is gone from the CMS. There is
nowhere left for a figure to be typed in, which is the point.
========================================================================= */
import type { Locale } from "~/i18n";
@@ -69,17 +73,13 @@ export interface HomeCopy {
readonly heroCtaPrimary: string;
readonly heroCtaSecondary: string;
/**
* The figures band. Bundled so the band is never empty, and overridden by
* `home-page.stats` the moment an editor writes one. Every figure here is
* verifiable: three consortium members and the Dec 2025 - Dec 2027 run come
* from the project brief, the four areas of work are the What We Do
* workstreams, and the sixteen human security sub-projects are the approved
* EN/AR draft's own wording.
* The band under the hero picture: what the project is and who delivers it.
* It replaces the launch-event band of the previous drop, which had dated
* the whole home page to a single day in June.
*/
readonly stats: readonly { readonly value: string; readonly label: string }[];
readonly launchEyebrow: string;
readonly launchHeading: string;
readonly launchLead: string;
readonly approachEyebrow: string;
readonly approachHeading: string;
readonly approachLead: string;
readonly workHeading: string;
readonly workLinkLabel: string;
readonly newsEyebrow: string;
@@ -194,21 +194,17 @@ export interface PageCopy {
const EN: PageCopy = {
home: {
heroHeadline: "SHARP-ER Project Officially Launches in Libya",
heroHeadline: "Advancing Justice and Accountability, Led by Libyan Communities",
heroLead:
"Shared Action on Rule of Law Progress — Exit Route: a nationwide initiative supporting Libya’s civil society, local institutions, and communities in advancing justice, accountability, and good governance.",
heroCtaPrimary: "Expression of Interest",
"SHARP-ER — Shared Action on Rule of Law Progress, Exit Route — works with civil society organisations, municipalities, and community leaders across Libya to strengthen access to justice, accountability, and local governance. Funded by the European Union, running to December 2027.",
heroCtaPrimary: "See open calls",
heroCtaSecondary: "About the project",
stats: [
{ value: "3", label: "Consortium partners" },
{ value: "4", label: "Areas of work" },
{ value: "16", label: "Human security sub-projects" },
{ value: "25 mo", label: "Dec 2025 – Dec 2027" },
],
launchEyebrow: "30 June · Launch event",
launchHeading: "An important start for the project",
launchLead:
"The launch brought together representatives from the European Union Delegation, the Ministry of Youth, the Ministry of Local Governance, the Ministry of Women’s Affairs, civil society organisations, partners, and stakeholders from across Libya. The event marked the opening of the Expression of Interest and the launch of this website.",
approachEyebrow: "Nationwide, Libya",
/* The same sentence the About page heads its body with - one project, one
description of it. */
approachHeading: "A nationwide project, delivered locally",
approachLead:
"Local organisations lead the design and delivery of the work. SHARP-ER partners with Libyan civil society, municipalities, and community leaders — through funded sub-projects, training, and open dialogue between communities and the institutions that serve them.",
workHeading: "Three areas of change",
workLinkLabel: "What we do",
newsEyebrow: "Newsroom",
@@ -383,23 +379,17 @@ const EN: PageCopy = {
const AR: PageCopy = {
home: {
heroHeadline: "انطلاق مشروع SHARP-ER رسميًا في ليبيا",
heroHeadline: "تعزيز العدالة والمساءلة بقيادة المجتمعات الليبية",
heroLead:
"العمل المشترك لتعزيز سيادة القانون – مسار الخروج: مبادرة وطنية تدعم المجتمع المدني والمؤسسات المحلية والمجتمعات في ليبيا في تعزيز العدالة والمساءلة والحوكمة الرشيدة.",
heroCtaPrimary: "الدعوة للتعبير عن الاهتمام",
"يعمل SHARP-ER — العمل المشترك لتعزيز سيادة القانون، مسار الخروج — مع منظمات المجتمع المدني والبلديات وقادة المجتمعات في مختلف أنحاء ليبيا على تعزيز الوصول إلى العدالة والمساءلة والحوكمة المحلية. بتمويل من الاتحاد الأوروبي، ويستمر حتى ديسمبر 2027.",
heroCtaPrimary: "اطّلع على الدعوات المفتوحة",
heroCtaSecondary: "عن المشروع",
/* Western digits, as the rest of the Arabic draft writes its numbers
("ديسمبر 2025", "SHARP I and II (2019–2024)"). */
stats: [
{ value: "3", label: "شركاء الائتلاف" },
{ value: "4", label: "مجالات العمل" },
{ value: "16", label: "مشروعًا فرعيًا للأمن الإنساني" },
{ value: "25 شهرًا", label: "ديسمبر 2025 – ديسمبر 2027" },
],
launchEyebrow: "30 يونيو · فعالية الإطلاق",
launchHeading: "بداية مهمة للمشروع",
launchLead:
"جمعت فعالية الإطلاق ممثلين عن بعثة الاتحاد الأوروبي، ووزارة الشباب، ووزارة الحكم المحلي، ووزارة شؤون المرأة، ومنظمات المجتمع المدني، والشركاء، وأصحاب المصلحة من مختلف أنحاء ليبيا. وشهد الحدث افتتاح الدعوة للتعبير عن الاهتمام وإطلاق هذا الموقع الإلكتروني.",
/* The same two lines the About page uses for its coverage fact and its
body heading, so the Arabic reads identically on both pages. */
approachEyebrow: "على مستوى ليبيا بأكملها",
approachHeading: "مشروع وطني يُنفَّذ محليًا",
approachLead:
"تتولّى المنظمات المحلية تصميم العمل وتنفيذه. ويعمل SHARP-ER بالشراكة مع المجتمع المدني الليبي والبلديات وقادة المجتمعات، من خلال المشاريع الفرعية المموَّلة، والتدريب، والحوار المفتوح بين المجتمعات والمؤسسات التي تخدمها.",
workHeading: "ثلاثة مجالات للتغيير",
workLinkLabel: "ماذا نفعل",
newsEyebrow: "غرفة الأخبار",