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/src/optimizing/index.md b/src/optimizing/index.md index 5f62b2c..5b7b217 100644 --- a/src/optimizing/index.md +++ b/src/optimizing/index.md @@ -170,7 +170,7 @@ For sharing profiles with others (e.g., on Slack or Discourse), [StatProfilerHTM No matter which tool you use, if your code is too fast to collect samples, you may need to run it multiple times in a loop. {% %} - To visualize memory allocation profiles, use PProf.jl or VSCode's `@profview_allocs`. + To visualize memory allocation profiles, use PProf.jl or VS Code's `@profview_allocs`. A known issue with the allocation profiler is that it is not able to determine the type of every object allocated, instead `Profile.Allocs.UnknownType` is shown instead. Inspecting the call graph can help identify which types are responsible for the allocations. {% %} diff --git a/src/sharing/index.md b/src/sharing/index.md index 43bacbd..e97b3e9 100644 --- a/src/sharing/index.md +++ b/src/sharing/index.md @@ -145,7 +145,7 @@ If you want to have more control over your tests, you can try * [ReTest.jl](https://github.com/JuliaTesting/ReTest.jl) to define tests next to the source code and control their execution. * [TestSetExtensions.jl](https://github.com/ssfrr/TestSetExtensions.jl) to make test set outputs more readable. * [TestReadme.jl](https://github.com/thchr/TestReadme.jl) to test whatever code samples are in your README. -* [ReTestItems.jl](https://github.com/JuliaTesting/ReTestItems.jl) for an alternative take on VSCode's test item framework. +* [ReTestItems.jl](https://github.com/JuliaTesting/ReTestItems.jl) for an alternative take on VS Code's test item framework. {% %} Code coverage refers to the fraction of lines in your source code that are covered by tests. @@ -200,7 +200,7 @@ Aqua.test_all(MyAwesomePackage) Meanwhile, [JET.jl](https://github.com/aviatesk/JET.jl) is a complementary tool, similar to a static linter. Here we focus on its [error analysis](https://aviatesk.github.io/JET.jl/stable/jetanalysis/), which can detect errors or typos without even running the code by leveraging type inference. -You can either use it in report mode (with a nice [VSCode display](https://www.julia-vscode.org/docs/stable/userguide/linter/#Runtime-diagnostics)) or in test mode as follows: +You can either use it in report mode (with a nice [VS Code display](https://www.julia-vscode.org/docs/stable/userguide/linter/#Runtime-diagnostics)) or in test mode as follows: ```>jet using JET, MyAwesomePackage diff --git a/src/writing/index.md b/src/writing/index.md index f9d099b..fe0188f 100644 --- a/src/writing/index.md +++ b/src/writing/index.md @@ -152,29 +152,29 @@ echo "Hello from the shell" ## Editor -{% %}VSCode is the IDE with the best Julia support.{% %} +{% %}VS Code is the IDE with the best Julia support.{% %} Most computer programs are just plain text files with a specific extension (in our case `.jl`). So in theory, any text editor suffices to write and modify Julia code. In practice, an Integrated Development Environment (or IDE) makes the experience much more pleasant, thanks to code-related utilities and language-specific plugins. -The best IDE for Julia is [Visual Studio Code](https://code.visualstudio.com/), or VSCode, developed by Microsoft. -Indeed, the [Julia VSCode extension](https://www.julia-vscode.org/) is the most feature-rich of all Julia IDE plugins. -You can download it from the VSCode Marketplace and read its [documentation](https://www.julia-vscode.org/docs/stable/). +The best IDE for Julia is [Visual Studio Code](https://code.visualstudio.com/), or VS Code, developed by Microsoft. +Indeed, the [Julia VS Code extension](https://www.julia-vscode.org/) is the most feature-rich of all Julia IDE plugins. +You can download it from the VS Code Marketplace and read its [documentation](https://www.julia-vscode.org/docs/stable/). {% %} In what follows, we will sometimes mention commands and [keyboard shortcuts](https://www.julia-vscode.org/docs/stable/userguide/keybindings/) provided by this extension. -But the only shortcut you need to remember is `Ctrl + Shift + P` (or `Cmd + Shift + P` on Mac): this opens the VSCode command palette, in which you can search for any command. +But the only shortcut you need to remember is `Ctrl + Shift + P` (or `Cmd + Shift + P` on Mac): this opens the VS Code command palette, in which you can search for any command. Type "julia" in the command palette to see what you can do. {% %} {% %} -Assuming you want to avoid the Microsoft ecosystem, [VSCodium](https://vscodium.com/) is a nearly bit-for-bit replacement for VSCode, but with an open source license and without telemetry. -If you don't want to use VSCode at all, other options include [Emacs](https://www.gnu.org/software/emacs/) and [Vim](https://www.vim.org/). +Assuming you want to avoid the Microsoft ecosystem, [VSCodium](https://vscodium.com/) is a nearly bit-for-bit replacement for VS Code, but with an open source license and without telemetry. +If you don't want to use VS Code at all, other options include [Emacs](https://www.gnu.org/software/emacs/) and [Vim](https://www.vim.org/). Check out [JuliaEditorSupport](https://github.com/JuliaEditorSupport) to see if your favorite IDE has a Julia plugin. -The available functionalities should be roughly similar to those of VSCode, at least for the basic aspects like running code. +The available functionalities should be roughly similar to those of VS Code, at least for the basic aspects like running code. You may also want to download the [JuliaMono](https://juliamono.netlify.app/) font for esthetically pleasant unicode handling. {% %} @@ -205,7 +205,7 @@ This will automatically update changes to function definitions in the file in th [Running code](https://www.julia-vscode.org/docs/stable/userguide/runningcode/) is made much easier by the following commands: -* `Julia: Restart REPL` (shortcut `Alt + J` then `Alt + R`) - this will open or restart the integrated Julia REPL. It is different from opening a plain VSCode terminal and launching Julia manually from there. +* `Julia: Restart REPL` (shortcut `Alt + J` then `Alt + R`) - this will open or restart the integrated Julia REPL. It is different from opening a plain VS Code terminal and launching Julia manually from there. * `Julia: Execute Code in REPL and Move` (shortcut `Shift + Enter`) - this will execute the selected code in the integrated Julia REPL, like a notebook. {% %} @@ -289,7 +289,7 @@ println("hello world") [Quarto](https://quarto.org/) "is an open-source scientific and technical publishing system." Quarto makes a plain text markdown file (`.md`) alternative called Quarto markdown file (`.qmd`). -Quarto markdown files like plain text markdown files also integrate with editors, such as VSCode. +Quarto markdown files like plain text markdown files also integrate with editors, such as VS Code. {% %} @@ -331,7 +331,7 @@ engine: julia Below is an executable code chunk. -If this file were opened in an editor such as VSCode one could execute the `println("hello world")` Julia code and view the output, like in a notebook. +If this file were opened in an editor such as VS Code one could execute the `println("hello world")` Julia code and view the output, like in a notebook. ```{julia} println("hello world") @@ -362,7 +362,7 @@ Create a separate local environment for each of your projects to contain the dep {% %} -You can configure the [environment](https://www.julia-vscode.org/docs/stable/userguide/env/) in which a VSCode Julia REPL opens. +You can configure the [environment](https://www.julia-vscode.org/docs/stable/userguide/env/) in which a VS Code Julia REPL opens. Just click the `Julia env: ...` button at the bottom. Note however that the Julia version itself will always be the default one from `juliaup`. {% %} @@ -492,7 +492,7 @@ More generally, the startup file allows you to define your own favorite helper f Here are a few more startup packages that can make your life easier once you know the language better: -* [AbbreviatedStackTraces.jl](https://github.com/BioTurboNick/AbbreviatedStackTraces.jl) allows you to shorten error stacktraces, which can sometimes get pretty long (beware of its [interactions with VSCode](https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/issues/38)) +* [AbbreviatedStackTraces.jl](https://github.com/BioTurboNick/AbbreviatedStackTraces.jl) allows you to shorten error stacktraces, which can sometimes get pretty long (beware of its [interactions with VS Code](https://github.com/BioTurboNick/AbbreviatedStackTraces.jl/issues/38)) * [Term.jl](https://github.com/FedeClaudi/Term.jl) offers a completely new way to display things like types and errors (see the [advanced configuration](https://fedeclaudi.github.io/Term.jl/stable/adv/adv/) to enable it by default). {% %} @@ -593,7 +593,7 @@ In particular, note that `@debug` messages are suppressed by default. You can enable them through the `JULIA_DEBUG` environment variable if you specify the source module name, typically `Main` or your package module. {% %} -Beyond the built-in logging utilities, [ProgressLogging.jl](https://github.com/JuliaLogging/ProgressLogging.jl) has a macro `@progress`, which interfaces nicely with VSCode and Pluto to display progress bars. +Beyond the built-in logging utilities, [ProgressLogging.jl](https://github.com/JuliaLogging/ProgressLogging.jl) has a macro `@progress`, which interfaces nicely with VS Code and Pluto to display progress bars. And [Suppressor.jl](https://github.com/JuliaIO/Suppressor.jl) can sometimes be handy when you need to suppress warnings or other bothersome messages (use at your own risk). ## Debugging @@ -728,7 +728,7 @@ About to run: (^)(2, 64) {% %} -VSCode offers a nice [graphical interface for debugging](https://www.julia-vscode.org/docs/stable/userguide/debugging/). +VS Code offers a nice [graphical interface for debugging](https://www.julia-vscode.org/docs/stable/userguide/debugging/). Click left of a line number in an editor pane to add a _breakpoint_, which is represented by a red circle. In the debugging pane of the Julia extension, click `Run and Debug` to start the debugger. The program will automatically halt when it hits a breakpoint. diff --git a/static/assets/favicon.png b/static/assets/favicon.png deleted file mode 100644 index 6bb6d57..0000000 Binary files a/static/assets/favicon.png and /dev/null differ diff --git a/static/assets/logo.svg b/static/assets/logo.svg index e71a420..bc7ba61 100644 --- a/static/assets/logo.svg +++ b/static/assets/logo.svg @@ -1 +1 @@ - + 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..ed62cc8
--- /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 (GitHub Primer's ANSI palettes, one
+    per theme, matching the giallo token themes).
+================================================================== */
+
+: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: #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) {
+  :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: #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;
+  }
+}
+
+:root[data-theme="dark"] {
+  --code-text: #e1e4e8;
+  --chip-bg: #2f3742;
+  --chip-text: #adbac7;
+  --chip-julia: #c79fe0;
+  --chip-repl: #7ecb6f;
+
+  --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;
+}
+
+/* ------------------------------------------------------------------
+    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..98eafb2
--- /dev/null
+++ b/static/css/site.css
@@ -0,0 +1,728 @@
+/* ==================================================================
+    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: 16px;
+  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: 1.5px solid 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);
+}
+
+.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: 1rem;
+  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;
+}
+
+/* Section currently on screen, tracked by the scrollspy in js/ui.js */
+.menu-list-link.current {
+  color: var(--link);
+}
+
+/* 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.8rem;
+}
+
+.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: light wash of the accent, title inline with the body
+------------------------------------------------------------------ */
+
+.admonition {
+  margin: 1.5rem 0 1rem;
+  padding: 0.75rem 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;
+}
+
+/* A quiet lead-in: the accent tinted toward the ink, run into the first
+   sentence after a colon. --adm-* knobs are set per theme. */
+.admonition-title {
+  color: color-mix(in srgb, var(--adm) var(--adm-ink-mix), var(--adm-ink));
+  font-weight: bold;
+}
+
+/* The box's own padding provides the spacing the outer margins would. */
+.admonition-body > :first-child {
+  margin-top: 0;
+}
+
+.admonition-body > :last-child {
+  margin-bottom: 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
+------------------------------------------------------------------ */
+
+/* The sidebar lists the current page's headings whenever it is on screen,
+   so the in-content copy only earns its place in the mobile layout, where
+   the sidebar is hidden behind the hamburger drawer. */
+.toc {
+  display: none;
+}
+
+.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.8rem;
+  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;
+  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;
+    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;
+  }
+
+  .toc {
+    display: block;
+  }
+
+  .page {
+    padding: 1.5rem 1.25rem 1rem;
+  }
+
+  h1,
+  h2,
+  h3,
+  h4 {
+    scroll-margin-top: 4.25rem;
+  }
+}
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644
index 0000000..0112e57
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/static/favicon.svg b/static/favicon.svg
new file mode 100644
index 0000000..5e387c3
--- /dev/null
+++ b/static/favicon.svg
@@ -0,0 +1 @@
+
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..9d3f281
--- /dev/null
+++ b/static/js/ui.js
@@ -0,0 +1,91 @@
+// 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();
+  });
+
+  // Scrollspy: mark the section currently being read in the sidebar list.
+  // Current is the last heading above the reading line — the heading's own
+  // scroll-margin-top plus some slack, so following a section link also
+  // highlights the section it jumps to.
+  var spyLinks = [];
+  var spyHeadings = [];
+  document.querySelectorAll(".menu-list-child-list.active .menu-list-link")
+    .forEach(function (link) {
+      var heading = document.getElementById(decodeURIComponent(link.hash.slice(1)));
+      if (heading) {
+        spyLinks.push(link);
+        spyHeadings.push(heading);
+      }
+    });
+  if (spyHeadings.length) {
+    var currentLink = null;
+    var updateSpy = function () {
+      var line = parseFloat(getComputedStyle(spyHeadings[0]).scrollMarginTop) + 8;
+      var current = null;
+      for (var i = 0; i < spyHeadings.length; i++) {
+        if (spyHeadings[i].getBoundingClientRect().top <= line) current = spyLinks[i];
+      }
+      // A short final section may never reach the reading line; count it
+      // once the page is scrolled to the bottom.
+      if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2) {
+        current = spyLinks[spyLinks.length - 1];
+      }
+      if (current !== currentLink) {
+        if (currentLink) currentLink.classList.remove("current");
+        if (current) current.classList.add("current");
+        currentLink = current;
+      }
+    };
+    var spyPending = false;
+    window.addEventListener("scroll", function () {
+      if (spyPending) return;
+      spyPending = true;
+      requestAnimationFrame(function () {
+        spyPending = false;
+        updateSpy();
+      });
+    }, { passive: true });
+    window.addEventListener("resize", updateSpy);
+    updateSpy();
+  }
+})();
diff --git a/templates/base.html b/templates/base.html
index bf37f53..f541807 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,40 +4,64 @@
 
   
   
-  
-  
-  
-  
-  
-  
-  
-  
+  
+  
+  
+  
+  
+  
+  
+  
   {%- block head_extra %}{% endblock %}
   {% block title %}{{ config.title }}{% endblock %}
 
 
 
+  
+ + Modern Julia Workflows + +
+ {% include "partials/sidebar.html" %} -
-
+
+
-
+ + {% include "partials/search.html" %} + {%- block foot_extra %}{% endblock %} diff --git a/templates/components.html b/templates/components.html index ddbc3f5..b71cd2b 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 wrappers are HTML #} +{# blocks, and the blank lines around the body keep it regular markdown. #} +{# The title is a span glued to the front of the body so that markdown folds #} +{# both into one paragraph, reading "Title: body ...". 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) %} +
+
-**TLDR**: {{ body | trim | safe }} +{{ title }}: {{ 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"