Skip to content

Design Systems

Integration Network Heroes — How Velocity X Animates 7 Tool Logos Around a Hub in Pure SVG

Central Aidxn Hub + 7 Animated Icons, Drawn in Phase

🔗

Most SaaS marketing sites show integration logos in a static grid: Slack, Salesforce, HubSpot, Notion, etc. — stacked in rows, no motion, no connection to what they actually do. Velocity X does something different: a central HQ node (Aidxn's logo) connected to 7 integration icons via SVG paths, with each connection drawn in sequence, circles pulsing as they appear, and soft glow underlines flowing along the circuits. The entire reveal phased over 700ms — globe spin → icons stagger in → lines glow. It reads as a living integration ecosystem, not a feature checklist.

Why Integration Networks Beat Static Grids for B2B

A static grid of logos says "we connect to these platforms." A network diagram with animated draw-ins says "your data flows through our system to every tool that matters." The visual metaphor is stronger — it's not just compatibility, it's orchestration. For B2B products, this matters: enterprise buyers want to know data moves, transforms, and syncs in real time. An animated network shows that without a single line of copy.

SVG animation is the most efficient way to achieve this. No DOM overhead, no layout thrashing, no JavaScript particle systems. Just SVG paths with animated stroke-dasharray and styled circles. Render once, animate forever on the compositor. Total payload for Velocity X's hero: 18KB SVG + 12KB CSS keyframes. Compare that to 50KB of GSAP, Three.js, or a Lottie animation file.

The Architecture: Central Hub, 7 Radial Paths, Phased Reveal

The diagram is an SVG with a central circle (Aidxn HQ) at `cx="400" cy="300"`, seven smaller circles positioned around it at radial angles (0°, 51.4°, 102.8°, 154.2°, 205.7°, 257.1°, 308.6°), and SVG paths connecting hub to each icon. Each path has `stroke-dasharray` set to its total length, then CSS keyframes animate `stroke-dashoffset` from full length to zero — the classic line-draw effect.

{`
  
  
  

  
  
    
    
  

  
  

  
  

  
`}

The paths use quadratic Bézier curves (`Q`) instead of straight lines so they arc gracefully around the center. Stroke-linecap is set to `round` so the animated line tip is smooth, not sharp. Each connection has a small pulsing circle (2–3px radius) that flows along the path after the line draws in.

The Animation: Four Phases in 700ms

Phase -1 (0ms): Hub is hidden, scale 0. Phase 0 (0–150ms): Hub rotates 360° and scales to 1 (entrance). Phase 1 (150–400ms): Icons 1–7 stagger in over 250ms with 20ms delays (icons fade + scale from 0 to 1). Phase 2 (400–700ms): Connection paths draw in sequence, offset by 50ms each, then glow-circles pulse forever after. All phases use `animation-fill-mode: forwards` so nothing resets.

{`@keyframes spinHub {
  0% { transform: rotate(0deg) scale(0); opacity: 0; }
  100% { transform: rotate(360deg) scale(1); opacity: 1; }
}

@keyframes fadeInIcon {
  0% { opacity: 0; transform: scale(0.8); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes drawPath {
  0% { stroke-dashoffset: var(--path-length); }
  100% { stroke-dashoffset: 0; }
}

@keyframes pulseFill {
  0%, 100% { r: 3px; opacity: 0.6; }
  50% { r: 6px; opacity: 0.2; }
}

.hub {
  animation: spinHub 150ms cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
}

.integration-1 { animation: fadeInIcon 150ms ease-out forwards; animation-delay: 150ms; }
.integration-2 { animation: fadeInIcon 150ms ease-out forwards; animation-delay: 170ms; }
/* ... i-7 delays: 190ms, 210ms, 230ms, 250ms, 270ms */

.connection-1 {
  animation: drawPath 150ms ease-in-out forwards;
  animation-delay: 400ms;
  --path-length: 250;
}

.connection-2 { animation-delay: 450ms; --path-length: 245; }
/* ... c-7 delays: 500ms–650ms, staggered 50ms apart */

.flow-1 {
  animation: pulseFill 1s ease-in-out 550ms infinite;
}
/* ... f-7 delays: 600ms–750ms, matching connection draw finish */`}

Total reveal time: 700ms. After that, glow circles pulse infinitely on the now-visible paths. If the user scrolls away and back, you'll likely miss the animation — but that's intentional; the reveal is a one-time eyeball-catch for viewers landing on the page.

Mobile Considerations: Icons Right-Half Only

On mobile viewports, the entire network diagram would overlap the left-column copy. Solution: reposition icons 1–4 to the right hemisphere only (0°, 85°, 170°, 255°) and keep the paths proportionally shorter so the diagram fits within 100vw. Use a `@media (max-width: 768px)` rule to reposition circles and adjust path `d` attributes to shorter Bézier curves. The hub stays centered; icons cluster right. Copy remains readable.

The Catch: Intersection Observer Gating

If the SVG is below the fold, don't start the animation until it's visible — wasted render cycles otherwise. Wrap the SVG in an IntersectionObserver callback that adds a `play` class once the element enters the viewport. CSS toggles animations on/off via `animation-play-state` or by conditionally removing the keyframes entirely for off-screen elements.

{`const svg = document.querySelector('.integration-hero');
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      svg.classList.add('play');
      observer.unobserve(svg); // Animate only once
    }
  });
});
observer.observe(svg);`}

With this, the SVG waits silently off-screen, then triggers its 700ms reveal the moment it scrolls into view. Bandwidth and performance win.

Respecting Reduced Motion & Graceful Fallback

Users with `prefers-reduced-motion: reduce` should see the network fully drawn and static: no spin, no stagger, no pulse. Set all keyframes to their final state or use `animation: none` under the media query. The SVG is still visible and conveys "integrations connect here" without motion.

Frequently Asked Questions

Can I use lucide-react icons instead of SVG inline?

Yes. Import `Route`, `RefreshCw`, `CalendarSync`, `Brain`, `MapPinned`, `CalendarCheck`, `Flame` from lucide-react, wrap each in a container div, and position them at the same radial coordinates. You'll lose the integrated SVG animation (icons won't have stroke-dasharray), but the pulsing circles and lines still work. If you want icons to draw themselves, embed their paths as inline SVG or use `` with SVG URIs.

How do I connect this to real integration data?

