Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,4 @@ $RECYCLE.BIN/
.cache/
.aider*
.continue/
.playwright-mcp/
60 changes: 60 additions & 0 deletions assets/css/newsletter-signup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* A hand-typed border width+style+color+radius block-style attribute
* doesn't byte-match WordPress's own style-engine serialization (see
* patterns/newsletter-signup.php's header comment) — real CSS instead,
* same fix already used for republish-notice.
*/
.newsletter-signup {
border: 1px solid var(--wp--preset--color--neutral-300);
border-radius: var(--wp--preset--border-radius--200);
}

/*
* newsletter-signup-compact.php embeds the site's real Gravity Form
* rather than static markup, so its input/button come from Gravity
* Forms' own generated HTML, not core/button — restyled directly here
* instead of rebuilt from scratch, the same CSS-bridge approach already
* used for the CC Post Republisher plugin's button in
* republish-notice.css. The field's visible label is screen-reader-only
* since the field's placeholder text (set directly in the form, in
* wp-admin) already communicates its purpose visually, matching Figma.
*/
.newsletter-signup-compact .gfield_label {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}

.newsletter-signup-compact .ginput_container input[type="email"],
.newsletter-signup-compact .ginput_container input[type="text"] {
width: 100%;
background-color: var(--wp--preset--color--base);
border: 1px solid var(--wp--preset--color--neutral-400);
border-radius: var(--wp--preset--border-radius--200);
padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
font-family: var(--wp--preset--font-family--body);
font-size: var(--wp--preset--font-size--200);
}

.newsletter-signup-compact .gform_footer input.gform_button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
background-color: var(--wp--preset--color--accent-200);
color: var(--wp--preset--color--neutral-100);
border: none;
border-radius: var(--wp--preset--border-radius--500);
padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--30);
font-family: var(--wp--preset--font-family--body);
font-size: var(--wp--preset--font-size--300);
font-weight: var(--wp--custom--font-weight--semi-bold);
cursor: pointer;
}

.newsletter-signup-compact .gform_footer input.gform_button:hover {
background-color: var(--wp--preset--color--accent-300);
}
56 changes: 56 additions & 0 deletions assets/css/republish-notice.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* The "CC" icon circle needs a fixed size core/group can't set via any
* block attribute — real CSS for size/centering. Border is CSS too, not
* a hand-typed inline-style block attribute — that combination (all
* four border sub-properties at once) triggered a "Block contains
* unexpected or invalid content" editor error, since it didn't match
* WordPress's own generated serialization.
*/
.republish-notice__icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5em;
height: 2.5em;
flex-shrink: 0;
border: var(--wp--custom--border-width--base) solid var(--wp--preset--color--neutral-700);
border-radius: var(--wp--preset--border-radius--500);
}

/*
* The plugin's button isn't a core/button, so
* styles/blocks/button/republish-article.json's is-style-republish-article
* class can't reach it — same values, targeted directly instead. The
* icon is the same arrow custom-button.css already uses for that style.
*/
.republish-notice #cc-post-republisher-modal-button-open {
display: inline-flex;
align-items: center;
background-color: var(--wp--preset--color--accent-500);
color: var(--wp--preset--color--base);
border: none;
border-radius: var(--wp--preset--border-radius--200);
padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);
font-size: var(--wp--preset--font-size--200);
font-weight: var(--wp--custom--font-weight--semi-bold);
cursor: pointer;
}

.republish-notice #cc-post-republisher-modal-button-open:hover {
background-color: var(--wp--preset--color--accent-600);
}

.republish-notice #cc-post-republisher-modal-button-open::before {
content: "";
display: inline-block;
width: 1.25em;
height: 1.25em;
margin-inline-end: var(--wp--preset--spacing--5);
background-color: currentcolor;
mask-image: url("../icons/arrow-line-up-right.svg");
mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-image: url("../icons/arrow-line-up-right.svg");
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: contain;
}
23 changes: 23 additions & 0 deletions assets/css/story-card.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,17 @@
* image's top-left corner, inset by story-card__media's own padding.
* core/group has no "position" style attribute, so this needs real CSS —
* .story-card__media is the positioned ancestor every variant shares.
*
* overflow:hidden also contains a broken/missing image to this box: a
* failed image falls back to its raw width/height HTML attributes
* (often large, oversized crops), and with overflow:visible up the
* ancestor chain that box bleeds straight through the rest of the
* page — not just a local-dev demo-content risk, any deleted/failed
* media file on staging or live would do the same without this.
*/
.story-card__media {
position: relative;
overflow: hidden;
}

