Files
CWC/docs/superpowers/plans/2026-07-05-blog-post-card.md

12 KiB

Blog Post Card Styling Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Restyle the blog post card to match blog-post-card.png — a vertical card with a featured image on top and a solid blue content area below containing the title, byline, and an orange "Read more" link.

Architecture: A targeted CSS update to styles/components/post-list.css (the card body, title, byline, image border, and a new .post-list__read-more rule), a one-line markup addition in index.php to render the "Read more" anchor, and a single new assertion in the existing tests/blog-page.spec.js. The dist CSS is rebuilt once at the end.

Tech Stack: WordPress 6.x, PHP 8.x, Tailwind CSS v4, Playwright, axe-core, PHPCS (WordPress standard).

Global Constraints

  • Tabs for PHP indentation (project standard; see composer.json and existing files).
  • The theme's Tailwind build outputs static/dist/theme.css; that file is gitignored but committed with git add -f per project convention.
  • PHPCS uses the WordPress coding standard; composer lint must pass. Pre-existing CRLF errors on untouched files are out of scope.
  • All design colors use the project's existing CSS custom properties from styles/base/colors.css. No hardcoded hex values for theme colors.
  • The card width behavior stays the same: grid-cols-[repeat(auto-fit,minmax(20rem,1fr))]. No layout refactor.
  • The "Read more" link reuses the post's permalink — it's the same destination as the title link.
  • The H1 page title and search.php are explicitly out of scope for this plan.

File Structure

File Responsibility Created/Modified
index.php Render the "Read more" link after the byline. Modify
styles/components/post-list.css Style the card body, image border, title, byline, and the new .post-list__read-more. Modify
static/dist/theme.css Rebuilt via npm run build. Modified (committed with git add -f).
tests/blog-page.spec.js Add a single assertion that the "Read more" link is visible in the first card. Modify

Files:

  • Modify: index.php:44-53

Interfaces:

  • Consumes: the standard WordPress loop (have_posts(), the_post()).

  • Produces: a <a class="post-list__read-more" href="<?php the_permalink(); ?>">Read more &rarr;</a> element inside .post-list__details, after the .post-list__byline div.

  • Step 1: Add the "Read more" anchor

In index.php, after the <div class="post-list__byline mt-auto">...</div> block (which ends on line 52 with </div>), and before the closing </div> of .post-list__details (line 53), add the new anchor. The result is:

<div class="post-list__details px-4 py-6 flex flex-col grow">
    <a href="<?php the_permalink(); ?>" class="">
        <h2 class="post-list__title font-normal text-25px text-balance line-clamp-4 truncate mt-0 mb-5 mx-0"><?php the_title(); ?></h2>
    </a>

    <div class="post-list__byline mt-auto">
        <span class="post-list__author"><?php echo esc_html( ucfirst( get_the_author() ) ); ?> &mdash;</span>
        <span class="post-list__date"><?php echo get_the_date(); ?></span>
    </div>

    <a class="post-list__read-more" href="<?php the_permalink(); ?>">Read more &rarr;</a>
</div>

Specifically:

  • Insert one new line after the closing </div> of .post-list__byline (the line that contains only </div> ending the byline block).

  • The new line is: <a class="post-list__read-more" href="<?php the_permalink(); ?>">Read more &rarr;</a>

  • Indentation: tabs (matching the existing PHP in the file).

  • Use &rarr; (HTML entity) for the arrow — this is consistent with other &mdash; / &hellip; / &laquo; usage in the same file (see lines 50, 64, 95, 96).

  • Step 2: Verify the file parses

Run: php -l index.php Expected: No syntax errors detected in index.php.

  • Step 3: Commit
git add index.php
git commit -m "feat(blog): add Read more link to post card"

Task 2: Add card styles to styles/components/post-list.css

Files:

  • Modify: styles/components/post-list.css

Interfaces:

  • Consumes: the new <a class="post-list__read-more"> from Task 1; the existing .post-list__post, .post-list__img, .post-list__details, .post-list__title, .post-list__byline elements from index.php.

  • Produces: a card with a solid blue content area, dark blue title and byline, and an orange "Read more" link. Removes the gray bottom border between the image and content area.

  • Step 1: Replace the contents of styles/components/post-list.css

The current file is 11 lines containing only .post-list__h1. Replace the entire file with:

/* Blog/post index listing styles */

.post-list__h1 {
    color: var(--color-cwc-blue-01);
    font-family: var(--font-quincy, 'Quincy', serif);
    font-size: var(--h1);
    font-weight: 700;
    line-height: 1.2;
    margin: 0 0 2rem;
    text-align: left;
}

.post-list__post {
    border-color: var(--color-cwc-blue-03);
    overflow: hidden;
}

.post-list__img {
    border-bottom: 0;
}

.post-list__details {
    background: var(--color-cwc-blue-03);
    color: var(--color-cwc-blue-01);
}

.post-list__title {
    color: var(--color-cwc-blue-01);
    font-weight: 700;
}

.post-list__byline {
    color: var(--color-cwc-blue-01);
    font-size: 0.875rem;
}

.post-list__read-more {
    color: var(--color-secondary);
    font-weight: 600;
    margin-top: 0.75rem;
    text-decoration: none;
}

.post-list__read-more:hover {
    text-decoration: underline;
}

