/* Qualios landing — shared helpers (exported to window) */ const DEMO_URL = 'https://demo.qualios.app'; /* React-safe Lucide icon (mirrors the DS QIcon pattern) */ function pascal(name) { return String(name).split(/[-_ ]/).filter(Boolean) .map((s) => s[0].toUpperCase() + s.slice(1)).join(''); } function Icon({ name, size = 20, stroke = 1.5, className = '', style }) { const set = (window.lucide && window.lucide.icons) || {}; const node = set[pascal(name)]; if (!node) return null; const kids = (node[2] || []).map(([tag, attrs]) => '<' + tag + ' ' + Object.entries(attrs).map(([k, v]) => k + '="' + v + '"').join(' ') + '>' ).join(''); return React.createElement('svg', { className: ('qicon ' + className).trim(), width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round', style, 'aria-hidden': true, dangerouslySetInnerHTML: { __html: kids }, }); } /* Reveal-on-scroll: arm only below-the-fold elements; above-the-fold stays visible immediately. Failsafe locks everything visible. */ function useReveal() { React.useEffect(() => { const els = Array.from(document.querySelectorAll('.reveal')); if (!els.length) return; const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reduce || !('IntersectionObserver' in window)) return; // visible by default const h = window.innerHeight || document.documentElement.clientHeight; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' }); els.forEach((el) => { const r = el.getBoundingClientRect(); if (r.top > h * 0.85) { el.classList.add('armed'); io.observe(el); } }); // Never leave content hidden — lock visible after a beat. const t = setTimeout(() => document.documentElement.classList.add('reveals-locked'), 1400); return () => { io.disconnect(); clearTimeout(t); }; }, []); } /* Sticky-nav scrolled state */ function useScrolled(threshold = 8) { const [scrolled, setScrolled] = React.useState(false); React.useEffect(() => { const onScroll = () => setScrolled(window.scrollY > threshold); onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, [threshold]); return scrolled; } Object.assign(window, { DEMO_URL, Icon, useReveal, useScrolled });