diff --git a/assets/scripts/script.js b/assets/scripts/script.js index ab94cb8..e9c170e 100755 --- a/assets/scripts/script.js +++ b/assets/scripts/script.js @@ -12,7 +12,6 @@ document.addEventListener("DOMContentLoaded", () => { restoreStackFromURL(); initClearPanesButton(); initInitialPaneControls(); - }); /* ========================================================= @@ -21,22 +20,22 @@ document.addEventListener("DOMContentLoaded", () => { function initCopyButtons() { document.querySelectorAll("pre.src").forEach(codeBlock => { - if (codeBlock.querySelector(".copy-btn")) return; // idempotent + if (codeBlock.querySelector(".copy-btn")) return; const button = document.createElement("button"); button.className = "copy-btn"; - button.textContent = "Copy"; + button.textContent = "copy"; codeBlock.appendChild(button); button.addEventListener("click", async () => { const text = codeBlock.innerText.replace(button.innerText, "").trim(); try { await navigator.clipboard.writeText(text); - button.textContent = "Copied!"; - setTimeout(() => (button.textContent = "Copy"), 1500); + button.textContent = "copied"; + setTimeout(() => (button.textContent = "copy"), 1600); } catch { - button.textContent = "Failed"; - setTimeout(() => (button.textContent = "Copy"), 1500); + button.textContent = "failed"; + setTimeout(() => (button.textContent = "copy"), 1600); } }); }); @@ -103,18 +102,27 @@ function initThemeToggle() { const key = "theme"; const saved = localStorage.getItem(key); - if (saved === "dark" || saved === "light") { - root.setAttribute("data-theme", saved); - } + // Apply saved preference, defaulting to dark + const initial = (saved === "dark" || saved === "light") ? saved : "dark"; + root.setAttribute("data-theme", initial); const btn = document.getElementById("theme-toggle"); if (!btn) return; + const updateIcon = theme => { + btn.textContent = theme === "dark" ? "☀" : "☽"; + btn.setAttribute("aria-label", + theme === "dark" ? "Switch to light theme" : "Switch to dark theme"); + }; + + updateIcon(initial); + btn.addEventListener("click", () => { const current = root.getAttribute("data-theme"); const next = current === "dark" ? "light" : "dark"; root.setAttribute("data-theme", next); localStorage.setItem(key, next); + updateIcon(next); }); } @@ -234,7 +242,6 @@ function initTOCHighlighting() { let fullscreenSnapshot = null; - /* ========================================================= STACKED NAVIGATION (PANES) ========================================================= */ @@ -256,16 +263,34 @@ function initStackedNavigation() { }); } +function scrollPaneIntoView(pane) { + // Scroll the horizontal stack container, not the page, to avoid + // the browser jumping the whole viewport vertically. + const root = document.getElementById("stack-root"); + if (!root) return; + const paneLeft = pane.offsetLeft; + const paneRight = paneLeft + pane.offsetWidth; + const viewRight = root.scrollLeft + root.offsetWidth; + if (paneLeft < root.scrollLeft || paneRight > viewRight) { + root.scrollTo({ left: paneLeft, behavior: "smooth" }); + } +} + async function pushPane(pathname, hash = "") { const track = document.querySelector(".stack-track"); if (!track) return; const existing = [...track.children].find(p => p.dataset.url === pathname); if (existing) { - existing.scrollIntoView({ behavior: "smooth", inline: "end" }); + scrollPaneIntoView(existing); return; } + // If in fullscreen, exit first so the new pane is immediately visible + if (document.body.classList.contains("pane-fullscreen")) { + await exitFullscreen(); + } + const res = await fetch(pathname); const doc = new DOMParser().parseFromString(await res.text(), "text/html"); @@ -278,86 +303,72 @@ async function pushPane(pathname, hash = "") { pane.appendChild(content); track.appendChild(pane); - pane.scrollIntoView({ behavior: "smooth", inline: "end" }); + scrollPaneIntoView(pane); if (hash) { requestAnimationFrame(() => { - pane.querySelector(`#${CSS.escape(hash)}`) - ?.scrollIntoView({ behavior: "smooth", block: "start" }); + const target = pane.querySelector(`#${CSS.escape(hash)}`); + if (target) { + pane.scrollTop = target.offsetTop; + } }); } - - // Find the title-section and attach event listeners to the controls - const titleSection = pane.querySelector(".title-section"); - if (titleSection) { - const closeBtn = titleSection.querySelector(".pane-close"); - const fullscreenBtn = titleSection.querySelector(".pane-fullscreen"); - - if (closeBtn) { - closeBtn.addEventListener("click", () => { - if (document.body.classList.contains("pane-fullscreen")) { - exitFullscreen({ removePane: pane }); - return; - } - pane.remove(); - updateURL(); - }); - } - - if (fullscreenBtn) { - fullscreenBtn.addEventListener("click", () => { - if (pane.classList.contains("is-fullscreen")) { - exitFullscreen(); - } else { - enterFullscreen(pane); - } - }); - } - } - + + attachPaneControls(pane); updateURL(); } -function initInitialPaneControls() { - // Initialize controls for the initial pane (pane-root) that's already in the HTML - const initialPane = document.querySelector(".pane-root"); - if (!initialPane) return; - - const titleSection = initialPane.querySelector(".title-section"); +function attachPaneControls(pane) { + const titleSection = pane.querySelector(".title-section"); if (!titleSection) return; - + + // Remove edit button if present + titleSection.querySelectorAll(".pane-edit").forEach(btn => btn.remove()); + const closeBtn = titleSection.querySelector(".pane-close"); const fullscreenBtn = titleSection.querySelector(".pane-fullscreen"); - + if (closeBtn) { closeBtn.addEventListener("click", () => { if (document.body.classList.contains("pane-fullscreen")) { - exitFullscreen({ removePane: initialPane }); + exitFullscreen({ removePane: pane }); return; } - initialPane.remove(); - updateURL(); + pane.style.animation = "pane-out var(--dur-mid) var(--ease-in) forwards"; + setTimeout(() => { + pane.remove(); + updateURL(); + }, 200); }); } - + if (fullscreenBtn) { fullscreenBtn.addEventListener("click", () => { - if (initialPane.classList.contains("is-fullscreen")) { + if (pane.classList.contains("is-fullscreen")) { exitFullscreen(); } else { - enterFullscreen(initialPane); + enterFullscreen(pane); } }); } } +function initInitialPaneControls() { + const initialPane = document.querySelector(".pane-root"); + if (!initialPane) return; + + // Remove edit button + initialPane.querySelectorAll(".pane-edit").forEach(btn => btn.remove()); + + attachPaneControls(initialPane); +} + document.addEventListener("keydown", e => { if (e.key === "Escape" && document.body.classList.contains("pane-fullscreen")) { exitFullscreen(); } }); - function enterFullscreen(pane) { if (!fullscreenSnapshot) { fullscreenSnapshot = [...document.querySelectorAll(".stack-pane")] @@ -374,7 +385,6 @@ function enterFullscreen(pane) { updateURL(); } - async function exitFullscreen({ removePane } = {}) { if (!fullscreenSnapshot) return; @@ -386,7 +396,6 @@ async function exitFullscreen({ removePane } = {}) { .querySelectorAll(".stack-pane.is-fullscreen") .forEach(p => p.remove()); - // Restore stack EXCEPT the removed pane for (const url of fullscreenSnapshot) { if (url === removeUrl) continue; await pushPane(url); @@ -396,12 +405,9 @@ async function exitFullscreen({ removePane } = {}) { updateURL(); } - function clearAllPanes() { const panes = [...document.querySelectorAll(".stack-pane")]; - panes.slice(1).forEach(pane => pane.remove()); - updateURL(); } @@ -423,6 +429,7 @@ async function restoreStackFromURL() { await pushPane(url); } } + function initClearPanesButton() { const btn = document.getElementById("close-all"); if (!btn) return; @@ -431,3 +438,16 @@ function initClearPanesButton() { clearAllPanes(); }); } + +/* ========================================================= + PANE EXIT ANIMATION (keyframe injected at runtime) + ========================================================= */ + +const style = document.createElement("style"); +style.textContent = ` + @keyframes pane-out { + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(16px); } + } +`; +document.head.appendChild(style); diff --git a/assets/styles/style.css b/assets/styles/style.css index 696ddd7..d13651e 100755 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -2,13 +2,15 @@ TOKENS / CUSTOM PROPERTIES ========================================================= */ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&family=DM+Mono:wght@400;500&family=Lora:ital@0;1&display=swap'); + :root { --gutter: 2rem; --margin: 420px; - --body-pad: 1rem; + --body-pad: 1.25rem; - --content-min: 60ch; - --content-max: 880px; + --content-min: 56ch; + --content-max: 820px; --content: clamp( var(--content-min), calc(100vi - (2 * var(--body-pad)) - (2 * (var(--margin) + var(--gutter)))), @@ -18,243 +20,401 @@ --bleed: 48px; --fullwidth-cap: 860px; - --bg: #333; - --page-bg: #444; - --fg: #f3f3f3; + /* Core palette — warm dark */ + --bg: #1c1a17; + --page-bg: #141210; + --pane-bg: #1e1c19; + --fg: #e8e2d9; + --fg-dim: #a09890; + --fg-faint: #5a534a; - --heading: #9ecbff; - --heading-2: #FFBB87; - --heading-3: #FF6F61; - --link: lightblue; + /* Accent — warm amber */ + --accent: #c8964a; + --accent-dim: color-mix(in oklab, var(--accent) 40%, transparent); + --accent-subtle: color-mix(in oklab, var(--accent) 10%, transparent); + + /* Headings */ + --heading: #e8dcc8; + --heading-2: #c8a878; + --heading-3: #a88858; + + /* Links */ + --link: #b8a888; --link-2: var(--link); - --code-bg: #444; + /* Surfaces */ + --code-bg: #161412; + --border: #2e2a24; + --border-mid: #3a342c; - --border: #d7d7d7; - --active-toc: #cacaca; + /* Misc */ + --muted: var(--fg-dim); + --note-color: #5a534a; + --note-bg: transparent; + --chip-bg: #2a2620; + --chip-fg: var(--fg-dim); - --muted: #666; - --note-color: #555; - --note-bg: transparent; - - --chip-bg: #f0f0f0; - --chip-fg: #444; - - /* Compatibility aliases (you reference these later) */ - --border-color: var(--border); - --text-color: var(--fg); - --muted-text: var(--muted); - --link-color: var(--link); + /* Aliases */ + --border-color: var(--border); + --text-color: var(--fg); + --muted-text: var(--muted); + --link-color: var(--link); --link-hover-color: var(--fg); - --bg-alt: #2a2a2a; + --bg-alt: #161412; + + /* Spacing rhythm */ + --r1: 0.25rem; + --r2: 0.5rem; + --r3: 0.75rem; + --r4: 1rem; + --r5: 1.5rem; + --r6: 2rem; + --r8: 3rem; + + /* Typography */ + --font-body: 'DM Sans', system-ui, sans-serif; + --font-mono: 'DM Mono', 'Fira Mono', monospace; + --font-serif: 'Lora', Georgia, serif; + + /* Motion */ + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --dur-fast: 120ms; + --dur-mid: 220ms; + --dur-slow: 380ms; } /* ========================================================= - BASE / TYPOGRAPHY + LIGHT THEME (optional override) ========================================================= */ -html, -body { - margin: 0; - background-color: var(--page-bg); - color: var(--fg); - transition: background-color 0.3s, color 0.3s; - font-family: Inter, sans-serif; - overflow-x: hidden; - padding-left: 1px; +[data-theme="light"] { + --bg: #f5f1eb; + --page-bg: #ede9e2; + --pane-bg: #f8f5f0; + --fg: #2a2520; + --fg-dim: #6a6058; + --fg-faint: #b0a898; + --accent: #a07030; + --border: #ddd8ce; + --border-mid:#ccc6bb; + --code-bg: #edeae4; + --bg-alt: #ede9e2; + --link: #7a5c30; + --heading: #2a2520; + --heading-2: #6a4820; + --heading-3: #8a5c30; + --chip-bg: #e5e0d8; + --chip-fg: #6a6058; } -.tag { - background-color: transparent !important; - color: var(--link); +/* ========================================================= + RESET / BASE + ========================================================= */ + +*, *::before, *::after { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + background-color: var(--page-bg); + color: var(--fg); + font-family: var(--font-body); + font-size: 16px; + line-height: 1.7; + overflow-x: hidden; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transition: background-color var(--dur-slow), color var(--dur-slow); } body { display: flex; flex-direction: column; + min-height: 100dvh; } +/* ========================================================= + TYPOGRAPHY + ========================================================= */ + h1 { + font-size: 1.65rem; + font-weight: 500; + line-height: 1.25; + letter-spacing: -0.02em; color: var(--heading); -} -h2 { - color: var(--heading); -} -h3 { - color: var(--heading-2); + margin: 0 0 var(--r5); } +h2 { + font-size: 1.25rem; + font-weight: 500; + letter-spacing: -0.01em; + color: var(--heading); + margin: var(--r8) 0 var(--r4); + padding-bottom: var(--r3); + border-bottom: 1px solid var(--border); +} + +h3 { + font-size: 1.05rem; + font-weight: 500; + color: var(--heading-2); + margin: var(--r6) 0 var(--r3); +} + +h4 { + font-size: 0.95rem; + font-weight: 500; + color: var(--heading-3); + margin: var(--r5) 0 var(--r2); +} + +p { + margin: 0 0 var(--r4); + font-weight: 300; +} a { - color: var(--link-2); + color: var(--link); text-decoration: none; + border-bottom: 1px solid transparent; + transition: color var(--dur-fast), border-color var(--dur-fast); } a:hover { - text-decoration: underline; + color: var(--fg); + border-bottom-color: var(--fg-faint); + text-decoration: none; } +strong { + font-weight: 500; + color: var(--fg); +} + +em { + font-style: italic; + font-family: var(--font-serif); +} + +.tag { + background-color: var(--chip-bg) !important; + color: var(--accent) !important; + font-family: var(--font-mono); + font-size: 0.72rem; + padding: 0.15em 0.5em; + border-radius: 3px; + letter-spacing: 0.04em; + border: 1px solid var(--border-mid); +} + +/* ========================================================= + BANNER / PREAMBLE + ========================================================= */ + #preamble { + position: sticky; top: 0; - z-index: 20; + z-index: 100; background: var(--bg); - border-bottom: 1px dotted var(--border); -} - -#preamble .banner-header, -#preamble #updated { - max-width: 100%; + border-bottom: 1px solid var(--border); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } .banner-header { - position: relative; display: flex; - justify-content: flex-start; align-items: center; - gap: 1rem; - - padding: 0.5rem 1rem; + gap: var(--r5); + padding: 0.6rem var(--r5); + max-width: 100%; + position: relative; } .banner-left { display: flex; - flex-direction: column; align-items: center; - gap: 0.5rem; + gap: var(--r4); + flex-shrink: 0; } .banner-logo { - height: 80px; - width: auto; + height: 38px; + width: 38px; border-radius: 50%; + object-fit: cover; + opacity: 0.92; + transition: opacity var(--dur-fast); +} + +.banner-logo:hover { + opacity: 1; +} + +#updated { + font-size: 0.7rem; + font-family: var(--font-mono); + color: var(--fg-faint); + white-space: nowrap; + letter-spacing: 0.03em; } nav { display: flex; - gap: 1rem; - font-weight: 600; - font-size: 1.1rem; + gap: var(--r4); + font-size: 0.9rem; + font-weight: 400; } -#updated { - font-size: 0.75rem; - color: color-mix(in oklab, var(--muted) 30%, var(--fg) 70%); - white-space: nowrap; - text-align: center; +nav a { + color: var(--fg-dim); + border-bottom: none; + padding: var(--r1) 0; + position: relative; +} + +nav a::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background: var(--accent); + transition: width var(--dur-mid) var(--ease-out); +} + +nav a:hover { + color: var(--fg); +} + +nav a:hover::after { + width: 100%; +} + +.banner-search { + flex: 1; + max-width: 26rem; + position: relative; +} + +#search-box { + width: 100%; + padding: 0.45rem 0.8rem; + font-size: 0.875rem; + font-family: var(--font-body); + font-weight: 300; + background-color: var(--bg-alt); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + transition: border-color var(--dur-fast), box-shadow var(--dur-fast); + outline: none; +} + +#search-box:focus { + border-color: var(--accent-dim); + box-shadow: 0 0 0 3px var(--accent-subtle); +} + +#search-box::placeholder { + color: var(--fg-faint); + font-style: italic; +} + +#search-results { + position: absolute; + top: calc(100% + 0.4rem); + left: 0; + right: 0; + background: var(--bg); + border: 1px solid var(--border-mid); + border-radius: 6px; + box-shadow: 0 12px 40px rgba(0,0,0,0.4); + max-height: 20rem; + overflow-y: auto; + z-index: 1000; +} + +#search-results > * { + padding: 0.5rem 0.8rem; + font-size: 0.875rem; + line-height: 1.4; + cursor: pointer; + border-bottom: 1px solid var(--border); + transition: background var(--dur-fast); +} + +#search-results > *:last-child { border-bottom: none; } +#search-results > *:hover, +#search-results > *.active { + background: var(--accent-subtle); + color: var(--fg); +} + +.web-logo { + height: auto; + width: 22%; + max-width: 80px; + opacity: 0.7; + transition: opacity var(--dur-fast); + flex-shrink: 0; +} + +.web-logo:hover { opacity: 1; } + +.banner-actions { + display: flex; + align-items: center; + gap: var(--r2); + margin-left: auto; + flex-shrink: 0; +} + +#theme-toggle { + background: transparent; + border: 1px solid var(--border); + border-radius: 5px; + width: 32px; + height: 32px; + font-size: 0.95rem; + cursor: pointer; + color: var(--fg-dim); + display: inline-flex; + align-items: center; + justify-content: center; + transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast); + line-height: 1; +} + +#theme-toggle:hover { + color: var(--fg); + border-color: var(--border-mid); + background: var(--chip-bg); } #close-all { - position: absolute; - right: 1rem; - top: 50%; - transform: translateY(-50%); - background: transparent; border: 1px solid var(--border); - border-radius: 4px; - - padding: 0.25rem 0.5rem; /* smaller so it doesn't dominate */ - font-size: 0.8rem; - color: color-mix(in oklab, var(--muted) 40%, var(--fg) 60%); + border-radius: 5px; + padding: 0.3rem 0.7rem; + font-size: 0.75rem; + font-family: var(--font-mono); + color: var(--fg-faint); cursor: pointer; + letter-spacing: 0.04em; + transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast); + white-space: nowrap; } #close-all:hover { color: var(--fg); - border-color: var(--fg); -} - -.banner-header > a { - display: flex; - align-items: center; -} - - -@media (max-width: 768px) { - .banner-header { - flex-direction: column; - gap: 0.75rem; - text-align: center; - align-items: center; - } - - .banner-left { - margin: 0 auto; - } - - .banner-logo { - margin: 0; - } - - #close-all { - position: static; - transform: none; - } -} - - -/* ========================================================= - CONTENT WRAPPER - ========================================================= */ - -#content.content { - max-width: var(--content); - margin-left: auto !important; - margin-right: auto !important; - padding-left: var(--body-pad); - padding-right: var(--body-pad); - box-sizing: content-box; - position: relative; -} - -/* ========================================================= - TITLE SECTION - ========================================================= */ - -.title-section { - border: 1px solid var(--border); - border-radius: 6px; - padding: 1.5rem; - padding-right: 8rem; /* Extra padding on right for controls */ - margin-bottom: 2rem; - background-color: color-mix(in oklab, var(--bg) 96%, var(--fg) 4%); - position: relative; /* For absolute positioning of controls */ -} - -.title-section .title { - margin: 0 0 1rem 0; - padding-bottom: 1rem; - border-bottom: 1px dotted var(--border); -} - -.title-metadata { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - font-size: 0.9rem; -} - -.metadata-item { - display: flex; - align-items: baseline; - gap: 0.5rem; -} - -.metadata-label { - color: var(--muted); - font-style: italic; - font-weight: 500; -} - -.metadata-value { - color: var(--fg); - font-weight: 400; -} - -#content .figure, -#content img:not(.fullwidth) { - max-width: 100%; - height: auto; + border-color: var(--border-mid); + background: var(--chip-bg); } /* ========================================================= @@ -264,11 +424,19 @@ nav { #stack-root { position: relative; width: 100vw; - height: calc(100vh - 120px); + height: calc(100dvh - 60px); overflow-x: auto; overflow-y: hidden; flex: 1 1 auto; scroll-snap-type: x proximity; + scrollbar-width: thin; + scrollbar-color: var(--border-mid) transparent; +} + +#stack-root::-webkit-scrollbar { height: 5px; } +#stack-root::-webkit-scrollbar-thumb { + background: var(--border-mid); + border-radius: 3px; } .stack-track { @@ -281,149 +449,197 @@ nav { .stack-pane { flex: 0 0 auto; - - width: clamp(420px, 33vw, 860px); + width: clamp(400px, 34vw, 800px); max-width: 100vw; - height: 100%; overflow-y: auto; overflow-x: hidden; - - position: relative; /* needed for ::after positioning */ - background-color: var(--bg); - border-right: 1px dotted var(--border); + background-color: var(--pane-bg); + border-right: 1px solid var(--border); + position: relative; + scroll-snap-align: start; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; + /* Smooth entrance */ + animation: pane-in var(--dur-slow) var(--ease-out); } +@keyframes pane-in { + from { opacity: 0; transform: translateX(24px); } + to { opacity: 1; transform: translateX(0); } +} + +.stack-pane::-webkit-scrollbar { width: 5px; } +.stack-pane::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +/* Depth shadow on last pane */ +.stack-pane:last-child { + box-shadow: -8px 0 32px rgba(0,0,0,0.25); +} + +/* Subtle vertical separator gradient */ .stack-pane::after { - content: ""; + content: ''; position: absolute; top: 0; right: 0; - width: 12px; + width: 1px; height: 100%; + background: linear-gradient( + to bottom, + transparent, + var(--border-mid) 20%, + var(--border-mid) 80%, + transparent + ); pointer-events: none; - opacity: 0.15; -} - -.stack-pane:last-child { - box-shadow: -4px 0 16px color-mix(in oklab, var(--fg) 6%, transparent); -} - -/* Scrollbar (WebKit) */ -.stack-pane::-webkit-scrollbar { - width: 8px; -} - -.stack-pane::-webkit-scrollbar-thumb { - background-color: color-mix(in oklab, var(--fg) 25%, transparent); - border-radius: 4px; } /* ========================================================= - PANE HEADER + CLOSE BUTTON + CONTENT WRAPPER ========================================================= */ -.pane-root { - background-color: var(--bg); -} -.pane-root .pane-close { - display: none !important; +#content.content { + max-width: var(--content); + margin-left: auto !important; + margin-right: auto !important; + padding: var(--r6) var(--r5); + box-sizing: content-box; + position: relative; } -.pane-header { - display: none; /* Hide the old pane-header */ +/* ========================================================= + TITLE SECTION + ========================================================= */ + +.title-section { + position: relative; + margin-bottom: var(--r6); + padding: var(--r5) var(--r5) var(--r4); + padding-right: 7rem; + background: color-mix(in oklab, var(--bg) 50%, var(--pane-bg) 50%); + border: 1px solid var(--border); + border-radius: 8px; } -/* Title section controls */ +.title-section .title { + margin: 0 0 var(--r4); + padding-bottom: var(--r4); + border-bottom: 1px solid var(--border); + font-size: 1.5rem; +} + +.title-metadata { + display: flex; + flex-wrap: wrap; + gap: var(--r4); +} + +.metadata-item { + display: flex; + align-items: baseline; + gap: var(--r2); + font-size: 0.75rem; + font-family: var(--font-mono); + letter-spacing: 0.03em; +} + +.metadata-label { + color: var(--fg-faint); + font-style: normal; +} + +.metadata-value { + color: var(--accent); +} + +/* ========================================================= + TITLE CONTROLS + ========================================================= */ + .title-controls { position: absolute; - top: 1.5rem; - right: 1.5rem; + top: var(--r4); + right: var(--r4); display: flex; - gap: 0.5rem; + gap: var(--r2); z-index: 10; } .title-controls button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; background: none; border: 1px solid var(--border); - border-radius: 4px; - font-size: 0.9rem; - line-height: 1; + border-radius: 5px; + font-size: 0.8rem; + font-family: var(--font-mono); cursor: pointer; - color: var(--muted); - padding: 0.3rem 0.6rem; - transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease; + color: var(--fg-faint); + transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast); + line-height: 1; } .title-controls button:hover { color: var(--fg); - border-color: var(--fg); - background-color: color-mix(in oklab, var(--bg) 90%, var(--fg) 10%); + border-color: var(--border-mid); + background: var(--chip-bg); } .pane-close { - font-size: 1.2rem; + font-size: 1rem !important; } -.pane-fullscreen, -.pane-edit { - font-weight: 500; +/* Hide close on root pane */ +.pane-root .pane-close { + display: none !important; } +/* Hide old pane-header */ +.pane-header { display: none; } + /* ========================================================= - FOOTER + IMAGES ========================================================= */ -footer { - color: var(--fg); - padding: 1rem; - border-radius: 6px; - text-align: center; - font-size: 0.9rem; - font-style: italic; - - flex-shrink: 0; - margin-top: 0; - - border-top: 1px dotted var(--border); - background: var(--bg); - - /* If you want it sticky later, use: - position: sticky; - bottom: 0; - */ - bottom: 0; - z-index: 10; +#content .figure, +#content img:not(.fullwidth) { + max-width: 100%; + height: auto; + border-radius: 4px; } /* ========================================================= LISTS ========================================================= */ -ul, -ol { - margin: 1rem 0 1.5rem 1.5rem; +ul, ol { + margin: var(--r3) 0 var(--r5) var(--r4); padding: 0; - line-height: 1.5; + line-height: 1.7; } -ul { - list-style: none; -} +ul { list-style: none; } ul li { position: relative; - padding-left: 1.2em; + padding-left: 1.4em; } ul li::before { - content: "•"; + content: "·"; position: absolute; left: 0; top: 0; - color: var(--heading); - font-weight: bold; + color: var(--accent); + font-weight: 700; + font-size: 1.1em; } ol { @@ -434,126 +650,225 @@ ol { ol li { counter-increment: list-counter; position: relative; - padding-left: 1.8em; + padding-left: 2em; } ol li::before { - content: counter(list-counter) "."; + content: counter(list-counter); position: absolute; left: 0; - top: 0; - color: var(--heading); - font-weight: bold; + top: 0.05em; + font-family: var(--font-mono); + font-size: 0.78em; + color: var(--accent); + font-weight: 500; + letter-spacing: 0.04em; + opacity: 0.7; } li { - margin-bottom: 8px; + margin-bottom: 0.45rem; display: flow-root; } -li::after { - content: none; -} +li::after { content: none; } -li ul, -li ol { - margin-top: 0.5rem; - margin-bottom: 0.5rem; +li ul, li ol { + margin-top: var(--r2); + margin-bottom: var(--r2); } li ul li::before { content: "–"; font-weight: normal; - color: var(--note-color); -} - -li ol li::before { - font-weight: normal; - color: var(--note-color); + color: var(--fg-faint); + font-size: 1em; } /* ========================================================= - EPIGRAPH + BLOCKQUOTE / EPIGRAPH ========================================================= */ -.epigraph { - margin: 2rem auto; - max-width: 80%; +blockquote { + margin: var(--r5) 0; + padding: var(--r4) var(--r5); + border-left: 2px solid var(--accent); + background: var(--accent-subtle); + border-radius: 0 6px 6px 0; + color: var(--fg-dim); + font-family: var(--font-serif); font-style: italic; + line-height: 1.75; +} + +blockquote p { margin: 0; } + +.epigraph { + margin: var(--r6) auto; + max-width: 82%; } .epigraph blockquote { margin: 0; - padding: 1rem 1.5rem; - border-left: 4px dotted var(--heading); - background-color: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%); - color: var(--fg); - line-height: 1.6; + padding: var(--r4) var(--r5); + border-left: 2px solid var(--accent-dim); } .epigraph blockquote footer { - margin-top: 0.75rem; + margin-top: var(--r3); font-style: normal; - font-size: 0.9em; - color: var(--muted); + font-size: 0.85em; + color: var(--fg-faint); text-align: right; + font-family: var(--font-mono); } .epigraph blockquote cite { font-style: italic; - font-weight: 500; - color: var(--link); -} - -.epigraph blockquote::before, -.epigraph blockquote::after { - content: none; + color: var(--accent); } /* ========================================================= - UTILITIES / MISC + CODE ========================================================= */ -.hidden { - display: none !important; +:not(pre) > code { + padding: 0.15em 0.45em; + margin: 0 0.1em; + font-family: var(--font-mono); + font-size: 0.8em; + background: var(--code-bg); + color: var(--heading-2); + border: 1px solid var(--border-mid); + border-radius: 4px; } -/* bigger-picture.js controls */ -.bp-x { - right: 72px; -} -.bp-next, -.bp-prev { - right: 8px; -} -.bp-prev { - left: 8px; -} -.bp-wrap { - transition: opacity 0.18s ease; -} -.bp-wrap.bp-fadeout { - opacity: 0; -} -.bp-controls { - padding-top: env(safe-area-inset-top, 0); - padding-right: calc(env(safe-area-inset-right, 0) + 8px); +pre { + background: var(--code-bg); } -/* code copy button */ +.org-src-container { + border: 1px solid var(--border); + border-radius: 8px; + font-family: var(--font-mono); + font-size: 0.82rem; + margin: var(--r5) auto; + overflow: hidden; + position: relative; +} + +.org-src-container > pre { + overflow: auto; + margin: 0; + padding: var(--r5) var(--r4); +} + +.org-src-container > pre:before { + display: block; + position: sticky; + top: 0; + background: color-mix(in oklab, var(--code-bg) 80%, var(--border) 20%); + color: var(--fg-faint); + padding: 0.35rem var(--r4); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; + border-bottom: 1px solid var(--border); +} + +.org-src-container > pre.src-bash:before { content: "bash"; } +.org-src-container > pre.src-sh:before { content: "sh"; } +.org-src-container > pre.src-shell:before { content: "shell"; } +.org-src-container > pre.src-python:before { content: "python"; } +.org-src-container > pre.src-emacs-lisp:before { content: "emacs lisp"; } +.org-src-container > pre.src-js:before, +.org-src-container > pre.src-javascript:before { content: "javascript"; } +.org-src-container > pre.src-typescript:before { content: "typescript"; } +.org-src-container > pre.src-html:before { content: "html"; } +.org-src-container > pre.src-css:before { content: "css"; } +.org-src-container > pre.src-c:before { content: "c"; } +.org-src-container > pre.src-cpp:before { content: "c++"; } +.org-src-container > pre.src-java:before { content: "java"; } +.org-src-container > pre.src-rust:before { content: "rust"; } +.org-src-container > pre.src-R:before { content: "R"; } + +pre.src::before { content: none !important; } +pre.src { + padding: var(--r5) var(--r4) var(--r4); + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1.55; + overflow-x: auto; + white-space: pre-wrap; + background: var(--code-bg); +} + +/* Copy button */ .copy-btn { position: absolute; - top: 0.4em; - right: 0.4em; - background-color: var(--code-bg); - color: var(--heading); - border: 1px dotted var(--heading); + top: 0.5rem; + right: 0.5rem; + background: var(--chip-bg); + color: var(--fg-faint); + border: 1px solid var(--border-mid); border-radius: 4px; padding: 0.2em 0.6em; - font-size: 0.8rem; + font-size: 0.72rem; + font-family: var(--font-mono); + letter-spacing: 0.04em; cursor: pointer; z-index: 10; - transition: background-color 0.3s; + transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast); +} + +.copy-btn:hover { + color: var(--fg); + border-color: var(--accent-dim); + background: var(--accent-subtle); +} + +/* ========================================================= + TABLE OF CONTENTS + ========================================================= */ + +#table-of-contents { + margin-bottom: var(--r6); +} + +#text-table-of-contents ul { + margin: var(--r2) 0; +} + +#text-table-of-contents a { + font-size: 0.875rem; + color: var(--fg-dim); + border-bottom: none; + transition: color var(--dur-fast); +} + +#text-table-of-contents a:hover { + color: var(--fg); + border-bottom: none; +} + +#text-table-of-contents a.is-active { + color: var(--accent); +} + +#text-table-of-contents a[aria-current] { + color: var(--accent); +} + +/* ========================================================= + SIDENOTES / FOOTNOTES + ========================================================= */ + +.sidenote { + font-size: 0.78rem; + line-height: 1.5; + color: var(--fg-dim); + font-family: var(--font-serif); + font-style: italic; } /* ========================================================= @@ -561,132 +876,147 @@ li ol li::before { ========================================================= */ .backlinks-section { - margin-top: 4rem; - padding-top: 1.5rem; - border-top: 1px dotted var(--border); - opacity: 0.95; - color: var(--muted); + margin-top: var(--r8); + padding-top: var(--r5); + border-top: 1px solid var(--border); + opacity: 0.9; +} + +.backlinks-section h2 { + font-size: 0.75rem; + font-family: var(--font-mono); + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--fg-faint); + border-bottom: none; + margin-top: 0; + padding-bottom: 0; } .backlinks-list { - margin-top: 0.75rem; + margin-top: var(--r3); padding-left: 0; } .backlinks-list li { - margin-bottom: 0.4rem; - font-size: 0.9rem; + margin-bottom: var(--r2); + font-size: 0.875rem; list-style: none; + padding-left: 0; } .backlinks-list li::before { content: "↩ "; - opacity: 0.5; - margin-right: 0.2rem; + font-size: 0.8em; + opacity: 0.4; + margin-right: var(--r1); + color: var(--accent); } .backlinks-list li a { - text-decoration: none; - font-weight: 500; color: var(--link); - border-bottom: 1px dotted color-mix(in oklab, var(--fg) 20%, transparent); - padding-bottom: 0.05em; - transition: color 0.15s ease, border-color 0.15s ease; + border-bottom: 1px solid transparent; + font-weight: 400; + transition: color var(--dur-fast), border-color var(--dur-fast); } -.backlinks-list li a:hover, -.backlinks-list li a:focus { +.backlinks-list li a:hover { color: var(--fg); - border-bottom-color: currentColor; -} - -.web-logo { - height: auto; - width: 30%; - position: relative; + border-bottom-color: var(--fg-faint); } /* ========================================================= - SEARCH + FOOTER ========================================================= */ -.banner-search { - position: relative; - max-width: 28rem; +footer { + color: var(--fg-faint); + padding: var(--r4) var(--r5); + border-top: 1px solid var(--border); + background: var(--bg); + text-align: center; + font-size: 0.78rem; + font-family: var(--font-mono); + letter-spacing: 0.03em; + flex-shrink: 0; + margin-top: 0; } -#search-box { - width: 100%; - padding: 0.55rem 0.75rem; - font-size: 0.95rem; - font-family: inherit; +footer a { color: var(--fg-faint); } +footer a:hover { color: var(--fg); } - background-color: var(--bg-alt); - color: var(--fg); +/* ========================================================= + FULLSCREEN PANE MODE + ========================================================= */ - border: 1px solid var(--border); - border-radius: 4px; +body.pane-fullscreen #stack-root { overflow: hidden; } - transition: border-color 0.15s ease, box-shadow 0.15s ease, - background-color 0.15s ease; +body.pane-fullscreen .stack-track { width: 100%; } + +body.pane-fullscreen .stack-pane { + width: 100% !important; + max-width: none; + border-right: none; } -#search-box:focus { - outline: none; - background-color: var(--bg); - border-color: var(--link); - box-shadow: 0 0 0 2px color-mix(in oklab, var(--link) 20%, transparent); +body.pane-fullscreen .stack-pane::after { display: none; } + +body.pane-fullscreen .stack-pane:not(.is-fullscreen) { display: none; } + +body.pane-fullscreen #content.content { max-width: 860px; } + +/* ========================================================= + BIGGER-PICTURE GALLERY + ========================================================= */ + +.bp-x { right: 72px; } +.bp-next, .bp-prev { right: 8px; } +.bp-prev { left: 8px; } +.bp-wrap { transition: opacity 0.18s ease; } +.bp-wrap.bp-fadeout { opacity: 0; } +.bp-controls { + padding-top: env(safe-area-inset-top, 0); + padding-right: calc(env(safe-area-inset-right, 0) + 8px); } -#search-box::placeholder { - color: color-mix(in oklab, var(--muted) 30%, var(--fg) 70%); - font-style: italic; -} +/* ========================================================= + UTILITIES + ========================================================= */ -#search-results { - position: absolute; - top: calc(100% + 0.3rem); - left: 0; - right: 0; - - background-color: var(--bg); - border: 1px dotted var(--border); - border-radius: 4px; - - box-shadow: 0 8px 24px color-mix(in oklab, var(--fg) 8%, transparent); - - max-height: 18rem; - overflow-y: auto; - - z-index: 1000; -} - -#search-results > * { - padding: 0.45rem 0.65rem; - font-size: 0.9rem; - line-height: 1.3; - - cursor: pointer; - border-bottom: 1px dotted color-mix(in oklab, var(--fg) 5%, transparent); -} - -#search-results > *:last-child { - border-bottom: none; -} - -#search-results > *:hover, -#search-results > *.active { - background-color: color-mix(in oklab, var(--link) 12%, transparent); -} +.hidden { display: none !important; } /* ========================================================= RESPONSIVE ========================================================= */ @media (max-width: 768px) { + .banner-header { + flex-wrap: wrap; + gap: var(--r3); + padding: var(--r3) var(--r4); + } + + .banner-left { + flex-direction: row; + gap: var(--r3); + } + + .banner-search { + max-width: 100%; + width: 100%; + order: 3; + } + + .web-logo { display: none; } + + .banner-actions { + margin-left: 0; + } + #stack-root { overflow-x: hidden; overflow-y: auto; + height: auto; } .stack-track { @@ -698,99 +1028,18 @@ li ol li::before { width: 100%; height: auto; border-right: none; - border-bottom: 1px dotted var(--border); + border-bottom: 1px solid var(--border); } - .pane-fullscreen { + .pane-fullscreen { display: none !important; } - display: none !important; - } -} -/* ========================================================= - FULLSCREEN PANE MODE - ========================================================= */ + .title-section { + padding-right: var(--r5); + } -body.pane-fullscreen #stack-root { - overflow: hidden; -} - -body.pane-fullscreen .stack-track { - width: 100%; -} - -body.pane-fullscreen .stack-pane { - width: 100% !important; - max-width: none; - border-right: none; -} - -body.pane-fullscreen .stack-pane::after { - display: none; -} - -/* Only the active fullscreen pane remains */ -body.pane-fullscreen .stack-pane:not(.is-fullscreen) { - display: none; -} - -/* Optional: make content breathe more in fullscreen */ -body.pane-fullscreen #content.content { - max-width: 900px; -} -:not(pre) > code{ - padding: 2px 5px; margin: 0 1px; - border: 1px solid #ddd; border-radius: 3px; - background-clip: padding-box; - color: #333; font-size: 80%; -} -pre { - background-color: var(--bg); -} - -.org-src-container{ - border: 1px solid #ccc; - box-shadow: 3px 3px 3px #eee; - font-family: Lucida Console, monospace; - font-size: 80%; - margin: 1em auto; - padding: .1em .5em; - position: relative; -} -.org-src-container > pre{ overflow: auto; } - -.org-src-container > pre:before{ - display: block; position: absolute; top: 0; right: 0; - background-color: #b3b3b3; color: #fff; - padding: 0 .5em; border-bottom-left-radius: 8px; border: 0; - font-size: 80%; -} -.org-src-container > pre.src-bash:before { content: "bash"; } -.org-src-container > pre.src-sh:before { content: "sh"; } -.org-src-container > pre.src-shell:before { content: "shell"; } -.org-src-container > pre.src-python:before { content: "Python"; } -.org-src-container > pre.src-emacs-lisp:before { content: "Emacs Lisp"; } -.org-src-container > pre.src-js:before, -.org-src-container > pre.src-javascript:before { content: "Javascript"; } -.org-src-container > pre.src-typescript:before { content: "Typescript"; } -.org-src-container > pre.src-html:before { content: "HTML"; } -.org-src-container > pre.src-css:before { content: "CSS"; } -.org-src-container > pre.src-c:before { content: "C"; } -.org-src-container > pre.src-cpp:before { content: "C++"; } -.org-src-container > pre.src-java:before { content: "Java"; } -.org-src-container > pre.src-rust:before { content: "Rust"; } -.org-src-container > pre.src-R:before { content: "R"; } - -pre.src::before{ content: none !important; } -pre.src{ - padding: 1.5em 1em 1em; - font-family: "Fira Mono","Courier New",monospace; - font-size: .9rem; line-height: 1.4; - overflow-x: auto; white-space: pre-wrap; -} - - -:not(pre) > code{ - border: 1px solid var(--border); - background-color: var(--code-bg); - color: var(--fg); + .title-controls { + position: static; + margin-bottom: var(--r3); + justify-content: flex-end; + } } diff --git a/lisp/build.el b/lisp/build.el index 8fc5de8..be5f151 100755 --- a/lisp/build.el +++ b/lisp/build.el @@ -96,9 +96,7 @@ html nil t)) - ;; Wrap title with metadata section - use lambda to properly capture title - ;; Handle titles with nested HTML tags by matching everything between opening and closing h1 tags - ;; Use .* to match any characters (greedy) - will match up to the last which should be our closing tag + ;; Wrap title with metadata section — edit button removed (setq html (replace-regexp-in-string "