diff --git a/web/index.html b/web/index.html
index c042f21..d99fb28 100644
--- a/web/index.html
+++ b/web/index.html
@@ -67,6 +67,7 @@ body {
color: var(--text);
font: 17px/1.65 "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
}
+main { display: block; }
.wrap { max-width: 44rem; margin: 0 auto; padding: 0 1.25rem 6rem; }
header { padding: 3.5rem 0 2rem; }
@@ -117,12 +118,63 @@ th { font-weight: 600; color: var(--muted); }
.lede { font-size: 1.1rem; }
.note { color: var(--muted); font-size: .94rem; }
-nav.toc { background: var(--panel); border-radius: 5px; padding: 1rem 1.25rem; margin: 2rem 0 0; }
-nav.toc ul { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 2rem; }
-nav.toc li { margin: .18rem 0; font-size: .93rem; break-inside: avoid; }
+
+/* The table of contents: a bar with a toggle when the window is narrow, a
+ fixed column beside the text when there is room for one. The toggle is a
+ checkbox so that no script is needed to open it. */
+.toc-toggle { position: absolute; width: 1px; height: 1px; opacity: 0; }
+.toc-button {
+ display: block; cursor: pointer; user-select: none;
+ position: sticky; top: 0; z-index: 2;
+ padding: .6rem 1.25rem;
+ background: var(--panel); border-bottom: 1px solid var(--rule);
+ color: var(--muted); font-size: .92rem; letter-spacing: .02em;
+}
+.toc-button::before { content: "\2261\00a0\00a0"; }
+.toc-toggle:focus-visible + .toc-button { outline: 2px solid var(--accent); outline-offset: -2px; }
+nav.toc {
+ display: none;
+ background: var(--panel); border-bottom: 1px solid var(--rule);
+ padding: .75rem 1.25rem 1.1rem;
+}
+.toc-toggle:checked ~ nav.toc { display: block; }
+.toc-title {
+ display: none; /* the toggle already says "Contents" */
+ margin: 0 0 .5rem; font-size: .78rem; letter-spacing: .09em;
+ text-transform: uppercase; color: var(--muted);
+}
+nav.toc ul { list-style: none; padding: 0; margin: 0; }
+nav.toc li { margin: 0; font-size: .93rem; }
+nav.toc a {
+ display: block; padding: .2rem 0; color: var(--muted);
+ text-decoration: none; border-left: 2px solid transparent; padding-left: .6rem;
+}
+nav.toc a:hover { color: var(--link); border-left-color: var(--accent); }
+h2, h3 { scroll-margin-top: 3.5rem; }
+
+/* Nineteen entries one to a line fills a phone screen, so the opened panel
+ goes to two columns as soon as there is room for them. */
+@media (min-width: 30rem) and (max-width: 65.99rem) {
+ nav.toc ul { columns: 2; column-gap: 1.5rem; }
+ nav.toc li { break-inside: avoid; }
+}
footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--rule); color: var(--muted); font-size: .92rem; }
-@media (max-width: 34rem) { nav.toc ul { columns: 1; } }
+/* Wide enough for both: the contents become a column of their own, scrolling
+ independently of the page, and the text moves over to sit beside it. */
+@media (min-width: 66rem) {
+ .toc-button { display: none; }
+ nav.toc {
+ display: block;
+ position: fixed; top: 0; left: 0; bottom: 0;
+ width: 15rem; overflow-y: auto; overscroll-behavior: contain;
+ background: none; border-bottom: 0; border-right: 1px solid var(--rule);
+ padding: 3.6rem 1rem 2rem 1.5rem;
+ }
+ main { margin-left: 15rem; }
+ .toc-title { display: block; }
+ h2, h3 { scroll-margin-top: 1rem; }
+}
.c { color: var(--c-comment); font-style: italic; }
.s { color: var(--c-string); }
@@ -133,34 +185,11 @@ footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ru
-
-
-
-
-
A statically typed Lisp for game development. Clojure's brackets,
- C's memory and value model, no garbage collector.
-
-
-
Flan compiles s-expressions to LLVM IR and then to a native binary.
-There are no object headers, so a Flan struct is exactly its C struct. There is no
-collector, so nothing runs between frames that you did not write. And a running
-program can be edited: a function recompiled in Emacs is installed into the live
-process at its next frame boundary, in about twenty milliseconds.
-
-
This page describes the compiler as it is, not as it is planned. Where
-something is designed but not built, it says so and gives the message the compiler
-prints for it. Every Flan program on this page is a file in web/examples/
-with its output recorded beside it; sh web/examples/check.sh runs them all
-and compares, and quotes.sh re-derives the blocks that are transcripts
-rather than programs. If the page and the compiler disagree, one of those two goes
-red.
+
+
+
+
+
+
+
+
A statically typed Lisp for game development. Clojure's brackets,
+ C's memory and value model, no garbage collector.
+
+
+
Flan compiles s-expressions to LLVM IR and then to a native binary.
+There are no object headers, so a Flan struct is exactly its C struct. There is no
+collector, so nothing runs between frames that you did not write. And a running
+program can be edited: a function recompiled in Emacs is installed into the live
+process at its next frame boundary, in about twenty milliseconds.
+
+
This page describes the compiler as it is, not as it is planned. Where
+something is designed but not built, it says so and gives the message the compiler
+prints for it. Every Flan program on this page is a file in web/examples/
+with its output recorded beside it; sh web/examples/check.sh runs them all
+and compares, and quotes.sh re-derives the blocks that are transcripts
+rather than programs. If the page and the compiler disagree, one of those two goes
+red.
+
What Flan is
A minimal Lisp for games. In one line: Odin with a Lisp frontend and a live REPL.
@@ -1304,6 +1362,7 @@ macro is a function from Form to Form, which needs