website: redesign part 10

This commit is contained in:
nym21
2026-06-06 11:03:55 +02:00
parent b00692249c
commit 66dc7cd8f5
4 changed files with 800 additions and 34 deletions
+25 -3
View File
@@ -3,14 +3,15 @@ main.learn {
counter-reset: content-theme;
position: sticky;
top: 0;
padding-top: var(--top-offset);
padding-bottom: calc(var(--top-offset) / 2);
padding-block: var(--offset);
max-height: 100dvh;
overflow: auto;
scrollbar-width: thin;
font-size: var(--font-size-xs);
line-height: var(--line-height-xs);
text-transform: uppercase;
padding-left: 0.5rem;
margin-left: -0.5rem;
ol {
list-style: none;
@@ -38,16 +39,37 @@ main.learn {
}
a {
display: block;
scroll-margin-block: var(--offset);
color: inherit;
text-decoration: none;
margin-right: 1rem;
&::before {
opacity: 0.5;
}
&:is(:hover, [aria-current="location"]) {
&:is(:hover, :active) {
margin-block: -0.25rem;
margin-left: -0.5rem;
padding: 0.25rem;
padding-left: 0.5rem;
border-radius: 0.25rem;
}
&:hover {
color: var(--white);
background-color: var(--dark-gray);
}
&[aria-current="location"] {
color: var(--orange);
}
&:active {
color: var(--black);
background-color: var(--orange);
}
}
> ol > li > a::before {
+63 -20
View File
@@ -1,7 +1,14 @@
/** @param {HTMLElement} main */
export function initScrollSpy(main) {
const headings = [...main.querySelectorAll("article h1, article h2")];
const visibleHeadings = new Set();
const sections = [...main.querySelectorAll("section[id]")];
const sectionStates = sections.map((section) => ({
section,
children: [...section.querySelectorAll(":scope > section")],
intersecting: false,
}));
const stateBySection = new Map(
sectionStates.map((state) => [state.section, state]),
);
const links = new Map(
[...main.querySelectorAll('nav a[href^="#"]')].map((link) => [
link.getAttribute("href"),
@@ -12,12 +19,24 @@ export function initScrollSpy(main) {
/** @type {string | null} */
let current = null;
/** @param {Element} heading */
function getHash(heading) {
const section = /** @type {HTMLElement} */ (
heading.closest("section[id]")
/** @param {Element} section */
function getVisibleHeight(section) {
const rect = section.getBoundingClientRect();
return Math.max(
0,
Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0),
);
return `#${section.id}`;
}
/** @param {{ section: Element, children: Element[] }} state */
function getOwnVisibleHeight(state) {
let height = getVisibleHeight(state.section);
for (const child of state.children) {
height -= getVisibleHeight(child);
}
return Math.max(0, height);
}
/** @param {string} hash */
@@ -26,38 +45,62 @@ export function initScrollSpy(main) {
}
/** @param {string} hash */
function setCurrent(hash) {
function setCurrentHash(hash) {
if (hash === current) return;
if (current) getLink(current).removeAttribute("aria-current");
getLink(hash).setAttribute("aria-current", "location");
const link = getLink(hash);
link.setAttribute("aria-current", "location");
link.scrollIntoView({ block: "nearest", inline: "nearest" });
history.replaceState(null, "", hash);
current = hash;
}
function getCurrentSection() {
/** @type {{ section: Element, children: Element[] } | undefined} */
let currentState;
let currentHeight = 0;
for (const state of sectionStates) {
if (!state.intersecting) continue;
const height = getOwnVisibleHeight(state);
if (height > currentHeight) {
currentState = state;
currentHeight = height;
}
}
return currentState?.section;
}
function update() {
if (main.hidden) return;
const heading = headings.findLast((heading) =>
visibleHeadings.has(heading),
);
if (heading) setCurrent(getHash(heading));
const section = getCurrentSection();
if (section) setCurrentHash(`#${section.id}`);
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
visibleHeadings.add(entry.target);
} else {
visibleHeadings.delete(entry.target);
}
const state = /** @type {{ intersecting: boolean }} */ (
stateBySection.get(entry.target)
);
state.intersecting = entry.isIntersecting;
}
update();
},
{ rootMargin: "0px 0px -80% 0px" },
{
threshold: [
0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1,
],
},
);
for (const heading of headings) observer.observe(heading);
for (const section of sections) observer.observe(section);
}
+10 -11
View File
@@ -1,5 +1,5 @@
main.learn {
--top-offset: 6rem;
--offset: 6rem;
--content-width: 52rem;
display: grid;
@@ -9,8 +9,7 @@ main.learn {
article {
counter-reset: theme;
padding-top: var(--top-offset);
padding-bottom: calc(var(--top-offset) / 2);
padding-block: var(--offset);
&::before {
content: "";
@@ -18,10 +17,10 @@ main.learn {
top: 0;
z-index: 2;
display: block;
height: var(--top-offset);
margin-top: calc(-1 * var(--top-offset));
height: var(--offset);
margin-top: calc(-1 * var(--offset));
margin-inline: auto;
margin-bottom: calc(-1 * var(--top-offset));
margin-bottom: calc(-1 * var(--offset));
background: var(--black);
pointer-events: none;
}
@@ -31,12 +30,12 @@ main.learn {
counter-reset: topic;
width: min(100%, var(--content-width));
margin-inline: auto;
scroll-margin-top: var(--top-offset);
scroll-margin-top: var(--offset);
}
> section:first-of-type {
margin-top: calc(-1 * var(--top-offset));
padding-top: var(--top-offset);
margin-top: calc(-1 * var(--offset));
padding-top: var(--offset);
}
> section + section {
@@ -45,14 +44,14 @@ main.learn {
section section {
counter-increment: topic;
scroll-margin-top: var(--top-offset);
scroll-margin-top: var(--offset);
}
}
h1,
h2 {
position: sticky;
top: var(--top-offset);
top: var(--offset);
padding-bottom: 0.5rem;
background: var(--black);
line-height: 1;