// ============================================================ // Cre2SolutionsMobile — MOBILE port of the desktop "Solutions for this // sector" section (Cre2SolutionsGreen) from the CRE page. // // Desktop = interactive single-storey CRE building schematic (left) + 4-row // hover list (right), on a Soft-Green surface with Paper-White text. The // approved desktop map is titled "INTEGRTED ENERGY SYSTEMS" and the codebase // default (useState(1)) + the task brief put zone 02 — LED Lighting — active. // Mobile keeps the SAME story, palette, type, iconography & illustration but // swaps hover for touch, following the approved Manufacturing, Warehousing, // Food-&-Beverage & Agriculture mobile pattern: // • Intro (eyebrow + headline + supporting copy), stacked // • Interactive diagram, always visible, the visual focus // • Solution cards as a horizontal swipe carousel below the diagram // • Tapping a card / diagram marker -> updates the active diagram zone // • A dedicated "Explore …" link inside each card handles navigation, so a // first tap activates and the link tap navigates (no touch conflict) // • Default active zone: 02 — LED Lighting (per approved desktop default) // ============================================================ const CRE_AMBER = 'rgba(233,199,134,1)'; // --gi-amber-soft const CRE_AMBER_FILL = 'rgba(233,199,134,0.18)'; const CRE_SOFT_GREEN = '#375844'; // ============================================================ // Cre2BuildingSchematicMobile — identical SVG schematic to desktop, with the // numbered markers made tappable (large invisible hit targets). // 4 zones · 01 Commercial Solar (rooftop) · 02 LED Lighting (ceiling) // · 03 Energy Audits (utility cabinet) · 04 EV Charging (parking) // ============================================================ const Cre2BuildingSchematicMobile = ({ activeIndex, onSelectZone }) => { const W = 540; const H = 360; const amber = CRE_AMBER; const amberFill= CRE_AMBER_FILL; const ink = 'rgba(255,255,255,0.55)'; const inkDim = 'rgba(255,255,255,0.32)'; const inkFaint = 'rgba(255,255,255,0.18)'; const inkText = 'rgba(255,255,255,0.62)'; const zones = [ // 01 Commercial Solar — rooftop panel array { id: 0, label: '01', cx: 270, cy: 90, hl: }, // 02 LED Lighting — ceiling fixtures inside { id: 1, label: '02', cx: 270, cy: 172, hl: }, // 03 Energy Audits — utility / meter cabinet on left exterior { id: 2, label: '03', cx: 30, cy: 230, hl: }, // 04 EV Charging — parking strip on right { id: 3, label: '04', cx: 510, cy: 270, hl: }, ]; return (
{/* Ground */} {[290, 298, 306].map((y, i) => ( ))} {/* Building shell — single storey, flat roof */} {/* parapet */} {/* Interior floor line (inside, for context) */} {/* Windows / clerestory band along the upper wall */} {[80, 130, 180, 230, 280, 330, 380, 430].map(x => ( ))} {/* Entrance / lobby (centre) */} {/* ===== ROOFTOP SOLAR — side-profile tilted panels sitting ON the roof ===== */} {(() => { const roofY = 114; const panelLen = 22; const panelThick = 2.2; const legFront = 5; const legBack = 10; const legInset = 3; const lit = activeIndex === 0; const stroke = lit ? amber : ink; const fill = lit ? amber : 'rgba(255,255,255,0.18)'; const legStroke = lit ? amber : inkDim; const panels = []; for (let i = 0; i < 14; i++) { const cx = 70 + i * 28; panels.push(cx); } return panels.map((cx, i) => { const x0 = cx - panelLen / 2; const x1 = cx + panelLen / 2; const xLegBack = x0 + legInset; const xLegFront = x1 - legInset; const yBackTop = roofY - legBack; const yFrontTop = roofY - legFront; return ( ); }); })()} {/* HVAC unit on roof */} HVAC {/* ===== LED HIGH-BAY CEILING FIXTURES — ceiling only ===== */} {[88, 138, 188, 238, 288, 338, 388, 438].map(x => { const lit = activeIndex === 1; return ( {lit && ( )} ); })} {/* ===== ENERGY AUDITS — utility / meter cabinet (left exterior) ===== */} AUDIT {/* ===== EV CHARGING — parking strip right of building ===== */} {[498, 510, 522, 534].map(x => ( ))} {[504, 528].map(x => ( ))} {[504, 528].map(x => ( EV ))} EV {/* Active overlay */} {zones[activeIndex].hl} {/* Numbered markers — tappable */} {zones.map((z, i) => { const isActive = i === activeIndex; return ( onSelectZone && onSelectZone(i)} role="button" tabIndex={0} aria-label={`Show zone ${z.label}`} style={{ cursor: 'pointer' }}> {/* large invisible touch target */} {z.label} ); })}
); }; // ============================================================ // Cre2SolutionsMobile — full mobile section // ============================================================ const Cre2SolutionsMobile = () => { const rows = [ { idx: '01', icon: 'sun', name: 'Commercial Solar', zone: 'Rooftop', tag: 'Turn unused roof into revenue.', why: 'Large flat CRE rooftops are unused real estate. On-site solar reduces grid electricity purchases — directly improving NOI and asset value at typical CRE cap rates.', href: 'Solutions - Commercial Solar.html' }, { idx: '02', icon: 'lightbulb', name: 'LED Lighting', zone: 'Common areas & tenants', tag: 'Reduce operating costs immediately.', why: 'Common-area, parking-lot, and tenant-space LED retrofits reduce operating costs immediately. saveONenergy incentives cover a significant portion of project cost. Improves NOI before solar is sized.', href: 'Solutions - LED Lighting.html' }, { idx: '03', icon: 'clipboard-check', name: 'Energy Audits', zone: 'Whole-building', tag: 'Establish the baseline.', why: 'BEPS reporting requires documented energy and emissions baselines. The audit also uncovers HVAC, controls, and envelope efficiencies that further reduce operating cost.', href: 'Solutions - Energy Audits.html' }, { idx: '04', icon: 'plug-zap', name: 'EV Charging', zone: 'Parking & garage', tag: 'Build before tenants demand it.', why: 'Adding EV charging now — before tenant demand forces a rush retrofit — is significantly cheaper. On-site solar provides a cost-effective power source and supports tenant retention.', href: 'Solutions - EV Charging.html' }, ]; // Default active zone: 02 — LED Lighting (index 1), per approved desktop default. const [active, setActive] = React.useState(1); const scrollerRef = React.useRef(null); const cardRefs = React.useRef([]); const programmatic = React.useRef(false); const rafId = React.useRef(0); const settleTimer = React.useRef(0); const didInit = React.useRef(false); // (Re)draw lucide icons whenever the active card changes. React.useEffect(() => { const t = setTimeout(() => window.lucide && window.lucide.createIcons({ attrs: { 'stroke-width': 1.4 } }), 20); return () => clearTimeout(t); }, [active]); const scrollToCard = (i, instant) => { const sc = scrollerRef.current; const card = cardRefs.current[i]; if (!sc || !card) return; programmatic.current = true; window.clearTimeout(settleTimer.current); const target = card.offsetLeft - (sc.clientWidth - card.clientWidth) / 2; const max = sc.scrollWidth - sc.clientWidth; const end = Math.max(0, Math.min(max, target)); const start = sc.scrollLeft; const dist = end - start; window.cancelAnimationFrame(scrollToCard._raf); if (instant) { sc.scrollLeft = end; window.requestAnimationFrame(() => { sc.scrollLeft = end; programmatic.current = false; }); return; } if (Math.abs(dist) < 1) { programmatic.current = false; return; } const dur = 420; const t0 = (window.performance || Date).now(); const ease = (t) => 1 - Math.pow(1 - t, 3); // easeOutCubic const step = (now) => { const p = Math.min(1, (now - t0) / dur); sc.scrollLeft = start + dist * ease(p); if (p < 1) { scrollToCard._raf = window.requestAnimationFrame(step); } else { programmatic.current = false; } }; scrollToCard._raf = window.requestAnimationFrame(step); }; // Center the default card (02) on first paint. React.useEffect(() => { if (didInit.current) return; let tries = 0; const tryCenter = () => { const sc = scrollerRef.current; const card = cardRefs.current[1]; if (sc && card) { didInit.current = true; scrollToCard(1, true); return; } if (tries++ < 40) requestAnimationFrame(tryCenter); }; requestAnimationFrame(tryCenter); const onLoad = () => { didInit.current = false; tries = 0; requestAnimationFrame(tryCenter); }; window.addEventListener('load', onLoad); return () => window.removeEventListener('load', onLoad); }, []); // Tap a card OR a diagram marker -> select that zone + center its card. const selectZone = (i) => { setActive(i); scrollToCard(i); }; const nearestCard = () => { const sc = scrollerRef.current; if (!sc) return 0; const center = sc.scrollLeft + sc.clientWidth / 2; let best = 0, bestDist = Infinity; cardRefs.current.forEach((c, i) => { if (!c) return; const cc = c.offsetLeft + c.clientWidth / 2; const d = Math.abs(cc - center); if (d < bestDist) { bestDist = d; best = i; } }); return best; }; // Swipe -> live-update the active zone to the nearest card, then JS-snap to // center it once the user stops scrolling. const handleScroll = () => { if (programmatic.current) return; window.clearTimeout(settleTimer.current); settleTimer.current = window.setTimeout(() => { const i = nearestCard(); scrollToCard(i); }, 110); if (rafId.current) return; rafId.current = requestAnimationFrame(() => { rafId.current = 0; const best = nearestCard(); setActive((prev) => (prev === best ? prev : best)); }); }; return (
{/* Top amber tick — mirrors desktop */}
); }; Object.assign(window, { Cre2SolutionsMobile, Cre2BuildingSchematicMobile });