replace scroll-driven SVG animations with click-to-play

scroll-driven view-timeline approach didn't work reliably.
SVGs now render fully visible with a play button overlay.
click triggers sequential fadeIn animation, button returns
when animation completes.
This commit is contained in:
russell@unturf.com 2026-03-07 20:06:07 -05:00
parent 70eae5f039
commit 2bbbdead17
9 changed files with 95 additions and 213 deletions

View file

@ -7,6 +7,101 @@ royal we & our & one & you
:tags: Opinion, Code
:status: published
.. raw:: html
<style>
@keyframes svgFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.svg-play-wrap {
position: relative;
display: block;
width: 100%;
}
.svg-play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 64px;
height: 64px;
border-radius: 50%;
background: rgba(0,0,0,0.55);
color: white;
font-size: 28px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: background 0.2s, opacity 0.3s;
border: 2px solid rgba(255,255,255,0.3);
}
.svg-play-btn:hover {
background: rgba(0,0,0,0.8);
border-color: rgba(255,255,255,0.5);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
var svgs = document.querySelectorAll('svg');
svgs.forEach(function(svg) {
var anims = svg.querySelectorAll('.anim');
if (anims.length === 0) return;
var maxD = 0;
anims.forEach(function(el) {
el.classList.forEach(function(cls) {
var m = cls.match(/^d(\d+)$/);
if (m) maxD = Math.max(maxD, parseInt(m[1]));
});
});
var wrap = document.createElement('div');
wrap.className = 'svg-play-wrap';
svg.parentNode.insertBefore(wrap, svg);
wrap.appendChild(svg);
var btn = document.createElement('div');
btn.className = 'svg-play-btn';
btn.innerHTML = '&#9654;';
wrap.appendChild(btn);
var step = maxD > 0 ? 15 / (maxD + 1) : 2;
var playing = false;
btn.addEventListener('click', function() {
if (playing) return;
playing = true;
btn.style.opacity = '0';
btn.style.pointerEvents = 'none';
anims.forEach(function(el) {
el.style.opacity = '0';
el.style.animation = 'none';
});
requestAnimationFrame(function() {
requestAnimationFrame(function() {
anims.forEach(function(el) {
var d = 0;
el.classList.forEach(function(cls) {
var m = cls.match(/^d(\d+)$/);
if (m) d = parseInt(m[1]);
});
el.style.animation = 'svgFadeIn 0.8s ease-out ' + (d * step) + 's forwards';
});
});
});
var total = ((maxD * step) + 1.5) * 1000;
setTimeout(function() {
anims.forEach(function(el) {
el.style.animation = '';
el.style.opacity = '1';
});
btn.style.opacity = '1';
btn.style.pointerEvents = '';
playing = false;
}, total);
});
});
});
</script>
Every README starts with a lie.
"We recommend installing with pip." Who recommends? A solo developer at 2am eating cold pizza wrote that README. No committee convened. No quorum reached. Just one person & a terminal & ``pip install``. But "we recommend" sounds institutional. Sounds authoritative. Sounds like a team of engineers in matching hoodies reviewed your options & reached consensus.

View file

@ -6,48 +6,6 @@
<!-- Title: contributor_decay Pages: 1 -->
<svg width="100%" height="100%"
viewBox="0.00 0.00 211.00 671.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 5%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 3% entry 8%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 7% entry 12%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 10% entry 15%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 14% entry 19%; }
.d5 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 17% entry 22%; }
.d6 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 21% entry 26%; }
.d7 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 25% entry 30%; }
.d8 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 28% entry 33%; }
.d9 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 32% entry 37%; }
.d10 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 35% entry 40%; }
.d11 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 39% entry 44%; }
.d12 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 42% entry 47%; }
.d13 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 46% entry 51%; }
.d14 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 50% entry 55%; }
.d15 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 53% entry 58%; }
.d16 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 57% entry 62%; }
.d17 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 60% entry 65%; }
.d18 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 64% entry 69%; }
.d19 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 67% entry 72%; }
.d20 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 71% entry 76%; }
.d21 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 75% entry 80%; }
.d22 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 78% entry 83%; }
.d23 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 82% entry 87%; }
.d24 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 85% entry 90%; }
.d25 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 89% entry 94%; }
.d26 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 92% entry 97%; }
.d27 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 96% entry 100%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 667)">
<g class="anim d0">
<title>contributor_decay</title>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

