Technical reference
arachnopress
Build a zero-JS static article site from hand-written HTML fragments.
DESCRIPTION
arachnopress reads hand-written HTML articles below
articles/ and builds a single-page or multi-page static site.
The generated interface uses only HTML and CSS; it requires no JavaScript,
cookies, or browser storage.
Generated output may be opened directly through file:// or
served as static files. It requires no CGI, PHP, application server, or
database.
The build uses sh, make, and standard Unix
utilities on BSD, Linux, and macOS. Pygments and GNU Source-highlight are
optional. A missing or failed highlighter falls back to escaped source.
FEATURES
- Editable HTML fragments with arbitrary HTML where required; no Markdown or template pipeline.
- HTML and CSS interface without JavaScript, cookies, or browser storage.
-
POSIX-style
shandmakebuild for BSD, Linux, and macOS. - Single-page or per-article output, unlisted articles, and extensionless internal URLs.
- Responsive CSS navigation, sortable article indexes, selectable themes, and automatic light/dark variants.
- Pygments or Source-highlight output with escaped-source fallback and reduced span markup.
- File-backed code, download, and image blocks with raw links, checksums, image sizing, framing, alignment, and text flow.
- Article dates, custom branding, SVG favicons, native popovers, and a GET contact form for access-log collection.
-
Direct
file://access or static-file deployment without CGI, PHP, an application server, or a database.
DOWNLOADS
arachnopress_1.0.53.tar.gzgzipped source archive
SYNOPSIS
Run make build from the project root. It builds every source
article and replaces site/. Supply settings through the
environment or as make command-line variables.
Maintainers run make release followed by
make full to publish and integrate a generator release. Run
every target from the project root. Do not store source files in or run
make from site/; each successful target replaces
it.
Routine buildRun from the project rootraw
cd /path/to/arachnopress
make build
# Override only the settings required for this build.
make build SITE_TITLE="Example Site" DEFAULT_THEME=solarized-light
# Use a fixed theme and omit the automatic mode control.
make build DEFAULT_THEME=solarized-light DEFAULT_THEME_AUTO=false
# Omit the Theme selector.
make build DEFAULT_THEME_SELECTOR=false
# Generate one HTML file per article.
make build SITE_MODE=multi-page
# Exclude unlisted articles.
make build SITE_MODE_UNLISTED=false
REQUIREMENTS
Required utilities
The build requires a POSIX-like operating environment with
sh, make, awk, sed,
grep, sort, tr, date,
dirname, pwd,
printf, mktemp, mkdir, rm,
wc, cat, cksum,
chmod, mv, cp, and find.
The release and full profiles also require cmp,
tar, and gzip support in tar.
mktemp is widely available but is not specified by POSIX.
Optional utilities
- pygmentize
- Provides Pygments syntax highlighting. This is the default highlighter when the command is available.
- source-highlight
- Provides GNU Source-highlight syntax highlighting when selected.
- sha256, sha256sum, shasum, or openssl
- Provides SHA256 values for download blocks. The first available implementation is used. Downloads remain usable without a checksum.
Browser features
The generated interface requires browser support only for HTML and CSS.
It uses CSS :has(), :target,
prefers-color-scheme, native
details/summary, and the HTML popover API.
SOURCE LAYOUT
Maintained files
- Makefile
-
Defines
build,release, andfull, their defaults, and their explicit inclusion lists. - styles.css
- Defines layout, themes, syntax colours, responsive behaviour, popovers, and generated block presentation. Targets copy it into their fresh site output but do not modify it.
- THIRD_PARTY_NOTICES.txt
- Identifies the upstream colour schemes, sources, authors, and licences. The license popover and optional Theme popover link to this file.
- licenses/
- Contains the retained third-party copyright and licence notices.
- tools/build.sh
-
Discovers, validates, orders, and renders articles. It generates
the selected page layout,
favicon.svg, automatic-theme rules, and embedded navigation rules in the selected build root. - tools/build-profile.sh
- Stages, builds, publishes, cleans, and packages target output.
- tools/theme-menu.html
-
Supplies theme controls and optional
data-auto-darkanddata-auto-lightpair mappings. Its IDs are reserved as page anchors. - tools/license.txt
- Supplies the authoritative static project-license text. Its first non-blank line is the displayed title.
- tools/html-fragment.outlang
- Configures Source-highlight to emit an HTML fragment.
- articles/<slug>/article.html
- Supplies one article. The directory name is the article slug and must match the article element ID.
- articles/<slug>/*
-
Supplies article-local code, download, and image files. These paths are
copied below
site/articles/and linked from the page.
The example tree shows maintained source and make build
output. site/ contains deployed files and copied articles. The
generator article is articles/arachnopress;
GENERATOR_VERSION names release archives.
Project layout after make buildraw↓
.
|-- Makefile
|-- README.arachnopress
|-- THIRD_PARTY_NOTICES.txt
|-- articles
| |-- arachnopress
| | |-- article.html
| | `-- ...
| |-- irc
| | |-- article.html
| | `-- ...
| `-- openbsd-rdsshd
| |-- article.html
| `-- ...
|-- licenses
| |-- mit.txt
| `-- oksolar-cc0.txt
|-- site
| |-- THIRD_PARTY_NOTICES.txt
| |-- articles
| | |-- arachnopress
| | | |-- article.html
| | | `-- ...
| | |-- irc
| | | |-- article.html
| | | `-- ...
| | `-- openbsd-rdsshd
| | |-- article.html
| | `-- ...
| |-- favicon.svg
| |-- index.html
| |-- licenses
| | |-- mit.txt
| | `-- oksolar-cc0.txt
| |-- styles.css
| `-- theme-auto.css
|-- styles.css
`-- tools
|-- build-profile.sh
|-- build.sh
|-- html-fragment.outlang
|-- license.txt
`-- theme-menu.html
Source components
The Makefile defines the targets, profile defaults, and inclusion lists.
MakefileComplete build entry pointraw↓
.PHONY: build release full
.NOTPARALLEL:
# Site-profile defaults. Edit RELEASE_SITE_ENV for release, or PUBLIC_SITE_ENV
# for build and full. Environment and command-line make values take precedence
# without being expanded into shell source by make.
RELEASE_SITE_ENV = \
SITE_TITLE="$${SITE_TITLE:-arachnopress}" \
SITE_ICON="$${SITE_ICON:-䷖}" \
SITE_ICON_COLOUR="$${SITE_ICON_COLOUR:-}" \
SITE_ICON_PATH="$${SITE_ICON_PATH:-}" \
SITE_ICON_PATH_THEME="$${SITE_ICON_PATH_THEME:-false}" \
SITE_URL_STYLE="$${SITE_URL_STYLE:-html}" \
SITE_MODE="$${SITE_MODE:-single-page}" \
SITE_MODE_UNLISTED="$${SITE_MODE_UNLISTED:-false}" \
DEFAULT_THEME="$${DEFAULT_THEME:-solarized-dark}" \
DEFAULT_THEME_AUTO="$${DEFAULT_THEME_AUTO:-false}" \
DEFAULT_THEME_SELECTOR="$${DEFAULT_THEME_SELECTOR:-true}"
PUBLIC_SITE_ENV = \
SITE_TITLE="$${SITE_TITLE:-arachnogoat}" \
SITE_ICON="$${SITE_ICON:-䷪}" \
SITE_ICON_COLOUR="$${SITE_ICON_COLOUR:-}" \
SITE_ICON_PATH="$${SITE_ICON_PATH-articles/arachnopress/arachnogoatsundual.svg}" \
SITE_ICON_PATH_THEME="$${SITE_ICON_PATH_THEME:-true}" \
SITE_URL_STYLE="$${SITE_URL_STYLE:-extensionless}" \
SITE_MODE="$${SITE_MODE:-single-page}" \
SITE_MODE_UNLISTED="$${SITE_MODE_UNLISTED:-true}" \
DEFAULT_THEME="$${DEFAULT_THEME:-everforest-hard-dark}" \
DEFAULT_THEME_AUTO="$${DEFAULT_THEME_AUTO:-true}" \
DEFAULT_THEME_SELECTOR="$${DEFAULT_THEME_SELECTOR:-true}"
BUILD_ENV = \
GENERATOR_LABEL="$${GENERATOR_LABEL:-arachnopress}" \
GENERATOR_VERSION="$${GENERATOR_VERSION:-1.0.53}" \
ARTICLE_ORDER="$${ARTICLE_ORDER:-title}" \
HIGHLIGHTER="$${HIGHLIGHTER:-pygments}" \
CODE_FOOTER_LINES="$${CODE_FOOTER_LINES:-23}"
RUNTIME_FILES = \
THIRD_PARTY_NOTICES.txt \
styles.css \
licenses
RELEASE_FILES = \
Makefile \
README.arachnopress \
$(RUNTIME_FILES) \
tools/build.sh \
tools/build-profile.sh \
tools/html-fragment.outlang \
tools/license.txt \
tools/theme-menu.html
build:
$(PUBLIC_SITE_ENV) $(BUILD_ENV) \
sh tools/build-profile.sh build $(RUNTIME_FILES) articles
release:
$(RELEASE_SITE_ENV) $(BUILD_ENV) \
sh tools/build-profile.sh release \
$(RELEASE_FILES) articles/arachnopress
full:
$(PUBLIC_SITE_ENV) $(BUILD_ENV) \
sh tools/build-profile.sh full $(RELEASE_FILES) articles
BUILD MODEL
Discovery and validation
Each target copies its Makefile inclusion list to a private
.site-build.* tree in the project root. Build and full select
every staged article; release selects articles/arachnopress.
Inclusion paths must exist and contain no symbolic links.
tools/build.sh validates and renders the staged tree. Its
transient files remain below TMPDIR, or /tmp
when TMPDIR is unset.
Publication and ownership
The target completes the staged output before replacing
site/. A failure before publication preserves the previous
site/.
Exit and handled signal traps remove unpublished staging and rendering files. An untrappable termination may leave them behind.
Release and full prepare the generator-release marker and archive cleanup
in staging and update the source only after publication succeeds.
All targets share site/ and must not run concurrently.
ARTICLE FORMAT
Root metadata
Each article begins with a one-line article element. Its
class list must include article. Its id must
equal the containing directory name. data-title is required.
Optional data-created and data-modified values
begin with YYYY-MM-DD. data-created defaults to
the current UTC build date; data-modified defaults to
data-created. Set both on published articles to keep visible
metadata and date ordering stable.
data-listed defaults to true and accepts only
true or false.
SITE_MODE_UNLISTED=false excludes articles marked
data-listed=false from generated HTML and navigation in
either output mode.
SITE_MODE_UNLISTED=true enables unlisted output.
Enabled single-page output embeds unlisted articles after listed articles,
omits them from article indexes, and exposes them through their article,
section, and subsection fragments. Enabled multi-page output writes an
unlisted article as slug.html, omits it from listed-page
indexes, and includes it only in its own index.
A multi-page unlisted document includes a noindex, nofollow
directive. A single-page article shares index.html and cannot
have a separate robots directive. Unlisted output remains publicly
accessible. At least one listed article is required. Multi-page mode
reserves the index slug. Targets still copy included article
directories to site/articles/.
articles/getting-started/article.htmlComplete example articleraw
<article class="article" id="getting-started" data-title="Getting Started" data-created="2026-07-09" data-modified="2026-07-09">
<header class="article-header">
<p class="kicker">Guide</p>
<h1>Getting Started</h1>
<p class="summary">
A short article built from one editable HTML fragment.
</p>
</header>
<section>
<h2>Example</h2>
<p>Article text is normal HTML.</p>
<h3>Subsection</h3>
<p>Plain one-line h3 headings appear under their preceding section.</p>
</section>
</article>
Header metadata
The source header contains an h1 and summary. The build inserts
Created and a time element before its closing tag. It adds
Updated when the dates differ and prefixes unlisted articles with
Unlisted. The order is Unlisted, Created, Updated, using one separator.
Indexed headings
Use h2 for major sections and h3 for genuine
subdivisions. Indexable headings contain plain text and close on the same
source line. An h3 must follow an h2. The build
preserves a valid explicit ID or derives a unique ID from the heading and
article.
Article, heading, theme, generated control, and generated block IDs share one collision registry. Explicit IDs and slugs may contain ASCII letters, digits, dots, underscores, and hyphens.
Indexed heading formsraw
<h2>BUILD</h2>
<h3>Requirements</h3>
<h3 id="custom-subsection">Explicit Subsection Anchor</h3>
<h2 id="custom-anchor">Explicit Anchor</h2>
GENERATED BLOCKS
Generated markers are empty, file-backed elements contained on one source
line. data-src is relative to the article directory. Missing
or invalid sources render visible missing blocks.
Code blocks
data-title changes the display title;
data-note adds a qualifier; and data-open
values 0, false, no,
closed, and collapsed close the block initially.
Other values leave it open. data-header="false" emits
always-visible content without a summary. A headerless block has no
footer unless data-footer="true" is explicit.
For normal blocks, an omitted data-footer hides the footer
when the source line count is at or below
CODE_FOOTER_LINES. Explicit true or false values override
that threshold. Raw links and sizes refer to the source file, whose
contents are HTML-escaped.
Code block markersKeep each marker on one lineraw
<pre class="code-block" data-src="src/example.c" data-title="example.c" data-note="A short C example" data-lang="c"></pre>
<pre class="code-block" data-src="build.sh" data-lang="sh" data-open="false" data-footer="false"></pre>
<pre class="code-block" data-src="output.txt" data-lang="text" data-header="false"></pre>
<pre class="code-block" data-src="output.txt" data-title="output.txt" data-lang="text" data-header="false" data-footer="true"></pre>
Download blocks
A download marker names exactly one file. data-title changes
only its display title and data-note adds a qualifier. The
link continues to target data-src. The block shows the raw
size and, when a supported checksum program is available, its SHA256.
The obsolete data-downloads attribute is rejected; use one
data-src marker per file.
The generator article has exactly one marker carrying
data-release="generator". The release and full targets replace
that complete line with the validated current archive name; a normal
build leaves it unchanged.
Download block markersOne source file per markerraw
<div class="article-downloads" data-src="release.tar.gz" data-title="release.tar.gz" data-note="Source archive"></div>
Image blocks
data-alt supplies alternative text.
data-caption adds a caption; data-title and
data-note label the header. data-open,
data-header, and data-footer behave as for code
blocks.
data-scale accepts small, medium,
large, or full and caps inline size at 20rem,
32rem, 48rem, or the article width. full is the default.
Images retain their aspect ratio, do not upscale, and remain within the
viewport.
data-border accepts full, fit, or
none. full is the default. fit
shrink-wraps the frame. none removes it and suppresses the
header and footer.
data-align accepts start, center, or
end; center is the default.
Above 760px, following prose and text lists may flow beside a start- or end-aligned fit or borderless block. Such a block uses at most 55% of the article measure.
Headings, generated blocks, preformatted blocks, tables, horizontal rules, consecutive images, and section ends clear the flow. Centered, full-frame, and narrower layouts keep images on their own row. Other scale, border, and alignment values are fatal. Generated images use lazy loading and asynchronous decoding.
Image block markersraw
<figure class="image-block" data-src="images/diagram.png" data-title="Diagram" data-note="Article-local image path" data-alt="Build flow diagram" data-caption="Article-local image"></figure>
<figure class="image-block" data-src="images/icon.png" data-title="Icon" data-alt="Small icon" data-scale="small" data-border="fit" data-align="start" data-header="false" data-footer="true"></figure>
<figure class="image-block" data-src="images/plain.png" data-alt="Borderless image" data-scale="small" data-border="none" data-align="end"></figure>
Missing sources
A missing or invalid source renders a missing block. A non-empty or multiline marker is fatal.
SYNTAX HIGHLIGHTING
Pygments
Pygments is the default. An omitted data-lang requests
filename inference. data-lang="auto" first uses filename
inference and may guess from content. An explicit value requests that
lexer.
Source-highlight
Source-highlight uses tools/html-fragment.outlang. An omitted
or automatic language lets the program infer its input language. The
make alias is translated to makefile.
Fallback and optimisation
A missing program, unknown lexer, failed invocation, missing output
definition, or empty highlighted result for non-empty input falls back to
escaped raw source for that block. HIGHLIGHTER=none always
uses that path.
Successful highlighted output is filtered to unwrap whitespace-only
Pygments span.w and Source-highlight
span.sh-normal elements. Their whitespace remains in the
preformatted code content.
NAVIGATION AND THEMES
Article and section selection
The build writes title, creation-date, and modification-date order lists.
ARTICLE_ORDER selects the initial list; CSS radio controls
select another.
In single-page mode, URL fragments and CSS
:target/:has() select articles, sections, and
subsections. The first listed article is visible without a target.
index.html embeds the generated navigation rules.
In multi-page mode, index.html contains the first listed
article and each article has slug.html. Each file contains one
article, its section index, and its generated navigation rules.
Title order is case-insensitive by title, then slug. Creation and modification orders are newest first by the corresponding source value, then slug.
Responsive index
Wide landscape layouts place article and section navigation in a vertical left pane. Narrow portrait layouts use independent horizontal article, section, and conditional subsection rows. Short and narrow layouts hide descriptive build metadata before hiding the generator identity, Contact, or license controls.
Theme state
DEFAULT_THEME selects the initial exact variant.
DEFAULT_THEME_AUTO adds an Exact, Auto, Light, and Dark
header control, beside the Theme button when present, and initially
selects Auto. Exact applies the selected theme unchanged. The other modes
use its light/dark mapping; Auto follows the browser preference. An
unmapped theme remains fixed.
Both controls have equal height. The mode selector has no frame or selected background; its checked symbol uses the active heading colour.
DEFAULT_THEME_SELECTOR enables the selector from
tools/theme-menu.html. Single-page selections span all
articles until reload. Multi-page selections reset when another page
loads. The mode control is independent and appears only when
DEFAULT_THEME_AUTO is true.
tools/theme-menu.htmlComplete theme menuraw↓
<div class="theme-menu" id="theme-popover" popover>
<div class="theme-head">
<h2>Theme</h2>
<button class="theme-close" type="button" popovertarget="theme-popover" popovertargetaction="hide" aria-label="Close theme menu">X</button>
</div>
<form class="theme-list" aria-label="Theme">
<fieldset>
<legend>Solarized</legend>
<input type="radio" name="theme" id="theme-solarized-dark" data-auto-dark="theme-solarized-dark" data-auto-light="theme-solarized-light" checked>
<label for="theme-solarized-dark">Dark</label>
<input type="radio" name="theme" id="theme-solarized-light" data-auto-dark="theme-solarized-dark" data-auto-light="theme-solarized-light">
<label for="theme-solarized-light">Light</label>
</fieldset>
<fieldset>
<legend>Selenized</legend>
<input type="radio" name="theme" id="theme-selenized-dark" data-auto-dark="theme-selenized-dark" data-auto-light="theme-selenized-light">
<label for="theme-selenized-dark">Dark</label>
<input type="radio" name="theme" id="theme-selenized-black" data-auto-dark="theme-selenized-black" data-auto-light="theme-selenized-white">
<label for="theme-selenized-black">Black</label>
<input type="radio" name="theme" id="theme-selenized-light" data-auto-dark="theme-selenized-dark" data-auto-light="theme-selenized-light">
<label for="theme-selenized-light">Light</label>
<input type="radio" name="theme" id="theme-selenized-white" data-auto-dark="theme-selenized-black" data-auto-light="theme-selenized-white">
<label for="theme-selenized-white">White</label>
</fieldset>
<fieldset>
<legend>OKSolar</legend>
<input type="radio" name="theme" id="theme-oksolar-dark" data-auto-dark="theme-oksolar-dark" data-auto-light="theme-oksolar-light">
<label for="theme-oksolar-dark">Dark</label>
<input type="radio" name="theme" id="theme-oksolar-light" data-auto-dark="theme-oksolar-dark" data-auto-light="theme-oksolar-light">
<label for="theme-oksolar-light">Light</label>
</fieldset>
<fieldset>
<legend>Gruvbox Material</legend>
<input type="radio" name="theme" id="theme-gruvbox-material-hard-dark" data-auto-dark="theme-gruvbox-material-hard-dark" data-auto-light="theme-gruvbox-material-hard-light">
<label for="theme-gruvbox-material-hard-dark">Hard Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-material-hard-light" data-auto-dark="theme-gruvbox-material-hard-dark" data-auto-light="theme-gruvbox-material-hard-light">
<label for="theme-gruvbox-material-hard-light">Hard Light</label>
<input type="radio" name="theme" id="theme-gruvbox-material-medium-dark" data-auto-dark="theme-gruvbox-material-medium-dark" data-auto-light="theme-gruvbox-material-medium-light">
<label for="theme-gruvbox-material-medium-dark">Medium Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-material-medium-light" data-auto-dark="theme-gruvbox-material-medium-dark" data-auto-light="theme-gruvbox-material-medium-light">
<label for="theme-gruvbox-material-medium-light">Medium Light</label>
<input type="radio" name="theme" id="theme-gruvbox-material-soft-dark" data-auto-dark="theme-gruvbox-material-soft-dark" data-auto-light="theme-gruvbox-material-soft-light">
<label for="theme-gruvbox-material-soft-dark">Soft Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-material-soft-light" data-auto-dark="theme-gruvbox-material-soft-dark" data-auto-light="theme-gruvbox-material-soft-light">
<label for="theme-gruvbox-material-soft-light">Soft Light</label>
</fieldset>
<fieldset>
<legend>Gruvbox Mix</legend>
<input type="radio" name="theme" id="theme-gruvbox-mix-hard-dark" data-auto-dark="theme-gruvbox-mix-hard-dark" data-auto-light="theme-gruvbox-mix-hard-light">
<label for="theme-gruvbox-mix-hard-dark">Hard Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-mix-hard-light" data-auto-dark="theme-gruvbox-mix-hard-dark" data-auto-light="theme-gruvbox-mix-hard-light">
<label for="theme-gruvbox-mix-hard-light">Hard Light</label>
<input type="radio" name="theme" id="theme-gruvbox-mix-medium-dark" data-auto-dark="theme-gruvbox-mix-medium-dark" data-auto-light="theme-gruvbox-mix-medium-light">
<label for="theme-gruvbox-mix-medium-dark">Medium Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-mix-medium-light" data-auto-dark="theme-gruvbox-mix-medium-dark" data-auto-light="theme-gruvbox-mix-medium-light">
<label for="theme-gruvbox-mix-medium-light">Medium Light</label>
<input type="radio" name="theme" id="theme-gruvbox-mix-soft-dark" data-auto-dark="theme-gruvbox-mix-soft-dark" data-auto-light="theme-gruvbox-mix-soft-light">
<label for="theme-gruvbox-mix-soft-dark">Soft Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-mix-soft-light" data-auto-dark="theme-gruvbox-mix-soft-dark" data-auto-light="theme-gruvbox-mix-soft-light">
<label for="theme-gruvbox-mix-soft-light">Soft Light</label>
</fieldset>
<fieldset>
<legend>Gruvbox Original</legend>
<input type="radio" name="theme" id="theme-gruvbox-original-hard-dark" data-auto-dark="theme-gruvbox-original-hard-dark" data-auto-light="theme-gruvbox-original-hard-light">
<label for="theme-gruvbox-original-hard-dark">Hard Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-original-hard-light" data-auto-dark="theme-gruvbox-original-hard-dark" data-auto-light="theme-gruvbox-original-hard-light">
<label for="theme-gruvbox-original-hard-light">Hard Light</label>
<input type="radio" name="theme" id="theme-gruvbox-original-medium-dark" data-auto-dark="theme-gruvbox-original-medium-dark" data-auto-light="theme-gruvbox-original-medium-light">
<label for="theme-gruvbox-original-medium-dark">Medium Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-original-medium-light" data-auto-dark="theme-gruvbox-original-medium-dark" data-auto-light="theme-gruvbox-original-medium-light">
<label for="theme-gruvbox-original-medium-light">Medium Light</label>
<input type="radio" name="theme" id="theme-gruvbox-original-soft-dark" data-auto-dark="theme-gruvbox-original-soft-dark" data-auto-light="theme-gruvbox-original-soft-light">
<label for="theme-gruvbox-original-soft-dark">Soft Dark</label>
<input type="radio" name="theme" id="theme-gruvbox-original-soft-light" data-auto-dark="theme-gruvbox-original-soft-dark" data-auto-light="theme-gruvbox-original-soft-light">
<label for="theme-gruvbox-original-soft-light">Soft Light</label>
</fieldset>
<fieldset>
<legend>Tomorrow</legend>
<input type="radio" name="theme" id="theme-tomorrow" data-auto-dark="theme-tomorrow-night" data-auto-light="theme-tomorrow">
<label for="theme-tomorrow">Light</label>
<input type="radio" name="theme" id="theme-tomorrow-night" data-auto-dark="theme-tomorrow-night" data-auto-light="theme-tomorrow">
<label for="theme-tomorrow-night">Night</label>
<input type="radio" name="theme" id="theme-tomorrow-eighties" data-auto-dark="theme-tomorrow-eighties" data-auto-light="theme-tomorrow">
<label for="theme-tomorrow-eighties">Eighties</label>
<input type="radio" name="theme" id="theme-tomorrow-blue" data-auto-dark="theme-tomorrow-blue" data-auto-light="theme-tomorrow">
<label for="theme-tomorrow-blue">Blue</label>
<input type="radio" name="theme" id="theme-tomorrow-bright" data-auto-dark="theme-tomorrow-bright" data-auto-light="theme-tomorrow">
<label for="theme-tomorrow-bright">Bright</label>
</fieldset>
<fieldset>
<legend>Nord</legend>
<input type="radio" name="theme" id="theme-nord">
<label for="theme-nord">Dark</label>
</fieldset>
<fieldset>
<legend>Everforest</legend>
<input type="radio" name="theme" id="theme-everforest-hard-dark" data-auto-dark="theme-everforest-hard-dark" data-auto-light="theme-everforest-hard-light">
<label for="theme-everforest-hard-dark">Hard Dark</label>
<input type="radio" name="theme" id="theme-everforest-hard-light" data-auto-dark="theme-everforest-hard-dark" data-auto-light="theme-everforest-hard-light">
<label for="theme-everforest-hard-light">Hard Light</label>
<input type="radio" name="theme" id="theme-everforest-dark" data-auto-dark="theme-everforest-dark" data-auto-light="theme-everforest-light">
<label for="theme-everforest-dark">Medium Dark</label>
<input type="radio" name="theme" id="theme-everforest-light" data-auto-dark="theme-everforest-dark" data-auto-light="theme-everforest-light">
<label for="theme-everforest-light">Medium Light</label>
<input type="radio" name="theme" id="theme-everforest-soft-dark" data-auto-dark="theme-everforest-soft-dark" data-auto-light="theme-everforest-soft-light">
<label for="theme-everforest-soft-dark">Soft Dark</label>
<input type="radio" name="theme" id="theme-everforest-soft-light" data-auto-dark="theme-everforest-soft-dark" data-auto-light="theme-everforest-soft-light">
<label for="theme-everforest-soft-light">Soft Light</label>
</fieldset>
<fieldset>
<legend>Edge</legend>
<input type="radio" name="theme" id="theme-edge-dark" data-auto-dark="theme-edge-dark" data-auto-light="theme-edge-light">
<label for="theme-edge-dark">Dark</label>
<input type="radio" name="theme" id="theme-edge-aura" data-auto-dark="theme-edge-aura" data-auto-light="theme-edge-light">
<label for="theme-edge-aura">Aura</label>
<input type="radio" name="theme" id="theme-edge-neon" data-auto-dark="theme-edge-neon" data-auto-light="theme-edge-light">
<label for="theme-edge-neon">Neon</label>
<input type="radio" name="theme" id="theme-edge-aura-dim" data-auto-dark="theme-edge-aura-dim" data-auto-light="theme-edge-light">
<label for="theme-edge-aura-dim">Aura Dim</label>
<input type="radio" name="theme" id="theme-edge-light" data-auto-dark="theme-edge-dark" data-auto-light="theme-edge-light">
<label for="theme-edge-light">Light</label>
</fieldset>
<fieldset>
<legend>Sonokai</legend>
<input type="radio" name="theme" id="theme-sonokai">
<label for="theme-sonokai">Default</label>
<input type="radio" name="theme" id="theme-sonokai-atlantis">
<label for="theme-sonokai-atlantis">Atlantis</label>
<input type="radio" name="theme" id="theme-sonokai-andromeda">
<label for="theme-sonokai-andromeda">Andromeda</label>
<input type="radio" name="theme" id="theme-sonokai-shusia">
<label for="theme-sonokai-shusia">Shusia</label>
<input type="radio" name="theme" id="theme-sonokai-maia">
<label for="theme-sonokai-maia">Maia</label>
<input type="radio" name="theme" id="theme-sonokai-espresso">
<label for="theme-sonokai-espresso">Espresso</label>
</fieldset>
<fieldset>
<legend>Spring Night</legend>
<input type="radio" name="theme" id="theme-spring-night">
<label for="theme-spring-night">Normal</label>
<input type="radio" name="theme" id="theme-spring-night-high-contrast">
<label for="theme-spring-night-high-contrast">High Contrast</label>
</fieldset>
<fieldset>
<legend>Ayu</legend>
<input type="radio" name="theme" id="theme-ayu-dark" data-auto-dark="theme-ayu-dark" data-auto-light="theme-ayu-light">
<label for="theme-ayu-dark">Dark</label>
<input type="radio" name="theme" id="theme-ayu-mirage" data-auto-dark="theme-ayu-mirage" data-auto-light="theme-ayu-light">
<label for="theme-ayu-mirage">Mirage</label>
<input type="radio" name="theme" id="theme-ayu-light" data-auto-dark="theme-ayu-dark" data-auto-light="theme-ayu-light">
<label for="theme-ayu-light">Light</label>
</fieldset>
<fieldset>
<legend>Catppuccin</legend>
<input type="radio" name="theme" id="theme-catppuccin-latte" data-auto-dark="theme-catppuccin-mocha" data-auto-light="theme-catppuccin-latte">
<label for="theme-catppuccin-latte">Latte</label>
<input type="radio" name="theme" id="theme-catppuccin-frappe" data-auto-dark="theme-catppuccin-frappe" data-auto-light="theme-catppuccin-latte">
<label for="theme-catppuccin-frappe">Frappe</label>
<input type="radio" name="theme" id="theme-catppuccin-macchiato" data-auto-dark="theme-catppuccin-macchiato" data-auto-light="theme-catppuccin-latte">
<label for="theme-catppuccin-macchiato">Macchiato</label>
<input type="radio" name="theme" id="theme-catppuccin-mocha" data-auto-dark="theme-catppuccin-mocha" data-auto-light="theme-catppuccin-latte">
<label for="theme-catppuccin-mocha">Mocha</label>
</fieldset>
<fieldset>
<legend>Rose Pine</legend>
<input type="radio" name="theme" id="theme-rose-pine" data-auto-dark="theme-rose-pine" data-auto-light="theme-rose-pine-dawn">
<label for="theme-rose-pine">Main</label>
<input type="radio" name="theme" id="theme-rose-pine-moon" data-auto-dark="theme-rose-pine-moon" data-auto-light="theme-rose-pine-dawn">
<label for="theme-rose-pine-moon">Moon</label>
<input type="radio" name="theme" id="theme-rose-pine-dawn" data-auto-dark="theme-rose-pine" data-auto-light="theme-rose-pine-dawn">
<label for="theme-rose-pine-dawn">Dawn</label>
</fieldset>
<p class="theme-third-party">Theme sources and licences: <a href="THIRD_PARTY_NOTICES.txt">third-party notices</a></p>
</form>
</div>
CONTACT AND LICENSE
Contact request
Contact opens a native popover with an optional 254-character reply
address and a required 500-character message. The form submits GET to the
current page and targets contact-confirmation.
The first query item is a lowercase SITE_TITLE identifier
followed by _contact=1. Runs outside letters, digits, dots,
underscores, and hyphens become one underscore; edge underscores are
removed; an empty identifier becomes arachnopress. Defaults are
arachnopress_contact=1 for release and
arachnogoat_contact=1 for build and full.
Contact request shapeValues are URL-encoded by the browserraw
Browser URL with SITE_TITLE="Example Site":
https://host.example/?example_site_contact=1&reply=operator%40example.com&message=Short+message#contact-confirmation
HTTP request target logged by a server that retains query strings:
GET /?example_site_contact=1&reply=operator%40example.com&message=Short+message
Contact privacy
HTTPS encrypts the request in transit. The address and message remain in the URL, browser history, and logs that record the query string. Do not submit confidential information. The static site provides no separate message delivery or storage.
The operator extracts and processes marked requests from the server log. arachnopress provides no log-processing component.
Project license
The license popover HTML-escapes and embeds
tools/license.txt. Its first non-blank line is the title.
THIRD_PARTY_NOTICES.txt maps bundled colour palettes to
notices below licenses/.
TARGETS
- build
-
Routine site-generation target. Replaces
site/with the deployment files, every source article, and newly generated output. Leaves release markers and archives unchanged. - release
-
Maintainer target for a new generator version. Builds only the generator
article with its release download absent, replaces
site/, createsarachnopress_GENERATOR_VERSION.tar.gzin the project root, and updates the source marker. The archive excludes itself. - full
-
Maintainer target used after release. Requires the root release archive,
installs it in the staged generator article, generates the configured
article set, replaces
site/, and updates the source marker.
No clean, install, serve, or watch target is defined.
VARIABLES
RELEASE_SITE_ENV defines release site defaults.
PUBLIC_SITE_ENV defines build and full site defaults.
BUILD_ENV defines shared defaults. Environment and
command-line make values override them.
Site identity
- SITE_TITLE
-
Page title and displayed brand. It also forms the contact request marker
prefix. Defaults:
arachnopressforrelease;arachnogoatforbuildandfull. - SITE_ICON
-
Character shown beside
SITE_TITLEand rendered into the generated SVG favicon. Defaults: U+4DD6 forrelease; U+4DEA forbuildandfull. - SITE_ICON_COLOUR
-
Optional fixed colour for the Unicode header icon and generated
favicon. The value must be
#rgbor#rrggbb. When empty, the header icon follows the current article-title colour and the generated favicon uses the SVG default text fill. It is unused whenSITE_ICON_PATHis set. Default: empty. - SITE_ICON_PATH
-
Optional relative path to an SVG in the selected build tree. The build
copies it unchanged to
site/favicon.svgand uses it besideSITE_TITLE.SITE_ICONandSITE_ICON_COLOURare then unused. Defaults: empty forrelease;articles/arachnopress/arachnogoatsundual.svgforbuildandfull. Directtools/build.shexecution defaults to empty. Set it to an empty value to restore the generated text icon. - SITE_ICON_PATH_THEME
-
trueembeds theSITE_ICON_PATHSVG in the header and maps itscolour-outandcolour-inclasses to the current article background and title colours. The favicon remains an unchanged copy.truerequiresSITE_ICON_PATH.falseuses the SVG as an external header image. Defaults:falseforrelease;trueforbuildandfull. - GENERATOR_LABEL
-
Generator name displayed in site metadata. Changing it does not affect
project identity, the contact marker, source paths, or archive names.
Default:
arachnopress. - GENERATOR_VERSION
-
Generator software version, displayed after
GENERATOR_LABELand used in release archive names. Maintainers change it for a new generator release. It must contain dot-separated digits only. Default:1.0.53.
Navigation and rendering
- DEFAULT_THEME
-
Exact input ID from
tools/theme-menu.html, with or without thetheme-prefix. Defaults:solarized-darkforrelease;everforest-hard-darkforbuildandfull. - DEFAULT_THEME_AUTO
-
Theme mode control.
truerenders Exact, Auto, Light, and Dark header choices, beside the Theme button when present, and initially selects Auto. Exact applies the selected theme unchanged. The other choices use itsdata-auto-darkanddata-auto-lightmapping; Auto follows the browser preference. A theme without a mapping remains fixed.falseuses the selected exact variant and omits the control. Defaults:falseforrelease;trueforbuildandfull. - DEFAULT_THEME_SELECTOR
-
Theme selector state.
truerenders the selector in either output mode;falsefixes the selection toDEFAULT_THEME. Default:true. - SITE_MODE
-
Output layout.
single-pagewrites all generated articles toindex.html.multi-pagewritesindex.htmlplus oneslug.htmlper article. Default:single-page. - SITE_MODE_UNLISTED
-
Unlisted article generation.
trueenables unlisted output using the selectedSITE_MODE;falseexcludes unlisted articles from generated HTML and navigation. Defaults:falseforrelease;trueforbuildandfull. - SITE_URL_STYLE
-
Page-link format.
htmlretains.htmllinks.extensionlesskeeps generated.htmlfiles but usesslugand./for multi-page navigation, home links, and contact actions. Defaults:htmlforrelease;extensionlessforbuildandfull. Extensionless output requires a matching web-server rewrite. Single-page output is unchanged. - ARTICLE_ORDER
-
Initial order. Accepted values are
title;created,creation, orctime; andmodified,modification, ormtime. Default:title. - HIGHLIGHTER
-
pygments,source-highlight, ornone. Default:pygments. - CODE_FOOTER_LINES
-
Non-negative line threshold for automatically hiding code block
footers. Default:
23.
ENVIRONMENT
- PATH
- Locates required utilities and optional highlighters and checksum programs.
- TMPDIR
-
Parent for private
static-site-build.*rendering directories. Default:/tmp. Target staging uses.site-build.*below the project root.
LC_ALL is set to C.
SOURCE_HIGHLIGHT_DATADIR is unset so Source-highlight uses
its installed data files and the project output definition.
BUILD
Routine site build
Run make build from the project root. It defaults to
single-page output, includes unlisted articles, and enables the Theme and
Exact/Auto/Light/Dark controls. The extensionless URL setting has no
effect in single-page mode.
Set SITE_MODE=multi-page for per-article HTML files.
Set DEFAULT_THEME_AUTO=false or
DEFAULT_THEME_SELECTOR=false to omit either theme control.
Set SITE_MODE_UNLISTED=false to exclude unlisted articles.
Representative build outputCounts depend on article content and installed toolsraw
Built 2 listed and 1 unlisted articles into index.html.
Default theme: theme-everforest-hard-dark. Article order: title. URL style: extensionless.
Theme selector: true.
Theme mode control: exact / auto / light / dark (theme-everforest-hard-dark / theme-everforest-hard-light).
Pygments available: 51 highlighted, 15 escaped raw.
Built site output in /path/to/arachnopress/site.
Custom site icon
Store the SVG below an included article and define
colour-out and colour-in classes. Clear
SITE_ICON_PATH to restore the generated text icon.
Custom site icon buildReplace article-slug after adding an SVGraw
# Run from the project root after adding the required SVG classes.
SITE_ICON_PATH=articles/article-slug/site-icon.svg \
SITE_ICON_PATH_THEME=true make build
Generator release
After updating GENERATOR_VERSION, run
make release from the project root. It renders the generator
download as missing and creates the root archive.
Release integration
Run make full from the same project root after
make release. It requires the matching root archive and
installs it in the generated generator article. Both targets update the
source marker only after publication.
Highlighter variants
HIGHLIGHTER=none selects escaped source. Select either
installed highlighter explicitly to test its output.
Highlighter buildsRun separately from the project rootraw
# Run each command separately from the project root.
make build HIGHLIGHTER=none
make build HIGHLIGHTER=pygments
make build HIGHLIGHTER=source-highlight
REBUILD AND RECOVERY
Rebuild conditions
Re-run the build after changing an article, an article-local file,
styles.css, a tool input, or a build setting. Generated
stylesheet and favicon URLs contain cache tokens, so rebuilding publishes
new URLs when those inputs change.
Failed or interrupted builds
Correct the diagnostic and rerun the target. Before publication, a
validation, staging, or rendering failure leaves the previous
site/, source marker, source archives, and root release archive
unchanged.
A publication failure may leave site/ incomplete; rerun before
serving.
After all builds stop, remove files left by an untrappable termination. Inspect the wildcard expansions before removal.
Temporary-file recoveryRun from the project root after all builds stopraw
# Run from the project root after confirming that no build is running.
rm -rf "${TMPDIR:-/tmp}"/static-site-build.*
rm -rf .site-build.*
# Rebuild and publish a complete site tree.
make build
Remove generated output
No clean target is defined. Remove site/ without changing
maintained source. Release replaces the current-version root archive but
does not remove older root archives. Remove obsolete root archives by
exact name.
Remove generated outputRun from the project rootraw
# Run from the project root when no build is running.
rm -rf site
FILES
- site/
-
Fresh published output from the most recent target. Do not store
maintained articles or run
makein this directory. - site/index.html
- Generated entry point. It contains every generated article in single-page mode or the first listed article in multi-page mode.
- site/<slug>.html
- Multi-page output for each listed article and each enabled unlisted article.
- site/favicon.svg
-
Generated fallback or unchanged copy of
SITE_ICON_PATH. Its link contains a cache version token. - site/theme-auto.css
-
Generated when
DEFAULT_THEME_AUTO=trueand a reachable theme has an automatic mapping. It contains light/dark rules derived fromstyles.cssand the mappings intools/theme-menu.html. Its link contains the shared stylesheet cache token. - site/styles.css
- Copied site stylesheet.
- THIRD_PARTY_NOTICES.txt
-
Maintained notice for bundled colour palettes. Targets copy it into
site/and release archives. - licenses/
-
Retained third-party licence notices. Targets copy the directory into
site/and release archives. - articles/<slug>/*
-
Maintained article source and published raw, download, and image assets.
Targets copy them to
site/articles/<slug>/. - arachnopress_GENERATOR_VERSION.tar.gz
-
Maintainer release archive produced by
make releaseand consumed bymake fullfrom the same project root. Its top-level directory has the same name without the.tar.gzsuffix. - README.arachnopress
- The compact operator and interface reference.
EXIT STATUS
A target exits zero after its complete staged tree is published as
site/, after release packaging when applicable, and after
managed source cleanup. It exits non-zero on invalid settings or article
structure, missing required tool inputs, duplicate or invalid IDs, no
articles, or an unhandled command or filesystem failure.
Optional highlighting and checksum failures are not fatal. The build either emits escaped raw source or omits the checksum.
DIAGNOSTICS
- Missing release archive
-
Run
make releasewith the same generator version beforemake full. Run both commands from the same project root, not fromsite/. - DEFAULT_THEME does not exist in theme menu / Automatic theme mapping does not exist
-
Use IDs from
tools/theme-menu.html. Definedata-auto-darkanddata-auto-lighttogether, and include the source theme in its pair. - No listed articles found
-
Add or select at least one article not marked
data-listed="false". - Extensionless article directory names must not contain dots / Extensionless article URL conflicts with a site root path
- Rename the article directory and matching article ID. The slug must not contain a dot or match another generated root path.
- Unsafe SITE_ICON_PATH / SITE_ICON_PATH_THEME requires SVG class
-
Use a readable relative
.svgfile in the staged tree. A themed icon must containcolour-outandcolour-inclass tokens. - Article, heading, or generated-marker format error
- Apply the ARTICLE FORMAT and GENERATED BLOCKS rules. Keep indexed headings and empty generated markers on one source line.
Missing or invalid generated block files appear as visible missing blocks. They do not produce these fatal diagnostics.
SERVING
Local file access
Generated documentation may be bundled with a project and opened directly
as site/index.html through file://. Single-page
output works directly; multi-page output requires
SITE_URL_STYLE=html. Contact submission is for HTTP or HTTPS
deployment.
Local HTTP inspection
Serve site/ without changing its relative paths. The Python 3
command provides HTTP only, creates no project files, and does not rewrite
extensionless URLs. Stop it with an interrupt.
Local HTTP serverRun after a build from the project rootraw
# Run from the project root after make build.
python3 -m http.server 8000 --directory site
Static deployment
Deploy site/ without changing its relative paths. Serve contact
forms over HTTPS. The access log must retain query strings for contact
messages to be available there.
OpenBSD httpd example
The OpenBSD httpd(8) example serves the site and rewrites
extensionless article paths. Replace its host and document-root paths,
validate the configuration, and reload httpd.
Its final rule appends .html internally to a missing final
path component without a dot. Keep specific rules before it.
/etc/httpd.confReplace host, certificate, and document-root pathsraw↓
server "arachnogoat.com" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "https://$HTTP_HOST$REQUEST_URI"
}
}
server "arachnogoat.com" {
listen on * tls port 443
tls {
certificate "/etc/ssl/arachnogoat.com.fullchain.pem"
key "/etc/ssl/private/arachnogoat.com.key"
}
root "/htdocs/arachnogoat.com"
# Required when arachnopress uses SITE_URL_STYLE=extensionless.
location not found match "/[^./]+$" {
request rewrite "$DOCUMENT_URI.html"
}
}
CAVEATS
Trusted input
Article HTML and a themed SITE_ICON_PATH SVG are trusted
author content. The build escapes generated text and code, but does not
sanitize embedded article or SVG markup. Review both before building.
Single-page scale
Single-page output contains every generated article and highlighted code
span. Large code-heavy collections increase HTML size and DOM cost. Use
multi-page mode, focused excerpts, raw downloads, or
HIGHLIGHTER=none when that cost becomes material.
Browser compatibility
CSS and popover support varies on older clients. No JavaScript compatibility layer is provided.
SEE ALSO
sh(1), make(1), awk(1),
sed(1), tar(1), and cksum(1). On OpenBSD,
see also httpd(8) for the optional deployment example.
LICENSE
arachnopress is distributed under the BSD 3-Clause license. See
tools/license.txt or the License control in the site metadata.
Bundled colour palettes retain their upstream values and third-party
licences; see
THIRD_PARTY_NOTICES.txt and licenses/.