Project Notes
zero-JS, cookie-free, monolithic static site generator
arachnopress builds a zero-JS, cookie-free static site from simple
hand-written HTML fragments within subdirectories of articles/.
It has no Markdown layer or client-side runtime, and provides freedom to
include any HTML content the user requires.
The build writes a single index.html with dynamic theming, flexible
navigation and layout suitable for desktop or mobile, sortable article and
section indexes, a static contact form, image and download blocks, and
collapsible code blocks with plain-text or optional server-side syntax
highlighting (escaped/tokenized at build time).
All dynamic features are provided using only the CSS within styles.css
(provided as-is) and nav-sections.css (created at build time).
The build is broadly POSIX-compatible, using sh, make, and standard
Unix tools, supporting BSD, GNU/Linux, macOS, and others.
The only known POSIX exceptions are: mktemp (broadly available) and
the optional syntax highlighters, pygments or source-highlight.
Source articles are read from articles/. Generated
index.html and nav-sections.css are written at
the project root.
Run the build from the project root. Settings may be supplied through the
environment or make command line. Make passes them through the recipe
environment; defaults remain in tools/build.sh.
The build accepts site identity, separate generator name and numeric dotted version, default theme, article order, highlighter, automatic code footer threshold, license file, and copyright holder.
Each article is stored at articles/<slug>/article.html.
Its article id must equal the directory slug and
data-title is required. Optional data-created
and data-modified dates control sorting. Missing creation
dates use the current UTC build date; missing modification dates use the
creation date. Interface, article, section, and generated block IDs share
one collision registry. The build emits title, creation, and modification
order lists.
Generator markers are file-backed, one-line empty elements. Referenced paths may contain subdirectories but must be relative to the article and must not escape it. Displayed sizes and checksums use the raw files.
Plain one-line h2 and h3 headings are included
in the article's section index. Each h3 belongs to the
preceding h2. The build preserves an explicit id
or generates a safe unique anchor.
Shared index layout is defined in styles.css;
nav-sections.css selects the current article and section.
Landscape subsections flow within the single vertically scrolling section
list. Narrow portrait navigation lists are independent horizontal scrollers
with fixed tracks for scrollbar stability, while their metadata remains in
a compact non-scrolling row. Landscape labels step down in size from article
to section to subsection. A selected subsection uses a quieter palette
variation while its parent section retains the primary highlight.
A code marker is an empty one-line pre with an article-local
data-src. data-lang selects highlighting;
data-title, data-note, and
data-open control displayed metadata and initial state.
data-header="false" emits an always-visible standalone block
without a summary or controls. A normal block's raw link targets the
source file. Its footer repeats the title, size, raw link, and top link.
A headerless block has no footer by default. Explicit
data-footer="true" adds a footer with the title, size, and raw
link; no navigation arrow is rendered because there is no header target.
Raw code is escaped before insertion. Highlighted output is used only when the selected highlighter succeeds. Otherwise escaped source is emitted.
Without data-footer, blocks at or below
CODE_FOOTER_LINES omit the footer. Explicit
true or false overrides the threshold. Headerless
blocks ignore the threshold and require an explicit true.
An image marker is an empty one-line figure with an
article-local data-src. It accepts data-alt,
data-caption, data-open,
data-title, data-note, and
data-footer. data-header="false" emits an
always-visible standalone image without a summary or controls. Its footer
is absent by default; explicit data-footer="true" adds the
title, size, and raw link. Images are lazy-loaded and contained within the
themed block. A normal block's footer also contains a top link.
A download marker is an empty one-line div with one
article-local data-src. Optional data-title and
data-note change displayed text, not the target. The rendered
link shows the raw file size and a SHA256 checksum when a supported local
checksum tool is available.
Pygments is the default highlighter; Source-highlight and no highlighting
are also supported. With Pygments, an omitted language uses filename
inference and data-lang="auto" may also guess from content.
An explicit language requests that lexer. Missing tools and failed
highlighting fall back to escaped source. Successful highlighted output
is filtered to unwrap whitespace-only Pygments and Source-highlight spans,
preserving their whitespace while reducing the generated DOM.
Source-highlight uses tools/html-fragment.outlang to emit an
HTML fragment rather than a complete document.
Theme selection uses HTML radio inputs and CSS only. The build copies
tools/theme-menu.html, marks the configured default, and uses
:has() to select theme variables. The checked theme is
highlighted using the active theme palette.
The page has a sticky header, an article index, and an article pane. The first article is shown by default; URL fragments select other articles and sections.
Wide layouts use a side index with subsections nested below the selected section. Narrow portrait layouts use horizontal article and section rows, adding a third subsection row only when the selected section has one. All layouts use document scrolling. Article content has a bounded readable width, while section headings retain a wider manual-page span. Fixed portrait navigation tracks reserve scrollbar space, while the metadata row is sized to fit without one. At portrait widths of 360px or less, and landscape heights of 330px or less, the descriptive and build-date entries are hidden while the generator identity, Contact control, and license remain. Coarse-pointer controls retain touch-sized targets. Landscape side indexes reserve room for two complete items in each main list, then give the article and section lists approximately one third and two thirds of the remaining space. A short article list stays content-sized so its unused share passes to the section list.
Code, images, downloads, popovers, indexes, and highlighting classes use
shared theme variables in styles.css. Syntax colours retain
each palette hue while mixing toward a readable foreground; Pygments
token subclasses keep distinct semantic roles.
The metadata Contact control opens a native HTML popover containing an
optional reply address and a required message of at most 500 characters.
The zero-JS form adds a fixed
<sanitised-generator-name>_contact=1 query item before
submitting a GET request to the static site root. For the default name,
this is arachnopress_contact=1, making contact requests easy
to identify without coupling the marker to the generator version. Its
fragment targets a CSS confirmation panel after the new page request.
HTTPS encrypts the request in transit, but the values remain in the URL
and server log, so the form warns against confidential information.
The license popover is rendered from a plain text template. The build
replaces <YEAR> and
<COPYRIGHT_HOLDER>; the latter defaults to
username@hostname.
The build completes temporary index.html and
nav-sections.css files before publishing either output. The
page links styles.css and embeds the favicon as a data URL.
The generated files are static and can be served without any server-side
scripting, or opened locally with file://. Minimal OpenBSD
acme-client(1) and httpd(8) examples follow.