// Shared layout: Logo, Nav, Footer, Reveal, helpers
const { useEffect, useState, useRef, useMemo, useCallback } = React;
// Hash-based router
function useHashRoute() {
const parse = () => {
const h = window.location.hash.replace(/^#\/?/, '') || 'home';
const [path, query] = h.split('?');
return { path: path || 'home', query: query || '' };
};
const [route, setRoute] = useState(parse());
useEffect(() => {
const handler = () => { setRoute(parse()); window.scrollTo({ top: 0, behavior: 'instant' }); };
window.addEventListener('hashchange', handler);
return () => window.removeEventListener('hashchange', handler);
}, []);
return route;
}
function navigate(path) {
window.location.hash = '/' + path;
}
// Logo mark — replicated abstract square (4 panel grid in diamond)
function LogoMark({ size = 38, dark = false }) {
const ink = dark ? '#F5F1EA' : '#2B1F18';
const sky = '#4A90C7';
const tan = '#C9A97A';
return (
);
}
function Wordmark({ dark = false }) {
const ink = dark ? '#F5F1EA' : '#2B1F18';
return (
SKY flooring
);
}
function Nav({ current }) {
const [scrolled, setScrolled] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 12);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => { setDrawerOpen(false); }, [current]);
const links = [
{ id: 'home', label: 'Home' },
{ id: 'services', label: 'Services' },
{ id: 'gallery', label: 'Gallery' },
{ id: 'about', label: 'About' },
{ id: 'contact', label: 'Contact' },
];
return (
<>
Call for a Free Quote
(226) 988-7694 →
>
);
}
function Footer() {
return (
);
}
// Reveal-on-scroll (scroll-listener based; IO unreliable in some iframe contexts)
function Reveal({ children, delay = 0, as = 'div', className = '', ...rest }) {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
let raf = 0;
const check = () => {
raf = 0;
if (!ref.current) return;
const r = ref.current.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
if (r.top < vh - 40 && r.bottom > 0) { setShown(true); cleanup(); }
};
const onScroll = () => { if (!raf) raf = requestAnimationFrame(check); };
const cleanup = () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
};
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
// Initial check (after layout)
requestAnimationFrame(check);
// Safety fallback — show after 600ms regardless
const t = setTimeout(() => setShown(true), 600);
return () => { cleanup(); clearTimeout(t); if (raf) cancelAnimationFrame(raf); };
}, []);
const Tag = as;
return (
{children}
);
}
// Photo with subtle zoom on scroll
function ParallaxPhoto({ src, alt, className = '', style = {}, loading = 'lazy', decoding = 'async' }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const onScroll = () => {
const r = el.getBoundingClientRect();
const vh = window.innerHeight;
const p = (r.top + r.height/2 - vh/2) / vh; // -1..1
const y = Math.max(-30, Math.min(30, p * 30));
el.style.setProperty('--py', y + 'px');
};
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
);
}
// SVG icons (small set)
const Icon = {
Plank: () => (
),
Tile: () => (
),
Refresh: () => (
),
Layers: () => (
),
Check: () => (
),
Pin: () => (
),
Star: () => (
),
Quote: () => (
),
};
// Reveal helpers shared
window.Sky = { useHashRoute, navigate, Nav, Footer, Reveal, ParallaxPhoto, LogoMark, Wordmark, Icon };