// kc-sections.jsx — Commercial Energy Knowledge Centre (the Q&A parent page).
// Rendered at the question-answer CPT archive: /question-answer/.
//
// Data flow (v1.41.0):
// KnowledgeCentreApp fetches, in parallel:
// GET /qa/topics → topic grid (slug, name, desc, icon, count, url)
// GET /qa → full question index (slug, question, preview, topic) for search
// GET /qa/popular → 8 popular questions
// Topic cards LINK to /question-answer/topic/{slug}/ (real archive pages — see
// the qa-topic taxonomy). Search filters the index in place and links each
// result to its individual /question-answer/{slug}/ page.
//
// Reuses shared primitives: Eyebrow, SectionHeader, Button, LockedNavBar, Footer.
const KC_SHELL = { maxWidth: 1440, margin: '0 auto', padding: '0 48px' };
const KC_REST_BASE = (typeof window !== 'undefined' && window.GI_REST_BASE) || '/wp-json/claude-pages/v1/';
// ---------------------------------------------------------------
// Breadcrumb — Home / Resources / Knowledge Centre (real URLs).
// ---------------------------------------------------------------
const KCBreadcrumb = ({ trail }) => (
{trail.map((c, i) => (
{i > 0 && / }
{c.href ? (
{c.label}
) : (
{c.label}
)}
))}
);
// ---------------------------------------------------------------
// Search field (hero global search + topic-page scoped filter).
// ---------------------------------------------------------------
const KCSearch = ({ value, onChange, placeholder, autoFocus }) => (
onChange(e.target.value)}
placeholder={placeholder} aria-label={placeholder}
style={{
width: '100%', boxSizing: 'border-box', font: '400 16px/24px var(--ff-sans)',
color: 'var(--gi-deep-ink)', background: '#fff', border: '1px solid rgba(15,33,51,0.16)',
borderRadius: 4, padding: '18px 20px 18px 54px', outline: 'none',
transition: 'border-color 180ms ease, box-shadow 180ms ease'
}}
onFocus={(e) => { e.target.style.borderColor = 'var(--gi-sage-accent)'; e.target.style.boxShadow = '0 0 0 3px rgba(94,143,109,0.14)'; }}
onBlur={(e) => { e.target.style.borderColor = 'rgba(15,33,51,0.16)'; e.target.style.boxShadow = 'none'; }}
/>
{value && (
onChange('')} aria-label="Clear search" style={{
position: 'absolute', right: 14, top: '50%', transform: 'translateY(-50%)',
width: 30, height: 30, borderRadius: 4, border: 'none', cursor: 'pointer',
background: 'transparent', color: 'var(--gi-graphite)', display: 'inline-flex',
alignItems: 'center', justifyContent: 'center'
}}>
)}
);
// ---------------------------------------------------------------
// Hero — cream. Tag · headline · description · search · stats.
// ---------------------------------------------------------------
const KCHero = ({ query, onQuery, total, topicCount }) => {
const rounded = `${Math.floor((total || 0) / 10) * 10}+`;
const stats = [
{ value: rounded, label: 'Questions answered' },
{ value: String(topicCount || 0), label: 'Energy topics' },
{ value: 'C&I', label: 'Commercial & industrial focus' }
];
return (
Knowledge Centre
Answers to commercial energy questions.
Explore expert answers on commercial solar, energy audits, EV charging, battery storage, incentives, financing, and energy-management strategies for Canadian businesses.
{stats.map((s, i) => (
))}
);
};
// ---------------------------------------------------------------
// Topic card — links to its topic archive page.
// ---------------------------------------------------------------
const KCTopicCard = ({ topic, gtmLocation = 'qa-topic-link' }) => {
const [hover, setHover] = React.useState(false);
return (
setHover(true)} onMouseLeave={() => setHover(false)}
style={{
textAlign: 'left', cursor: 'pointer', textDecoration: 'none', border: '1px solid rgba(15,33,51,0.12)',
opacity: 1, /* override global a:hover { opacity: 0.6 } — card has its own hover affordance */
background: '#fff', borderRadius: 8, padding: '28px 26px 24px', display: 'flex',
flexDirection: 'column', gap: 0, minHeight: 248, width: 'auto',
boxShadow: hover ? '0 18px 40px -20px rgba(15,33,51,0.24)' : 'var(--shadow-1)',
borderColor: hover ? 'rgba(94,143,109,0.55)' : 'rgba(15,33,51,0.12)',
transform: hover ? 'translateY(-3px)' : 'none',
transition: 'transform 240ms var(--ease-standard), box-shadow 240ms var(--ease-standard), border-color 240ms var(--ease-standard)'
}}>
{topic.name}
{topic.desc}
{topic.count} Questions
Browse
);
};
// ---------------------------------------------------------------
// Sectors grid — sits ABOVE the topics grid; mirrors its design.
// Cards link to the sector archive (/sector/{slug}/).
// ---------------------------------------------------------------
const KCSectorGrid = ({ sectors }) => (
);
const KCTopicGrid = ({ topics }) => (
);
// ---------------------------------------------------------------
// Question row — list item (search results). Links to the Q&A page.
// ---------------------------------------------------------------
const KCQuestionRow = ({ q, showTopic }) => {
const [hover, setHover] = React.useState(false);
return (
setHover(true)} onMouseLeave={() => setHover(false)}
data-gtm-event="cta_click" data-gtm-label={q.question}
data-gtm-location="qa-search-result" data-gtm-stage="consideration"
style={{
display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'center',
padding: '26px 0', borderBottom: '1px solid rgba(15,33,51,0.12)', textDecoration: 'none', border: 'none',
borderBottomStyle: 'solid', borderBottomWidth: 1, borderBottomColor: 'rgba(15,33,51,0.12)',
opacity: 1 /* override global a:hover fade */
}}>
{showTopic && q.topicName && (
{q.topicName}
)}
{q.question}
{q.preview &&
{q.preview}
}
View answer
);
};
// ---------------------------------------------------------------
// Global search results — replaces grid/popular while typing.
// ---------------------------------------------------------------
const KCSearchResults = ({ query, index }) => {
const q = query.trim().toLowerCase();
const tokens = q.split(/\s+/).filter(Boolean);
const results = index.filter((item) => {
const hay = (item.question + ' ' + (item.preview || '') + ' ' + (item.topicName || '')).toLowerCase();
return tokens.every((t) => hay.includes(t));
});
return (
Search results
{results.length} {results.length === 1 ? 'answer' : 'answers'} for “{query.trim()}”
{results.length > 0
? results.map((item) =>
)
: (
We couldn’t find a match. Try a broader term — or speak with our team and we’ll answer your question directly.
Contact our team
)}
);
};
// ---------------------------------------------------------------
// Popular questions — compact card grid.
// ---------------------------------------------------------------
const KCPopularCard = ({ q }) => {
const [hover, setHover] = React.useState(false);
return (
setHover(true)} onMouseLeave={() => setHover(false)}
style={{
display: 'flex', flexDirection: 'column', textDecoration: 'none', border: 'none',
background: '#fff', borderRadius: 8, padding: '24px 24px 22px', opacity: 1,
boxShadow: hover ? '0 16px 36px -20px rgba(15,33,51,0.22)' : 'var(--shadow-1)',
outline: hover ? '1px solid rgba(94,143,109,0.55)' : '1px solid rgba(15,33,51,0.10)',
transform: hover ? 'translateY(-3px)' : 'none',
transition: 'transform 220ms var(--ease-standard), box-shadow 220ms var(--ease-standard), outline-color 220ms var(--ease-standard)'
}}>
{q.topicName}
{q.question}
View answer
);
};
const KCPopular = ({ popular }) => (
);
// ---------------------------------------------------------------
// CTA — "Still have questions?" (soft-green brand surface).
// ---------------------------------------------------------------
const KCQuestionsCTA = () => (
Talk to a specialist
Still have questions?
Speak with our team about your facility, energy goals, and the opportunities available to you.
Book a 20-minute call
Contact our team
);
// ---------------------------------------------------------------
// App — fetches the three datasets and orchestrates hub vs search.
// ---------------------------------------------------------------
const KnowledgeCentreApp = () => {
// Prerender/hydration seed: the build injects __GI_INITIAL_STATE__.kc so the
// server markup (topic grid + popular) matches the client's first render.
const SEED = (typeof window !== 'undefined' && window.__GI_INITIAL_STATE__ && window.__GI_INITIAL_STATE__.kc) || null;
const [state, setState] = React.useState(SEED
? { loading: false, sectors: SEED.sectors || [], topics: SEED.topics || [], index: SEED.index || [], popular: SEED.popular || [], total: SEED.total || 0 }
: { loading: true, sectors: [], topics: [], index: [], popular: [], total: 0 });
const [query, setQuery] = React.useState('');
React.useEffect(() => {
if (SEED) return; // already populated from the prerender seed
const base = KC_REST_BASE.replace(/\/?$/, '/');
const get = (path) => fetch(base + path, { headers: { Accept: 'application/json' } }).then((r) => r.ok ? r.json() : Promise.reject(r.status));
Promise.all([get('qa/sectors'), get('qa/topics'), get('qa'), get('qa/popular')])
.then(([sectors, topics, index, popular]) => setState({
loading: false,
sectors: (sectors && sectors.sectors) || [],
topics: (topics && topics.topics) || [],
index: (index && index.items) || [],
popular: (popular && popular.items) || [],
total: (topics && topics.total) || ((index && index.total) || 0)
}))
.catch(() => setState((s) => ({ ...s, loading: false })));
}, []);
// Keep lucide icons painted as content swaps in.
React.useEffect(() => {
const t = setInterval(() => window.lucide && window.lucide.createIcons({ attrs: { 'stroke-width': 1.5 } }), 200);
return () => clearInterval(t);
}, []);
const searching = query.trim().length > 0;
return (
{searching
?
: (
{state.sectors.length > 0 && }
{state.popular.length > 0 && }
)}
);
};
Object.assign(window, { KnowledgeCentreApp });