diff --git a/.gitignore b/.gitignore index 38bc024..c9aa086 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ data/nuggets-workflow.json # Live download counters — server data volume only data/download-counts.json + +# Generated audio (upload via Admin → Asset Manager, do not commit) +audio-out/ diff --git a/package.json b/package.json index 0861a40..2b1d4b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.39.2", + "version": "2.40.0", "type": "module", "scripts": { "dev": "vite", diff --git a/public/salvation/index.html b/public/salvation/index.html index 6cea367..ab5a014 100644 --- a/public/salvation/index.html +++ b/public/salvation/index.html @@ -628,6 +628,21 @@ @keyframes plea-glow { 0%, 100% { box-shadow: 0 0 0 4px rgba(184,134,11,0.3), 0 10px 30px rgba(0,0,0,0.5), 0 0 30px rgba(240,194,74,0.25); } 50% { box-shadow: 0 0 0 8px rgba(184,134,11,0.2), 0 10px 30px rgba(0,0,0,0.5), 0 0 55px rgba(240,194,74,0.55); } } @media (prefers-reduced-motion: reduce) { .plea-big { animation: none; } } @media (max-width: 600px) { .plea-big { display: block; padding: 1.15rem 0.6rem; font-size: 1.12rem; letter-spacing: 0.05em; white-space: nowrap; } .plea-big span { margin-left: 0.3rem; } } + + .audio-card { margin: 1.5rem 0 0; padding: 1.1rem 1.3rem; background: linear-gradient(145deg, rgba(139,26,26,0.3), rgba(26,9,5,0.85)); border: 1px solid var(--gold); border-radius: 4px; color: var(--parchment); } + .audio-card[hidden] { display: none; } + .audio-top { display: flex; align-items: center; gap: 0.9rem; } + .audio-icon { font-size: 1.8rem; } + .audio-title { font-family: 'Cinzel', serif; font-weight: 700; font-size: 1.05rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--gold-light); } + .audio-sub { font-size: 1rem; opacity: 0.9; font-style: italic; } + .audio-controls { display: flex; align-items: center; gap: 0.7rem; margin-top: 0.9rem; } + .audio-btn { font-family: 'Cinzel', serif; font-weight: 700; font-size: 0.8rem; letter-spacing: 0.05em; color: var(--parchment); background: transparent; border: 1px solid rgba(184,134,11,0.6); border-radius: 2px; padding: 0.55rem 0.7rem; cursor: pointer; white-space: nowrap; } + .audio-btn:hover { background: rgba(184,134,11,0.18); } + .audio-play { width: 2.9rem; height: 2.9rem; border-radius: 50%; padding: 0; font-size: 1rem; color: #1a0f05; background: var(--gold); border-color: var(--gold); } + .audio-play:hover { background: var(--gold-light); } + #audio-seek { flex: 1; min-width: 0; accent-color: var(--gold); height: 1.6rem; cursor: pointer; } + .audio-time { font-family: 'Cinzel', serif; font-size: 0.8rem; min-width: 4.6rem; text-align: right; color: var(--parchment-dark); white-space: nowrap; } + @media (max-width: 600px) { .audio-controls { flex-wrap: wrap; } #audio-seek { flex-basis: 100%; order: 5; } .audio-time { margin-left: auto; } } @@ -668,6 +683,24 @@
+ +
)} + {/* GOSPEL PAGE (/salvation) */} + {adminView === 'salvation' && ( +
+
+

Gospel Page

+

Settings for /salvation, The Gospel in a Courtroom.

+
+
+

Audio narration

+

An audio version of The Case, with a player at the top of the page. Leave this blank to hide the player.

+
+
+ + handleChange('salvationAudioUrl', e.target.value)} /> +
+ {form.salvationAudioUrl?.trim() && ( +
+ +
+ )} +

+ To make the file: put your ElevenLabs key and voice in your local .env, then run{' '} + node --env-file=.env scripts/generate-salvation-audio.mjs. Upload the MP3 in Asset Manager and paste its URL here, then Save and Publish. +

+
+ )} + {/* LEGAL */} {adminView === 'legal' && (
diff --git a/src/content.ts b/src/content.ts index 50e08d5..38e5992 100644 --- a/src/content.ts +++ b/src/content.ts @@ -285,6 +285,8 @@ export interface SiteContent { shareQrImageUrl: string // ── PRISM Video ── prismVideoUrl: string + // Audio narration of the /salvation page (served to the static page via /api/salvation-audio) + salvationAudioUrl: string prismEyebrow: string prismHeading: string prismIntro: string @@ -528,6 +530,7 @@ export const DEFAULTS: SiteContent = { shareP: 'Scan the QR code or text the show link to a friend who needs encouragement today.', shareQrImageUrl: '/images/qr-code.jpg', prismVideoUrl: 'https://files.versebyversewithnate.us/images/PRISM.mp4', + salvationAudioUrl: '', prismEyebrow: 'Featured Video', prismHeading: 'PRISM', prismIntro: