feat: showcase-mode homepage (docs, downloads, curated reel) - #511
feat: showcase-mode homepage (docs, downloads, curated reel)#511NotThatKindOfDrLiz wants to merge 19 commits into
Conversation
🚀 Preview DeploymentLast updated:
|
Deploying divine-web with
|
| 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 |
…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.
1718121 to
5c2dd4e
Compare
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
left a comment
There was a problem hiding this comment.
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:
-
Subdomain profile sites regress (bug). In showcase mode the worker still serves
alice.divine.videothe SPA with__DIVINE_USER__, butShowcaseRoutesrendersShowcasePageat/— the subdomain profile never mounts. The PR body never mentions subdomains. Either route subdomains toFullAppRoutesor state their retirement explicitly. Silent breakage of every existing profile site is the worst option. -
Non-Fastly deploys flip silently. Default
showcaseapplies to every deploy target, butverify:web-mode-syncis wired only into thefastly:*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 tobuildor 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.
What & why
Turns
divine.videointo 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(defaultshowcase;fullrestores today's app).Highlights
Homepage (showcase mode)
store_badge_click).Curation (fails closed)
dtags), unionse-tag anda-tag video refs, then applies an all-ages safety floor that drops age-restricted / content-warned clips. No fallback to an open feed.Plumbing
/auth/callback,/app/callback,/invite/:codefor mobile deep-links. Public safety-gated single-video page at/video/:id.display_name(was dropping ~half to generic names);fetchListVideosshared withListDetailPage; "Link copied" toast auto-dismisses.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/(andeslint index.html) clean.src/config/curation.tscurrently points the reel at Liz's live publicdivine-weblist. Confirm the intended editorial curator(s)/title.compute-js/src/webMode.jsis a hand-synced constant that must matchVITE_WEB_MODE— a mismatch injects trending video into the curated page. Consider a Fastly ConfigStore later./get-embedand the/embedwidget are dropped in showcase mode — confirm no third-party embeds are live before flipping the flag.fullmode 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, andwindow._hsphas the consent listener registered. None of it is routed —hubSpotLoader.tsloads from a<script type="module">inindex.html, andinitCookieConsent()/initializeAnalytics()run inmain.tsxbefore React mounts. Firebase Analytics stays gated behindonAnalyticsConsentChanged, so the newstore_badge_clickevents are consent-gated like everything else.Sitemap and robots are clean. All 12 sitemap URLs resolve in showcase mode; nothing 404s.
robots.txtis 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 toindex.html, and they don't run JS, soShowcasePage'suseSeoMetanever reached them. Every share ofdivine.videopreviewed 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 whatShowcasePagesets. Prerendered legal and family routes build their own<head>, confirmed unaffected by a full build.verify-og-tags.shlearned 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.jsstill appends the old site description to/video/*pages, andhandleApexOgTags()still carries the old strings (dead in showcase mode). Both are worker-side and left for the SEO cut-over.Known limits
/family./kidsand Divine Greenlight stay reachable through the footer.🤖 Generated with Claude Code