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.
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: relativeon the section — so the circles can be positioned inside itoverflow: hiddenon the section — so the oversized circles are clipped at the section edgez-index: 1on the inner content — so text sits above the circles in the stacking order
Stacking order — bottom to top
.cta-section — the dark gradient background; position: relative anchors everything inside
.cta-section::before — large blurred circle, top-left; paints over the gradient but under the content
.cta-section::after — large blurred circle, bottom-right
.cta-inner — heading, paragraph, button; always above the circles ✓
▲ top
CSS — the 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
/* 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 across dozens of elements.
CSS — inner content + white button
.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).
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 projectStep 11 — complete
- Dark gradient background with three colour stops ✓
position: relative+overflow: hiddenon the section ✓::before— top-left blurred glow circle ✓::after— bottom-right blurred glow circle ✓.cta-inner—z-index: 1lifts content above circles ✓.btn-white— inverted button for dark backgrounds, hover ghost ✓
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:
/* ── 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.
.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
.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
.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.
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-columnauto-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
transition only on :hover, the animation plays going IN but snaps back instantly going OUT — always define it on the base state.
.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).
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:
.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.
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-columnauto-fit✓.footer-brand— logo, tagline, opacity hierarchy ✓.footer-col a— hover: white +translateX(4px)nudge ✓.footer-bottom—space-betweenflex, wraps on mobile ✓.footer-legal— dimmed links, colour-only hover ✓
IntersectionObserver for scroll-reveal animations on .reveal elements, plus the scroll-shadow effect on the sticky nav.