Redesign resources page with PageBanner, featured download, and consistent page headers; v1.1.40
Publish Container / docker (push) Has been cancelled
Publish Container / docker (push) Has been cancelled
- Replace hero study guide form with PageBanner + featured download card in library - Add PageBanner component used consistently across all interior pages (Downloads, Episodes, About, Contact, Q&A, Finished Books) - Remove duplicate headings/eyebrows that conflicted with banner titles on About, Contact, Q&A, Finished Books pages - Compact download card sizing (padding, border-radius, image size) - Add Books & Resources tabbed section with fallback to TruthForLife widget - Fix server sanitizer to pass through 'resources' placement items without URL - Add featured-badge and featured card styling for primary guide Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "siteforge",
|
||||
"private": true,
|
||||
"version": "1.1.39",
|
||||
"version": "1.1.40",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ function sanitizeCustomLinks(value) {
|
||||
placement,
|
||||
}
|
||||
})
|
||||
.filter(item => item.label && (item.url || item.amazonUrl || item.placement === 'books' || item.placement === 'recommended'))
|
||||
.filter(item => item.label && (item.url || item.amazonUrl || item.placement === 'books' || item.placement === 'recommended' || item.placement === 'resources'))
|
||||
}
|
||||
|
||||
function sanitizeCustomBlocks(value) {
|
||||
|
||||
+102
-12
@@ -4119,11 +4119,11 @@
|
||||
.resource-download-card {
|
||||
background: #111;
|
||||
border: 1px solid rgba(201, 168, 76, 0.18);
|
||||
border-radius: 18px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 14px;
|
||||
padding: 1rem 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
|
||||
@@ -4172,9 +4172,9 @@
|
||||
}
|
||||
|
||||
.resource-link-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 14px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgba(201, 168, 76, 0.2);
|
||||
flex-shrink: 0;
|
||||
@@ -4480,13 +4480,10 @@
|
||||
|
||||
/* ── Recommended Resources section ── */
|
||||
.recommended-resources-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.25rem;
|
||||
margin-top: 1rem;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.recommended-resource-card {
|
||||
@@ -4541,6 +4538,99 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Interior page banner ── */
|
||||
.section-page-banner {
|
||||
padding: 3.5rem 0 2rem;
|
||||
border-bottom: 1px solid rgba(201, 168, 76, 0.12);
|
||||
}
|
||||
|
||||
.page-banner-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.page-banner-title {
|
||||
font-family: var(--brand-font-heading);
|
||||
font-size: clamp(2rem, 4vw, 3rem);
|
||||
font-weight: 700;
|
||||
color: var(--brand-warm-white);
|
||||
margin: 0.15rem 0 0;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.page-banner-desc {
|
||||
font-family: var(--brand-font-body);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 300;
|
||||
color: var(--brand-muted);
|
||||
max-width: 52ch;
|
||||
margin: 0.25rem 0 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ── Featured download card ── */
|
||||
.resource-download-card--featured {
|
||||
margin-bottom: 1.5rem;
|
||||
border-color: rgba(201, 168, 76, 0.35);
|
||||
background: rgba(201, 168, 76, 0.04);
|
||||
}
|
||||
|
||||
.resource-link-image--featured {
|
||||
width: 80px !important;
|
||||
height: 110px !important;
|
||||
border-radius: 6px !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.featured-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--brand-gold);
|
||||
background: rgba(201, 168, 76, 0.12);
|
||||
border: 1px solid rgba(201, 168, 76, 0.3);
|
||||
border-radius: 4px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* ── Books + Recommended Resources tabs ── */
|
||||
.books-resources-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 1px solid rgba(201, 168, 76, 0.15);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.books-resources-tab {
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 0.6rem 1.1rem;
|
||||
margin-bottom: -1px;
|
||||
font-family: var(--brand-font-body);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
transition: color 160ms ease, border-color 160ms ease;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.books-resources-tab:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.books-resources-tab--active {
|
||||
color: var(--brand-gold);
|
||||
border-bottom-color: var(--brand-gold);
|
||||
}
|
||||
|
||||
/* ── Study Guide section ── */
|
||||
.section-guide {
|
||||
background: #0d0d0d;
|
||||
|
||||
+203
-163
@@ -267,11 +267,27 @@ interface DownloadPageResource {
|
||||
resourceId: string
|
||||
amazonUrl?: string
|
||||
amazonLabel?: string
|
||||
isPrimaryGuide?: boolean
|
||||
}
|
||||
|
||||
function resolveDownloadPageResource(content: SiteContent, pageId: string | undefined): DownloadPageResource | null {
|
||||
if (!pageId) return null
|
||||
|
||||
if (pageId === 'primary-guide') {
|
||||
return {
|
||||
id: 'primary-guide',
|
||||
label: content.studyGuideTitle || 'Companion Study Guide',
|
||||
imageUrl: content.seriesImageUrl || '/images/titus-cover.png',
|
||||
summary: content.studyGuideDescription || 'Complete the short form below and your download will start right away.',
|
||||
tags: [],
|
||||
buttonText: `Download ${content.studyGuideTitle || 'Guide'}`,
|
||||
resourceId: 'primary-guide',
|
||||
amazonUrl: content.studyGuideUrl,
|
||||
amazonLabel: content.studyGuideAmazonButtonLabel,
|
||||
isPrimaryGuide: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (pageId.startsWith('custom--')) {
|
||||
const customId = pageId.slice('custom--'.length)
|
||||
const resource = (content.customLinks ?? []).find(link => link.id === customId && link.placement === 'resources')
|
||||
@@ -543,7 +559,6 @@ function AboutSection({ content }: { content: SiteContent }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="about-text">
|
||||
<p className="eyebrow">{content.aboutShowEyebrow || 'About the Show'}</p>
|
||||
<h2>{content.aboutShowHeading}</h2>
|
||||
<p>{content.aboutShowP1}</p>
|
||||
<p>{content.aboutShowP2}</p>
|
||||
@@ -559,42 +574,6 @@ function AboutSection({ content }: { content: SiteContent }) {
|
||||
)
|
||||
}
|
||||
|
||||
function StudyGuideSection({ content }: { content: SiteContent }) {
|
||||
return (
|
||||
<section className="section-guide" aria-label="Companion study guide">
|
||||
<div className="section-inner guide-inner">
|
||||
<div className="guide-cover-art">
|
||||
<img
|
||||
src={content.seriesImageUrl || '/images/titus-cover.png'}
|
||||
alt={content.seriesTitle}
|
||||
className="guide-cover-img"
|
||||
/>
|
||||
</div>
|
||||
<div className="guide-text">
|
||||
<p className="eyebrow">Downloads</p>
|
||||
<h1 className="guide-page-title">Study Guides and Downloads</h1>
|
||||
<p className="guide-page-intro">Start with the primary guide below, then explore the rest of the download library further down the page.</p>
|
||||
<p className="eyebrow">Free Download</p>
|
||||
<h2>{content.studyGuideTitle}</h2>
|
||||
<p>{content.studyGuideDescription}</p>
|
||||
<StudyDownloadForm buttonText={`Download ${content.studyGuideTitle || 'Guide'}`} />
|
||||
{content.studyGuideUrl && (
|
||||
<div className="guide-actions">
|
||||
<a
|
||||
href={content.studyGuideUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn-secondary"
|
||||
>
|
||||
{content.studyGuideAmazonButtonLabel || 'Get it on Amazon'}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ContactSection({ content }: { content: SiteContent }) {
|
||||
return (
|
||||
@@ -602,9 +581,6 @@ function ContactSection({ content }: { content: SiteContent }) {
|
||||
<div className="section-inner contact-inner">
|
||||
<div className="contact-copy">
|
||||
<div className="contact-card">
|
||||
<p className="eyebrow">{content.contactEyebrow || 'Get in Touch'}</p>
|
||||
<h2>{content.contactHeading || 'Contact Nate'}</h2>
|
||||
|
||||
<div className="contact-profile">
|
||||
<img
|
||||
src={content.contactPhotoUrl || '/images/nate-contact-photo.png'}
|
||||
@@ -716,83 +692,116 @@ function PodcastHighlightsSection({ content }: { content: SiteContent }) {
|
||||
)
|
||||
}
|
||||
|
||||
function PageBanner({ eyebrow, title, desc }: { eyebrow: string; title: string; desc?: string }) {
|
||||
return (
|
||||
<section className="section-page-banner" aria-label={title}>
|
||||
<div className="section-inner page-banner-inner">
|
||||
<p className="eyebrow">{eyebrow}</p>
|
||||
<h1 className="page-banner-title">{title}</h1>
|
||||
{desc && <p className="page-banner-desc">{desc}</p>}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function DownloadLibrarySection({ content }: { content: SiteContent }) {
|
||||
const resources = (content.customLinks ?? []).filter(link => link.placement === 'resources')
|
||||
const [activeTag, setActiveTag] = useState<string | null>(null)
|
||||
|
||||
if (resources.length === 0) return null
|
||||
|
||||
const hasPrimaryGuide = !!(content.studyGuideTitle)
|
||||
const allTags = Array.from(new Set(resources.flatMap(r => r.tags ?? []))).filter(Boolean)
|
||||
|
||||
const filteredResources = resources.filter(r => {
|
||||
return !activeTag || (r.tags ?? []).includes(activeTag)
|
||||
})
|
||||
|
||||
if (!hasPrimaryGuide && resources.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="section-resources section-download-library" aria-label="Download library">
|
||||
<div className="section-inner">
|
||||
<div className="download-library-head">
|
||||
<p className="eyebrow">Download Library</p>
|
||||
<h2 className="section-heading">Guides, worksheets, and study downloads.</h2>
|
||||
</div>
|
||||
|
||||
{allTags.length > 0 && (
|
||||
<div className="download-filter-bar">
|
||||
<div className="download-tag-filter">
|
||||
<button
|
||||
type="button"
|
||||
className={`download-tag-chip${activeTag === null ? ' download-tag-chip--active' : ''}`}
|
||||
onClick={() => setActiveTag(null)}
|
||||
>
|
||||
All topics
|
||||
</button>
|
||||
{allTags.map(tag => (
|
||||
<button
|
||||
key={tag}
|
||||
type="button"
|
||||
className={`download-tag-chip${activeTag === tag ? ' download-tag-chip--active' : ''}`}
|
||||
onClick={() => setActiveTag(prev => prev === tag ? null : tag)}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
{activeTag !== null && (
|
||||
<button
|
||||
type="button"
|
||||
className="download-clear-link"
|
||||
onClick={() => setActiveTag(null)}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
{hasPrimaryGuide && (
|
||||
<Link to="/downloads/primary-guide" className="resource-download-card resource-download-card--link resource-download-card--featured">
|
||||
<div className="resource-download-header">
|
||||
{(content.seriesImageUrl || '/images/titus-cover.png') && (
|
||||
<img
|
||||
src={content.seriesImageUrl || '/images/titus-cover.png'}
|
||||
alt={content.seriesTitle}
|
||||
className="resource-link-image resource-link-image--featured"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
<div className="resource-download-meta">
|
||||
<div className="featured-badge">Featured</div>
|
||||
<span className="resource-link-label">{content.studyGuideTitle}</span>
|
||||
{content.studyGuideDescription && (
|
||||
<p className="resource-link-description">{content.studyGuideDescription}</p>
|
||||
)}
|
||||
<span className="resource-link-action">Download free guide →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{filteredResources.length > 0 ? (
|
||||
<div className="download-library-group">
|
||||
<div className="resources-list">
|
||||
{filteredResources.map(resource => (
|
||||
<Link key={resource.id} to={`/downloads/${buildCustomDownloadPageId(resource.id)}`} className="resource-download-card resource-download-card--link">
|
||||
<div className="resource-download-header">
|
||||
{resource.imageUrl && (
|
||||
<img src={resource.imageUrl} alt={resource.label} className="resource-link-image" loading="lazy" />
|
||||
)}
|
||||
<div className="resource-download-meta">
|
||||
<span className="resource-link-label">{resource.label}</span>
|
||||
{resource.description && <p className="resource-link-description">{resource.description}</p>}
|
||||
{(resource.tags ?? []).length > 0 && (
|
||||
<div className="resource-link-tags">{(resource.tags ?? []).join(', ')}</div>
|
||||
{resources.length > 0 && (
|
||||
<>
|
||||
{allTags.length > 0 && (
|
||||
<div className="download-filter-bar">
|
||||
<div className="download-tag-filter">
|
||||
<button
|
||||
type="button"
|
||||
className={`download-tag-chip${activeTag === null ? ' download-tag-chip--active' : ''}`}
|
||||
onClick={() => setActiveTag(null)}
|
||||
>
|
||||
All topics
|
||||
</button>
|
||||
{allTags.map(tag => (
|
||||
<button
|
||||
key={tag}
|
||||
type="button"
|
||||
className={`download-tag-chip${activeTag === tag ? ' download-tag-chip--active' : ''}`}
|
||||
onClick={() => setActiveTag(prev => prev === tag ? null : tag)}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
{activeTag !== null && (
|
||||
<button
|
||||
type="button"
|
||||
className="download-clear-link"
|
||||
onClick={() => setActiveTag(null)}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredResources.length > 0 ? (
|
||||
<div className="resources-list">
|
||||
{filteredResources.map(resource => (
|
||||
<Link key={resource.id} to={`/downloads/${buildCustomDownloadPageId(resource.id)}`} className="resource-download-card resource-download-card--link">
|
||||
<div className="resource-download-header">
|
||||
{resource.imageUrl && (
|
||||
<img src={resource.imageUrl} alt={resource.label} className="resource-link-image" loading="lazy" />
|
||||
)}
|
||||
<span className="resource-link-action">Open download page →</span>
|
||||
<div className="resource-download-meta">
|
||||
<span className="resource-link-label">{resource.label}</span>
|
||||
{resource.description && <p className="resource-link-description">{resource.description}</p>}
|
||||
{(resource.tags ?? []).length > 0 && (
|
||||
<div className="resource-link-tags">{(resource.tags ?? []).join(', ')}</div>
|
||||
)}
|
||||
<span className="resource-link-action">Open download page →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="download-empty-state">No downloads tagged "{activeTag}".</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="download-empty-state">No downloads tagged "{activeTag}".</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
@@ -834,7 +843,9 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
|
||||
)}
|
||||
|
||||
<div className="download-detail-form-wrap">
|
||||
<ResourceDownloadForm resourceId={resource.resourceId} buttonText={resource.buttonText} />
|
||||
{resource.isPrimaryGuide
|
||||
? <StudyDownloadForm buttonText={resource.buttonText} />
|
||||
: <ResourceDownloadForm resourceId={resource.resourceId} buttonText={resource.buttonText} />}
|
||||
</div>
|
||||
|
||||
<div className="download-detail-actions">
|
||||
@@ -1466,16 +1477,9 @@ function FinishedBooksPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<PageBanner eyebrow="Studies" title="Finished Books" desc="Every series we've completed — with a full ordered episode playlist for each book of the Bible." />
|
||||
<section className="section-finished-books">
|
||||
<div className="section-inner">
|
||||
<div className="finished-books-header">
|
||||
<h1 className="section-heading">
|
||||
<span className="ornament">✦</span> Finished Books <span className="ornament">✦</span>
|
||||
</h1>
|
||||
<p className="finished-books-intro">
|
||||
Every series we've completed — with a full ordered episode playlist for each book of the Bible.
|
||||
</p>
|
||||
</div>
|
||||
{books.length === 0 ? (
|
||||
<div className="finished-books-empty-state">
|
||||
<div className="finished-books-empty-icon">📖</div>
|
||||
@@ -1715,6 +1719,7 @@ function EpisodesPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<PageBanner eyebrow="Episodes" title="Podcast Episodes" desc={content.episodesSeoIntro || 'Expository Bible teaching — one verse at a time.'} />
|
||||
|
||||
<section className="section-player" aria-label="Current series episodes">
|
||||
<div className="section-inner">
|
||||
@@ -1738,64 +1743,97 @@ function EpisodesPage({ content }: { content: SiteContent }) {
|
||||
)
|
||||
}
|
||||
|
||||
function RecommendedResourcesSection({ content }: { content: SiteContent }) {
|
||||
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
|
||||
if (recommended.length === 0) return <TruthForLifeWidget type="resourcead" />
|
||||
return (
|
||||
<section className="section-tfl" aria-label="Recommended resources">
|
||||
<div className="section-inner">
|
||||
<div className="tfl-label-row">
|
||||
<h2 className="section-heading" style={{ marginBottom: '1rem' }}>
|
||||
<span className="ornament">✦</span> Recommended Resources <span className="ornament">✦</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="recommended-resources-list">
|
||||
{recommended.map(item => (
|
||||
<a key={item.id} href={item.url} target="_blank" rel="noopener noreferrer" className="recommended-resource-card">
|
||||
{item.imageUrl && (
|
||||
<img src={item.imageUrl} alt={item.label} className="recommended-resource-image" loading="lazy" />
|
||||
)}
|
||||
<div className="recommended-resource-meta">
|
||||
<span className="recommended-resource-title">{item.label}</span>
|
||||
{item.description && <p className="recommended-resource-description">{item.description}</p>}
|
||||
<span className="recommended-resource-action">Learn more →</span>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function BooksSectionSection({ content }: { content: SiteContent }) {
|
||||
function BooksAndResourcesSection({ content }: { content: SiteContent }) {
|
||||
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
|
||||
if (books.length === 0) return null
|
||||
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
|
||||
const [activeTab, setActiveTab] = useState<'books' | 'recommended'>('books')
|
||||
|
||||
const hasBooks = books.length > 0
|
||||
const hasRecommended = recommended.length > 0
|
||||
const showTabs = hasBooks && hasRecommended
|
||||
|
||||
if (!hasBooks && !hasRecommended) return <TruthForLifeWidget type="resourcead" />
|
||||
|
||||
const tab = showTabs ? activeTab : (hasBooks ? 'books' : 'recommended')
|
||||
|
||||
return (
|
||||
<section className="section-resources section-books" aria-label="Recommended books">
|
||||
<section className="section-resources section-books-resources" aria-label="Books and recommended resources">
|
||||
<div className="section-inner">
|
||||
<div className="download-library-head">
|
||||
<p className="eyebrow">Reading List</p>
|
||||
<h2 className="section-heading">Books worth your time.</h2>
|
||||
</div>
|
||||
<div className="books-list">
|
||||
{books.map(book => (
|
||||
<div key={book.id} className="book-card">
|
||||
{book.imageUrl && (
|
||||
<img src={book.imageUrl} alt={book.label} className="book-card-cover" loading="lazy" />
|
||||
)}
|
||||
<div className="book-card-meta">
|
||||
<span className="book-card-title">{book.label}</span>
|
||||
{book.description && <p className="book-card-description">{book.description}</p>}
|
||||
{(book.amazonUrl || book.url) && (
|
||||
<a href={book.amazonUrl || book.url} target="_blank" rel="noopener noreferrer" className="btn-primary book-card-btn">
|
||||
{book.amazonUrl ? (book.amazonLabel || 'View on Amazon') : (book.amazonLabel || 'Learn More')}
|
||||
</a>
|
||||
)}
|
||||
{showTabs ? (
|
||||
<div className="books-resources-tabs">
|
||||
<button
|
||||
type="button"
|
||||
className={`books-resources-tab${tab === 'books' ? ' books-resources-tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('books')}
|
||||
>
|
||||
Reading List
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`books-resources-tab${tab === 'recommended' ? ' books-resources-tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('recommended')}
|
||||
>
|
||||
Recommended Resources
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="download-library-head">
|
||||
<p className="eyebrow">{tab === 'books' ? 'Reading List' : 'Recommended'}</p>
|
||||
<h2 className="section-heading">{tab === 'books' ? 'Books worth your time.' : 'Resources worth your time.'}</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'books' && (
|
||||
<>
|
||||
{showTabs && (
|
||||
<div className="download-library-head" style={{ marginBottom: '1.5rem' }}>
|
||||
<h2 className="section-heading">Books worth your time.</h2>
|
||||
</div>
|
||||
)}
|
||||
<div className="books-list">
|
||||
{books.map(book => (
|
||||
<div key={book.id} className="book-card">
|
||||
{book.imageUrl && (
|
||||
<img src={book.imageUrl} alt={book.label} className="book-card-cover" loading="lazy" />
|
||||
)}
|
||||
<div className="book-card-meta">
|
||||
<span className="book-card-title">{book.label}</span>
|
||||
{book.description && <p className="book-card-description">{book.description}</p>}
|
||||
{(book.amazonUrl || book.url) && (
|
||||
<a href={book.amazonUrl || book.url} target="_blank" rel="noopener noreferrer" className="btn-primary book-card-btn">
|
||||
{book.amazonUrl ? (book.amazonLabel || 'View on Amazon') : (book.amazonLabel || 'Learn More')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{tab === 'recommended' && (
|
||||
<>
|
||||
{showTabs && (
|
||||
<div className="download-library-head" style={{ marginBottom: '1.5rem' }}>
|
||||
<h2 className="section-heading">Recommended Resources</h2>
|
||||
</div>
|
||||
)}
|
||||
<div className="recommended-resources-list">
|
||||
{recommended.map(item => (
|
||||
<a key={item.id} href={item.url} target="_blank" rel="noopener noreferrer" className="recommended-resource-card">
|
||||
{item.imageUrl && (
|
||||
<img src={item.imageUrl} alt={item.label} className="recommended-resource-image" loading="lazy" />
|
||||
)}
|
||||
<div className="recommended-resource-meta">
|
||||
<span className="recommended-resource-title">{item.label}</span>
|
||||
{item.description && <p className="recommended-resource-description">{item.description}</p>}
|
||||
<span className="recommended-resource-action">Learn more →</span>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
@@ -1811,11 +1849,10 @@ function ResourcesPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<StudyGuideSection content={content} />
|
||||
<PageBanner eyebrow="Downloads" title="Study Guides & Resources" desc="Free guides, worksheets, and curated resources to help you go deeper in Scripture." />
|
||||
<DownloadLibrarySection content={content} />
|
||||
<BooksSectionSection content={content} />
|
||||
<CustomBlocksSection content={content} page="downloads" />
|
||||
<RecommendedResourcesSection content={content} />
|
||||
<BooksAndResourcesSection content={content} />
|
||||
<SiteFooter content={content} />
|
||||
<AnalyticsConsentBanner content={content} />
|
||||
</div>
|
||||
@@ -1832,6 +1869,7 @@ function AboutPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<PageBanner eyebrow="About" title="About the Show" />
|
||||
<AboutSection content={content} />
|
||||
<TestimonialsSection testimonials={content.testimonials ?? []} />
|
||||
<CustomBlocksSection content={content} page="about" />
|
||||
@@ -1846,6 +1884,7 @@ function ContactPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<PageBanner eyebrow="Contact" title={content.contactHeading || 'Get in Touch'} />
|
||||
<ContactSection content={content} />
|
||||
<CustomBlocksSection content={content} page="contact" />
|
||||
<SiteFooter content={content} />
|
||||
@@ -2367,6 +2406,7 @@ function QuestionsPage({ content }: { content: SiteContent }) {
|
||||
<div className="site">
|
||||
<SiteHeader content={content} />
|
||||
<SiteSearchBar content={content} />
|
||||
<PageBanner eyebrow="Q&A" title="Questions & Answers" desc="Real questions answered from Scripture — browse by topic or submit your own." />
|
||||
<QASection />
|
||||
<CustomBlocksSection content={content} page="questions" />
|
||||
<SiteFooter content={content} />
|
||||
|
||||
@@ -783,10 +783,6 @@ export default function QASection() {
|
||||
return (
|
||||
<section id="qa" className="section-qa" aria-label="Questions and answers">
|
||||
<div className="section-inner">
|
||||
<h2 className="section-heading">
|
||||
<span className="ornament">✦</span> Questions & Answers <span className="ornament">✦</span>
|
||||
</h2>
|
||||
|
||||
<div className="qa-submit-cta">
|
||||
<p className="qa-submit-cta-text">Have a question about Scripture or the podcast?</p>
|
||||
<Link to="/contact" className="btn-primary">Submit a Question →</Link>
|
||||
|
||||
Reference in New Issue
Block a user