diff --git a/docs/site/css/style.css b/docs/site/css/style.css index 8e22083..453c8eb 100644 --- a/docs/site/css/style.css +++ b/docs/site/css/style.css @@ -481,6 +481,117 @@ code { font-size: 0.875em; } } } + + +/* Scroll: the protocol draws itself. No scroll-jack. */ +.rail-ink { + position: absolute; + left: 15px; + top: 10px; + width: 1px; + height: 0; + background: var(--ink); + z-index: 0; + pointer-events: none; +} + +.js .gates > li { + opacity: 0.22; + transform: translateY(14px); + transition: opacity 0.55s ease, transform 0.55s ease; +} + +.js .gates > li.is-on { + opacity: 1; + transform: none; +} + +.js .n { + transition: background 0.35s ease, color 0.35s ease, border-color 0.35s ease; +} + +.js .gates > li.is-on .n:not(.n-src) { + background: var(--ink); + color: var(--bg); + border-color: var(--ink); +} + +.js .gates > li.is-on .n-src { + background: var(--ink); +} + +.js .loop-a::after, +.js .loop-m::after, +.js .loop-z::after { + opacity: 0; + transform: scaleY(0.08); + transform-origin: top; + transition: opacity 0.5s ease, transform 0.7s ease; +} + +.js .gates.is-loop .loop-a::after, +.js .gates.is-loop .loop-m::after, +.js .gates.is-loop .loop-z::after { + opacity: 1; + transform: none; +} + +.js .reenter { + opacity: 0; + transform: translateX(8px); + transition: opacity 0.45s ease 0.15s, transform 0.45s ease 0.15s; +} + +.js .gates.is-loop .reenter { + opacity: 1; + transform: none; +} + +.js .gates > li.is-on .kill { + animation: kill-flash 0.8s ease 1; +} + +@keyframes kill-flash { + 0% { color: var(--ink); } + 40% { color: var(--kill); } + 100% { color: var(--kill); } +} + +.js .block { + opacity: 0.28; + transform: translateY(18px); + transition: opacity 0.65s ease, transform 0.65s ease; +} + +.js .block.is-on { + opacity: 1; + transform: none; +} + +.js .ident { + opacity: 0; + transform: translateY(10px); + animation: ident-in 0.7s ease 0.08s forwards; +} + +@keyframes ident-in { + to { opacity: 1; transform: none; } +} + @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } + .js .gates > li, + .js .block, + .js .ident, + .js .reenter, + .js .loop-a::after, + .js .loop-m::after, + .js .loop-z::after { + opacity: 1; + transform: none; + animation: none; + transition: none; + } + .js .gates > li.is-on .kill { animation: none; } } + diff --git a/docs/site/js/main.js b/docs/site/js/main.js index a361dfb..fda78f8 100644 --- a/docs/site/js/main.js +++ b/docs/site/js/main.js @@ -1,5 +1,9 @@ -/* Copy for the install block only. No typing. No replay. */ +/* Copy for install. Scroll draws the protocol. No typing. No replay. */ (function () { + "use strict"; + + document.documentElement.classList.add("js"); + function textOf(sel) { var el = document.querySelector(sel); if (!el) return ""; @@ -42,4 +46,68 @@ } }); }); + + var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + 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")); + + function paintRail() { + if (!gates || !ink) return; + var on = steps.filter(function (li) { return li.classList.contains("is-on"); }); + if (!on.length) { + ink.style.height = "0px"; + return; + } + var last = on[on.length - 1]; + var n = last.querySelector(".n"); + var top = 10; + var end = (n ? n.offsetTop : last.offsetTop) + (n ? n.offsetHeight / 2 : 16); + ink.style.height = Math.max(0, end - top) + "px"; + } + + var ink = null; + 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"); + 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); + } + 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"); + paintRail(); + return; + } + + if ("IntersectionObserver" in window) { + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (!e.isIntersecting) return; + e.target.classList.add("is-on"); + if (e.target.parentElement === gates) onStep(e.target, true); + }); + }, { 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"); + } + + window.addEventListener("resize", paintRail); })();