Skip to content

feat: divine.video/merch landing page#327

Open
rabble wants to merge 4 commits into
mainfrom
feat/merch-landing-page
Open

feat: divine.video/merch landing page#327
rabble wants to merge 4 commits into
mainfrom
feat/merch-landing-page

Conversation

@rabble
Copy link
Copy Markdown
Member

@rabble rabble commented May 4, 2026

Summary

  • Adds /merch — a brand-owned hero page that fronts the Bonfire merch store at bonfire.com/store/divine-18/.
  • Rewires the five existing internal merch links (header dropdown, sidebar, footer, landing page, marketing header) from external Bonfire URLs to internal /merch. The CTA on /merch is the only place that links out.
  • Includes the work from the unmerged feature/add-merch-link branch (commit a7cd032 was cherry-picked) — that PR can be closed once this merges.

Why

We never remember the Bonfire URL. divine.video/merch is the URL we actually want to type and share, with brand framing visitors can trust before they hit Bonfire's checkout.

What's deliberately not in v1

  • No product grid, no per-product cards, no prices on the page.
  • No hero video/photo asset — the v1 ships a brand-color hero block that's already on-brand.
  • No email "notify me on new drops" capture.
  • See docs/superpowers/specs/2026-05-04-merch-landing-page-design.md for the rationale on each.

Open questions to flag

  • Hero copy. v1 strings ("Wear your loops." / "Tees, stickers, and stuff that doesn't take itself too seriously.") are a starting point. Tag for a copy pass before merging.
  • Hero asset. No video/photo in v1. When a real merch lifestyle clip exists, swap in a <video autoplay muted loop playsinline poster="..."> inside the hero <section> — one-file change.
  • Subdomain coverage. /merch will resolve identically on every subdomain (alice.divine.video/merch shows the same page). Intentional — matches /discovery, /trending, etc.

Test plan

  • npx tsc --noEmit passes
  • npx vitest run src/pages/MerchPage.test.tsx passes (3/3)
  • npx vitest run src/components/AppHeader.test.tsx src/components/AppFooter.test.tsx passes (4/4) — both updated to assert internal /merch nav
  • Full npm test — 715 pass; 2 unrelated pre-existing flakes (static-pages-i18n timeout, useWindowNostrJWT instance equality) that pass in isolation. Not introduced by this PR.
  • Playwright a11y sweep covers /merch (CI)
  • Manual smoke test of /merch in browser (could not exercise dev server in this session — please verify before merge)
  • grep -rn "MERCH_STORE_URL" src/ returns only externalLinks.ts (definition) and MerchPage.tsx / MerchPage.test.tsx (single consumer)
  • grep -rn "bonfire" src/components returns zero matches

🤖 Generated with Claude Code

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 4, 2026

Deploying divine-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: 846255a
Status: ✅  Deploy successful!
Preview URL: https://b99033b0.divine-web.pages.dev
Branch Preview URL: https://feat-merch-landing-page.divine-web.pages.dev

View logs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

🚀 Preview Deployment

Property Value
Preview URL https://eda08f59.divine-web-fm8.pages.dev
Commit 846255a
Branch feat/merch-landing-page

@DSanich
Copy link
Copy Markdown
Contributor

DSanich commented May 4, 2026

@rabble Looks good to merge!

Nice work. I left a couple of small follow-ups on the scraper script (failure handling and JSON diff noise), but nothing blocking for this PR.

cc @jalcine @NotThatKindOfDrLiz

console.log(` ${variants.length} variant(s) for ${campaign.slug}: ${variants.map((v) => v.name).join(', ')}`);
allProducts.push(...variants);
} catch (err) {
console.warn(` Failed to scrape ${campaign.slug}: ${err.message}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If one campaign fails, we still finish with a successful run and can write partial data. Can we track failed campaigns and set a non-zero exit code at the end?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b77f3d1 — failures now collect into a list and the script exits non-zero at the end if any campaign failed, even with partial data written.

Comment thread scripts/scrape-bonfire-products.mjs Outdated
await mkdir(dirname(OUTPUT), { recursive: true });
const payload = {
storeUrl: STORE_URL,
scrapedAt: new Date().toISOString(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

scrapedAt changes every run, so this file always gets a diff even when products are unchanged. Can we remove it from committed JSON to reduce PR noise?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b77f3d1scrapedAt removed from committed JSON. Re-running npm run merch:scrape now only produces a diff when product data actually changes.

@NotThatKindOfDrLiz NotThatKindOfDrLiz requested a review from jalcine May 4, 2026 17:51
rabble added a commit that referenced this pull request May 4, 2026
The hoodie card was landing users on the tee variant. Now each variant
gets its own productType UUID in the URL so Bonfire opens directly to
the right item (e.g. Pullover Hoodie → ?productType=0d740304-...).

Source of UUIDs: storefront a.sw-ProdCard_Fig anchors expose them in
href. Match storefront URLs to campaign-page picker labels by index
since both lists follow the same vm.productTypes order. Angular scope
access was tried first but Bonfire ships with debugInfoEnabled(false)
in prod so it returns nothing.

Review feedback (DSanich on PR #327):
- Track campaign scrape failures and exit non-zero if any failed,
  even when partial data was written.
- Drop scrapedAt from committed JSON — was causing a noisy diff on
  every run even when product data was unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rabble and others added 4 commits May 6, 2026 00:02
Scope correction. The earlier spec said "no product grid" — wrong call
on my part. A merch page with no merch is broken on its face.

What's here:
- scripts/scrape-bonfire-products.mjs — Playwright job that loads the
  Bonfire storefront (it's an Angular SPA, X-Frame-Options blocks iframes,
  the JSON API is auth-gated, so we render in headless and pull products
  out of the DOM).
- src/data/merchProducts.json — current scrape output (5 products).
  Re-run via `npm run merch:scrape` whenever the lineup changes.
- src/pages/MerchPage.tsx — hero + responsive 3-column product grid +
  single "Shop everything" CTA. Each card deep-links to its Bonfire
  product page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/<...>

The campaign-title anchor's relative href ("white-bucket-hat-2/") resolved
against the storefront base produced /store/divine-18/<slug>/, which is
not a real product URL. The product card figure anchor (a.sw-ProdCard_Fig)
has the canonical absolute path. Strip ?productType=<uuid> so each card
links to the clean product URL the user actually shares.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous scrape collapsed each campaign into one card, hiding the
Pullover Hoodie variant under "Divine Tees & Hoodie". Now the scraper
visits each campaign page and extracts every style variant from the
.sw-ProductPicker radio group (Premium Unisex Tee, Pullover Hoodie,
etc.). 6 products incl. the hoodie.

Card readability fixes:
- Larger, bolder product titles (text-lg, font-extrabold).
- Optional campaign overline ("Divine Tees & Hoodie") above the variant
  name only when the campaign has multiple variants.
- "Shop on Bonfire" affordance with arrow icon on every card so the
  click target is unmistakable.
- Hero CTA moved into the hero block (above the grid), not just below.
- Trust line now uses brand-cream on the dark page background instead
  of muted-foreground (which was unreadable in dark mode).
- Bonfire CDN thumbnails upgraded from /150/ → /500/ for crisp images.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hoodie card was landing users on the tee variant. Now each variant
gets its own productType UUID in the URL so Bonfire opens directly to
the right item (e.g. Pullover Hoodie → ?productType=0d740304-...).

Source of UUIDs: storefront a.sw-ProdCard_Fig anchors expose them in
href. Match storefront URLs to campaign-page picker labels by index
since both lists follow the same vm.productTypes order. Angular scope
access was tried first but Bonfire ships with debugInfoEnabled(false)
in prod so it returns nothing.

Review feedback (DSanich on PR #327):
- Track campaign scrape failures and exit non-zero if any failed,
  even when partial data was written.
- Drop scrapedAt from committed JSON — was causing a noisy diff on
  every run even when product data was unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rabble rabble force-pushed the feat/merch-landing-page branch from b77f3d1 to 846255a Compare May 5, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants