website: redesign part 7

This commit is contained in:
nym21
2026-06-05 16:03:04 +02:00
parent 7cee0e2c5a
commit ff2c04a100
11 changed files with 254 additions and 164 deletions
+50
View File
@@ -0,0 +1,50 @@
/**
* @param {{ title: string, children: Section[] }} section
*/
function createSectionId(section) {
return section.title.toLowerCase().replaceAll(" ", "-");
}
/**
* @param {{ title: string, children: Section[] }} section
* @param {number[]} indexes
*/
function createContentsItem(section, indexes) {
const item = document.createElement("li");
const anchor = document.createElement("a");
anchor.href = `#${createSectionId(section)}`;
anchor.append(section.title);
if (section.children.length) {
const list = document.createElement("ol");
for (const [index, child] of section.children.entries()) {
list.append(createContentsItem(child, indexes.concat(index + 1)));
}
item.append(list);
}
item.prepend(anchor);
return item;
}
/** @param {Section[]} sections */
export function createContents(sections) {
const nav = document.createElement("nav");
const list = document.createElement("ol");
nav.setAttribute("aria-label", "Learn contents");
for (const [index, section] of sections.entries()) {
list.append(createContentsItem(section, [index + 1]));
}
nav.append(list);
return nav;
}
/**
* @typedef {Object} Section
* @property {string} title
* @property {Section[]} children
*/
+60
View File
@@ -0,0 +1,60 @@
main.learn {
> nav {
position: sticky;
top: 0;
padding-top: var(--top-offset);
padding-bottom: calc(var(--top-offset) / 2);
max-height: 100dvh;
overflow: auto;
scrollbar-width: thin;
font-size: var(--font-size-xs);
line-height: var(--line-height-xs);
text-transform: uppercase;
ol {
list-style: none;
margin: 0;
padding: 0;
}
> ol > li {
counter-increment: content-theme;
counter-reset: content-topic;
}
ol ol {
margin-top: 0.5rem;
margin-left: 1rem;
color: var(--gray);
}
ol ol > li {
counter-increment: content-topic;
}
li + li {
margin-top: 0.5rem;
}
a {
color: inherit;
text-decoration: none;
&::before {
opacity: 0.5;
}
&:hover {
color: var(--orange);
}
}
> ol > li > a::before {
content: counter(content-theme, upper-roman) ". ";
}
ol ol > li > a::before {
content: counter(content-topic) ". ";
}
}
}
+38 -58
View File
@@ -1,3 +1,4 @@
import { createContents } from "./contents/index.js";
import { sections } from "./data.js";
/** @param {string} label */
@@ -31,7 +32,7 @@ function createSection(section, indexes) {
const description = document.createElement("p");
const id = createSectionId(section.title);
title.id = id;
element.id = id;
anchor.href = `#${id}`;
anchor.append(section.title);
title.append(anchor);
@@ -45,49 +46,10 @@ function createSection(section, indexes) {
return element;
}
/**
* @param {{ title: string, children: Section[] }} section
* @param {number[]} indexes
*/
function createContentsItem(section, indexes) {
const item = document.createElement("li");
const anchor = document.createElement("a");
anchor.href = `#${createSectionId(section.title)}`;
anchor.append(section.title);
item.append(anchor);
if (section.children.length) {
const list = document.createElement("ol");
for (const [index, child] of section.children.entries()) {
list.append(createContentsItem(child, indexes.concat(index + 1)));
}
item.append(list);
}
return item;
}
function createContents() {
const nav = document.createElement("nav");
const list = document.createElement("ol");
nav.setAttribute("aria-label", "Learn contents");
for (const [index, section] of sections.entries()) {
list.append(createContentsItem(section, [index + 1]));
}
nav.append(list);
return nav;
}
/**
* @param {HTMLElement} main
* @param {HTMLElement} article
*/
function initScrollSpy(main, article) {
const titles = [...article.querySelectorAll("h1[id], h2[id]")];
const visible = new Set();
/** @param {HTMLElement} main */
function initScrollSpy(main) {
const headings = [...main.querySelectorAll("article h1, article h2")];
const visibleHeadings = new Set();
const links = new Map(
[...main.querySelectorAll('nav a[href^="#"]')].map((link) => [
link.getAttribute("href"),
@@ -98,36 +60,54 @@ function initScrollSpy(main, article) {
/** @type {string | null} */
let current = null;
function update() {
const title = titles.find((title) => visible.has(title.id));
if (!title) return;
/** @param {Element} heading */
function getHash(heading) {
const section = /** @type {HTMLElement} */ (
heading.closest("section[id]")
);
return `#${section.id}`;
}
const hash = `#${title.id}`;
/** @param {string} hash */
function getLink(hash) {
return /** @type {HTMLAnchorElement} */ (links.get(hash));
}
/** @param {string} hash */
function setCurrent(hash) {
if (hash === current) return;
links.get(current)?.removeAttribute("aria-current");
links.get(hash)?.setAttribute("aria-current", "location");
if (current) getLink(current).removeAttribute("aria-current");
getLink(hash).setAttribute("aria-current", "location");
history.replaceState(null, "", hash);
current = hash;
}
function update() {
if (main.hidden) return;
const heading = headings.findLast((heading) =>
visibleHeadings.has(heading),
);
if (heading) setCurrent(getHash(heading));
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
visible.add(entry.target.id);
visibleHeadings.add(entry.target);
} else {
visible.delete(entry.target.id);
visibleHeadings.delete(entry.target);
}
}
update();
},
{ root: article },
{ rootMargin: "0px 0px -80% 0px" },
);
for (const title of titles) {
observer.observe(title);
}
for (const heading of headings) observer.observe(heading);
}
export function createLearnPage() {
@@ -139,8 +119,8 @@ export function createLearnPage() {
article.append(createSection(section, [index + 1]));
}
main.append(article, createContents());
initScrollSpy(main, article);
main.append(createContents(sections), article);
initScrollSpy(main);
return main;
}
+20 -82
View File
@@ -1,19 +1,16 @@
main.learn {
--sticky-h: 5rem;
--sidebar-top: 6rem;
--top-offset: 6rem;
--sidebar-bottom: 1rem;
display: flex;
overflow: hidden;
display: grid;
grid-template-columns: 14rem minmax(0, 1fr);
gap: 4rem;
padding: 0 2rem;
article {
position: relative;
counter-reset: theme;
min-width: 0;
min-height: 0;
overflow: auto;
scroll-behavior: smooth;
scrollbar-gutter: stable;
padding-top: var(--top-offset);
padding-bottom: calc(var(--top-offset) / 2);
&::before {
content: "";
@@ -22,9 +19,10 @@ main.learn {
z-index: 2;
display: block;
width: min(100%, 52rem);
height: var(--sticky-h);
height: var(--top-offset);
margin-top: calc(-1 * var(--top-offset));
margin-inline: auto;
margin-bottom: calc(-1 * var(--sticky-h));
margin-bottom: calc(-1 * var(--top-offset));
background: var(--black);
pointer-events: none;
}
@@ -34,6 +32,12 @@ main.learn {
counter-reset: topic;
width: min(100%, 52rem);
margin-inline: auto;
scroll-margin-top: var(--top-offset);
}
> section:first-of-type {
margin-top: calc(-1 * var(--top-offset));
padding-top: var(--top-offset);
}
> section + section {
@@ -42,7 +46,7 @@ main.learn {
section section {
counter-increment: topic;
margin-top: 4rem;
scroll-margin-top: var(--top-offset);
}
}
@@ -86,8 +90,7 @@ main.learn {
h1 {
z-index: 3;
top: var(--sticky-h);
scroll-margin-top: var(--sticky-h);
top: var(--top-offset);
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--dark-gray);
font-size: 2.75rem;
@@ -99,9 +102,8 @@ main.learn {
h2 {
z-index: 1;
top: var(--sticky-h);
scroll-margin-top: var(--sticky-h);
padding-top: 3.5rem;
top: var(--top-offset);
padding-top: 4.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px dashed var(--dark-gray);
font-size: 1.5rem;
@@ -134,68 +136,4 @@ main.learn {
margin-top: 0.75rem;
}
}
> nav {
counter-reset: content-theme;
min-height: 0;
overflow: auto;
scrollbar-gutter: stable;
padding-top: var(--sidebar-top);
padding-bottom: var(--sidebar-bottom);
font-size: var(--font-size-xs);
line-height: var(--line-height-sm);
text-transform: uppercase;
ol {
list-style: none;
margin: 0;
padding: 0;
}
> ol > li {
counter-increment: content-theme;
counter-reset: content-topic;
}
ol ol {
margin-top: 0.5rem;
margin-left: 1rem;
color: var(--gray);
}
ol ol > li {
counter-increment: content-topic;
}
li + li {
margin-top: 0.5rem;
}
a {
color: inherit;
text-decoration: none;
&::before {
opacity: 0.5;
}
&:hover {
color: var(--orange);
}
}
> ol > li > a::before {
content: counter(content-theme, upper-roman) ". ";
}
ol ol > li > a::before {
content: counter(content-topic) ". ";
}
}
}
@media (prefers-reduced-motion: reduce) {
main.learn article {
scroll-behavior: auto;
}
}