Skip to content

feat: showcase-mode homepage (docs, downloads, curated reel) - #511

Closed
NotThatKindOfDrLiz wants to merge 19 commits into
mainfrom
feat/showcase-mode
Closed

feat: showcase-mode homepage (docs, downloads, curated reel)#511
NotThatKindOfDrLiz wants to merge 19 commits into
mainfrom
feat/showcase-mode

Conversation

@NotThatKindOfDrLiz

@NotThatKindOfDrLiz NotThatKindOfDrLiz commented Jul 24, 2026

Copy link
Copy Markdown
Member

What & why

Turns divine.video into a documents + app-download + curated-taste surface, so the web stops duplicating the mobile client (and its open, potentially age-gated feed) on an indexed, no-login page. The legacy Nostr client is preserved, lazy-loaded behind a flag, so nothing is deleted yet.

Gated by a build-time flag: VITE_WEB_MODE (default showcase; full restores today's app).

Highlights

Homepage (showcase mode)

  • Hero + device-aware Get the app CTA: iOS → App Store; Android → Google Play / Zapstore dropdown; desktop → all three. Every click tracked (store_badge_click).
  • Phone-framed, swipeable curated reel: read-only (no like/follow/comment), starts muted with an unmute toggle, tap top/bottom to navigate (scrolls only the reel, not the page), share via native sheet on touch / copy-link on desktop, white wordmark watermark. Square Vine classics letterbox to preserve aspect ratio; portrait clips fill. Shuffled every visit.
  • The reel loops in both directions — past the last clip returns to the first, back from the first lands on the last. Wraps jump instantly instead of smooth-scrolling back through every clip in between.
  • "Building a healthier social media experience" block below the reel intro: Divine's position on the scrutiny facing social media, and a link to the family resources hub.
  • Mobile ordering: hero copy → reel intro → phone → family block, so the reel isn't buried under every paragraph. Desktop keeps the copy stacked in one column with the phone spanning both rows beside it.

Curation (fails closed)

  • Reads an allowlisted curator's titled kind-30005 list (matched by title — the mobile app auto-generates d tags), unions e-tag and a-tag video refs, then applies an all-ages safety floor that drops age-restricted / content-warned clips. No fallback to an open feed.

Plumbing

  • Router splits showcase vs full; keeps /auth/callback, /app/callback, /invite/:code for mobile deep-links. Public safety-gated single-video page at /video/:id.
  • Edge worker no longer injects the trending feed or OG image into the curated homepage in showcase mode.
  • Fixes: creator names now resolve via display_name (was dropping ~half to generic names); fetchListVideos shared with ListDetailPage; "Link copied" toast auto-dismisses.
  • Reel navigation reads the scroller's own scroll position instead of activeIndex. That state is set by an IntersectionObserver callback and trails the real scroll position, so a tap arriving before the observer caught up computed its target from a stale index and landed on the wrong clip.

Testing

  • npx vitest run — 1624 unit tests pass (safety floor, curation merge, platform/store selection, name resolution, aspect fit, shuffle, config parsing, circular slide navigation, family-block link targets).
  • npx playwright test — 43 pass (responsive/no-overflow, a11y, brand snapshot, header + reel behavior, hero ordering at both breakpoints, reel looping driven by real key presses).
  • npx tsc --noEmit + npx eslint src/ (and eslint index.html) clean.
  • Full production build with both prerender passes, to confirm the shell metadata change doesn't leak into prerendered routes.

⚠️ Before this can ship (needs your input, @rabble)

  • Curator config: src/config/curation.ts currently points the reel at Liz's live public divine-web list. Confirm the intended editorial curator(s)/title.
  • Worker mode sync: compute-js/src/webMode.js is a hand-synced constant that must match VITE_WEB_MODE — a mismatch injects trending video into the curated page. Consider a Fastly ConfigStore later.
  • Live embeds: /get-embed and the /embed widget are dropped in showcase mode — confirm no third-party embeds are live before flipping the flag.
  • SEO cut-over (sitemap + 301s from removed content routes) is intentionally deferred until full mode is retired.

SEO and cookie-consent audit

Checked because the router split is an easy place to drop either one.

Cookie consent carries over unchanged. Verified live in showcase mode: the HubSpot banner renders (cookieBanner-244466832), the loader script is injected, and window._hsp has the consent listener registered. None of it is routed — hubSpotLoader.ts loads from a <script type="module"> in index.html, and initCookieConsent() / initializeAnalytics() run in main.tsx before React mounts. Firebase Analytics stays gated behind onAnalyticsConsentChanged, so the new store_badge_click events are consent-gated like everything else.

Sitemap and robots are clean. All 12 sitemap URLs resolve in showcase mode; nothing 404s. robots.txt is unchanged.

Fixed here — apex social metadata. / is not prerendered (only the five /family* routes are), and showcase mode switches off the worker's apex OG injection. Social crawlers therefore fall through to index.html, and they don't run JS, so ShowcasePage's useSeoMeta never reached them. Every share of divine.video previewed with stale full-mode copy — "Watch and share 6-second looping videos on the decentralized Nostr network" — describing exactly the feed this mode removes. The shell defaults now match what ShowcasePage sets. Prerendered legal and family routes build their own <head>, confirmed unaffected by a full build. verify-og-tags.sh learned the new fallback string so its "no per-route handler fired" check keeps working.

Still outstanding:

  • / has no JSON-LD structured data (the family pages do). Not a regression.
  • compute-js/src/index.js still appends the old site description to /video/* pages, and handleApexOgTags() still carries the old strings (dead in showcase mode). Both are worker-side and left for the SEO cut-over.

Known limits

  • The reel loops via tap zones and arrow keys. Native swipe/scroll still stops at the physical ends of the scroll container — making the drag itself infinite means duplicating the slide list and silently repositioning, which is a separate piece of work.
  • The homepage family copy links only to /family. /kids and Divine Greenlight stay reachable through the footer.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

🚀 Preview Deployment

Last updated: 2026-07-25T03:42:04.584Z

Property Value
Preview URL https://7e93f478.divine-web-fm8.pages.dev
Commit a219ea9
Branch feat/showcase-mode
Workflow run #988

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploying divine-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: a219ea9
Status: ✅  Deploy successful!
Preview URL: https://6f767ea0.divine-web.pages.dev
Branch Preview URL: https://feat-showcase-mode.divine-web.pages.dev

View logs

…reel

Introduce a build-time WEB_MODE flag (VITE_WEB_MODE, default "showcase")
that turns divine.video into a documents + app-download + curated-taste
surface, with the legacy Nostr client lazy-loaded behind "full" mode.

Showcase mode:
- Homepage: hero + device-aware "Get the app" CTA (iOS -> App Store,
  Android -> Play/Zapstore dropdown, desktop -> all three; clicks tracked)
  and a phone-framed, swipeable reel of curated video.
- Curated reel reads an allowlisted curator's titled kind 30005 list
  (matched by title, since the mobile app auto-generates d tags), unions
  e-tag and a-tag video refs, and applies an all-ages safety floor
  (drops age-restricted / content-warned clips). Shuffled per visit.
- Reel: read-only (no like/follow/comment), start-muted with an unmute
  toggle, tap top/bottom to navigate (scrolls only the reel, not the
  page), share via native sheet on touch / copy-link on desktop, white
  wordmark watermark. Square classics letterbox to preserve aspect.
- Public single-video share page at /video/:id, safety-gated.
- Router splits showcase vs full; keeps /auth/callback, /app/callback,
  and /invite/:code for mobile deep-links. Edge worker stops injecting
  the trending feed / OG image on the curated homepage in showcase mode.

Notable fixes: resolve creator names via display_name (was dropping ~half
to generic names); reuse fetchListVideos across ListDetailPage and the
reel; "Link copied" toast auto-dismisses.
Adds a section below the joyscrolling block that says out loud what
Divine wants social media to be for a household: a thing you enjoy
together rather than a thing you fight about.

Two doors out of it — the /family hub for parents and kids creating
together, and the Divine Greenlight section of /kids for teens 13-15
who get a parent-supported start instead of going it alone.

Greenlight copy stays hedged ("where local rules allow it") so the
homepage never promises more than the policy page does.
Moves the family / Divine Greenlight copy out of a full-width band
below the fold and into the left column directly under the
joyscrolling block, so it reads as the next beat of the same
column rather than a separate marketing section.

Drops the link cards for inline links in body copy, matching the
heading size, muted paragraph, and spacing of the block above it.
Heading becomes "Building a healthier social media experience" and
the copy leads with the friction before the pivot.

Keeps "family guides" lowercase rather than coining a "Divine Family"
product name the /family hub doesn't use yet. Trimmed back to the
length of the block it replaced so the hero column stays level with
the phone frame.
Leads with the compliment instead of the mechanic, and loses both
the "loops" jargon and the vague "here".
Splits the family block in two: the invitation stays first, and a
second paragraph states the age posture plainly — 16 and up, no solo
accounts under 13, parent-held family accounts instead, Greenlight
for 13-15 where local law allows.

Claims track /kids and the Terms; a comment marks them as needing to
stay in sync with those pages.
Author's revision, applied verbatim. States the under-13 rule as a
prohibition on solo accounts rather than a hosting choice, and moves
the age posture to "designed for ages 16 and up."
Replaces both paragraphs with a single mission-voice paragraph and
consolidates the two links into the closing sentence.

Drops the explicit age posture (16+, under-13 accounts, the 13-15
range) from the homepage; that detail still lives on /kids, which
both links reach.
Author's revision. Opens on the humility line, names what the guides
actually do for a family, and restores the research-backed claim that
/family's citations support.
"AI-generated content" is the neutral description; "AI slop" is the
brand's own word for it, and the tone guide uses it directly.
Narrows the middle of the paragraph to kids specifically and adds a
third destination, so the homepage now reaches the policy page, the
family hub, and the Greenlight section.

Covers the new /kids link in the section test.
Replaces the mission framing with a direct statement of the age
posture: not built for under-13s, rules vary by age and location,
Greenlight for 13-15 where permitted, families welcome together.

Links move onto "Divine Greenlight", "tools", and "resources".
Replaces the age-rule recitation with Divine's stance — better answers
than blanket bans or mass surveillance — and narrows the block to a
single call to action on the family resources page.

Test now pins the link count so a stray second CTA can't creep back in.
Also makes the CTA an instruction ("Download the app and join the
fun") instead of a question-and-answer, and switches the apostrophe
to a curly one so both paragraphs in the column now match.
The family copy was nested inside the hero column, so on a phone it
rendered before the reel and pushed it down the page. Lifts it out as
a sibling and lets the phone span both grid rows, so mobile reads
copy, reel, family while desktop keeps the family block beside the
phone rather than below it.

Adds Playwright coverage for the ordering at both breakpoints.
Navigation clamped at both ends, so the last clip was a dead stop and
the first could not go back. Both edges now wrap, and a wrap jumps
instantly rather than smooth-scrolling back through every clip in
between, which read as a glitch rather than a loop.

Also stops navigation reading the current slide from activeIndex.
That state is set by an IntersectionObserver callback and trails the
real scroll position, so a tap arriving before the observer caught up
computed its target from a stale index and landed on the wrong clip.
Scroll position is authoritative, so navigation reads it directly.

Adds unit tests for the wrap arithmetic and Playwright coverage that
drives the reel with real key presses through both edges.
Paragraph now leads with Divine's position instead of the context for
it.
/ is not prerendered, and showcase mode switches off the worker's OG
injection for the apex, so social crawlers fall through to index.html.
They don't run JS, so ShowcasePage's useSeoMeta never reached them and
every share of divine.video previewed as "Watch and share 6-second
looping videos" — a description of the feed showcase mode removes.

Points the shell defaults at the same copy ShowcasePage sets. Prerendered
legal and family routes build their own head, so they are unaffected.

Also teaches verify-og-tags.sh the new fallback string. That check fails
a route whose og:title is the shell default, meaning no per-route handler
fired; without this it would have stopped catching that. The old string
stays matched so a stale cached shell is still caught.

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review pass complete. Quality is high — fails closed everywhere, the safety floor is redundant with curation, worker feed/OG injection is correctly gated, and the sync verify script + tests are solid. Two blocking issues:

  1. Subdomain profile sites regress (bug). In showcase mode the worker still serves alice.divine.video the SPA with __DIVINE_USER__, but ShowcaseRoutes renders ShowcasePage at / — the subdomain profile never mounts. The PR body never mentions subdomains. Either route subdomains to FullAppRoutes or state their retirement explicitly. Silent breakage of every existing profile site is the worst option.

  2. Non-Fastly deploys flip silently. Default showcase applies to every deploy target, but verify:web-mode-sync is wired only into the fastly:* scripts. deploy:cloudflare, deploy, and preview builds would silently become showcase with no sync check and no edge worker at all (OG/feed behavior differs per target). Add the verify step to build or document the per-target mode matrix.

Also: merge state is CONFLICTING (MarketingHeader overlap with #191) — needs a rebase. And the open questions in the body (curator config, /get-embed removal, worker mode sync) are still unanswered, so holding as draft is correct.

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