Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
191481d
feat: add showcase-mode homepage with docs, downloads, and a curated …
NotThatKindOfDrLiz Jul 24, 2026
5c2dd4e
fix: harden showcase video safety
NotThatKindOfDrLiz Jul 24, 2026
63afef8
feat: invite families in on the showcase homepage
NotThatKindOfDrLiz Jul 24, 2026
756a659
refactor: fold the family block into the hero column
NotThatKindOfDrLiz Jul 24, 2026
2b99070
copy: retitle and tighten the family block
NotThatKindOfDrLiz Jul 25, 2026
915c1dd
copy: drop "make loops together here" from the family block
NotThatKindOfDrLiz Jul 25, 2026
b1f8859
copy: say Divine is 16+ and built for the real world
NotThatKindOfDrLiz Jul 25, 2026
9551075
copy: adopt Liz's wording for the family block
NotThatKindOfDrLiz Jul 25, 2026
20c2e9a
copy: lead the family block with humility, not policy
NotThatKindOfDrLiz Jul 25, 2026
be91fec
copy: tighten the family block to its final wording
NotThatKindOfDrLiz Jul 25, 2026
ea9e2bb
copy: call it AI slop in the homepage hero
NotThatKindOfDrLiz Jul 25, 2026
f075085
copy: link the kids policy alongside the guides and Greenlight
NotThatKindOfDrLiz Jul 25, 2026
edf5846
copy: lead the family block with the age rules
NotThatKindOfDrLiz Jul 25, 2026
96eaa17
copy: argue the position, point at one destination
NotThatKindOfDrLiz Jul 25, 2026
cf9d359
copy: swap hand-crafted for handpicked in the reel intro
NotThatKindOfDrLiz Jul 25, 2026
e9a52be
fix: put the reel above the family block on mobile
NotThatKindOfDrLiz Jul 25, 2026
946f5b5
feat: loop the showcase reel in both directions
NotThatKindOfDrLiz Jul 25, 2026
75ca253
copy: drop the scrutiny opener from the family block
NotThatKindOfDrLiz Jul 25, 2026
a219ea9
fix(seo): give the shell showcase-mode social metadata
NotThatKindOfDrLiz Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions compute-js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { transformVideoApiResponse } from './videoMetadata.js';
import { renderEmbedPage } from './embedPage.js';
import { resolveFeedInjectedHtml } from './feedInjection.js';
import { isShowcaseMode } from './webMode.js';

