From 5be13d63b56ffca52abcdf88c7e42ed09872d3d0 Mon Sep 17 00:00:00 2001 From: adrhill Date: Sat, 15 Aug 2026 14:03:59 +0200 Subject: [PATCH 01/11] feat: redesign the site with true light and dark themes - consolidate the poole_hyde/franklin/custom/search/ansi stylesheets into token-driven css/site.css and css/code.css - add a theme toggle (system preference default, persisted override, no-flash inline script); switch Zola highlighting to class-based giallo with github-light/github-dark and per-theme ANSI palettes - restyle the sidebar as a theme-following panel; replace the stacked mobile layout with a top bar and slide-in drawer - restyle admonitions with tinted headers in the four Julia brand colors and add a blue 'note' type Co-Authored-By: Claude Fable 5 --- CONTRIBUTING.md | 3 +- static/css/ansi.css | 96 ----- static/css/code.css | 200 +++++++++ static/css/custom.css | 41 -- static/css/franklin.css | 188 --------- static/css/poole_hyde.css | 391 ------------------ static/css/search.css | 129 ------ static/css/site.css | 713 ++++++++++++++++++++++++++++++++ static/js/search.js | 5 +- static/js/ui.js | 45 ++ templates/base.html | 64 ++- templates/components.html | 39 +- templates/partials/sidebar.html | 64 +-- zola.toml | 8 +- 14 files changed, 1075 insertions(+), 911 deletions(-) delete mode 100644 static/css/ansi.css create mode 100644 static/css/code.css delete mode 100644 static/css/custom.css delete mode 100644 static/css/franklin.css delete mode 100644 static/css/poole_hyde.css delete mode 100644 static/css/search.css create mode 100644 static/css/site.css create mode 100644 static/js/ui.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0855327..bda3eee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,7 +75,8 @@ Two conventions replace explicit setup code: ## Admonitions -Callout boxes use the components defined in [`templates/components.html`](templates/components.html): `tldr`, `advanced`, and `vscode`. +Callout boxes use the components defined in [`templates/components.html`](templates/components.html): `tldr`, `advanced`, `vscode`, and `note`. +Each maps to one of the four Julia brand colors. The body is regular Markdown: ```markdown diff --git a/static/css/ansi.css b/static/css/ansi.css deleted file mode 100644 index fa35e33..0000000 --- a/static/css/ansi.css +++ /dev/null @@ -1,96 +0,0 @@ -/* ================================================================== - CODE BLOCK CHROME (Zola/giallo replaces highlight.js) - - Zola emits highlighted fences as
-    with inline colors from the theme in zola.toml. The executed REPL
-    fences arrive from MoJuWoPreprocessor as 
-    (REPL modes) or 
 (`!` fence output) and are
-    colored by the .sgrNN palette below.
-================================================================== */
-
-pre {
-  position: relative;
-  font-size: var(--small);
-  line-height: 1.35em;
-  border-radius: 10px;
-  padding: 1em;
-  overflow-x: auto;
-}
-
-/* franklin.css gives inline `code` a light background; undo it inside blocks. */
-pre code {
-  background: transparent;
-  padding: 0;
-  font-size: inherit;
-}
-
-/* Language label chips, like the highlight.js setup had. */
-pre[data-lang]::before,
-pre.julia-repl.ansi::before {
-  position: absolute;
-  top: 0;
-  left: 0;
-  padding: 0.2rem 0.5rem 0.1rem;
-  background-color: #27303c; /* brighter than the code background */
-  color: #adbac7;
-  border-bottom-right-radius: 10px;
-  border-top-left-radius: 10px;
-  font-size: 0.7rem;
-  font-weight: 700;
-  text-transform: uppercase;
-}
-
-/* Reserve room for the chip so it doesn't overlap the first code line. */
-pre[data-lang]:not([data-lang="plain"]),
-pre.julia-repl.ansi {
-  padding-top: 2.4em;
-}
-
-pre[data-lang]::before { content: attr(data-lang); }
-/* Giallo reports canonical grammar names; keep the labels the site used. */
-pre[data-lang="shellscript"]::before { content: "Shell"; }
-/* Fences without a recognized language get no chip. */
-pre[data-lang="plain"]::before { content: none; }
-pre.julia-repl.ansi::before { content: "Julia REPL"; }
-
-/* ==================================================================
-    ANSI COLORS (ANSIColoredPrinters.jl .sgrNN spans)
-
-    REPL blocks are dark like the github-dark highlighting theme; the
-    palette is the VS Code dark terminal palette. The current Franklin
-    site ships no CSS for .sgr spans at all, so any styling here is an
-    improvement.
-================================================================== */
-
-pre.julia-repl.ansi {
-  background-color: #24292e; /* match giallo's github-dark background */
-  color: #e1e4e8;
-}
-
-/* `!` fence output stays light, like Franklin's .code-output blocks. */
-pre.code-output.ansi {
-  color: #24292e;
-}
-
-.ansi .sgr1 { font-weight: bold; }
-.ansi .sgr2 { opacity: 0.8; }
-.ansi .sgr3 { font-style: italic; }
-.ansi .sgr4 { text-decoration: underline; }
-.ansi .sgr9 { text-decoration: line-through; }
-
-.ansi .sgr30 { color: #666666; }
-.ansi .sgr31 { color: #cd3131; }
-.ansi .sgr32 { color: #0dbc79; }
-.ansi .sgr33 { color: #e5c516; }
-.ansi .sgr34 { color: #4886d0; }
-.ansi .sgr35 { color: #bc3fbc; }
-.ansi .sgr36 { color: #11a8cd; }
-.ansi .sgr37 { color: #e5e5e5; }
-.ansi .sgr90 { color: #8b949e; }
-.ansi .sgr91 { color: #f14c4c; }
-.ansi .sgr92 { color: #23d18b; }
-.ansi .sgr93 { color: #d7ba21; }
-.ansi .sgr94 { color: #3b8eea; }
-.ansi .sgr95 { color: #d670d6; }
-.ansi .sgr96 { color: #29b8db; }
-.ansi .sgr97 { color: #ffffff; }
diff --git a/static/css/code.css b/static/css/code.css
new file mode 100644
index 0000000..1ccc275
--- /dev/null
+++ b/static/css/code.css
@@ -0,0 +1,200 @@
+/* ==================================================================
+    Code styling: block chrome, language chips, ANSI colors.
+
+    Zola emits highlighted fences as 
 with `z-*` classes; the token colors come
+    from the generated giallo-light.css / giallo-dark.css, which
+    base.html loads through theme-controlled media attributes. The
+    executed REPL fences arrive from MoJuWoPreprocessor as
+    
 (REPL modes) or
+    
 (`!` fence output) and are colored
+    by the .sgrNN palettes below (the VS Code terminal palettes, one
+    per theme).
+================================================================== */
+
+:root {
+  --code-text: #24292e;   /* match giallo's github-light */
+  --chip-bg: #e6e9ed;
+  --chip-text: #57606a;
+  --chip-julia: #82489e;   /* Julia purple, darkened for light chips */
+  --chip-repl: #2f7d1f;    /* Julia green, darkened, like the julia> prompt */
+
+  --sgr30: #000000;
+  --sgr31: #cd3131;
+  --sgr32: #00bc00;
+  --sgr33: #949800;
+  --sgr34: #0451a5;
+  --sgr35: #bc05bc;
+  --sgr36: #0598bc;
+  --sgr37: #555555;
+  --sgr90: #666666;
+  --sgr91: #cd3131;
+  --sgr92: #14ce14;
+  --sgr93: #b5ba00;
+  --sgr94: #0451a5;
+  --sgr95: #bc05bc;
+  --sgr96: #0598bc;
+  --sgr97: #a5a5a5;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme="light"]) {
+    --code-text: #e1e4e8;   /* match giallo's github-dark */
+    --chip-bg: #2f3742;
+    --chip-text: #adbac7;
+    --chip-julia: #c79fe0;
+    --chip-repl: #7ecb6f;
+
+    --sgr30: #666666;
+    --sgr31: #cd3131;
+    --sgr32: #0dbc79;
+    --sgr33: #e5c516;
+    --sgr34: #4886d0;
+    --sgr35: #bc3fbc;
+    --sgr36: #11a8cd;
+    --sgr37: #e5e5e5;
+    --sgr90: #8b949e;
+    --sgr91: #f14c4c;
+    --sgr92: #23d18b;
+    --sgr93: #d7ba21;
+    --sgr94: #3b8eea;
+    --sgr95: #d670d6;
+    --sgr96: #29b8db;
+    --sgr97: #ffffff;
+  }
+}
+
+:root[data-theme="dark"] {
+  --code-text: #e1e4e8;
+  --chip-bg: #2f3742;
+  --chip-text: #adbac7;
+  --chip-julia: #c79fe0;
+  --chip-repl: #7ecb6f;
+
+  --sgr30: #666666;
+  --sgr31: #cd3131;
+  --sgr32: #0dbc79;
+  --sgr33: #e5c516;
+  --sgr34: #4886d0;
+  --sgr35: #bc3fbc;
+  --sgr36: #11a8cd;
+  --sgr37: #e5e5e5;
+  --sgr90: #8b949e;
+  --sgr91: #f14c4c;
+  --sgr92: #23d18b;
+  --sgr93: #d7ba21;
+  --sgr94: #3b8eea;
+  --sgr95: #d670d6;
+  --sgr96: #29b8db;
+  --sgr97: #ffffff;
+}
+
+/* ------------------------------------------------------------------
+    Inline code
+------------------------------------------------------------------ */
+
+code {
+  font-family: var(--font-mono);
+  font-size: 0.82rem;
+  background-color: var(--bg-hover);
+  padding: 0.1em 0.25em;
+  border-radius: 3px;
+}
+
+/* ------------------------------------------------------------------
+    Block chrome
+------------------------------------------------------------------ */
+
+pre {
+  position: relative;
+  font-family: var(--font-mono);
+  font-size: 0.8rem;
+  line-height: 1.45;
+  border: 1px solid var(--border);
+  border-radius: 10px;
+  padding: 1em;
+  overflow-x: auto;
+}
+
+pre code {
+  background: transparent;
+  padding: 0;
+  font-size: inherit;
+}
+
+/* giallo-light paints blocks white; use the inset gray so they read as
+   blocks on the white page. In dark mode the token matches github-dark. */
+pre.giallo,
+pre.julia-repl.ansi {
+  background-color: var(--bg-inset);
+  color: var(--code-text);
+}
+
+/* `!` fence output: page background and a dashed border, so results
+   read as distinct from the code that produced them. */
+pre.code-output.ansi {
+  color: var(--code-text);
+}
+
+/* ------------------------------------------------------------------
+    Language label chips
+------------------------------------------------------------------ */
+
+pre[data-lang]::before,
+pre.julia-repl.ansi::before {
+  position: absolute;
+  top: 0;
+  left: 0;
+  padding: 0.2rem 0.5rem 0.1rem;
+  background-color: var(--chip-bg);
+  color: var(--chip-text);
+  border-bottom-right-radius: 10px;
+  border-top-left-radius: 10px;
+  font-size: 0.7rem;
+  font-weight: 700;
+  text-transform: uppercase;
+}
+
+/* Reserve room for the chip so it doesn't overlap the first code line. */
+pre[data-lang]:not([data-lang="plain"]),
+pre.julia-repl.ansi {
+  padding-top: 2.4em;
+}
+
+pre[data-lang]::before { content: attr(data-lang); }
+/* Giallo reports canonical grammar names; keep the labels the site used. */
+pre[data-lang="shellscript"]::before { content: "Shell"; }
+/* Fences without a recognized language get no chip. */
+pre[data-lang="plain"]::before { content: none; }
+pre.julia-repl.ansi::before { content: "Julia REPL"; }
+
+/* The rationed Julia-color accents: tinted label text, neutral chip. */
+pre[data-lang="julia"]::before { color: var(--chip-julia); }
+pre.julia-repl.ansi::before { color: var(--chip-repl); }
+
+/* ------------------------------------------------------------------
+    ANSI colors (ANSIColoredPrinters.jl .sgrNN spans)
+------------------------------------------------------------------ */
+
+.ansi .sgr1 { font-weight: bold; }
+.ansi .sgr2 { opacity: 0.8; }
+.ansi .sgr3 { font-style: italic; }
+.ansi .sgr4 { text-decoration: underline; }
+.ansi .sgr9 { text-decoration: line-through; }
+
+.ansi .sgr30 { color: var(--sgr30); }
+.ansi .sgr31 { color: var(--sgr31); }
+.ansi .sgr32 { color: var(--sgr32); }
+.ansi .sgr33 { color: var(--sgr33); }
+.ansi .sgr34 { color: var(--sgr34); }
+.ansi .sgr35 { color: var(--sgr35); }
+.ansi .sgr36 { color: var(--sgr36); }
+.ansi .sgr37 { color: var(--sgr37); }
+.ansi .sgr90 { color: var(--sgr90); }
+.ansi .sgr91 { color: var(--sgr91); }
+.ansi .sgr92 { color: var(--sgr92); }
+.ansi .sgr93 { color: var(--sgr93); }
+.ansi .sgr94 { color: var(--sgr94); }
+.ansi .sgr95 { color: var(--sgr95); }
+.ansi .sgr96 { color: var(--sgr96); }
+.ansi .sgr97 { color: var(--sgr97); }
diff --git a/static/css/custom.css b/static/css/custom.css
deleted file mode 100644
index 1736e4b..0000000
--- a/static/css/custom.css
+++ /dev/null
@@ -1,41 +0,0 @@
-/* ==================================================================
-    Badges
-================================================================== */
-
-/* Custom CSS required to display the docs|stable badge on the landing page */
-img[alt=badge] {
-  height: 1em;
-  width: auto;
-  display: inline;
-  margin: 0;
-  padding: 0 0.5ex;
-}
-
-/* ==================================================================
-    Admonitions
-================================================================== */
-
-.tldr {
-  background-color: rgba(34, 139, 34, 0.291);
-  border-radius: 10px;
-  padding: 0.5em;
-  margin-bottom: 1em;
-}
-
-.tldr p {
-  margin-bottom: 0em;
-}
-
-.advanced {
-  background: #cb3d337a;
-  border-radius: 10px;
-  padding: 0.5em;
-  margin-bottom: 1em;
-}
-
-.vscode {
-  background-color: #9558b248;
-  border-radius: 10px;
-  padding: 0.5em;
-  margin-bottom: 1em;
-}
diff --git a/static/css/franklin.css b/static/css/franklin.css
deleted file mode 100644
index 50900a4..0000000
--- a/static/css/franklin.css
+++ /dev/null
@@ -1,188 +0,0 @@
-/* ==================================================================
-    VARIABLES
-================================================================== */
-
-:root {
-  --block-background: hsl(0, 0%, 94%);
-  --output-background: hsl(0, 0%, 98%);
-  --small: 14px;
-  --normal: 19px;
-}
-
-/* ==================================================================
-    DEFAULT FONT AND LAYOUT
-================================================================== */
-
-html {
-  font-family: Helvetica, Arial, sans-serif;
-  font-size: var(--normal);
-}
-
-/* ==================================================================
-    FOOT / COPYRIGHT
-================================================================== */
-
-.franklin-content .page-foot a {
-  text-decoration: none;
-  color: #a6a2a0;
-  text-decoration: underline;
-}
-
-.page-foot {
-  font-size: 80%;
-  font-family: Arial, serif;
-  color: #a6a2a0;
-  text-align: center;
-  margin-top: 6em;
-  border-top: 1px solid lightgrey;
-  padding-top: 2em;
-  margin-bottom: 4em;
-}
-
-/* ==================================================================
-    TEXT GEOMETRY
-================================================================== */
-
-.franklin-content {
-  position: relative;
-  padding-left: 12.5%;
-  padding-right: 12.5%;
-  line-height: 1.35em;
-}
-
-/* On wide screens, fix content width to a max value. */
-@media (min-width: 940px) {
-  .franklin-content {
-    width: 705px;
-    margin-left: auto;
-    margin-right: auto;
-  }
-}
-
-/* On narrow device, reduce margins. */
-@media (max-width: 480px) {
-  .franklin-content {
-    padding-left: 6%;
-    padding-right: 6%;
-  }
-}
-
-/* ==================================================================
-    TITLES
-================================================================== */
-
-.franklin-content h1 { font-size: 24px; }
-.franklin-content h2 { font-size: 22px; }
-.franklin-content h3 { font-size: 20px; }
-
-.franklin-content h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
-  text-align: left;
-}
-
-.franklin-content h1 {
-  padding-bottom: 0.5em;
-  border-bottom: 3px double lightgrey;
-  margin-top: 1.5em;
-  margin-bottom: 1em;
-}
-
-.franklin-content h2 {
-  padding-bottom: 0.3em;
-  border-bottom: 1px solid lightgrey;
-  margin-top: 2em;
-  margin-bottom: 1em;
-}
-
-.franklin-content h1 a { color: inherit; }
-.franklin-content h1 a:hover { text-decoration: none; }
-.franklin-content h2 a { color: inherit; }
-.franklin-content h2 a:hover { text-decoration: none; }
-.franklin-content h3 a { color: inherit; }
-.franklin-content h3 a:hover { text-decoration: none; }
-.franklin-content h4 a { color: inherit; }
-.franklin-content h4 a:hover { text-decoration: none; }
-.franklin-content h5 a { color: inherit; }
-.franklin-content h5 a:hover { text-decoration: none; }
-.franklin-content h6 a { color: inherit; }
-.franklin-content h6 a:hover { text-decoration: none; }
-
-.franklin-content table {
-  margin-left: auto;
-  margin-right: auto;
-  border-collapse: collapse;
-  text-align: center;
-}
-
-.franklin-content th,
-td {
-  font-size: var(--small);
-  padding: 10px;
-  border: 1px solid black;
-}
-
-.franklin-content blockquote {
-  background: var(--block-background);
-  border-left: 7px solid #a8a8a8;
-  margin: 1.5em 10px;
-  padding: 0.5em 10px;
-  font-style: italic;
-}
-
-.franklin-content blockquote p {
-  display: inline;
-}
-
-/* ==================================================================
-    GENERAL FORMATTING
-================================================================== */
-
-/* Spacing between bullet points. */
-.franklin-content li p {
-  margin: 10px 0;
-}
-
-.franklin-content a {
-  color: #004de6;
-  text-decoration: none;
-}
-
-.franklin-content a:hover {
-  text-decoration: underline;
-}
-
-/* ==================================================================
-    IMAGES in CONTENT
-================================================================== */
-
-.franklin-content img {
-  width: 70%;
-  text-align: center;
-  padding-left: 10%;
-}
-
-/* ==================================================================
-    KATEX
-================================================================== */
-
-.katex { font-size: 1em !important; }
-
-/* ==================================================================
-    CODE
-================================================================== */
-
-code {
-  background-color: var(--block-background);
-  padding: 0.1em 0.2em;
-  border-radius: 2px;
-  font-size: var(--small);
-}
-
-.code-output {
-  background: var(--output-background);
-  border: 1px dashed #dbdbdb;
-}
diff --git a/static/css/poole_hyde.css b/static/css/poole_hyde.css
deleted file mode 100644
index dedc206..0000000
--- a/static/css/poole_hyde.css
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- *                        ___
- *                       /\_ \
- *  _____     ___     ___\//\ \      __
- * /\ '__`\  / __`\  / __`\\ \ \   /'__`\
- * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\  __/
- *  \ \ ,__/\ \____/\ \____//\____\ \____\
- *   \ \ \/  \/___/  \/___/ \/____/\/____/
- *    \ \_\
- *     \/_/
- *
- * Designed, built, and released under MIT license by @mdo. Learn more at
- * https://github.com/poole/poole.
- */
-
-
-/*
- * Contents
- *
- * Body resets
- * Custom type
- * Messages
- * Container
- * Masthead
- * Posts and pages
- * Pagination
- * Reverse layout
- * Themes
- */
-
-
-/*
- * Body resets
- *
- * Update the foundational and global aspects of the page.
- */
-
-* {
-  -webkit-box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  box-sizing: border-box;
-}
-
-html,
-body {
-  margin: 0;
-  padding: 0;
-}
-
-html {
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-  font-size: 16px;
-  line-height: 1.5;
-}
-
-@media (min-width: 38em) {
-  html {
-    font-size: 20px;
-  }
-}
-
-body {
-  color: #515151;
-  background-color: #fff;
-  -webkit-text-size-adjust: 100%;
-  -ms-text-size-adjust: 100%;
-}
-
-/* No `:visited` state is required by default (browsers will use `a`) */
-a {
-  color: #268bd2;
-  text-decoration: none;
-}
-
-a strong {
-  color: inherit;
-}
-
-/* `:focus` is linked to `:hover` for basic accessibility */
-a:hover,
-a:focus {
-  text-decoration: underline;
-}
-
-/* Headings */
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
-  margin-bottom: 0.5rem;
-  font-weight: bold;
-  line-height: 1.25;
-  color: #313131;
-  text-rendering: optimizeLegibility;
-}
-
-h1 {
-  font-size: 2rem;
-}
-
-h2 {
-  margin-top: 1rem;
-  font-size: 1.5rem;
-}
-
-h3 {
-  margin-top: 1.5rem;
-  font-size: 1.25rem;
-}
-
-h4,
-h5,
-h6 {
-  margin-top: 1rem;
-  font-size: 1rem;
-}
-
-/* Body text */
-p {
-  margin-top: 0;
-  margin-bottom: 1rem;
-}
-
-strong {
-  color: #303030;
-}
-
-
-/* Lists */
-ul,
-ol,
-dl {
-  margin-top: 0;
-  margin-bottom: 1rem;
-}
-
-dt {
-  font-weight: bold;
-}
-
-dd {
-  margin-bottom: 0.5rem;
-}
-
-/* Misc */
-hr {
-  position: relative;
-  margin: 1.5rem 0;
-  border: 0;
-  border-top: 1px solid #eee;
-  border-bottom: 1px solid #fff;
-}
-
-abbr {
-  font-size: 85%;
-  font-weight: bold;
-  color: #555;
-  text-transform: uppercase;
-}
-
-abbr[title] {
-  cursor: help;
-  border-bottom: 1px dotted #e5e5e5;
-}
-
-/* Quotes */
-blockquote {
-  padding: 0.5rem 1rem;
-  margin: 0.8rem 0;
-  color: #7a7a7a;
-  border-left: 0.25rem solid #e5e5e5;
-}
-
-blockquote p:last-child {
-  margin-bottom: 0;
-}
-
-@media (min-width: 30em) {
-  blockquote {
-    padding-right: 5rem;
-    padding-left: 1.25rem;
-  }
-}
-
-img {
-  display: block;
-  max-width: 100%;
-  margin: 0 0 1rem;
-  border-radius: 5px;
-}
-
-/* Tables */
-table {
-  margin-bottom: 1rem;
-  width: 100%;
-  border: 1px solid #e5e5e5;
-  border-collapse: collapse;
-}
-
-td,
-th {
-  padding: 0.25rem 0.5rem;
-  border: 1px solid #e5e5e5;
-}
-
-tbody tr:nth-child(odd) td,
-tbody tr:nth-child(odd) th {
-  background-color: #f9f9f9;
-}
-
-/*
- *  __                  __
- * /\ \                /\ \
- * \ \ \___   __  __   \_\ \     __
- *  \ \  _ `\/\ \/\ \  /'_` \  /'__`\
- *   \ \ \ \ \ \ \_\ \/\ \_\ \/\  __/
- *    \ \_\ \_\/`____ \ \___,_\ \____\
- *     \/_/\/_/`/___/> \/__,_ /\/____/
- *                /\___/
- *                \/__/
- *
- * Designed, built, and released under MIT license by @mdo. Learn more at
- * https://github.com/poole/hyde.
- */
-
-
-/*
- * Contents
- *
- * Global resets
- * Sidebar
- * Container
- * Reverse layout
- * Themes
- */
-
-
-/*
- * Global resets
- *
- * Update the foundational and global aspects of the page.
- */
-
-html {
-  font-family: "PT Sans", Helvetica, Arial, sans-serif;
-}
-
-/*
- * Sidebar
- *
- * Flexible banner for housing site name, intro, and "footer" content. Starts
- * out above content in mobile and later moves to the side with wider viewports.
- */
-
-.sidebar {
-  text-align: center;
-  color: rgba(255, 255, 255, 0.5);
-  background-color: #202020;
-}
-
-@media (min-width: 768px) {
-  .sidebar {
-    position: fixed;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    width: 15rem;
-    text-align: left;
-    overflow-y: scroll;
-  }
-}
-
-/* Sidebar links */
-.sidebar a {
-  color: #fff;
-}
-
-/* About section */
-.sidebar-about h1 {
-  color: #fff;
-  margin-top: 0;
-  font-size: 40px;
-}
-
-/* Sidebar nav */
-.sidebar-nav {
-  margin-bottom: 1rem;
-}
-
-.sidebar-nav-item {
-  display: block;
-  line-height: 1.75;
-}
-
-a.sidebar-nav-item:hover,
-a.sidebar-nav-item:focus {
-  text-decoration: underline;
-}
-
-.sidebar-nav-item.active {
-  font-weight: bold;
-}
-
-.copyright-notice a {
-  text-decoration: none;
-  color: rgba(255, 255, 255, 0.5)  !important;
-}
-
-.github-link {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.github-link a {
-  color: rgba(255, 255, 255, 0.5) !important;
-}
-
-/* Hide submenu by default */
-.menu-list-child-list {
-  display: none;
-  padding-left: 1.5rem;
-}
-
-/* Show submenu when parent item has 'active' class */
-.sidebar-nav-item.active+.menu-list-child-list {
-  display: block;
-}
-
-.menu-list-link {
-  color: rgba(255, 255, 255, 0.5) !important;
-  font-size: var(--small);
-}
-
-/* Sticky sidebar
- *
- * Add the `sidebar-sticky` class to the sidebar's container to affix it the
- * contents to the top of the sidebar in tablets and up.
- */
-
-.sidebar-sticky {
-  padding-top: 1rem;
-  padding-left: 1.5rem;
-  padding-right: 1.5rem;
-  padding-bottom: 0.5rem;
-}
-
-@media (min-width: 768px) {
-  .sidebar-sticky {
-    position: relative;
-  }
-}
-
-/* Container
- *
- * Align the contents of the site above the proper threshold with some margin-fu
- * with a 25%-wide `.sidebar`.
- */
-
-.content {
-  padding-top: 4rem;
-  padding-bottom: 4rem;
-}
-
-@media (min-width: 768px) {
-  .content {
-    max-width: 45rem;
-    margin-left: 15rem;
-    margin-right: 2rem;
-  }
-}
-
-/* Hide the logo and submenu on screens with width less than 768px */
-@media (max-width: 767px) {
-  .logo-container {
-    display: none;
-  }
-  .sidebar-nav-item.active+.menu-list-child-list {
-    display: none;
-  }
-}
-
-/* Center the logo within the logo container */
-.logo-img {
-  display: block;
-  margin-bottom: 15px;
-  margin-left: 20px;
-  width: 150px;
-}
diff --git a/static/css/search.css b/static/css/search.css
deleted file mode 100644
index 447c410..0000000
--- a/static/css/search.css
+++ /dev/null
@@ -1,129 +0,0 @@
-/* ==================================================================
-    Site search: sidebar launcher button and results dialog
-    (templates/partials/search.html, js/search.js)
-================================================================== */
-
-/* Launcher, styled like an input to match the dark sidebar */
-
-.search-open {
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
-  width: 100%;
-  max-width: 20rem;
-  margin: 0 auto 1rem;
-  padding: 0.35rem 0.6rem;
-  font: inherit;
-  color: rgba(255, 255, 255, 0.5);
-  background-color: rgba(255, 255, 255, 0.08);
-  border: 1px solid rgba(255, 255, 255, 0.25);
-  border-radius: 6px;
-  cursor: pointer;
-}
-
-.search-open:hover,
-.search-open:focus {
-  color: #fff;
-  border-color: rgba(255, 255, 255, 0.5);
-}
-
-.search-open kbd {
-  margin-left: auto;
-  padding: 0 0.35em;
-  font-size: 0.8em;
-  border: 1px solid rgba(255, 255, 255, 0.25);
-  border-radius: 4px;
-}
-
-/* No keyboard on most narrow viewports, so hide the shortcut hint */
-@media (max-width: 767px) {
-  .search-open kbd {
-    display: none;
-  }
-}
-
-/* Dialog */
-
-#search-dialog {
-  width: min(34rem, 92vw);
-  margin: 10vh auto auto;
-  padding: 0;
-  border: none;
-  border-radius: 10px;
-  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
-}
-
-#search-dialog::backdrop {
-  background: rgba(0, 0, 0, 0.45);
-}
-
-#search-input {
-  width: 100%;
-  padding: 0.8rem 1rem;
-  font: inherit;
-  font-size: 1.1rem;
-  border: none;
-  border-bottom: 1px solid #ddd;
-  border-radius: 10px 10px 0 0;
-  outline: none;
-}
-
-/* Results */
-
-#search-results {
-  max-height: 60vh;
-  overflow-y: auto;
-}
-
-#search-results:not(:empty) {
-  padding-bottom: 0.4rem;
-}
-
-.search-group-label {
-  padding: 0.6rem 1rem 0.2rem;
-  font-size: 0.7rem;
-  font-weight: bold;
-  text-transform: uppercase;
-  letter-spacing: 0.05em;
-  color: #999;
-}
-
-.search-empty {
-  padding: 0.8rem 1rem;
-  color: #7a7a7a;
-}
-
-a.search-result {
-  display: block;
-  padding: 0.4rem 1rem;
-  color: inherit;
-  text-decoration: none;
-}
-
-a.search-result:hover,
-a.search-result.selected {
-  background-color: #eaf3fa;
-}
-
-.search-crumb {
-  color: #999;
-}
-
-.search-result-title b {
-  color: #268bd2;
-}
-
-.search-teaser {
-  font-size: 0.85rem;
-  line-height: 1.4;
-  color: #7a7a7a;
-  overflow-wrap: anywhere;
-}
-
-/* Pagefind wraps matched terms in ; render them like the  in titles
-   instead of the browser's yellow highlight */
-.search-teaser mark {
-  background: none;
-  color: #268bd2;
-  font-weight: bold;
-}
diff --git a/static/css/site.css b/static/css/site.css
new file mode 100644
index 0000000..47e75f1
--- /dev/null
+++ b/static/css/site.css
@@ -0,0 +1,713 @@
+/* ==================================================================
+    Site stylesheet.
+
+    Everything except code-block styling (css/code.css). One light and
+    one dark theme, driven by the custom properties below: the default
+    follows the system preference, and js/ui.js pins `data-theme` on
+     when the reader uses the toggle. Julia's four brand colors
+    are reserved for admonitions, links/active nav, and code chips.
+================================================================== */
+
+/* ------------------------------------------------------------------
+    Theme tokens
+------------------------------------------------------------------ */
+
+:root {
+  --bg: #ffffff;
+  --bg-sidebar: #f6f7f8;
+  --bg-inset: #f6f8fa;   /* code blocks, table stripes */
+  --bg-hover: #eef1f4;
+  --text: #515151;
+  --text-strong: #313131;
+  --text-muted: #82878f;
+  --border: #e2e4e8;
+  --border-strong: #cfd3d8;
+
+  --julia-green: #389826;
+  --julia-red: #cb3c33;
+  --julia-purple: #9558b2;
+  --julia-blue: #4063d8;
+  --link: var(--julia-blue);
+
+  --font-body: Helvetica, Arial, sans-serif;
+  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+
+  /* Admonition header knobs: wash opacity, and title-text blend toward ink */
+  --adm-tint: 9%;
+  --adm-ink: #1c1c1c;
+  --adm-ink-mix: 75%;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme="light"]) {
+    --bg: #14171a;
+    --bg-sidebar: #1c2025;
+    --bg-inset: #24292e;
+    --bg-hover: #2a3038;
+    --text: #b9c1c9;
+    --text-strong: #e8ebee;
+    --text-muted: #8b939c;
+    --border: #30363d;
+    --border-strong: #3d444d;
+    --link: #91a9f0;   /* Julia blue, lightened for dark backgrounds */
+    --adm-tint: 16%;
+    --adm-ink: #ffffff;
+    --adm-ink-mix: 55%;
+  }
+}
+
+:root[data-theme="dark"] {
+  --bg: #14171a;
+  --bg-sidebar: #1c2025;
+  --bg-inset: #24292e;
+  --bg-hover: #2a3038;
+  --text: #b9c1c9;
+  --text-strong: #e8ebee;
+  --text-muted: #8b939c;
+  --border: #30363d;
+  --border-strong: #3d444d;
+  --link: #91a9f0;
+  --adm-tint: 16%;
+  --adm-ink: #ffffff;
+  --adm-ink-mix: 55%;
+}
+
+/* ------------------------------------------------------------------
+    Base
+------------------------------------------------------------------ */
+
+* {
+  box-sizing: border-box;
+}
+
+html {
+  margin: 0;
+  font-family: var(--font-body);
+  font-size: 17px;
+  line-height: 1.5;
+}
+
+body {
+  margin: 0;
+  color: var(--text);
+  background-color: var(--bg);
+  -webkit-text-size-adjust: 100%;
+}
+
+a {
+  color: var(--link);
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  text-decoration: underline;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  margin-top: 1.5em;
+  margin-bottom: 0.6em;
+  font-weight: bold;
+  line-height: 1.25;
+  color: var(--text-strong);
+}
+
+h1 {
+  font-size: 1.6rem;
+  padding-bottom: 0.4em;
+  border-bottom: 3px double var(--border-strong);
+}
+
+h2 {
+  font-size: 1.35rem;
+  padding-bottom: 0.25em;
+  border-bottom: 1px solid var(--border-strong);
+}
+
+h3 {
+  font-size: 1.15rem;
+}
+
+h4,
+h5,
+h6 {
+  font-size: 1rem;
+}
+
+/* Section links land right at the heading, not under the mobile top bar. */
+h1,
+h2,
+h3,
+h4 {
+  scroll-margin-top: 1rem;
+}
+
+h1 a,
+h2 a,
+h3 a,
+h4 a {
+  color: inherit;
+}
+
+p,
+ul,
+ol,
+dl {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+li p {
+  margin: 0.5rem 0;
+}
+
+strong {
+  color: var(--text-strong);
+}
+
+hr {
+  margin: 1.5rem 0;
+  border: 0;
+  border-top: 1px solid var(--border);
+}
+
+blockquote {
+  margin: 1rem 0;
+  padding: 0.25rem 1rem;
+  color: var(--text-muted);
+  border-left: 3px solid var(--border-strong);
+}
+
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+img {
+  display: block;
+  max-width: 100%;
+  margin: 0 auto 1rem;
+  border-radius: 5px;
+}
+
+/* Inline docs|stable badges on the landing page flow with the text. */
+img[alt="badge"] {
+  height: 1em;
+  width: auto;
+  display: inline;
+  margin: 0;
+  padding: 0 0.5ex;
+  vertical-align: text-bottom;
+}
+
+table {
+  margin-bottom: 1rem;
+  width: 100%;
+  border: 1px solid var(--border);
+  border-collapse: collapse;
+  font-size: 0.9rem;
+}
+
+td,
+th {
+  padding: 0.3rem 0.6rem;
+  border: 1px solid var(--border);
+  text-align: left;
+}
+
+th {
+  color: var(--text-strong);
+}
+
+tbody tr:nth-child(odd) td,
+tbody tr:nth-child(odd) th {
+  background-color: var(--bg-inset);
+}
+
+kbd {
+  padding: 0 0.35em;
+  font-family: inherit;   /* no monospace outside code */
+  font-size: 0.8em;
+  border: 1px solid var(--border-strong);
+  border-radius: 4px;
+}
+
+.katex {
+  font-size: 1em !important;
+}
+
+/* ------------------------------------------------------------------
+    Layout: fixed sidebar on the left, one reading column
+------------------------------------------------------------------ */
+
+.sidebar {
+  position: fixed;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  z-index: 20;
+  width: 15rem;
+  overflow-y: auto;
+  background-color: var(--bg-sidebar);
+  border-right: 1px solid var(--border);
+  font-size: 0.95rem;
+}
+
+.content {
+  margin-left: 15rem;
+}
+
+.page {
+  max-width: 45rem;
+  margin: 0 auto;
+  padding: 2.5rem 2rem 1.5rem;
+}
+
+.page > div > h1:first-child {
+  margin-top: 0;
+}
+
+/* ------------------------------------------------------------------
+    Sidebar
+------------------------------------------------------------------ */
+
+.sidebar-inner {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+  padding: 1.5rem 1.25rem 1rem;
+}
+
+.sidebar-brand {
+  display: block;
+  text-align: center;
+  color: inherit;
+}
+
+.sidebar-brand:hover,
+.sidebar-brand:focus {
+  text-decoration: none;
+}
+
+.sidebar-wordmark {
+  display: block;
+  font-size: 1.6rem;
+  font-weight: bold;
+  color: var(--text-strong);
+  line-height: 1.2;
+}
+
+.sidebar-tagline {
+  display: block;
+  margin-top: 0.2rem;
+  font-size: 0.8rem;
+  color: var(--text-muted);
+}
+
+.sidebar-logo {
+  width: 105px;
+  margin: 1rem auto;
+}
+
+/* Search launcher, styled like an input */
+
+.search-open {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  width: 100%;
+  margin: 0.25rem 0 1.25rem;
+  padding: 0.35rem 0.6rem;
+  font: inherit;
+  color: var(--text-muted);
+  background-color: var(--bg);
+  border: 1px solid var(--border);
+  border-radius: 6px;
+  cursor: pointer;
+}
+
+.search-open:hover,
+.search-open:focus {
+  color: var(--text-strong);
+  border-color: var(--border-strong);
+}
+
+.search-open kbd {
+  margin-left: auto;
+}
+
+/* Navigation: page links, with the active page's sections below it */
+
+.sidebar-nav {
+  flex-grow: 1;
+}
+
+.sidebar-nav-item {
+  display: block;
+  padding: 0.15rem 0;
+  color: var(--text);
+  line-height: 1.6;
+}
+
+.sidebar-nav-item.active {
+  color: var(--link);
+  font-weight: bold;
+}
+
+.menu-list-child-list {
+  display: none;
+  list-style: none;
+  margin: 0.2rem 0 0.4rem;
+  padding-left: 0.75rem;
+  border-left: 1px solid var(--border-strong);
+}
+
+.sidebar-nav-item.active + .menu-list-child-list {
+  display: block;
+}
+
+.menu-list-item {
+  margin: 0;
+}
+
+.menu-list-link {
+  display: block;
+  padding: 0.1rem 0;
+  font-size: 0.8rem;
+  color: var(--text-muted);
+}
+
+.menu-list-link:hover,
+.menu-list-link:focus {
+  color: var(--text-strong);
+  text-decoration: none;
+}
+
+/* Footer: theme toggle, GitHub, license */
+
+.sidebar-foot {
+  margin-top: 1.5rem;
+}
+
+.sidebar-foot-icons {
+  display: flex;
+  justify-content: center;
+  gap: 1rem;
+  margin-bottom: 0.5rem;
+}
+
+.sidebar-foot-icons a,
+.theme-toggle {
+  display: flex;
+  align-items: center;
+  padding: 0.3rem;
+  color: var(--text-muted);
+  background: none;
+  border: none;
+  cursor: pointer;
+}
+
+.sidebar-foot-icons a:hover,
+.sidebar-foot-icons a:focus,
+.theme-toggle:hover,
+.theme-toggle:focus {
+  color: var(--text-strong);
+}
+
+.copyright-notice {
+  margin: 0;
+  text-align: center;
+  font-size: 0.7rem;
+}
+
+.copyright-notice a {
+  color: var(--text-muted);
+}
+
+/* The toggle shows the mode it switches to: a moon in light mode, a sun
+   in dark mode. "Auto" (no data-theme) resolves via the media query. */
+
+.theme-toggle .icon-sun {
+  display: none;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme="light"]) .theme-toggle .icon-sun {
+    display: inline;
+  }
+
+  :root:not([data-theme="light"]) .theme-toggle .icon-moon {
+    display: none;
+  }
+}
+
+:root[data-theme="dark"] .theme-toggle .icon-sun {
+  display: inline;
+}
+
+:root[data-theme="dark"] .theme-toggle .icon-moon {
+  display: none;
+}
+
+:root[data-theme="light"] .theme-toggle .icon-sun {
+  display: none;
+}
+
+:root[data-theme="light"] .theme-toggle .icon-moon {
+  display: inline;
+}
+
+/* ------------------------------------------------------------------
+    Admonitions: colored title strip, plain bordered body
+------------------------------------------------------------------ */
+
+.admonition {
+  margin: 1.5rem 0;
+  border: 1px solid color-mix(in srgb, var(--adm) 45%, transparent);
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+/* A quiet header: a light wash of the accent with tinted title text,
+   rather than a solid color strip. --adm-* knobs are set per theme. */
+.admonition-title {
+  margin: 0;
+  padding: 0.3rem 1rem;
+  background-color: color-mix(in srgb, var(--adm) var(--adm-tint), transparent);
+  border-bottom: 1px solid color-mix(in srgb, var(--adm) 45%, transparent);
+  color: color-mix(in srgb, var(--adm) var(--adm-ink-mix), var(--adm-ink));
+  font-size: 0.9rem;
+  font-weight: bold;
+}
+
+.admonition-body {
+  padding: 0.75rem 1rem 0;
+}
+
+.tldr {
+  --adm: var(--julia-green);
+}
+
+.advanced {
+  --adm: var(--julia-red);
+}
+
+.vscode {
+  --adm: var(--julia-purple);
+}
+
+.note {
+  --adm: var(--julia-blue);
+}
+
+/* ------------------------------------------------------------------
+    In-content table of contents
+------------------------------------------------------------------ */
+
+.toc ol {
+  margin: 1rem 0;
+}
+
+/* ------------------------------------------------------------------
+    Page foot
+------------------------------------------------------------------ */
+
+.page-foot {
+  margin-top: 5rem;
+  padding-top: 1.5rem;
+  border-top: 1px solid var(--border);
+  font-size: 0.75rem;
+  color: var(--text-muted);
+  text-align: center;
+}
+
+.page-foot a {
+  color: var(--text-muted);
+  text-decoration: underline;
+}
+
+/* ------------------------------------------------------------------
+    Search dialog
+------------------------------------------------------------------ */
+
+#search-dialog {
+  width: min(34rem, 92vw);
+  margin: 10vh auto auto;
+  padding: 0;
+  color: var(--text);
+  background-color: var(--bg);
+  border: 1px solid var(--border);
+  border-radius: 10px;
+  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
+}
+
+#search-dialog::backdrop {
+  background: rgba(0, 0, 0, 0.45);
+}
+
+#search-input {
+  width: 100%;
+  padding: 0.8rem 1rem;
+  font: inherit;
+  font-size: 1.1rem;
+  color: inherit;
+  background: none;
+  border: none;
+  border-bottom: 1px solid var(--border);
+  border-radius: 10px 10px 0 0;
+  outline: none;
+}
+
+#search-results {
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+#search-results:not(:empty) {
+  padding-bottom: 0.4rem;
+}
+
+.search-group-label {
+  padding: 0.6rem 1rem 0.2rem;
+  font-size: 0.7rem;
+  font-weight: bold;
+  text-transform: uppercase;
+  letter-spacing: 0.05em;
+  color: var(--text-muted);
+}
+
+.search-empty {
+  padding: 0.8rem 1rem;
+  color: var(--text-muted);
+}
+
+a.search-result {
+  display: block;
+  padding: 0.4rem 1rem;
+  color: inherit;
+  text-decoration: none;
+}
+
+a.search-result:hover,
+a.search-result.selected {
+  background-color: var(--bg-hover);
+}
+
+.search-crumb {
+  color: var(--text-muted);
+}
+
+.search-result-title b {
+  color: var(--link);
+}
+
+.search-teaser {
+  font-size: 0.85rem;
+  line-height: 1.4;
+  color: var(--text-muted);
+  overflow-wrap: anywhere;
+}
+
+/* Pagefind wraps matched terms in ; render them like the  in
+   titles instead of the browser's yellow highlight */
+.search-teaser mark {
+  background: none;
+  color: var(--link);
+  font-weight: bold;
+}
+
+/* ------------------------------------------------------------------
+    Mobile: top bar and slide-in drawer
+------------------------------------------------------------------ */
+
+.topbar {
+  display: none;
+}
+
+@media (max-width: 767px) {
+  .topbar {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
+    height: 3.25rem;
+    padding: 0 0.75rem;
+    background-color: var(--bg-sidebar);
+    border-bottom: 1px solid var(--border);
+  }
+
+  .topbar button {
+    display: flex;
+    align-items: center;
+    padding: 0.4rem;
+    color: var(--text);
+    background: none;
+    border: none;
+    cursor: pointer;
+  }
+
+  .topbar-title {
+    font-weight: bold;
+    font-size: 1.1rem;
+    color: var(--text-strong);
+  }
+
+  .topbar-search {
+    margin-left: auto;
+  }
+
+  .sidebar {
+    width: 16rem;
+    transform: translateX(-100%);
+    transition: transform 0.2s ease;
+    visibility: hidden;
+  }
+
+  body.sidebar-open .sidebar {
+    transform: none;
+    visibility: visible;
+  }
+
+  .sidebar-backdrop {
+    position: fixed;
+    inset: 0;
+    z-index: 15;
+    background: rgba(0, 0, 0, 0.45);
+    opacity: 0;
+    visibility: hidden;
+    transition: opacity 0.2s ease;
+  }
+
+  body.sidebar-open .sidebar-backdrop {
+    opacity: 1;
+    visibility: visible;
+  }
+
+  body.sidebar-open {
+    overflow: hidden;
+  }
+
+  .content {
+    margin-left: 0;
+    padding-top: 3.25rem;
+  }
+
+  .page {
+    padding: 1.5rem 1.25rem 1rem;
+  }
+
+  h1,
+  h2,
+  h3,
+  h4 {
+    scroll-margin-top: 4.25rem;
+  }
+}
diff --git a/static/js/search.js b/static/js/search.js
index e523fcf..78dc264 100644
--- a/static/js/search.js
+++ b/static/js/search.js
@@ -32,7 +32,10 @@
     loadPagefind();
   }
 
-  document.getElementById("search-open").addEventListener("click", openDialog);
+  // Launchers: the sidebar button and the mobile top-bar button.
+  document.querySelectorAll("[data-search-open]").forEach(function (b) {
+    b.addEventListener("click", openDialog);
+  });
   document.addEventListener("keydown", function (e) {
     if (dialog.open) return;
     var typing = /^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement.tagName);
diff --git a/static/js/ui.js b/static/js/ui.js
new file mode 100644
index 0000000..2be95a3
--- /dev/null
+++ b/static/js/ui.js
@@ -0,0 +1,45 @@
+// Theme toggle and mobile drawer (markup in templates/base.html and
+// templates/partials/sidebar.html). The saved theme is applied before
+// first paint by the inline script in base.html's ; this file
+// only handles interaction.
+(function () {
+  // Point the generated giallo stylesheets at the active theme. With no
+  // explicit choice they follow the system preference on their own.
+  function syncHighlight() {
+    var t = document.documentElement.dataset.theme || "";
+    document.getElementById("hl-light").media =
+      t ? (t === "light" ? "all" : "not all") : "(prefers-color-scheme: light)";
+    document.getElementById("hl-dark").media =
+      t ? (t === "dark" ? "all" : "not all") : "(prefers-color-scheme: dark)";
+  }
+
+  document.getElementById("theme-toggle").addEventListener("click", function () {
+    var explicit = document.documentElement.dataset.theme;
+    var effective = explicit ||
+      (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
+    var next = effective === "dark" ? "light" : "dark";
+    document.documentElement.dataset.theme = next;
+    try {
+      localStorage.setItem("theme", next);
+    } catch (e) {} // private browsing: the choice just won't persist
+    syncHighlight();
+  });
+
+  // Mobile drawer: the sidebar slides in over a backdrop.
+  var body = document.body;
+  function closeDrawer() {
+    body.classList.remove("sidebar-open");
+  }
+  document.getElementById("menu-open").addEventListener("click", function () {
+    body.classList.toggle("sidebar-open");
+  });
+  document.getElementById("sidebar-backdrop").addEventListener("click", closeDrawer);
+  // Close on any nav click: same-page anchors don't trigger a page load,
+  // so the drawer would otherwise stay open over the scrolled content.
+  document.querySelector(".sidebar").addEventListener("click", function (e) {
+    if (e.target.closest("a")) closeDrawer();
+  });
+  document.addEventListener("keydown", function (e) {
+    if (e.key === "Escape" && body.classList.contains("sidebar-open")) closeDrawer();
+  });
+})();
diff --git a/templates/base.html b/templates/base.html
index bf37f53..abd5ced 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,40 +4,64 @@
 
   
   
-  
-  
-  
-  
-  
-  
-  
+  
+  
+  
+  
+  
+  
   
   {%- block head_extra %}{% endblock %}
   {% block title %}{{ config.title }}{% endblock %}
 
 
 
+  
+ + MoJuWo + +
+ {% include "partials/sidebar.html" %} -
-
+
+
-
+ + {% include "partials/search.html" %} + {%- block foot_extra %}{% endblock %} diff --git a/templates/components.html b/templates/components.html index ddbc3f5..3716fac 100644 --- a/templates/components.html +++ b/templates/components.html @@ -1,31 +1,36 @@ {# Admonition components, invoked from markdown as e.g. #} {# {% %} ... {% %} #} {# Content is Tera-templated before the markdown pass (Zola 0.23), so the #} -{# component output lands in the markdown source: the body stays markdown #} -{# and the surrounding blank lines make the
a proper HTML block. The #} -{# bold prefix merges into the body's first paragraph, like Franklin's #} -{# \tldr{...} commands did. #} +{# component output lands in the markdown source: the title strip and the #} +{# wrappers are HTML blocks, and the blank lines around the body keep it #} +{# regular markdown. Styles live in css/site.css (.admonition); each type #} +{# maps to one of the four Julia brand colors. The title is marked #} +{# data-pagefind-ignore so searching for e.g. "advanced" doesn't return #} +{# every box. #} -{% component tldr() %} -
+{% component admonition(class, title, content) %} +
+

{{ title }}

+
-**TLDR**: {{ body | trim | safe }} +{{ content | trim | safe }}
+
+{% endcomponent admonition %} + +{% component tldr() %} +{{}} {% endcomponent tldr %} {% component advanced() %} -
- -**Advanced**: {{ body | trim | safe }} - -
+{{}} {% endcomponent advanced %} {% component vscode() %} -
- -**VSCode**: {{ body | trim | safe }} - -
+{{}} {% endcomponent vscode %} + +{% component note() %} +{{}} +{% endcomponent note %} diff --git a/templates/partials/sidebar.html b/templates/partials/sidebar.html index 9c240dc..812e752 100644 --- a/templates/partials/sidebar.html +++ b/templates/partials/sidebar.html @@ -2,7 +2,8 @@ so anchors never go stale. `.menu-list-child-list` is shown by the CSS sibling selector `.sidebar-nav-item.active + .menu-list-child-list`, so each {%- endfor %} - - -
+ diff --git a/zola.toml b/zola.toml index b5f5890..8171274 100644 --- a/zola.toml +++ b/zola.toml @@ -16,9 +16,13 @@ build_search_index = false [markdown] # Fences are highlighted at build time: -# executed REPL fences arrive from `MoJuWoPreprocessor` as pre-rendered HTML and are styled by css/ansi.css. +# executed REPL fences arrive from `MoJuWoPreprocessor` as pre-rendered HTML and are styled by css/code.css. [markdown.highlighting] -theme = "github-dark" +# Class-based output so the light/dark toggle can restyle code: Zola emits +# `z-*` classes and writes giallo-light.css / giallo-dark.css into the output. +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" # Put data-lang on the
 so CSS can render language label chips on code blocks.
 data_attr_position = "pre"
 

From cbbf8d866fcc859b3d1c2f2a2ca22b7c7cf12d28 Mon Sep 17 00:00:00 2001
From: adrhill 
Date: Sat, 15 Aug 2026 14:53:14 +0200
Subject: [PATCH 02/11] fix: use GH color scheme for Julia syntax highlights

---
 static/css/code.css | 96 ++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 48 deletions(-)

diff --git a/static/css/code.css b/static/css/code.css
index 1ccc275..ed62cc8 100644
--- a/static/css/code.css
+++ b/static/css/code.css
@@ -8,8 +8,8 @@
     executed REPL fences arrive from MoJuWoPreprocessor as
     
 (REPL modes) or
     
 (`!` fence output) and are colored
-    by the .sgrNN palettes below (the VS Code terminal palettes, one
-    per theme).
+    by the .sgrNN palettes below (GitHub Primer's ANSI palettes, one
+    per theme, matching the giallo token themes).
 ================================================================== */
 
 :root {
@@ -19,22 +19,22 @@
   --chip-julia: #82489e;   /* Julia purple, darkened for light chips */
   --chip-repl: #2f7d1f;    /* Julia green, darkened, like the julia> prompt */
 
-  --sgr30: #000000;
-  --sgr31: #cd3131;
-  --sgr32: #00bc00;
-  --sgr33: #949800;
-  --sgr34: #0451a5;
-  --sgr35: #bc05bc;
-  --sgr36: #0598bc;
-  --sgr37: #555555;
-  --sgr90: #666666;
-  --sgr91: #cd3131;
-  --sgr92: #14ce14;
-  --sgr93: #b5ba00;
-  --sgr94: #0451a5;
-  --sgr95: #bc05bc;
-  --sgr96: #0598bc;
-  --sgr97: #a5a5a5;
+  --sgr30: #24292f;
+  --sgr31: #cf222e;
+  --sgr32: #1a7f37;
+  --sgr33: #4d2d00;
+  --sgr34: #0969da;
+  --sgr35: #8250df;
+  --sgr36: #1b7c83;
+  --sgr37: #6e7781;
+  --sgr90: #57606a;
+  --sgr91: #a40e26;
+  --sgr92: #1a7f37;
+  --sgr93: #633c01;
+  --sgr94: #218bff;
+  --sgr95: #a475f9;
+  --sgr96: #3192aa;
+  --sgr97: #8c959f;
 }
 
 @media (prefers-color-scheme: dark) {
@@ -45,21 +45,21 @@
     --chip-julia: #c79fe0;
     --chip-repl: #7ecb6f;
 
-    --sgr30: #666666;
-    --sgr31: #cd3131;
-    --sgr32: #0dbc79;
-    --sgr33: #e5c516;
-    --sgr34: #4886d0;
-    --sgr35: #bc3fbc;
-    --sgr36: #11a8cd;
-    --sgr37: #e5e5e5;
-    --sgr90: #8b949e;
-    --sgr91: #f14c4c;
-    --sgr92: #23d18b;
-    --sgr93: #d7ba21;
-    --sgr94: #3b8eea;
-    --sgr95: #d670d6;
-    --sgr96: #29b8db;
+    --sgr30: #484f58;
+    --sgr31: #ff7b72;
+    --sgr32: #3fb950;
+    --sgr33: #d29922;
+    --sgr34: #58a6ff;
+    --sgr35: #bc8cff;
+    --sgr36: #39c5cf;
+    --sgr37: #b1bac4;
+    --sgr90: #6e7681;
+    --sgr91: #ffa198;
+    --sgr92: #56d364;
+    --sgr93: #e3b341;
+    --sgr94: #79c0ff;
+    --sgr95: #d2a8ff;
+    --sgr96: #56d4dd;
     --sgr97: #ffffff;
   }
 }
@@ -71,21 +71,21 @@
   --chip-julia: #c79fe0;
   --chip-repl: #7ecb6f;
 
-  --sgr30: #666666;
-  --sgr31: #cd3131;
-  --sgr32: #0dbc79;
-  --sgr33: #e5c516;
-  --sgr34: #4886d0;
-  --sgr35: #bc3fbc;
-  --sgr36: #11a8cd;
-  --sgr37: #e5e5e5;
-  --sgr90: #8b949e;
-  --sgr91: #f14c4c;
-  --sgr92: #23d18b;
-  --sgr93: #d7ba21;
-  --sgr94: #3b8eea;
-  --sgr95: #d670d6;
-  --sgr96: #29b8db;
+  --sgr30: #484f58;
+  --sgr31: #ff7b72;
+  --sgr32: #3fb950;
+  --sgr33: #d29922;
+  --sgr34: #58a6ff;
+  --sgr35: #bc8cff;
+  --sgr36: #39c5cf;
+  --sgr37: #b1bac4;
+  --sgr90: #6e7681;
+  --sgr91: #ffa198;
+  --sgr92: #56d364;
+  --sgr93: #e3b341;
+  --sgr94: #79c0ff;
+  --sgr95: #d2a8ff;
+  --sgr96: #56d4dd;
   --sgr97: #ffffff;
 }
 

From e11407eca1f23329376efcfc4aba412a769c2577 Mon Sep 17 00:00:00 2001
From: adrhill 
Date: Sat, 15 Aug 2026 15:14:58 +0200
Subject: [PATCH 03/11] fix: minor CSS tweaks

---
 static/css/site.css | 24 ++++++++++--------------
 templates/base.html |  2 +-
 2 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/static/css/site.css b/static/css/site.css
index 47e75f1..344262c 100644
--- a/static/css/site.css
+++ b/static/css/site.css
@@ -83,7 +83,7 @@
 html {
   margin: 0;
   font-family: var(--font-body);
-  font-size: 17px;
+  font-size: 16px;
   line-height: 1.5;
 }
 
@@ -120,7 +120,7 @@ h6 {
 h1 {
   font-size: 1.6rem;
   padding-bottom: 0.4em;
-  border-bottom: 3px double var(--border-strong);
+  border-bottom: 1.5px solid var(--border-strong);
 }
 
 h2 {
@@ -254,7 +254,6 @@ kbd {
   overflow-y: auto;
   background-color: var(--bg-sidebar);
   border-right: 1px solid var(--border);
-  font-size: 0.95rem;
 }
 
 .content {
@@ -304,7 +303,7 @@ kbd {
 .sidebar-tagline {
   display: block;
   margin-top: 0.2rem;
-  font-size: 0.8rem;
+  font-size: 1rem;
   color: var(--text-muted);
 }
 
@@ -421,7 +420,7 @@ kbd {
 .copyright-notice {
   margin: 0;
   text-align: center;
-  font-size: 0.7rem;
+  font-size: 0.8rem;
 }
 
 .copyright-notice a {
@@ -466,8 +465,10 @@ kbd {
 ------------------------------------------------------------------ */
 
 .admonition {
-  margin: 1.5rem 0;
+  margin: 1.5rem 0 1rem;
+  font-size: 1rem;
   border: 1px solid color-mix(in srgb, var(--adm) 45%, transparent);
+  background-color: color-mix(in srgb, var(--adm) var(--adm-tint), transparent);
   border-radius: 8px;
   overflow: hidden;
 }
@@ -476,16 +477,13 @@ kbd {
    rather than a solid color strip. --adm-* knobs are set per theme. */
 .admonition-title {
   margin: 0;
-  padding: 0.3rem 1rem;
-  background-color: color-mix(in srgb, var(--adm) var(--adm-tint), transparent);
-  border-bottom: 1px solid color-mix(in srgb, var(--adm) 45%, transparent);
+  padding: 0.75rem 1rem 0.3rem;
   color: color-mix(in srgb, var(--adm) var(--adm-ink-mix), var(--adm-ink));
-  font-size: 0.9rem;
   font-weight: bold;
 }
 
 .admonition-body {
-  padding: 0.75rem 1rem 0;
+  padding: 0 1rem;
 }
 
 .tldr {
@@ -520,7 +518,7 @@ kbd {
   margin-top: 5rem;
   padding-top: 1.5rem;
   border-top: 1px solid var(--border);
-  font-size: 0.75rem;
+  font-size: 0.8rem;
   color: var(--text-muted);
   text-align: center;
 }
@@ -553,7 +551,6 @@ kbd {
   width: 100%;
   padding: 0.8rem 1rem;
   font: inherit;
-  font-size: 1.1rem;
   color: inherit;
   background: none;
   border: none;
@@ -656,7 +653,6 @@ a.search-result.selected {
 
   .topbar-title {
     font-weight: bold;
-    font-size: 1.1rem;
     color: var(--text-strong);
   }
 
diff --git a/templates/base.html b/templates/base.html
index abd5ced..56027d2 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -39,7 +39,7 @@
         
       
     
-    MoJuWo
+    Modern Julia Workflows