feat: divine.video/merch landing page#327
Conversation
Deploying divine-web with
|
| 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 |
🚀 Preview Deployment
|
| 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}`); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| await mkdir(dirname(OUTPUT), { recursive: true }); | ||
| const payload = { | ||
| storeUrl: STORE_URL, | ||
| scrapedAt: new Date().toISOString(), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Fixed in b77f3d1 — scrapedAt removed from committed JSON. Re-running npm run merch:scrape now only produces a diff when product data actually changes.
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>
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>
b77f3d1 to
846255a
Compare
Summary
/merch— a brand-owned hero page that fronts the Bonfire merch store atbonfire.com/store/divine-18/./merch. The CTA on/merchis the only place that links out.feature/add-merch-linkbranch (commita7cd032was cherry-picked) — that PR can be closed once this merges.Why
We never remember the Bonfire URL.
divine.video/merchis 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
docs/superpowers/specs/2026-05-04-merch-landing-page-design.mdfor the rationale on each.Open questions to flag
<video autoplay muted loop playsinline poster="...">inside the hero<section>— one-file change./merchwill resolve identically on every subdomain (alice.divine.video/merchshows the same page). Intentional — matches/discovery,/trending, etc.Test plan
npx tsc --noEmitpassesnpx vitest run src/pages/MerchPage.test.tsxpasses (3/3)npx vitest run src/components/AppHeader.test.tsx src/components/AppFooter.test.tsxpasses (4/4) — both updated to assert internal/merchnavnpm test— 715 pass; 2 unrelated pre-existing flakes (static-pages-i18ntimeout,useWindowNostrJWTinstance equality) that pass in isolation. Not introduced by this PR./merch(CI)/merchin browser (could not exercise dev server in this session — please verify before merge)grep -rn "MERCH_STORE_URL" src/returns onlyexternalLinks.ts(definition) andMerchPage.tsx/MerchPage.test.tsx(single consumer)grep -rn "bonfire" src/componentsreturns zero matches🤖 Generated with Claude Code