17 — CTA & Footer

📣 Chapter 17 — CTA & Footer

Two sections close out the page's visible content: a dark call-to-action band that uses ::before/::after purely for decoration rather than content, and a four-column footer that reuses the auto-fit grid pattern one more time before finishing with a classic space-between flex bar.

11
CTA Section — gradient + decorative glow circles

The technique: pseudo-elements on the section

So far ::before has appeared on li elements and a blockquote to inject real content (a checkmark, a quote mark). Here we use both ::before and ::after on the section itself — not to add text, but to paint two large blurred circles that create a soft glow behind the content.

Three CSS properties make this work together:

  • position: relative on the section — so the circles can be positioned inside it
  • overflow: hidden on the section — so the oversized circles are clipped at the section edge
  • z-index: 1 on the inner content — so text sits above the circles in the stacking order

Stacking order — bottom to top

z auto .cta-section — the dark gradient background; position: relative anchors everything inside
z auto .cta-section::before — large blurred circle, top-left; paints over the gradient but under the content
z auto .cta-section::after — large blurred circle, bottom-right
z-index: 1 .cta-inner — heading, paragraph, button; always above the circles ✓ ▲ top

CSS — the section

🎨 style.css — .cta-section
/* ── Step 11 · CTA ───────────────────────────────── */ .cta-section { padding: clamp(4rem, 10vw, 7rem) 0; background: linear-gradient(135deg, var(--clr-dark) 0%, var(--clr-navy) 60%, #1a1f35 100%); position: relative; /* anchors ::before and ::after */ overflow: hidden; /* clips the circles at section edges */ text-align: center; }

CSS — the glow circles

🎨 style.css — ::before and ::after circles
/* Shared rules for both circles */ .cta-section::before, .cta-section::after { content: ''; /* required — even for purely visual elements */ position: absolute; border-radius: 50%; background: var(--clr-accent); pointer-events: none; } /* Top-left circle */ .cta-section::before { width: 500px; height: 500px; top: -200px; /* half the circle outside the top edge */ left: -150px; opacity: 0.1; filter: blur(80px); } /* Bottom-right circle */ .cta-section::after { width: 400px; height: 400px; bottom: -180px; right: -100px; opacity: 0.08; filter: blur(70px); }

The negative top/left/bottom/right values push circles outside the section boundary — overflow: hidden clips them cleanly.

filter: blur() performance note: Blurring large elements is GPU-accelerated in modern browsers, but using it on many elements at once can hurt performance. One or two blurred pseudo-elements per page is fine — don't scatter filter: blur across dozens of elements.

CSS — inner content + white button

🎨 style.css — .cta-inner + .btn-white
.cta-inner { position: relative; /* lifts above the pseudo-element circles */ z-index: 1; max-width: 640px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 1.5rem; } /* White button — inverts the standard button for dark backgrounds */ .btn-white { background: var(--clr-bg); /* white fill */ color: var(--clr-dark); /* dark text */ border-color: var(--clr-bg); } .btn-white:hover { background: transparent; color: var(--clr-bg); /* white text on hover */ }

.btn-white is a modifier class that overrides the base .btn colours — it relies on the button system built in Chapter 12 (Step 3).

Why z-index: 1 is enough: Pseudo-elements created with position: absolute on the section participate in the same stacking context as their siblings. Since they have no explicit z-index, they paint in DOM order — before .cta-inner. Setting z-index: 1 on the inner div guarantees it's always on top, even if the order ever changes.

Live demo

Hover the button to see the invert transition. Toggle the controls to reveal the hidden circles underneath the content.

Ready to start?

Let's build something worth remembering

Tell us about your project and we'll get back to you within one business day.

Start a project

Step 11 — complete

  • Dark gradient background with three colour stops ✓
  • position: relative + overflow: hidden on the section ✓
  • ::before — top-left blurred glow circle ✓
  • ::after — bottom-right blurred glow circle ✓
  • .cta-innerz-index: 1 lifts content above circles ✓
  • .btn-white — inverted button for dark backgrounds, hover ghost ✓
12
Footer — 4-column grid, link transitions, bottom bar

The footer starts with a dark shell matching the CTA above it — a solid --clr-dark background with a hairline top border separating it from the CTA section:

🎨 style.css — .site-footer
/* ── Step 12 · Footer ────────────────────────────── */ .site-footer { background: var(--clr-dark); padding-top: clamp(3rem, 6vw, 5rem); color: rgba(255, 255, 255, 0.7); }

CSS — the four-column grid

Another auto-fit grid — the same pattern used for Services (Chapter 14) and Testimonials (Chapter 16). On a wide screen the brand column sits beside three link columns; on mobile all four stack.

🎨 style.css — .footer-grid
.footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: clamp(2rem, 5vw, 3.5rem); padding-bottom: clamp(3rem, 6vw, 4rem); border-bottom: 1px solid rgba(255,255,255,0.08); }

minmax(180px, 1fr) gives four columns on screens wider than ~800 px and gracefully collapses to fewer columns below that — no breakpoints written.

CSS — brand column

🎨 style.css — .footer-brand + .footer-logo + .footer-tagline
.footer-brand { display: flex; flex-direction: column; gap: 1rem; } .footer-logo { font-size: 1.4rem; font-weight: 800; color: #fff; letter-spacing: -0.03em; text-decoration: none; } .footer-logo span { color: var(--clr-accent); } .footer-tagline { font-size: 0.85rem; color: rgba(255,255,255,0.4); /* dimmer than body text */ line-height: 1.65; max-width: 22ch; /* wraps after ~22 characters */ }

CSS — link columns

🎨 style.css — .footer-col + .footer-col-heading
.footer-col { display: flex; flex-direction: column; gap: 0.6rem; } .footer-col-heading { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: rgba(255,255,255,0.45); margin-bottom: 0.25rem; } /* Base link — hover transition added below */ .footer-col a { font-size: 0.88rem; color: rgba(255,255,255,0.55); text-decoration: none; display: block; /* full-width click target */ }

Links are display: block so the entire row is clickable — not just the text characters.

Colour opacity for hierarchy: Using rgba(255,255,255, opacity) rather than hardcoded hex values lets us express a clear dimming hierarchy on the dark footer — logo at full white, tagline at 40%, headings at 45%, links at 55% — all relative to the background, automatically.

Live demo — grid structure

Toggle the grid outlines to see how the four columns are placed. The footer-bottom bar below is intentionally unstyled here — it gets its layout next.

Step 12 so far

  • .site-footer — dark background, top padding ✓
  • .footer-grid — 4-column auto-fit, fluid gap, border-bottom ✓
  • .footer-brand — logo, tagline, opacity hierarchy ✓
  • .footer-col — column headings + base link styles ✓

Part 1 — Link hover transitions

Footer links have a two-part hover effect: they brighten to full white and nudge 4 px to the right with translateX. The nudge gives a directional feel — a subtle visual cue that the link leads somewhere.

The golden rule: put transition on the base state

.footer-col a { transition: color 0.2s, transform 0.2s; } Transition defined here — plays in both directions
.footer-col a:hover { color: #fff; transform: translateX(4px); } Only the end values — no transition needed here
⚠ If you put transition only on :hover, the animation plays going IN but snaps back instantly going OUT — always define it on the base state.
🎨 style.css — .footer-col a hover transition
.footer-col a { font-size: 0.88rem; color: rgba(255,255,255,0.55); text-decoration: none; display: block; transition: color 0.2s ease, transform 0.2s ease; /* ← base state */ } .footer-col a:hover { color: #fff; /* full white */ transform: translateX(4px); /* rightward nudge */ }

Two properties transition independently — color and transform can have different durations or easings if you want (e.g. a faster colour change than the nudge).

Why transform instead of margin-left or padding-left? transform: translateX() moves the element visually without affecting layout — no reflow, no pushing neighbouring elements. It's also GPU-composited, making it buttery smooth. Animating margin or padding triggers layout recalculation on every frame.

Part 2 — The footer-bottom bar

A thin bar below the grid containing copyright text (left) and legal links (right). The classic justify-content: space-between flex pattern handles the split:

← copyright (left) legal links (right) →
© 2026 Lumen Studio
Privacy Terms
🎨 style.css — .footer-bottom
.footer-bottom { display: flex; align-items: center; justify-content: space-between; /* copyright left | legal right */ gap: 1rem; /* gap when they wrap on mobile */ flex-wrap: wrap; /* stack gracefully on narrow screens */ padding: 1.25rem 0; font-size: 0.8rem; color: rgba(255,255,255,0.28); /* dimmer than the link cols */ } .footer-legal { display: flex; gap: 1.5rem; } .footer-legal a { color: rgba(255,255,255,0.3); text-decoration: none; transition: color 0.2s; } .footer-legal a:hover { color: rgba(255,255,255,0.75); }

flex-wrap: wrap means on narrow screens copyright stacks above the legal links rather than overflowing — both items left-align naturally when they wrap.

Why no translateX nudge on the legal links? The footer-bottom bar is purely functional — it doesn't need the same navigational energy as the main link columns. A simple colour brightening is sufficient and keeps the bar feeling calm.

Live demo — complete footer

Hover any footer link to see the colour + nudge transition. Toggle the footer-bottom annotation to see the flex split.

Step 12 — complete

  • .site-footer — dark background, top padding ✓
  • .footer-grid — 4-column auto-fit
  • .footer-brand — logo, tagline, opacity hierarchy ✓
  • .footer-col a — hover: white + translateX(4px) nudge ✓
  • .footer-bottomspace-between flex, wraps on mobile ✓
  • .footer-legal — dimmed links, colour-only hover ✓
Next — Chapter 18: The JavaScript layer — IntersectionObserver for scroll-reveal animations on .reveal elements, plus the scroll-shadow effect on the sticky nav.