ELF-457: add slot 3 rotating tagline above hero sub on hatch.surf
Some checks failed
CI / go (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / lint-go (push) Has been cancelled
CI / lint-docs (push) Has been cancelled
CI / check-paperclip (push) Has been cancelled
Deploy static site / Deploy to GitHub Pages (push) Has been cancelled
Deploy static site / Deploy via Docker to hatch.surf (push) Has been cancelled

Three taglines rotate every 3.5s with a subtle 700ms opacity crossfade
between the H1 and the hero sub on the hatch.surf landing page:
  1. Inspect any request. Mock any response. (default, in initial HTML)
  2. Your HTTP bin, on your machine.
  3. Send. Capture. Mock. No signup.

Implementation:
- site/index.html: <div class='hero-tagline' aria-live=polite> with three
  <p class='hero-tagline-item'> children stacked; default #1 has
  is-active class and no aria-hidden (for SEO + no-JS users)
- site/style.css: .hero-tagline uses position:relative with fixed
  height: 1.7em (no CLS during rotation); absolute children crossfade
  via transition: opacity 700ms var(--ease-out); font: JetBrains Mono
  weight 500 in var(--text-secondary); clamp(0.85rem, 2.2vw, 1rem)
  so mobile (375px+) fits the longest tagline on one line
- site/main.js: initRotatingTagline() in the existing IIFE; setInterval
  every 3500ms swaps is-active and aria-hidden between siblings;
  early-returns if prefers-reduced-motion is set, so the default
  tagline stays visible without animation
- Spacing: H1 mb-sp-6, tagline mb-sp-6, sub mb-sp-8 keeps the
  existing 24px/24px/32px rhythm unchanged below the headline

Note: site/index.html (not the Next.js app under app/ + components/)
is what hatch.surf/nginx actually serves. The Next.js work from
the earlier failed run was reverted.
This commit is contained in:
Senior Web Designer 2026-06-26 19:47:22 +02:00
parent bcb4f1688e
commit 478e2818b9
3 changed files with 70 additions and 0 deletions

View File

@ -67,6 +67,13 @@
<h1 data-animate="fade-up" data-delay="80"> <h1 data-animate="fade-up" data-delay="80">
Self-hostable HTTP request<br>inspector&nbsp;+&nbsp;mocker Self-hostable HTTP request<br>inspector&nbsp;+&nbsp;mocker
</h1> </h1>
<!-- Slot 3 rotating tagline (ELF-457, brief ELF-456). Default #1 is in the
initial HTML for no-JS / SEO. JS in main.js cycles opacity every 3.5s. -->
<div class="hero-tagline" aria-live="polite" aria-atomic="true">
<p class="hero-tagline-item is-active">Inspect any request. Mock any response.</p>
<p class="hero-tagline-item" aria-hidden="true">Your HTTP bin, on your machine.</p>
<p class="hero-tagline-item" aria-hidden="true">Send. Capture. Mock. No signup.</p>
</div>
<p class="hero-sub" data-animate="fade-up" data-delay="160"> <p class="hero-sub" data-animate="fade-up" data-delay="160">
One Go binary. SQLite under the hood.<br> One Go binary. SQLite under the hood.<br>
<code>docker compose up</code>, and you have an inspection endpoint and a live feed in under 30 seconds. <code>docker compose up</code>, and you have an inspection endpoint and a live feed in under 30 seconds.

View File

@ -420,6 +420,32 @@
initEntranceAnimations(animate, stagger); initEntranceAnimations(animate, stagger);
initTerminalGlow(animate); initTerminalGlow(animate);
initRotatingTagline();
}
// ============================================================
// Slot 3 rotating tagline (ELF-457)
// Cycles 3 tagline <p>s every 3.5s with a 700ms opacity crossfade
// (handled in CSS via .hero-tagline-item transition). Honors
// prefers-reduced-motion: skips the timer entirely so the default
// #1 stays visible without any animation.
// ============================================================
function initRotatingTagline() {
var container = document.querySelector('.hero-tagline');
if (!container) return;
var items = container.querySelectorAll('.hero-tagline-item');
if (items.length < 2) return;
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var idx = 0;
window.setInterval(function () {
items[idx].classList.remove('is-active');
items[idx].setAttribute('aria-hidden', 'true');
idx = (idx + 1) % items.length;
items[idx].classList.add('is-active');
items[idx].removeAttribute('aria-hidden');
}, 3500);
} }
// Defer boot by one frame so anime.js engine is ready // Defer boot by one frame so anime.js engine is ready

View File

@ -363,6 +363,43 @@ nav {
color: var(--text-primary); color: var(--text-primary);
} }
/* Slot 3: rotating tagline (ELF-457). Sits between H1 and hero-sub.
Fixed height prevents CLS during rotation; absolute children crossfade. */
.hero-tagline {
position: relative;
max-width: 560px;
height: 1.7em;
margin: 0 auto var(--sp-6);
overflow: hidden;
font-family: var(--font-mono);
font-size: clamp(0.85rem, 2.2vw, 1rem);
font-weight: 500;
line-height: 1.7;
color: var(--text-secondary);
text-align: center;
}
.hero-tagline-item {
position: absolute;
inset: 0;
margin: 0;
opacity: 0;
transition: opacity 700ms var(--ease-out);
display: flex;
align-items: center;
justify-content: center;
}
.hero-tagline-item.is-active {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.hero-tagline-item {
transition: none;
}
}
.hero-sub { .hero-sub {
font-size: clamp(1rem, 2vw, 1.2rem); font-size: clamp(1rem, 2vw, 1.2rem);
color: var(--text-secondary); color: var(--text-secondary);