.story-card__badge-overlay {
Expand All @@ -54,3 +62,18 @@
left: var(--wp--preset--spacing--50);
z-index: 1;
}

/*
* CSS Grid's default align-items:stretch makes every card in a
* post-template row match the row's tallest card (e.g. one with a much
* longer excerpt) — with no block attribute to opt a grid item out of
* that (same gap as columnStart/rowSpan, confirmed in tasks.md task
* 2.9). The stretch cascades into story-card__media's aspect-ratio
* figure, blowing its image out to the row's full height instead of
* its own 3/2 ratio. Each card sizes to its own content instead.
*/
.story-card,
.story-card-featured,
.story-card-editorial {
align-self: start;
}
38 changes: 38 additions & 0 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,24 @@ function spotlight_theme_2026_hide_duplicate_card_image_link( $block_content, $b
}
add_filter( 'render_block', 'spotlight_theme_2026_hide_duplicate_card_image_link', 10, 2 );

/**
* Suppresses the CC Post Republisher plugin's own `cc/post-republisher`
* block wherever editors have inserted it directly into post content.
*
* `patterns/republish-notice.php` already renders the plugin's button/modal
* markup with the site's own design, appended once per single.html via
* `wp:pattern`. Editors are used to placing the plugin's default block
* inline in the article body too, which duplicates the button (and its
* element IDs) on the page. The pattern doesn't use this block, so
* suppressing it here only ever removes the redundant inline copy.
*
* @return string
*/
function spotlight_theme_2026_suppress_inline_republish_block() {
return '';
}
add_filter( 'render_block_cc/post-republisher', 'spotlight_theme_2026_suppress_inline_republish_block' );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

/**
* Returns a cache-busting version string for a theme file.
*
Expand Down Expand Up @@ -420,6 +438,26 @@ function spotlight_theme_2026_enqueue_assets() {
spotlight_theme_2026_asset_version( 'assets/css/pagination.css' )
);

// cc-post-republisher-style is only ever auto-enqueued by WordPress when
// a real cc/post-republisher block is detected in the content; our
// pattern embeds the plugin's markup as plain HTML instead (see
// patterns/republish-notice.php), so it never gets picked up on its
// own. Declaring it as a dependency here enqueues the plugin's own
// modal styling — already registered by the plugin — ahead of ours.
wp_enqueue_style(
'spotlight-theme-2026-republish-notice',
get_theme_file_uri( 'assets/css/republish-notice.css' ),
array( 'cc-post-republisher-style' ),
spotlight_theme_2026_asset_version( 'assets/css/republish-notice.css' )
);

wp_enqueue_style(
'spotlight-theme-2026-newsletter-signup',
get_theme_file_uri( 'assets/css/newsletter-signup.css' ),
array(),
spotlight_theme_2026_asset_version( 'assets/css/newsletter-signup.css' )
);

// Add wp_enqueue_script() here when assets/js/main.js exists.
}
add_action( 'enqueue_block_assets', 'spotlight_theme_2026_enqueue_assets' );
2 changes: 2 additions & 0 deletions openspec/changes/spotlight-pattern-library/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,5 @@ Additive per PR — no existing template/part is removed; each PR adds pattern f
- Will the dynamic latest-post-swap and province-count-grid map variants ever move from Figma exploration to "Ready for Dev"? Not tracked by this change; a future change would define and build them if/when confirmed.
- Posts with more than one category break `spotlight-badge`/`story-card`'s plain category label, since `core/post-terms` always renders every assigned term joined by a separator, with no "primary category" concept. Options discussed with the user (take-first-term via a render filter, a manual primary-category field, or an editorial single-category policy) — to be decided with Zared; not yet implemented.
- Special Projects and Perspectives showed identical posts in the same order during task 2.5 testing — possibly the tag-filtering isn't distinguishing them, or the posts genuinely share both tags. Flagged to the user, not yet investigated further.
- `topic-band.php`'s front-page "Explore by topic" tiles use `spacing|50`/`spacing|20` padding, both in the range affected by the `defaultSpacingSizes:false` fix (2026-08-24) — the same class of bug already found and fixed in `topic-band-compact.php` (sidebar) and `trust-bar.html`. Left unverified here: the Figma MCP connection hit a hard View-seat rate limit before this file could be checked against a fresh pull. Deferred to a later PR rather than guessed at.
- `newsletter-signup`'s form integration is resolved for one size variant and open for the other. The sidebar/compact variant embeds the site's real Gravity Form (ID 1, "Sign up to get stories that matter in your inbox," already wired to Mailchimp) — confirmed as the correct, working approach, not a placeholder. The front-page variant's "Subscribe for the latest news" button still has no confirmed destination (submitting its own copy of the form vs. linking to the sidebar form or a dedicated page) — to be resolved with Zared before this button is wired to anything real.
20 changes: 11 additions & 9 deletions openspec/changes/spotlight-pattern-library/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@

## 3. Trust, newsletter, and editorial-utility patterns (LS-1721 / PR 3)

- [ ] 3.1 Create `patterns/newsletter-signup.php` (front-page size) and `patterns/newsletter-signup-compact.php` (sidebar size) — satisfying the `newsletter-signup` requirement's two size variants
- [ ] 3.2 Create `patterns/republish-notice.php`, including the CC BY-ND 4.0 attribution copy confirmed in the Figma client annotation — satisfying the `republish-notice` requirement
- [ ] 3.3 Confirm `parts/trust-bar.html` remains an existing template part, not converted to a pattern
- [ ] 3.4 Update `templates/front-page.html`'s `front-page__provincial-newsletter` section to reference `newsletter-signup` via `wp:pattern`
- [ ] 3.5 Update `parts/sidebar-editorial.html`'s newsletter module to reference `newsletter-signup-compact` via `wp:pattern`
- [ ] 3.6 Update `templates/single.html`'s republish section to reference `republish-notice` via `wp:pattern`, replacing `[Republish pattern placeholder]`
- [ ] 3.7 Tighten `parts/sidebar-editorial.html` spacing, borders, and Explore-topics icon treatment to match the Figma "Single Blog Post" sidebar exactly
- [ ] 3.8 Note any dependencies on form-integration decisions (e.g. real newsletter subscription handling vs. static markup) in design.md
- [ ] 3.9 Run `npm run schema:validate` and `npm run theme:validate`; manually verify in the Site Editor and front end
- [x] 3.1 Create `patterns/newsletter-signup.php` (front-page size) and `patterns/newsletter-signup-compact.php` (sidebar size) — satisfying the `newsletter-signup` requirement's two size variants. Front-page variant is static placeholder content (3 hardcoded issues) — this site has real "Edition"/"Spotlight Edition" categories that could back a real query later, deferred pending Zared's feedback on whether it's needed. Sidebar variant embeds the site's real Gravity Form (ID 1, already wired to Mailchimp) rather than static markup, restyled via `assets/css/newsletter-signup.css` to match the design (same CSS-bridge approach as the CC Post Republisher plugin's button in republish-notice.css) — field placeholder text and button label ("Subscribe") set directly in the form via wp-admin. Found and fixed the same "invalid content" bug as republish-notice.php in newsletter-signup.php's outer card: a hand-typed border width+style+color+radius block-style attribute doesn't byte-match WordPress's own serialization; moved to CSS. The front-page button's real destination (form submission vs. linking elsewhere) is unresolved, pending discussion with Zared.
- [x] 3.2 Create `patterns/republish-notice.php`, including the CC BY-ND 4.0 attribution copy confirmed in the Figma client annotation — satisfying the `republish-notice` requirement. The visible "Republish article" button and its modal match the site's own CC Post Republisher plugin's expected markup, so its existing modal JS actually works — the plugin only ever renders that button/modal, so the card, icon, heading, and body copy around it are authored directly. Written as plain HTML wrapped in `wp:html` (not a `wp:cc/post-republisher` block reference): that block's `save()` returns real content, and a self-closing block reference stores none, which broke both the front-end button and editor validation — the plugin's modal JS only looks for the element IDs, not a real block instance, so plain HTML works identically. Card has no background — confirmed unnecessary via live testing. Button styling reuses `styles/blocks/button/republish-article.json`'s values in `assets/css/republish-notice.css`, since the plugin's button isn't a `core/button`.
- Confirmed working end-to-end via Playwright (2026-08-24) after fixing three stacked environment issues, all local-dev/config, not pattern code: Perfmatters was serving the plugin's `modal.js` over an untrusted local `https://` cert (button did nothing — deactivated Perfmatters locally); Classic Editor was forced as the default editor, triggering the plugin's own classic-editor fallback to auto-inject a second button into every post body (deactivated Classic Editor — flagged to Zared since he'd previously asked to keep it on); the plugin's modal CSS only auto-enqueues alongside a real registered block, so it never loaded for our plain-HTML button (fixed by declaring it as a dependency in `functions.php`). Added a defensive `render_block_cc/post-republisher` filter too, in case an editor ever inserts the real block directly.
- Spacing-vs-Figma check (2026-08-24) surfaced a sitewide bug, not scoped to this pattern: `theme.json`'s custom spacing sizes reuse slugs ("20"–"80") that collide with WordPress core's own built-in default spacing scale, and without `defaultSpacingSizes:false`, core's identically-slugged flat values were winning over our fluid `clamp()` ones everywhere — this pattern's padding included. Fixed by adding `"defaultSpacingSizes":false` to `theme.json`; confirmed fluid spacing now resolves correctly here and spot-checked elsewhere (home.html cards, topic-band) with no regressions.
- [x] 3.3 Confirm `parts/trust-bar.html` remains an existing template part, not converted to a pattern — confirmed unchanged. Its spacing needed re-checking against a fresh Figma pull ("Stats Bar" component) after today's theme.json spacing-scale fix: the outer bar's padding was wrong on all four sides (should be vertical-only, spacing|30, with horizontal bounds from wide alignment, not manual padding), each item was missing vertical padding and an explicit icon-to-text gap (both spacing|10), and the heading/description text pairing was relying on the sitewide default gap instead of Figma's explicit spacing|5 — now fixed, plus the divider color corrected from neutral-100 to base to match the design exactly.
- [x] 3.4 Update `templates/front-page.html`'s `front-page__provincial-newsletter` section to reference `newsletter-signup` via `wp:pattern` — replaced the whole existing heading+placeholder group, since the pattern bakes in its own heading (same reasoning as `topic-band-compact.php`)
- [x] 3.5 Update `parts/sidebar-editorial.html`'s newsletter module to reference `newsletter-signup-compact` via `wp:pattern` — same whole-group replacement; confirmed live in the sidebar with the real Gravity Form rendering and styled correctly
- [x] 3.6 Update `templates/single.html`'s republish section to reference `republish-notice` via `wp:pattern`, replacing `[Republish pattern placeholder]`
- [x] 3.7 Tighten `parts/sidebar-editorial.html` spacing, borders, and Explore-topics icon treatment to match the Figma "Single Blog Post" sidebar exactly. Icon/text typography (topic-band.css) already matched Figma correctly, nothing to change there. `topic-band-compact.php`'s tile padding was genuinely wrong (spacing|40 instead of Figma's spacing|20, confirmed against a fresh pull of the sidebar's "Topic Sidebar" component) — not something today's theme.json fix caused, but the fix made it newly visible at its real (larger) size. Also removed the heading's redundant padding-bottom, since the outer wrapper's own blockGap already provides the correct single gap per Figma.
- [x] 3.8 Note any dependencies on form-integration decisions (e.g. real newsletter subscription handling vs. static markup) in design.md — sidebar variant's real Gravity Form embed is confirmed correct; front-page variant's button destination is still open, pending Zared
- [x] 3.9 Run `npm run schema:validate` and `npm run theme:validate`; manually verify in the Site Editor and front end — both pass; all PR 3 patterns and template wiring confirmed live via Playwright throughout today's session

## 4. Related-coverage and onward-journey patterns (LS-1722 / PR 4)

Expand Down
24 changes: 1 addition & 23 deletions parts/sidebar-editorial.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,7 @@ <h3>Track the HIV &amp; TB response</h3>
</div>
<!-- /wp:group -->

<!-- wp:group {"className":"sidebar-editorial__module sidebar-editorial__newsletter","backgroundColor":"neutral-200","style":{"spacing":{"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|30","left":"var:preset|spacing|30","right":"var:preset|spacing|30"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group sidebar-editorial__module sidebar-editorial__newsletter has-neutral-200-background-color has-background" style="padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)">
<!-- wp:heading {"level":3,"fontSize":"300"} -->
<h3 class="has-300-font-size">Stories that matter, in your inbox</h3>
<!-- /wp:heading -->

<!-- wp:paragraph {"fontSize":"100"} -->
<p class="has-100-font-size">Spotlight's free newsletter delivers in-depth health journalism — no spam, unsubscribe anytime.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>[Newsletter signup form placeholder]</p>
<!-- /wp:paragraph -->

<!-- wp:buttons -->
<div class="wp-block-buttons">
<!-- wp:button {"className":"is-style-secondary"} -->
<div class="wp-block-button is-style-secondary"><a class="wp-block-button__link wp-element-button" href="#">Subscribe</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->
<!-- wp:pattern {"slug":"spotlight-theme-2026/newsletter-signup-compact"} /-->

</aside>
<!-- /wp:group -->
Loading
Loading