Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/site/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -595,3 +595,57 @@ code { font-size: 0.875em; }
.js .gates > li.is-on .kill { animation: none; }
}


.loop-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
z-index: 2;
}

.loop-track {
fill: none;
stroke: var(--line);
stroke-width: 1;
}

.loop-run {
fill: none;
stroke: var(--ink);
stroke-width: 1.75;
stroke-linecap: square;
stroke-dashoffset: 0;
}

.js .gates.is-loop .loop-run {
animation: loop-travel 1.7s linear 3 forwards;
}

@keyframes loop-travel {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: calc(-1 * var(--len, 800)); }
}

.js .gates.is-loop .loop-a::after,
.js .gates.is-loop .loop-m::after,
.js .gates.is-loop .loop-z::after {
display: none;
}

.js .gates.is-loop-done .loop-run {
stroke-dasharray: none;
animation: none;
stroke: var(--ink);
opacity: 0.35;
}

@media (prefers-reduced-motion: reduce) {
.js .gates.is-loop .loop-run {
animation: none;
stroke-dasharray: none;
opacity: 0.7;
}
}
99 changes: 90 additions & 9 deletions docs/site/js/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copy for install. Scroll draws the protocol. No typing. No replay. */
/* Copy for install. Scroll draws the protocol. The 5→2 path runs as a loop, cap 3. */
(function () {
"use strict";

Expand Down Expand Up @@ -51,6 +51,13 @@
var gates = document.querySelector(".gates");
var steps = gates ? Array.prototype.slice.call(gates.querySelectorAll(":scope > li")) : [];
var blocks = Array.prototype.slice.call(document.querySelectorAll(".block"));
var ink = null;
var svg = null;
var runPath = null;
var trackPath = null;
var loopStarted = false;
var reenter = gates ? gates.querySelector(".reenter") : null;
var reenterHome = reenter ? reenter.innerHTML : "";

function paintRail() {
if (!gates || !ink) return;
Expand All @@ -66,29 +73,100 @@
ink.style.height = Math.max(0, end - top) + "px";
}

var ink = null;
function loopPathD() {
var a = gates.querySelector(".loop-a .n");
var z = gates.querySelector(".loop-z .n");
if (!a || !z) return "";
var gr = gates.getBoundingClientRect();
var ar = a.getBoundingClientRect();
var zr = z.getBoundingClientRect();
var x1 = ar.left - gr.left + ar.width / 2;
var y1 = ar.top - gr.top + ar.height / 2;
var y2 = zr.top - gr.top + zr.height / 2;
var x2 = Math.max(x1 + 40, gates.clientWidth - 24);
return "M " + x1 + " " + y1 + " L " + x1 + " " + y2 + " L " + x2 + " " + y2 + " L " + x2 + " " + y1 + " Z";
}

function layoutLoop() {
if (!gates || !runPath || !trackPath) return;
var d = loopPathD();
if (!d) return;
trackPath.setAttribute("d", d);
runPath.setAttribute("d", d);
var len = 0;
try { len = runPath.getTotalLength(); } catch (e) { len = 800; }
runPath.style.setProperty("--len", String(len));
runPath.style.strokeDasharray = "22 " + len;
}

function ensureLoopSvg() {
if (!gates || svg) return;
svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("class", "loop-svg");
svg.setAttribute("aria-hidden", "true");
trackPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
trackPath.setAttribute("class", "loop-track");
runPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
runPath.setAttribute("class", "loop-run");
svg.appendChild(trackPath);
svg.appendChild(runPath);
gates.appendChild(svg);
}

function setLap(n, done) {
if (!reenter) return;
if (done) {
reenter.innerHTML = '<span class="arrow">loop</span> 3 / 3 · then 6';
return;
}
reenter.innerHTML = '<span class="arrow">loop</span> ' + n + " / 3 · 5 → 2";
}

function startLoop() {
if (!gates || loopStarted || reduce) return;
loopStarted = true;
ensureLoopSvg();
layoutLoop();
gates.classList.add("is-loop");
setLap(1, false);
if (!runPath) return;
var lap = 1;
runPath.addEventListener("animationiteration", function () {
lap += 1;
if (lap <= 3) setLap(lap, false);
});
runPath.addEventListener("animationend", function () {
setLap(3, true);
gates.classList.add("is-loop-done");
});
}

if (gates) {
ink = document.createElement("span");
ink.className = "rail-ink";
ink.setAttribute("aria-hidden", "true");
gates.appendChild(ink);
}

function onStep(li, vis) {
if (vis) li.classList.add("is-on");
function onStep() {
if (gates) {
var loopOn = steps.some(function (s) {
return s.classList.contains("loop-z") && s.classList.contains("is-on");
});
gates.classList.toggle("is-loop", loopOn);
if (loopOn) startLoop();
}
paintRail();
}

if (reduce) {
steps.forEach(function (li) { li.classList.add("is-on"); });
blocks.forEach(function (b) { b.classList.add("is-on"); });
if (gates) gates.classList.add("is-loop");
if (gates) {
ensureLoopSvg();
layoutLoop();
gates.classList.add("is-loop");
gates.classList.add("is-loop-done");
}
paintRail();
return;
}
Expand All @@ -98,16 +176,19 @@
entries.forEach(function (e) {
if (!e.isIntersecting) return;
e.target.classList.add("is-on");
if (e.target.parentElement === gates) onStep(e.target, true);
if (e.target.parentElement === gates) onStep();
});
}, { threshold: 0.28, rootMargin: "0px 0px -12% 0px" });
steps.forEach(function (li) { io.observe(li); });
blocks.forEach(function (b) { io.observe(b); });
} else {
steps.forEach(function (li) { li.classList.add("is-on"); });
blocks.forEach(function (b) { b.classList.add("is-on"); });
if (gates) gates.classList.add("is-loop");
startLoop();
}

window.addEventListener("resize", paintRail);
window.addEventListener("resize", function () {
paintRail();
layoutLoop();
});
})();
Loading