const publisherServer = PublisherServer.fromStaticPublishRc(rc);
const DEFAULT_OG_IMAGE = 'https://divine.video/og.png';
Expand Down Expand Up @@ -305,10 +306,17 @@ async function handleRequest(event) {
const isApexDomain = APEX_DOMAINS.includes(hostnameToUse);
const isApexLanding = isApexDomain && (url.pathname === '/' || url.pathname === '/index.html');
const discoveryFeedType = isApexDomain ? getDiscoveryFeedType(url.pathname) : null;
const shouldInjectFeed = isApexLanding || discoveryFeedType;
// In showcase mode the homepage renders a hand-curated, all-ages set and the
// /discovery routes do not exist. Injecting the trending feed here would put
// uncurated content on the one page that exists to guarantee curation, so the
// LCP optimization is switched off rather than pointed somewhere else.
const shouldInjectFeed = !isShowcaseMode() && (isApexLanding || discoveryFeedType);

if (isApexLanding && isSocialMediaCrawler(request)) {
const ogResponse = await handleApexOgTags();
// Same reasoning: handleApexOgTags() seeds the social preview image from the
// top trending video, which is not curated. Fall through to the static
// shell's own OG tags in showcase mode.
const ogResponse = isShowcaseMode() ? null : await handleApexOgTags();
if (ogResponse) return ogResponse;
}

Expand Down
20 changes: 20 additions & 0 deletions compute-js/src/webMode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ABOUTME: Edge-side mirror of the frontend VITE_WEB_MODE flag
// ABOUTME: Decides whether the worker may inject uncurated feed data into pages

/**
* Which experience the site serves. MUST stay in sync with `VITE_WEB_MODE`
* in `src/config/webMode.ts`.
*
* The worker has no access to Vite's env, and both are deployed together from
* this repo (`npm run fastly:deploy && npm run fastly:publish`), so a checked-in
* constant is the sync mechanism. If you flip one, flip the other in the same
* commit — a mismatch means the edge injects trending videos into a page whose
* whole purpose is that its content is curated.
*
* @type {'showcase' | 'full'}
*/
export const WEB_MODE = 'showcase';

export function isShowcaseMode() {
return WEB_MODE === 'showcase';
}
19 changes: 12 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
with self-referencing canonicals at build time. -->

<!-- PWA Meta Tags -->
<title>Divine Web - Short-form Looping Videos on Nostr</title>
<meta name="description" content="Watch and share 6-second looping videos on the decentralized Nostr network." />
<!-- These are the shell defaults, and on the apex they are also what social
crawlers actually read: / is not prerendered, and in showcase mode the
edge worker no longer injects OG tags for the homepage. Crawlers do not
run JS, so ShowcasePage's useSeoMeta never reaches them. Keep these in
sync with the useSeoMeta call in src/pages/ShowcasePage.tsx. -->
<title>Divine — short video on an open protocol</title>
<meta name="description" content="Six-second loops, no algorithm feeding you slop. Get Divine on iOS, Android, Zapstore, or build it yourself." />
<meta name="theme-color" content="#27C58B" />
<!-- X-Frame-Options must be set by server headers; remove meta to avoid console error -->

Expand All @@ -30,18 +35,18 @@
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://divine.video/" />
<meta property="og:title" content="Divine Web - Short-form Looping Videos on Nostr" />
<meta property="og:description" content="Watch and share 6-second looping videos on the decentralized Nostr network." />
<meta property="og:title" content="Divine — short video on an open protocol" />
<meta property="og:description" content="Six-second loops, no algorithm feeding you slop. Get Divine on iOS, Android, Zapstore, or build it yourself." />
<meta property="og:image" content="https://divine.video/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Divine Web - Short-form looping videos on the Nostr network" />
<meta property="og:image:alt" content="Divine — six-second looping videos from real humans" />
<meta property="og:image:type" content="image/png" />

<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Divine Web - Short-form Looping Videos on Nostr" />
<meta name="twitter:description" content="Watch and share 6-second looping videos on the decentralized Nostr network." />
<meta name="twitter:title" content="Divine — short video on an open protocol" />
<meta name="twitter:description" content="Six-second loops, no algorithm feeding you slop. Get Divine on iOS, Android, Zapstore, or build it yourself." />
<meta name="twitter:image" content="https://divine.video/og.png" />

<!-- Icons -->
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
"deploy": "npm run build && npx -y nostr-deploy-cli deploy --skip-setup",
"deploy:cloudflare": "npm run build && wrangler pages deploy dist",
"deploy:cloudflare:preview": "npm run build && wrangler pages deploy dist --branch preview",
"fastly:local": "npm run build && npm i && npm run -w divine-web-edge dev:publish && npm run -w divine-web-edge dev:start",
"fastly:deploy": "npm run build && npm i && npm run -w divine-web-edge fastly:deploy",
"fastly:publish": "npm run build && npm i && npm run -w divine-web-edge fastly:publish",
"fastly:local": "npm run verify:web-mode-sync && npm run build && npm i && npm run -w divine-web-edge dev:publish && npm run -w divine-web-edge dev:start",
"fastly:deploy": "npm run verify:web-mode-sync && npm run build && npm i && npm run -w divine-web-edge fastly:deploy",
"fastly:publish": "npm run verify:web-mode-sync && npm run build && npm i && npm run -w divine-web-edge fastly:publish",
"fastly:release": "npm run fastly:deploy && npm run fastly:publish",
"verify:well-known": "node scripts/verify-well-known.mjs",
"verify:web-mode-sync": "node scripts/verify-web-mode-sync.mjs",
"precalculate-thumbnails": "tsx scripts/precalculate-hashtag-thumbnails.ts",
"generate-icons": "node scripts/generate-icons.js",
"test:visual": "playwright test",
Expand Down
31 changes: 24 additions & 7 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { defineConfig, devices } from '@playwright/test';
// Use a dedicated port so parallel worktrees on 8080 don't collide.
const PORT = Number(process.env.PLAYWRIGHT_PORT ?? 8088);
const BASE_URL = `http://localhost:${PORT}`;
const FULL_MODE_PORT = Number(process.env.PLAYWRIGHT_FULL_MODE_PORT ?? 8089);
const FULL_MODE_BASE_URL = `http://localhost:${FULL_MODE_PORT}`;

export default defineConfig({
testDir: './tests/visual',
Expand All @@ -14,11 +16,26 @@ export default defineConfig({
baseURL: BASE_URL,
trace: 'on-first-retry',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
webServer: {
command: `npx vite --port ${PORT} --strictPort`,
url: BASE_URL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{
name: 'full-mode-a11y',
testMatch: /a11y\.spec\.ts/,
use: { ...devices['Desktop Chrome'], baseURL: FULL_MODE_BASE_URL },
},
],
webServer: [
{
command: `npx vite --port ${PORT} --strictPort`,
url: BASE_URL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
{
command: `VITE_WEB_MODE=full npx vite --port ${FULL_MODE_PORT} --strictPort`,
url: FULL_MODE_BASE_URL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
],
});
7 changes: 7 additions & 0 deletions public/store-badges/zapstore-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading