Hover Service Index
A three-column services index: a monospace label on the left, a column of large bold service names in the middle (light gray until hovered or focused, then black), and a tall image on the right that crossfades and scales slightly to match. With no interaction it rotates through the list.
Taken from the page template Houtu Creative Studio Home. Use it on its own, or combine it with the other components from the same template to rebuild the full page.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Hover Service Index to your project in your colors.
Where to use it
Animated headlines, testimonials, emphasis copy and large-type layouts.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
Code
Source for Hover Service Index. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-hsl"><p class="x-hsl__lbl">服务</p><div><ul class="x-hsl__list"><li><button type="button" class="is-on">品牌</button></li><li><button type="button">数字</button></li><li><button type="button">内容</button></li><li><button type="button">动态</button></li><li><button type="button">印刷</button></li></ul><a class="x-hsl__more" href="#">查看更多<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 12h15"/><path d="m13.5 6 6 6-6 6"/></svg></a></div><div class="x-hsl__frame"><img class="is-on" src="https://8pt.dev/api/media/0323c8430eefc3479f288435.webp" alt="彩色粗针织物"><img src="https://8pt.dev/api/media/753a33bd99dc9499d42f843e.webp" alt="版式海报与色卡"><img src="https://8pt.dev/api/media/ee76c50d97626dae74b76707.webp" alt="工作间里整理礼服的匠人"><img src="https://8pt.dev/api/media/5ecdf074ff94f69a69c7b033.webp" alt="漂浮的镜面缎带"><img src="https://8pt.dev/api/media/464d4d88e7c42a8bfe0d0409.webp" alt="黄色包装的能量棒"></div></div>
@import url("https://fonts.googleapis.com/css2?family=Fragment+Mono&family=Inter+Tight:wght@500;600;700&family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;600&family=ZCOOL+QingKe+HuangYou&display=swap");.x-hsl{display:grid;grid-template-columns:22% 1fr 420px;width:100%;max-width:1160px;box-sizing:border-box;padding:40px 28px;background:#FFFFFF;color:#000;font-family:"Inter Tight","Noto Sans SC",system-ui,sans-serif}.x-hsl__lbl{margin:0;font-family:"Fragment Mono","Noto Sans SC",ui-monospace,monospace;font-size:12px;letter-spacing:.06em}.x-hsl__list{list-style:none;margin:0;padding:0}.x-hsl__list button{display:block;padding:0;border:0;background:none;font-family:inherit;font-size:66px;font-weight:700;line-height:1.04;letter-spacing:-.02em;color:#D6D5D3;cursor:pointer;transition:color .3s}.x-hsl__list button.is-on{color:#2B2A27}.x-hsl__more{display:inline-flex;align-items:center;gap:6px;margin-top:48px;height:28px;padding:0 14px;border-radius:6px;background:#F2F2EF;color:#000;font-size:13px;font-weight:500;text-decoration:none}.x-hsl__more svg{width:13px;height:13px}.x-hsl__frame{position:relative;width:420px;height:540px;border-radius:6px;overflow:hidden;background:#F2F2EF}.x-hsl__frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transform:scale(1.06);transition:opacity .6s,transform 1.2s cubic-bezier(.2,.7,.2,1)}.x-hsl__frame img.is-on{opacity:1;transform:none}@media (prefers-reduced-motion:reduce){.x-hsl__frame img,.x-hsl__list button{transition:none}}(root) => {
const btns = [...root.querySelectorAll(".x-hsl__list button")];
const imgs = [...root.querySelectorAll(".x-hsl__frame img")];
let cur = 0, timer = null;
const pick = (i) => { cur = i; btns.forEach((b, k) => b.classList.toggle("is-on", k === i)); imgs.forEach((m, k) => m.classList.toggle("is-on", k === i)); };
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const start = () => { if (reduce || timer) return; timer = setInterval(() => pick((cur + 1) % btns.length), 2600); };
const stop = () => { clearInterval(timer); timer = null; };
const hs = btns.map((b, i) => { const h = () => { stop(); pick(i); }; b.addEventListener("mouseenter", h); b.addEventListener("focus", h); b.addEventListener("click", h); return h; });
root.addEventListener("mouseleave", start);
start();
return () => { stop(); root.removeEventListener("mouseleave", start); btns.forEach((b, i) => { b.removeEventListener("mouseenter", hs[i]); b.removeEventListener("focus", hs[i]); b.removeEventListener("click", hs[i]); }); };
…