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>