How Signpost was built
Four real pages from savethefrogs.com — Ways To Support, Dr. Kerry Kriger, Art Contest 2024 and Save The Frogs Day — migrated off WordPress. The brief's rule, in the site owner's own priority order: (1) every piece of text and media arrives, (2) how nice it looks. There is deliberately no beauty clause in the gate.
The design direction
These pages are an arbitrary stack of heterogeneous sections whose order was chosen by someone else — a scroll-story cannot absorb that, a sectional system can. Signpost is interpretive nature-reserve signage: deep forest-green plates carrying cream signage type, cream panels of reading prose, chunky numbered wayfinders, slab headings sized to be read standing up. It suits SAVE THE FROGS! literally, and it makes the organisation's big-readable-text rule structural: body type never drops below 20px.
Type is Rokkitt (a Rockwell-derived slab — the wordmark's own genus, and the classic
interpretive-signage genus) with Nunito Sans for body — the faces the 2026-08-05 STF type
decision chose, reused from /journal/fonts/ with their
SIL Open Font License. No new font family was added.
Every colour is measured, not chosen: cream on plate 13.65:1, body ink on panel 10.43:1, links 8.64:1. The brand accents — green #72af43 and orange #f5821f — are fills only: as text on this cream they measure 2.31:1 and 2.26:1, so the orange link role becomes the darkened #ab5200 (4.64:1), the same variant the Field Guide established. White-on-green and white-on-orange stay pinned in the gate as expected failures.
The method: count the source before any mapper exists
The acceptance baseline was measured from the live WordPress REST API before the renderer was written, and committed: 9,243 words · 78 headings · 197 images · 1 PDF · 7 YouTube videos · 2 live Airtable embeds · 300 links. The build then maps every element by its rendered tag, never by its block name — Kadence's "Advanced Heading" block renders 176 of its 243 instances as plain paragraphs, so mapping by name would promote 176 paragraphs to headings or demote 62 real headings to prose.
The transformer refuses to write a page containing any element it does not recognise — an unmapped leaf is a build failure, never a silent skip. The only deliberate drops are the Kadence layout system itself and a related-posts widget carrying other posts' content, each recorded in an authored drop manifest that the gate reads. A manifest generated by the same mapper the renderer uses would be empty by construction and would pass while losing things.
The countdown, and the trap it proved
The live page's countdown block hydrates client-side from an attribute that is only visible with authenticated API access. That attribute's authored target is 2026-04-28T03:00:00 — a past date, carrying a time and no timezone. A migrated countdown silently inheriting a past date is the exact failure the brief predicted, so the rule fires here for real: one authored constant wins — the 19th Annual Save The Frogs Day, Wednesday April 28th, 2027 — and the disagreement is printed by the build, recorded in the manifest, and stated on this page. The countdown counts to the viewer's own local midnight of April 28, from explicit date components — never from a parsed date-only string, which every parser reads as UTC midnight and rolls back a day west of Greenwich.
Save The Frogs Day is April 28 every year. Only the page's single future-tense promise moved to 2027; the past-tense thank-you to 2026's participants, the 2026 events listing, video titles and the "as of March 2026" statistics stay exactly as authored.
The donation route survives — behind one click
The support page carries a live Chariot donor-advised-fund widget: a third-party script plus a custom element — neither text nor media, so a completeness gate counting images, videos and links would pass with it silently gone. It has its own gate clause, asserted by name, and its canary is deleting the widget and watching the build go red. Per the owner's ruling it is carried behind a click, the same facade shape as the YouTube posters: no third-party script on first paint, one tap between a donor and giving. The loaded tile was verified rendering in a real browser.
The seven YouTube embeds use the same pattern at parity with the source's own click-to-load module:
committed poster, real play button, youtube-nocookie iframe injected on demand, and a visible
Watch-on-YouTube link for crawlers and no-JS readers. The résumé PDF is committed and shown in a native
<iframe> (headers measured first: application/pdf, no
content-disposition, no frame block); on phones the frame hides — iOS renders only page one
of a framed PDF — and the link remains at every width.
What the migration surfaced on the live site 🔴
Content findings for the site owner, none of them changed upstream by this build:
- A dead video. The Save The Frogs Day page embeds
youtube.com/watch?v=FJsVMPfa0O8, which YouTube reports as removed (oembed 404, every thumbnail 404, measured 2026-08-23). The live page shows a click-to-play facade with a broken poster for a video that cannot play. The migrated page states the gap instead. - Three invisible Discord links. The same page contains three
<a href="…/discord"></a>anchors with no content — invisible and unclickable on the live page too. Dropped here and recorded; if a Discord link was intended, it needs link text. - One content photograph declared decorative. Kerry-Pichincha-scaled.jpg carries
alt=""in the source. This build drafts an alt from what the photograph plainly shows (markeddata-alt-status="draft"); the final wording is the owner's call. - A mixed-tense sentence — "will be celebrated worldwide on April 28th, 2026 and was a huge success!" — kept as authored (it is content, not markup).
- Nine stock images used as row backgrounds (Envato scenics and two finalist artworks duplicated as decoration) were dropped with the layout; both artworks arrive as real content images.
The gate
npm run verify:signpost — eleven clauses asserted per element class, not per page: the
197-image / 1-PDF / 7-video / 2-embed media ledger with an authored-manifest escape and no third state;
word counts within 2% of the source; all 78 headings with their text, in order, exactly one
<h1> per page; all 300 links present, absolutized, migrated or manifest-named — and a
refuse rule that no source-relative link may leak onto a host where every wrong path returns a soft 404;
the alt attribute on every image; the natural-width cap; per-surface contrast with the two accents pinned
as expected text-failures; the donation widget by name; the countdown constant with the past date refused;
and eighteen canaries that break each clause on purpose, run the gate as a child process, and require a
non-zero exit naming the clause that caught it.
Source: savethefrogs.com, harvested 2026-08-21 with declared-block census · Built 2026-08-23 · the full portfolio