Put the contents beside the text instead of above it

Nineteen entries at the top of a long page are scrolled past once and then
unreachable. A fixed column stays put and scrolls on its own. Below 66rem there
is not room for two columns, so it collapses to a bar with a toggle — a
checkbox, so the page still needs no script to navigate.

The wordmark's dot was a circle at a guessed x, which drifted from the n
whenever the reader's serif was not the one it was measured against. It is a
full stop in the same text run now.
This commit is contained in:
Joseph Ferano 2026-09-12 04:17:27 +07:00
parent 403e598145
commit d995094b52

View File

@ -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
</style>
</head>
<body>
<div class="wrap">
<header>
<svg class="mark" viewBox="0 0 210 44" role="img" aria-label="Flan">
<text x="0" y="33" font-family="Iowan Old Style, Palatino, Georgia, serif"
font-size="38" font-weight="600" letter-spacing="1.5"
fill="currentColor">flan</text>
<circle cx="128" cy="28" r="3.4" fill="var(--accent)"/>
</svg>
<p class="tagline">A statically typed Lisp for game development. Clojure's brackets,
C's memory and value model, no garbage collector.</p>
</header>
<p class="lede">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.</p>
<p>This page describes the compiler <em>as it is</em>, 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 <code>web/examples/</code>
with its output recorded beside it; <code>sh web/examples/check.sh</code> runs them all
and compares, and <code>quotes.sh</code> re-derives the blocks that are transcripts
rather than programs. If the page and the compiler disagree, one of those two goes
red.</p>
<input type="checkbox" id="toc-toggle" class="toc-toggle">
<label class="toc-button" for="toc-toggle">Contents</label>
<nav class="toc" aria-label="Contents">
<p class="toc-title">Contents</p>
<ul>
<li><a href="#what">What Flan is</a></li>
<li><a href="#start">Getting started</a></li>
@ -184,6 +213,35 @@ red.</p>
</ul>
</nav>
<main>
<div class="wrap">
<header>
<svg class="mark" viewBox="0 0 118 44" role="img" aria-label="Flan">
<!-- The dot is a full stop in the same run, so it stays beside the n
whichever serif the reader actually has. -->
<text x="0" y="33" font-family="Iowan Old Style, Palatino, Georgia, serif"
font-size="38" font-weight="600" letter-spacing="1"
fill="currentColor">flan<tspan fill="var(--accent)">.</tspan></text>
</svg>
<p class="tagline">A statically typed Lisp for game development. Clojure's brackets,
C's memory and value model, no garbage collector.</p>
</header>
<p class="lede">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.</p>
<p>This page describes the compiler <em>as it is</em>, 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 <code>web/examples/</code>
with its output recorded beside it; <code>sh web/examples/check.sh</code> runs them all
and compares, and <code>quotes.sh</code> re-derives the blocks that are transcripts
rather than programs. If the page and the compiler disagree, one of those two goes
red.</p>
<h2 id="what">What Flan is</h2>
<p>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 <code>Form</code> to <code>Form</code>, which needs
</footer>
</div>
</main>
<script>
// A small hand-written highlighter for the Flan blocks. One pass, no library.