Primitive · 2 of 13
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.
| Prop | Type | Notes |
|---|---|---|
| title * | string | Required. The label. |
| href | string | Always renders an <a>; with no href it is a non-navigating anchor. |
| class | string | Applied to both the wrapper and the anchor — see findings. |
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%.
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.
| Layer | Role | Behavior |
|---|---|---|
| box-shadow | The static bloom | Three stacked shadows, #7A58FFA8 / #4F22FFA6 / #5B34FA. Shipped as --shadow-glow-button. |
| .orb | Pointer-tracked highlight | Inside the anchor, overflow: hidden. GSAP tweens left/top to the cursor over 0.5s. |
| .edge-glow ×2 | Proximity flare | Outside the anchor. Opacity ramps in the outer 10% of the width on each side. |
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.