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 sh and make build 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

Missingarachnopress_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, and full, 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-dark and data-auto-light pair 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
Project layout after make build872 Braw

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>
tools/theme-menu.html14 KiBraw

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/, creates arachnopress_GENERATOR_VERSION.tar.gz in 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: arachnopress for release; arachnogoat for build and full.
SITE_ICON
Character shown beside SITE_TITLE and rendered into the generated SVG favicon. Defaults: U+4DD6 for release; U+4DEA for build and full.
SITE_ICON_COLOUR
Optional fixed colour for the Unicode header icon and generated favicon. The value must be #rgb or #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 when SITE_ICON_PATH is 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.svg and uses it beside SITE_TITLE. SITE_ICON and SITE_ICON_COLOUR are then unused. Defaults: empty for release; articles/arachnopress/arachnogoatsundual.svg for build and full. Direct tools/build.sh execution defaults to empty. Set it to an empty value to restore the generated text icon.
SITE_ICON_PATH_THEME
true embeds the SITE_ICON_PATH SVG in the header and maps its colour-out and colour-in classes to the current article background and title colours. The favicon remains an unchanged copy. true requires SITE_ICON_PATH. false uses the SVG as an external header image. Defaults: false for release; true for build and full.
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_LABEL and 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 the theme- prefix. Defaults: solarized-dark for release; everforest-hard-dark for build and full.
DEFAULT_THEME_AUTO
Theme mode control. true renders 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 its data-auto-dark and data-auto-light mapping; Auto follows the browser preference. A theme without a mapping remains fixed. false uses the selected exact variant and omits the control. Defaults: false for release; true for build and full.
DEFAULT_THEME_SELECTOR
Theme selector state. true renders the selector in either output mode; false fixes the selection to DEFAULT_THEME. Default: true.
SITE_MODE
Output layout. single-page writes all generated articles to index.html. multi-page writes index.html plus one slug.html per article. Default: single-page.
SITE_MODE_UNLISTED
Unlisted article generation. true enables unlisted output using the selected SITE_MODE; false excludes unlisted articles from generated HTML and navigation. Defaults: false for release; true for build and full.
SITE_URL_STYLE
Page-link format. html retains .html links. extensionless keeps generated .html files but uses slug and ./ for multi-page navigation, home links, and contact actions. Defaults: html for release; extensionless for build and full. Extensionless output requires a matching web-server rewrite. Single-page output is unchanged.
ARTICLE_ORDER
Initial order. Accepted values are title; created, creation, or ctime; and modified, modification, or mtime. Default: title.
HIGHLIGHTER
pygments, source-highlight, or none. 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 make in 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=true and a reachable theme has an automatic mapping. It contains light/dark rules derived from styles.css and the mappings in tools/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 release and consumed by make full from the same project root. Its top-level directory has the same name without the .tar.gz suffix.
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 release with the same generator version before make full. Run both commands from the same project root, not from site/.
DEFAULT_THEME does not exist in theme menu / Automatic theme mapping does not exist
Use IDs from tools/theme-menu.html. Define data-auto-dark and data-auto-light together, 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 .svg file in the staged tree. A themed icon must contain colour-out and colour-in class 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/.

Message sent

X

Thank you. Your message has been sent.