From a1adfa0ef1549faa67cc2f6464617a53cef6f9e8 Mon Sep 17 00:00:00 2001 From: nmemmert Date: Thu, 15 Oct 2026 11:50:19 -0400 Subject: [PATCH] Scripture Index: verse of the week (admin override or weekly rotation through covered passages); v2.45.0 Co-Authored-By: Claude Sonnet 5.5 --- package-lock.json | 4 ++-- package.json | 2 +- src/AdminPage.tsx | 15 ++++++++++++++ src/App.css | 6 +++++- src/App.tsx | 50 +++++++++++++++++++++++++++++++++++++++++++++++ src/content.ts | 2 ++ 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7688609..094cc46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siteforge", - "version": "2.44.0", + "version": "2.45.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siteforge", - "version": "2.44.0", + "version": "2.45.0", "dependencies": { "@aws-sdk/client-s3": "^3.1135.0", "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index ddc925d..c598e6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.44.0", + "version": "2.45.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index f9ec03a..6ea564b 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -884,6 +884,7 @@ const ADMIN_SECTION_LINKS: Partial> = { { id: 'global-header', label: 'Header' }, { id: 'global-platforms', label: 'Platform URLs' }, { id: 'global-banner', label: 'Analytics Banner' }, + { id: 'global-verse', label: 'Verse of the Week' }, { id: 'global-welcome-email', label: 'Welcome Email' }, ], 'start-here': [ @@ -1027,6 +1028,7 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect { key: 'platformCreatorUrl', label: 'Platform — Creator Profile URL', section: 'global' }, { key: 'platformKofiUrl', label: 'Platform — Ko-fi URL', section: 'global' }, { key: 'platformKofiQrUrl', label: 'Platform — Ko-fi QR Code Image URL', section: 'global' }, + { key: 'verseOfTheWeek', label: 'Scripture Index — Verse of the Week (optional)', section: 'global' }, { key: 'welcomeEmailSubject', label: 'Welcome Email — Subject', section: 'global' }, { key: 'welcomeEmailGreetingPrefix', label: 'Welcome Email — Greeting Prefix', section: 'global' }, { key: 'welcomeEmailIntro', label: 'Welcome Email — Intro Paragraph', multiline: true, section: 'global' }, @@ -6436,6 +6438,19 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ))} + } + {tabOf('global', 'global-footer') === 'global-verse' && <> +

Verse of the Week

+
+ + handleChange('verseOfTheWeek', e.target.value)} /> +

Shown at the top of the Scripture Index. Leave blank and the site picks a different covered passage each week automatically. Use a full reference with the book name.

