---
id: seed:archetypes/cta
title: Call-to-action section archetypes
source_type: seed_archetype
priority: hot
---

A CTA section converts intent into action. It is the second-most-important section after the hero, and it should appear at least twice on a long page — once mid-scroll, once at the bottom. Six working archetypes:

---

**Banner CTA (default).** Full-width band, contrasting background, one centered headline + one button. The headline restates the core promise as a forward-looking outcome.

*Animation & Visual Details:*
- *Entrance*: Section fades in with translateY(30px) → 0 on scroll-into-view (IntersectionObserver at 20% threshold)
- *Hover*: Button scales to 1.04 with shadow lift; background gradient shifts on the inner pseudo-element
- *Gradients*: Banner uses a linear gradient background (accent-dark to accent); button has a subtle gradient overlay
- *Radial circles*: Two radial glow orbs positioned behind the button at opposite corners (8% opacity)
- *Scroll trigger*: Stagger children (title → description → actions) at 0.1s intervals on scroll-in
- *JS interaction*: None required for basic banner — pure CSS animation on hover

```html
<section data-amb-section="cta" class="cta-section cta-section--banner">
  <div class="cta-bg-layer">
    <div class="cta-orb cta-orb--1"></div>
    <div class="cta-orb cta-orb--2"></div>
  </div>
  <div class="container">
    <div class="cta-content">
      <h2 class="cta-title">{specific, outcome-led, second-person: "Start shipping in minutes"}</h2>
      <p class="cta-description">{one-sentence risk reducer: "Free 14-day trial. No credit card."}</p>
      <div class="cta-actions">
        <a class="cta-button" href="#">{action verb + object}</a>
        <a class="cta-link" href="#">{lower commitment alternative}</a>
      </div>
    </div>
  </div>
</section>
```

```css
:root {
  --cta-orb-size: 300px;
  --cta-enter-duration: 0.5s;
}

.cta-section--banner {
  position: relative;
  padding: clamp(64px, 8vw, 96px) 0;
  background: linear-gradient(135deg, #1e1e2e 0%, #2d1b4e 100%);
  overflow: hidden;
}

.cta-bg-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

.cta-orb {
  position: absolute;
  border-radius: 50%;
  opacity: 0.08;
}

.cta-orb--1 {
  width: var(--cta-orb-size);
  height: var(--cta-orb-size);
  background: radial-gradient(circle, #6366f1 0%, transparent 70%);
  top: -50px;
  right: 10%;
}

.cta-orb--2 {
  width: calc(var(--cta-orb-size) * 0.7);
  height: calc(var(--cta-orb-size) * 0.7);
  background: radial-gradient(circle, #a855f7 0%, transparent 70%);
  bottom: -30px;
  left: 5%;
}

.cta-content {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 640px;
  margin: 0 auto;
}

.cta-title {
  font-size: clamp(1.75rem, 3.5vw, 2.5rem);
  font-weight: 700;
  color: #fff;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--cta-enter-duration) ease, transform var(--cta-enter-duration) ease;
}

.cta-description {
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  color: rgba(255,255,255,0.7);
  margin-top: 12px;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--cta-enter-duration) ease, transform var(--cta-enter-duration) ease;
  transition-delay: 0.1s;
}

.cta-actions {
  display: flex;
  gap: 16px;
  justify-content: center;
  align-items: center;
  margin-top: 32px;
  flex-wrap: wrap;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--cta-enter-duration) ease, transform var(--cta-enter-duration) ease;
  transition-delay: 0.2s;
}

.cta-section--banner.is-visible .cta-title,
.cta-section--banner.is-visible .cta-description,
.cta-section--banner.is-visible .cta-actions {
  opacity: 1;
  transform: translateY(0);
}

.cta-button {
  display: inline-flex;
  align-items: center;
  padding: 14px 32px;
  background: linear-gradient(135deg, #6366f1, #7c3aed);
  color: #fff;
  font-weight: 600;
  font-size: 1rem;
  border-radius: 8px;
  text-decoration: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  box-shadow: 0 4px 20px rgba(99,102,241,0.35);
  cursor: pointer;
}

.cta-button:hover {
  transform: scale(1.04) translateY(-1px);
  box-shadow: 0 8px 28px rgba(99,102,241,0.45);
}

.cta-button:active {
  transform: scale(0.98);
}

.cta-link {
  color: rgba(255,255,255,0.7);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.2s ease;
}

.cta-link:hover {
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .cta-title, .cta-description, .cta-actions {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
```

*JS behavior:*
```javascript
// Scroll-triggered entrance
const ctaSection = document.querySelector('.cta-section--banner');
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      observer.unobserve(entry.target);
    }
  });
}, { threshold: 0.2 });
observer.observe(ctaSection);
```

---

**Two-column CTA.** Headline + description on the left, form (email capture, demo request) on the right. Use when the action is a form rather than a button click.

*Animation & Visual Details:*
- *Entrance*: Left column (content) fades in from left; right column (form) fades in from right, 0.15s stagger
- *Hover*: Form inputs show gradient border on focus; submit button has radial glow on hover
- *Gradients*: Form container has a subtle gradient border (1px gradient stroke); submit button uses gradient background
- *Radial circles*: Radial glow behind the submit button (accent color at 15% opacity)
- *Scroll trigger*: Each column animates independently on scroll-into-view
- *JS interaction*: Floating labels on inputs (label moves up on focus/filled), inline validation with shake on error and check on success

```html
<section data-amb-section="cta" class="cta-section cta-section--two-col">
  <div class="container">
    <div class="cta-two-col">
      <div class="cta-col-content">
        <h2 class="cta-title">{specific, outcome-led headline}</h2>
        <p class="cta-description">{one-sentence risk reducer}</p>
      </div>
      <div class="cta-col-form">
        <form class="cta-form" novalidate>
          <div class="cta-form-field">
            <label class="cta-form-label" for="cta-email">Work email</label>
            <input class="cta-form-input" type="email" id="cta-email" name="email" required autocomplete="email" />
            <span class="cta-form-error">Please enter a valid work email</span>
          </div>
          <button class="cta-button cta-button--submit" type="submit">
            <span class="cta-button-text">{action verb}</span>
            <span class="cta-button-loading" aria-hidden="true">
              <svg class="spinner" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" opacity="0.25"/><path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"/></svg>
            </span>
          </button>
          <p class="cta-form-note">No credit card required · Cancel anytime</p>
        </form>
      </div>
    </div>
  </div>
</section>
```

```css
.cta-section--two-col {
  padding: clamp(64px, 8vw, 96px) 0;
  background: var(--color-bg-alt, #f8fafc);
}

.cta-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: center;
}

.cta-col-content {
  opacity: 0;
  transform: translateX(-30px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.cta-col-form {
  position: relative;
  opacity: 0;
  transform: translateX(30px);
  transition: opacity 0.5s ease, transform 0.5s ease;
  transition-delay: 0.15s;
}

.cta-section--two-col.is-visible .cta-col-content,
.cta-section--two-col.is-visible .cta-col-form {
  opacity: 1;
  transform: translateX(0);
}

.cta-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.cta-form-field {
  position: relative;
}

.cta-form-label {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translateY(-50%);
  font-size: 0.95rem;
  color: #64748b;
  pointer-events: none;
  transition: all 0.2s ease;
  background: #fff;
  padding: 0 4px;
}

.cta-form-input {
  width: 100%;
  padding: 16px;
  border: 2px solid #e2e8f0;
  border-radius: 10px;
  font-size: 1rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  background: #fff;
}

.cta-form-input:focus {
  outline: none;
  border-color: #6366f1;
  box-shadow: 0 0 0 4px rgba(99,102,241,0.1);
}

.cta-form-input:focus + .cta-form-label,
.cta-form-input:not(:placeholder-shown) + .cta-form-label {
  top: 0;
  font-size: 0.75rem;
  color: #6366f1;
}

.cta-form-error {
  display: none;
  font-size: 0.8rem;
  color: #ef4444;
  margin-top: 6px;
  padding-left: 4px;
}

.cta-form-field.is-invalid .cta-form-input {
  border-color: #ef4444;
  animation: shake 0.4s ease;
}

.cta-form-field.is-invalid .cta-form-error {
  display: block;
}

.cta-form-field.is-valid .cta-form-input {
  border-color: #22c55e;
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-6px); }
  40%, 80% { transform: translateX(6px); }
}

.cta-button--submit {
  position: relative;
  padding: 16px 32px;
  font-size: 1rem;
  overflow: hidden;
}

.cta-button-loading {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: inherit;
}

.cta-button--submit.is-loading .cta-button-text {
  opacity: 0;
}

.cta-button--submit.is-loading .cta-button-loading {
  display: flex;
}

.cta-button--submit.is-loading .cta-button-loading .spinner {
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.cta-button--submit::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at center, rgba(255,255,255,0.15) 0%, transparent 70%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.cta-button--submit:hover::before {
  opacity: 1;
}

.cta-form-note {
  font-size: 0.8rem;
  color: #64748b;
  text-align: center;
}

@media (max-width: 768px) {
  .cta-two-col {
    grid-template-columns: 1fr;
  }
  .cta-col-form {
    transition-delay: 0s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cta-col-content, .cta-col-form {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .cta-form-field.is-invalid .cta-form-input {
    animation: none;
  }
}
```

*JS behavior:*
```javascript
// Floating label already handled by CSS :focus + :not(:placeholder-shown)
// Validation
const form = document.querySelector('.cta-form');
const emailInput = form.querySelector('#cta-email');
const submitBtn = form.querySelector('.cta-button--submit');

emailInput.addEventListener('blur', () => {
  const field = emailInput.closest('.cta-form-field');
  if (emailInput.validity.valid) {
    field.classList.remove('is-invalid');
    field.classList.add('is-valid');
  } else if (emailInput.value !== '') {
    field.classList.remove('is-valid');
    field.classList.add('is-invalid');
  }
});

emailInput.addEventListener('input', () => {
  const field = emailInput.closest('.cta-form-field');
  field.classList.remove('is-invalid', 'is-valid');
});

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  if (!emailInput.validity.valid) {
    emailInput.closest('.cta-form-field').classList.add('is-invalid');
    return;
  }
  submitBtn.classList.add('is-loading');
  await new Promise(r => setTimeout(r, 1500)); // simulate API call
  submitBtn.classList.remove('is-loading');
  // handle success
});
```

---

**Card CTA.** Boxed card centered on the page, large radius, distinct from the surrounding sections. Use when the CTA needs to feel like a discrete decision rather than a banner crossing.

*Animation & Visual Details:*
- *Entrance*: Card scales from 0.95 → 1 with opacity fade on scroll-in, 0.6s ease-out
- *Hover*: Card lifts with translateY(-4px) and shadow deepens; gradient border animates (rotates 15deg)
- *Gradients*: Card has an animated gradient border (border-image with conic-gradient), subtle background gradient inside
- *Radial circles*: Radial glow centered behind the card, pulses at 0.5Hz on hover
- *Scroll trigger*: Single IntersectionObserver triggers the entrance animation
- *JS interaction*: None beyond scroll trigger — hover effects are CSS-only

```html
<section data-amb-section="cta" class="cta-section cta-section--card">
  <div class="cta-card-glow"></div>
  <div class="container">
    <div class="cta-card-wrapper">
      <div class="cta-card">
        <h2 class="cta-title">{outcome-led headline}</h2>
        <p class="cta-description">{risk reducer in one sentence}</p>
        <div class="cta-actions">
          <a class="cta-button" href="#">{primary CTA}</a>
          <a class="cta-link" href="#">{secondary}</a>
        </div>
      </div>
    </div>
  </div>
</section>
```

```css
.cta-section--card {
  padding: clamp(80px, 10vw, 120px) 0;
  background: var(--color-bg, #ffffff);
}

.cta-card-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 500px;
  height: 300px;
  background: radial-gradient(ellipse, rgba(99,102,241,0.12) 0%, transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: card-glow-pulse 3s ease-in-out infinite;
}

@keyframes card-glow-pulse {
  0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
}

.cta-card-wrapper {
  display: flex;
  justify-content: center;
  position: relative;
  z-index: 1;
}

.cta-card {
  position: relative;
  max-width: 560px;
  width: 100%;
  padding: 48px;
  background: #fff;
  border-radius: 24px;
  text-align: center;
  box-shadow: 0 4px 24px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 0.6s ease, transform 0.6s ease, box-shadow 0.3s ease;
}

.cta-card::before {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 26px;
  background: conic-gradient(from 0deg, #6366f1, #a855f7, #ec4899, #f59e0b, #6366f1);
  z-index: -1;
  opacity: 0.5;
  transition: opacity 0.3s ease;
  animation: gradient-rotate 6s linear infinite;
}

@keyframes gradient-rotate {
  to { background: conic-gradient(from 360deg, #6366f1, #a855f7, #ec4899, #f59e0b, #6366f1); }
}

.cta-section--card.is-visible .cta-card {
  opacity: 1;
  transform: scale(1);
}

.cta-card:hover {
  box-shadow: 0 12px 40px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.04);
}

.cta-card:hover::before {
  opacity: 0.8;
}

@media (prefers-reduced-motion: reduce) {
  .cta-card {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .cta-card::before, .cta-card-glow {
    animation: none;
  }
}
```

*JS behavior:*
```javascript
const cardSection = document.querySelector('.cta-section--card');
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      observer.unobserve(entry.target);
    }
  });
}, { threshold: 0.3 });
observer.observe(cardSection);
```

---

**Gradient wave CTA.** Animated gradient background wave (CSS-only mesh using multiple radial gradients) with content centered. The wave shifts on scroll.

*Animation & Visual Details:*
- *Entrance*: Content fades up with standard stagger; background wave animates in from below
- *Hover*: Wave animation speed increases by 1.5x on section hover; CTA button has enhanced glow
- *Gradients*: Multiple overlapping radial gradients form a wave pattern (coral, violet, cyan); animated via CSS custom properties
- *Radial circles*: Four radial burst points animate independently at different speeds
- *Scroll trigger*: Background wave position shifts on scroll (±3% based on scrollY)
- *JS interaction*: Wave position controlled via CSS custom property updated on scroll

```html
<section data-amb-section="cta" class="cta-section cta-section--wave">
  <div class="cta-wave-bg">
    <div class="cta-wave-orb cta-wave-orb--1"></div>
    <div class="cta-wave-orb cta-wave-orb--2"></div>
    <div class="cta-wave-orb cta-wave-orb--3"></div>
    <div class="cta-wave-orb cta-wave-orb--4"></div>
  </div>
  <div class="container">
    <div class="cta-content">
      <h2 class="cta-title">{outcome-led headline}</h2>
      <p class="cta-description">{risk reducer}</p>
      <div class="cta-actions">
        <a class="cta-button cta-button--glow" href="#">{action verb}</a>
      </div>
    </div>
  </div>
</section>
```

```css
:root {
  --wave-speed: 15s;
  --wave-orb-1-x: 15%;
  --wave-orb-1-y: 20%;
  --wave-orb-2-x: 85%;
  --wave-orb-2-y: 15%;
  --wave-orb-3-x: 75%;
  --wave-orb-3-y: 75%;
  --wave-orb-4-x: 20%;
  --wave-orb-4-y: 80%;
}

.cta-section--wave {
  position: relative;
  padding: clamp(80px, 10vw, 120px) 0;
  overflow: hidden;
}

.cta-wave-bg {
  position: absolute;
  inset: -20%;
  z-index: 0;
}

.cta-wave-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  animation: orb-drift var(--wave-speed) ease-in-out infinite alternate;
}

.cta-wave-orb--1 {
  width: 600px;
  height: 600px;
  top: var(--wave-orb-1-y);
  left: var(--wave-orb-1-x);
  background: radial-gradient(circle, rgba(255,107,107,0.5) 0%, transparent 70%);
  animation-duration: calc(var(--wave-speed) * 0.7);
}

.cta-wave-orb--2 {
  width: 500px;
  height: 500px;
  top: var(--wave-orb-2-y);
  left: var(--wave-orb-2-x);
  background: radial-gradient(circle, rgba(168,85,247,0.4) 0%, transparent 70%);
  animation-duration: calc(var(--wave-speed) * 0.9);
  animation-delay: -3s;
}

.cta-wave-orb--3 {
  width: 450px;
  height: 450px;
  top: var(--wave-orb-3-y);
  left: var(--wave-orb-3-x);
  background: radial-gradient(circle, rgba(72,219,251,0.4) 0%, transparent 70%);
  animation-duration: calc(var(--wave-speed) * 1.1);
  animation-delay: -7s;
}

.cta-wave-orb--4 {
  width: 550px;
  height: 550px;
  top: var(--wave-orb-4-y);
  left: var(--wave-orb-4-x);
  background: radial-gradient(circle, rgba(245,158,11,0.35) 0%, transparent 70%);
  animation-duration: calc(var(--wave-speed) * 0.8);
  animation-delay: -11s;
}

@keyframes orb-drift {
  0% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(20px, -15px) scale(1.05); }
  100% { transform: translate(-15px, 20px) scale(0.95); }
}

.cta-content {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 600px;
  margin: 0 auto;
}

.cta-button--glow {
  background: linear-gradient(135deg, #6366f1, #8b5cf6, #a855f7);
  background-size: 200% 200%;
  animation: glow-gradient 3s ease infinite;
  box-shadow: 0 4px 24px rgba(99,102,241,0.4);
}

@keyframes glow-gradient {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

.cta-button--glow:hover {
  box-shadow: 0 8px 36px rgba(99,102,241,0.55);
}

@media (prefers-reduced-motion: reduce) {
  .cta-wave-orb {
    animation: none;
  }
  .cta-button--glow {
    animation: none;
    background-size: 100% 100%;
  }
}
```

*JS behavior:*
```javascript
// Scroll-linked wave position
const waveSection = document.querySelector('.cta-section--wave');
const orbs = waveSection.querySelectorAll('.cta-wave-orb');

window.addEventListener('scroll', () => {
  const rect = waveSection.getBoundingClientRect();
  const scrollProgress = Math.max(0, Math.min(1, (window.innerHeight - rect.top) / (window.innerHeight + rect.height)));
  const offsetX = (scrollProgress - 0.5) * 6;
  const offsetY = (scrollProgress - 0.5) * 4;
  orbs.forEach((orb, i) => {
    const factor = (i % 2 === 0) ? 1 : 0.8;
    orb.style.setProperty('--scroll-offset-x', `${offsetX * factor}%`);
    orb.style.setProperty('--scroll-offset-y', `${offsetY * factor}%`);
  });
}, { passive: true });
```

---

**Radial burst CTA.** Radial circles (concentric rings) burst from behind the CTA button on hover. A visually striking, attention-grabbing CTA.

*Animation & Visual Details:*
- *Entrance*: Standard fade-up on scroll-in; radial rings are hidden initially
- *Hover*: Three concentric radial rings burst outward from the button center (scale 1 → 2.5 with opacity fade), 0.4s stagger between rings
- *Gradients*: Rings use gradient strokes (accent to transparent); button uses solid accent with glow
- *Radial circles*: Three rings positioned absolutely behind the button, each a different size (80px, 120px, 160px diameter)
- *Scroll trigger*: Content fades in with stagger, rings animate in on first viewport entry (not hover)
- *JS interaction*: Ring burst triggered on mouseenter with staggered timeouts; rings reset on mouseleave

```html
<section data-amb-section="cta" class="cta-section cta-section--radial">
  <div class="container">
    <div class="cta-content">
      <h2 class="cta-title">{outcome-led headline}</h2>
      <p class="cta-description">{risk reducer}</p>
      <div class="cta-radial-wrapper">
        <div class="cta-radial-ring cta-radial-ring--1"></div>
        <div class="cta-radial-ring cta-radial-ring--2"></div>
        <div class="cta-radial-ring cta-radial-ring--3"></div>
        <a class="cta-button cta-button--radial" href="#">{action verb}</a>
      </div>
    </div>
  </div>
</section>
```

```css
.cta-section--radial {
  padding: clamp(80px, 10vw, 120px) 0;
  background: linear-gradient(180deg, #0f0f1a 0%, #1a1a2e 100%);
}

.cta-radial-wrapper {
  display: inline-block;
  position: relative;
  margin-top: 36px;
}

.cta-radial-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(1);
  border-radius: 50%;
  border: 1px solid rgba(99,102,241,0.3);
  pointer-events: none;
  opacity: 0;
  transition: none;
}

.cta-radial-ring--1 {
  width: 80px;
  height: 80px;
  border-color: rgba(99,102,241,0.5);
}

.cta-radial-ring--2 {
  width: 140px;
  height: 140px;
  border-color: rgba(99,102,241,0.25);
}

.cta-radial-ring--3 {
  width: 200px;
  height: 200px;
  border-color: rgba(99,102,241,0.15);
}

.cta-button--radial {
  position: relative;
  z-index: 1;
  padding: 16px 40px;
  font-size: 1.1rem;
  font-weight: 600;
  background: linear-gradient(135deg, #6366f1, #7c3aed);
  color: #fff;
  border-radius: 12px;
  box-shadow: 0 0 0 0 rgba(99,102,241,0);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.cta-radial-wrapper.is-active .cta-radial-ring--1 {
  animation: ring-burst 0.6s ease-out forwards;
  animation-delay: 0ms;
}

.cta-radial-wrapper.is-active .cta-radial-ring--2 {
  animation: ring-burst 0.6s ease-out forwards;
  animation-delay: 80ms;
}

.cta-radial-wrapper.is-active .cta-radial-ring--3 {
  animation: ring-burst 0.6s ease-out forwards;
  animation-delay: 160ms;
}

@keyframes ring-burst {
  0% { opacity: 0.8; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(2.5); }
}

.cta-button--radial:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 30px rgba(99,102,241,0.45);
}
```

*JS behavior:*
```javascript
const radialWrapper = document.querySelector('.cta-radial-wrapper');
const radialBtn = radialWrapper.querySelector('.cta-button--radial');

radialBtn.addEventListener('mouseenter', () => {
  radialWrapper.classList.add('is-active');
});

radialBtn.addEventListener('mouseleave', () => {
  radialWrapper.classList.remove('is-active');
  // Reset ring animations
  const rings = radialWrapper.querySelectorAll('.cta-radial-ring');
  rings.forEach(ring => {
    ring.style.animation = 'none';
    ring.offsetHeight; // trigger reflow
    ring.style.animation = '';
  });
});
```

---

**Magnetic button CTA.** The CTA button physically moves toward the cursor when within a 100px radius. Creates a playful, interactive feel.

