Technical reference

arachnopress

Build a zero-JS static article site from hand-written HTML fragments.

DESCRIPTION

arachnopress reads articles/slug/article.html files and builds a single-page or multi-page static article site in site/. make build is the normal interface.

It runs in POSIX-like BSD, Linux, and macOS environments. Generated output is served as static files and may also be opened through file://. It requires no server-side processing.

FEATURES

  • File-backed code, download, and image blocks with raw links, checksums, image sizing, framing, alignment, text flow, and native enlarged-image popovers.
  • Pygments or Source-highlight syntax highlighting with escaped-source fallback and reduced span markup.
  • Minimal, hand-edited article fragments with arbitrary HTML; no Markdown or template pipeline.
  • Single-page or per-article output, optional unlisted articles, and HTML or extensionless internal URLs.
  • Opt-in Atom entries for articles or sections, with summaries, updated dates, and section/subsection links.
  • Responsive CSS navigation, sortable title and date indexes, and article creation and modification metadata.
  • Selectable upstream colour palettes with Exact, Auto, Light, and Dark modes.
  • SVG logo and favicon support, theme-aware inline SVG, and optional fixed or randomised SVG backdrops. A single Unicode symbol supplies the logo/favicon fallback when no SVG icon is configured.
  • An HTML and CSS interface without JavaScript, cookies, browser storage, or server-side processing.
  • Native theme, contact, and license popovers; marked GET contact requests for access-log collection.
  • POSIX-style sh and make builds for BSD, Linux, and macOS, with optional versioned release packaging.

DOWNLOADS

Missingarachnopress_1.0.65.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.

Do not store source files in or run make from site/; each successful build replaces it.

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 maintainer targets additionally 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 successful 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. The optional randomised article backdrop requires CSS random(); unsupported browsers retain the SVG's base fit. The bundled backdrop uses a centred cover fit.

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, optional Atom feed, 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 example contains only the arachnopress article.

Example project and build outputraw
.
|-- Makefile
|-- README.arachnopress
|-- THIRD_PARTY_NOTICES.txt
|-- articles
|   `-- arachnopress
|       |-- article.html
|       `-- ...
|-- licenses
|   |-- mit.txt
|   `-- oksolar-cc0.txt
|-- site
|   |-- THIRD_PARTY_NOTICES.txt
|   |-- articles
|   |   `-- arachnopress
|   |       |-- article.html
|   |       `-- ...
|   |-- favicon.svg
|   |-- feed.atom
|   |-- 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
Example project and build output632 Braw

BUILD MODEL

make build copies the configured files and articles into a private staging tree, renders the complete site, then replaces site/. A failure before publication preserves the previous output.

site/ is generated output. All targets replace the same directory and must not run concurrently. The maintainer release targets also manage the generator download marker and versioned archive.

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. Use double-quoted values for attributes read by the generator.

data-created and data-modified accept a value beginning with a valid YYYY-MM-DD date or auto:

  • data-created: missing, empty, or auto uses the current UTC build date.
  • data-modified: missing or empty uses the resolved creation date; auto uses the build date.
  • Explicit dates remain unchanged. Use explicit dates on published articles and dated headings to keep metadata and 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.

data-feed-sections selects none, h1, h2, or h3. Unset or empty means none. See Atom feed modes under NAVIGATION AND THEMES.

Unlisted output:

  • Single-page: embedded after listed articles, absent from article indexes, and accessible through article, section, and subsection fragments.
  • Multi-page: written as slug.html, absent from listed-page indexes, and included only in its own index. Its document has noindex, nofollow.
  • Single-page articles share index.html and have no separate robots directive.
  • Unlisted output remains publicly accessible. Targets still copy included article directories to site/articles/.
  • At least one listed article is required. Multi-page mode reserves index.
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" data-feed-sections="h1">
  <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. Updated is the newest resolved article or heading date and appears when it differs from Created. Unlisted articles are prefixed with Unlisted; fields use middle-dot separators.

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.

Every source h2 and h3 is indexed. IDs on other author-supplied elements are preserved but are not registered; keep them unique and clear of generated IDs.

Indexable headings may use data-created and data-modified. Values follow the article-date format and may use auto. Their resolved dates contribute to Updated metadata, modification ordering, and Atom dates. They do not reorder section or subsection indexes.

Indexed heading formsraw
<h2 data-created="2026-07-09">BUILD</h2>
<h3>Requirements</h3>
<h3 id="custom-subsection" data-modified="2026-07-10">Explicit Subsection Anchor</h3>
<h2 id="custom-anchor" data-created="2026-07-09" data-modified="2026-07-10">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. Use double-quoted marker attributes.

File paths (data-src, SITE_ICON_PATH, SITE_BACKGROUND_PATH):

  • Relative, slash-separated paths; each component contains only ASCII letters, digits, dots, underscores, and hyphens.
  • Empty, . and .. components and symlinks are rejected.

Code blocks

  • data-title: display title. data-note: header qualifier.
  • data-open="false": initially closed.
  • data-header="false": always visible, without a summary.
  • data-footer: true or false overrides the default. Normal blocks hide the footer for source line counts at or below CODE_FOOTER_LINES; headerless blocks hide it unless explicitly enabled.

Raw links and sizes refer to the original file. Only the rendered code is 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: alternative text. data-caption: caption.
  • data-title and data-note: header labels.
  • data-open and data-header: as for code blocks.
  • data-footer: shown with a normal header unless false; a headerless image requires true to show it.
  • data-scale caps inline size: small (20rem), medium (32rem), large (48rem), or full (article width, default). Images retain their aspect ratio, do not upscale, and remain within the viewport.
  • data-border: full (default), fit, or none. fit shrink-wraps the block with one width for its header, image border, and footer. none removes the frame, uses the opaque article background, and suppresses both bars.
  • data-align: start, center (default), or end.
  • Other scale, border, and alignment values are fatal.
  • Images use lazy loading and asynchronous decoding.

Text flow:

  • Above 760px, following prose and text lists may flow beside start- or end-aligned fit or borderless blocks, capped at 55% of the containing section's width and by the configured size limit.
  • 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.

Image viewer:

  • Selecting an image opens a themed viewer over the article pane.
  • Intrinsic size and aspect ratio are retained, scaling down to fit.
  • The enlarged image, X button, Escape, or light dismissal closes it.
  • Missing images have no viewer.
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

HIGHLIGHTER selects pygments, source-highlight, or none. Use an explicit data-lang for predictable lexer selection, or omit it for automatic selection.

Pygments

Pygments is the default. An omitted data-lang requests filename inference. data-lang="auto" may also guess from content.

Source-highlight

Source-highlight uses tools/html-fragment.outlang. An omitted or automatic language lets the program infer its input language.

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. A slug page title is its article title followed by SITE_TITLE; the index page title is SITE_TITLE.

Title order is case-insensitive by title, then slug. Creation order uses the resolved article creation date. Modification order uses the newest resolved article or heading date. Date orders are newest first, then slug. Article-title links provide the article summary and resolved Created and Updated dates as native hover text, with the dates on the final line. If a summary cannot be reduced to clean text, the hover text contains only the dates.

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 while retaining the generator identity, Contact, and license controls.

Theme state

DEFAULT_THEME selects the initial exact variant. DEFAULT_THEME_MODE selects the initial mode:

  • off: omit the mode control; apply DEFAULT_THEME unchanged.
  • exact: apply the selected theme unchanged.
  • auto: select the mapped variant using the browser preference.
  • light or dark: select the corresponding mapped variant.

Every value except off renders all four choices. An unmapped theme remains fixed in every mode.

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 of the Theme selector.

Light and dark pairs are the data-auto-light and data-auto-dark attributes in tools/theme-menu.html. Edit those attributes to change a pairing.

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>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.html11 KiBraw

Atom feed

SITE_FEED=true writes feed.atom, feed discovery metadata, and a text-form Atom link before the Theme control. Listed articles opt in through data-feed-sections:

none
No entries. Also the default when unset or empty.
h1
One entry with the article title, link, and plain-text summary.
h2
One entry per h2, titled "Article title - Section heading", with a link to that section instead of the article summary.
h3
As h2, with a linked list of its h3 headings in each entry body.

Feed rules:

  • h2 and h3 fall back to h1 without h2 headings. In h3 mode, an h2 without child headings has the same content as h2 mode.
  • Section bodies contain generated HTML links, not article prose.
  • Unlisted articles never enter the feed. Other mode values are fatal.
  • An h1 entry uses the newest article or heading date. An h2 entry uses its data-modified, then its data-created, then the article's resolved modification date.
  • A dated h3 updates its parent entry when newer. An undated h3 has no effect. Article-level auto rolls into h1 entries; in section modes it is only the fallback for an undated h2. Feed dates use midnight UTC.
  • Article groups are ordered by their newest entry. Sections and linked subsections retain source order. An empty feed uses the UTC build date.
  • The feed identifies the generator and uses the versioned site favicon.
  • Links follow SITE_MODE and SITE_URL_STYLE. IDs remain SITE_URL/#article-id or SITE_URL/#section-id, independent of dates and output mode.
  • Explicit heading IDs are optional and preserve identity across renames and reordering of duplicate headings. New h2 headings create entries; section edits and added h3 links retain the parent entry ID.
  • Enabling section entries introduces existing sections as new entries. Revisions retain their IDs; read/unread state is reader-controlled.

Branding and backdrop

SITE_ICON supplies the Unicode header logo and generated favicon when SITE_ICON_PATH is empty. SITE_ICON_PATH supplies an SVG copied unchanged as the favicon and used as the header logo. SITE_ICON_PATH_THEME=true embeds the header copy and maps its colour-out and colour-in classes to the current article background and title colours. See Custom site icon under BUILD for class assignment.

SITE_BACKGROUND enables a fixed article-pane SVG backdrop. SITE_BACKGROUND_RANDOM lets supporting browsers select its scale and crop with CSS random() when loading the document. Other browsers retain the SVG's base fit. The bundled SVG uses a centred cover crop. Its colour-out class is not painted; colour-in uses the panel background colour at 25% opacity. Code, download, and image block surfaces remain opaque.

CONTACT AND LICENSE

Contact request

Contact opens a native popover:

  • Reply address: optional, at most 254 characters.
  • Message: required, at most 500 characters.
  • Submission: GET to the current page, targeting 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. Identifiers without ASCII letters or digits become 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 generator release. Builds only the arachnopress article, marks its archive download as missing, and replaces site/. It creates the versioned archive in the project root, removes all arachnopress_*.tar.gz source article archives, and updates the source marker.
full
Optional maintainer target that installs the matching root release archive in a full staged site. It replaces site/, removes all arachnopress_*.tar.gz source article archives, 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 and direct tools/build.sh use; arachnogoat for build and full.
SITE_FEED
true writes feed.atom, its discovery metadata, and a header link. Listed articles opt in with data-feed-sections. false omits them. Defaults: false for release and direct tools/build.sh use; true for build and full.
SITE_URL
Canonical site root used for Atom links. SITE_FEED=true requires an absolute HTTP or HTTPS URL without a query or fragment. A trailing slash is removed. Defaults: empty for release and direct tools/build.sh use; https://arachnogoat.com for build and full.
SITE_AUTHOR
Atom feed author. Defaults: SITE_TITLE for release and direct tools/build.sh use; musol@arachnogoat.com for build and full.
SITE_ICON
Character shown beside SITE_TITLE and rendered into the generated SVG favicon. Defaults: U+4DD6 for release and direct tools/build.sh use; 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. The path must not name the generated favicon.svg. Defaults: empty for release; articles/arachnopress/arachnogoatsundual.svg for build and full. Direct tools/build.sh execution defaults to empty. Restore the text icon with SITE_ICON_PATH= and SITE_ICON_PATH_THEME=false.
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 and direct tools/build.sh use; true for build and full.
SITE_BACKGROUND
Article-pane backdrop. true embeds the SITE_BACKGROUND_PATH SVG. The bundled SVG uses a centred cover crop; a replacement's root SVG attributes determine its base fit. Defaults: false for release and direct tools/build.sh execution; true for build and full.
SITE_BACKGROUND_RANDOM
Backdrop crop. true lets browsers with CSS random() support select a scale and crop on page load; unsupported browsers retain the base fit. false always uses the base fit. Default: true.
SITE_BACKGROUND_PATH
Relative path to the backdrop SVG in the selected build tree. When SITE_BACKGROUND=true, the file must define colour-out and colour-in classes. The former is not painted; the latter uses the active theme's panel background colour at 25% opacity. Default: articles/arachnopress/arachnogoatsundual.svg. Use preserveAspectRatio="xMidYMid slice" for a centred cover fit.
GENERATOR_LABEL
Maintainer setting for the generator name displayed in site metadata and the Atom feed. Default: arachnopress.
GENERATOR_VERSION
Maintainer setting displayed after GENERATOR_LABEL and used in release archive names and Atom generator metadata. It contains dot-separated digits. Default: 1.0.64.

Navigation and rendering

DEFAULT_THEME
Exact input ID from tools/theme-menu.html, with or without the theme- prefix. Defaults: solarized-dark for release and direct tools/build.sh use; everforest-hard-dark for build and full.
DEFAULT_THEME_MODE
Theme mode. off omits the control and applies DEFAULT_THEME unchanged. exact, auto, light, or dark render all four choices and select that initial mode. Auto follows the browser preference; Light and Dark use the selected theme's mapping. An unmapped theme remains fixed. Defaults: off for release and direct tools/build.sh use; exact 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 and direct tools/build.sh use; 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 and direct tools/build.sh use; extensionless for build and full. Extensionless output requires a matching web-server rewrite. Single-page output is unchanged.
ARTICLE_ORDER
Initial order: title, created, or modified. 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

make build is the normal target. Run it from the project root after changing articles, assets, styles, tool inputs, or build settings.

Routine build profilesRun from the project rootraw
make build

# Per-article pages with system light/dark selection and no theme menu.
make build SITE_MODE=multi-page DEFAULT_THEME_MODE=auto \
    DEFAULT_THEME_SELECTOR=false

# Per-article files for file:// navigation, using a fixed theme.
make build SITE_MODE=multi-page SITE_URL_STYLE=html \
    DEFAULT_THEME=solarized-dark DEFAULT_THEME_MODE=off \
    DEFAULT_THEME_SELECTOR=false

# Published articles only, without theme controls or an article backdrop.
make build SITE_MODE_UNLISTED=false SITE_BACKGROUND=false \
    DEFAULT_THEME_MODE=off DEFAULT_THEME_SELECTOR=false

# Override the public Atom feed identity.
make build SITE_FEED=true SITE_URL=https://example.com \
    SITE_AUTHOR='Example Author'

Custom site icon

Store the SVG below an included article. For theme colouring, assign colour-out and colour-in classes to its drawable elements or groups. Edit the class attributes directly or with Inkscape's XML Editor. Restore the Unicode icon with SITE_ICON_PATH= and SITE_ICON_PATH_THEME=false.

Custom site icon buildReplace article-slug after adding an SVGraw
# Run each command separately from the project root.
# Custom SVG after adding the required classes.
make build SITE_ICON_PATH=articles/article-slug/site-icon.svg \
    SITE_ICON_PATH_THEME=true

# Restore the Unicode icon.
make build SITE_ICON_PATH= SITE_ICON_PATH_THEME=false

Generator release

Maintainers changing the generator update GENERATOR_VERSION and run make release. This builds only the arachnopress article and creates the root archive. Copy that archive into articles/arachnopress/, then use make build for a normal site. The optional make full target performs the archive integration within its staged output.

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

  • Re-run make build after changing maintained input. Stylesheet and favicon cache tokens change with their resources.
  • After failure or interruption, correct the diagnostic and rerun the target. Normal failure and signal handling remove unpublished temporary files and preserve the previous site before publication.
  • After an untrappable termination, wait until no build is running. Inspect .site-build.* below the project root and static-site-build.* below TMPDIR before removing leftovers.

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/feed.atom
Site-wide Atom feed generated when SITE_FEED=true.
site/theme-auto.css
Generated when DEFAULT_THEME_MODE is not off 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 must name a theme option
Use theme radio 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.
Unsafe SITE_BACKGROUND_PATH / SITE_BACKGROUND requires SVG class
Enable the backdrop with a readable relative .svg file in the staged tree containing 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

Deploy site/ as static files without changing its relative paths. It may instead be opened as site/index.html through file://; multi-page file access requires SITE_URL_STYLE=html.

Contact submission is for HTTP or HTTPS deployment. Serve contact forms over HTTPS and retain query strings in the access log to receive their contents.

OpenBSD httpd example

The OpenBSD httpd(8) example serves .atom as application/atom+xml 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
types {
	include "/usr/share/misc/mime.types"
	application/atom+xml atom
}

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, a SITE_ICON_PATH SVG embedded by SITE_ICON_PATH_THEME, and a SITE_BACKGROUND_PATH SVG are trusted author content. The build escapes generated text and code, but does not sanitize author HTML or embedded SVG markup. Review them before building. The zero-JS guarantee applies to the generated interface; author HTML may add its own active content or external dependencies.

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

Older clients may not support every CSS or popover feature. See REQUIREMENTS.

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.