Notes on the rules:

  • .post-list__post { border-color: var(--color-cwc-blue-03); } overrides the border-secondary class on the element so the card border matches the content area (effectively invisible against the same-colored background).

  • .post-list__post { overflow: hidden; } ensures the image's rounded-t-md corners are clipped by the card's rounded-md corners (this is the standard "image fills top of card" pattern).

  • .post-list__img { border-bottom: 0; } removes the existing border-b border-secondary so the image and content area flow as separate blocks without a divider line.

  • .post-list__details { background: var(--color-cwc-blue-03); } is the solid blue content area (#90c9e7).

  • .post-list__title { font-weight: 700; } overrides the font-normal in the markup.

  • .post-list__read-more uses --color-secondary (aliased to --color-cwc-orange-01).

  • Indentation: 4 spaces (CSS convention, matches the existing .post-list__h1 rule).

  • Step 2: Verify the CSS file parses

Tailwind build is the integration test (Task 3). No standalone CSS lint step.

  • Step 3: Commit
git add styles/components/post-list.css
git commit -m "feat(blog): style the post card body, title, byline, and Read more link"

Task 3: Add the "Read more" assertion in tests/blog-page.spec.js

Files:

  • Modify: tests/blog-page.spec.js:31-35

Interfaces:

  • Consumes: the new .post-list__read-more element from Tasks 1 and 2.

  • Produces: a passing assertion that the "Read more" link is visible in the first card and points to a post URL.

  • Step 1: Add the assertion in the desktop test

In tests/blog-page.spec.js, after the existing first-card assertions (after await expect(firstCard.locator(".post-list__byline")).toBeVisible(); on line 35), add the new assertion. The result is:

// First card: image, title link, byline.
const firstCard = posts.first();
await expect(firstCard.locator(".post-list__img img")).toBeVisible();
await expect(firstCard.locator(".post-list__title")).toBeVisible();
await expect(firstCard.locator(".post-list__byline")).toBeVisible();

// First card has a "Read more" link.
const readMore = firstCard.locator(".post-list__read-more");
await expect(readMore).toBeVisible();
await expect(readMore).toHaveText(/^Read more\s*\S+$/);
await expect(readMore).toHaveAttribute("href", /\/[a-z0-9-]+\/?$/);

Notes:

  • The text regex /^Read more\s*\S+$/ matches "Read more" followed by whitespace and any non-whitespace character (the arrow or any other trailing punctuation). This is robust to HTML-entity encoding in the rendered DOM.

  • The href regex matches a WordPress post permalink like /my-post-slug/ or /my-post-slug.

  • Indentation: 4 spaces (matches the existing test file).

  • Do NOT remove the existing .post-list__cats/.post-list__excerpt count assertions (lines 38-39). They guard against the trim being undone and remain valid.

  • Step 2: Run the new assertion in isolation

Run: npx playwright test tests/blog-page.spec.js -g "Blog title, post grid" Expected: PASS (the new assertion passes, all existing assertions still pass).

If the test fails:

  • "Read more" not found → Task 1's markup change didn't land.

  • Text mismatch → confirm &rarr; was used (not -> or &amp;rarr;).

  • href mismatch → confirm the markup uses <?php the_permalink(); ?> (not a hardcoded URL).

  • Step 3: Commit

git add tests/blog-page.spec.js
git commit -m "test(blog): assert Read more link is present in the first card"

Task 4: Build the dist CSS and run the final quality gate

Files:

  • Modify: static/dist/theme.css (committed with git add -f).

  • Step 1: Run the build

Run: npm run build Expected: build completes without errors.

  • Step 2: Verify the new classes are in the dist

Run: grep -c "post-list__read-more" static/dist/theme.css Expected: at least 1 match.

Run: grep -c "post-list__details" static/dist/theme.css Expected: at least 1 match (the new background rule should compile into the dist).

  • Step 3: Commit the rebuilt dist
git add -f static/dist/theme.css
git commit -m "build: regenerate dist with blog post card styles"
  • Step 4: Run the new test file

Run: npx playwright test tests/blog-page.spec.js Expected: all 4 tests pass.

  • Step 5: Run the full Playwright suite

Run: npx playwright test Expected: same pass/fail count as before this plan. The new test (4th) added 1 passing test; no other tests should regress. The 14 pre-existing failures (12 from prior plans + 2 contact-page pre-existing) are unchanged.

  • Step 6: Run PHPCS

Run: composer lint Expected: no NEW errors. The 5 pre-existing CRLF errors on untouched files (or 20 pre-existing issues per the prior composer lint baseline) remain unchanged.

  • Step 7: Final commit (only if anything changed in step 1-6)
git add -A
git commit -m "chore: final pass after Playwright and PHPCS"

If nothing changed (no dist drift, no PHPCS issues, no other drift), skip the commit.


Self-Review Notes

  • Spec coverage:

    • "Vertical card (image top, content below)" → already true; the card structure is unchanged.
    • "Title + byline + Read more link" → Task 1 adds the link; existing markup already has title and byline.
    • "Solid --color-cwc-blue-03 background" → Task 2 (.post-list__details).
    • "Title in --color-cwc-blue-01, weight 700" → Task 2 (.post-list__title).
    • "Byline in --color-cwc-blue-01, smaller" → Task 2 (.post-list__byline).
    • "Read more in --color-secondary (orange)" → Task 2 (.post-list__read-more).
    • "Remove gray border between image and content" → Task 2 (.post-list__img { border-bottom: 0; }).
    • "Card border matches content area" → Task 2 (.post-list__post { border-color: ...; }).
    • "Single new test assertion" → Task 3.
    • "Dist rebuild" → Task 4.
    • "All design tokens, no hardcoded hex" → Task 2 uses only var(--...) references.
    • "Out of scope: H1, search.php" → explicitly noted in Global Constraints.
  • Placeholder scan: No TBD/TODO. The "→" character is exact in both index.php (&rarr;) and the test (). The href regex is exact.

  • Type consistency: Class names: post-list__read-more (consistent with post-list__post, post-list__title, post-list__byline). CSS variable names: --color-cwc-blue-01, --color-cwc-blue-03, --color-secondary (all existing tokens).

  • Indentation: Tabs for PHP (Task 1), 4 spaces for CSS (Task 2) and JS (Task 3). Each matches the convention in its file.