Pass integration metadata (name, icon URL, color, link) as a JSON array, then render circles + paths dynamically. Use JavaScript to calculate radial positions (`cos(angle) * radius + centerX`) and generate path `d` attributes via a loop. Store animation offsets in CSS custom properties so each icon gets the correct delay. This scales to 10+ integrations without hand-coding positions.

Can I use GSAP instead of CSS keyframes?

Sure, but you don't need to. GSAP is powerful for scrubbing animations or orchestrating sequences with callbacks. Here, CSS keyframes handle the entire timeline. If you're already loading GSAP for other interactions on the page, GSAP's timeline API is cleaner than inline delays. Otherwise, pure CSS is lighter and faster.

What if I want the animation to loop continuously?

Remove `animation-fill-mode: forwards` and add `animation-iteration-count: infinite`. The hub spins, icons stagger in, paths draw, circles pulse — then everything resets and starts again. The reset might feel jarring; add a fade-out and pause before restart to smooth it. Alternatively, keep the 700ms reveal as a one-time entrance and let only the pulse circles loop infinitely.

How do I add interactivity (hover on an icon to highlight its connection)?

Use CSS `:hover` on the integration group to highlight that icon's path and glow-circle. Change the path stroke color on hover, brighten the circle, and maybe scale the icon slightly. No JavaScript needed — just CSS adjacent-sibling or child selectors. If you want a tooltip on hover, that requires JavaScript or a custom `` element inside the SVG.</p> <h3>Does this work in Safari and older browsers?</h3> <p>Yes. SVG, stroke-dasharray, and CSS keyframes have been stable across all browsers since 2016. IntersectionObserver is supported in all modern browsers (IE excluded). Test in Safari 14+, Chrome 90+, Firefox 88+, and recent mobile browsers. If supporting IE11, skip IntersectionObserver and just play the animation on page load.</p> <h2>The Bottom Line</h2> <p>An integration network animated in pure SVG is a credibility signal for B2B: "we move data in real time." The animation itself is inexpensive — one SVG, a dozen CSS keyframes, and an IntersectionObserver callback. No dependencies beyond the browser. Ship it on your home page, link each icon to a detailed integration page, and watch conversion-rate lifts as viewers realize you're not just compatible — you're orchestrated. For custom diagrams with 10+ integrations or real-time data feeds, <a href="/pricing/">book a design call</a>.</p> </div> </article> </div> <!-- Body-scroll progress block grid (matches service pages) --> <div class="mt-16 sm:mt-24 max-w-md text-primary-600 dark:text-primary-400"> <div class="scroll-grid w-full astro-kv54kbgo" data-scroll-grid data-dir="normal" data-target="[data-doc-body]" style="--sg-cols:24" aria-hidden="true"> <span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span> </div> <script type="module">function v(){const l=Array.from(document.querySelectorAll("[data-scroll-grid]"));if(!l.length)return;const i=[];l.forEach(e=>{const n=Array.from(e.querySelectorAll("[data-cell]"));if(!n.length)return;const h=e.dataset.dir==="reverse",a=e.dataset.target,d=a?document.querySelector(a):null,m=()=>{if(d){const o=d.getBoundingClientRect(),s=window.innerHeight||1;return(s-o.top)/(o.height+s)}const t=document.documentElement;return t.scrollTop/(t.scrollHeight-t.clientHeight||1)},u=()=>{const t=Math.min(1,Math.max(0,m())),o=Math.round(t*n.length);n.forEach((s,g)=>{const f=h?n.length-1-g:g;s.classList.toggle("is-on",f<o)})};let c=!1;const r=()=>{c||(c=!0,requestAnimationFrame(()=>{u(),c=!1}))};window.addEventListener("scroll",r,{passive:!0}),window.addEventListener("resize",r,{passive:!0}),u(),i.push(()=>{window.removeEventListener("scroll",r),window.removeEventListener("resize",r)})}),document.addEventListener("astro:before-swap",()=>i.forEach(e=>e()),{once:!0})}document.addEventListener("astro:page-load",v);</script> </div> </div> <!-- ── Mobile contents drawer ───────────────────────────────────────────── --> <div x-data="{ open: false }" class="fixed inset-x-0 bottom-0 z-40 lg:hidden"> <div x-show="open" x-transition.opacity @click="open = false" class="fixed inset-0 z-0 bg-black/40 backdrop-blur-sm" style="display:none"></div> <div x-show="open" x-transition class="relative z-10 max-h-[70vh] overflow-y-auto rounded-t-2xl border-t border-primary-500/15 bg-white px-5 pb-6 pt-5 shadow-2xl dark:bg-secondary-950" style="display:none"> <p class="h-eyebrow text-primary-950/45 dark:text-primary-200/45">On this page</p> <ul class="mt-4 space-y-2.5" data-toc-list-mobile></ul> <a href="/book/" class="mt-5 flex items-center justify-between rounded-xl bg-primary-600 px-4 py-3 text-white dark:bg-primary-500"> <span class="font-semibold">Get in touch</span> <span>→</span> </a> </div> <button @click="open = !open" class="relative z-10 flex w-full items-center justify-between border-t border-primary-500/15 bg-white/90 px-5 py-3.5 backdrop-blur dark:bg-secondary-950/90"> <span class="h-eyebrow text-primary-950/70 dark:text-primary-200/70">On this page</span> <span class="text-primary-950/70 transition-transform dark:text-primary-200/70" :class="open ? 'rotate-180' : ''">↑</span> </button> </div> <script type="module">function g(){const o=document.documentElement.scrollHeight-window.innerHeight;if(o>0){const i=Math.min(100,Math.round(window.scrollY/o*100)),n=document.getElementById("reading-progress-bar");n&&(n.style.width=i+"%")}}window.addEventListener("scroll",g,{passive:!0});g();function f(o){return o.toLowerCase().replace(/[^\w\s-]/g,"").trim().replace(/\s+/g,"-").slice(0,60)}function u(o,i,n,c,l){const d=document.createElement("li"),a=document.createElement("a");if(a.href=`#${o}`,c==="rail"){a.dataset.tocLink=o,a.className="group flex items-center gap-2 py-1 text-sm leading-snug text-primary-950/55 transition-colors hover:text-primary-950 dark:text-primary-200/55 dark:hover:text-primary-100"+(n?" pl-4":"");const s=document.createElement("span");s.dataset.tocArrow="",s.setAttribute("aria-hidden","true"),s.className="inline-block w-0 overflow-hidden text-accent-500 opacity-0 transition-all duration-200",s.textContent="⟶";const m=document.createElement("span");m.textContent=i,a.append(s,m)}else a.className="block text-primary-950/70 dark:text-primary-200/70"+(n?" pl-3 text-base":" text-lg"),a.textContent=i,l&&a.addEventListener("click",l);return d.appendChild(a),d}function y(){const o=document.querySelector("[data-blog-article]"),i=document.querySelector("[data-toc-list]"),n=document.querySelector("[data-toc-list-mobile]");if(!o||!i)return;const c=Array.from(o.querySelectorAll(".prose h2, .prose h3")).filter(t=>(t.textContent||"").trim().length>0);if(i.replaceChildren(),n&&n.replaceChildren(),!c.length)return;const l=new Set,d=c.map((t,e)=>{let r=t.id||f(t.textContent||"")||`section-${e}`;for(;l.has(r);)r=`${r}-${e}`;return l.add(r),t.id=r,t.style.scrollMarginTop="7rem",{id:r,label:(t.textContent||"").trim(),sub:t.tagName==="H3"}});d.forEach(t=>{i.appendChild(u(t.id,t.label,t.sub,"rail")),n&&n.appendChild(u(t.id,t.label,t.sub,"mobile",()=>{const e=n.closest("[x-data]");e&&e._x_dataStack&&e._x_dataStack[0]&&(e._x_dataStack[0].open=!1)}))});const a=Array.from(document.querySelectorAll("a[data-toc-link]")),s=t=>{a.forEach(e=>{const r=e.dataset.tocLink===t;e.classList.toggle("text-primary-950",r),e.classList.toggle("dark:text-primary-300",r),e.classList.toggle("font-medium",r);const p=e.querySelector("[data-toc-arrow]");p&&(p.style.width=r?"1.4em":"0",p.style.opacity=r?"1":"0")})};s(d[0].id);const m=new IntersectionObserver(t=>{const e=t.filter(r=>r.isIntersecting).sort((r,p)=>r.boundingClientRect.top-p.boundingClientRect.top);e[0]&&s(e[0].target.id)},{rootMargin:"-20% 0px -65% 0px",threshold:0});c.forEach(t=>m.observe(t)),document.addEventListener("astro:before-swap",()=>m.disconnect(),{once:!0})}document.addEventListener("astro:page-load",y);</script> <section class="my-28 pb-12 astro-ovjhrnls"> <div class="w-full v-focus v-container-tight text-center astro-ovjhrnls"> <div class="border border-primary-900/10 dark:border-primary-300/10 bg-primary-200/10 dark:bg-primary-800/10 flex flex-col items-center rounded-3xl px-5 py-16 gap-8 astro-ovjhrnls"> <div class="flex flex-col gap-6 astro-ovjhrnls"> <h5 class="h-section mx-auto my-4 max-w-4xl astro-ovjhrnls"> Let us make some quick suggestions? </h5> <p class="hidden text-primary-950/80 dark:text-primary-200/80 mx-auto max-w-2xl body-lg astro-ovjhrnls"> Unique solutions for your brand. </p> </div> <form netlify action="https://api.web3forms.com/submit" method="POST" id="cta-form" class="needs-validation w-full lg:w-3/5 astro-ovjhrnls" novalidate> <input type="hidden" name="access_key" value="1e0ffadd-2f05-471b-bbc2-36fa32793837" class="astro-ovjhrnls"> <!-- Create your free access key from https://web3forms.com/ --> <!-- key for email - aidenthomasgodbywood@gmail.com --> <input type="checkbox" class="hidden astro-ovjhrnls" style="display:none" name="botcheck"> <div class="w-full flex flex-col md:flex-row gap-6 astro-ovjhrnls"> <div class="w-full md:w-1/2 astro-ovjhrnls"> <label for="cta-short-name" class="sr-only astro-ovjhrnls">Full name</label> <input id="cta-short-name" type="text" placeholder="Name" required class="w-full px-4 py-3 border-2 placeholder:text-gray-800 duration-75 bg-primary-50/20 focus:bg-white dark:bg-primary-300/20 rounded-md outline-none focus:ring-4 border-gray-300 focus:border-gray-600 ring-gray-100 astro-ovjhrnls" name="name"> <div class="empty-feedback invalid-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide your full name. </div> </div> <div class="w-full md:w-1/2 astro-ovjhrnls"> <label for="phone" class="sr-only astro-ovjhrnls">Phone Number</label><input id="phone" type="text" placeholder="Phone" name="phone" required class="w-full px-4 py-3 border-2 placeholder:text-gray-800 duration-75 bg-primary-50/20 focus:bg-white dark:bg-primary-300/20 rounded-md outline-none focus:ring-4 border-gray-300 focus:border-gray-600 ring-gray-100 astro-ovjhrnls"> <div class="empty-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide your phone number. </div> <div class="invalid-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide a valid phone number. </div> </div> </div> <div class="flex flex-col md:flex-row gap-6 pt-4 astro-ovjhrnls"> <div class="w-full md:w-1/2 astro-ovjhrnls"> <label for="email_address" class="sr-only astro-ovjhrnls">Email Address</label><input id="email_address" type="email" placeholder="Email" name="email" required class="w-full px-4 py-3 border-2 placeholder:text-gray-800 duration-75 bg-primary-50/20 focus:bg-white dark:bg-primary-300/20 rounded-md outline-none focus:ring-4 border-gray-300 focus:border-gray-600 ring-gray-100 astro-ovjhrnls"> <div class="empty-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide your email address. </div> <div class="invalid-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide a valid email address. </div> </div> <div class="w-full md:w-1/2 astro-ovjhrnls"> <label for="brand" class="sr-only astro-ovjhrnls">Brand</label><input id="brand" type="text" placeholder="Brand / Website" name="brand" required class="w-full px-4 py-3 border-2 placeholder:text-gray-800 duration-75 bg-primary-50/20 focus:bg-white dark:bg-primary-300/20 rounded-md outline-none focus:ring-4 border-gray-300 focus:border-gray-600 ring-gray-100 astro-ovjhrnls"> <div class="empty-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide your brand name or website. </div> <div class="invalid-feedback text-red-400 text-sm mt-1 astro-ovjhrnls"> Please provide your brand name or website. </div> </div> <input type="hidden" class="sourceLogging astro-ovjhrnls" id="hiddenKey02" name="Contacted on Page" data-name="formUrl1" value=""> <input type="hidden" class="sourceLogging astro-ovjhrnls" id="hiddenKey03" name="Previous Page" data-name="formRef1" value=""> </div> <button class="btn btn-primary mt-8 astro-ovjhrnls" type="submit"> Submit </button> <div id="result" class="mt-3 text-center astro-ovjhrnls"></div> </form> </div> </div> </section> <script> const form = document.getElementById("cta-form"); const result = document.getElementById("result"); if (form && result) { form.addEventListener("submit", function (e) { e.preventDefault(); form.classList.add("was-validated"); if (!form.checkValidity()) { form.querySelectorAll(":invalid")[0].focus(); return; } const formData = new FormData(form); const object = Object.fromEntries(formData); // Source tracking — page/landing/referrer/journey/UTMs land as extra // fields in the Web3Forms email. Provided by the Layout attribution // tracker (window.__vxAttrFlat); is:inline scripts can't import it. try { Object.assign(object, window.__vxAttrFlat ? window.__vxAttrFlat() : {}); } catch {} const json = JSON.stringify(object); result.innerHTML = "Sending..."; fetch("https://api.web3forms.com/submit", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: json, }) .then(async (response) => { let json = await response.json(); if (response.status == 200) { result.classList.add("text-green-500"); result.innerHTML = json.message; // Conversion: GA4 generate_lead + Meta Lead + dataLayer (source auto-merged). try { window.__vxTrackLead?.({ form: 'cta_short', page: location.pathname }); } catch {} } else { console.log(response); result.classList.add("text-red-500"); result.innerHTML = json.message; } }) .catch((error) => { console.log(error); result.innerHTML = "Something went wrong!"; }) .then(function () { form.reset(); form.classList.remove("was-validated"); setTimeout(() => { result.style.display = "none"; }, 5000); }); }); } </script> <script> { const k2 = document.getElementById('hiddenKey02'); const k3 = document.getElementById('hiddenKey03'); if (k2) k2.value = location.pathname; if (k3) k3.value = document.referrer; } </script> </main> <footer class="v-footer bg-secondary-950 text-white astro-35ed7um5" aria-labelledby="footer-heading"> <h3 id="footer-heading" class="sr-only astro-35ed7um5">Footer</h3> <div class="v-wide v-container py-14 sm:py-16 lg:py-20 astro-35ed7um5"> <!-- Top region: CONTACT · NEWSLETTER · SOCIAL (sitemap moves to its own full-width row below so the 10 link groups can breathe). --> <div class="grid grid-cols-1 gap-10 lg:grid-cols-12 lg:gap-8 astro-35ed7um5"> <!-- CONTACT --> <div class="lg:col-span-4 astro-35ed7um5"> <p class="h-eyebrow text-white/45 astro-35ed7um5">Contact</p> <a href="/" class="mt-4 inline-block astro-35ed7um5" data-cursor-text="made with care ✦"> <img class="h-9 w-auto astro-35ed7um5" src="/ax-logo-white.svg" alt="Aidxn Design — Oxenford web design" width="160" height="36"> </a> <address class="mt-5 not-italic text-sm leading-relaxed text-white/70 astro-35ed7um5"> <p class="astro-35ed7um5">Oxenford, QLD</p> <p class="mt-1 astro-35ed7um5">Gold Coast, QLD, Australia</p> <p class="mt-3 astro-35ed7um5"> <a href="mailto:hi@aidxn.com" class="text-white/85 underline-offset-4 hover:text-primary-300 hover:underline astro-35ed7um5"> hi@aidxn.com </a> </p> <p class="mt-1 astro-35ed7um5"> <a href="tel:0412213169" class="text-white/85 underline-offset-4 hover:text-primary-300 hover:underline astro-35ed7um5"> 0412 213 169 </a> </p> </address> </div> <!-- NEWSLETTER --> <div class="lg:col-span-6 astro-35ed7um5"> <p class="h-eyebrow text-white/45 astro-35ed7um5">Newsletter</p> <h4 class="mt-3 text-lg font-medium leading-snug text-white astro-35ed7um5"> Get the occasional build note, no spam. </h4> <form action="https://api.web3forms.com/submit" method="POST" id="newsletter-form" class="mt-5 needs-validation astro-35ed7um5" novalidate> <input type="hidden" name="access_key" value="1e0ffadd-2f05-471b-bbc2-36fa32793837" class="astro-35ed7um5"> <input type="hidden" name="subject" value="New Velocity X newsletter signup" class="astro-35ed7um5"> <input type="hidden" name="from_name" value="Aidxn footer newsletter" class="astro-35ed7um5"> <input type="checkbox" name="botcheck" class="hidden astro-35ed7um5" style="display:none" tabindex="-1" autocomplete="off"> <label for="newsletter-email" class="sr-only astro-35ed7um5">Email address</label> <input id="newsletter-email" type="email" name="email" required placeholder="Enter your email address" class="w-full rounded-md border-2 border-white/15 bg-white/5 px-4 py-3 text-sm text-white placeholder:text-white/45 outline-none transition focus:border-primary-400 focus:ring-4 focus:ring-primary-500/20 astro-35ed7um5"> <p class="newsletter-feedback mt-1 text-sm text-red-400 astro-35ed7um5">Please enter a valid email address.</p> <label class="mt-3 flex items-start gap-2.5 text-xs leading-relaxed text-white/60 astro-35ed7um5"> <input type="checkbox" name="consent" required class="mt-0.5 h-4 w-4 shrink-0 rounded border-white/25 bg-white/5 text-primary-500 accent-primary-500 focus:ring-2 focus:ring-primary-500/40 astro-35ed7um5"> <span class="astro-35ed7um5"> I agree to receive emails and have read the <a href="/privacy/" class="text-white/80 underline underline-offset-2 hover:text-primary-300 astro-35ed7um5">privacy policy</a>. </span> </label> <button type="submit" class="btn btn-primary mt-4 w-full sm:w-auto astro-35ed7um5"> Subscribe </button> <div id="newsletter-result" class="mt-3 text-sm astro-35ed7um5"></div> </form> </div> <!-- SOCIAL cluster — top-right --> <div class="lg:col-span-2 lg:flex lg:flex-col lg:items-end astro-35ed7um5"> <p class="h-eyebrow text-white/45 lg:sr-only astro-35ed7um5">Social</p> <ul role="list" class="mt-3 flex flex-wrap gap-2 lg:mt-0 lg:flex-col astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="https://instagram.com/aidxn.design" target="_blank" rel="noopener" aria-label="Instagram" data-cursor-text="Instagram" class="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/70 transition hover:border-primary-400 hover:bg-primary-500/15 hover:text-primary-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 astro-35ed7um5"> <svg class="h-4 w-4 astro-35ed7um5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.012-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://facebook.com/aidxn.design" target="_blank" rel="noopener" aria-label="Facebook" data-cursor-text="Facebook" class="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/70 transition hover:border-primary-400 hover:bg-primary-500/15 hover:text-primary-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 astro-35ed7um5"> <svg class="h-4 w-4 astro-35ed7um5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://twitter.com/aidxn.design" target="_blank" rel="noopener" aria-label="Twitter" data-cursor-text="Twitter" class="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/70 transition hover:border-primary-400 hover:bg-primary-500/15 hover:text-primary-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 astro-35ed7um5"> <svg class="h-4 w-4 astro-35ed7um5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://github.com/aidenwood" target="_blank" rel="noopener" aria-label="Github" data-cursor-text="Github" class="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/70 transition hover:border-primary-400 hover:bg-primary-500/15 hover:text-primary-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 astro-35ed7um5"> <svg class="h-4 w-4 astro-35ed7um5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://www.youtube.com/@aidxndesign" target="_blank" rel="noopener" aria-label="YouTube" data-cursor-text="YouTube" class="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-white/5 text-white/70 transition hover:border-primary-400 hover:bg-primary-500/15 hover:text-primary-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 astro-35ed7um5"> <svg class="h-4 w-4 astro-35ed7um5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" class="astro-35ed7um5"></path> </svg> </a> </li> </ul> </div> </div> <!-- SITEMAP — its own full-width row so the 10 link groups lay out on a responsive 2→3→4→5 column grid instead of being crushed into a third of the footer. Every Velocity 8 group is carried forward unchanged. --> <nav class="mt-12 border-t border-white/10 pt-10 astro-35ed7um5" aria-label="Sitemap"> <p class="h-eyebrow text-white/45 astro-35ed7um5">Sitemap</p> <div class="mt-6 grid grid-cols-2 gap-x-6 gap-y-8 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 astro-35ed7um5"> <div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Main </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Home</span> </a> </li><li class="astro-35ed7um5"> <a href="/about/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">About</span> </a> </li><li class="astro-35ed7um5"> <a href="/pricing/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Pricing</span> </a> </li><li class="astro-35ed7um5"> <a href="/contact/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Contact</span> </a> </li><li class="astro-35ed7um5"> <a href="/get-started/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Get started</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Services </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/web-design/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Web Design</span> </a> </li><li class="astro-35ed7um5"> <a href="/digital-marketing/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Digital Marketing</span> </a> </li><li class="astro-35ed7um5"> <a href="/graphic-design/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Graphic Design</span> </a> </li><li class="astro-35ed7um5"> <a href="/case-studies/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Case Studies</span> </a> </li><li class="astro-35ed7um5"> <a href="/use-cases/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Use Cases</span> </a> </li><li class="astro-35ed7um5"> <a href="/pricing/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Pricing</span> </a> </li><li class="astro-35ed7um5"> <a href="/labs/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Velocity Labs</span> </a> </li><li class="astro-35ed7um5"> <a href="/blog/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Blog</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Work </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/web-design/previous-work/esteem-clinics/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Medical</span> </a> </li><li class="astro-35ed7um5"> <a href="https://blog.aidxn.com/posts/ecommerce-design-tricks/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5" target="_blank" rel="noopener"> <span class="break-words astro-35ed7um5">E-Commerce</span> <svg class="h-2.5 w-2.5 shrink-0 opacity-50 astro-35ed7um5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="/web-design/previous-work/the-boogie-collective/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Events</span> </a> </li><li class="astro-35ed7um5"> <a href="/web-design/previous-work/ball-realty/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Real Estate</span> </a> </li><li class="astro-35ed7um5"> <a href="/case-studies/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Case studies</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> The build </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/how-it-works/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">How it works</span> </a> </li><li class="astro-35ed7um5"> <a href="/do-i-need-a-rebuild-or-a-refresh/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Rebuild or refresh?</span> </a> </li><li class="astro-35ed7um5"> <a href="/timeline-and-process/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Timeline & process</span> </a> </li><li class="astro-35ed7um5"> <a href="/whats-included/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">What's included</span> </a> </li><li class="astro-35ed7um5"> <a href="/you-own-the-code/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">You own the code</span> </a> </li><li class="astro-35ed7um5"> <a href="/our-guarantee/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Our guarantee</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Why Velocity X </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/velocity-x-vs-agency/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">vs an agency</span> </a> </li><li class="astro-35ed7um5"> <a href="/velocity-x-vs-diy-website-builder/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">vs a DIY builder</span> </a> </li><li class="astro-35ed7um5"> <a href="/velocity-x-vs-freelancer/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">vs a freelancer</span> </a> </li><li class="astro-35ed7um5"> <a href="/integrations/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Integrations</span> </a> </li><li class="astro-35ed7um5"> <a href="/whats-the-catch/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">What's the catch?</span> </a> </li><li class="astro-35ed7um5"> <a href="/velocity-x-faq/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">FAQ</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Industries </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/for-trades/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For trades</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-clinics/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For clinics</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-realty/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For real estate</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-hospitality/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For hospitality</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-fitness/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For fitness</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-professional-services/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Professional services</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-ecommerce/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For e-commerce</span> </a> </li><li class="astro-35ed7um5"> <a href="/for-nonprofits/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">For nonprofits</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Dashboard </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/dashboard/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Overview</span> </a> </li><li class="astro-35ed7um5"> <a href="/dashboard/projects/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Projects</span> </a> </li><li class="astro-35ed7um5"> <a href="/dashboard/analytics/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Analytics</span> </a> </li><li class="astro-35ed7um5"> <a href="/dashboard/marketing-analytics/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Marketing</span> </a> </li><li class="astro-35ed7um5"> <a href="/dashboard/invoices/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Invoices</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Resources </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/blog/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Blog</span> </a> </li><li class="astro-35ed7um5"> <a href="/docs/getting-started/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Documentation</span> </a> </li><li class="astro-35ed7um5"> <a href="/styleguide/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Style guide</span> </a> </li><li class="astro-35ed7um5"> <a href="/quote/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Quote</span> </a> </li><li class="astro-35ed7um5"> <a href="/enquire/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Enquire</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Other apps </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="https://whisperline.aidxn.com" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5" target="_blank" rel="noopener"> <span class="break-words astro-35ed7um5">WhisperLine</span> <svg class="h-2.5 w-2.5 shrink-0 opacity-50 astro-35ed7um5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://midi.aidxn.com" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5" target="_blank" rel="noopener"> <span class="break-words astro-35ed7um5">MIDI Bridge</span> <svg class="h-2.5 w-2.5 shrink-0 opacity-50 astro-35ed7um5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="https://trade.aidxn.com" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5" target="_blank" rel="noopener"> <span class="break-words astro-35ed7um5">TradePilot</span> <svg class="h-2.5 w-2.5 shrink-0 opacity-50 astro-35ed7um5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z" class="astro-35ed7um5"></path> </svg> </a> </li><li class="astro-35ed7um5"> <a href="/web-development/#projects" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Staff Operations Dashboard</span> </a> </li> </ul> </div><div class="flex flex-col gap-2.5 astro-35ed7um5"> <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-white/40 astro-35ed7um5"> Legal </p> <ul role="list" class="flex flex-col gap-1.5 astro-35ed7um5"> <li class="astro-35ed7um5"> <a href="/privacy/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Privacy</span> </a> </li><li class="astro-35ed7um5"> <a href="/terms/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">Terms</span> </a> </li><li class="astro-35ed7um5"> <a href="/about/" class="inline-flex items-center gap-1 rounded text-xs leading-snug text-white/75 hover:text-primary-300 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-400 break-words astro-35ed7um5"> <span class="break-words astro-35ed7um5">ABN 13 392 753 698</span> </a> </li> </ul> </div> </div> </nav> <!-- Copyright + scroll-progress grid --> <div class="mt-12 flex flex-col gap-6 border-t border-white/10 pt-6 sm:flex-row sm:items-end sm:justify-between astro-35ed7um5"> <div class="flex flex-wrap items-center gap-x-4 gap-y-2 astro-35ed7um5"> <p class="text-[11px] text-white/50 astro-35ed7um5"> © Aidxn Design. All rights reserved. · Velocity X build · in active development </p> <script>(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value=="object"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event("astro:visible"));})();</script><astro-island uid="2csBNR" prefix="r6" component-url="/_astro/TestFeatureDropdown.ikubYZSk.js" component-export="default" renderer-url="/_astro/client.BTxWZCU5.js" props="{}" ssr client="visible" opts="{"name":"TestFeatureDropdown","value":true}" await-children><button type="button" class="inline-flex items-center gap-1.5 rounded-full border border-violet-500/30 bg-violet-500/10 px-3 py-1.5 text-xs font-medium text-violet-700 transition hover:border-violet-500/60 hover:bg-violet-500/15 dark:text-violet-200" aria-label="Open test-features menu" id="radix-_r6R_0_" aria-haspopup="menu" aria-expanded="false" data-state="closed"><svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"></path></svg>Test new features<svg class="h-3 w-3 transition-transform group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button><!--astro:end--></astro-island> </div> <div class="text-white/70 sm:w-40 lg:w-56 astro-35ed7um5"> <p class="mb-1.5 text-[10px] uppercase tracking-[0.14em] text-white/35 astro-35ed7um5">Scroll left</p> <div class="scroll-grid astro-35ed7um5 astro-kv54kbgo" data-scroll-grid data-dir="normal" data-target style="--sg-cols:14" aria-hidden="true"> <span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span><span data-cell class="sg-cell astro-kv54kbgo"></span> </div> </div> </div> </div> <!-- GIANT GSAP horizontal-scroll marquee headline --> <div class="v-marquee select-none overflow-hidden border-t border-white/10 py-8 sm:py-10 lg:py-12 astro-35ed7um5" aria-hidden="true"> <div data-footer-marquee class="flex w-max items-center gap-6 whitespace-nowrap pl-[8vw] will-change-transform astro-35ed7um5"> <span class="text-[13vw] font-bold uppercase leading-none tracking-tight text-white sm:text-[11vw] lg:text-[9vw] astro-35ed7um5"> PAY ONCE. OWN FOREVER. </span> <!-- doodle arrow --> <svg class="h-[8vw] w-auto shrink-0 text-primary-400 sm:h-[7vw] lg:h-[6vw] astro-35ed7um5" viewBox="0 0 120 60" fill="none" aria-hidden="true"> <path d="M4 40 C 30 10, 70 10, 104 32" stroke="currentColor" stroke-width="5" stroke-linecap="round" fill="none" class="astro-35ed7um5"></path> <path d="M88 22 L 108 33 L 90 46" stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none" class="astro-35ed7um5"></path> </svg> <span class="text-[13vw] font-bold leading-none tracking-tight text-white/60 sm:text-[11vw] lg:text-[9vw] astro-35ed7um5"> no rent, no lock-in </span> <span class="text-[11vw] leading-none sm:text-[9vw] lg:text-[7vw] astro-35ed7um5">⚡️</span> <span class="text-[13vw] font-bold leading-none tracking-tight text-transparent [-webkit-text-stroke:2px_theme(colors.primary.400)] sm:text-[11vw] lg:text-[9vw] lg:[-webkit-text-stroke:3px_theme(colors.primary.400)] astro-35ed7um5"> just your site. </span> </div> </div> <div aria-hidden="true" class="lg:hidden astro-35ed7um5" style="height: var(--v-sticky-cta-h, 0px);"></div> </footer> <!-- GSAP + ScrollTrigger (repo convention: inline CDN tags, matches Slot-img-gsap.astro) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/ScrollTrigger.min.js"></script> <script type="module">function p(){const t=document.getElementById("newsletter-form"),e=document.getElementById("newsletter-result");!t||!e||t.dataset.bound==="1"||(t.dataset.bound="1",t.addEventListener("submit",n=>{if(n.preventDefault(),t.classList.add("was-validated"),!t.checkValidity()){t.querySelector(":invalid")?.focus();return}const i=JSON.stringify(Object.fromEntries(new FormData(t)));e.textContent="Subscribing…",e.className="mt-3 text-sm text-white/60",fetch("https://api.web3forms.com/submit",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:i}).then(async r=>{const s=await r.json();if(r.status===200){e.className="mt-3 text-sm text-green-400",e.textContent="You're on the list. Cheers.";try{window.__vxTrack?.("newsletter_signup",{page:location.pathname})}catch{}}else e.className="mt-3 text-sm text-red-400",e.textContent=s.message||"Something went wrong."}).catch(()=>{e.className="mt-3 text-sm text-red-400",e.textContent="Something went wrong!"}).then(()=>{t.reset(),t.classList.remove("was-validated"),setTimeout(()=>{e.textContent=""},5e3)})}))}let o=null,l=null;function w(){const t=window.gsap;if(!t)return;const e=document.querySelector("[data-footer-marquee]");if(!e||e.dataset.looped==="1")return;e.dataset.looped="1";const n=document.createElement("div");for(n.className="flex shrink-0 items-center gap-6 whitespace-nowrap pr-6";e.firstChild;)n.appendChild(e.firstChild);const i=n.cloneNode(!0);if(i.setAttribute("aria-hidden","true"),e.append(n,i),e.classList.remove("pl-[8vw]","gap-6","whitespace-nowrap"),window.matchMedia("(prefers-reduced-motion: reduce)").matches){t.set(e,{x:0});return}const r=95,s=()=>n.offsetWidth||1;t.set(e,{x:0}),o=t.to(e,{x:`-=${s()}`,duration:s()/r,ease:"none",repeat:-1,paused:!0,modifiers:{x:t.utils.unitize(a=>t.utils.wrap(-s(),0,a))}});let d=!1,u=!1;const m=()=>{const a=window.innerHeight+window.scrollY>=document.documentElement.scrollHeight-4;a&&!d?o?.play():!a&&d&&o?.pause(),d=a},c=()=>{u||(u=!0,requestAnimationFrame(()=>{m(),u=!1}))};window.addEventListener("scroll",c,{passive:!0}),window.addEventListener("resize",c,{passive:!0}),m(),l=()=>{window.removeEventListener("scroll",c),window.removeEventListener("resize",c)}}function f(){p(),w()}document.addEventListener("astro:page-load",f);document.addEventListener("astro:before-swap",()=>{l&&(l(),l=null),o&&(o.kill(),o=null)});</script> <script type="module" src="/_astro/Cursor.astro_astro_type_script_index_0_lang.D5PzqQ5J.js"></script> <script type="module">const a=new WeakSet;function v(){const p=window.matchMedia("(hover: hover) and (pointer: fine)").matches,x=window.matchMedia("(prefers-reduced-motion: reduce)").matches;if(!p||x)return;const g=new Set([...Array.from(document.querySelectorAll("[data-magnetic]")),...Array.from(document.querySelectorAll(".btn"))]),E=Array.from(g).filter(e=>!a.has(e)),s=[];E.forEach(e=>{a.add(e);const w=Number(e.dataset.magneticStrength)||14,t={rect:e.getBoundingClientRect(),strength:w,raf:null,tx:0,ty:0,dx:0,dy:0},L=40,c=()=>{t.tx+=(t.dx-t.tx)*.18,t.ty+=(t.dy-t.ty)*.18,e.style.transform=`translate(${t.tx.toFixed(2)}px, ${t.ty.toFixed(2)}px)`,Math.abs(t.dx-t.tx)>.1||Math.abs(t.dy-t.ty)>.1?t.raf=requestAnimationFrame(c):(t.raf=null,t.dx===0&&t.dy===0&&(e.style.transform=""))},o=()=>{t.raf==null&&(t.raf=requestAnimationFrame(c))},d=r=>{const n=t.rect,h=n.left+n.width/2,u=n.top+n.height/2,A=Math.hypot(r.clientX-h,r.clientY-u),y=Math.max(n.width,n.height)/2+L;if(A>y){t.dx=0,t.dy=0,o();return}const l=t.strength/y;t.dx=(r.clientX-h)*l,t.dy=(r.clientY-u)*l,o()},m=()=>{t.rect=e.getBoundingClientRect()},f=()=>{t.dx=0,t.dy=0,o()};e.addEventListener("pointerenter",m),e.addEventListener("pointermove",d),e.addEventListener("pointerleave",f),s.push(()=>{e.removeEventListener("pointerenter",m),e.removeEventListener("pointermove",d),e.removeEventListener("pointerleave",f),t.raf!=null&&cancelAnimationFrame(t.raf),e.style.transform="",a.delete(e)})});const i=()=>{s.forEach(e=>e()),document.removeEventListener("astro:before-swap",i)};document.addEventListener("astro:before-swap",i)}v();document.addEventListener("astro:page-load",v);</script> <!-- Velocity Growth Layer — site-wide conversion surfaces. client:idle so they never compete with the critical render path. --> <astro-island uid="18Uvqk" prefix="r7" component-url="/_astro/LeadCapturePopup.B2UJ5etU.js" component-export="default" renderer-url="/_astro/client.BTxWZCU5.js" props="{}" ssr client="idle" opts="{"name":"LeadCapturePopup","value":true}"></astro-island> <astro-island uid="Z28LPCz" prefix="r8" component-url="/_astro/SocialProofToast.BYiVPbC5.js" component-export="default" renderer-url="/_astro/client.BTxWZCU5.js" props="{"messages":[1,[[0,"Built one-on-one by Aiden Wood — 15 years full-stack."],[0,"Trusted by trades, clinics, realty and hospitality."],[0,"Production-grade builds shipped across Australia."],[0,"The agency build and the ops dashboard — bought once."]]]}" ssr client="idle" opts="{"name":"SocialProofToast","value":true}"></astro-island> <script type="module" src="/_astro/Layout.astro_astro_type_script_index_2_lang.B1BbvcOt.js"></script> <script type="module" src="/_astro/Layout.astro_astro_type_script_index_3_lang.DSS55SzR.js"></script> <script> // Flickity + Astro ViewTransitions lifecycle. // // Bug history: using `astro:after-swap` alone caused carousels to fail on // 2nd page change because the new page's DOM nodes sometimes inherit the // `.flickity-enabled` class (or stale Flickity binding state) — the init // skipped them but Flickity's internal bindings were orphaned. // // Fix: hard-destroy every Flickity instance before nav, then re-init from // scratch via `astro:page-load` (fires on initial load AND every nav). (function () { var CONFIGS = [ ['.fullscreen-carousel', { cellAlign: 'left', contain: true, prevNextButtons: false, imagesLoaded: true, adaptiveHeight: false, pageDots: false, autoPlay: 3200, wrapAround: true }], ['.web-carousel', { cellAlign: 'left', contain: true, prevNextButtons: false, imagesLoaded: true, adaptiveHeight: true, groupCells: '85%', pageDots: false, autoPlay: 2400, wrapAround: true }], ['.market-carousel', { cellAlign: 'left', contain: true, prevNextButtons: false, imagesLoaded: true, adaptiveHeight: true, groupCells: '85%', pageDots: false, autoPlay: 2400, wrapAround: true }], // Card-grid scrollers (Work showcase, Blog posts, future galleries). // Drag-to-scroll with arrows, no auto-play — these are content lists, // not hero sliders. ['.work-carousel', { cellAlign: 'left', contain: true, prevNextButtons: true, imagesLoaded: true, pageDots: false, freeScroll: true, freeScrollFriction: 0.04, groupCells: '80%', wrapAround: false }], ['.blog-carousel', { cellAlign: 'left', contain: true, prevNextButtons: true, imagesLoaded: true, pageDots: false, freeScroll: true, freeScrollFriction: 0.04, groupCells: '80%', wrapAround: false }], ['.labs-carousel', { cellAlign: 'left', contain: true, prevNextButtons: true, imagesLoaded: true, pageDots: false, freeScroll: true, freeScrollFriction: 0.04, groupCells: '80%', wrapAround: false }], ['.dashboard-carousel', { cellAlign: 'left', contain: true, prevNextButtons: true, imagesLoaded: true, pageDots: false, freeScroll: true, freeScrollFriction: 0.04, groupCells: '80%', wrapAround: false }], ]; function destroyAll() { if (typeof window.Flickity === 'undefined') return; for (var i = 0; i < CONFIGS.length; i++) { document.querySelectorAll(CONFIGS[i][0]).forEach(function (el) { var inst = window.Flickity.data(el); if (inst) { try { inst.destroy(); } catch (e) {} } // strip any stale class in case destroy didn't fully clean up el.classList.remove('flickity-enabled'); el.classList.remove('is-draggable'); }); } } function initAll() { if (typeof window.Flickity === 'undefined') { // Flickity CDN may load slightly after this script — retry once on next frame. return void requestAnimationFrame(initAll); } for (var i = 0; i < CONFIGS.length; i++) { document.querySelectorAll(CONFIGS[i][0]).forEach(function (el) { if (el.classList.contains('flickity-enabled')) return; new window.Flickity(el, CONFIGS[i][1]); }); } } // BEFORE the swap: destroy current carousels so we never inherit zombies. document.addEventListener('astro:before-swap', destroyAll); // AFTER the new DOM is in place (initial load AND every ViewTransitions nav). document.addEventListener('astro:page-load', initAll); // First load fallback in case astro:page-load missed it. if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAll); } else { initAll(); } })(); </script> <!-- Attribution tracker — records the session journey (landing page, every page visited, external referrer) + persists first-touch UTMs/gclid so lead forms can report where a lead actually came from. Also exposes window.__vxAttr / __vxAttrFlat for is:inline form handlers (Web3Forms) that can't import modules. See src/lib/attribution.ts. --> <script type="module" src="/_astro/Layout.astro_astro_type_script_index_4_lang.Xm0fppYO.js"></script> <!-- Google tag (gtag.js) + Consent Mode v2. Loaded async on the MAIN thread (NOT Partytown): GA4 Realtime is unreliable when gtag runs inside a web worker, so it stays on the main thread. The inline config below defines gtag() and pushes consent/js/config to dataLayer BEFORE gtag.js loads, so Consent Mode v2 order is preserved. --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-MN4T5PXYLD"></script> <script>(function(){const gaId = "G-MN4T5PXYLD"; const adsId = ""; window.dataLayer = window.dataLayer || []; // MUST be window.gtag, not `function gtag(){}` — define:vars wraps this // script in an IIFE, so a bare function declaration stays closure-local // and ConsentBanner/analytics.ts calls to window.gtag silently no-op. var gtag = window.gtag = window.gtag || function(){dataLayer.push(arguments);}; // Consent Mode v2 — analytics on by default (AU-first), ad/remarketing // storage denied until the visitor accepts (ConsentBanner flips these). var consented = false; try { consented = localStorage.getItem('velocity.consent') === 'granted'; } catch (e) {} gtag('consent', 'default', { ad_storage: consented ? 'granted' : 'denied', ad_user_data: consented ? 'granted' : 'denied', ad_personalization: consented ? 'granted' : 'denied', analytics_storage: 'granted', }); gtag('js', new Date()); gtag('config', gaId); if (adsId) gtag('config', adsId); // Google Ads remarketing tag (only if set) // ViewTransitions: client-side navs never reload this script, so gtag's // automatic page_view only covers the landing page. Fire one per swap — // skip the first astro:page-load (initial load already counted by config). var vxFirstLoad = true; document.addEventListener('astro:page-load', function () { if (vxFirstLoad) { vxFirstLoad = false; return; } gtag('event', 'page_view', { page_location: window.location.href, page_title: document.title, }); }); })();</script> <!-- Meta Pixel — only loads when business.metaPixelId is set. Remarketing events wait for ad consent (see analytics.ts + ConsentBanner). --> <!-- Meta Pixel + Conversions API helper (browser + server, shared eventID dedup). No-ops until brand.json metaPixelId + META_CAPI_TOKEN are set. --> <script type="module">document.addEventListener("astro:after-swap",()=>{try{localStorage.getItem("theme")==="dark"&&document.documentElement.classList.add("dark")}catch{}});</script> <!-- Velocity Growth Layer — cookie/consent banner (controls ad storage). --> <astro-island uid="WL4Ls" prefix="r9" component-url="/_astro/ConsentBanner.IIA8veUx.js" component-export="default" renderer-url="/_astro/client.BTxWZCU5.js" props="{}" ssr client="idle" opts="{"name":"ConsentBanner","value":true}"></astro-island> </body></html>