Repository navigation
feat(templates): redesign the marketing template with a photo-led demo - #3969
Conversation
Replace the Acme placeholder with a photo-led demo for Halden, a fictional energy-intelligence startup: Instrument Serif display type over Inter, a warm neutral palette with an ink brand and one amber accent, a floating glass header, rounded near-full-bleed sheets, glass caption chips on photos, a logo marquee and a footer wordmark. Add four block types (logo wall, text and image, stats, call to action) and optional fields on the existing five (hero announcement and image caption, features eyebrow and nine icons, pricing badge, FAQ subheadline). Existing block contracts only gain optional fields, so re-seeding with --on-conflict update stays compatible. The seed downloads four Unsplash photos for the home page.
… the hero photo in The hero photo was invisible for the first 270ms of its entrance animation, which delayed largest contentful paint. Photos also used the default 100vw sizes: the hero and call to action under-fetched for their mobile crops and the half-width split photos over-fetched on desktop.
…rim intact on small screens Stat values kept a 56px floor in two columns and overflowed at 320px, long single words in display headings could widen the page, and the mobile CTA scrim sat in a selector list with :dir(), so browsers without :dir() dropped it and left white text on a clear photo.
The marquee loops indefinitely and only paused on hover, so keyboard and touch readers had no way to stop it. Logos now fade out before they reach the button.
…ng headline The seeded pricing page went from the hero h1 straight to h3 plan names.
…s with reduced motion The right-to-left mask rule outranked the reduced-motion reset, so the static logo row kept an uneven fade.
…ons block The contact page rendered its email cards from a hard-coded list, so editors could not change them in the admin. A new marketing_contact block holds the cards (icon, title, description and link), the contact page now renders only its blocks like home and pricing, and the seeded page drops the second heading in favour of a fuller hero subheadline.
…s from menus The header's Book a demo button and the footer's Product, Company and Support headings were hard-coded in the layout, so they could not be edited or translated. A primary menu item with the CSS class button now renders as the header button, and each footer column uses its menu's label. Both come from the menus Base already loads, so no queries are added.
The contact options link was a plain string, so a bare email address saved as a broken relative link. The url field type accepts http, https, mailto and tel links or a site-relative path, and repeater subfields support it, unlike validation patterns.
Every primary menu item with the button class stayed in the header, so a second one overflowed the bar at 320px. On narrow screens only the first stays and the rest move into the menu panel.
The admin menu editor doesn't edit CSS classes or menu labels, and re-applying the seed doesn't rename existing menus.
At 1024px and wider, the giant wordmark's text box rose over the legal row and covered the Light, Dark and System buttons, so the theme switcher couldn't be clicked.
…oto captions The amber dot on the announcement pill and eyebrows and the pulsing green dot on photo captions read as filler. Labels and captions are now plain text, the --color-live token goes with the dot, and the amber accent is left on the testimonial quote mark.
🦋 Changeset detectedLatest commit: 04054f1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 04054f1 | Oct 08 2026, 04:48 PM |
The marketing template uses each footer menu's label as its column heading and renders a primary menu item with the `button` class as a header button, but the admin's menu editor could change neither. A pencil button next to the menu title now renames the locale version being edited, and the add and edit item dialogs gain a CSS classes field, with each item's classes shown next to its URL. Both use the existing menu API routes.
The blocks field's root grid had one implicit auto column, so a long truncated header (a testimonial quote as a repeater item title, or a block summary) set its minimum width. The column grew past the editor, pushing Add block and each block's duplicate and delete buttons under the settings panel on desktop and off screen on phones. A minmax(0, 1fr) column keeps it inside, and long headers end in an ellipsis.
… from the CMS Only Home, Pricing and Contact had routes, so a page created or renamed in the admin returned 404 even after publishing. A [slug] route now renders any page through a shared PageView, which adds a title heading to pages without a hero and shows editors an empty-page hint with an edit link. Halden copy no longer hides in code: meta descriptions and share images come from the page's first hero, cleared taglines stay cleared, an empty site title falls back to "My Site", the title separator follows Settings > SEO, and Home shares under the site title. The smoke test previews Pricing, since Home's admin title no longer renders.
…ext block The admin labels a collapsed block with its first filled-in text field, so blocks read "platform" or "faq" instead of their headlines. Every block now lists headline first and anchor_id last, and since block fields have no help text, the labels carry it: "Section ID (lowercase, for links like /#pricing)", button text and link labels that name the accepted link formats, and notes on how fields render. Field order and labels aren't part of the block type contract, so sites seeded from main re-apply the seed cleanly. A new marketing_text block holds Portable Text for pages such as About or Privacy. The seed also sets the em dash title separator, so the admin shows the value the site uses.
The admin edits plain-text fields in a multi-line box, but FAQ answers, quotes and card descriptions rendered as one paragraph, so blank lines an editor typed were lost. They now split into paragraphs like the Text and image and call to action bodies. An emptied plan badge also stays empty instead of falling back to "Most popular"; plans stored before the badge field existed keep that fallback.
…rsed heroSeoDefaults passed the hero image's src and previewUrl to new URL() unguarded. Block image values written through the REST API, MCP or an import are plain strings, so a malformed one made every request for that page return 500. A value that can't be parsed now yields no share image, and the site's default og:image applies.
The announcement link, split link and CTA band button fields copied the hero buttons' pattern, which needs a character after the slash, so a link to the home page failed validation although the labels say links start with /. These fields are new, so their pattern can still change without a new block type version; once released, any pattern change except removal is breaking. The new pattern also rejects paths starting with /\, which browsers treat as protocol-relative. The hero's two button links exist on main and keep their pattern.
…:dir() Browsers without :dir() support (Chrome before 120) drop a whole selector list that contains it, so reduced-motion visitors kept the clipped single-line marquee. :is() takes a forgiving selector list, as the CTA block already does.
Astro decodes the path with decodeURI, which leaves reserved characters such as #, ?, & and + percent-encoded in params. decodeSlug decodes them before the lookup, like the starter and blog routes and the blocks guide.
There was a problem hiding this comment.
This is a well-scoped, substantial redesign of the marketing template plus the small admin affordances needed to make the new design fully editable. The approach is sound: it converts more hard-coded surface into blocks/menus/settings, keeps existing block types additive-only, preserves the logged-out query shape (the [slug] catch-all adds one content lookup on 404s as noted), and adds the missing menu-rename and CSS-classes UI through the existing menu API.
I reviewed the diff, the changed components, the seed schema, the page/query code, and the updated docs. The code is clean: fields are optional, unknown block data is handled, URLs are run through sanitizeHref(), light-dark() fallbacks are kept in sync, logical CSS properties are used, and the new admin strings are Lingui-wrapped. The BlocksField grid-cols-1 fix matches the regression test, and the MenuEditor tests cover rename/add/clear classes. Docs accurately describe the new blocks, routing, SEO fallbacks, and menu editor changes.
The only administrative note is that the PR template’s “New features link to an approved Discussion” checkbox is unchecked and no discussion URL is included. If this is maintainer-driven and already approved, that link is a template formality; otherwise it should be provided before merge.
…te-halden # Conflicts: # packages/admin/src/components/BlocksField.tsx # packages/admin/tests/components/BlocksField.layout.test.tsx
Main fixed the same implicit-column overflow in #4012 with an identical minmax(0, 1fr) column, and its blocks-field-phone-width changeset describes the fix. This branch no longer changes BlocksField, so its own changeset would add a duplicate CHANGELOG entry. The repeater layout test stays as extra coverage.
What does this PR do?
Redesigns the marketing template (
templates/marketing, synced totemplates/marketing-cloudflare) and replaces the Acme placeholder copy with a realistic demo company: Halden, a fictional energy-intelligence product for commercial buildings. The seed now downloads four Unsplash photos for the home page, so a new site starts with a finished, photo-led landing page instead of placeholder SaaS copy. Content that used to be hard-coded is now edited in the admin: every page, including contact, is built from blocks, and the header button and footer headings come from menus. To make those editable without the API, the admin's menu editor can now rename a menu and set a menu item's CSS classes. An editability audit then closed the remaining gaps: pages created in the admin get a URL, SEO text and share images come from the content, block fields are labelled for editors, and the blocks editor no longer overflows its column.Design
light-dark()tokens with the no-light-dark()fallback kept in sync.image_captionfield, a logo marquee, and a giant cropped serif wordmark at the bottom of the footer.animation-timeline: view()is supported), and the marquee. All of it switches off underprefers-reduced-motion: reduce.Base.astro(.sheet,.section,.section-headline,.eyebrow,.btn-*,.card,.media,.media-chip,.tickand friends), so new blocks can reuse them.Blocks
marketing_heroeyebrowandeyebrow_url(an announcement pill) andimage_caption. The photo runs below the text as a near full-width sheet. Without an image the hero is text only; the isometrichero-visual.svgfallback is removedmarketing_featureseyebrow; nine more icon keys (building,leaf,gauge,thermometer,plug,bell,file,sun,trend). The subheadline renders as a muted second sentence of the headlinemarketing_testimonialsmarketing_pricingbadge. A highlighted plan sits on the night panel with its badge; plans stored without a badge value keep "Most popular", and an emptied badge shows none. Plan names becomeh2when the block has no headlinemarketing_faqsubheadline, a two-column layout, and each block is its own exclusive accordionmarketing_logos(new)marketing_split(new)image_firstswaps the sides on desktopmarketing_stats(new)marketing_cta(new)marketing_contact(new)/contactnow renders only its blocks, like home and pricingmarketing_text(new)Every block lists
headlinefirst andanchor_idlast, because the admin labels a collapsed block with its first filled-in text field (it showed "platform" and "faq" before). Block fields have no help text in EmDash, so the labels carry it: "Section ID (lowercase, for links like /#pricing)", "Primary button link (starts with /, https://, mailto:, tel: or #)", "Second sentence (shown muted after the headline)" and so on. FAQ answers, quotes and card descriptions keep the paragraphs an editor types.Pages, SEO and site identity
src/pages/[slug].astrorenders every page except Home, replacingpricing.astroandcontact.astro(same URLs). Pages created or renamed in the admin used to return 404 even after publishing; now they render, and the automatic redirect from a renamed page's old URL lands on a working page. A page without a hero gets its title as theh1. A page with no blocks shows visitors its title and shows editors a hint with an Edit page link.Admin (
@emdash-cms/admin: a minor changeset for the menu editor)PUT /_emdash/api/menus/:nameandPUT /_emdash/api/menus/:name/items/:id), so the server is unchanged.Backward compatibility
mainwith--on-conflict updateupdates the five existing types in place and creates the six new ones, with noBLOCK_TYPE_VERSION_CONFLICT.--on-conflict skipalso applies cleanly.mainwith both--on-conflict skipandupdate; both completed without errors./and reject paths starting with/\, which browsers read as//. The hero's two button links keepmain's pattern, which needs a character after the slash, because changing an existing field's pattern is a breaking block type change.Other changes worth knowing about:
primarymenu item with the CSS classbuttonrenders as the header button (the seed's Book a demo), and each footer column uses its menu's label, so both can be edited in the admin's menu editor and translated per locale. They come from the menusBase.astroalready loads, so no queries are added./,/pricingand/contactrun the same queries as before. An unknown single-segment URL now looks the slug up before rendering the 404 page, one extra lookup on a miss, which is the cost of the catch-all route (the starter template has the same).#78716c(4.8:1 on white cards) and#8f8982in dark mode (5.2:1).sizesthat match their crops, and the hero photo isn't faded in, so it doesn't hold back largest contentful paint.AGENTS-template.mdand both generatedAGENTS.mdfiles,TEMPLATES.md, the README blurb and the block map indocs/.../themes/creating-themes.mdxdescribe the eleven blocks, page routing and the SEO fallbacks, the menus guide covers renaming and CSS classes, and the API reference listsgetContentSeoin the site-template API contract. The smoke test expects the new hero headlines.Type of change
Checklist
pnpm typecheckpasses (plusastro checkfortemplates/marketingandtemplates/marketing-cloudflare: 0 errors, 0 warnings)pnpm lintpasses (pnpm lint:json: 0 diagnostics)pnpm testpasses (or targeted tests for my change): the smoke suite for both marketing templates (-t "templates/marketing": 28 passed, including real photo downloads, re-seeding and boot checks), plustests/unit/seed/validate.test.ts,tests/integration/transfer/template-targets.test.tsandtests/integration/seed-portable-text-keys.test.ts(103 passed), and the admin'sMenuEditor,MenuListandBlocksFieldtests (40 passed)pnpm formathas been runminmax(0, 1fr)column, in both text directions); for the template, the existing seed and smoke tests cover the new seed. The smoke expectations use the new headlines, and the draft preview check now previews Pricing, because Home's admin title no longer renders@emdash-cms/admin; templates are not published packagesAI-generated code disclosure
Screenshots / test output
Seeded sample content on the local dev server. README screenshots in
assets/templates/marketing/latest/are regenerated.Desktop (1440px), first screen
Home, section by section (after)
Phone (390px)
Dark mode
Admin menu editor
Admin blocks editor (Home page)
The column fit in the after shot comes from #4012 on main. The field order, labels and headline summaries come from this PR.