Coach — Transformations
An energetic dark transformations gallery for fitness coaches: drag-to-reveal before/after slider cards, each with weeks and pounds-lost stats, a signature goal badge, and a client testimonial quote. Filter the whole grid by goal — fat loss, muscle gain, recomposition, or endurance — with an animated result count, and drag the electric-lime handle across each photo to watch the transformation happen.
MCP
Code
:root {
--bg: #0f1115;
--surface: #171a21;
--surface-2: #1f232c;
--lime: #c2f542;
--lime-d: #a3d62f;
--orange: #ff6b35;
--ink: #f2f4f0;
--muted: #9aa0a6;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 600px at 80% -10%, rgba(194, 245, 66, 0.08), transparent 60%),
radial-gradient(900px 500px at -10% 10%, rgba(255, 107, 53, 0.08), transparent 55%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrap {
max-width: 1120px;
margin: 0 auto;
padding: 48px 20px 64px;
}
/* ---------- Header ---------- */
.head {
margin-bottom: 28px;
}
.eyebrow {
margin: 0 0 8px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--lime);
}
.title {
margin: 0;
font-size: clamp(38px, 8vw, 68px);
font-weight: 800;
line-height: 0.98;
letter-spacing: 0.01em;
text-transform: uppercase;
}
.lede {
margin: 12px 0 0;
max-width: 46ch;
color: var(--muted);
font-size: 16px;
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px;
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid var(--line);
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 8px 15px;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease, border-color 0.15s ease,
color 0.15s ease;
}
.chip:hover {
transform: translateY(-1px);
border-color: var(--lime);
}
.chip:focus-visible {
outline: 2px solid var(--lime);
outline-offset: 2px;
}
.chip.is-active {
background: var(--lime);
border-color: var(--lime);
color: #10130a;
box-shadow: 0 0 0 4px rgba(194, 245, 66, 0.16);
}
.count {
margin: 0 0 0 auto;
font-size: 13px;
font-weight: 600;
color: var(--muted);
}
.count span {
color: var(--lime);
}
/* ---------- Grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 28px;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 16px;
display: flex;
flex-direction: column;
gap: 14px;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease,
opacity 0.25s ease;
animation: pop 0.35s ease both;
}
.card:hover {
transform: translateY(-4px);
border-color: var(--line-2);
box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.7);
}
.card.is-hidden {
display: none;
}
@keyframes pop {
from {
opacity: 0;
transform: translateY(10px);
}
}
.badge {
position: absolute;
top: 26px;
left: 26px;
z-index: 4;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 5px 10px;
border-radius: 999px;
color: #10130a;
backdrop-filter: blur(4px);
}
.badge--fat-loss {
background: var(--lime);
}
.badge--muscle {
background: var(--orange);
color: #fff;
}
.badge--recomp {
background: #6ee7ff;
}
.badge--endurance {
background: #c9a3ff;
}
/* ---------- Before / After slider ---------- */
.ba {
position: relative;
aspect-ratio: 4 / 5;
border-radius: var(--r-md);
overflow: hidden;
cursor: ew-resize;
user-select: none;
touch-action: none;
border: 1px solid var(--line);
--pos: 50%;
}
.ba:focus-visible {
outline: 2px solid var(--lime);
outline-offset: 3px;
}
.ba-img {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
}
.ba-tag {
position: absolute;
bottom: 12px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 4px 9px;
border-radius: 999px;
background: rgba(15, 17, 21, 0.72);
color: var(--ink);
backdrop-filter: blur(4px);
}
.ba-after .ba-tag {
right: 12px;
color: var(--lime);
}
.ba-before .ba-tag {
left: 12px;
}
.ba-before {
width: var(--pos);
border-right: 2px solid var(--lime);
z-index: 2;
filter: grayscale(0.35) contrast(0.92) brightness(0.82);
}
.ba-handle {
position: absolute;
top: 0;
bottom: 0;
left: var(--pos);
transform: translateX(-50%);
width: 40px;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.ba-grip {
width: 34px;
height: 34px;
border-radius: 50%;
background: var(--lime);
box-shadow: 0 0 0 4px rgba(194, 245, 66, 0.28), 0 6px 16px rgba(0, 0, 0, 0.5);
position: relative;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.ba-grip::before,
.ba-grip::after {
content: "";
position: absolute;
top: 50%;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
transform: translateY(-50%);
}
.ba-grip::before {
left: 8px;
border-right: 6px solid #10130a;
}
.ba-grip::after {
right: 8px;
border-left: 6px solid #10130a;
}
.ba:hover .ba-grip,
.ba:focus-visible .ba-grip {
transform: scale(1.08);
box-shadow: 0 0 0 6px rgba(194, 245, 66, 0.32), 0 6px 18px rgba(0, 0, 0, 0.55);
}
/* Gradient "photos" — clearly fictional stand-ins */
.ba-before--1 {
background: linear-gradient(160deg, #2a2f3a, #3b3040 70%, #4a3b2e);
}
.ba-after--1 {
background: linear-gradient(160deg, #1f2a1a, #2f4a20 60%, #c2f542 200%);
}
.ba-before--2 {
background: linear-gradient(160deg, #2b2b33, #34313f 70%, #453040);
}
.ba-after--2 {
background: linear-gradient(160deg, #3a2416, #5a2f18 60%, #ff6b35 220%);
}
.ba-before--3 {
background: linear-gradient(160deg, #2a2e38, #363042 70%, #40323a);
}
.ba-after--3 {
background: linear-gradient(160deg, #16323a, #1f4a55 60%, #6ee7ff 240%);
}
.ba-before--4 {
background: linear-gradient(160deg, #2c2c34, #33313d 70%, #3f3145);
}
.ba-after--4 {
background: linear-gradient(160deg, #241a3a, #3a2f5a 60%, #c9a3ff 240%);
}
.ba-before--5 {
background: linear-gradient(160deg, #2a2f3a, #3d3040 70%, #4a382e);
}
.ba-after--5 {
background: linear-gradient(160deg, #1e2a19, #33501f 60%, #a3d62f 220%);
}
.ba-before--6 {
background: linear-gradient(160deg, #2b2b33, #35313f 70%, #43303e);
}
.ba-after--6 {
background: linear-gradient(160deg, #3a2214, #5a2c16 60%, #ff8c42 220%);
}
/* ---------- Stats ---------- */
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 12px 8px;
}
.stat {
text-align: center;
display: flex;
flex-direction: column;
gap: 2px;
border-right: 1px solid var(--line);
}
.stat:last-child {
border-right: 0;
}
.stat b {
font-size: 20px;
font-weight: 800;
letter-spacing: -0.01em;
color: var(--lime);
}
.stat small {
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
/* ---------- Quote ---------- */
.quote {
margin: 0;
font-size: 14px;
color: var(--ink);
padding-left: 12px;
border-left: 3px solid var(--orange);
}
.quote cite {
display: block;
margin-top: 6px;
font-size: 12px;
font-style: normal;
font-weight: 600;
color: var(--muted);
}
/* ---------- Empty ---------- */
.empty {
margin: 40px 0;
text-align: center;
color: var(--muted);
font-weight: 600;
}
/* ---------- CTA ---------- */
.cta {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 18px;
background: linear-gradient(120deg, var(--surface-2), var(--surface));
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
padding: 26px 28px;
}
.cta-title {
margin: 0;
font-size: 26px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.01em;
}
.cta-sub {
margin: 4px 0 0;
color: var(--muted);
font-size: 14px;
}
.btn {
font: inherit;
font-weight: 700;
font-size: 15px;
color: #10130a;
background: var(--lime);
border: 0;
border-radius: var(--r-md);
padding: 14px 26px;
cursor: pointer;
box-shadow: 0 0 24px rgba(194, 245, 66, 0.35);
transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.15s ease;
}
.btn:hover {
background: var(--lime-d);
transform: translateY(-2px);
box-shadow: 0 0 34px rgba(194, 245, 66, 0.55);
}
.btn:active {
transform: translateY(0);
}
.btn:focus-visible {
outline: 2px solid var(--white);
outline-offset: 3px;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 24px);
background: var(--surface-2);
color: var(--ink);
border: 1px solid var(--lime);
border-radius: 999px;
padding: 12px 22px;
font-size: 14px;
font-weight: 600;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 20;
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.wrap {
padding: 32px 14px 48px;
}
.grid {
grid-template-columns: 1fr;
}
.count {
margin-left: 0;
width: 100%;
}
.chip {
padding: 9px 14px;
}
.cta {
padding: 22px 18px;
}
.btn {
width: 100%;
}
.ba-grip {
width: 40px;
height: 40px;
}
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2200);
}
/* ---------- Before / After sliders ---------- */
function clamp(v, min, max) {
return Math.min(max, Math.max(min, v));
}
function setPos(ba, pct) {
pct = clamp(pct, 0, 100);
ba.style.setProperty("--pos", pct + "%");
ba.setAttribute("aria-valuenow", Math.round(pct));
}
function posFromEvent(ba, clientX) {
var rect = ba.getBoundingClientRect();
return ((clientX - rect.left) / rect.width) * 100;
}
document.querySelectorAll(".ba").forEach(function (ba) {
var dragging = false;
function start(clientX) {
dragging = true;
setPos(ba, posFromEvent(ba, clientX));
}
function move(clientX) {
if (!dragging) return;
setPos(ba, posFromEvent(ba, clientX));
}
function end() {
dragging = false;
}
ba.addEventListener("pointerdown", function (e) {
ba.setPointerCapture(e.pointerId);
start(e.clientX);
});
ba.addEventListener("pointermove", function (e) {
move(e.clientX);
});
ba.addEventListener("pointerup", end);
ba.addEventListener("pointercancel", end);
// Keyboard support
ba.addEventListener("keydown", function (e) {
var cur = parseFloat(ba.getAttribute("aria-valuenow")) || 50;
var step = e.shiftKey ? 20 : 5;
switch (e.key) {
case "ArrowLeft":
case "ArrowDown":
setPos(ba, cur - step);
e.preventDefault();
break;
case "ArrowRight":
case "ArrowUp":
setPos(ba, cur + step);
e.preventDefault();
break;
case "Home":
setPos(ba, 0);
e.preventDefault();
break;
case "End":
setPos(ba, 100);
e.preventDefault();
break;
}
});
});
/* ---------- Filtering ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
var cards = Array.prototype.slice.call(document.querySelectorAll(".card"));
var countEl = document.getElementById("count");
var emptyEl = document.getElementById("empty");
var grid = document.getElementById("grid");
function animateCount(target) {
var start = parseInt(countEl.textContent, 10) || 0;
var diff = target - start;
var steps = Math.min(Math.abs(diff), 12);
if (steps === 0) {
countEl.textContent = String(target);
return;
}
var i = 0;
var timer = setInterval(function () {
i++;
countEl.textContent = String(Math.round(start + (diff * i) / steps));
if (i >= steps) {
clearInterval(timer);
countEl.textContent = String(target);
}
}, 30);
}
function applyFilter(filter) {
var visible = 0;
cards.forEach(function (card) {
var match = filter === "all" || card.getAttribute("data-goal") === filter;
card.classList.toggle("is-hidden", !match);
if (match) {
visible++;
// retrigger entrance animation
card.style.animation = "none";
// force reflow
void card.offsetWidth;
card.style.animation = "";
}
});
animateCount(visible);
emptyEl.hidden = visible !== 0;
grid.hidden = visible === 0;
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
/* ---------- CTA ---------- */
var book = document.getElementById("book");
if (book) {
book.addEventListener("click", function () {
toast("Nice — check your inbox to pick a time.");
});
}
// Initial count
animateCount(cards.length);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Coach — Transformations</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<header class="head">
<p class="eyebrow">Real clients · Real numbers</p>
<h1 class="title">Transformations</h1>
<p class="lede">
Drag the lime handle to reveal each after. Filter by the goal you want to crush.
</p>
<div class="toolbar">
<div class="filters" role="group" aria-label="Filter transformations by goal">
<button class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="chip" data-filter="fat-loss" aria-pressed="false">Fat Loss</button>
<button class="chip" data-filter="muscle" aria-pressed="false">Muscle Gain</button>
<button class="chip" data-filter="recomp" aria-pressed="false">Recomposition</button>
<button class="chip" data-filter="endurance" aria-pressed="false">Endurance</button>
</div>
<p class="count" aria-live="polite"><span id="count">6</span> stories</p>
</div>
</header>
<section class="grid" id="grid" aria-label="Transformation cases">
<!-- Card 1 -->
<article class="card" data-goal="fat-loss">
<div class="badge badge--fat-loss">Fat Loss</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Maya R."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--1"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--1"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>18</b><small>weeks</small></div>
<div class="stat"><b>−34</b><small>lbs</small></div>
<div class="stat"><b>−11%</b><small>body fat</small></div>
</div>
<blockquote class="quote">
“I stopped chasing crash diets. The plan finally fit my life.”
<cite>Maya R. · Nurse</cite>
</blockquote>
</article>
<!-- Card 2 -->
<article class="card" data-goal="muscle">
<div class="badge badge--muscle">Muscle Gain</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Deshawn T."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--2"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--2"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>24</b><small>weeks</small></div>
<div class="stat"><b>+21</b><small>lbs</small></div>
<div class="stat"><b>+40</b><small>lb bench</small></div>
</div>
<blockquote class="quote">
“First time I actually saw muscle instead of just soreness.”
<cite>Deshawn T. · Teacher</cite>
</blockquote>
</article>
<!-- Card 3 -->
<article class="card" data-goal="recomp">
<div class="badge badge--recomp">Recomposition</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Priya N."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--3"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--3"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>20</b><small>weeks</small></div>
<div class="stat"><b>−9</b><small>lbs</small></div>
<div class="stat"><b>+7</b><small>lb muscle</small></div>
</div>
<blockquote class="quote">
“Same scale weight, completely different body. Wild.”
<cite>Priya N. · Designer</cite>
</blockquote>
</article>
<!-- Card 4 -->
<article class="card" data-goal="endurance">
<div class="badge badge--endurance">Endurance</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Marco V."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--4"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--4"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>16</b><small>weeks</small></div>
<div class="stat"><b>−19</b><small>lbs</small></div>
<div class="stat"><b>−12min</b><small>10K time</small></div>
</div>
<blockquote class="quote">
“Went from winded on stairs to a sub-50 10K. Unreal.”
<cite>Marco V. · Developer</cite>
</blockquote>
</article>
<!-- Card 5 -->
<article class="card" data-goal="fat-loss">
<div class="badge badge--fat-loss">Fat Loss</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Alina K."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--5"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--5"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>30</b><small>weeks</small></div>
<div class="stat"><b>−52</b><small>lbs</small></div>
<div class="stat"><b>−14%</b><small>body fat</small></div>
</div>
<blockquote class="quote">
“Weekly check-ins kept me honest when motivation dipped.”
<cite>Alina K. · Analyst</cite>
</blockquote>
</article>
<!-- Card 6 -->
<article class="card" data-goal="muscle">
<div class="badge badge--muscle">Muscle Gain</div>
<div
class="ba"
role="slider"
tabindex="0"
aria-label="Before and after reveal for Theo B."
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
<div class="ba-img ba-after ba-after--6"><span class="ba-tag">After</span></div>
<div class="ba-img ba-before ba-before--6"><span class="ba-tag">Before</span></div>
<div class="ba-handle" aria-hidden="true"><span class="ba-grip"></span></div>
</div>
<div class="stats">
<div class="stat"><b>28</b><small>weeks</small></div>
<div class="stat"><b>+27</b><small>lbs</small></div>
<div class="stat"><b>+95</b><small>lb squat</small></div>
</div>
<blockquote class="quote">
“Skinny my whole life. Now I have to buy new shirts.”
<cite>Theo B. · Student</cite>
</blockquote>
</article>
</section>
<p class="empty" id="empty" hidden>No stories in that category yet — check back soon.</p>
<footer class="cta">
<div>
<h2 class="cta-title">Your turn is next</h2>
<p class="cta-sub">Free 20-minute strategy call. No pressure, just a plan.</p>
</div>
<button class="btn" id="book">Book a consult</button>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A social-proof gallery built to sell the outcome. Each card is an interactive before/after comparison: a rounded photo frame layered with two gradient “photos” and a draggable electric-lime handle. Grab the handle (pointer or keyboard) and slide it across to wipe from the before state into the after, with a live vertical seam and glowing grip so the reveal feels tactile. Above every image sits a goal badge, and below it a compact stat strip calls out the numbers that matter — weeks on program and total pounds changed — followed by a short client quote.
The header carries a bold uppercase display headline and a row of filter chips (All, Fat Loss, Muscle Gain, Recomposition, Endurance). Selecting a goal fades out the non-matching cards, reflows the grid, and animates the result counter so the coach can spotlight a single category on demand. Empty states are handled gracefully with a friendly message.
Everything is vanilla: pointer + keyboard handling for the sliders, an accessible slider role with aria values, filter logic driven by data attributes, and a lightweight toast helper that confirms actions like booking a consult. It is fully responsive down to ~360px, collapsing the grid to a single column and enlarging touch targets, with visible focus rings throughout for keyboard users.