2026-08-19 08:20:17 -04:00
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
|
import CandleCanvas from './Candle'
|
2026-08-19 08:33:56 -04:00
|
|
|
import { daySeed, loadKJV, msUntilRollover, pickForDay, type DailyVerse } from './verse'
|
2026-08-19 08:20:17 -04:00
|
|
|
|
|
|
|
|
type Phase = 'sealed' | 'revealing' | 'revealed'
|
|
|
|
|
|
|
|
|
|
function formatCountdown(ms: number): string {
|
|
|
|
|
const s = Math.max(0, Math.ceil(ms / 1000))
|
|
|
|
|
const h = Math.floor(s / 3600)
|
|
|
|
|
const m = Math.floor((s % 3600) / 60)
|
|
|
|
|
const sec = s % 60
|
|
|
|
|
return `${String(h).padStart(2, '0')}h ${String(m).padStart(2, '0')}m ${String(sec).padStart(2, '0')}s`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function Home() {
|
|
|
|
|
const [phase, setPhase] = useState<Phase>('sealed')
|
2026-08-19 08:33:56 -04:00
|
|
|
const [verse, setVerse] = useState<DailyVerse | null>(null)
|
2026-08-19 08:20:17 -04:00
|
|
|
const [countdown, setCountdown] = useState('')
|
|
|
|
|
const [error, setError] = useState(false)
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const update = () => setCountdown(formatCountdown(msUntilRollover()))
|
|
|
|
|
update()
|
|
|
|
|
const id = window.setInterval(update, 1000)
|
|
|
|
|
return () => window.clearInterval(id)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const seek = useCallback(async () => {
|
|
|
|
|
if (phase !== 'sealed') return
|
|
|
|
|
setPhase('revealing')
|
|
|
|
|
try {
|
|
|
|
|
const verses = await loadKJV()
|
|
|
|
|
setVerse(pickForDay(verses, daySeed()))
|
|
|
|
|
setPhase('revealed')
|
|
|
|
|
} catch {
|
|
|
|
|
setError(true)
|
|
|
|
|
setPhase('sealed')
|
|
|
|
|
}
|
|
|
|
|
}, [phase])
|
|
|
|
|
|
|
|
|
|
const today = new Date().toLocaleDateString(undefined, {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: 'long',
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="app">
|
|
|
|
|
<CandleCanvas />
|
|
|
|
|
|
|
|
|
|
<nav className="home-nav" aria-label="Site navigation">
|
|
|
|
|
<Link to="/read/Genesis/1">Read</Link>
|
|
|
|
|
<Link to="/search">Search</Link>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<header className="app-header">
|
|
|
|
|
<h1 className="title">Walk By Faith</h1>
|
|
|
|
|
<p className="tagline">
|
|
|
|
|
“For we walk by faith, not by sight.” <span className="tagline-ref">— 2 Corinthians 5:7</span>
|
|
|
|
|
</p>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<main className="app-main">
|
|
|
|
|
{phase !== 'revealed' && (
|
|
|
|
|
<section className="sealed" aria-label="Today's verse is sealed">
|
|
|
|
|
<div className="seal">
|
|
|
|
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
|
<path
|
|
|
|
|
d="M12 1.8C12 1.8 4.6 9.4 4.6 15a7.4 7.4 0 0 0 14.8 0c0-5.6-7.4-13.2-7.4-13.2z"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="1.1"
|
|
|
|
|
/>
|
|
|
|
|
<path
|
|
|
|
|
d="M12 8.4c0 0-3 4.2-3 7a3 3 0 0 0 6 0c0-2.8-3-7-3-7z"
|
|
|
|
|
fill="currentColor"
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="seal-caption">The verse for this day is sealed</p>
|
|
|
|
|
<button className="seek-btn" onClick={seek} disabled={phase === 'revealing'}>
|
|
|
|
|
{error ? 'Relight the lamp' : phase === 'revealing' ? 'Lighting the lamp…' : 'Seek the verse'}
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{phase === 'revealed' && verse && (
|
|
|
|
|
<section className="revealed" aria-live="polite">
|
|
|
|
|
<p className="reveal-label">The verse for {today}</p>
|
|
|
|
|
<div className="verse">
|
|
|
|
|
<p className="verse-text">{verse.text}</p>
|
|
|
|
|
<p className="verse-ref">
|
2026-08-19 08:33:56 -04:00
|
|
|
—{' '}
|
|
|
|
|
<Link to={`/read/${encodeURIComponent(verse.book)}/${verse.chapter}#v${verse.verse}`}>
|
|
|
|
|
{verse.reference}
|
|
|
|
|
</Link>
|
2026-08-19 08:20:17 -04:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
)}
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<footer className="app-footer">
|
|
|
|
|
<p className="countdown">
|
|
|
|
|
A new verse is appointed in <strong>{countdown}</strong>
|
|
|
|
|
</p>
|
|
|
|
|
<p className="foot-note">King James Version · Public Domain · The New Testament</p>
|
|
|
|
|
</footer>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|