Zero2Webmaster ✦ Cinematic Starter

One config file. A whole cinematic page.

The Cinematic Starter is the tooling distilled from hand-crafted showcase pages. Everything those builds derived from scratch (shader hero, scroll choreography, particle morph, big-type discipline, SEO baseline) is factored into a reusable engine. Producing a new brand's page is now: edit one JSON config → run one command → get a polished, crawlable, on-brand page.

There are now two layouts, selected by the config's "layout" field, because two page families had each shipped twice by hand:

Both layouts share the config envelope (SEO block, out, JSON-LD, section-array-is-page-order) and the colour helpers. They share nothing else, deliberately: a newspaper has no hero, no shader and no scroll choreography, so running one through the cinematic renderer would mean switching most of it off.

Proof of range: the four samples

All four pages below were rendered by the same engine from different configs. Within each layout the two samples share no palette, no fonts, no section list, and no signature set-piece. If they looked like siblings, the kit would have failed its one job.

Sample A — Ursa Observatory cinematic. Fictional community observatory. Dark midnight-indigo palette, Sora/Inter, WebGL aurora hero, and a 🌙 → wordmark particle morph. Sample B — Quill & Meadow cinematic. Fictional neighborhood bookshop. Warm paper palette, Fraunces/Newsreader serifs, ruled-paper hero, scroll-progress hairline, and a kinetic-typography set-piece. Sample C — The Willowbank Crier broadsheet. Fictional village weekly. Pale-straw newsprint, DM Serif Display / Source Serif 4 / Barlow Condensed, two accent tiers, three justified columns, a reversed drop cap, an edition banner, and a deterministic notice composer. Sample D — The Ashgrove Register broadsheet. Fictional parish quarterly — Sample C with every axis reversed. Off-white ground, Libre Caslon / Montserrat, one accent tier, two columns, a coloured first-letter cap, no banner, and no set-piece at all, so the page ships zero JavaScript.

All four sample brands are fictional demo brands, and say so on their pages. Their copy, figures and institutions are demonstration content, not real-world claims. The broadsheet samples take their structural axes from the two real papers but none of their content or colour, so nothing here can be mistaken for a real publication.

How to make a new page

  1. Pick a layout and copy its config: for a motion-led showcase, duplicate configs/sample-a.config.json (dark) or sample-b (light). For a newspaper, duplicate sample-c (three faces, two accent tiers, three columns) or sample-d (two faces, one tier, two columns). Save as configs/yourbrand.config.json.
  2. Fill it in: palette, fonts, copy, section list, set-piece, SEO block. Set "out" to the output folder name.
  3. Bake it: node public/cinematic-starter/build.mjs configs/yourbrand.config.json (or npm run build:cinematic to rebuild all). The output is one fully-static index.html — deployable anywhere. Broadsheet configs are additionally gated: npm run verify:broadsheet runs inside npm run build, so a drifted default or a failing contrast tier fails the build rather than shipping.
  4. Capture og.jpg from the live page at 1200×630 and drop it in the output folder.

The config schema

{
  "out": "sample-a",                  // output folder under cinematic-starter/
  "meta": {                           // SEO baseline — title/description/canonical,
    "title", "description", "url",    // OG + Twitter cards, favicon, Fathom site id
    "siteName", "fathomSiteId"
  },
  "brand": {
    "name", "wordmarkHtml",           // nav + footer wordmark (span.wm-accent = colored part)
    "mode": "dark" | "light",         // flips derived tints, glyph brightness, morph glow
    "palette": {                      // ALL page color comes from these 8 tokens
      "bg", "ink", "inkDim",
      "primary", "primaryBright", "primaryDeep",
      "accent", "accentSoft"          // accent = links + primary CTA
    },
    "fonts": {
      "display": { "family", "axis", "stack", "headingWeight" },
      "body":    { "family", "axis", "stack" }   // any Google Fonts pair
    }
  },
  "effects": { "grain": 0.045, "cursor": false, "ripple": false, "progress": false },
  "nav":     { "homeHref", "guideHref", "guideLabel", "guideShort" },
  "jsonLd":  { ... },                 // structured data, emitted verbatim
  "sections": [ ... ],                // array order = page order; "enabled": false to skip
  "footer":  { "lines": [ ... ] }
}

Pointer effects are opt-in, and that is a design rule

Two effects flags control mouse-following flourishes, and both default to false:

They default off because of what happened when they defaulted on. The ripple was written for /stf-technology/ — a page about a pond, where concentric rings on water are the subject — and because it arrived by default it then appeared on a piano page, a video-migration page and a web-studio page, where a water ripple depicts nothing at all. Kerry named it exactly: "It shouldn't be on pages unless it's relevant. Currently it's distracting and also a giveaway that that page was a 'copy' of another page."

That last clause is the real cost. A signature effect reused where it means nothing stops reading as craft and starts reading as a template, which is the opposite of what a showcase page is for. So the question to answer before setting either flag is not "would this look nice" but "does this depict something this page is actually about".

npm run verify:cinematic enforces it: it scans every built page and fails the build if one carries either flourish without being named in the allowlist in scripts/verify-cinematic.mjs. Turning an effect on is therefore a deliberate, reviewable edit rather than something a new page inherits by accident.

Copy fields are author-trusted HTML: <strong> for emphasis, <span class="accent"> in the headline, <span class="hl-a"> / <span class="hl-b"> for primary/accent highlights anywhere.

Section menu

TypeWhat it renders
heroFull-viewport opener: eyebrow, one <h1> (accent styled as gradient, italic, or color), sub, optional CTAs + note, backdrop module (aurora WebGL shader, paper ruled-light CSS, or none), scroll cue (droplet/beam/none).
statsCount-up numerals strip. Final values live in the HTML (no-JS safe); GSAP animates on first view.
manifestoGiant statement with word-by-word scroll-scrub reveal + follow-up paragraph.
marqueeLooping keyword strip (decorative, stops under reduced motion).
featuresAlternating editorial features: ghost numeral, self-drawing SVG visual (10 built-ins: orbits, constellation, scope, stack, press, path, window, chart, wave, shield — or raw svg), body, pill or link. A pill with "measured": true becomes the honest Navigation-Timing speed pill.
cards3-up card grid with glyph icons (10 built-ins), cursor spotlight + 3D tilt on fine pointers.
wowThe signature set-piece — see the module menu below.
closeClosing pitch + CTA row.

Wow-module menu

New modules drop into engine/wow/ as a file exporting html/css/js and one registry line — candidates from the recipe's technique menu: a data-viz orbit system, a WebAudio-reactive piece, a retro-play tile board.

The broadsheet layout

Set "layout": "broadsheet" and the config drives a print newspaper instead: a nameplate between double rules, ears, a dateline strip, justified multi-column stories with a drop cap, desk-labelled secondaries, classifieds, folio-headed inside pages, and a colophon. It exists because two newspapers had already been built by hand — the gate for turning a one-off into a template is two shipped builds, never one.

What the two hand-built papers actually disagreed about

This is the whole basis of the parameterization. Every row below is an axis in the config; every row not below is fixed in the engine.

AxisConfig field#7 Dispatch#8 Bulletin
Ground / tintbrand.paper.ground, .tintoff-white #F4E9E1pale straw #F1EFE2
Inkbrand.paper.inknavy #2E3660forest #1F2A16
Accent tiers.accentInk/.accentBright + optional .linkInk/.linkBrightone (maroon does kickers and links)two (green kickers, orange links)
Facesbrand.faces.display/body/furnitureLibre Caslon ×2 + MontserratDM Serif + Source Serif 4 + Barlow Condensed
Sheet widthbrand.metrics.sheetMax1360pxmin(1400px, 94vw)
Lead columnsbrand.metrics.leadColumns23
Drop capbrand.metrics.dropcapfirstLetter — a coloured ::first-letterreversed — a cream glyph on an accent block
Furniture tracking.kickerTracking, .folioTracking, .datelineTracking0.24em0.3em
Front-page ratiobrand.metrics.frontRatio2.15fr2.35fr
Rubric set + copyeach section's title / deskVitals · Diagnosis · Prescriptions · Case Notes · PrognosisDispatches · Field Notes · Almanac · Bulletin Board
Edition bannermasthead.editionBannerabsentpresent
Set-piecea wow panel in front.railcanvas ECG tracetypeset event notice

dropcap is the only axis that changes the emitted HTML rather than just CSS — a pseudo-element cannot carry a background block on a justified column, so a reversed cap needs a real element (with the letter repeated in a .sr-only span so a screen reader still hears "Since", not "S" then "ince").

The fixed half — what a config cannot touch

These are the things both papers agreed on without discussing, and they are what make a page read "newspaper" at thumbnail size before a single word is legible. An author can change the paper's identity completely and still cannot break its layout; that asymmetry is the point of the template.

Still by default

A broadsheet has no ambient motion of any kind — no shader, no particles, no GSAP, no scroll-triggered reveal, no ticking clock. Two screenshots taken seconds apart are identical. That stillness is the style, not an omission, and the verifier asserts it: zero @keyframes, zero animation/transition properties, zero timers in the page script.

The one thing that can change is the set-piece, and only in response to the reader. Nothing renders on load, so the live page stays byte-identical to its own static HTML until someone touches an input.

Set-piece: notice-composer

A small panel of inputs (select / text / range), a pure function from those inputs to typeset output, and no motion. Templates interpolate {field}, plus {a:field} for the indefinite article, {lower:field} / {upper:field}, and {lc:field} / {uc:field} for first-letter case. Empty text fields fall back to a declared fallback rather than going blank.

The drift problem is solved structurally. Both hand-built papers had to keep their static default markup in sync with what their JS renders for the default inputs — if it drifts, the page visibly rewrites itself the moment a reader acts, and a no-JS reader sees copy the composer would never produce. On both pages that match was checked by eye. Here the two pure functions are used at build time to render the static defaults and serialized into the page script from their own source, so there is no second implementation to keep in step. scripts/verify-broadsheet.mjs then re-derives the default output from the emitted page's own script and asserts it equals the emitted page's own markup.

The canvas-trace model from #7 is deliberately not a module: it is one build, not two.

Imagery — engravings, photographs, and the screen

A newspaper's pictures are cuts. Two kinds are supported, and a cut can sit inside a story or on its own plate page.

Rasters get screened. A full-colour photograph dropped onto newsprint reads as a website — real papers put it through a halftone dot screen first, so treatment does the same:

TreatmentWhat it doesUse it for
halftoneGrayscale, contrast lifted, then a dot screen multiplied over the image. The dots are a CSS radial-gradient — no asset.Any photograph in a story. The default choice.
duotoneThe paper's accent ink on the frame, the grayscaled image screened over it.A masthead cut or section opener, where a dot screen would fight the type.
noneNothing. The image as supplied.An image that is already screened, or a logo.

span picks the measure: column sets the cut inside the justified column (the caption sets to the column width), while full uses column-span: all to break the cut across every column of a multi-column story. Both take a caption (trusted HTML) and an optional credit.

Two details that were bugs first, in case you extend this: the treatment lives on an inner .cut-frame wrapper, not on the <figure> — on the figure, the dot screen covered the caption text. And mix-blend-mode on an <img> blends against the backdrop, not against the image's own background, so a duotone set directly on the image silently does nothing; the ink has to go on the wrapper.

See Plate I on Sample C for both raster treatments side by side from one source file, the full-width engraving in its lead story, and the column-width cut on Sample D.

The per-surface contrast gate

Every text tier is checked at build time against both the page ground and the tint panels, and a failure stops the build with the measured ratio. This exists because contrast is per-surface, not per-palette, and that fact already cost a shipped page: #8's orange link tier began as #ab5200, which measures 5.4:1 on the straw ground — comfortably passing — but only 4.2:1 on the tint panels it also sat on. It had to be darkened to #964800. A ground-only check would have passed it, and a human caught it with a screenshot audit. Now the build does. (The verifier feeds #ab5200 back through the gate on every run to prove the gate still rejects it.)

Other things that stop the build rather than becoming a page that looks fine and is quietly wrong: a reversed drop cap whose ground fails 3:1 on the accent block; an unknown section type or set-piece module; a notice template naming a field that was never declared; zero mastheads, or more than one.

Broadsheet section menu

TypeWhat it renders
mastheadRequired, exactly once. Ears row (incl. a pubmark kind for the publisher's emblem + motto), the one <h1> nameplate, standfirst, optional edition banner, dateline strip, closing rule. Rendered outside <main> — it is the paper's nameplate, not content.
frontThe front page: a lead story (kicker, headline, deck, byline, body blocks of kind p / callout / jumpline, plus a dotted-leader "inside this edition" index) beside a rail of panels — each either a box or a wow set-piece.
secondariesBelow-fold teasers, one column each, rule-separated: desk label, headline (optionally linked), teaser, "more" link.
classifiedThe rubric grid — services, contests, ways in. Boxed on the tint ground with a configurable column count; each ad has a desk, a tag, a title, an optional big figure, body and a link.
spreadAn inside page: folio, rubric kicker, optional feature headline, justified essay body, optional tint-panel sidebar.
closeThe closing page: big pull-quote, attribution, a standing statement between double rules, and a CTA row.
plateA full-width engraving band — the "Plate I" pattern. One or several cuts across the sheet, folio'd like any inside page. See imagery.
rule / ornamentFurniture: a double section rule, or a centred typographic mark.

What the engine guarantees on every page

Provenance

The cinematic layout, Samples A and B, and the first version of this guide were designed, coded, and iteration-tested in a single session by Claude Fable 5 (Anthropic's Mythos-class AI model), working from a one-page written brief — the same method that produced the two exemplar pages it generalizes.

The broadsheet layout, Samples C and D, scripts/verify-broadsheet.mjs, and this section were added in a later session by Claude Opus 5, factored from the two shipped newspapers (#7, #8) once the two-build gate was met. Both layouts are structured to port cleanly to a future Next.js <CinematicSite spec={...}> renderer.