← All components

Primitive · 2 of 13

GlowButton

The single conversion CTA. It is the one place the system fills a large area with purple, and therefore the one place text runs near-black on light. There is exactly one of these per page.

Vue island src/components/common/GlowButton.vue rendered live from compiled/GlowButton.js GSAP

Props

PropTypeNotes
title *stringRequired. The label.
hrefstringAlways renders an <a>; with no href it is a non-navigating anchor.
classstringApplied to both the wrapper and the anchor — see findings.

Stories

This is the real component with its GSAP wired up — move the pointer across it and the orb follows at 0.5s power2.out; the edge flares ramp in the outer 10% of the width. On leave, a delayed tween parks the orb back at left: 20%, top: 50%.

Default title="Book a free consultation" #9980FF fill, #1B0D4E label — 5.67:1, the system's only sanctioned dark-on-light pairing.
Full width class="w-full" The cookie-banner usage. Press it — the anchor scales to 0.98.

Anatomy

Three glow layers, not one. They stack around the anchor rather than inside it, which is why the wrapper is position: relative and the edges are pulled outside the box with -left-2 / -right-2.

LayerRoleBehavior
box-shadowThe static bloomThree stacked shadows, #7A58FFA8 / #4F22FFA6 / #5B34FA. Shipped as --shadow-glow-button.
.orbPointer-tracked highlightInside the anchor, overflow: hidden. GSAP tweens left/top to the cursor over 0.5s.
.edge-glow ×2Proximity flareOutside the anchor. Opacity ramps in the outer 10% of the width on each side.

Findings

class is applied twice. The prop lands on the wrapper <div> and again on the inner <a>. Layout utilities that make sense on one usually do not on the other — Cookies.vue passes w-full max-w-[12.5rem]!, which constrains the wrapper and then re-applies the same cap to the anchor inside it. Anything positional (absolute, mx-auto) will double-apply.

Nothing guards reduced motion. The pointer tween is GSAP, driven from JS on mousemove — and the current site ships no prefers-reduced-motion handling at all (an earlier revision of global.css neutralized CSS animation, and even that never covered JS tweens). A user who has asked for reduced motion gets the full tracking glow. Guarding it needs a window.matchMedia('(prefers-reduced-motion: reduce)') check alongside the existing pointer: coarse one.

Touch is handled, and handled early. isTouchDevice is resolved in onMounted from three signals — pointer: coarse, ontouchstart, maxTouchPoints — and every handler returns early when true. The static bloom remains, so the button still reads as the CTA without the tracking.

The label is unselectable by design. select-none on the span stops a drag across the button from highlighting the text mid-tween.