TV that delivers
<html><head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Montserrat:wght@400;500;600&display=swap');
:root {
--font-ui: "Montserrat", sans-serif;
--font-display: "Poppins", sans-serif; /* fix: was referenced but never defined */
--neutral-50: #EFF6FF;
--neutral-100: #F1F1F1;
--neutral-200: #E0E0E0;
--neutral-300: #C4C4C4;
--primary-main: #D42B3A;
--text-primary: #212121;
--text-secondary: #757575;
--red-50: #FFF2F2;
--blue-50: #EEF3FB;
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font-ui);
color: var(--text-primary);
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
min-height: 80vh;
}
.carousel {
width: 100%;
max-width: 100%;
display: flex;
flex-direction: column;
}
/* ── media ── */
.media {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 10px;
overflow: hidden;
background: var(--neutral-100);
box-shadow: inset 0 0 0 1px var(--neutral-200);
}
.media .slot {
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 240ms cubic-bezier(.4, 0, .2, 1);
background: linear-gradient(135deg, var(--red-50), var(--blue-50));
}
.media .slot::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,.4) 100%);
pointer-events: none;
z-index: 1;
}
.media .slot.on { opacity: 1; }
.media img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.media .caption {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 10px;
color: #fff;
z-index: 2;
background: linear-gradient(
to top,
rgba(0,0,0,.72) 0%,
rgba(0,0,0,.4) 55%,
rgba(0,0,0,0) 100%
);
}
.media .caption .cat {
display: block;
font-family: var(--font-display); /* fix: now resolves to Poppins */
font-size: 11px; /* fix: was 6px — far too small */
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: rgba(255,255,255,.95);
margin-bottom: 6px;
}
.media .caption .ttl {
display: block;
font-family: var(--font-ui);
font-weight: 500;
font-size: 16px;
line-height: 1.35;
color: #fff;
text-wrap: pretty;
margin: 0;
}
/* ── grid ── */
.grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
padding-top: 8px;
position: relative;
font-size: 14px;
}
/* ── rail — fix: was var(--grid-left), should be var(--rail-top) ── */
.rail {
position: absolute;
left: 0;
right: 0;
top: var(--rail-top); /* fix: matched to JS setProperty name */
height: px;
background: var(--neutral-200);
border-radius: 999px;
pointer-events: none;
}
.rail-thumb {
position: absolute;
top: 0;
bottom: 0;
background: var(--primary-main);
border-radius: 999px;
transition:
left 240ms cubic-bezier(.4, 0, .2, 1),
width 240ms cubic-bezier(.4, 0, .2, 1);
}
/* ── step ── */
.step {
position: relative;
background: transparent;
border: 1px solid #AEC8EC;
border-radius: 8px;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-align: center;
font: inherit;
color: inherit;
min-height: 30px;
transition: all 180ms cubic-bezier(.4, 0, .2, 1);
overflow: hidden;
}
.step:hover {
border-color: var(--neutral-300);
background: var(--neutral-50);
}
/* fix: HTML uses .lbl, not .cat — renamed selector to match */
.step .lbl {
font-size: 10px;
font-weight: 100;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
display: block;
}
.step.is-on {
background: #fff;
border-color: var(--primary-main);
box-shadow: 0 0 0 1px var(--primary-main), 0 6px 18px rgba(212,43,58,.08);
}
.step.is-on .lbl { color: var(--primary-main); } /* fix: .cat → .lbl */
/* ── narrow / dot mode ── */
@media (max-width: 350px) {
.grid { grid-template-columns: repeat(5, 1fr); gap: 10px; }
.step .lbl { display: none; } /* fix: .cat → .lbl */
.step {
min-height: 10px;
padding: 0;
border-radius: 20%;
aspect-ratio: 1.7;
}
.step::after {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%; /* fix: was 0% — dots should be round */
background: var(--neutral-300);
}
.step.is-on::after { background: var(--primary-main); }
.step.is-on { box-shadow: 0 0 0 1px var(--primary-main); }
.rail { display: none; }
}
@media (min-width: 350px) {
.step::after { content: none; }
}
</style>
</head>
<body>
<div class="carousel">
<div class="media" id="media">
<div class="slot" data-k="0">
<img alt="Awareness" src="https://3426102.fs1.hubspotusercontent-na1.net/hubfs/3426102/mediasmart-tv/home-images/awareness.webp" onerror="this.style.display='none'">
<div class="caption"><span class="ttl">Reach premium TV channels with precision.</span></div>
</div>
<div class="slot" data-k="1">
<img alt="Mobile sync" src="https://3426102.fs1.hubspotusercontent-na1.net/hubfs/3426102/mediasmart-tv/home-images/sync.webp" onerror="this.style.display='none'">
<div class="caption"><span class="ttl">Extend ads to phones in the same household.</span></div>
</div>
<div class="slot" data-k="2">
<img alt="App growth" src="https://3426102.fs1.hubspotusercontent-na1.net/hubfs/3426102/mediasmart-tv/home-images/growth.webp" onerror="this.style.display='none'">
<div class="caption"><span class="ttl">TV sparks action, install on phone.</span></div>
</div>
<div class="slot on" data-k="3">
<img alt="Online purchases" src="https://3426102.fs1.hubspotusercontent-na1.net/hubfs/3426102/mediasmart-tv/home-images/purchases.webp" onerror="this.style.display='none'">
<div class="caption"><span class="ttl">Turn TV views into measurable website sales.</span></div>
</div>
<div class="slot" data-k="4">
<img alt="Offline visits" src="https://3426102.fs1.hubspotusercontent-na1.net/hubfs/3426102/mediasmart-tv/home-images/visits.webp" onerror="this.style.display='none'">
<div class="caption"><span class="ttl">Link TV ads to real-world store visits.</span></div>
</div>
</div>
<div class="grid" id="grid" style="--rail-top: 21px;">
<div class="rail"><div class="rail-thumb" id="railThumb" style="left: 580.398px; width: 190.797px;"></div></div>
<button class="step" data-i="0"><span class="lbl">Awareness</span></button>
<button class="step" data-i="1"><span class="lbl">Sync</span></button>
<button class="step" data-i="2"><span class="lbl">Growth</span></button>
<button class="step is-on" data-i="3"><span class="lbl">Purchases</span></button>
<button class="step" data-i="4"><span class="lbl">Visits</span></button>
</div>
</div>
<script>
const slots = document.querySelectorAll("#media .slot");
const thumb = document.getElementById("railThumb");
const grid = document.getElementById("grid");
function moveThumb(btn) {
const gRect = grid.getBoundingClientRect();
const bRect = btn.getBoundingClientRect();
const railTop = (bRect.top - gRect.top) + bRect.height / 2 - 2;
grid.style.setProperty("--rail-top", railTop + "px"); // matches CSS var(--rail-top)
thumb.style.left = (bRect.left - gRect.left) + "px";
thumb.style.width = bRect.width + "px";
}
function focusStep(btn) {
document.querySelectorAll(".step").forEach(s => s.classList.remove("is-on"));
btn.classList.add("is-on");
const k = btn.dataset.i;
slots.forEach(s => s.classList.toggle("on", s.dataset.k === k));
moveThumb(btn);
}
document.querySelectorAll(".step").forEach(btn => {
btn.addEventListener("mouseenter", () => { pauseAuto(); focusStep(btn); });
btn.addEventListener("mouseleave", resumeAuto);
btn.addEventListener("focus", () => focusStep(btn));
btn.addEventListener("click", () => focusStep(btn));
});
document.getElementById("media").addEventListener("mouseenter", pauseAuto);
document.getElementById("media").addEventListener("mouseleave", resumeAuto);
let autoTimer = null;
let autoPaused = false;
function tickAuto() {
const steps = document.querySelectorAll(".step");
const cur = document.querySelector(".step.is-on");
focusStep(steps[([...steps].indexOf(cur) + 1) % steps.length]);
}
function startAuto() {
if (!autoTimer) autoTimer = setInterval(tickAuto, 2000); // fix: guard moved to pauseAuto/resumeAuto
}
function pauseAuto() {
autoPaused = true;
clearInterval(autoTimer); // fix: actually stop ticking while paused
autoTimer = null;
}
function resumeAuto() {
autoPaused = false;
startAuto(); // fix: restart cleanly on resume
}
startAuto();
requestAnimationFrame(() => moveThumb(document.querySelector(".step.is-on")));
window.addEventListener("resize", () => moveThumb(document.querySelector(".step.is-on")));
</script>
</body></html>