View file

@ -3,35 +3,6 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%"
viewBox="0.00 0.00 424.00 1563.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 6%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 6% entry 12%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 12% entry 18%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 18% entry 24%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 25% entry 31%; }
.d5 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 31% entry 37%; }
.d6 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 37% entry 43%; }
.d7 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 43% entry 49%; }
.d8 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 50% entry 56%; }
.d9 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 56% entry 62%; }
.d10 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 62% entry 68%; }
.d11 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 68% entry 74%; }
.d12 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 75% entry 81%; }
.d13 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 81% entry 87%; }
.d14 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 87% entry 93%; }
.d15 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 93% entry 99%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1559)">
<!-- d0: cluster box + separation label + 1 Lucifer -->

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -3,28 +3,6 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%"
viewBox="0.00 0.00 530.00 439.24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 11%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 11% entry 22%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 22% entry 33%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 33% entry 44%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 44% entry 55%; }
.d5 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 55% entry 66%; }
.d6 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 66% entry 77%; }
.d7 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 77% entry 88%; }
.d8 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 88% entry 99%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 435.24)">
<!-- d0: cluster box -->

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before After
Before After

View file

@ -6,49 +6,6 @@
<!-- Title: mystic_pronouns Pages: 1 -->
<svg width="100%" height="100%"
viewBox="0.00 0.00 493.00 482.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 5%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 3% entry 8%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 6% entry 11%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 10% entry 15%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 13% entry 18%; }
.d5 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 17% entry 22%; }
.d6 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 20% entry 25%; }
.d7 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 24% entry 29%; }
.d8 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 27% entry 32%; }
.d9 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 31% entry 36%; }
.d10 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 34% entry 39%; }
.d11 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 37% entry 42%; }
.d12 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 41% entry 46%; }
.d13 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 44% entry 49%; }
.d14 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 48% entry 53%; }
.d15 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 51% entry 56%; }
.d16 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 55% entry 60%; }
.d17 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 58% entry 63%; }
.d18 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 62% entry 67%; }
.d19 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 65% entry 70%; }
.d20 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 68% entry 73%; }
.d21 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 72% entry 77%; }
.d22 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 75% entry 80%; }
.d23 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 79% entry 84%; }
.d24 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 82% entry 87%; }
.d25 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 86% entry 91%; }
.d26 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 89% entry 94%; }
.d27 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 93% entry 98%; }
.d28 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 96% entry 100%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 478)">
<g class="anim d0">
<title>mystic_pronouns</title>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

View file

@ -6,24 +6,6 @@
<!-- Title: naming_dance Pages: 1 -->
<svg width="100%" height="100%"
viewBox="0.00 0.00 166.50 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 20%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 20% entry 40%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 40% entry 60%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 60% entry 80%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 80% entry 100%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 400)">
<!-- d0: Mrs. Lastname node -->

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

View file

@ -3,30 +3,6 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%"
viewBox="0.00 0.00 400.50 705.82" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 9%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 9% entry 18%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 18% entry 27%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 27% entry 36%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 36% entry 45%; }
.d5 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 45% entry 54%; }
.d6 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 54% entry 63%; }
.d7 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 63% entry 72%; }
.d8 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 72% entry 81%; }
.d9 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 81% entry 90%; }
.d10 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 90% entry 99%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 701.82)">
<!-- cluster: pretends to contain -->

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

@ -3,23 +3,6 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%"
viewBox="0.00 0.00 393.00 266.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 25%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 25% entry 50%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 50% entry 75%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 75% entry 100%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 262)">
<!-- clusters (always visible) -->

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Before After
Before After

View file

@ -6,24 +6,6 @@
<!-- Title: royal_we Pages: 1 -->
<svg width="100%" height="100%"
viewBox="0.00 0.00 287.00 368.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
svg { view-timeline: --diagram block; }
.anim { opacity: 0; }
.d0 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 0% entry 20%; }
.d1 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 20% entry 40%; }
.d2 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 40% entry 60%; }
.d3 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 60% entry 80%; }
.d4 { animation: fadeIn 1s linear both; animation-timeline: --diagram; animation-range: entry 80% entry 100%; }
@supports not (animation-timeline: view()) {
.anim { opacity: 1; }
}
</style>
</defs>
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 364)">
<!-- d0: readme node -->

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Before After
Before After