// blog-page.jsx — Resources / Insights listing for Green Integrations. // Variant-grid layout matching Claude's design for the "All Articles" tab, // uniform 3-col grid for filtered tabs. Data from /wp-json/claude-pages/v1/posts. const API_BASE = '/wp-json/claude-pages/v1'; // "All" tab loads up to 19 posts in one editorial layout (matches Claude design). // Filtered tabs paginate 9 at a time. const ALL_PER_PAGE = 14; const FILTERED_PER_PAGE = 9; // Category presentation — MUST stay in sync with blog-endpoints.php and blog-post-page.jsx. const CAT_DISPLAY = { 'power-solar-storage': { label: 'Power · Solar & Storage', chipBg: '#E8F2EC', chipFg: '#1E4D2B', accent: '#2A4A38' }, 'transform-ev-electrification':{ label: 'Transform · EV & Electrification', chipBg: '#E4EBF6', chipFg: '#1A3562', accent: '#1F3556' }, 'policy-compliance': { label: 'Policy & Compliance', chipBg: '#F7EDDA', chipFg: '#7A5010', accent: '#C9952E' }, 'industry-focus': { label: 'Industry Focus', chipBg: '#E4EEF5', chipFg: '#1A4060', accent: '#21455F' }, 'market-technology': { label: 'Market & Technology', chipBg: '#F0EBF7', chipFg: '#4A2D6B', accent: '#3A2D55' }, }; const TABS = [ { id: 'all', label: 'All Articles' }, { id: 'power-solar-storage', label: 'Power · Solar & Storage' }, { id: 'transform-ev-electrification', label: 'Transform · EV & Electrification' }, { id: 'policy-compliance', label: 'Policy & Compliance' }, { id: 'industry-focus', label: 'Industry Focus' }, { id: 'market-technology', label: 'Market & Technology' }, ]; // ============================================================ // Primitives // ============================================================ const MonoCaption = ({ children, color = 'rgba(255,255,255,0.42)', tracking = '0.16em', size = 10 }) => {children}; const CategoryChip = ({ cat }) => { if (!cat || !CAT_DISPLAY[cat.slug]) return null; const c = CAT_DISPLAY[cat.slug]; return ( {cat.label} ); }; const Divider = ({ marginTop = 24, marginBottom = 24 }) =>
; const RowLabel = ({ label, link = null, marginTop = 36 }) =>
{label} {link && ( {link} )}
; // ============================================================ // ArticleCard — renders one card. `size` controls layout proportions per // guide §4.3: large (190px image, 16px headline), standard (130px, 15px), // compact (110px, no excerpt, tags only). Wide & narrow share standard. // ============================================================ const ArticleCard = ({ post, size = 'standard' }) => { const href = post.permalink || ('/resources/insights/' + post.slug + '/'); const cfg = (() => { switch (size) { case 'large': return { imgHeight: 190, padding: '22px 22px 18px', titleFont: 16, titleLh: 1.32, showExcerpt: true, showTags: true }; case 'wide': return { imgHeight: 190, padding: '22px 22px 18px', titleFont: 16, titleLh: 1.32, showExcerpt: true, showTags: true }; case 'standard': return { imgHeight: 130, padding: '18px 20px 16px', titleFont: 15, titleLh: 1.36, showExcerpt: true, showTags: true }; case 'narrow': return { imgHeight: 130, padding: '18px 20px 16px', titleFont: 15, titleLh: 1.36, showExcerpt: true, showTags: true }; case 'compact': return { imgHeight: 110, padding: '14px 18px 14px', titleFont: 14, titleLh: 1.36, showExcerpt: false, showTags: true }; default: return { imgHeight: 130, padding: '18px 20px 16px', titleFont: 15, titleLh: 1.36, showExcerpt: true, showTags: true }; } })(); // Tags: max 2 (per guide §4.1). Hide if no tags or compact card with no space. const tagsToShow = (post.tags || []).slice(0, 2); const showTagsRow = cfg.showTags && tagsToShow.length > 0; return (
{post.featured_image ? {post.featured_image_alt :
} {post.category && }

{post.title}

{cfg.showExcerpt && post.excerpt && (

{post.excerpt}

)} {showTagsRow && (
{tagsToShow.map((tag) => ( {tag.label} ))}
)} {/* Footer: date + Read → arrow, with thin border-top */}
{post.date_formatted} Read
); }; // ============================================================ // FeaturedPanel — large sticky/editorial card at the top of "all" view // Per guide §2.3: // - Badge "Featured · [Category]" in amber, bottom-left of image panel // - Category label above headline in amber: "Category · Tag1 · Tag2" // - 2-sentence excerpt (server-side truncates) // - "Read article →" CTA in accent green // ============================================================ const FeaturedPanel = ({ post }) => { if (!post) return null; const href = post.permalink || ('/resources/insights/' + post.slug + '/'); const cat = post.category && CAT_DISPLAY[post.category.slug] ? post.category : null; // Build the amber category label: "Category · Tag1 · Tag2" (up to 2 tags) const labelParts = []; if (post.category) labelParts.push(post.category.label); (post.tags || []).slice(0, 2).forEach((t) => labelParts.push(t.label)); const amberLabel = labelParts.join(' · '); return (
{post.featured_image ? {post.featured_image_alt :
} {/* "Featured · [Category]" badge, bottom-left of image panel, amber */} {cat && (
Featured · {post.category.label}
)}
{/* Amber category label above headline */} {amberLabel && (
{amberLabel}
)}

{post.title}

{post.excerpt &&

{post.excerpt}

}
Read article {post.date_formatted} · {post.read_minutes} min read
); }; // ============================================================ // BlogHeader — navy header band with title + tabs (per guide §2.1, §2.2) // ============================================================ const BlogHeader = ({ activeTab, setActiveTab, counts }) => (
Green Integrations · Insights {/* H1 per guide §2.1: "Energy insights for C&I operators. No filler." with amber accent on "No filler." */}

