Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ This repository contains the McCullough Digital block theme. The notes below sum
4. **Always** update `AGENTS.md`, `bug-report.md`, and `readme.txt` to reflect any bug fixes or improvements.

## Bug Fix & Improvement Highlights
-### Latest (2025-11-11) - Hero Media Accessibility Sweep
- **Responsive Hero Imagery:** Updated `blocks/hero/render.php` to prefer attachment IDs so WordPress emits responsive `srcset` markup, lazy-loading, and the saved alt text whenever editors provide it.
- **Static CTA Readability:** Removed `aria-hidden` from CTA fallbacks, toned down `.is-static` styling in both CSS bundles, and kept service card copy visible to assistive technology whenever no link is set.
- **Home Seeding Guard:** Adjusted `mcd_maybe_seed_home_page()` to skip trashed "Home" pages and continue hydrating the first live page (or create a new one) so pattern seeding never aborts early.
- **Docs Synced:** Raised the documented WordPress minimum to 5.9 and noted the accessibility fixes across `AGENTS.md`, `bug-report.md`, and `readme.txt`.
-### Latest (2025-11-10) - Blog Archive Loop Block
- **Dynamic Loop:** Added a `mccullough-digital/blog-archive-loop` server-rendered block that outputs the curated category pills, a standalone latest-post hero, the remaining grid, and pagination while respecting the active archive context.
- **Markup Cleanup:** Replaced the template-level Query Loop with the new block in `templates/index.html` and `templates/archive.html`, updated search/404 templates to the shared `.post-grid` class, and trimmed the post-card pattern so badges are exclusive to the featured hero.
Expand Down Expand Up @@ -209,7 +214,7 @@ canvas regardless of copy length.
- Ensured all WordPress API calls use correct signatures to prevent PHP warnings and maintain forward compatibility.

## Development Notes
- The theme requires WordPress 5.0+ and PHP 7.4+
- The theme requires WordPress 5.9+ and PHP 7.4+
- Build assets are tracked in `build/blocks/*/editor.js`
- Block registrations are handled automatically via `functions.php`
- Custom blocks support InnerBlocks for flexible content composition
Expand Down
2 changes: 1 addition & 1 deletion blocks/cta/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<?php
} else {
?>
<span class="cta-button is-static" aria-hidden="true">
<span class="cta-button is-static">
<span class="btn-text"><?php echo esc_html( $button_text ); ?></span>
</span>
<?php
Expand Down
92 changes: 86 additions & 6 deletions blocks/hero/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
);

// Image attributes
$hero_image_url = isset( $attributes['heroImageUrl'] ) ? esc_url( $attributes['heroImageUrl'] ) : '';
$hero_image_alt = isset( $attributes['heroImageAlt'] ) ? esc_attr( $attributes['heroImageAlt'] ) : '';
$hero_image_id = isset( $attributes['heroImageId'] ) ? intval( $attributes['heroImageId'] ) : 0;
$hero_image_url = isset( $attributes['heroImageUrl'] ) ? esc_url_raw( $attributes['heroImageUrl'] ) : '';
$hero_image_alt_input = isset( $attributes['heroImageAlt'] ) ? sanitize_text_field( $attributes['heroImageAlt'] ) : '';
$hero_image_width = isset( $attributes['heroImageWidth'] ) ? intval( $attributes['heroImageWidth'] ) : 0;
$image_position = isset( $attributes['imagePosition'] ) ? $attributes['imagePosition'] : 'bottom-right';
$image_size = isset( $attributes['imageSize'] ) ? intval( $attributes['imageSize'] ) : 40;
Expand All @@ -44,6 +45,17 @@
$image_horizontal_offset = isset( $attributes['imageHorizontalOffset'] ) ? intval( $attributes['imageHorizontalOffset'] ) : 0;
$hide_image_on_mobile = isset( $attributes['hideImageOnMobile'] ) && $attributes['hideImageOnMobile'] === true;

$hero_image_alt = $hero_image_alt_input;

if ( 0 !== $hero_image_id && '' === $hero_image_alt ) {
$attachment_alt = get_post_meta( $hero_image_id, '_wp_attachment_image_alt', true );
if ( is_string( $attachment_alt ) ) {
$hero_image_alt = sanitize_text_field( $attachment_alt );
}
}

$hero_image_alt = trim( $hero_image_alt );

$inner_content = trim( (string) $content );

