Drive scroll reveals via Web Animations API — immune to OS reduced-motion suppression
Changes
@@@ -81,16 +81,25 @@ const pageUrl = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();<Footer /><script>-- // Reveal-on-scroll++ // Reveal-on-scroll — Web Animations API so OS reduced-motion can't suppress itconst io = new IntersectionObserver((entries) => {for (const e of entries) {if (e.isIntersecting) {-- e.target.classList.add("in");-- io.unobserve(e.target);++ const el = e.target as HTMLElement;++ el.style.opacity = '1';++ el.style.transform = 'none';++ el.animate(++ [++ { opacity: 0, transform: 'translateY(24px)' },++ { opacity: 1, transform: 'translateY(0)' }++ ],++ { duration: 600, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'forwards' }++ );++ io.unobserve(el);}}-- }, { rootMargin: "0px 0px -10% 0px", threshold: 0.05 });-- document.querySelectorAll("[data-reveal]").forEach((el) => io.observe(el));++ }, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 });++ document.querySelectorAll('[data-reveal]').forEach((el) => io.observe(el));// Theme toggleconst KEY = "afnet-theme";@@@ -150,11 +150,9 @@ section { padding: clamp(60px, 10vw, 120px) 0; position: relative; }}.skip-link:focus { left: 12px; }--/* Reveal */++/* Reveal — initial hidden state only; animation driven by JS (Web Animations API) */[data-reveal] {opacity: 0;transform: translateY(24px);-- transition: opacity .8s var(--ease), transform .8s var(--ease);-- will-change: opacity, transform;}[data-reveal].in { opacity: 1; transform: none; }
@@ -81,16 +81,25 @@ const pageUrl = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();
@@ -81,16 +81,25 @@ const pageUrl = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();
<Footer />
<Footer />
<script>
<script>
- // Reveal-on-scroll
+ // Reveal-on-scroll — Web Animations API so OS reduced-motion can't suppress it
const io = new IntersectionObserver((entries) => {
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
for (const e of entries) {
if (e.isIntersecting) {
if (e.isIntersecting) {
- e.target.classList.add("in");
- io.unobserve(e.target);
+ const el = e.target as HTMLElement;
+ el.style.opacity = '1';
+ el.style.transform = 'none';
+ el.animate(
+ [
+ { opacity: 0, transform: 'translateY(24px)' },
+ { opacity: 1, transform: 'translateY(0)' }
+ ],
+ { duration: 600, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'forwards' }
+ );
+ io.unobserve(el);
}
}
}
}
- }, { rootMargin: "0px 0px -10% 0px", threshold: 0.05 });
- document.querySelectorAll("[data-reveal]").forEach((el) => io.observe(el));
+ }, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 });
+ document.querySelectorAll('[data-reveal]').forEach((el) => io.observe(el));
// Theme toggle
// Theme toggle
const KEY = "afnet-theme";
const KEY = "afnet-theme";
@@ -150,11 +150,9 @@ section { padding: clamp(60px, 10vw, 120px) 0; position: relative; }
@@ -150,11 +150,9 @@ section { padding: clamp(60px, 10vw, 120px) 0; position: relative; }
}
}
.skip-link:focus { left: 12px; }
.skip-link:focus { left: 12px; }
-/* Reveal */
+/* Reveal — initial hidden state only; animation driven by JS (Web Animations API) */
[data-reveal] {
[data-reveal] {
opacity: 0;
opacity: 0;
transform: translateY(24px);
transform: translateY(24px);
- transition: opacity .8s var(--ease), transform .8s var(--ease);
- will-change: opacity, transform;
}
}
[data-reveal].in { opacity: 1; transform: none; }
[data-reveal].in { opacity: 1; transform: none; }