+ {(() => { + const vowProblem = checkPassageReference(String(form.verseOfTheWeek ?? ''), '') + return vowProblem ?

⚠ {vowProblem}

: null + })()} +
+ } {tabOf('global', 'global-footer') === 'global-welcome-email' && <>

Welcome Email

diff --git a/src/App.css b/src/App.css index f985978..51f2ac2 100644 --- a/src/App.css +++ b/src/App.css @@ -13718,6 +13718,10 @@ a.study-track-card:hover { .si-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .si-verse-btn { margin-left: auto; font: inherit; font-size: 0.8rem; background: none; border: 0; padding: 0; cursor: pointer; color: var(--brand-muted); text-decoration: underline; } .si-verse-btn:hover { color: var(--brand-gold); } +.si-vow { margin-bottom: 1rem; padding: 1.1rem 1.25rem; border: 1px solid var(--brand-gold-dark); border-radius: 12px; background: radial-gradient(ellipse at top left, rgba(201, 168, 76, 0.12), transparent 70%), var(--brand-black-3); } +.si-vow-eyebrow { margin: 0 0 0.25rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--brand-gold); } +.si-vow-ref { margin: 0 0 0.5rem; font-family: var(--brand-font-heading); font-size: 1.6rem; font-weight: 600; line-height: 1.15; } +.si-vow-text { margin: 0 0 0.75rem; font-style: italic; line-height: 1.65; color: var(--brand-muted); } .si-top { margin-bottom: 1rem; padding: 0.9rem 1rem; background: var(--brand-black-3); border: 1px solid var(--brand-border); border-radius: 12px; } .si-top-title { margin: 0 0 0.6rem; font-family: var(--brand-font-heading); font-size: 1.15rem; font-weight: 600; } .si-top-list { display: flex; flex-wrap: wrap; gap: 0.5rem; } @@ -13731,7 +13735,7 @@ a.study-track-card:hover { .si-main { padding: 0; } .si-main, .si-group, .si-top { background: #fff !important; color: #000 !important; } .si-print-title { display: block; font-size: 1.6rem; margin: 0 0 1rem; color: #000; } - .si-toolbar, .si-books, .si-meta, .si-top, .si-verse-btn, .si-verse, .si-chev { display: none !important; } + .si-toolbar, .si-books, .si-meta, .si-top, .si-vow, .si-verse-btn, .si-verse, .si-chev { display: none !important; } .si-group { border: 0; border-bottom: 1px solid #999; break-inside: avoid-page; border-radius: 0; } .si-group-head { padding: 0.5rem 0; color: #000; } .si-ref { color: #000; } diff --git a/src/App.tsx b/src/App.tsx index c4c800e..b468633 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2838,6 +2838,38 @@ function ScriptureIndexPage({ content }: { content: SiteContent }) { return promise } + // Verse of the week: the admin's pick if set, otherwise rotate through covered passages by calendar week + const [weekIndex] = useState(() => Math.floor(Date.now() / (7 * 24 * 60 * 60 * 1000))) + const verseOfWeek = useMemo(() => { + const override = content.verseOfTheWeek?.trim() + const parsed = override ? parseSIRef(override) : null + if (parsed) { + const key = `${parsed.bookId}-${parsed.chapter}-${parsed.verseStart}-${parsed.endChapter}-${parsed.verseEnd}` + const existing = bookGroups.flatMap(g => g.rows).find(r => r.key === key) + return existing ?? { + key, + displayRef: `${SI_BOOK_NAME[parsed.bookId]} ${parsed.chapter}:${parsed.verseStart}${parsed.verseEnd !== parsed.verseStart ? `-${parsed.verseEnd}` : ''}`, + bookId: parsed.bookId, chapter: parsed.chapter, endChapter: parsed.endChapter, + verseStart: parsed.verseStart, verseEnd: parsed.verseEnd, sources: [], + } as ScriptureRow + } + const pool = bookGroups.flatMap(g => g.rows) + if (pool.length === 0) return null + return pool[weekIndex % pool.length] + }, [bookGroups, content.verseOfTheWeek, weekIndex]) + const [votText, setVotText] = useState(null) + const votKey = verseOfWeek?.key + useEffect(() => { + if (!verseOfWeek || !verseOfWeek.bookId) return + let cancelled = false + const verseEnd = verseOfWeek.endChapter !== verseOfWeek.chapter ? Number.MAX_SAFE_INTEGER : verseOfWeek.verseEnd + fetchChapter(verseOfWeek.bookId, verseOfWeek.chapter) + .then(ch => { if (!cancelled) setVotText(extractVerseRange(ch, verseOfWeek.verseStart, verseEnd) || null) }) + .catch(() => { if (!cancelled) setVotText(null) }) + return () => { cancelled = true } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [votKey]) + function toggleVerse(row: ScriptureRow) { const open = shownVerses.has(row.key) setShownVerses(prev => { @@ -2921,6 +2953,24 @@ function ScriptureIndexPage({ content }: { content: SiteContent }) { + {!filtering && verseOfWeek && ( +
+

Verse of the week

+

{verseOfWeek.displayRef}

+ {votText &&

“{votText}”

} + {verseOfWeek.sources.length > 0 && ( +
+ {verseOfWeek.sources.slice(0, 3).map((src, i) => ( + + {src.type === 'episode' ? 'Episode' : 'Study'} + {src.label} + + ))} +
+ )} +
+ )} + {!filtering && topRows.length > 0 && (

Most-studied passages

diff --git a/src/content.ts b/src/content.ts index b031d77..0049230 100644 --- a/src/content.ts +++ b/src/content.ts @@ -319,6 +319,7 @@ export interface SiteContent { platformCreatorUrl: string platformKofiUrl?: string platformKofiQrUrl?: string + verseOfTheWeek?: string // ── Welcome Email ── welcomeEmailSubject: string welcomeEmailGreetingPrefix: string @@ -571,6 +572,7 @@ export const DEFAULTS: SiteContent = { platformCreatorUrl: 'https://creators.spotify.com/pod/profile/nmemmert/', platformKofiUrl: 'https://ko-fi.com/versebyversewithnate', platformKofiQrUrl: '/images/kofi-qr.png', + verseOfTheWeek: '', welcomeEmailSubject: 'Welcome to Verse by Verse with Nate', welcomeEmailGreetingPrefix: 'Glad you\'re here', welcomeEmailIntro: 'Thanks for subscribing to Verse by Verse with Nate - a Bible teaching podcast where we slow down, dig into the text, and pull out the nuggets God has for us word by word.',