if ( '' === $inner_content ) {
Expand Down Expand Up @@ -134,14 +146,82 @@
if ( ! empty( $transform_parts ) ) {
$image_styles[] = 'transform: ' . implode( ' ', $transform_parts ) . ';';
}
$image_style_attr = implode( ' ', $image_styles );
$image_style_attr = trim( implode( ' ', $image_styles ) );

$hero_image_markup = '';

if ( 0 !== $hero_image_id ) {
$image_attributes = array(
'class' => 'hero__decorative-image',
'loading' => 'lazy',
'alt' => $hero_image_alt,
);

if ( '' === $hero_image_alt ) {
$image_attributes['role'] = 'presentation';
}

$hero_image_markup = wp_get_attachment_image( $hero_image_id, 'full', false, $image_attributes );

if ( ! $hero_image_markup && '' !== $hero_image_url ) {
$fallback_attributes = $image_attributes;
$fallback_attributes['src'] = $hero_image_url;

$hero_image_markup = '<img';

foreach ( $fallback_attributes as $attr_name => $attr_value ) {
$escaped_value = 'src' === $attr_name ? esc_url( $attr_value ) : esc_attr( $attr_value );
$hero_image_markup .= sprintf( ' %s="%s"', esc_attr( $attr_name ), $escaped_value );
}

$hero_image_markup .= ' />';
}
} elseif ( '' !== $hero_image_url ) {
$attr_pairs = array(
'src' => $hero_image_url,
'class' => 'hero__decorative-image',
'loading' => 'lazy',
'alt' => $hero_image_alt,
);

if ( '' === $hero_image_alt ) {
$attr_pairs['role'] = 'presentation';
}

$hero_image_markup = '<img';

foreach ( $attr_pairs as $attr_name => $attr_value ) {
$escaped_value = 'src' === $attr_name ? esc_url( $attr_value ) : esc_attr( $attr_value );
$hero_image_markup .= sprintf( ' %s="%s"', esc_attr( $attr_name ), $escaped_value );
}

$hero_image_markup .= ' />';
}
Comment on lines +151 to +199

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for manually building the <img> tag is duplicated in two places: once as a fallback for wp_get_attachment_image(), and again for when only a URL is provided. This can be refactored to a single block to improve maintainability and reduce code duplication.

$hero_image_markup = '';
$manual_img_attrs  = null;

if ( 0 !== $hero_image_id ) {
    $image_attributes = array(
        'class'   => 'hero__decorative-image',
        'loading' => 'lazy',
        'alt'     => $hero_image_alt,
    );

    if ( '' === $hero_image_alt ) {
        $image_attributes['role'] = 'presentation';
    }

    $hero_image_markup = wp_get_attachment_image( $hero_image_id, 'full', false, $image_attributes );

    if ( ! $hero_image_markup && '' !== $hero_image_url ) {
        $manual_img_attrs = $image_attributes;
        $manual_img_attrs['src'] = $hero_image_url;
    }
} elseif ( '' !== $hero_image_url ) {
    $manual_img_attrs = array(
        'src'     => $hero_image_url,
        'class'   => 'hero__decorative-image',
        'loading' => 'lazy',
        'alt'     => $hero_image_alt,
    );

    if ( '' === $hero_image_alt ) {
        $manual_img_attrs['role'] = 'presentation';
    }
}

if ( is_array( $manual_img_attrs ) ) {
    $hero_image_markup = '<img';

    foreach ( $manual_img_attrs as $attr_name => $attr_value ) {
        $escaped_value     = 'src' === $attr_name ? esc_url( $attr_value ) : esc_attr( $attr_value );
        $hero_image_markup .= sprintf( ' %s="%s"', esc_attr( $attr_name ), $escaped_value );
    }

    $hero_image_markup .= ' />';
}


$image_container_attributes = array(
'class' => $image_container_class,
);

if ( '' !== $image_style_attr ) {
$image_container_attributes['style'] = $image_style_attr;
}

if ( '' === $hero_image_alt ) {
$image_container_attributes['aria-hidden'] = 'true';
}

$image_container_attr_string = '';

foreach ( $image_container_attributes as $attr_name => $attr_value ) {
$image_container_attr_string .= sprintf( ' %s="%s"', esc_attr( $attr_name ), esc_attr( $attr_value ) );
}
?>

<section <?php echo $wrapper_attributes; ?>>
<canvas class="hero__particle-canvas" aria-hidden="true" role="presentation"></canvas>
<?php if ( '' !== $hero_image_url ) : ?>
<div class="<?php echo esc_attr( $image_container_class ); ?>" aria-hidden="true" style="<?php echo esc_attr( $image_style_attr ); ?>">
<img src="<?php echo $hero_image_url; ?>" alt="<?php echo $hero_image_alt; ?>" class="hero__decorative-image" />
<?php if ( '' !== $hero_image_markup ) : ?>
<div<?php echo $image_container_attr_string; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Individually escaped above. ?>>
<?php echo $hero_image_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup escaped through wp_get_attachment_image or attribute assembly. ?>
</div>
<?php endif; ?>
<div class="hero-content">
Expand Down
2 changes: 1 addition & 1 deletion blocks/service-card/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
<?php
} else {
?>
<span class="learn-more is-static" aria-hidden="true">
<span class="learn-more is-static">
<?php echo esc_html( $link_text ); ?>
</span>
<?php
Expand Down
3 changes: 2 additions & 1 deletion blocks/services/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ h2.section-title::after {
cursor: default;
text-shadow: none;
transform: none;
opacity: 0.75;
opacity: 1;
color: var(--text-secondary);
}

@media (prefers-reduced-motion: reduce) {
Expand Down
21 changes: 21 additions & 0 deletions bug-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ This report tracks all production-impacting fixes and continuous improvements in

## Fixed Bugs

### 2025-11-11 Sweep
1. **Hero Imagery Uses Attachment Metadata**
*Files:* `blocks/hero/render.php`, `AGENTS.md`, `bug-report.md`, `readme.txt`
*Issue:* The hero image wrapper was always flagged `aria-hidden`, so descriptive alt text never surfaced, and the render callback ignored the stored attachment ID, preventing responsive sources or lazy-loading from being output.
*Resolution:* Reworked the render logic to prefer `heroImageId`, fetch attachment alt text when authors leave the custom field empty, and emit markup via `wp_get_attachment_image()` (falling back to sanitized `<img>` output) while only hiding the container when the image is decorative.

2. **Static CTA Copy Remains Visible**
*Files:* `blocks/cta/render.php`, `blocks/service-card/render.php`, `blocks/services/style.css`, `style.css`, `editor-style.css`, `AGENTS.md`, `bug-report.md`, `readme.txt`
*Issue:* When no URL was provided, CTA and service card fallbacks rendered inside spans marked `aria-hidden` and retained neon button styling, leaving assistive-technology users with no call-to-action context while sighted users saw something that still looked clickable.
*Resolution:* Removed the hidden attribute, introduced neutral `.is-static` styling in both CSS bundles, and ensured fallback text stays accessible without imitating a link when no destination exists.

3. **Home Seeding Skips Trashed Pages**
*Files:* `functions.php`, `AGENTS.md`, `bug-report.md`, `readme.txt`
*Issue:* The activation routine bailed out as soon as it encountered a trashed page titled "Home", preventing the landing pattern from ever being seeded for live content.
*Resolution:* Filtered the lookup to non-trashed statuses, skipped trashed results from `get_page_by_path`, and continued searching so the first active page receives the seeded layout or a fresh page is created.

4. **WordPress Minimum Clarified**
*Files:* `readme.txt`, `AGENTS.md`, `style.css`, `bug-report.md`
*Issue:* Documentation still claimed WordPress 5.0 compatibility even though the theme depends on block features introduced in 5.9, and the version header lagged the latest sweep.
*Resolution:* Raised the documented minimum to 5.9 across project docs and bumped the theme version to 1.2.40 to reflect the accessibility and reliability fixes.

### 2025-11-09 Sweep
1. **Blog Hero Glitch Parity**
*Files:* `js/header-scripts.js`, `style.css`, `editor-style.css`, `AGENTS.md`, `bug-report.md`, `readme.txt`
Expand Down
35 changes: 35 additions & 0 deletions editor-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@
transition: letter-spacing 0.35s ease, text-shadow 0.35s ease, transform 0.35s ease;
}

.editor-styles-wrapper .cta-button.is-static:not(.hero__cta-button),
.editor-styles-wrapper .wp-block-button__link.cta-button.is-static:not(.hero__cta-button) {
pointer-events: none;
cursor: default;
transform: none;
background: none;
box-shadow: none;
color: var(--text-secondary);
text-decoration: none;
padding: 0;
transition: none;
}

.editor-styles-wrapper .cta-button.is-static:not(.hero__cta-button) .btn-text,
.editor-styles-wrapper .wp-block-button__link.cta-button.is-static:not(.hero__cta-button) .btn-text {
letter-spacing: normal;
text-shadow: none;
}

.editor-styles-wrapper .cta-button:not(.hero__cta-button)::before,
.editor-styles-wrapper .wp-block-button__link.cta-button:not(.hero__cta-button)::before {
content: '';
Expand All @@ -90,6 +109,13 @@
z-index: 0;
}

.editor-styles-wrapper .cta-button.is-static:not(.hero__cta-button)::before,
.editor-styles-wrapper .wp-block-button__link.cta-button.is-static:not(.hero__cta-button)::before,
.editor-styles-wrapper .cta-button.is-static:not(.hero__cta-button)::after,
.editor-styles-wrapper .wp-block-button__link.cta-button.is-static:not(.hero__cta-button)::after {
display: none;
}

.editor-styles-wrapper .cta-button:not(.hero__cta-button)::after,
.editor-styles-wrapper .wp-block-button__link.cta-button:not(.hero__cta-button)::after {
content: '';
Expand Down Expand Up @@ -142,6 +168,15 @@
text-decoration: none;
}

.editor-styles-wrapper .learn-more.is-static,
.editor-styles-wrapper .wp-block-button__link.learn-more.is-static {
color: var(--text-secondary);
pointer-events: none;
cursor: default;
text-shadow: none;
transform: none;
}

.editor-styles-wrapper .container {
width: 90%;
max-width: 1200px;
Expand Down
18 changes: 10 additions & 8 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,26 +154,32 @@ function mcd_maybe_seed_home_page() {
if ( $front_page_id ) {
$front_page = get_post( $front_page_id );

if ( $front_page && 'page' === $front_page->post_type ) {
if ( $front_page && 'page' === $front_page->post_type && 'trash' !== $front_page->post_status ) {
$page = $front_page;
}
}

if ( ! $page ) {
$page = get_page_by_path( 'home' );

if ( $page && 'trash' === $page->post_status ) {
$page = null;
}
}

if ( ! $page ) {
$pages = get_posts(
[
'post_type' => 'page',
'title' => __( 'Home', 'mccullough-digital' ),
'post_status' => 'all',
'post_status' => [ 'publish', 'pending', 'draft', 'future', 'private' ],
'numberposts' => 1,
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'orderby' => 'post_date ID',
'order' => 'ASC',
'orderby' => [
'post_date' => 'ASC',
'ID' => 'ASC',
],
]
);
if ( ! empty( $pages ) ) {
Expand All @@ -182,10 +188,6 @@ function mcd_maybe_seed_home_page() {
}

if ( $page ) {
if ( 'trash' === $page->post_status ) {
return;
}

$existing_content = trim( (string) $page->post_content );

if ( '' === wp_strip_all_tags( $existing_content ) ) {
Expand Down
8 changes: 6 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== McCullough Digital ===
Contributors: McCullough Digital
Requires at least: 5.0
Requires at least: 5.9
Tested up to: 6.9
Requires PHP: 7.4
License: GNU General Public License v2 or later
Expand Down Expand Up @@ -33,7 +33,11 @@ This theme does not have any widget areas registered by default.

== Changelog ==

= 1.2.39 - Unreleased =
= 1.2.40 - Unreleased =
* **Hero Image Accessibility:** Let the hero image container expose descriptive alt text, pull attachment metadata when available, and render via `wp_get_attachment_image()` so responsive sources and lazy-loading are applied automatically.
* **Static CTA States:** Keep CTA and service card copy visible to assistive technology and restyle static variants as neutral text so empty links are no longer implied when no URL is stored.
* **Home Pattern Seeding:** Ignore trashed "Home" pages while seeding the landing layout to ensure a published page always receives the starter content.
* **WordPress Requirement Bump:** Raised the documented minimum WordPress version to 5.9 to match the block theme features the codebase already relies on.
* **Header Logo Clamp:** Scoped the masthead logo styling to outrank WordPress core selectors and added max constraints so oversize uploads respect `--logo-size-header` without inflating the fixed header or Site Editor preview.
* **Neon Blog Archive Template:** Rebuilt the archive and index templates around a radial hero, live search, pill-style category filters, and a featured post grid with matching editor styles so the blog listing mirrors the new mockup without duplicating markup in patterns.
* **Blog Hero Glitch Parity:** Extended the header enhancement script and blog hero styles so the archive title now splits into interactive glitch letters with proper reduced-motion fallbacks, matching the front-page hero treatment.
Expand Down
15 changes: 14 additions & 1 deletion style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Theme URI: https://mccullough.digital/
Author: McCullough Digital
Author URI: https://mccullough.digital/
Description: Custom theme scaffold with fixed header, mobile menu, and simple template hierarchy.
Version: 1.2.39
Version: 1.2.40
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: mccullough-digital
Expand Down Expand Up @@ -399,6 +399,12 @@ main.site-content { padding-top: var(--mcd-header-offset, var(--header-height));
pointer-events: none;
cursor: default;
transform: none;
background: none;
box-shadow: none;
color: var(--text-secondary);
text-decoration: none;
padding: 0;
transition: none;
}

.cta-button.is-static:not(.hero__cta-button)::before,
Expand All @@ -413,6 +419,13 @@ main.site-content { padding-top: var(--mcd-header-offset, var(--header-height));
display: none;
}

.cta-button.is-static:not(.hero__cta-button) .btn-text,
.wp-block-button__link.cta-button.is-static:not(.hero__cta-button) .btn-text,
.post-card .wp-block-read-more a.is-static .btn-text {
text-shadow: none;
letter-spacing: normal;
}

@keyframes star-drift-slow {
from { background-position: 0 0; }
to { background-position: -2200px 1600px; }
Expand Down