feat(hero): rotate three positioning lines on a 10s cycle
All three lines render in the DOM; CSS drives the fade via data-active. Reduced-motion users see the first line only (no interval, no fade). Rotation pauses on hover and keyboard focus. aria-live=off on the rotator so AT does not announce every swap; aria-hidden flips per-swap to avoid duplicate heading announcements. Also set vite.build.assetsInlineLimit=0 in astro.config.mjs so Astro emits the rotator script as a same-origin external file (dist/assets/) rather than inlining it — required for CSP script-src 'self' compliance.
This commit is contained in:
@@ -20,8 +20,14 @@ import TopographicBg from '../TopographicBg.astro';
|
||||
Your camels called. They want a GPS.
|
||||
</p>
|
||||
</div>
|
||||
<h1 class="text-display font-bold text-text mb-6">
|
||||
Run Apache Camel without running Apache Camel.
|
||||
<h1
|
||||
class="text-display font-bold text-text mb-6 hero-rotator"
|
||||
aria-live="off"
|
||||
data-hero-rotator
|
||||
>
|
||||
<span class="hero-line" data-active aria-hidden="false">Run Apache Camel without running Apache Camel.</span>
|
||||
<span class="hero-line" aria-hidden="true">Camel integrations, minus the baggage.</span>
|
||||
<span class="hero-line" aria-hidden="true">Your camels, our caravan. You just ride.</span>
|
||||
</h1>
|
||||
<p class="text-lg md:text-xl text-text-muted max-w-prose leading-relaxed mb-10">
|
||||
The hosted home for your Camel integrations — with deep tracing, replay, and live control built in. Because you chose Camel to stay free, not to stay up all night.
|
||||
@@ -33,3 +39,57 @@ import TopographicBg from '../TopographicBg.astro';
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero-rotator {
|
||||
position: relative;
|
||||
display: block;
|
||||
/* Reserve height for the tallest line so no layout shift on swap.
|
||||
Two lines at current H1 size handles all three on most viewports. */
|
||||
min-height: 2.2em;
|
||||
}
|
||||
.hero-line {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transition: opacity 700ms ease-in-out;
|
||||
/* Stack all lines on top of each other — only [data-active] is visible. */
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hero-line[data-active] {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-line {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Hero rotating headline. Bundled by Astro (CSP: script-src 'self').
|
||||
const rotator = document.querySelector<HTMLElement>('[data-hero-rotator]');
|
||||
if (rotator) {
|
||||
const lines = Array.from(rotator.querySelectorAll<HTMLElement>('.hero-line'));
|
||||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (!reduced && lines.length > 1) {
|
||||
let index = 0;
|
||||
let paused = false;
|
||||
const pause = () => { paused = true; };
|
||||
const resume = () => { paused = false; };
|
||||
rotator.addEventListener('mouseenter', pause);
|
||||
rotator.addEventListener('mouseleave', resume);
|
||||
rotator.addEventListener('focusin', pause);
|
||||
rotator.addEventListener('focusout', resume);
|
||||
setInterval(() => {
|
||||
if (paused) return;
|
||||
lines[index].removeAttribute('data-active');
|
||||
lines[index].setAttribute('aria-hidden', 'true');
|
||||
index = (index + 1) % lines.length;
|
||||
lines[index].setAttribute('data-active', '');
|
||||
lines[index].setAttribute('aria-hidden', 'false');
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user