diff --git a/website_next/AGENTS.md b/website_next/AGENTS.md index 4873a8318..1ea5f041c 100644 --- a/website_next/AGENTS.md +++ b/website_next/AGENTS.md @@ -14,15 +14,17 @@ npx --package typescript tsc --noEmit --pretty false | grep -v "modules/" ALWAYS -- fast +- very fast +- light (memory) - KISS - DRY - very well organized - contained - colocated +- composed - prefer one concept per file - prefer more files and folders than big files - reads like english -- very easy to understand -- very easy to maintain +- easy to understand +- maintainability - avoid defensive checks when the code itself guarantees correctness diff --git a/website_next/header/index.js b/website_next/header/index.js index 2180a468c..28b943852 100644 --- a/website_next/header/index.js +++ b/website_next/header/index.js @@ -1,24 +1,32 @@ import { createCube } from "../cube/index.js"; +import { primaryRoutes } from "../routes.js"; -const header = document.createElement("header"); +export function createHeader() { + const header = document.createElement("header"); -const home = document.createElement("a"); -const cube = document.createElement("span"); + const home = document.createElement("a"); + const cube = document.createElement("span"); -home.href = "/"; -home.ariaLabel = "bitview home"; -cube.append(createCube()); -home.append(cube, "bitview"); + home.href = "/"; + home.ariaLabel = "bitview home"; + cube.append(createCube()); + home.append(cube, "bitview"); -const nav = document.createElement("nav"); -nav.setAttribute("aria-label", "Primary"); -nav.innerHTML = ` -
-`; + const nav = document.createElement("nav"); + const list = document.createElement("ul"); + nav.setAttribute("aria-label", "Primary"); -header.append(home, nav); -document.body.append(header); + for (const { pathname, label } of primaryRoutes) { + const item = document.createElement("li"); + const anchor = document.createElement("a"); + + anchor.href = pathname; + anchor.append(label); + item.append(anchor); + list.append(item); + } + + nav.append(list); + header.append(home, nav); + return header; +} diff --git a/website_next/header/style.css b/website_next/header/style.css index bad412d1e..4ccf7a9f1 100644 --- a/website_next/header/style.css +++ b/website_next/header/style.css @@ -1,7 +1,7 @@ body { > header { position: fixed; - inset: 1.5rem 2rem auto; + inset: 1.5rem var(--page-x) auto; z-index: var(--layer-header); display: grid; grid-template-columns: 1fr auto 1fr; diff --git a/website_next/index.html b/website_next/index.html index f8dd7a51c..12bb4f42f 100644 --- a/website_next/index.html +++ b/website_next/index.html @@ -89,9 +89,9 @@ - - + + diff --git a/website_next/learn/contents/index.js b/website_next/learn/contents/index.js index e67a26bb4..d02e286dc 100644 --- a/website_next/learn/contents/index.js +++ b/website_next/learn/contents/index.js @@ -1,25 +1,19 @@ -/** - * @param {{ title: string, children: Section[] }} section - */ -function createSectionId(section) { - return section.title.toLowerCase().replaceAll(" ", "-"); -} +import { createId } from "../../utils/id.js"; /** * @param {{ title: string, children: Section[] }} section - * @param {number[]} indexes */ -function createContentsItem(section, indexes) { +function createContentsItem(section) { const item = document.createElement("li"); const anchor = document.createElement("a"); - anchor.href = `#${createSectionId(section)}`; + anchor.href = `#${createId(section.title)}`; 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))); + for (const child of section.children) { + list.append(createContentsItem(child)); } item.append(list); } @@ -35,8 +29,8 @@ export function createContents(sections) { nav.setAttribute("aria-label", "Learn contents"); - for (const [index, section] of sections.entries()) { - list.append(createContentsItem(section, [index + 1])); + for (const section of sections) { + list.append(createContentsItem(section)); } nav.append(list); diff --git a/website_next/learn/contents/style.css b/website_next/learn/contents/style.css index 6757c72fd..cc8a17127 100644 --- a/website_next/learn/contents/style.css +++ b/website_next/learn/contents/style.css @@ -1,5 +1,6 @@ main.learn { > nav { + counter-reset: content-theme; position: sticky; top: 0; padding-top: var(--top-offset); @@ -44,7 +45,7 @@ main.learn { opacity: 0.5; } - &:hover { + &:is(:hover, [aria-current="location"]) { color: var(--orange); } } diff --git a/website_next/learn/index.js b/website_next/learn/index.js index 96e2a383a..5926fbce9 100644 --- a/website_next/learn/index.js +++ b/website_next/learn/index.js @@ -1,5 +1,7 @@ import { createContents } from "./contents/index.js"; import { sections } from "./data.js"; +import { initScrollSpy } from "./scroll-spy.js"; +import { createId } from "../utils/id.js"; /** @param {string} label */ function createChart(label) { @@ -14,23 +16,16 @@ function createChart(label) { return figure; } -/** - * @param {string} title - */ -function createSectionId(title) { - return title.toLowerCase().replaceAll(" ", "-"); -} - /** * @param {Section} section - * @param {number[]} indexes + * @param {number} [level] */ -function createSection(section, indexes) { +function createSection(section, level = 1) { const element = document.createElement("section"); - const title = document.createElement(indexes.length === 1 ? "h1" : "h2"); + const title = document.createElement(level === 1 ? "h1" : "h2"); const anchor = document.createElement("a"); const description = document.createElement("p"); - const id = createSectionId(section.title); + const id = createId(section.title); element.id = id; anchor.href = `#${id}`; @@ -39,84 +34,20 @@ function createSection(section, indexes) { description.append(section.description); element.append(title, description, createChart(section.chart)); - for (const [index, child] of section.children.entries()) { - element.append(createSection(child, indexes.concat(index + 1))); + for (const child of section.children) { + element.append(createSection(child, level + 1)); } return element; } -/** @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"), - link, - ]), - ); - - /** @type {string | null} */ - let current = null; - - /** @param {Element} heading */ - function getHash(heading) { - const section = /** @type {HTMLElement} */ ( - heading.closest("section[id]") - ); - return `#${section.id}`; - } - - /** @param {string} hash */ - function getLink(hash) { - return /** @type {HTMLAnchorElement} */ (links.get(hash)); - } - - /** @param {string} hash */ - function setCurrent(hash) { - if (hash === current) return; - - 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) { - visibleHeadings.add(entry.target); - } else { - visibleHeadings.delete(entry.target); - } - } - - update(); - }, - { rootMargin: "0px 0px -80% 0px" }, - ); - - for (const heading of headings) observer.observe(heading); -} - export function createLearnPage() { const main = document.createElement("main"); main.className = "learn"; const article = document.createElement("article"); - for (const [index, section] of sections.entries()) { - article.append(createSection(section, [index + 1])); + for (const section of sections) { + article.append(createSection(section)); } main.append(createContents(sections), article); diff --git a/website_next/learn/scroll-spy.js b/website_next/learn/scroll-spy.js new file mode 100644 index 000000000..1b36bad8a --- /dev/null +++ b/website_next/learn/scroll-spy.js @@ -0,0 +1,63 @@ +/** @param {HTMLElement} main */ +export 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"), + link, + ]), + ); + + /** @type {string | null} */ + let current = null; + + /** @param {Element} heading */ + function getHash(heading) { + const section = /** @type {HTMLElement} */ ( + heading.closest("section[id]") + ); + return `#${section.id}`; + } + + /** @param {string} hash */ + function getLink(hash) { + return /** @type {HTMLAnchorElement} */ (links.get(hash)); + } + + /** @param {string} hash */ + function setCurrent(hash) { + if (hash === current) return; + + 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) { + visibleHeadings.add(entry.target); + } else { + visibleHeadings.delete(entry.target); + } + } + + update(); + }, + { rootMargin: "0px 0px -80% 0px" }, + ); + + for (const heading of headings) observer.observe(heading); +} diff --git a/website_next/learn/style.css b/website_next/learn/style.css index a77600439..1410a5d54 100644 --- a/website_next/learn/style.css +++ b/website_next/learn/style.css @@ -1,11 +1,11 @@ main.learn { --top-offset: 6rem; - --sidebar-bottom: 1rem; + --content-width: 52rem; display: grid; grid-template-columns: 14rem minmax(0, 1fr); gap: 4rem; - padding: 0 2rem; + padding: 0 var(--page-x); article { counter-reset: theme; @@ -18,7 +18,6 @@ main.learn { top: 0; z-index: 2; display: block; - width: min(100%, 52rem); height: var(--top-offset); margin-top: calc(-1 * var(--top-offset)); margin-inline: auto; @@ -30,7 +29,7 @@ main.learn { > section { counter-increment: theme; counter-reset: topic; - width: min(100%, 52rem); + width: min(100%, var(--content-width)); margin-inline: auto; scroll-margin-top: var(--top-offset); } @@ -53,13 +52,15 @@ main.learn { h1, h2 { position: sticky; + top: var(--top-offset); + padding-bottom: 0.5rem; background: var(--black); line-height: 1; a { position: relative; display: inline-block; - color: white; + color: var(--white); text-decoration: none; &::before { @@ -90,8 +91,6 @@ main.learn { h1 { z-index: 3; - top: var(--top-offset); - padding-bottom: 0.5rem; border-bottom: 1px solid var(--dark-gray); font-size: 2.75rem; @@ -102,9 +101,7 @@ main.learn { h2 { z-index: 1; - top: var(--top-offset); padding-top: 4.5rem; - padding-bottom: 0.5rem; border-bottom: 1px dashed var(--dark-gray); font-size: 1.5rem; diff --git a/website_next/main.js b/website_next/main.js index d163db183..47b19c7a3 100644 --- a/website_next/main.js +++ b/website_next/main.js @@ -1,17 +1,7 @@ -import "./header/index.js"; -import { createBuildPage } from "./build/index.js"; -import { createExplorePage } from "./explore/index.js"; -import { createHomePage } from "./home/index.js"; -import { createLearnPage } from "./learn/index.js"; -import { readCssDuration, wait } from "./utils/timing.js"; - -/** @type {Record