浏览代码

Add the rekopub.monoexp.com website and VERSION

site/ is a one-page site in the monoexp.com style: summary, latest
downloads with version and commit, usage, licence and libraries. The
deploy pipeline (cli kind) builds the binaries at the version in VERSION
and publishes both on every merge into main, so .goreleaser.yaml goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude 1 天之前
父节点
当前提交
53d7d8f7be
共有 11 个文件被更改,包括 458 次插入 和 59 次删除
  1. 0 1
      .gitignore
  2. 0 41
      .goreleaser.yaml
  3. 16 14
      README.md
  4. 1 0
      VERSION
  5. 27 0
      pull-releases/002-site/README.md
  6. 二进制
      pull-releases/002-site/desktop.png
  7. 二进制
      pull-releases/002-site/mobile.png
  8. 7 3
      pull-releases/README.md
  9. 12 0
      site/favicon.svg
  10. 210 0
      site/index.html
  11. 185 0
      site/style.css

+ 0 - 1
.gitignore

@@ -1,7 +1,6 @@
 # Built binaries
 /rekopub
 /rekopub.exe
-/dist/
 
 # Test output
 *.test

+ 0 - 41
.goreleaser.yaml

@@ -1,41 +0,0 @@
-# Release builds, made locally into dist/ (see "Releasing" in README.md):
-#   goreleaser release --clean              # on a version tag
-#   goreleaser release --snapshot --clean   # test build of the current commit
-version: 2
-
-project_name: rekopub
-
-builds:
-  - id: rekopub
-    main: ./cmd/rekopub
-    binary: rekopub
-    env:
-      - CGO_ENABLED=0 # pure Go: one self-contained file per platform
-    goos: [windows, linux, darwin]
-    goarch: [amd64, arm64]
-    flags: [-trimpath]
-    ldflags:
-      - -s -w -X main.version={{ .Version }}
-
-archives:
-  - id: rekopub
-    name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
-    formats: [tar.gz]
-    format_overrides:
-      - goos: windows
-        formats: [zip]
-    files:
-      - README.md
-      - LICENSE
-
-checksum:
-  name_template: checksums.txt
-
-changelog:
-  use: git
-  sort: asc
-
-release:
-  # Nothing is published: the files in dist/ are attached to a release on
-  # git.monoexp.com by hand.
-  disable: true

+ 16 - 14
README.md

@@ -33,17 +33,17 @@ rekopub -URL https://example.com/some-article -SAVE ~/Books/
 
 ### Download (recommended)
 
-Download the archive for your computer from the [Releases](https://git.monoexp.com/bajdev/ReKoPub/releases) page:
+Download the latest version for your computer from [rekopub.monoexp.com](https://rekopub.monoexp.com/#download):
 
 | Computer | File |
 |---|---|
-| Windows (most PCs) | `rekopub_<version>_windows_amd64.zip` |
-| Windows on Arm (e.g. Snapdragon laptops) | `rekopub_<version>_windows_arm64.zip` |
-| Mac with Apple silicon (M1 and later) | `rekopub_<version>_darwin_arm64.tar.gz` |
-| Mac with Intel | `rekopub_<version>_darwin_amd64.tar.gz` |
-| Linux | `rekopub_<version>_linux_amd64.tar.gz` (or `_arm64`) |
+| Windows (most PCs) | [`rekopub_windows_amd64.zip`](https://rekopub.monoexp.com/download/rekopub_windows_amd64.zip) |
+| Windows on Arm (e.g. Snapdragon laptops) | [`rekopub_windows_arm64.zip`](https://rekopub.monoexp.com/download/rekopub_windows_arm64.zip) |
+| Mac with Apple silicon (M1 and later) | [`rekopub_darwin_arm64.tar.gz`](https://rekopub.monoexp.com/download/rekopub_darwin_arm64.tar.gz) |
+| Mac with Intel | [`rekopub_darwin_amd64.tar.gz`](https://rekopub.monoexp.com/download/rekopub_darwin_amd64.tar.gz) |
+| Linux | [`rekopub_linux_amd64.tar.gz`](https://rekopub.monoexp.com/download/rekopub_linux_amd64.tar.gz) (or [`_arm64`](https://rekopub.monoexp.com/download/rekopub_linux_arm64.tar.gz)) |
 
-Unpack it and put `rekopub` (`rekopub.exe` on Windows) somewhere convenient. There's nothing to install and nothing else to download. It's a command-line program, so run it from Command Prompt, PowerShell or Terminal rather than by double-clicking. `checksums.txt` on the release page lets you check the download.
+Unpack it and put `rekopub` (`rekopub.exe` on Windows) somewhere convenient. There's nothing to install and nothing else to download. It's a command-line program, so run it from Command Prompt, PowerShell or Terminal rather than by double-clicking. The site shows the version and the commit it was built from; [`checksums.txt`](https://rekopub.monoexp.com/download/checksums.txt) lets you check the download.
 
 The binaries aren't code-signed, so the first run may show a warning:
 
@@ -274,22 +274,24 @@ internal/kepub/     KEPUB conversion (kepubify)
 internal/save/      Output path rules, folder creation, -FORCE, atomic writes
 internal/epubtest/  Test helpers for checking generated books
 testdata/           Saved HTML pages and images used by the tests
+site/               rekopub.monoexp.com (published on every merge into main)
+VERSION             The version release builds report
 ```
 
 ### Releasing
 
-Releases are built by [GoReleaser](https://goreleaser.com) from `.goreleaser.yaml`: self-contained binaries for Windows, macOS and Linux on amd64 and arm64, plus checksums. `goreleaser release --snapshot --clean` makes a test build of all of them without a tag.
+Every merge into `main` builds and publishes [rekopub.monoexp.com](https://rekopub.monoexp.com/): the server's deploy pipeline ([bajdev/devops](https://git.monoexp.com/bajdev/devops), kind `cli`) builds `./cmd/rekopub` for Windows, macOS and Linux on amd64 and arm64, archives each binary with `README.md` and `LICENSE`, writes `checksums.txt`, and publishes them in `download/` alongside the website in `site/`. The page shows the version, the build date and the commit, which the pipeline fills in where `site/index.html` has `@VERSION@`, `@DATE@`, `@COMMIT@` and `@SHORT@`.
 
-To make a release, update to the newest Go release (the binaries include its standard library, so they get its security fixes), check for known vulnerabilities, then tag the commit on `main`, push the tag and build:
+The version comes from `VERSION` (`1.2.3`, without the `v`). To release a new version, change it in the pull request. After the merge, tag the merge commit:
 
 ```bash
-go run golang.org/x/vuln/cmd/govulncheck@latest ./...
-git tag -a v1.0.0 -m "v1.0.0"
-git push origin v1.0.0
-goreleaser release --clean
+git tag -a v1.2.3 -m "v1.2.3"
+git push origin v1.2.3
 ```
 
-GoReleaser writes the archives and `checksums.txt` to `dist/` and publishes nothing. On git.monoexp.com, open **Releases → New Release**, choose the tag and attach the files from `dist/`. Run govulncheck now and then even when nothing has changed here, so a newly published vulnerability in Go or a dependency shows up; when one does, make a new release.
+Merges that don't change `VERSION` still republish the site and rebuild the binaries, with the same version and the new commit. Release binaries include the Go standard library, so run [govulncheck](https://go.dev/doc/security/vuln/) (`go run golang.org/x/vuln/cmd/govulncheck@latest ./...`) now and then; when it finds something, update Go or the dependency and merge.
+
+To preview the site, open `site/index.html` in a browser; the placeholders show as they are.
 
 See [`PLAN.md`](PLAN.md) for the full design.
 

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+1.1.0

+ 27 - 0
pull-releases/002-site/README.md

@@ -0,0 +1,27 @@
+# Add the rekopub.monoexp.com website and VERSION
+
+Branch `feature/site` · version 1.1.0
+
+## What changed
+
+- **`site/`**: a one-page website for rekopub.monoexp.com, styled like monoexp.com (same stylesheet, colours and dark mode). It has a summary and features, the latest downloads for each platform with the version, build date and the commit they were built from, usage (flags, where books are saved, Kobo and reMarkable steps), and the licence with the Go libraries ReKoPub uses and their licences.
+- **`VERSION`**: `1.1.0`. The deploy pipeline builds the binaries with it, and the page shows it. The code is the same as the old 1.0.1; the minor bump marks the rename, the new install path and the new download site.
+- **`.goreleaser.yaml` removed**: the pipeline now builds the release archives, so there's one way to make them. `/dist/` is gone from `.gitignore`.
+- **`README.md`**: Download links point to rekopub.monoexp.com, with stable file names (`rekopub_<os>_<arch>`, no version). "Releasing" explains that every merge into `main` rebuilds and publishes the site and binaries, and that `VERSION` sets the version. The project layout lists `site/` and `VERSION`.
+
+## Notes
+
+- This needs the `cli` kind from bajdev/devops `feature/cli-kind`, installed with its `sites.json` entry, plus the webhook on this repo, before this PR is merged. Otherwise nothing is published.
+- `site/index.html` contains `@VERSION@`, `@DATE@`, `@COMMIT@` and `@SHORT@`, which the pipeline fills in. Opened straight from the repo, the page shows them as they are.
+- The download files keep the same names in every release, so links to them always give the latest version. Older versions aren't kept on the site; tag `v<version>` and build from the tag to get one.
+- Every merge republishes, even if `VERSION` didn't change: the binaries then have the same version but a newer commit, which the page shows.
+
+## Testing
+
+- Ran the devops `release` script from `feature/cli-kind` against this branch in a sandbox (a local bare remote instead of Gogs). It took 15 s, built all six archives and `checksums.txt` (`sha256sum -c` passes), and filled in every placeholder. The unpacked Linux binary prints `rekopub v1.1.0` for `-VERSION`; the Windows zip holds `rekopub.exe`, `README.md` and `LICENSE`.
+- Served the published folder locally and checked the full page at 1280 px and 390 px wide. Fixed what that showed: licence names and inline code wrapping mid-word, long file names in the download table on phones (now stacked), and the long macOS command (now in a code block).
+- `go test ./...` passes (no Go code changed).
+
+## Screenshots
+
+`desktop.png` and `mobile.png` show the top of the page as the pipeline publishes it.

二进制
pull-releases/002-site/desktop.png


二进制
pull-releases/002-site/mobile.png


+ 7 - 3
pull-releases/README.md

@@ -7,17 +7,21 @@ added on the PR's own branch, so it is reviewed and merged with the change.
 pull-releases/
   NNN-branch-name/      NNN = next number in sequence, branch name without feature/
     README.md           title (first line), what changed, notes, testing
+    desktop.png         site changes only: the page at 1280×900
+    mobile.png          site changes only: the page at 390×844 (2× pixel density)
 ```
 
 To open the PR on Gogs, use the compare link for the branch, paste the first
 line of `README.md` as the title and the rest as the description.
 
-ReKoPub is a command-line tool with nothing to show in a browser, so records
-have no `desktop.png` or `mobile.png`. A record may include terminal output or
-files from a test run instead.
+Records for changes to the website in `site/` include `desktop.png` (1280×900)
+and `mobile.png` (390×844 at 2×), taken with `screenshot-site` from the site as
+the deploy pipeline builds it. Records for changes to the command-line tool
+alone have no screenshots.
 
 ## Index
 
 | # | Title | Branch |
 |---|---|---|
 | 001 | [Import the project as ReKoPub](001-rekopub-import/README.md) | `feature/rekopub-import` |
+| 002 | [Add the rekopub.monoexp.com website and VERSION](002-site/README.md) | `feature/site` |

+ 12 - 0
site/favicon.svg

@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+  <style>
+    rect { fill: #1c1c1a; }
+    path { stroke: #fafaf9; }
+    @media (prefers-color-scheme: dark) {
+      rect { fill: #ececea; }
+      path { stroke: #121211; }
+    }
+  </style>
+  <rect width="32" height="32" rx="7"/>
+  <path d="M11 24V8h6a4.5 4.5 0 0 1 0 9h-6m6 0 5 7" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 210 - 0
site/index.html

@@ -0,0 +1,210 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>ReKoPub: web articles as e-ink EPUBs</title>
+  <meta name="description" content="ReKoPub is a small command-line tool that turns a web article into a clean EPUB for the reMarkable 2 and Kobo Clara HD.">
+  <meta name="color-scheme" content="light dark">
+  <link rel="canonical" href="https://rekopub.monoexp.com/">
+  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
+  <link rel="stylesheet" href="/style.css">
+</head>
+<body>
+  <!--
+    Published by the monoexp.com pipeline on every merge into main. It fills
+    in the version, commit and date placeholders below and builds the files
+    in /download/ (see "Releasing" in README.md).
+  -->
+  <header class="site-header">
+    <div class="wrap header-inner">
+      <a class="wordmark" href="/">reko<span>pub</span></a>
+      <nav aria-label="Main">
+        <a href="#download">Download</a>
+        <a href="#usage">Usage</a>
+        <a href="https://git.monoexp.com/bajdev/ReKoPub">Source</a>
+      </nav>
+    </div>
+  </header>
+
+  <main class="wrap">
+    <section class="about" aria-labelledby="about-title">
+      <h1 id="about-title">ReKoPub</h1>
+      <p class="lede">Turn any web article into a clean EPUB for your e-ink reader.</p>
+      <p>
+        ReKoPub is a small command-line tool. You give it a URL. It downloads
+        the page, keeps the article itself (dropping navigation, ads, pop-ups
+        and comments) and writes an EPUB tuned for grayscale e-ink screens.
+        It is built for the <strong>reMarkable 2</strong> and the
+        <strong>Kobo Clara HD</strong>.
+      </p>
+      <pre><code>rekopub -URL https://example.com/some-article -SAVE ~/Books/</code></pre>
+    </section>
+
+    <section id="features" aria-labelledby="features-title">
+      <h2 id="features-title">Features</h2>
+      <ul class="cards">
+        <li class="card">
+          <h3>Readable content only</h3>
+          <p>Finds the main article text. Menus, ads, cookie banners, share buttons and comments are left out.</p>
+        </li>
+        <li class="card">
+          <h3>Tuned for e-ink</h3>
+          <p>Images become grayscale and fit the screen, diagrams stay crisp, and fonts and margins are left to your reader.</p>
+        </li>
+        <li class="card">
+          <h3>Works offline</h3>
+          <p>Images are embedded in the book. Title, author, date and language are filled in, and every book gets a cover.</p>
+        </li>
+        <li class="card">
+          <h3>Kobo-optimised</h3>
+          <p><code>-KEPUB</code> writes a <code>.kepub.epub</code>, which Kobo opens with its faster renderer and reading stats.</p>
+        </li>
+        <li class="card">
+          <h3>Safe saving</h3>
+          <p>Never overwrites a file unless you pass <code>-FORCE</code>, and never leaves a half-written book behind.</p>
+        </li>
+        <li class="card">
+          <h3>Single binary</h3>
+          <p>No runtime dependencies. One file for Linux, macOS or Windows; nothing to install.</p>
+        </li>
+      </ul>
+    </section>
+
+    <section id="download" aria-labelledby="download-title">
+      <h2 id="download-title">Download</h2>
+      <div class="release">
+        <p class="release-version">
+          <strong>@VERSION@</strong>
+          <span>built @DATE@ from commit
+          <a href="https://git.monoexp.com/bajdev/ReKoPub/commit/@COMMIT@"><code>@SHORT@</code></a></span>
+        </p>
+        <table class="downloads">
+          <thead>
+            <tr><th scope="col">Computer</th><th scope="col">File</th></tr>
+          </thead>
+          <tbody>
+            <tr><td>Windows (most PCs)</td><td><a href="/download/rekopub_windows_amd64.zip">rekopub_windows_amd64.zip</a></td></tr>
+            <tr><td>Windows on Arm</td><td><a href="/download/rekopub_windows_arm64.zip">rekopub_windows_arm64.zip</a></td></tr>
+            <tr><td>Mac with Apple silicon</td><td><a href="/download/rekopub_darwin_arm64.tar.gz">rekopub_darwin_arm64.tar.gz</a></td></tr>
+            <tr><td>Mac with Intel</td><td><a href="/download/rekopub_darwin_amd64.tar.gz">rekopub_darwin_amd64.tar.gz</a></td></tr>
+            <tr><td>Linux (x86-64)</td><td><a href="/download/rekopub_linux_amd64.tar.gz">rekopub_linux_amd64.tar.gz</a></td></tr>
+            <tr><td>Linux (Arm)</td><td><a href="/download/rekopub_linux_arm64.tar.gz">rekopub_linux_arm64.tar.gz</a></td></tr>
+          </tbody>
+        </table>
+        <p class="small"><a href="/download/checksums.txt">checksums.txt</a> lists the SHA-256 of each file.</p>
+      </div>
+      <p>
+        Unpack the archive and put <code>rekopub</code> (<code>rekopub.exe</code>
+        on Windows) somewhere convenient. It's a command-line program, so run it
+        from Terminal, Command Prompt or PowerShell rather than by double-clicking.
+        The binaries aren't code-signed, so the first run may show a warning:
+      </p>
+      <ul>
+        <li><strong>Windows:</strong> SmartScreen may say "Windows protected your PC". Choose <em>More info &rarr; Run anyway</em>.</li>
+        <li><strong>macOS:</strong> Gatekeeper may refuse to open it. Run this once, in the folder where you unpacked it:
+          <pre><code>xattr -d com.apple.quarantine rekopub</code></pre></li>
+      </ul>
+      <p>With Go 1.26 or newer you can also build it yourself:</p>
+      <pre><code>go install git.monoexp.com/bajdev/rekopub/cmd/rekopub@latest</code></pre>
+    </section>
+
+    <section id="usage" aria-labelledby="usage-title">
+      <h2 id="usage-title">Usage</h2>
+      <pre><code>rekopub -URL &lt;url&gt; -SAVE &lt;path&gt; [-FORCE] [-KEPUB]
+rekopub -VERSION</code></pre>
+      <div class="table-scroll">
+        <table>
+          <thead>
+            <tr><th scope="col">Flag</th><th scope="col">What it does</th></tr>
+          </thead>
+          <tbody>
+            <tr><td><code>-URL</code></td><td>The web page to convert. Must start with <code>http://</code> or <code>https://</code>. Required.</td></tr>
+            <tr><td><code>-SAVE</code></td><td>Where to write the book: a file or a folder. Missing folders are created. Required.</td></tr>
+            <tr><td><code>-FORCE</code></td><td>Overwrite the output file if it already exists.</td></tr>
+            <tr><td><code>-KEPUB</code></td><td>Write a Kobo KEPUB (<code>.kepub.epub</code>) instead of a plain EPUB.</td></tr>
+            <tr><td><code>-VERSION</code></td><td>Print the version and exit.</td></tr>
+          </tbody>
+        </table>
+      </div>
+      <p>Flag names are case-sensitive: <code>-URL</code> works, <code>-url</code> does not.</p>
+
+      <h3>Where the book is saved</h3>
+      <ul>
+        <li><strong>A folder</strong>, such as <code>~/Books/</code> or an existing <code>~/Books</code>: the file name comes from the article title.</li>
+        <li><strong>A path ending in <code>.epub</code></strong>: written to exactly that file.</li>
+        <li><strong>Anything else</strong>, such as <code>~/Books/a</code>: treated as a file name, with <code>.epub</code> added.</li>
+      </ul>
+      <p>
+        Watch the trailing slash when the folder doesn't exist yet:
+        <code>-SAVE ~/Books/new/</code> creates a folder called <code>new</code>,
+        while <code>-SAVE ~/Books/new</code> writes <code>new.epub</code>.
+        On success, the saved file's full path is printed, so it's easy to use in scripts.
+      </p>
+
+      <h3>Kobo Clara HD</h3>
+      <p>Use <code>-KEPUB</code> for faster page turns and reading stats. Connect the Kobo by USB, tap <em>Connect</em>, and save straight to it:</p>
+      <pre><code># Linux
+rekopub -URL https://example.com/article -SAVE /media/$USER/KOBOeReader/Articles/ -KEPUB
+# macOS
+rekopub -URL https://example.com/article -SAVE /Volumes/KOBOeReader/Articles/ -KEPUB
+# Windows (the drive letter may differ)
+rekopub.exe -URL https://example.com/article -SAVE E:\Articles\ -KEPUB</code></pre>
+      <p>Eject the Kobo and the article appears in your library.</p>
+
+      <h3>reMarkable 2</h3>
+      <p>Use a plain EPUB, without <code>-KEPUB</code>. Save it on your computer:</p>
+      <pre><code>rekopub -URL https://example.com/article -SAVE ~/reMarkable/</code></pre>
+      <p>
+        Then drag the file into the reMarkable desktop or mobile app, or upload it
+        through the USB web interface (<em>Settings &rarr; Storage &rarr; USB web
+        interface</em>, then open <code>http://10.11.99.1</code>).
+      </p>
+
+      <h3>Good to know</h3>
+      <ul>
+        <li>Pages that build their content with JavaScript, paywalls and cookie walls can't be converted.</li>
+        <li>ReKoPub only reads web pages: it never reads files on your computer, and pages on the internet can't make it connect to your own network.</li>
+        <li>It sends no cookies, never logs in and runs no JavaScript. Every book records its source URL.</li>
+      </ul>
+      <p>
+        The <a href="https://git.monoexp.com/bajdev/ReKoPub#readme">README</a> has
+        the full details: exit codes, troubleshooting and privacy.
+      </p>
+    </section>
+
+    <section id="license" aria-labelledby="license-title">
+      <h2 id="license-title">License</h2>
+      <p>
+        ReKoPub is free software, released under the
+        <a href="https://git.monoexp.com/bajdev/ReKoPub/src/main/LICENSE">MIT License</a>.
+        Copyright &copy; 2026 bajdev.
+      </p>
+      <p>It builds on these open-source Go libraries:</p>
+      <div class="table-scroll">
+        <table class="libraries">
+          <thead>
+            <tr><th scope="col">Library</th><th scope="col">Used for</th><th scope="col">License</th></tr>
+          </thead>
+          <tbody>
+            <tr><td><a href="https://github.com/markusmobius/go-trafilatura">go-trafilatura</a></td><td>Article and metadata extraction</td><td>Apache-2.0</td></tr>
+            <tr><td><a href="https://github.com/go-shiori/go-epub">go-epub</a></td><td>EPUB packaging</td><td>MIT</td></tr>
+            <tr><td><a href="https://github.com/pgaskin/kepubify">kepubify</a></td><td>KEPUB conversion</td><td>MIT</td></tr>
+            <tr><td><a href="https://github.com/microcosm-cc/bluemonday">bluemonday</a></td><td>HTML sanitising</td><td>BSD-3-Clause</td></tr>
+            <tr><td><a href="https://github.com/srwiley/oksvg">oksvg</a> and <a href="https://github.com/srwiley/rasterx">rasterx</a></td><td>SVG rendering</td><td>BSD-3-Clause</td></tr>
+            <tr><td><a href="https://github.com/vincent-petithory/dataurl">dataurl</a></td><td>Images embedded in pages</td><td>MIT</td></tr>
+            <tr><td><a href="https://github.com/google/uuid">uuid</a></td><td>Book identifiers</td><td>BSD-3-Clause</td></tr>
+            <tr><td><a href="https://pkg.go.dev/golang.org/x/image">x/image</a>, <a href="https://pkg.go.dev/golang.org/x/net">x/net</a>, <a href="https://pkg.go.dev/golang.org/x/text">x/text</a>, <a href="https://pkg.go.dev/golang.org/x/sync">x/sync</a></td><td>WebP decoding, image scaling, cover fonts, HTML parsing, character sets</td><td>BSD-3-Clause</td></tr>
+          </tbody>
+        </table>
+      </div>
+    </section>
+  </main>
+
+  <footer class="site-footer">
+    <div class="wrap">
+      <p>A <a href="https://monoexp.com/">Mono Experiments</a> project &middot; <a href="https://git.monoexp.com/bajdev/ReKoPub">Source</a></p>
+    </div>
+  </footer>
+</body>
+</html>

+ 185 - 0
site/style.css

@@ -0,0 +1,185 @@
+/* rekopub.monoexp.com: the monoexp.com stylesheet, plus code, tables and downloads */
+
+:root {
+  --bg: #fafaf9;
+  --surface: #ffffff;
+  --text: #1c1c1a;
+  --muted: #5f5f5a;
+  --border: #e4e4e0;
+  --accent: #1c1c1a;
+  --tag-bg: #f0f0ed;
+  --focus: #2f6fdb;
+
+  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
+  --radius: 10px;
+  --max: 760px;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --bg: #121211;
+    --surface: #1a1a19;
+    --text: #ececea;
+    --muted: #a3a39e;
+    --border: #2c2c2a;
+    --accent: #ececea;
+    --tag-bg: #252524;
+    --focus: #7aa7f5;
+  }
+}
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
+
+body {
+  margin: 0;
+  background: var(--bg);
+  color: var(--text);
+  font: 17px/1.6 var(--sans);
+  -webkit-font-smoothing: antialiased;
+}
+
+a { color: inherit; text-underline-offset: 3px; }
+a:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 4px; }
+
+.wrap {
+  max-width: var(--max);
+  margin: 0 auto;
+  padding: 0 20px;
+}
+
+.sr-only {
+  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
+  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
+}
+
+/* Header */
+.site-header { border-bottom: 1px solid var(--border); }
+.header-inner {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 64px;
+}
+.wordmark {
+  font: 600 18px/1 var(--mono);
+  letter-spacing: -0.02em;
+  text-decoration: none;
+}
+.wordmark span { color: var(--muted); }
+nav { display: flex; gap: 20px; }
+nav a {
+  font-size: 15px;
+  color: var(--muted);
+  text-decoration: none;
+}
+nav a:hover { color: var(--text); }
+
+/* About */
+.about { padding: 56px 0 40px; }
+.about h1 {
+  margin: 0 0 12px;
+  font-size: clamp(30px, 6vw, 42px);
+  line-height: 1.15;
+  letter-spacing: -0.03em;
+}
+.about p { margin: 0 0 14px; color: var(--muted); max-width: 62ch; }
+.about .lede { font-size: 20px; color: var(--text); }
+
+/* Sections */
+main section:not(.about) { padding-bottom: 48px; scroll-margin-top: 16px; }
+main h2 {
+  margin: 0 0 16px;
+  font: 600 13px/1 var(--mono);
+  text-transform: uppercase;
+  letter-spacing: 0.08em;
+  color: var(--muted);
+}
+main h3 { margin: 28px 0 8px; font-size: 19px; letter-spacing: -0.01em; }
+main section p, main section li { max-width: 68ch; }
+main section ul:not(.cards) { padding-left: 22px; }
+main section li { margin-bottom: 6px; }
+main section li pre { margin: 8px 0 0; }
+.small { font-size: 14.5px; color: var(--muted); }
+
+/* Code */
+code { font: 0.88em/1.5 var(--mono); }
+:not(pre) > code {
+  background: var(--tag-bg);
+  padding: 2px 5px;
+  border-radius: 5px;
+  white-space: nowrap;
+}
+pre {
+  margin: 0 0 16px;
+  padding: 14px 16px;
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  overflow-x: auto;
+}
+pre code { font-size: 14px; }
+
+/* Feature cards */
+.cards {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 16px;
+}
+@media (min-width: 640px) {
+  .cards { grid-template-columns: 1fr 1fr; }
+}
+.card {
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  padding: 20px;
+}
+.card h3 { margin: 0 0 8px; font-size: 17px; }
+.card p { margin: 0; color: var(--muted); font-size: 15.5px; }
+
+/* Tables */
+.table-scroll { overflow-x: auto; margin: 0 0 16px; }
+table { width: 100%; border-collapse: collapse; font-size: 15.5px; }
+th, td { text-align: left; vertical-align: top; padding: 9px 12px 9px 0; border-bottom: 1px solid var(--border); }
+th { font-weight: 600; font-size: 14px; color: var(--muted); }
+td:first-child { white-space: nowrap; }
+.libraries td:first-child { white-space: normal; min-width: 9em; }
+.libraries td:last-child { white-space: nowrap; padding-right: 0; }
+
+/* Download box */
+.release {
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  padding: 20px 20px 6px;
+  margin-bottom: 20px;
+}
+.release-version {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: baseline;
+  gap: 4px 12px;
+  margin: 0 0 8px;
+}
+.release-version strong { font: 600 22px/1.2 var(--mono); letter-spacing: -0.02em; }
+.release-version span { color: var(--muted); font-size: 15px; }
+.downloads td:last-child a { font: 14.5px/1.5 var(--mono); overflow-wrap: anywhere; }
+.downloads td:first-child { white-space: normal; }
+@media (max-width: 520px) {
+  .downloads thead { display: none; }
+  .downloads tr { display: block; padding: 9px 0; border-bottom: 1px solid var(--border); }
+  .downloads td { display: block; padding: 0; border: 0; }
+  .downloads td:first-child { font-size: 14px; color: var(--muted); }
+}
+
+/* Footer */
+.site-footer { border-top: 1px solid var(--border); }
+.site-footer p { margin: 0; padding: 24px 0 32px; font-size: 14px; color: var(--muted); }