// Page components for the Direction 3 standalone site. // pages.jsx — site pages // YouTube sometimes truncates titles with "..." in the API response; strip it // so CSS line-clamp doesn't add a second ellipsis on top. function stripTrailingEllipsis(s = '') { return s.replace(/[….]{2,}\s*$/, '').trimEnd(); } function LatestSermonBand({ lang, t, setRoute, accent }) { const [latest, setLatest] = React.useState(null); React.useEffect(() => { let cancelled = false; ytLatestPlayableVideo(YT.uploadsPlaylistId, lang).then((v) => { if (!cancelled) setLatest(v); }).catch(() => {}); return () => { cancelled = true; }; }, [lang]); return (
◆ 02 — {t.latest}

{lang === 'ko' ? '이번 주의 말씀' : 'This week, the Word'}

{e.preventDefault();setRoute('sermons');}}> {t.misc.viewAll} →
{latest ? : }
{lang === 'ko' ? '최근 업로드' : 'Latest uploads'}

{stripTrailingEllipsis(latest?.title) || (lang === 'ko' ? '이번 주 말씀을 들어보세요' : "Listen to this week's message")}

{latest?.publishedAt ? new Date(latest.publishedAt).toLocaleDateString(lang === 'ko' ? 'ko-KR' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : (lang === 'ko' ? '그리심소망교회 유튜브 채널' : 'Gerizim Hope YouTube channel')}
); } function HomePage({ lang, t, setRoute, layout, accent, accentTone }) { return (
{/* Hero variants */} {layout === 'fullbleed' &&
{t.indicator} / 2026

{lang === 'ko' ? '의와 영광의 빛.' : 'Light of righteousness.'}

{t.scriptures}
} {layout === 'split' &&
◆ {t.indicator}

{lang === 'ko' ? '의의 빛을\n발하라' : 'Shine\nthe light.'}

{t.tagline}

{t.scriptures}
} {layout === 'poster' &&
VOL. 20 — {t.indicator} NORCROSS, GA — EST. 2006

{lang === 'ko' ? <>의와 영광, 빛. : <>Right·eous.}

{t.tagline}

{t.scriptures}
} {/* Welcome */}
◆ 01 — {t.welcomeTitle}

{t.welcomeBody}

{e.preventDefault();setRoute('welcome');}}> {t.misc.forFirstTime} →
{/* Latest sermon */} {/* Services */}
◆ 03 — {lang === 'ko' ? '예배' : 'Worship'}

{lang === 'ko' ? '함께 모여' : 'Gather together'}

{t.services.map(([name, time, room, groups], i) =>
0{i + 1}
{name}
{time}
{room}
{groups?.length > 0 && (
    {groups.map(([gname, gtime]) => (
  • {gname} {gtime}
  • ))}
)}
)}
); } function AboutPage({ lang, t, accent, accentTone }) { return (
◆ {t.aboutKicker} / VOL. 20

{lang === 'ko' ? <>20년 빛. : <>Twenty years.}

{t.aboutLead}

{t.aboutNarrative.intro}

{t.aboutNarrative.origin}

{t.aboutNarrative.pillars.map(([heading, body], i) => (
◆ 0{i + 1}

{heading}

{body}

))}

{t.aboutNarrative.closing}

◆ {t.leadershipKicker}

{t.leadershipTitle}

{t.leadership.map(([name, role, tone, bio, creds, src], i) =>
{role}
{name}

{bio}

{creds && creds.length > 0 &&
    {creds.map((c, j) =>
  • {c}
  • )}
}
)}
{[ [lang === 'ko' ? '말씀' : 'Word', lang === 'ko' ? '구속사 강해를 통해 성경을 깊이 듣습니다.' : 'Hearing Scripture through redemptive-history exposition.'], [lang === 'ko' ? '기도' : 'Prayer', lang === 'ko' ? '토요 새벽 기도회로 함께 무릎 꿇습니다.' : 'Kneeling together at the Saturday dawn prayer gathering.'], [lang === 'ko' ? '교제' : 'Fellowship', lang === 'ko' ? '식탁과 교제 가운데 가족을 이룹니다.' : 'Becoming family around the table.']]. map(([h, body], i) =>
0{i + 1}
{h}

{body}

)}
); } function SermonsPage({ lang, t, accent }) { const [activePid, setActivePid] = React.useState(YT.playlists[0].id); const [videos, setVideos] = React.useState(null); // null = loading, [] = empty/no key const [activeVideoId, setActiveVideoId] = React.useState(null); const activeMeta = YT.playlists.find((p) => p.id === activePid) || YT.playlists[0]; React.useEffect(() => { let cancelled = false; setVideos(null); setActiveVideoId(null); ytFetchPlaylistItems(activePid, 50).then((items) => { if (cancelled) return; const filtered = (items || []).filter( v => lang === 'ko' ? ytIsKorean(v.title) : !ytIsKorean(v.title) ); setVideos(filtered); if (filtered.length) setActiveVideoId(filtered[0].videoId); }).catch(() => { if (!cancelled) setVideos([]); }); return () => { cancelled = true; }; }, [activePid, lang]); const activeVideo = videos && videos.find((v) => v.videoId === activeVideoId); const formatDate = (iso) => { if (!iso) return ''; try { return new Date(iso).toLocaleDateString(lang === 'ko' ? 'ko-KR' : 'en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } catch (_) { return ''; } }; return (
◆ {lang === 'ko' ? '말씀영상' : 'Sermons'}

{lang === 'ko' ? <>말씀, 깃발. : <>Word as banner.}

{/* Featured player */}
{activeVideoId ? : }
{lang === 'ko' ? '재생목록' : 'Playlist'}
{lang === 'ko' ? activeMeta.ko : activeMeta.en}

{stripTrailingEllipsis(activeVideo?.title) || (lang === 'ko' ? `${activeMeta.ko} 모음` : `${activeMeta.en} sermons`)}

{activeVideo?.publishedAt ? formatDate(activeVideo.publishedAt) : (lang === 'ko' ? '아래 목록에서 영상을 선택하세요.' : 'Pick a video from the list below.')}
{/* Playlist tabs */}
{YT.playlists.map((p) => ( ))}
{videos === null ? (lang === 'ko' ? '불러오는 중…' : 'Loading…') : (lang === 'ko' ? `${videos.length}개 영상` : `${videos.length} videos`)}
{/* Video grid */}
{videos === null && (
{lang === 'ko' ? '영상 목록을 불러오는 중입니다…' : 'Loading videos…'}
)} {videos && videos.length === 0 && (
{lang === 'ko' ? '영상을 표시할 수 없습니다.' : 'No videos to display.'}
)} {videos && videos.length > 0 && (
{videos.map((v) => ( ))}
)}
); } function EventsPage({ lang, t, accent }) { return (
◆ {lang === 'ko' ? '교회행사' : 'Events'} / 2026 — APR–JUN

{lang === 'ko' ? <>함께하는 . : <>Come together.}

{t.eventsList.map(([d, day, name, time, room], i) =>
{d}
{day}
{name}
{time} {room}
)}
); } function WelcomePage({ lang, t, setRoute, accent }) { return (
◆ {t.welcomePageKicker}

{lang === 'ko' ? <>문은 열려있다. : <>The door is open.}

{lang === 'ko' ? '처음 오시는 길이 어색하지 않도록, 한 걸음씩 함께 안내합니다.' : 'So your first visit feels familiar — we walk through it with you, one step at a time.'}

{t.welcomeSteps.map(([h, body], i) =>
0{i + 1}
{h}

{body}

)}
{t.misc.sundayLabel}
{t.misc.sundayTime}
{lang === 'ko' ? '본당' : 'Sanctuary'}
); } function Lightbox({ items, index, onClose, onPrev, onNext }) { React.useEffect(() => { document.body.style.overflow = 'hidden'; const onKey = (e) => { if (e.key === 'Escape') onClose(); if (e.key === 'ArrowLeft') onPrev(); if (e.key === 'ArrowRight') onNext(); }; window.addEventListener('keydown', onKey); return () => { document.body.style.overflow = ''; window.removeEventListener('keydown', onKey); }; }, [onClose, onPrev, onNext]); const [label, , tag, src] = items[index]; return (
e.stopPropagation()}> {label}
{tag} {label}
); } function GalleryPage({ lang, t, accent }) { const [lightboxIndex, setLightboxIndex] = React.useState(null); const count = t.galleryItems.length; return (
◆ {t.galleryKicker}

{lang === 'ko' ? <>함께한 자리. : <>Together.}

{t.galleryItems.map(([label, tone, tag, src], i) =>
{lightboxIndex !== null && ( setLightboxIndex(null)} onPrev={() => setLightboxIndex((lightboxIndex - 1 + count) % count)} onNext={() => setLightboxIndex((lightboxIndex + 1) % count)} /> )}
); } function ContactPage({ lang, t, accent }) { return (
◆ {lang === 'ko' ? '오시는 길' : 'Visit'}

{lang === 'ko' ? <>오시는 . : <>Come and visit.}