Refine homepage sections and contact card design
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
@@ -1404,6 +1404,70 @@ app.delete('/api/admin-questions/:id', requireAdminAuth, (req, res) => {
|
|||||||
|
|
||||||
res.json({ ok: true })
|
res.json({ ok: true })
|
||||||
})
|
})
|
||||||
|
// ── Episodes (RSS feed proxy) ──────────────────────────────────────────────
|
||||||
|
const RSS_FEED_URL = 'https://anchor.fm/nmemmert/podcast/rss'
|
||||||
|
let episodesCache = null
|
||||||
|
let episodesCacheAt = 0
|
||||||
|
const EPISODES_CACHE_TTL = 30 * 60 * 1000 // 30 minutes
|
||||||
|
|
||||||
|
function extractCdata(raw) {
|
||||||
|
const cdata = /^<!\[CDATA\[([\s\S]*?)\]\]>$/.exec(raw.trim())
|
||||||
|
return cdata ? cdata[1].trim() : raw.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRssItems(xml, limit = 6) {
|
||||||
|
const items = []
|
||||||
|
const itemRegex = /<item>([\s\S]*?)<\/item>/g
|
||||||
|
let match
|
||||||
|
while ((match = itemRegex.exec(xml)) !== null && items.length < limit) {
|
||||||
|
const block = match[1]
|
||||||
|
const titleRaw = /<title>([\s\S]*?)<\/title>/.exec(block)?.[1] ?? ''
|
||||||
|
const title = extractCdata(titleRaw)
|
||||||
|
if (!title) continue
|
||||||
|
|
||||||
|
const pubDate = (/<pubDate>([\s\S]*?)<\/pubDate>/.exec(block)?.[1] ?? '').trim()
|
||||||
|
const guidRaw = /<guid[^>]*>([\s\S]*?)<\/guid>/.exec(block)?.[1] ?? ''
|
||||||
|
const guid = extractCdata(guidRaw)
|
||||||
|
const enclosureUrl = /<enclosure[^>]+url="([^"]+)"/.exec(block)?.[1] ?? ''
|
||||||
|
const link = guid.startsWith('http') ? guid : enclosureUrl
|
||||||
|
const descRaw = /<description>([\s\S]*?)<\/description>/.exec(block)?.[1] ?? ''
|
||||||
|
const descText = extractCdata(descRaw).replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim()
|
||||||
|
const duration = (/<itunes:duration>([\s\S]*?)<\/itunes:duration>/.exec(block)?.[1] ?? '').trim()
|
||||||
|
const episode = (/<itunes:episode>([\s\S]*?)<\/itunes:episode>/.exec(block)?.[1] ?? '').trim()
|
||||||
|
items.push({
|
||||||
|
title,
|
||||||
|
pubDate,
|
||||||
|
link,
|
||||||
|
description: descText.slice(0, 220) + (descText.length > 220 ? '…' : ''),
|
||||||
|
duration,
|
||||||
|
episode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/api/episodes', async (_req, res) => {
|
||||||
|
const now = Date.now()
|
||||||
|
if (episodesCache && (now - episodesCacheAt) < EPISODES_CACHE_TTL) {
|
||||||
|
return res.json({ episodes: episodesCache })
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 8000)
|
||||||
|
const response = await fetch(RSS_FEED_URL, { signal: controller.signal })
|
||||||
|
clearTimeout(timeout)
|
||||||
|
if (!response.ok) throw new Error(`RSS fetch failed: ${response.status}`)
|
||||||
|
const xml = await response.text()
|
||||||
|
const episodes = parseRssItems(xml, 6)
|
||||||
|
episodesCache = episodes
|
||||||
|
episodesCacheAt = now
|
||||||
|
res.json({ episodes })
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[episodes] RSS fetch error:', err.message)
|
||||||
|
res.json({ episodes: episodesCache ?? [] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.use(express.static(DIST_DIR))
|
app.use(express.static(DIST_DIR))
|
||||||
|
|
||||||
app.use(async (_req, res) => {
|
app.use(async (_req, res) => {
|
||||||
|
|||||||
+244
-29
@@ -267,6 +267,119 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Episode List ── */
|
||||||
|
.episode-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-card {
|
||||||
|
display: block;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(201, 168, 76, 0.2);
|
||||||
|
border-left: 3px solid var(--brand-gold);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1.1rem 1.4rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 180ms, border-color 180ms, transform 180ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-card:hover {
|
||||||
|
background: rgba(201, 168, 76, 0.07);
|
||||||
|
border-color: rgba(201, 168, 76, 0.45);
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.85rem;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-number {
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--brand-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-date,
|
||||||
|
.episode-duration {
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--brand-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-date::before {
|
||||||
|
content: '·';
|
||||||
|
margin-right: 0.85rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-duration::before {
|
||||||
|
content: '·';
|
||||||
|
margin-right: 0.85rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-title {
|
||||||
|
font-family: var(--brand-font-heading);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--brand-warm-white);
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-desc {
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--brand-muted);
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-listen-cta {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--brand-gold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-list-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-skeleton {
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-shimmer 1.4s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
.platform-buttons {
|
.platform-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -607,10 +720,11 @@
|
|||||||
|
|
||||||
.qr-frame {
|
.qr-frame {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 12px;
|
padding: 16px 18px;
|
||||||
background: #f5eed8;
|
background: #ffffff;
|
||||||
border-radius: 8px;
|
border-radius: 24px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-code {
|
.qr-code {
|
||||||
@@ -734,28 +848,120 @@
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-copy .section-heading {
|
.contact-card {
|
||||||
text-align: left;
|
background: #111111;
|
||||||
margin-bottom: 1rem;
|
border: 1px solid rgba(201, 168, 76, 0.2);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.3rem;
|
||||||
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-copy p {
|
.contact-card .eyebrow {
|
||||||
|
margin: 0 0 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-card h2 {
|
||||||
|
margin: 0 0 1.75rem;
|
||||||
|
font-family: var(--brand-font-heading);
|
||||||
|
color: var(--brand-warm-white);
|
||||||
|
font-size: clamp(1.45rem, 2vw, 1.9rem);
|
||||||
|
line-height: 1.3;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-profile {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-profile-photo {
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
border-radius: 999px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: top;
|
||||||
|
border: 2px solid var(--brand-gold);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-profile-name {
|
||||||
|
margin: 0 0 0.15rem;
|
||||||
font-family: var(--brand-font-body);
|
font-family: var(--brand-font-body);
|
||||||
font-weight: 300;
|
font-size: 1.45rem;
|
||||||
font-size: 1.08rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: var(--brand-muted);
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-copy .contact-highlight {
|
|
||||||
font-size: clamp(1.25rem, 2.2vw, 1.65rem);
|
|
||||||
line-height: 1.35;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--brand-warm-white);
|
color: var(--brand-warm-white);
|
||||||
margin: 0 0 0.6rem;
|
}
|
||||||
text-wrap: balance;
|
|
||||||
|
.contact-profile-role {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--brand-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-quote {
|
||||||
|
border-left: 2px solid var(--brand-gold);
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin: 0 0 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-quote p {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #d4c4a0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-intro {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #9a9994;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-points {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-point {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
color: #8f8e89;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-point svg {
|
||||||
|
color: var(--brand-gold);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-scripture {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid #2c2116;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-scripture p {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #6d6b66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-scripture-ref {
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-form {
|
.contact-form {
|
||||||
@@ -1111,19 +1317,22 @@
|
|||||||
|
|
||||||
.guide-inner {
|
.guide-inner {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px 1fr;
|
grid-template-columns: 160px 1fr;
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
align-items: center;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-icon {
|
.guide-cover-art {
|
||||||
width: 100px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-icon svg {
|
.guide-cover-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-text h2 {
|
.guide-text h2 {
|
||||||
@@ -2290,8 +2499,9 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-icon {
|
.guide-cover-art {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.study-download-grid {
|
.study-download-grid {
|
||||||
@@ -2319,7 +2529,7 @@
|
|||||||
|
|
||||||
.contact-inner {
|
.contact-inner {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
text-align: center;
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatbot-feature-inner {
|
.chatbot-feature-inner {
|
||||||
@@ -2335,8 +2545,13 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-copy .section-heading {
|
.contact-card {
|
||||||
text-align: center;
|
max-width: 100%;
|
||||||
|
padding: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-quote p {
|
||||||
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-ornament {
|
.header-ornament {
|
||||||
|
|||||||
+139
-26
@@ -640,6 +640,94 @@ function ShareShowButton() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Episode {
|
||||||
|
title: string
|
||||||
|
pubDate: string
|
||||||
|
link: string
|
||||||
|
description: string
|
||||||
|
duration: string
|
||||||
|
episode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPubDate(raw: string): string {
|
||||||
|
try {
|
||||||
|
return new Date(raw).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
|
||||||
|
} catch {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function LatestEpisodesList() {
|
||||||
|
const [episodes, setEpisodes] = useState<Episode[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [failed, setFailed] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/episodes')
|
||||||
|
.then(r => (r.ok ? r.json() : Promise.reject(new Error('fetch failed'))))
|
||||||
|
.then((data: { episodes: Episode[] }) => {
|
||||||
|
if (data.episodes.length === 0) setFailed(true)
|
||||||
|
else setEpisodes(data.episodes)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setFailed(true)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="episode-list-loading">
|
||||||
|
{[1, 2, 3].map(i => <div key={i} className="episode-skeleton" aria-hidden="true" />)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed) {
|
||||||
|
return (
|
||||||
|
<div className="embed-wrap">
|
||||||
|
<iframe
|
||||||
|
title="Verse by Verse with Nate"
|
||||||
|
src={SPOTIFY_EMBED_URL}
|
||||||
|
width="100%"
|
||||||
|
height="352"
|
||||||
|
frameBorder="0"
|
||||||
|
allowFullScreen
|
||||||
|
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||||
|
loading="lazy"
|
||||||
|
style={{ borderRadius: '14px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="episode-list">
|
||||||
|
{episodes.map((ep, idx) => (
|
||||||
|
<a
|
||||||
|
key={idx}
|
||||||
|
href={ep.link || SPOTIFY_SHOW_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="episode-card"
|
||||||
|
>
|
||||||
|
<div className="episode-card-meta">
|
||||||
|
{ep.episode && <span className="episode-number">Ep. {ep.episode}</span>}
|
||||||
|
{ep.pubDate && <span className="episode-date">{formatPubDate(ep.pubDate)}</span>}
|
||||||
|
{ep.duration && <span className="episode-duration">{ep.duration}</span>}
|
||||||
|
</div>
|
||||||
|
<h3 className="episode-title">{ep.title}</h3>
|
||||||
|
{ep.description && <p className="episode-desc">{ep.description}</p>}
|
||||||
|
<span className="episode-listen-cta">
|
||||||
|
<SpotifyIcon /> Listen →
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function LandingPage({ content }: { content: SiteContent }) {
|
function LandingPage({ content }: { content: SiteContent }) {
|
||||||
const archivedSeries = content.archivedSeries ?? []
|
const archivedSeries = content.archivedSeries ?? []
|
||||||
const [seriesView, setSeriesView] = useState<'current' | 'archive'>('current')
|
const [seriesView, setSeriesView] = useState<'current' | 'archive'>('current')
|
||||||
@@ -739,19 +827,7 @@ function LandingPage({ content }: { content: SiteContent }) {
|
|||||||
<span className="ornament">✦</span> Latest Episodes{' '}
|
<span className="ornament">✦</span> Latest Episodes{' '}
|
||||||
<span className="ornament">✦</span>
|
<span className="ornament">✦</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="embed-wrap">
|
<LatestEpisodesList />
|
||||||
<iframe
|
|
||||||
title="Verse by Verse with Nate"
|
|
||||||
src={SPOTIFY_EMBED_URL}
|
|
||||||
width="100%"
|
|
||||||
height="352"
|
|
||||||
frameBorder="0"
|
|
||||||
allowFullScreen
|
|
||||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
|
||||||
loading="lazy"
|
|
||||||
style={{ borderRadius: '14px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="platform-buttons">
|
<div className="platform-buttons">
|
||||||
<a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer" className="platform-btn spotify-btn">
|
<a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer" className="platform-btn spotify-btn">
|
||||||
<SpotifyIcon />
|
<SpotifyIcon />
|
||||||
@@ -977,14 +1053,12 @@ function LandingPage({ content }: { content: SiteContent }) {
|
|||||||
{/* ── STUDY GUIDE ── */}
|
{/* ── STUDY GUIDE ── */}
|
||||||
<section className="section-guide" aria-label="Companion study guide">
|
<section className="section-guide" aria-label="Companion study guide">
|
||||||
<div className="section-inner guide-inner">
|
<div className="section-inner guide-inner">
|
||||||
<div className="guide-icon" aria-hidden="true">
|
<div className="guide-cover-art">
|
||||||
<svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<img
|
||||||
<rect x="8" y="6" width="36" height="48" rx="3" fill="#c8860a" opacity="0.15" stroke="#c8860a" strokeWidth="1.5"/>
|
src={content.seriesImageUrl || '/images/titus-cover.png'}
|
||||||
<rect x="14" y="6" width="36" height="48" rx="3" fill="#0f0f0f" stroke="#c8860a" strokeWidth="1.5"/>
|
alt={content.seriesTitle}
|
||||||
<line x1="22" y1="22" x2="42" y2="22" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round"/>
|
className="guide-cover-img"
|
||||||
<line x1="22" y1="30" x2="42" y2="30" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round" opacity="0.6"/>
|
/>
|
||||||
<line x1="22" y1="38" x2="34" y2="38" stroke="#c8860a" strokeWidth="1.5" strokeLinecap="round" opacity="0.4"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="guide-text">
|
<div className="guide-text">
|
||||||
<p className="eyebrow">Free Download</p>
|
<p className="eyebrow">Free Download</p>
|
||||||
@@ -1044,14 +1118,53 @@ function LandingPage({ content }: { content: SiteContent }) {
|
|||||||
<section className="section-contact" id="contact" aria-label="Contact form">
|
<section className="section-contact" id="contact" aria-label="Contact form">
|
||||||
<div className="section-inner contact-inner">
|
<div className="section-inner contact-inner">
|
||||||
<div className="contact-copy">
|
<div className="contact-copy">
|
||||||
|
<div className="contact-card">
|
||||||
<p className="eyebrow">Get in Touch</p>
|
<p className="eyebrow">Get in Touch</p>
|
||||||
<h2 className="section-heading">
|
<h2>Contact Nate</h2>
|
||||||
<span className="ornament">✦</span> Contact Nate <span className="ornament">✦</span>
|
|
||||||
</h2>
|
<div className="contact-profile">
|
||||||
<p className="contact-highlight">
|
<img
|
||||||
|
src="/images/nate-contact-photo.png"
|
||||||
|
alt="Nate"
|
||||||
|
className="contact-profile-photo"
|
||||||
|
/>
|
||||||
|
<div className="contact-profile-meta">
|
||||||
|
<p className="contact-profile-name">Nate</p>
|
||||||
|
<p className="contact-profile-role">Bible teacher · Lynchburg, VA</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<blockquote className="contact-quote">
|
||||||
|
<p>"I read every message - nothing blesses me more than hearing how God's Word is at work in your life."</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p className="contact-intro">
|
||||||
Ask a Bible question, share a testimony, or request a topic for a future episode.
|
Ask a Bible question, share a testimony, or request a topic for a future episode.
|
||||||
</p>
|
</p>
|
||||||
<p>We usually respond within 48 hours.</p>
|
|
||||||
|
<div className="contact-points" aria-label="Contact response details">
|
||||||
|
<div className="contact-point">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||||
|
</svg>
|
||||||
|
<span>I read every message personally</span>
|
||||||
|
</div>
|
||||||
|
<div className="contact-point">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
<span>Response within 48 hours</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="contact-scripture">
|
||||||
|
<p>
|
||||||
|
"Your word is a lamp to my feet and a light to my path." -{' '}
|
||||||
|
<span className="contact-scripture-ref">Psalm 119:105</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user