*Animation & Visual Details:*
- *Entrance*: Standard fade-up on scroll-in; button starts at center position
- *Hover*: Button translates toward cursor position within radius; spring physics applied (not linear snap)
- *Gradients*: Background uses subtle linear gradient; subtle gradient glow around button
- *Radial circles*: Radial glow behind the button that intensifies as cursor approaches
- *Scroll trigger*: Button resets to center when section scrolls out of viewport
- *JS interaction*: Full magnetic attraction physics — button position tracked relative to cursor, spring applied each frame

```html
<section data-amb-section="cta" class="cta-section cta-section--magnetic">
  <div class="container">
    <div class="cta-content">
      <h2 class="cta-title">{outcome-led headline}</h2>
      <p class="cta-description">{risk reducer}</p>
      <div class="cta-magnetic-area">
        <div class="cta-magnetic-glow"></div>
        <a class="cta-button cta-button--magnetic" href="#">{action verb}</a>
      </div>
    </div>
  </div>
</section>
```

```css
.cta-section--magnetic {
  padding: clamp(80px, 10vw, 120px) 0;
  background: var(--color-bg-alt, #f8fafc);
}

.cta-magnetic-area {
  position: relative;
  display: inline-block;
  margin-top: 36px;
}

.cta-magnetic-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 100px;
  background: radial-gradient(ellipse, rgba(99,102,241,0.12) 0%, transparent 70%);
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.cta-button--magnetic {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 16px 40px;
  font-size: 1.05rem;
  font-weight: 600;
  background: linear-gradient(135deg, #1e1e2e, #2d1b4e);
  color: #fff;
  border-radius: 12px;
  transition: box-shadow 0.3s ease;
  will-change: transform;
}

.cta-button--magnetic:hover {
  box-shadow: 0 8px 32px rgba(99,102,241,0.3);
}

.cta-magnetic-area.is-near .cta-magnetic-glow {
  opacity: 1.5;
  transform: translate(-50%, -50%) scale(1.2);
}
```

*JS behavior:*
```javascript
const magneticArea = document.querySelector('.cta-magnetic-area');
const magneticBtn = magneticArea.querySelector('.cta-button--magnetic');

let currentX = 0, currentY = 0;
let targetX = 0, targetY = 0;
const spring = 0.12;
const maxDistance = 100;

magneticArea.addEventListener('mousemove', (e) => {
  const rect = magneticArea.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  const deltaX = e.clientX - centerX;
  const deltaY = e.clientY - centerY;
  const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

  if (distance < maxDistance) {
    const strength = 1 - distance / maxDistance;
    targetX = deltaX * strength * 0.5;
    targetY = deltaY * strength * 0.5;
    magneticArea.classList.add('is-near');
  } else {
    targetX = 0;
    targetY = 0;
    magneticArea.classList.remove('is-near');
  }
});

magneticArea.addEventListener('mouseleave', () => {
  targetX = 0;
  targetY = 0;
  magneticArea.classList.remove('is-near');
});

function animateMagnetic() {
  currentX += (targetX - currentX) * spring;
  currentY += (targetY - currentY) * spring;
  magneticBtn.style.transform = `translate(${currentX}px, ${currentY}px)`;
  requestAnimationFrame(animateMagnetic);
}
animateMagnetic();
```

---

Copy rules: button verb must match the resulting page. "Get started" should lead to a signup; "Talk to sales" should lead to a calendar booking. Risk-reducer line ("No credit card", "14-day trial", "Cancel anytime") almost always lifts conversion — include it unless paying-only.

Layout rules: the CTA section should be the most visually distinct band on the page — a different background color, an accent border, or oversized type. Never have two CTAs of equal visual weight in the same viewport — pick the primary, downrank the secondary.

CSS custom property summary:
```css
:root {
  --cta-enter-duration: 0.5s;
  --cta-orb-size: 300px;
  --color-accent: #6366f1;
}
```