From dae27a9756a7bac2a1f801dbdd1d0c40aff5d8ea Mon Sep 17 00:00:00 2001 From: Orion Edwards Date: Fri, 7 Aug 2026 14:10:23 +1200 Subject: [PATCH 1/5] Claude re-styling the right nav; first pass --- src/components/ArticleNav.astro | 2 - src/styles/main.css | 223 +++++++++++++++----------------- 2 files changed, 107 insertions(+), 118 deletions(-) diff --git a/src/components/ArticleNav.astro b/src/components/ArticleNav.astro index 40deaa609c..aef1e174d4 100644 --- a/src/components/ArticleNav.astro +++ b/src/components/ArticleNav.astro @@ -2,7 +2,6 @@ import { accelerator } from '@lib/accelerator'; import { SITE } from '@config'; import { Translations, Lang } from '@util/Languages'; -import Separator from './Separator.astro'; const stats = new accelerator.statistics('octopus/components/ArticleNav.astro'); stats.start(); @@ -31,7 +30,6 @@ stats.stop(); {_(Translations.toc.title)} -
    {headings.map((heading) => (
  1. only ever collapses at the restack breakpoint, where this + column sits above the article. On desktop the component's script forces it + open and the summary is inert. */ +.article-nav__details { + display: flex; + flex-direction: column; + gap: var(--space16); } -.article-nav .article-nav__details[open] .article-nav__title::after { - transform: rotate(180deg); +/* Two classes throughout, so these beat the global `summary:hover, + summary:focus` rule — which would otherwise leave the title in a link colour + after it has been clicked open at the restack breakpoint. */ +.article-nav .article-nav__title, +.article-nav .article-nav__title:hover, +.article-nav .article-nav__title:focus { + color: var(--colorTextPrimary); + text-decoration: none; } .article-nav__title { + /* Laying the summary out as a flex box drops the disclosure triangle + everywhere except Safari, which needs the -webkit rule below. */ + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space8); + padding-block-end: var(--space6); + padding-inline-start: var(--space16); + font: var(--textBodyBoldMedium); pointer-events: none; } -@media (max-width: 1130px) { - .article-nav__title { - pointer-events: auto; - } - - .article-nav .article-nav__details .article-nav__title::after { - /* fa-chevron-down */ - content: '\f078'; - font-family: fa-solid; - float: right; - transition: transform var(--duration-default) ease-in-out; - display: block; - } -} - -.article-nav__title { - color: var(--color-heading); - font-family: var(--fontFamilySystem-ui); - font-size: var(--fontSizeBase); - font-weight: var(--fontWeight700); - text-transform: uppercase; - text-decoration: none; - transition: color var(--duration-default) ease-in-out; - - @media (prefers-reduced-motion: reduce) { - transition: none; - } -} - -.article-nav .separator { - margin: 1rem 0 0.69rem; +.article-nav__title::-webkit-details-marker { + display: none; } +/* The rail runs the full height of the list. The current heading paints its own + bold segment over the top of it. */ .article-nav__list { display: flex; flex-direction: column; - justify-content: center; + gap: var(--space4); list-style: none; - gap: 1rem; + border-inline-start: var(--borderWidth2) solid var(--colorBorderPrimary); } -.article-nav__item { - cursor: pointer; -} - -.article-nav__link { - display: flex; - color: var(--color-subtitle); - font-size: var(--fontSizeBase); - line-height: 1.5rem; - font-weight: var(--fontWeight400); +/* Two classes throughout, so these beat the global `a:hover, a:focus` rule — + including after a click, which leaves the link focused but not + focus-visible. */ +.article-nav .article-nav__link { + display: block; + position: relative; + padding-block: var(--space6); + padding-inline-start: var(--space16); + color: var(--colorTextSecondary); + font: var(--textBodyRegularMedium); text-decoration: none; - transition-property: font-weight, color, transform; - transition-timing-function: ease-in-out; - transition-duration: var(--duration-default); + overflow-wrap: break-word; + transition: color var(--duration-default) ease-in-out; @media (prefers-reduced-motion: reduce) { - transition-property: none; + transition: none; } } -.article-nav__link.highlight, -.article-nav__item:hover .article-nav__link { - font-weight: var(--fontWeight600); - color: var(--badge-color); +/* `highlight` is applied by scripts/modules/toc.js to whichever heading is in + view. */ +.article-nav .article-nav__link.highlight { + color: var(--colorTextPrimary); } -.article-nav__item:hover .article-nav__link { - transform: translateX(0.5rem); +/* The segment sits one border-width to the left of the link's box, which is + where the rail is regardless of how far the heading level indents. */ +.article-nav__link.highlight::before { + content: ''; + position: absolute; + inset-block: 0; + inset-inline-start: calc(-1 * var(--borderWidth2)); + width: var(--borderWidth2); + background-color: var(--colorBorderBold); } -.article-nav .article-nav__item--level-3 { - padding-inline-start: 1em; +.article-nav .article-nav__link:hover, +.article-nav .article-nav__link:focus-visible { + color: var(--colorTextLinkDefault); } -.article-nav .article-nav__item--level-4 { - padding-inline-start: 2em; +/* h2 takes the base indent. The design steps two levels in from there and + stops, so anything deeper lines up with h4. */ +.article-nav__item--level-3 .article-nav__link { + padding-inline-start: var(--space32); } -.article-nav .article-nav__item--level-5 { - padding-inline-start: 3em; +.article-nav__item--level-4 .article-nav__link, +.article-nav__item--level-5 .article-nav__link, +.article-nav__item--level-6 .article-nav__link { + padding-inline-start: var(--space48); } -.article-nav .article-nav__item--level-6 { - padding-inline-start: 4em; +@media (max-width: 1130px) { + .article-nav__title { + pointer-events: auto; + } + + /* Masked pseudo-element rather than inline SVG, matching the site nav. */ + .article-nav__title::after { + content: ''; + flex: none; + width: 1.25rem; + height: 1.25rem; + background-color: var(--colorIconTertiary); + mask: url('../assets/icons/chevron-right.svg') center / contain no-repeat; + } + + .article-nav__details[open] .article-nav__title::after { + mask-image: url('../assets/icons/chevron-down.svg'); + } } /* Card */ @@ -1335,22 +1336,19 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { .side-nav { align-self: start; - overflow: overlay; position: sticky; - font-size: var(--fontSizeMedium); - font-weight: var(--fontWeight400); - margin-block-start: 10vh; - height: calc(100vh - 82px - 10.625rem); - top: 10.625rem; -} - -.side-nav > ul { - padding-inline-end: 1rem; - padding-block-end: 3rem; -} - -.side-nav li { - list-style: none; + /* The last of the three column start heights below the header — 16px for the + nav, 56px for the content, 100px here. */ + margin-block-start: 6.25rem; + top: calc(82px + 6.25rem); + /* Everything from the sticky top to the bottom of the window, so a long list + scrolls inside the column instead of running off the end of it. */ + height: calc(100vh - 82px - 6.25rem); + padding-block-end: var(--space48); + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color) transparent; } @media (max-width: 1130px) { @@ -1370,22 +1368,15 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { height: calc(100vh - 7.625rem - var(--space16)); } + /* Stacked above the article rather than beside it, so none of the sticky + column geometry applies. */ .side-nav { + position: static; + top: auto; height: auto; margin: 0; - padding: 0; - position: unset; - top: unset; - } - - .side-nav details { - margin-block: 1rem; - width: auto; - margin-inline: auto; - } - - .side-nav summary { - text-align: center; + padding-block: var(--space16) var(--space32); + overflow: visible; } } @@ -1530,9 +1521,9 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { /* Drops the breadcrumb 56px below the header. The design gives each column its own start height below the header — 16px for the nav, 56px here, 100px for the toc — so there is no shared value to put on .content-group, - whose margin-top is clearance for the fixed header. Only this column is - built to the design so far: the nav still starts at 0 and .side-nav still - uses a viewport-relative 10vh. Reset in the restack breakpoint below. */ + whose margin-top is clearance for the fixed header. The nav is the one + column not built to the design yet; it still starts at 0. Reset in the + restack breakpoint below. */ margin: var(--space56) 0 0; } From 0d3a00fcee1b669ca7c1d16353398201eb765410 Mon Sep 17 00:00:00 2001 From: Orion Edwards Date: Fri, 7 Aug 2026 14:50:34 +1200 Subject: [PATCH 2/5] Pad away from the right hand edge --- src/styles/main.css | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/styles/main.css b/src/styles/main.css index 15c82578bd..57926ccf18 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1337,6 +1337,9 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { .side-nav { align-self: start; position: sticky; + /* The global reset does not inherit html's border-box, and this column is + sized by the grid with padding on top of it. */ + box-sizing: border-box; /* The last of the three column start heights below the header — 16px for the nav, 56px for the content, 100px here. */ margin-block-start: 6.25rem; @@ -1344,9 +1347,11 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { /* Everything from the sticky top to the bottom of the window, so a long list scrolls inside the column instead of running off the end of it. */ height: calc(100vh - 82px - 6.25rem); + /* Keeps the toc off the right edge of the window. The grid column is widened + to match, so the nav itself keeps its designed width. */ + padding-inline-end: var(--space16); padding-block-end: var(--space48); overflow-y: auto; - overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--scrollbar-color) transparent; } @@ -1375,7 +1380,8 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { top: auto; height: auto; margin: 0; - padding-block: var(--space16) var(--space32); + /* The grid's own left and right areas provide the gutters here. */ + padding: var(--space16) 0 var(--space32); overflow: visible; } } @@ -1461,7 +1467,10 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { grid-template-columns: var(--navigation-width) minmax(300px, var(--content-width-article)) - var(--toc-width); + /* space-between pins this column to the right edge of the window, so the + column carries a gutter on top of the width the toc itself is designed + to. .side-nav spends it as padding. */ + calc(var(--toc-width) + var(--space16)); display: grid; grid-template-rows: auto; gap: var(--grid-gap-main); From ab3028ffd4a964b9674023b02a52c15449d27c4d Mon Sep 17 00:00:00 2001 From: Orion Edwards Date: Fri, 7 Aug 2026 14:51:02 +1200 Subject: [PATCH 3/5] Claude rewrites the TOC.js to fix pre-existing bugs and be faster --- src/scripts/modules/toc.js | 131 +++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 49 deletions(-) diff --git a/src/scripts/modules/toc.js b/src/scripts/modules/toc.js index 3af857d873..42f3fc0124 100644 --- a/src/scripts/modules/toc.js +++ b/src/scripts/modules/toc.js @@ -2,82 +2,115 @@ import { qsa } from './query.js'; -let links = []; -let current = ''; -const headings = []; const highlightClass = 'highlight'; /** - * Makes an entire block clickable based on a data-attribute, usually "data-destination" + * Marks the link in a table of contents whose section the reader is in, and + * keeps it in step as the page scrolls. * - * Example: You have a list of blog posts, including featured images. If you make the title - * clickable, clicks on the image won't open the blog. Adding links to the images means - * keyboard users have to tab twice as much to get through the list. - * - * Use clickable blocks to allow keyboard users to tab through the real links, but still - * capture clicks elsewhere on the block. + * Every table of contents on the page gets its own call, and each keeps its + * own state — a page can show more than one at a time. * + * @param {string} tocSelector selector for the links of one table of contents */ function highlightCurrentHeading(tocSelector) { - links = qsa(tocSelector); + /** @type {{link: HTMLElement, heading: HTMLElement}[]} */ + const entries = []; + + qsa(tocSelector).forEach((link) => { + const id = getBookmarkLink(link.href); + const heading = id ? document.getElementById(id) : null; - links.forEach((link) => { - const bookmarkLink = getBookmarkLink(link.href); - if (bookmarkLink) { - headings.push(document.getElementById(bookmarkLink)); + // A link can outlive its heading — a stale anchor, or a heading rendered + // conditionally. Those links just never highlight. + if (heading) { + entries.push({ link, heading }); } }); - recheck(); -} - -function getBookmarkLink(link) { - const linkParts = link.split('#'); - if (linkParts.length === 2) { - return linkParts[1]; + if (entries.length === 0) { + return; } - return ''; -} + /** @type {{link: HTMLElement, heading: HTMLElement} | undefined} */ + let current; + let queued = false; -function highlight(id) { - links.forEach((link) => { - link.classList.remove(highlightClass); + const update = () => { + queued = false; - const bookmarkLink = getBookmarkLink(link.href); - if (bookmarkLink === id) { - link.classList.add(highlightClass); + const entry = currentEntry(entries); + if (entry === current) { + return; } - }); + + current = entry; + entries.forEach((candidate) => { + candidate.link.classList.toggle(highlightClass, candidate === entry); + }); + }; + + // Scroll fires far more often than the page can paint, so the reading is + // taken once per frame at most. + const queue = () => { + if (!queued) { + queued = true; + window.requestAnimationFrame(update); + } + }; + + update(); + window.addEventListener('scroll', queue, { passive: true }); + window.addEventListener('resize', queue); } -function recheck() { - const docTop = Math.floor(document.documentElement.scrollTop); - const vh = Math.max( - document.documentElement.clientHeight || 0, - window.innerHeight || 0 - ); +/** + * The section the reader is in: the last heading to have passed the line that + * `scroll-padding-block-start` parks a clicked anchor on. Clicking a link in + * the table of contents therefore always highlights the link that was clicked. + * + * Above the first heading the first section is used, and at the bottom of the + * page the last one is, so a final section too short to scroll to the line + * still gets its turn. + * + * @param {{link: HTMLElement, heading: HTMLElement}[]} entries + */ +function currentEntry(entries) { + const doc = document.documentElement; + + if (Math.ceil(window.scrollY + window.innerHeight) >= doc.scrollHeight) { + return entries[entries.length - 1]; + } - const validItems = []; + // `scrollPaddingTop` is `auto` when the page sets no scroll padding. + const line = parseFloat(getComputedStyle(doc).scrollPaddingTop) || 0; - headings.forEach((elem) => { - const hasPassed = elem.offsetTop < docTop; - const inView = elem.offsetTop > docTop && elem.offsetTop < docTop + vh; - const isValid = docTop + vh - elem.offsetTop > vh / 1.5; + let current = entries[0]; - if (isValid) { - validItems.push(elem); + entries.forEach((entry) => { + // A clicked heading lands exactly on the line, so allow a pixel for the + // browser's rounding of the scroll position. + if (entry.heading.getBoundingClientRect().top <= line + 1) { + current = entry; } }); - const item = validItems.pop(); + return current; +} - if (item && item.id !== current) { - current = item.id; - highlight(item.id); +/** + * The fragment of a link's href, if it has one. + * + * @param {string} link + */ +function getBookmarkLink(link) { + const linkParts = link.split('#'); + + if (linkParts.length === 2) { + return linkParts[1]; } - window.setTimeout(recheck, 1000); + return ''; } export { highlightCurrentHeading }; From 78633ab9e052374c274df1a51c5e9a1248a37888 Mon Sep 17 00:00:00 2001 From: Orion Edwards Date: Fri, 7 Aug 2026 16:09:11 +1200 Subject: [PATCH 4/5] Delete unused separator component and fix non-american spelling in css comment # Conflicts: # src/pages/components.mdx Separator component is gone --- src/components/Separator.astro | 5 ----- src/pages/components.mdx | 14 +------------- src/styles/main.css | 2 +- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 src/components/Separator.astro diff --git a/src/components/Separator.astro b/src/components/Separator.astro deleted file mode 100644 index 357eed7b0d..0000000000 --- a/src/components/Separator.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -// No props are needed ---- - -
    diff --git a/src/pages/components.mdx b/src/pages/components.mdx index 416bf7a853..c5ebdacc81 100644 --- a/src/pages/components.mdx +++ b/src/pages/components.mdx @@ -18,7 +18,6 @@ import Card from 'src/components/Card.astro'; import IconTile from 'src/components/IconTile.astro'; import Image from 'src/components/Image.astro'; import Link from 'src/components/Link.astro'; -import Separator from 'src/components/Separator.astro'; import TopNav from 'src/components/TopNav.astro'; ## Component Usage Guide @@ -35,7 +34,7 @@ To use Astro components like `Card` and `Separator` in your articles, you will n ```javascript import Card from 'src/components/Card.astro'; -import Separator from 'src/components/Separator.astro'; +import Link from 'src/components/Link.astro'; ``` ## Components usage @@ -401,17 +400,6 @@ The Link component is designed to provide a standardized way to display links wi -### Separator - -
    - -
    - ``` - - ``` -
    -
    - ## Layout ### Grid diff --git a/src/styles/main.css b/src/styles/main.css index 57926ccf18..03964b79de 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -914,7 +914,7 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { } /* Two classes throughout, so these beat the global `summary:hover, - summary:focus` rule — which would otherwise leave the title in a link colour + summary:focus` rule — which would otherwise leave the title in a link color after it has been clicked open at the restack breakpoint. */ .article-nav .article-nav__title, .article-nav .article-nav__title:hover, From 8632b76460fe392f8bafbc11df469a4c64a4e3f0 Mon Sep 17 00:00:00 2001 From: Orion Edwards Date: Mon, 10 Aug 2026 22:51:10 +1200 Subject: [PATCH 5/5] Update after designer feedback. Attach article-nav to main content and center them both together. Article nav position indicator now animates smoothly --- src/components/ArticleNav.astro | 8 +++++ src/scripts/modules/toc.js | 59 +++++++++++++++++++++++++++++---- src/styles/main.css | 45 +++++++++++++++++-------- src/styles/vars.css | 9 +++-- 4 files changed, 100 insertions(+), 21 deletions(-) diff --git a/src/components/ArticleNav.astro b/src/components/ArticleNav.astro index aef1e174d4..25d32d1ce0 100644 --- a/src/components/ArticleNav.astro +++ b/src/components/ArticleNav.astro @@ -45,6 +45,14 @@ stats.stop();
  2. ))}
+ {/* Slid onto the current heading's link by scripts/modules/toc.js, + which measures the links against this element's offset parent — + the
above. */} +
) diff --git a/src/scripts/modules/toc.js b/src/scripts/modules/toc.js index 42f3fc0124..6d2ad3be2d 100644 --- a/src/scripts/modules/toc.js +++ b/src/scripts/modules/toc.js @@ -3,6 +3,7 @@ import { qsa } from './query.js'; const highlightClass = 'highlight'; +const indicatorReadyClass = 'article-nav__indicator--ready'; /** * Marks the link in a table of contents whose section the reader is in, and @@ -32,6 +33,12 @@ function highlightCurrentHeading(tocSelector) { return; } + // Optional: only the article nav draws a sliding indicator, and only when the + // component has rendered one. + const indicator = entries[0].link + .closest('[data-article-nav]') + ?.querySelector('[data-article-nav-indicator]'); + /** @type {{link: HTMLElement, heading: HTMLElement} | undefined} */ let current; let queued = false; @@ -40,14 +47,17 @@ function highlightCurrentHeading(tocSelector) { queued = false; const entry = currentEntry(entries); - if (entry === current) { - return; + + if (entry !== current) { + current = entry; + entries.forEach((candidate) => { + candidate.link.classList.toggle(highlightClass, candidate === entry); + }); } - current = entry; - entries.forEach((candidate) => { - candidate.link.classList.toggle(highlightClass, candidate === entry); - }); + // Measured every time rather than only on a change of section: a resize can + // rewrap the links under an unchanged one. + moveIndicator(indicator, entry.link); }; // Scroll fires far more often than the page can paint, so the reading is @@ -62,6 +72,43 @@ function highlightCurrentHeading(tocSelector) { update(); window.addEventListener('scroll', queue, { passive: true }); window.addEventListener('resize', queue); + + // A collapsed list has no geometry to measure, so the indicator has to be + // placed again once it reopens. + entries[0].link.closest('details')?.addEventListener('toggle', queue); +} + +/** + * Puts the sliding indicator, where there is one, over a link. + * + * A hidden link — the list collapsed at the restack breakpoint — measures zero, + * so the indicator is taken back to its unplaced state instead: hidden, and + * without a transition, so that reopening the list does not animate it in from + * wherever it used to be. + * + * @param {Element | null | undefined} indicator + * @param {HTMLElement} link + */ +function moveIndicator(indicator, link) { + if (!(indicator instanceof HTMLElement)) { + return; + } + + if (link.offsetHeight === 0) { + indicator.classList.remove(indicatorReadyClass); + return; + } + + indicator.style.transform = `translateY(${link.offsetTop}px)`; + indicator.style.height = `${link.offsetHeight}px`; + + // A frame's grace, so the browser has the placement above as the state to + // animate from rather than animating the placement itself. + if (!indicator.classList.contains(indicatorReadyClass)) { + window.requestAnimationFrame(() => { + indicator.classList.add(indicatorReadyClass); + }); + } } /** diff --git a/src/styles/main.css b/src/styles/main.css index 03964b79de..0704d113d2 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -911,6 +911,8 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { display: flex; flex-direction: column; gap: var(--space16); + /* The containing block the indicator below is positioned against. */ + position: relative; } /* Two classes throughout, so these beat the global `summary:hover, @@ -975,15 +977,29 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { color: var(--colorTextPrimary); } -/* The segment sits one border-width to the left of the link's box, which is - where the rail is regardless of how far the heading level indents. */ -.article-nav__link.highlight::before { - content: ''; +/* The nav indicator overlays the list so it can animate up and down and move smoothly as the user scrolls. */ +.article-nav__indicator { position: absolute; - inset-block: 0; - inset-inline-start: calc(-1 * var(--borderWidth2)); + inset-block-start: 0; + inset-inline-start: 0; width: var(--borderWidth2); background-color: var(--colorBorderBold); + /* Nowhere to be until the script has measured a link. */ + opacity: 0; +} + +/* Added a frame after the first measurement, so the first placement is silent + and every later one is animated. Shorter than --duration-default, and eased + out, because this trails a scroll: at 300ms it reads as lagging behind. */ +.article-nav__indicator--ready { + opacity: 1; + transition: + transform 150ms ease-out, + height 150ms ease-out; + + @media (prefers-reduced-motion: reduce) { + transition: none; + } } .article-nav .article-nav__link:hover, @@ -1464,19 +1480,22 @@ html[data-theme='light'] .theme-switcher__icon--dark svg path { /* Main content group */ .content-group { + /* Every gap on this row is a track of its own, so that the one between the + article and the toc can differ from the rest. */ grid-template-columns: var(--navigation-width) + minmax(var(--grid-gap-main), 1fr) minmax(300px, var(--content-width-article)) - /* space-between pins this column to the right edge of the window, so the - column carries a gutter on top of the width the toc itself is designed - to. .side-nav spends it as padding. */ - calc(var(--toc-width) + var(--space16)); + var(--toc-offset) + /* The toc column carries a gutter on top of the width of the toc itself, + which is what keeps it off the right edge of the window */ + calc(var(--toc-width) + var(--space16)) + minmax(0, 1fr); display: grid; grid-template-rows: auto; - gap: var(--grid-gap-main); + column-gap: 0; max-width: 100vw; - justify-content: space-between; - grid-template-areas: 'menu content toc'; + grid-template-areas: 'menu . content . toc .'; margin-top: 82px; } diff --git a/src/styles/vars.css b/src/styles/vars.css index d151be36cc..593a6af588 100644 --- a/src/styles/vars.css +++ b/src/styles/vars.css @@ -166,7 +166,9 @@ --hamburger-line-width: 1.5rem; --hamburger-line-height: 0.2rem; --hamburger-line-margin: 0.2rem; - --hamburger-translate-y: calc(var(--hamburger-line-height) + var(--hamburger-line-margin)); + --hamburger-translate-y: calc( + var(--hamburger-line-height) + var(--hamburger-line-margin) + ); --hamburger-lines-color: var(--colorIconPrimary); /* Theme Switcher */ @@ -179,7 +181,8 @@ /* Card */ --card-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.15); --card-related-topics-shadow: 0px 10px 35px 0px rgba(133, 152, 165, 0.1); - --card-related-topics-hover-shadow: 0px 10px 35px 0px rgba(133, 152, 165, 0.25); + --card-related-topics-hover-shadow: 0px 10px 35px 0px + rgba(133, 152, 165, 0.25); /* To be checked and cleaned */ @@ -216,6 +219,8 @@ --content-width-article: 800px; --content-width-mobile: calc(100vw - 4rem); --toc-width: 250px; + /* Space between the article and the toc beside it. */ + --toc-offset: 80px; --grid-max-width: 1250px; --grid-gap: 1rem; --grid-gap-main: 1rem;