Energy insights for C&I operators.{' '} No filler.

Commercial and industrial energy strategy, policy, and market coverage — written for decision-makers.

{TABS.map((tab) => { const active = activeTab === tab.id; const count = counts ? (counts[tab.id] || 0) : 0; return ( ); })}
); // ============================================================ // VariantGrid — Claude's editorial layout for "all" view (per guide §2.4) // 5 rows: 2 large · 3 standard · (1 wide + 2 narrow) · 3 standard · 3 compact // = 14 posts per page. Allocate from items in order; skip rows we can't fill. // ============================================================ const VariantGrid = ({ items }) => { let i = 0; const take = (n) => { const slice = items.slice(i, i + n); i += slice.length; return slice; }; const row1 = take(2); // 2 large const row2 = take(3); // 3 standard const row3 = take(3); // 1 wide + 2 narrow const row4 = take(3); // 3 standard const row5 = take(3); // 3 compact return (
{row1.length > 0 && (<>
{row1.map((p) => )}
)} {row2.length > 0 && (
{row2.map((p) => )}
)} {row3.length > 0 && (<>
{row3.map((p, idx) => )}
)} {row4.length > 0 && (
{row4.map((p) => )}
)} {row5.length > 0 && (<>
{row5.map((p) => )}
)}
); }; // ============================================================ // UniformGrid — 3-column grid for filtered tabs + paginated pages // ============================================================ const UniformGrid = ({ items, label }) => (
{label && } {items.length > 0 ? (
{items.map((p) => )}
) : (
No articles in this category yet.
)}
); // ============================================================ // Pagination — numbered // ============================================================ const paginationBtn = (disabled, active) => ({ padding: '10px 14px', minWidth: 40, cursor: disabled ? 'default' : 'pointer', background: active ? 'var(--gi-deep-ink, #0D1C35)' : '#fff', color: active ? '#fff' : 'var(--gi-deep-ink, #0D1C35)', border: '0.5px solid var(--gi-platinum, #E5E2DA)', borderRadius: 6, fontFamily: 'var(--ff-mono)', fontSize: 11, fontWeight: 500, letterSpacing: '0.10em', textTransform: 'uppercase', opacity: disabled ? 0.4 : 1, }); const Pagination = ({ page, totalPages, onChange }) => { if (totalPages <= 1) return null; const pages = []; for (let i = 1; i <= totalPages; i++) pages.push(i); return (
); }; // ============================================================ // EditorialStandards — quiet "who writes this" strip // ============================================================ const EditorialStandards = () => (
Editorial standards

Written from real industry experience.

{[ ['Ontario-Focused', 'Built around Ontario utility rates, IESO programs, and real commercial energy economics.'], ['Executive-Level', 'Written for facility managers, operations teams, CFOs, and commercial property decision-makers.'], ['Data-Backed', 'Insights supported by published rate schedules, utility filings, and real project analysis.'], ].map(([title, body]) => (
{title}

{body}

))}
); // ============================================================ // SubscribeBand — visual only (form does not yet POST anywhere) // ============================================================ const SubscribeBand = () => (
Subscribe

New analysis, once a month.

Insights on commercial energy in Canada.

{ e.preventDefault(); /* TODO: wire to forms plugin */ }} style={{ display: 'flex', gap: 8 }}>
); // ============================================================ // Main BlogPage // ============================================================ const BlogPage = () => { const [activeTab, setActiveTab] = React.useState('all'); const [page, setPage] = React.useState(1); const [data, setData] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const isFiltered = activeTab !== 'all'; const perPage = isFiltered ? FILTERED_PER_PAGE : ALL_PER_PAGE; React.useEffect(() => { setLoading(true); const cat = isFiltered ? activeTab : ''; const url = `${API_BASE}/posts?page=${page}&per_page=${perPage}&category=${encodeURIComponent(cat)}`; fetch(url) .then((r) => r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status))) .then((json) => { setData(json); setError(null); }) .catch((e) => setError(e.message)) .finally(() => setLoading(false)); }, [activeTab, page, perPage, isFiltered]); const handleTab = (id) => { setActiveTab(id); setPage(1); }; return (
{error && (
Could not load articles. Please try refreshing the page.
({error})
)} {!error && loading && (
Loading…
)} {!error && !loading && data && ( <> {/* Featured panel — only on page 1 of "all" tab */} {!isFiltered && page === 1 && data.featured && ( )} {/* Grid — variant on page 1 of "all", uniform otherwise */} {!isFiltered && page === 1 ? : t.id === activeTab).label}` : `Page ${data.pagination.page} of ${data.pagination.total_pages}`} /> } { setPage(p); window.scrollTo({ top: 0, behavior: 'smooth' }); }} /> )} {/* */}
); };