/* Skaleup — single-file app (in-browser Babel build). Globals from CDN. */ const { useState, useEffect, useRef, useContext, createContext, StrictMode } = React; const { createRoot } = ReactDOM; const { BrowserRouter, Routes, Route, Link, NavLink, useParams, useLocation } = ReactRouterDOM; // import.meta shim (Vite-only in source) const import_meta_env = {}; /* ===== lib/api.js ===== */ // Central API client. In dev, Vite proxies /api to the PHP server (see // vite.config.js). In production the React app and PHP live on the same // origin, so relative /api paths just work. const BASE = import_meta_env.VITE_API_URL || ''; async function req(path, { method = 'GET', body, token } = {}) { const headers = { 'Content-Type': 'application/json' }; if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(`${BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); let json = null; try { json = await res.json(); } catch { /* non-JSON */ } if (!res.ok) { const msg = json?.message || `Request failed (${res.status})`; throw Object.assign(new Error(msg), { status: res.status, data: json }); } return json; } const api = { site: () => req('/api/site'), services: () => req('/api/public/services'), service: (slug) => req(`/api/public/service/${slug}`), industries: () => req('/api/public/industries'), industry: (slug) => req(`/api/public/industry/${slug}`), packages: () => req('/api/public/packages'), package: (slug) => req(`/api/public/package/${slug}`), posts: () => req('/api/public/posts'), post: (slug) => req(`/api/public/post/${slug}`), page: (slug) => req(`/api/public/page/${slug || '_home'}`), lead: (payload) => req('/api/public/lead', { method: 'POST', body: payload }), }; var __default_lib_api_js = api; /* ===== lib/site.jsx ===== */ // ---- generic data-fetch hook ---- function useAsync(fn, deps = []) { const [state, setState] = useState({ loading: true, data: null, error: null }); useEffect(() => { let alive = true; setState({ loading: true, data: null, error: null }); Promise.resolve(fn()) .then((res) => alive && setState({ loading: false, data: res?.data ?? res, error: null })) .catch((err) => alive && setState({ loading: false, data: null, error: err })); return () => { alive = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); return state; } // ---- global site settings + menus, loaded once ---- const SiteCtx = createContext({ settings: {}, menus: {} }); function SiteProvider({ children }) { const [site, setSite] = useState({ settings: {}, menus: {}, ready: false }); useEffect(() => { api.site() .then((r) => setSite({ ...(r.data || {}), ready: true })) .catch(() => setSite((s) => ({ ...s, ready: true }))); }, []); return {children}; } const useSite = () => useContext(SiteCtx); /* ===== lib/effects.js ===== */ // Global visual effects that operate on DOM the React pages render. // Called after each route settles (see App.jsx ScrollManager). function runGlobalEffects() { const reduce = matchMedia('(prefers-reduced-motion:reduce)').matches; // image-section zoom-in document.querySelectorAll('.img-section').forEach((s) => { const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) e.target.classList.add('in-view'); }), { threshold: 0.15 }); io.observe(s); }); // card cursor-glow + tilt document.querySelectorAll('.svc,.f-card').forEach((c) => { if (c.__glow) return; c.__glow = true; c.addEventListener('mousemove', (e) => { const r = c.getBoundingClientRect(); c.style.setProperty('--mx', (e.clientX - r.left) + 'px'); c.style.setProperty('--my', (e.clientY - r.top) + 'px'); c.style.setProperty('--ry', (((e.clientX - r.left) / r.width) - 0.5) * 8 + 'deg'); }); c.addEventListener('mouseleave', () => c.style.setProperty('--ry', '0deg')); }); if (reduce) return; // parallax on image backgrounds const layers = [...document.querySelectorAll('.img-section')].map((s) => ({ s, bg: s.querySelector('.img-bg') })).filter((l) => l.bg); if (!window.__parallaxBound) { window.__parallaxBound = true; let tick = false; const update = () => { const vh = innerHeight; document.querySelectorAll('.img-section').forEach((s) => { const bg = s.querySelector('.img-bg'); if (!bg) return; const r = s.getBoundingClientRect(); if (r.bottom < 0 || r.top > vh) return; let off = -(r.top + r.height / 2 - vh / 2) * 0.12; off = Math.max(-90, Math.min(90, off)); bg.style.setProperty('--py', off.toFixed(1) + 'px'); }); tick = false; }; addEventListener('scroll', () => { if (!tick) { tick = true; requestAnimationFrame(update); } }, { passive: true }); } // video fallback / ready const vid = document.getElementById('bgv'); if (vid && !vid.__bound) { vid.__bound = true; const ready = () => document.documentElement.classList.add('video-ready'); vid.addEventListener('playing', ready); vid.addEventListener('loadeddata', ready); vid.addEventListener('error', () => { vid.style.display = 'none'; }); } } /* ===== components/Motion.jsx ===== */ // Scroll-reveal wrapper — mirrors the original .reveal / .in behaviour. function Reveal({ children, className = '', tag: Tag = 'div', tilt, ...rest }) { const ref = useRef(null); const [seen, setSeen] = useState(false); useEffect(() => { const el = ref.current; if (!el || seen) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); io.observe(el); return () => io.disconnect(); }, [seen]); const cls = ['reveal', tilt ? `tilt-${tilt}` : '', seen ? 'in' : '', className].filter(Boolean).join(' '); return {children}; } function Loading() { return
; } // Count-up number, animates when scrolled into view. function Counter({ to, prefix = '', suffix = '', decimals = 0 }) { const ref = useRef(null); const [val, setVal] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { if (!entries[0].isIntersecting) return; io.disconnect(); const end = parseFloat(to), start = performance.now(), dur = 1400; const step = (t) => { const p = Math.min((t - start) / dur, 1); setVal(end * (1 - Math.pow(1 - p, 3))); if (p < 1) requestAnimationFrame(step); }; requestAnimationFrame(step); }, { threshold: 0.6 }); io.observe(el); return () => io.disconnect(); }, [to]); return {prefix}{decimals ? val.toFixed(decimals) : Math.round(val)}{suffix}; } /* ===== components/PageHero.jsx ===== */ // Shared compact page-hero with breadcrumbs. // crumbs: array of [label, href?] — last item has no href. function PageHero({ crumbs = [], title, lede }) { return (
{crumbs.map((c, i) => ( {c[1] ? {c[0]} : c[0]} {i < crumbs.length - 1 &&  /  } ))} {lede && }
); } /* ===== components/Cta.jsx ===== */ function Cta() { const { settings: s = {} } = useSite(); return (

Get started

Let's turn this into your next growth curve.

Book a free consultation and we'll map the first 90 days — no obligation.

Book Consultation {s.contact_email || 'hello@skaleup.consultant'}
); } /* ===== components/Forms.jsx ===== */ function useLeadForm(source) { const [status, setStatus] = useState('idle'); // idle | sending | ok | error const [error, setError] = useState(''); async function submit(payload) { setStatus('sending'); setError(''); try { await api.lead({ ...payload, source, page_url: location.pathname }); setStatus('ok'); } catch (e) { setStatus('error'); setError(e.message || 'Something went wrong.'); } } return { status, error, submit }; } // Compact hero "Get Started" quote form function QuoteForm() { const { status, error, submit } = useLeadForm('quote'); function onSubmit(e) { e.preventDefault(); const f = e.target; submit({ name: f.qn.value, email: f.qe.value, phone: f.qp.value, service: f.qs.value, budget: f.qb.value, }); } return (

Get Started

Tell us the basics. A senior strategist replies within 4 hours.

No spam. No sales sequence. Just a real answer.

{status === 'ok' &&
✓ Got it. We'll be in touch within 4 hours.
} {status === 'error' &&
{error}
}
); } // Full contact form function ContactForm() { const { status, error, submit } = useLeadForm('contact'); const [services, setServices] = useState([]); const toggle = (s) => setServices((cur) => cur.includes(s) ? cur.filter((x) => x !== s) : [...cur, s]); function onSubmit(e) { e.preventDefault(); const f = e.target; submit({ name: f.cn.value, company: f.cc.value, email: f.ce.value, phone: f.cp.value, service: services.join(', '), budget: f.cb.value, timeline: f.ct.value, message: f.cm.value, }); } const opts = ['SEO', 'Advanced SEO', 'Digital Marketing', 'Web Design', 'Virtual Assistant', 'Not sure yet']; return (

Start a project

The more you tell us, the sharper the first call will be.

{opts.map((o) => ( ))}