CTA Section

Shader-backed CTA used site-wide. A live WebGL flowing-silk gradient with a cursor-driven liquid trail behind the copy. Ground rule: aqua on dark surfaces (the default), blue on light ones. A legibility scrim, a ground-aware ring + shadow, and a large CTA pill button.

Default Preview

Default site-wide variant — heading + subtitle + button, no eyebrow — in the default aqua tone: the light flowing-silk shader with ink copy. The tone rule is ground-based: aqua on dark surfaces, blue on light ones. This is the page-closing banner on every dark page. Move the cursor across it for the liquid trail.

Let's start something big

Open to full-time, freelance, and contract work. Book a free 30-minute intro call to chat or send me an email.

Get in touch

Compact

Short banner — copy left, button right, no eyebrow, h3rather than h2. Stacks to one column below lg. Use it to close out a section rather than to anchor a page — the Services module and the home footer render it, in whichever tone their ground calls for.

Not quite what you need?

Let's talk through your goals and shape the right support.

Aqua tone

The default, for dark grounds. FlowingGradient, a port of the composio.dev card background — a white / sky / cyan “liquid silk” gradient with sharp folds, a faint grid and grain. The copy is the paper ladder. The card carries surface-paper surface-white itself, so it reads the same on either ground; only the shadow adapts — a sky bloom on the dark page, a neutral cast shadow under a paper ancestor. Hover: the cursor leaves a blue liquid trail in the shader that keeps flowing and fades over about a second, plus the shadow lift. Rendered by every dark-page CTA, including the Services closing banner on /services. Move the cursor across the previews below to see the trail.

Guard rail: the sky stop behind the copy clears AA for --foreground and --foreground-secondary with margin but not for --text-tertiary or --text-muted — an aqua card must not carry those rungs.

On the dark page

Not quite what you need?

Let's talk through your goals and shape the right support.

Inside a white paper band (home)

Not quite what you need?

Let's talk through your goals and shape the right support.

Blue tone

For light grounds — the home page's white band renders both the Services closing banner (ServicesSection ctaTone="blue") and the footer card in it. Same shader as aqua, resting on --hero-mesh-1 and folding into --hero-mesh-2 / -3, over the shared .hero-mesh-base; copy is the dark ladder (surface-ink). The trail is light pooling in the navy — sky at rest, cyan along the stroke, vivid blue across it. The shadow is ground-aware: .card-cta-blue casts a neutral shadow under a paper ancestor and would glow on the dark page.

Guard rails: the tone rests on the vivid #1a7bff and turns the silk fold highlight off, so that stop is the brightest pixel the shader can put behind copy; a directional scrim holds 35% → 30% over the copy and falls to 8% past it, so the far end glows while white @ 72% body copy stays above 4.5:1 (measured worst ≈5.2, headline ≈8.5). That hold is sized for the compact layout only — the blue tone is never the centred default variant. No --text-tertiary / --text-muted copy. And the trail is bright enough that body copy dips below AA for the second the pool takes to drain after the cursor crosses it — a deliberate trade for the effect.

Compact, on the white band (home)

Let's start something big

Open to full-time, freelance, and contract work. Book a free 30-minute intro call to chat or send me an email.

Default variant, on paper

Let's start something big

Open to full-time, freelance, and contract work. Book a free 30-minute intro call to chat or send me an email.

Get in touch

With Eyebrow

Default card with an optional uppercase eyebrow above the heading and the subtitle suppressed. Rendered on the UX-audit page. The eyebrow is ignored by the compact variant.

Start a conversation

Pick the offering that fits — we'll shape the engagement together.

Get in touch

View Full Version

Props

eyebrow

string (optional)

Small uppercase label rendered above the heading. Omit (or pass an empty string) to hide it.

title

string (optional)

Default: "Let's start something big"

subtitle

string (optional)

Default: "Open to full-time, freelance, and contract work. Book a free 30-minute intro call to chat or send me an email." Pass an empty string (subtitle="") to hide it entirely.

