From 6d8f75dd5873f1427a343971edd0e0ea40b015a5 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 28 Feb 2023 19:50:01 -0400 Subject: hash link highlighting & additional skippers --- src/static/client.js | 34 ++++++++++++++++++++++++++++++++++ src/static/site3.css | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 2 deletions(-) (limited to 'src/static') diff --git a/src/static/client.js b/src/static/client.js index 87b74004..4eb1d2ba 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -452,6 +452,8 @@ function addHashLinkHandlers() { // This lets the scroll offset be consolidated where it makes sense, and // sets an appropriate offset when (re)loading a page with hash for free! + let wasHighlighted; + for (const a of document.links) { const href = a.getAttribute('href'); if (!href || !href.startsWith('#')) { @@ -469,6 +471,17 @@ function addHashLinkHandlers() { const href = evt.target.getAttribute('href'); const id = href.slice(1); const linked = document.getElementById(id); + + if (!linked) { + return; + } + + // Hide skipper box right away, so the layout is updated on time for the + // math operations coming up next. + const skipper = document.getElementById('skippers'); + skipper.style.display = 'none'; + setTimeout(() => skipper.style.display = ''); + const box = linked.getBoundingClientRect(); const style = window.getComputedStyle(linked); @@ -480,6 +493,27 @@ function addHashLinkHandlers() { evt.preventDefault(); history.pushState({}, '', href); window.scrollTo({top: scrollY, behavior: 'smooth'}); + linked.focus({preventScroll: true}); + + const maxScroll = + document.body.scrollHeight + - window.innerHeight; + + if (scrollY > maxScroll && linked.classList.contains('content-heading')) { + if (wasHighlighted) { + wasHighlighted.classList.remove('highlight-hash-link'); + } + + wasHighlighted = linked; + linked.classList.add('highlight-hash-link'); + linked.addEventListener('animationend', function handle(evt) { + if (evt.animationName === 'highlight-hash-link') { + linked.removeEventListener('animationend', handle); + linked.classList.remove('highlight-hash-link'); + wasHighlighted = null; + } + }); + } } } diff --git a/src/static/site3.css b/src/static/site3.css index 9128bd81..c522bc9d 100644 --- a/src/static/site3.css +++ b/src/static/site3.css @@ -208,7 +208,19 @@ body::before { box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); } -#skippers > .skipper:not(:last-child)::after { +#skippers > * { + display: inline-block; +} + +#skippers > .skipper-list:not(:last-child)::after { + display: inline-block; + content: "\00a0"; + margin-left: 2px; + margin-right: -2px; + border-left: 1px dotted; +} + +#skippers .skipper-list > .skipper:not(:last-child)::after { content: " \00b7 "; font-weight: 800; } @@ -1137,6 +1149,37 @@ html[data-url-key="localized.home"] .carousel-container { margin-bottom: 0; } +/* Custom hash links */ + +.content-heading { + border-bottom: 3px double transparent; + margin-bottom: -3px; +} + +.content-heading.highlight-hash-link { + animation: highlight-hash-link 4s; + animation-delay: 125ms; +} + +/* This animation's name is referenced in JavaScript */ +@keyframes highlight-hash-link { + 0% { + border-bottom-color: transparent; + } + + 10% { + border-bottom-color: white; + } + + 25% { + border-bottom-color: white; + } + + 100% { + border-bottom-color: transparent; + } +} + /* Sticky heading */ #content [id] { @@ -1145,7 +1188,7 @@ html[data-url-key="localized.home"] .carousel-container { 74px /* Sticky heading */ + 33px /* Sticky subheading */ - 1em /* One line of text (align bottom) */ - - 4px /* Padding for hanging letters */ + - 12px /* Padding for hanging letters & focus ring */ ); } -- cgit 1.3.0-6-gf8a5