buttonText

string (optional)

Default: "Get in touch"

buttonHref

string (optional)

Default: "/contact"

variant

'default' | 'compact' (optional)

Default: "default" — the tall centred card. " compact" is the short banner: copy left, button right, no eyebrow, h3 heading, one column below lg.

tone

'aqua' | 'blue' | 'ink' (optional)

Ground rule: aqua on dark, blue on light. Default: "aqua" — flowing shader with the light stops from --cta-flow-1…4, ink copy, .card-cta-aqua surface-paper surface-white. "blue" — the same shader on the hero navy (--hero-mesh-1…3), white copy, .card-cta-blue surface-ink; pass it on the white and paper bands. "ink" — the older navy mesh, .card-cta surface-ink, rendered by no page now. Forwarded to MeshCardBackdrop, whose own default is ink.

Visual Composition

Background

FlowingGradient in both live tones. Blue: the hero blue family over a .hero-mesh-base fallback, fold highlight off, with a directional scrim (35% → 30% over the copy, 8% past it — horizontal on lg+, vertical when stacked). The older ink tone is the HeroMeshGradient mesh at speed=0.7 over the same base with the 55% → 15% → 55% scrim. Aqua (the default): stops from --cta-flow-1…4 over .card-mesh-base-aqua, and the same scrim turns white because it is built from --background.

Ring + glow

.card-cta — 1px accent ring plus a soft blue page glow. That glow is additive light, so on a light ground .surface-paper .card-cta swaps it for a tighter neutral shadow; otherwise it haloes. .card-cta-blue is the same ring and bloom expressed through the accent tokens, plus the hover lift.

.card-cta-aqua follows the same split: a sky-tinted ring + bloom (from --cta-flow-2) on the dark page, and .surface-paper .card-cta-aqua the neutral shadow under a paper ancestor. Both transition on hover to a deeper shadow. On aqua the shader itself also reacts: the cursor drives a liquid trail (see Mesh Card Backdrop).

Button

Uses CTAButton — the large animated-glow pill — for visual consistency with hero/contact CTAs elsewhere on the site.

Usage

tsx
import { CTASection } from '@/components/sections/CTASection'

// Default site-wide usage on dark pages (blog, work, faq, about, etc.) — aqua
<CTASection />

// On a light band (home): blue
<CTACard variant="compact" tone="blue" />

// With custom content
<CTASection
  title="Ready to get started?"
  subtitle="Let's build something amazing together."
  buttonText="Contact me"
  buttonHref="/contact"
/>

// Compact banner: closes out a section
<CTASection
  variant="compact"
  title="Not quite what you need?"
  subtitle="Let's talk through your goals and shape the right support."
  buttonText="Get in touch"
/>

// Light aqua mesh, ink copy (Services closing banner)
<CTACard
  variant="compact"
  tone="aqua"
  title="Not quite what you need?"
  subtitle="Let's talk through your goals and shape the right support."
  buttonText="Get in touch"
/>

// With eyebrow, no subtitle (UX-audit page)
<CTASection
  eyebrow="Start with an audit"
  title="See exactly what to fix first."
  subtitle=""
  buttonText="Request a UX audit"
/>

Embed Without Section Wrapper (CTACard)

When you need the card inline inside an existing layout (e.g. the Services module), import CTACard instead — it renders the visual chrome without the py-20 lg:py-32 section padding or the container. CTASection wraps the card in .panel-container: still the page measure, but with the SectionFrame panel's fixed 16px inset rather than the wider px-4 sm:px-6 lg:px-8 content scale, so the card lines up with the outer edges of the panels above it.

tsx
import { CTACard } from '@/components/sections/CTASection'

<div className="mt-16 lg:mt-24">
  <CTACard
    variant="compact"
    title="Not quite what you need?"
    subtitle="Let's talk through your goals and shape the right support."
    buttonText="Get in touch"
  />
</div>

Import

tsx
import { CTASection, CTACard } from '@/components/sections/CTASection'