Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
927f5aac98 | ||
|
|
23bc196e80 | ||
|
|
53d17ca29e | ||
|
|
d6992a3b80 | ||
|
|
4ff619f422 | ||
|
|
11510a863b | ||
|
|
40e88da30a | ||
|
|
444b5e416e | ||
|
|
d9d144e563 |
No files matched your search
@@ -71,8 +71,8 @@ jobs:
|
|||||||
- name: 🖨️ Build PDFs
|
- name: 🖨️ Build PDFs
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
run: python scripts/build_guide_pdf.py --${{ steps.mode.outputs.build_mode }}
|
run: python scripts/build_guide_pdf.py --${{ steps.mode.outputs.build_mode }} 2>/dev/null
|
||||||
|
# ignore stderr (DEPRECATED_ENDPOINT, etc.)
|
||||||
- name: 📤 Upload PDF artifacts
|
- name: 📤 Upload PDF artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
sudo apt-get install -y gnupg
|
sudo apt-get install -y gnupg
|
||||||
gpg --import pgp/anonymousplanet-release.asc
|
gpg --import pgp/anonymousplanet.asc
|
||||||
|
|
||||||
- name: 🐍 Set up Python
|
- name: 🐍 Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|||||||
@@ -1,19 +1,47 @@
|
|||||||
name: 🚀 Publish docs via GitHub Pages
|
name: 🚀 Publish docs via GitHub Pages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
paths:
|
||||||
|
- "docs/**"
|
||||||
|
- "mkdocs.yml"
|
||||||
|
- "requirements.txt"
|
||||||
|
- ".github/workflows/publish.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Publish the Hitchhiker's Guide
|
name: Build and deploy the Hitchhiker's Guide
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout main
|
- name: 🛠️ Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Deploy docs
|
- name: 🐍 Set up Python
|
||||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
uses: actions/setup-python@v5
|
||||||
env:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
python-version: "3.13"
|
||||||
CUSTOM_DOMAIN: anonymousplanet.net
|
cache: "pip"
|
||||||
|
|
||||||
|
- name: 📦 Install pinned dependencies
|
||||||
|
run: pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: 🔍 Sanity-check the build (fail fast on broken markup/links)
|
||||||
|
run: mkdocs build --strict
|
||||||
|
|
||||||
|
- name: 🚀 Deploy to gh-pages
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
mkdocs gh-deploy --force --clean
|
||||||
@@ -27,3 +27,4 @@ export/thgtoa-dark.pdf.sha256
|
|||||||
export/thgtoa.pdf.b2sum
|
export/thgtoa.pdf.b2sum
|
||||||
export/thgtoa-dark.pdf.b2sum
|
export/thgtoa-dark.pdf.b2sum
|
||||||
export/*.asc
|
export/*.asc
|
||||||
|
export/.*.body.pdf
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
|
<!-- markdownlint-disable MD041 -->
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://anonymousplanet.net">
|
<a href="https://anonymousplanet.net">
|
||||||
<img src="https://anonymousplanet.net/media/profile.png"></a>
|
<img src="https://anonymousplanet.net/media/anon_planet.svg"></a>
|
||||||
|
|
||||||
<p><em>The comprehensive guide for online anonymity and OpSec.</em></p>
|
<p><em>The comprehensive guide for online anonymity and OpSec.</em></p>
|
||||||
|
|
||||||
<a href="https://github.com/Anon-Planet/thgtoa/stargazers">
|
<a href="https://github.com/Anon-Planet/thgtoa/stargazers">
|
||||||
<img src="https://img.shields.io/github/stars/Anon-Planet?style=social"></a>
|
<img src="https://img.shields.io/github/stars/Anon-Planet?style=social"></a>
|
||||||
|
<a href="https://github.com/Anon-Planet/thgtoa/actions/workflows/03-release.yml">
|
||||||
|
<img src="https://img.shields.io/github/actions/workflow/status/Anon-Planet/thgtoa/03-release.yml?label=build"></a>
|
||||||
<a href="https://github.com/Anon-Planet/thgtoa/releases/latest">
|
<a href="https://github.com/Anon-Planet/thgtoa/releases/latest">
|
||||||
<img src="https://img.shields.io/badge/MkDocs_Material-526CFE?logo=MaterialForMkDocs&logoColor=green"></a>
|
<img src="https://img.shields.io/github/v/release/Anon-Planet/thgtoa?include_prereleases&sort=semver&display_name=tag&style=flat&logo=github&logoColor=green&labelColor=black&color=black"></a>
|
||||||
<a href="https://github.com/Anon-Planet/thgtoa/actions/workflows/release.yml">
|
<a href="https://opencollective.com/thgtoa">
|
||||||
<img src="https://img.shields.io/github/actions/workflow/status/Anon-Planet/thgtoa/release.yml?label=release"></a></p>
|
<img src="https://img.shields.io/opencollective/backers/thgtoa?style=flat&logo=open-collective&logoColor=white&label=Open%20Collective%20backers"></a>
|
||||||
|
<a href="https://github.com/Anon-Planet/thgtoa/issues">
|
||||||
|
<img src="https://img.shields.io/github/issues-closed/Anon-Planet/thgtoa?style=flat&logo=github&labelColor=rgba(0%2C%20255%2C%200%2C%200.05)&color=green"></a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Welcome.
|
Welcome.
|
||||||
|
|
||||||
This is a guide with the aim of providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. <span style="color: red">**It is written with hope for activists, journalists, scientists, lawyers, whistle-blowers, and good people being oppressed, censored, harassed anywhere!**</span> This guide has no affiliation with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> collective/movement.
|
The purpose: providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. <span style="color: red">**It is written with hope for activists, journalists, scientists, lawyers, whistle-blowers, and good people being oppressed, censored, and harassed anywhere!**</span> This guide has no affiliation with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> collective/movement.
|
||||||
|
|
||||||
This guide is an open-source non-profit initiative, [licensed](LICENSE.html) under **Creative Commons Attribution-ShareAlike 4.0 International** ([cc-by-sa-4.0](https://creativecommons.org/licenses/by-sa/4.0/) <sup>[[Archive.org]](https://web.archive.org/web/https://creativecommons.org/licenses/by-sa/4.0/)</sup>) and is **not sponsored/endorsed by any commercial/governmental entity**. This means that you are free to use our guide for pretty much any purpose **excluding commercially** as long as you do attribute it. There are no ads or any affiliate links.
|
The website and projects are free (as in freedom) and not affiliated with any donor or projects discussed, [licensed](LICENSE.html) under **Creative Commons Attribution-ShareAlike 4.0 International** ([cc-by-sa-4.0](https://creativecommons.org/licenses/by-sa/4.0/) <sup>[[Archive.org]](https://web.archive.org/web/https://creativecommons.org/licenses/by-sa/4.0/)</sup>), and is **not sponsored/endorsed by any commercial/governmental entity**. This means that you are free to use our guide for pretty much any purpose **including commercially** as long as you attribute it. There are no ads or any affiliate links.
|
||||||
|
|
||||||
**If you would like to make a donation to help this project, you can do so from [here](donations.html) where you will also find the project goals. All the donations will be strictly used within the context of this project. All donations and spendings are logged on the donations page.**
|
**If you would like to make a donation to help this project, you can do so from [here](donations.html) where you will also find the project goals. All the donations will be strictly used within the context of this project. All donations and spendings are logged on the donations page.**
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
title: "Anonymous Planet"
|
title: "Anonymous Planet"
|
||||||
description: "We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space."
|
description: "We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space."
|
||||||
hide:
|
hide:
|
||||||
|
- navigation
|
||||||
- toc
|
- toc
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
@@ -13,7 +14,6 @@ schema:
|
|||||||
sameAs:
|
sameAs:
|
||||||
- https://github.com/Anon-Planet
|
- https://github.com/Anon-Planet
|
||||||
- https://opencollective.com/anonymousplanetorg
|
- https://opencollective.com/anonymousplanetorg
|
||||||
- https://mastodon.social/@anonymousplanet
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
@@ -47,6 +47,28 @@ We are high-risk individuals. We do this because it matters.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Maintainers { #maintainers }
|
||||||
|
|
||||||
|
<div class="maintainer-bio">
|
||||||
|
<img src="../media/nope-keyoxide-profile.png" alt="nope" class="maintainer-avatar">
|
||||||
|
<div class="maintainer-text" markdown="1">
|
||||||
|
|
||||||
|
Nope -- background in signals intelligence (SIGINT), software engineering, "hacking", privacy software engineering, operational security, drone piloting and repair. Years ago, I realized there really wasn't a definitive guide for whistleblowers, without having to dig on various sites including darknet locations that could provide potentially incorrect or misleading information. I haven't stopped working on that since. We're the people this guide is written for - the threat model isn't hypothetical for us. I stay anonymous by necessity, not preference. Previously this project was ran by Lena, the founder and a few other people with various backgrounds. Obviously, not much else can be said, but we are active members of the intelligence community (IC) and privacy/security circles. We strive to be the definitive source, not just a glorified recommendation website.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="maintainer-bio">
|
||||||
|
<img src="../media/daskolburn-keyoxide-profile.png" alt="daskolburn" class="maintainer-avatar">
|
||||||
|
<div class="maintainer-text" markdown="1">
|
||||||
|
|
||||||
|
<>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Community { #community }
|
## Community { #community }
|
||||||
|
|
||||||
<div class="index-grid">
|
<div class="index-grid">
|
||||||
@@ -62,7 +84,7 @@ We are high-risk individuals. We do this because it matters.
|
|||||||
<div class="index-card">
|
<div class="index-card">
|
||||||
<h3 class="index-card__title">Mastodon</h3>
|
<h3 class="index-card__title">Mastodon</h3>
|
||||||
<p class="index-card__body">Follow us for announcements on the Fediverse.</p>
|
<p class="index-card__body">Follow us for announcements on the Fediverse.</p>
|
||||||
<a href="https://ioc.exchange/@anonymousplanet" class="index-card__link" rel="me">@anonymousplanet@ioc.exchange</a>
|
<a href="https://ioc.exchange/@unknown" class="index-card__link" rel="me">@unknown@ioc.exchange</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="index-card">
|
<div class="index-card">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: "Changelog"
|
title: "Changelog"
|
||||||
description: "Release notes for the Hitchhiker's Guide to Online Anonymity."
|
description: "Release notes for the Hitchhiker's Guide to Online Anonymity."
|
||||||
|
hide:
|
||||||
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": Organization
|
"@type": Organization
|
||||||
@@ -11,7 +13,6 @@ schema:
|
|||||||
sameAs:
|
sameAs:
|
||||||
- https://github.com/Anon-Planet
|
- https://github.com/Anon-Planet
|
||||||
- https://opencollective.com/anonymousplanetorg
|
- https://opencollective.com/anonymousplanetorg
|
||||||
- https://mastodon.social/@anonymousplanet
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
@@ -31,14 +32,47 @@ schema:
|
|||||||
<!-- Change nothing below this line, please. -->
|
<!-- Change nothing below this line, please. -->
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [v1.2.6]
|
||||||
|
|
||||||
|
!!! abstract "Meta"
|
||||||
|
|
||||||
|
- Released 2026-10-24
|
||||||
|
- Various website improvements (CSS)
|
||||||
|
- PDF build overhaul: larger text, page numbers, tighter layout, and a new cover
|
||||||
|
|
||||||
|
!!! info "Changed"
|
||||||
|
|
||||||
|
- Adjust keyring path use the new relative path: pgp/anonymousplanet.asc
|
||||||
|
- 02-sign.yml: use a fpr match to lock a specific key
|
||||||
|
- 02-sign.yml: quick mkdir -p repo/export before copying files to the dir
|
||||||
|
- Moved the Acknowledgements section to the bottom
|
||||||
|
- PDF body text enlarged to 13pt, with headings, code, and tables scaled to match
|
||||||
|
- PDF page margins reduced to 12 mm (16 mm at the bottom) and spacing tightened to cut the page count; the cover and Table of Contents use the same margins
|
||||||
|
- PDF tables, code blocks, and admonitions can now break across pages, and table header rows repeat on each page
|
||||||
|
- PDF Table of Contents shrinks slightly (down to 85%) when needed to avoid a nearly empty last page
|
||||||
|
- Glossary abbreviations are no longer underlined in the PDF; the website keeps the underline and tooltips
|
||||||
|
- PDF cover redesigned with Fira Code monospace, green and amber accents, in both light and dark builds
|
||||||
|
|
||||||
|
!!! tip "Added"
|
||||||
|
|
||||||
|
- COM Class Identifiers (CLSID/GUID) (NEW)
|
||||||
|
- Windows Global Device Identifier (GDID) (NEW)
|
||||||
|
- USB bus telemetry (device enumeration history) (NEW)
|
||||||
|
- Page numbers on every page of both PDFs (bottom right)
|
||||||
|
- Page numbers in the PDF Table of Contents
|
||||||
|
|
||||||
|
!!! info "Improved"
|
||||||
|
|
||||||
|
- Glossary (`docs/includes/glossary.md`) and removed inline expansions where applicable
|
||||||
|
|
||||||
## [v1.2.5]
|
## [v1.2.5]
|
||||||
|
|
||||||
!!! Note "Meta"
|
!!! abstract "Meta"
|
||||||
|
|
||||||
- Website theme customization
|
- Website theme customization
|
||||||
- License change to accomodate our fiscal host Open Source Collective
|
- License change to accomodate our fiscal host Open Source Collective
|
||||||
|
|
||||||
!!! Note "Changed"
|
!!! info "Changed"
|
||||||
|
|
||||||
- Comprehensive updates throughout the guide reflecting the transition to Whonix 18.x as the newest version
|
- Comprehensive updates throughout the guide reflecting the transition to Whonix 18.x as the newest version
|
||||||
- Replaced outdated Whonix.org wiki docs links with GitHub releases mirror and Archive.org backups
|
- Replaced outdated Whonix.org wiki docs links with GitHub releases mirror and Archive.org backups
|
||||||
@@ -52,14 +86,14 @@ schema:
|
|||||||
- Added comprehensive upgrade path guidance with backup procedures
|
- Added comprehensive upgrade path guidance with backup procedures
|
||||||
- Updated 196! Wikipedia reference links throughout the entire guide
|
- Updated 196! Wikipedia reference links throughout the entire guide
|
||||||
|
|
||||||
!!! Note "Added"
|
!!! tip "Added"
|
||||||
|
|
||||||
- All Whonix.org/wiki links now have Archive.org mirror backups for availability
|
- All Whonix.org/wiki links now have Archive.org mirror backups for availability
|
||||||
- Upgrade path documented: Whonix 17 to 18 with automated release-upgrade support
|
- Upgrade path documented: Whonix 17 to 18 with automated release-upgrade support
|
||||||
- Detailed Qubes OS compatibility notes for both Whonix versions
|
- Detailed Qubes OS compatibility notes for both Whonix versions
|
||||||
- You can now get the Anonymous Planet PGP keyring from the site (copy/paste or download)
|
- You can now get the Anonymous Planet PGP keyring from the site (copy/paste or download)
|
||||||
|
|
||||||
!!! Note "Improved"
|
!!! info "Improved"
|
||||||
|
|
||||||
- Virtualbox hardening section reorganized
|
- Virtualbox hardening section reorganized
|
||||||
- AppArmor configuration guidance added where applicable (Whonix 18.x)
|
- AppArmor configuration guidance added where applicable (Whonix 18.x)
|
||||||
@@ -69,11 +103,11 @@ schema:
|
|||||||
|
|
||||||
## [v1.2.4]
|
## [v1.2.4]
|
||||||
|
|
||||||
!!! Note "Meta"
|
!!! abstract "Meta"
|
||||||
|
|
||||||
- Rename workflows (GH - now we can know the order)
|
- Rename workflows (GH - now we can know the order)
|
||||||
|
|
||||||
!!! Note "Changed"
|
!!! info "Changed"
|
||||||
|
|
||||||
- Change the repo URL for our tor mirror
|
- Change the repo URL for our tor mirror
|
||||||
- Fix recommended reading admonition
|
- Fix recommended reading admonition
|
||||||
@@ -93,7 +127,7 @@ schema:
|
|||||||
|
|
||||||
CI/CD pipeline split into independent stages, dark PDF quality improved, release signing automated, and the changelog now updates itself on every build. Skipping v1.2.2 which was a placeholder and contained broken Python unsuitable for a tag/release.
|
CI/CD pipeline split into independent stages, dark PDF quality improved, release signing automated, and the changelog now updates itself on every build. Skipping v1.2.2 which was a placeholder and contained broken Python unsuitable for a tag/release.
|
||||||
|
|
||||||
???+ tip "Added"
|
!!! tip "Added"
|
||||||
|
|
||||||
- **Dark mode PDF** (`scripts/convert.py`): pixel-level converter replaces the broken `--prefers-color-scheme=dark` Chromium flag. Produces a 200 DPI hacker-themed PDF (`#1f1f31` background, `#e0e0e0` text, `#5e8bde` links) with batched page processing to avoid OOM on large documents.
|
- **Dark mode PDF** (`scripts/convert.py`): pixel-level converter replaces the broken `--prefers-color-scheme=dark` Chromium flag. Produces a 200 DPI hacker-themed PDF (`#1f1f31` background, `#e0e0e0` text, `#5e8bde` links) with batched page processing to avoid OOM on large documents.
|
||||||
- **Three independent CI workflows** replacing the old monolithic `build-sign-release.yml`:
|
- **Three independent CI workflows** replacing the old monolithic `build-sign-release.yml`:
|
||||||
@@ -129,7 +163,7 @@ CI/CD pipeline split into independent stages, dark PDF quality improved, release
|
|||||||
|
|
||||||
First automated PDF build and the start of the CI pipeline.
|
First automated PDF build and the start of the CI pipeline.
|
||||||
|
|
||||||
???+ tip "Added"
|
!!! tip "Added"
|
||||||
|
|
||||||
- `scripts/build_guide_pdf.py`: builds the MkDocs site and renders the full guide to a single PDF via headless Chromium (Chrome or Edge). Supports `--dark`, `--light`, and `--both` modes.
|
- `scripts/build_guide_pdf.py`: builds the MkDocs site and renders the full guide to a single PDF via headless Chromium (Chrome or Edge). Supports `--dark`, `--light`, and `--both` modes.
|
||||||
- GitHub Actions workflow that installs Chromium, runs the build script, and uploads `export/thgtoa.pdf` as an artifact on every push to `main` or manual dispatch.
|
- GitHub Actions workflow that installs Chromium, runs the build script, and uploads `export/thgtoa.pdf` as an artifact on every push to `main` or manual dispatch.
|
||||||
@@ -148,6 +182,7 @@ First automated PDF build and the start of the CI pipeline.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
[v1.2.6-pre1]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.6-pre1
|
||||||
[v1.2.5]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.5
|
[v1.2.5]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.5
|
||||||
[v1.2.4]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.4
|
[v1.2.4]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.4
|
||||||
[v1.2.3]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.3
|
[v1.2.3]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.3
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ title: "Content Contributions"
|
|||||||
description: "How to contribute to the Hitchhiker's Guide — building, signing, releasing, and writing."
|
description: "How to contribute to the Hitchhiker's Guide — building, signing, releasing, and writing."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- toc
|
||||||
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": Organization
|
"@type": Organization
|
||||||
|
|||||||
@@ -3,21 +3,14 @@ title: "A Constitution"
|
|||||||
description: "The rules, values, and principles governing the Anonymous Planet initiative and PSA community."
|
description: "The rules, values, and principles governing the Anonymous Planet initiative and PSA community."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- toc
|
||||||
|
- navigation
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
<div class="hero-eyebrow">Applicable to all projects of the initiative.</div>
|
<div class="hero-eyebrow">Applicable to all projects of the initiative.</div>
|
||||||
<h1 class="hero-title">A Constitution<span class="hero-subtitle">The rules we hold ourselves to.</span></h1>
|
<h1 class="hero-title">A Constitution<span class="hero-subtitle">The rules we hold ourselves to.</span></h1>
|
||||||
<div class="hero-cta-row">
|
|
||||||
<a href="#principles" class="hero-cta hero-cta--primary">Our Principles</a>
|
|
||||||
<a href="#goals" class="hero-cta hero-cta--secondary">Core Goals</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Principles { #principles }
|
|
||||||
|
|
||||||
<div class="index-grid">
|
<div class="index-grid">
|
||||||
|
|
||||||
<div class="index-card">
|
<div class="index-card">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: Contribute & Donate
|
title: Contribute & Donate
|
||||||
description: "Support the Anonymous Planet project through crypto donations or content contributions. No fiat. No tracking. No profit."
|
description: "Support the Anonymous Planet project through crypto donations or content contributions. No fiat. No tracking. No profit."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- navigation
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
@@ -19,7 +19,7 @@ hide:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="donate-goals-block">
|
<div class="donate-goals-block">
|
||||||
<h2 class="donate-goals-title">Current Goals</h2>
|
<h3 class="donate-goals-title">Current Goals</h3>
|
||||||
<ul class="donate-goals-list">
|
<ul class="donate-goals-list">
|
||||||
<li class="donate-goal donate-goal--done"><span class="donate-goal-icon">✓</span> VPS for .onion hosting <strong>funded</strong></li>
|
<li class="donate-goal donate-goal--done"><span class="donate-goal-icon">✓</span> VPS for .onion hosting <strong>funded</strong></li>
|
||||||
<li class="donate-goal donate-goal--done"><span class="donate-goal-icon">✓</span> Domain <code>anonymousplanet.net</code> — <strong>secured until 2029</strong></li>
|
<li class="donate-goal donate-goal--done"><span class="donate-goal-icon">✓</span> Domain <code>anonymousplanet.net</code> — <strong>secured until 2029</strong></li>
|
||||||
@@ -172,5 +172,4 @@ All donations and spendings are recorded here. <span style="color:var(--status-e
|
|||||||
| 2022-07-11 | 0.503232 XMR | 1984.is VPS (12 mo) | <span class="status-indicator status-warning">Ended</span> |
|
| 2022-07-11 | 0.503232 XMR | 1984.is VPS (12 mo) | <span class="status-indicator status-warning">Ended</span> |
|
||||||
| 2022-09-19 | 0.345024 XMR | VPS RAM/disk upgrade | <span class="status-indicator status-warning">Ended</span> |
|
| 2022-09-19 | 0.345024 XMR | VPS RAM/disk upgrade | <span class="status-indicator status-warning">Ended</span> |
|
||||||
|
|
||||||
!!! note "About lost funds"
|
**Entries marked <span class="status-indicator status-error">Lost</span> represent funds spent on services that are no longer available.**
|
||||||
Entries marked **Lost** represent funds spent on services that are no longer available.
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<!-- Abbreviation definitions. No prose. -->
|
||||||
|
<!-- markdownlint-disable MD041 -->
|
||||||
|
|
||||||
|
*[AEM]: Anti Evil Maid
|
||||||
|
*[AES]: Advanced Encryption Standard
|
||||||
|
*[AML]: Anti-Money Laundering
|
||||||
|
*[APT]: Advanced Persistent Threat
|
||||||
|
*[ARP]: Address Resolution Protocol
|
||||||
|
*[ASLR]: Address Space Layout Randomization
|
||||||
|
*[BGP]: Border Gateway Protocol
|
||||||
|
*[BIOS]: Basic Input/Output System
|
||||||
|
*[CA]: Certificate Authority
|
||||||
|
*[CDN]: Content Delivery Network
|
||||||
|
*[CLI]: Command Line Interface
|
||||||
|
*[CLSID]: COM Class Identifiers
|
||||||
|
*[CNAME]: Canonical Name (DNS record type)
|
||||||
|
*[CRL]: Certificate Revocation List
|
||||||
|
*[CSP]: Content Security Policy
|
||||||
|
*[CT]: Certificate Transparency
|
||||||
|
*[CVE]: Common Vulnerabilities and Exposures
|
||||||
|
*[DDoS]: Distributed Denial of Service
|
||||||
|
*[DEA]: Disposable Email Address
|
||||||
|
*[DEP]: Data Execution Prevention
|
||||||
|
*[DHCP]: Dynamic Host Configuration Protocol
|
||||||
|
*[DNS]: Domain Name System
|
||||||
|
*[DNSSEC]: Domain Name System Security Extensions
|
||||||
|
*[DoH]: DNS over HTTPS
|
||||||
|
*[DoS]: Denial of Service
|
||||||
|
*[DoT]: DNS over TLS
|
||||||
|
*[DRM]: Digital Rights Management
|
||||||
|
*[DSP]: Digital Signal Processor
|
||||||
|
*[ECH]: Encrypted Client Hello
|
||||||
|
*[ECPA]: Electronic Communications Privacy Act
|
||||||
|
*[EFF]: Electronic Frontier Foundation
|
||||||
|
*[EIDV]: Electronic Identity Verification
|
||||||
|
*[EXIF]: Exchangeable Image File Format
|
||||||
|
*[FIDO]: Fast Identity Online
|
||||||
|
*[FISA]: Foreign Intelligence Surveillance Act
|
||||||
|
*[FOSS]: Free and Open-Source Software
|
||||||
|
*[FSB]: Federal Security Service (Russian Federation)
|
||||||
|
*[GDID]: Global Device Identifier (Windows OS)
|
||||||
|
*[GDPR]: General Data Protection Regulation
|
||||||
|
*[GEOINT]: Geospatial Intelligence
|
||||||
|
*[GFW]: Great Firewall of China
|
||||||
|
*[GSM]: Global System for Mobile Communications
|
||||||
|
*[HUMINT]: Human Intelligence
|
||||||
|
*[IANAL]: I Am Not A Lawyer
|
||||||
|
*[ICMP]: Internet Control Message Protocol
|
||||||
|
*[IMEI]: International Mobile Equipment Identity
|
||||||
|
*[IMSI]: International Mobile Subscriber Identity
|
||||||
|
*[IoT]: Internet of Things
|
||||||
|
*[ISP]: Internet Service Provider
|
||||||
|
*[IV]: Initialization Vector
|
||||||
|
*[KDF]: Key Derivation Function
|
||||||
|
*[KYC]: Know Your Customer
|
||||||
|
*[LUKS]: Linux Unified Key Setup
|
||||||
|
*[MAC]: Media Access Control (hardware address) or Message Authentication Code
|
||||||
|
*[MITM]: Man-in-the-Middle
|
||||||
|
*[MNO]: Mobile Network Operator
|
||||||
|
*[MTU]: Maximum Transmission Unit
|
||||||
|
*[NAT]: Network Address Translation
|
||||||
|
*[NIST]: National Institute of Standards and Technology
|
||||||
|
*[NLP]: Natural Language Processing
|
||||||
|
*[NTFS]: New Technology File System
|
||||||
|
*[OEM]: Original Equipment Manufacturer
|
||||||
|
*[OPSec]: Operational Security
|
||||||
|
*[OPSEC]: Operational Security
|
||||||
|
*[OSINT]: Open-Source Intelligence
|
||||||
|
*[OTP]: One-Time Password or One-Time Pad
|
||||||
|
*[PGP]: Pretty Good Privacy
|
||||||
|
*[PKI]: Public Key Infrastructure
|
||||||
|
*[POP]: Point of Presence
|
||||||
|
*[PQ]: Post-Quantum
|
||||||
|
*[PQXDH]: Post-Quantum Extended Diffie-Hellman
|
||||||
|
*[PSP]: A hardware-based security subsystem built into AMD processors
|
||||||
|
*[PTR]: Pointer (DNS record type)
|
||||||
|
*[QUIC]: Quick UDP Internet Connections
|
||||||
|
*[RF]: Radio Frequency
|
||||||
|
*[RSA]: Rivest-Shamir-Adleman (asymmetric cryptographic algorithm)
|
||||||
|
*[SDR]: Software-Defined Radio
|
||||||
|
*[SIGINT]: Signals Intelligence
|
||||||
|
*[SNI]: Server Name Indication
|
||||||
|
*[SOC]: System on a Chip or Security Operations Center
|
||||||
|
*[SSH]: Secure Shell
|
||||||
|
*[SSL]: Secure Sockets Layer
|
||||||
|
*[SSID]: Service Set Identifier (Wi-Fi network name)
|
||||||
|
*[TCP]: Transmission Control Protocol
|
||||||
|
*[TLS]: Transport Layer Security
|
||||||
|
*[TOR]: The Onion Router
|
||||||
|
*[Tor]: The Onion Router
|
||||||
|
*[TPM]: Trusted Platform Module
|
||||||
|
*[UA]: User Agent
|
||||||
|
*[UEFI]: Unified Extensible Firmware Interface
|
||||||
|
*[UUID]: Universally Unique Identifier
|
||||||
|
*[VoIP]: Voice over Internet Protocol
|
||||||
|
*[VPN]: Virtual Private Network
|
||||||
|
*[WebRTC]: Web Real-Time Communication
|
||||||
|
*[XMPP]: Extensible Messaging and Presence Protocol
|
||||||
|
*[XMP]: Extensible Metadata Platform
|
||||||
|
*[XSS]: Cross-Site Scripting
|
||||||
|
*[XTLS]: XTLS Protocol (Xray TLS)
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
---
|
---
|
||||||
title: "Home"
|
title: "Home"
|
||||||
description: "The Hitchhiker's Guide to Online Anonymity"
|
description: "The Hitchhiker's Guide to Online Anonymity"
|
||||||
|
hide:
|
||||||
|
- toc
|
||||||
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": WebPage
|
"@type": WebPage
|
||||||
"@id": https://anonymousplanet.net/
|
"@id": https://anonymousplanet.net/
|
||||||
name: Anonymous Planet
|
name: Anonymous Planet
|
||||||
url: https://anonymousplanet.net/
|
url: https://anonymousplanet.net/
|
||||||
logo: media/profile.png
|
logo: media/profile.svg
|
||||||
sameAs:
|
sameAs:
|
||||||
- https://github.com/Anon-Planet
|
- https://github.com/Anon-Planet
|
||||||
- https://opencollective.com/anonymousplanetorg
|
- https://opencollective.com/anonymousplanetorg
|
||||||
- https://mastodon.social/@anonymousplanet
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
<div class="hero-eyebrow">Anonymous Planet presents</div>
|
|
||||||
<h1 class="hero-title">The Hitchhiker's Guide<br><span class="hero-subtitle">to Online Anonymity (and Privacy)</span></h1>
|
<h1 class="hero-title">The Hitchhiker's Guide<br><span class="hero-subtitle">to Online Anonymity (and Privacy)</span></h1>
|
||||||
<p class="hero-tagline">
|
<p class="hero-tagline">
|
||||||
You are being tracked. Your metadata is being sold. Your identity is a product. This guide is your manual out.
|
You are being tracked. Your metadata is being sold. Your identity is a product. This guide is your manual out.
|
||||||
|
|||||||
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
<svg width="100%" viewBox="0 0 680 260" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet</title>
|
||||||
|
<desc>A hacker terminal style logo: an eye shape with a curved orbital arc passing through it and a small satellite riding the arc, set inside a bracket frame</desc>
|
||||||
|
<style>
|
||||||
|
.ap-frame { stroke: #0a8a3d80; }
|
||||||
|
.ap-fill { fill: #eef6ee; }
|
||||||
|
.ap-line { stroke: #0a8a3d; }
|
||||||
|
.ap-line-faint { stroke: #0a8a3d80; }
|
||||||
|
.ap-core { fill: #007a3d; }
|
||||||
|
.ap-accent { stroke: #007a3d; }
|
||||||
|
.ap-accent-fill { fill: #007a3d; }
|
||||||
|
|
||||||
|
[data-md-color-scheme="slate"] .ap-frame { stroke: #0b850080; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-fill { fill: #0f1f0f; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-line { stroke: #039634; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-line-faint { stroke: #0b850080; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-core { fill: #00f080; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-accent { stroke: #00f080; }
|
||||||
|
[data-md-color-scheme="slate"] .ap-accent-fill { fill: #00f080; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.ap-frame { stroke: #0b850080; }
|
||||||
|
.ap-fill { fill: #0f1f0f; }
|
||||||
|
.ap-line { stroke: #039634; }
|
||||||
|
.ap-line-faint { stroke: #0b850080; }
|
||||||
|
.ap-core { fill: #00f080; }
|
||||||
|
.ap-accent { stroke: #00f080; }
|
||||||
|
.ap-accent-fill { fill: #00f080; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="ap-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<path class="ap-frame" d="M250 40 L230 40 L230 110" fill="none" stroke-width="1.5"/>
|
||||||
|
<path class="ap-frame" d="M430 40 L450 40 L450 110" fill="none" stroke-width="1.5"/>
|
||||||
|
<path class="ap-frame" d="M250 220 L230 220 L230 150" fill="none" stroke-width="1.5"/>
|
||||||
|
<path class="ap-frame" d="M430 220 L450 220 L450 150" fill="none" stroke-width="1.5"/>
|
||||||
|
|
||||||
|
<path class="ap-fill ap-line" d="M255 130 C 285 85, 395 85, 425 130 C 395 175, 285 175, 255 130 Z" stroke-width="0.75"/>
|
||||||
|
|
||||||
|
<circle class="ap-line" cx="340" cy="130" r="32" fill="none" stroke-width="0.5"/>
|
||||||
|
<circle class="ap-core" cx="340" cy="130" r="13" opacity="0.10"/>
|
||||||
|
<circle class="ap-line" cx="340" cy="130" r="13" fill="none" stroke-width="0.5"/>
|
||||||
|
|
||||||
|
<g class="ap-line-faint" stroke-width="0.5">
|
||||||
|
<line x1="340" y1="98" x2="340" y2="106"/>
|
||||||
|
<line x1="340" y1="154" x2="340" y2="162"/>
|
||||||
|
<line x1="308" y1="130" x2="316" y2="130"/>
|
||||||
|
<line x1="364" y1="130" x2="372" y2="130"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<circle class="ap-core" cx="340" cy="130" r="2.5"/>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<circle class="ap-fill ap-line" cx="255" cy="130" r="3" stroke-width="1"/>
|
||||||
|
<circle class="ap-fill ap-line" cx="425" cy="130" r="3" stroke-width="1"/>
|
||||||
|
<circle class="ap-fill ap-line" cx="295" cy="98" r="2.5" stroke-width="1"/>
|
||||||
|
<circle class="ap-fill ap-line" cx="385" cy="98" r="2.5" stroke-width="1"/>
|
||||||
|
<circle class="ap-fill ap-line" cx="295" cy="162" r="2.5" stroke-width="1"/>
|
||||||
|
<circle class="ap-fill ap-line" cx="385" cy="162" r="2.5" stroke-width="1"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<path class="ap-accent" d="M225 185 C 300 130, 380 130, 455 75" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<circle class="ap-accent-fill" cx="225" cy="185" r="3"/>
|
||||||
|
<circle class="ap-accent-fill" cx="455" cy="75" r="3"/>
|
||||||
|
|
||||||
|
<g transform="translate(403, 95) rotate(-32)">
|
||||||
|
<rect class="ap-fill ap-accent" x="-7" y="-5" width="14" height="10" rx="1.5" stroke-width="1"/>
|
||||||
|
<rect class="ap-accent" x="-22" y="-2" width="12" height="4" fill="none" stroke-width="0.75"/>
|
||||||
|
<line class="ap-accent" x1="-22" y1="0" x2="-10" y2="0" stroke-width="0.5"/>
|
||||||
|
<rect class="ap-accent" x="10" y="-2" width="12" height="4" fill="none" stroke-width="0.75"/>
|
||||||
|
<line class="ap-accent" x1="10" y1="0" x2="22" y2="0" stroke-width="0.5"/>
|
||||||
|
<line class="ap-accent" x1="0" y1="-5" x2="0" y2="-11" stroke-width="0.75"/>
|
||||||
|
<circle class="ap-accent-fill" cx="0" cy="-11" r="1.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="220" height="220" viewBox="0 0 220 220" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet favicon mark: a simplified eye outline with a solid pupil</title>
|
||||||
|
<desc>A minimal eye outline with a filled pupil, simplified for legibility at very small sizes such as a browser favicon.</desc>
|
||||||
|
<path d="M 25,110 Q 110,64 195,110 Q 110,156 25,110 Z" fill="none" stroke="#00f080" stroke-width="6" stroke-linejoin="round"/>
|
||||||
|
<circle cx="110" cy="110" r="17" fill="#060d06" stroke="#00f080" stroke-width="3"/>
|
||||||
|
<circle cx="110" cy="110" r="5" fill="#00f080"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="220" height="220" viewBox="0 0 220 220" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet favicon mark: a simplified eye outline with a solid pupil</title>
|
||||||
|
<desc>A minimal eye outline with a filled pupil, simplified for legibility at very small sizes such as a browser favicon.</desc>
|
||||||
|
<path d="M 25,110 Q 110,64 195,110 Q 110,156 25,110 Z" fill="none" stroke="#4a3800" stroke-width="6" stroke-linejoin="round"/>
|
||||||
|
<circle cx="110" cy="110" r="17" fill="#1a1a1a" stroke="#4a3800" stroke-width="3"/>
|
||||||
|
<circle cx="110" cy="110" r="5" fill="#4a3800"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,36 @@
|
|||||||
|
<svg width="220" height="150" viewBox="0 0 220 150" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet mark: a simplified eye with a striated iris and crosshair reticle</title>
|
||||||
|
<desc>A standalone eye mark with a radially striated iris and a crosshair target reticle over the pupil, used as a compact icon without the full seal border.</desc>
|
||||||
|
<path d="M 29,72 Q 110,29 191,72" fill="none" stroke="#00f080" stroke-width="1" opacity="0.45"/>
|
||||||
|
<path d="M 25,80 Q 110,34 195,80 Q 110,126 25,80 Z" fill="none" stroke="#00f080" stroke-width="2.6" stroke-linejoin="round"/>
|
||||||
|
<g stroke="#00f080" stroke-width="1" opacity="0.6" stroke-linecap="round">
|
||||||
|
<line x1="30.4" y1="83.9" x2="18.7" y2="89.1"/>
|
||||||
|
<line x1="34.3" y1="89.1" x2="25.2" y2="97.0"/>
|
||||||
|
<line x1="39.6" y1="93.0" x2="33.0" y2="103.5"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="110" cy="80" r="32" fill="none" stroke="#00f080" stroke-width="0.8" opacity="0.5"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(0.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(22.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(45.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(67.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(90.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(112.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(135.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(157.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(180.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(202.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(225.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(247.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(270.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(292.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(315.0 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(337.5 110 80)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<circle cx="110" cy="80" r="17" fill="#060d06"/>
|
||||||
|
<g stroke="#00f080" stroke-width="1.6" stroke-linecap="round">
|
||||||
|
<line x1="110" y1="56" x2="110" y2="70"/>
|
||||||
|
<line x1="110" y1="90" x2="110" y2="104"/>
|
||||||
|
<line x1="86" y1="80" x2="100" y2="80"/>
|
||||||
|
<line x1="134" y1="80" x2="120" y2="80"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="110" cy="80" r="2.1" fill="#00f080"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,36 @@
|
|||||||
|
<svg width="220" height="150" viewBox="0 0 220 150" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet mark: a simplified eye with a striated iris and crosshair reticle</title>
|
||||||
|
<desc>A standalone eye mark with a radially striated iris and a crosshair target reticle over the pupil, used as a compact icon without the full seal border.</desc>
|
||||||
|
<path d="M 29,72 Q 110,29 191,72" fill="none" stroke="#4a3800" stroke-width="1" opacity="0.45"/>
|
||||||
|
<path d="M 25,80 Q 110,34 195,80 Q 110,126 25,80 Z" fill="none" stroke="#4a3800" stroke-width="2.6" stroke-linejoin="round"/>
|
||||||
|
<g stroke="#4a3800" stroke-width="1" opacity="0.6" stroke-linecap="round">
|
||||||
|
<line x1="30.4" y1="83.9" x2="18.7" y2="89.1"/>
|
||||||
|
<line x1="34.3" y1="89.1" x2="25.2" y2="97.0"/>
|
||||||
|
<line x1="39.6" y1="93.0" x2="33.0" y2="103.5"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="110" cy="80" r="32" fill="none" stroke="#4a3800" stroke-width="0.8" opacity="0.5"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(0.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(22.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(45.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(67.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(90.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(112.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(135.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(157.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(180.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(202.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(225.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(247.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(270.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(292.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(315.0 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="110" y1="63" x2="110" y2="48" transform="rotate(337.5 110 80)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<circle cx="110" cy="80" r="17" fill="#1a1a1a"/>
|
||||||
|
<g stroke="#4a3800" stroke-width="1.6" stroke-linecap="round">
|
||||||
|
<line x1="110" y1="56" x2="110" y2="70"/>
|
||||||
|
<line x1="110" y1="90" x2="110" y2="104"/>
|
||||||
|
<line x1="86" y1="80" x2="100" y2="80"/>
|
||||||
|
<line x1="134" y1="80" x2="120" y2="80"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="110" cy="80" r="2.1" fill="#4a3800"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,102 @@
|
|||||||
|
<svg width="340" height="340" viewBox="0 0 340 340" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet seal: an eye with a striated iris and crosshair reticle, under watch by two satellites, circuit traces and a subtle grid inside a ringed badge</title>
|
||||||
|
<desc>A circular seal mark. A horizontal eye sits at the center with a radially striated iris and a crosshair target reticle over the pupil. Two satellites float at different angles nearby, unconnected to the eye, representing observation from multiple vantage points. Short circuit-trace lines with node pads radiate toward the ring, and a faint coordinate grid sits behind everything, all inside a double-ring circular border with tick marks at the four cardinal points.</desc>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="circleClip"><circle cx="170" cy="170" r="141"/></clipPath>
|
||||||
|
<clipPath id="eyeClip"><path d="M 85,170 Q 170,124 255,170 Q 170,216 85,170 Z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#circleClip)" stroke="#00f080" stroke-width="0.5" opacity="0.08">
|
||||||
|
<line x1="66" y1="20" x2="66" y2="320"/>
|
||||||
|
<line x1="20" y1="66" x2="320" y2="66"/>
|
||||||
|
<line x1="118" y1="20" x2="118" y2="320"/>
|
||||||
|
<line x1="20" y1="118" x2="320" y2="118"/>
|
||||||
|
<line x1="170" y1="20" x2="170" y2="320"/>
|
||||||
|
<line x1="20" y1="170" x2="320" y2="170"/>
|
||||||
|
<line x1="222" y1="20" x2="222" y2="320"/>
|
||||||
|
<line x1="20" y1="222" x2="320" y2="222"/>
|
||||||
|
<line x1="274" y1="20" x2="274" y2="320"/>
|
||||||
|
<line x1="20" y1="274" x2="320" y2="274"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line x1="224" y1="264" x2="237" y2="286" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="237" cy="286" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="224" cy="264" r="1.1" fill="#00f080"/>
|
||||||
|
<line x1="116" y1="264" x2="103" y2="286" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="103" cy="286" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="116" cy="264" r="1.1" fill="#00f080"/>
|
||||||
|
<line x1="77" y1="224" x2="54" y2="237" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="54" cy="237" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="77" cy="224" r="1.1" fill="#00f080"/>
|
||||||
|
<line x1="77" y1="116" x2="54" y2="103" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="54" cy="103" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="77" cy="116" r="1.1" fill="#00f080"/>
|
||||||
|
<line x1="142" y1="66" x2="135" y2="41" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="135" cy="41" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="142" cy="66" r="1.1" fill="#00f080"/>
|
||||||
|
<line x1="224" y1="77" x2="237" y2="54" stroke="#00f080" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="237" cy="54" r="2.2" fill="#00f080"/>
|
||||||
|
<circle cx="224" cy="77" r="1.1" fill="#00f080"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="150" fill="none" stroke="#00f080" stroke-width="1.6"/>
|
||||||
|
<circle cx="170" cy="170" r="141" fill="none" stroke="#00f080" stroke-width="0.6" opacity="0.5"/>
|
||||||
|
<g stroke="#00f080" stroke-width="1.4" opacity="0.8" stroke-linecap="round">
|
||||||
|
<line x1="170" y1="20" x2="170" y2="32"/>
|
||||||
|
<line x1="170" y1="308" x2="170" y2="320"/>
|
||||||
|
<line x1="20" y1="170" x2="32" y2="170"/>
|
||||||
|
<line x1="308" y1="170" x2="320" y2="170"/>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#eyeClip)">
|
||||||
|
<line x1="85" y1="130" x2="255" y2="130" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="145" x2="255" y2="145" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="160" x2="255" y2="160" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="180" x2="255" y2="180" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="195" x2="255" y2="195" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="210" x2="255" y2="210" stroke="#00f080" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
</g>
|
||||||
|
<path d="M 89,162 Q 170,119 251,162" fill="none" stroke="#00f080" stroke-width="1" opacity="0.45"/>
|
||||||
|
<path d="M 85,170 Q 170,124 255,170 Q 170,216 85,170 Z" fill="none" stroke="#00f080" stroke-width="2.6" stroke-linejoin="round"/>
|
||||||
|
<g stroke="#00f080" stroke-width="1" opacity="0.6" stroke-linecap="round">
|
||||||
|
<line x1="90.4" y1="173.9" x2="78.7" y2="179.1"/>
|
||||||
|
<line x1="94.3" y1="179.1" x2="85.2" y2="187.0"/>
|
||||||
|
<line x1="99.6" y1="183.0" x2="93.0" y2="193.5"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="32" fill="none" stroke="#00f080" stroke-width="0.8" opacity="0.5"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(0.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(22.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(45.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(67.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(90.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(112.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(135.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(157.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(180.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(202.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(225.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(247.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(270.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(292.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(315.0 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(337.5 170 170)" stroke="#00f080" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<circle cx="170" cy="170" r="17" fill="#060d06"/>
|
||||||
|
<g stroke="#00f080" stroke-width="1.6" stroke-linecap="round">
|
||||||
|
<line x1="170" y1="146" x2="170" y2="160"/>
|
||||||
|
<line x1="170" y1="180" x2="170" y2="194"/>
|
||||||
|
<line x1="146" y1="170" x2="160" y2="170"/>
|
||||||
|
<line x1="194" y1="170" x2="180" y2="170"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="2.1" fill="#00f080"/>
|
||||||
|
<g transform="translate(104,98) rotate(35)" stroke="#00f080" fill="#00f080" stroke-linecap="round">
|
||||||
|
<rect x="-3.0" y="-3.0" width="6.0" height="6.0" rx="1.2" stroke="none"/>
|
||||||
|
<rect x="-1.3" y="-9.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<rect x="-1.3" y="3.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<line x1="-3.0" y1="0" x2="-10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
<line x1="3.0" y1="0" x2="10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(242,237) rotate(-60)" stroke="#00f080" fill="#00f080" stroke-linecap="round">
|
||||||
|
<rect x="-3.0" y="-3.0" width="6.0" height="6.0" rx="1.2" stroke="none"/>
|
||||||
|
<rect x="-1.3" y="-9.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<rect x="-1.3" y="3.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<line x1="-3.0" y1="0" x2="-10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
<line x1="3.0" y1="0" x2="10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,102 @@
|
|||||||
|
<svg width="340" height="340" viewBox="0 0 340 340" role="img" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Anonymous Planet seal: an eye with a striated iris and crosshair reticle, under watch by two satellites, circuit traces and a subtle grid inside a ringed badge</title>
|
||||||
|
<desc>A circular seal mark. A horizontal eye sits at the center with a radially striated iris and a crosshair target reticle over the pupil. Two satellites float at different angles nearby, unconnected to the eye, representing observation from multiple vantage points. Short circuit-trace lines with node pads radiate toward the ring, and a faint coordinate grid sits behind everything, all inside a double-ring circular border with tick marks at the four cardinal points.</desc>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="circleClip"><circle cx="170" cy="170" r="141"/></clipPath>
|
||||||
|
<clipPath id="eyeClip"><path d="M 85,170 Q 170,124 255,170 Q 170,216 85,170 Z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#circleClip)" stroke="#4a3800" stroke-width="0.5" opacity="0.08">
|
||||||
|
<line x1="66" y1="20" x2="66" y2="320"/>
|
||||||
|
<line x1="20" y1="66" x2="320" y2="66"/>
|
||||||
|
<line x1="118" y1="20" x2="118" y2="320"/>
|
||||||
|
<line x1="20" y1="118" x2="320" y2="118"/>
|
||||||
|
<line x1="170" y1="20" x2="170" y2="320"/>
|
||||||
|
<line x1="20" y1="170" x2="320" y2="170"/>
|
||||||
|
<line x1="222" y1="20" x2="222" y2="320"/>
|
||||||
|
<line x1="20" y1="222" x2="320" y2="222"/>
|
||||||
|
<line x1="274" y1="20" x2="274" y2="320"/>
|
||||||
|
<line x1="20" y1="274" x2="320" y2="274"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line x1="224" y1="264" x2="237" y2="286" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="237" cy="286" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="224" cy="264" r="1.1" fill="#4a3800"/>
|
||||||
|
<line x1="116" y1="264" x2="103" y2="286" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="103" cy="286" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="116" cy="264" r="1.1" fill="#4a3800"/>
|
||||||
|
<line x1="77" y1="224" x2="54" y2="237" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="54" cy="237" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="77" cy="224" r="1.1" fill="#4a3800"/>
|
||||||
|
<line x1="77" y1="116" x2="54" y2="103" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="54" cy="103" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="77" cy="116" r="1.1" fill="#4a3800"/>
|
||||||
|
<line x1="142" y1="66" x2="135" y2="41" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="135" cy="41" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="142" cy="66" r="1.1" fill="#4a3800"/>
|
||||||
|
<line x1="224" y1="77" x2="237" y2="54" stroke="#4a3800" stroke-width="1" opacity="0.5"/>
|
||||||
|
<circle cx="237" cy="54" r="2.2" fill="#4a3800"/>
|
||||||
|
<circle cx="224" cy="77" r="1.1" fill="#4a3800"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="150" fill="none" stroke="#4a3800" stroke-width="1.6"/>
|
||||||
|
<circle cx="170" cy="170" r="141" fill="none" stroke="#4a3800" stroke-width="0.6" opacity="0.5"/>
|
||||||
|
<g stroke="#4a3800" stroke-width="1.4" opacity="0.8" stroke-linecap="round">
|
||||||
|
<line x1="170" y1="20" x2="170" y2="32"/>
|
||||||
|
<line x1="170" y1="308" x2="170" y2="320"/>
|
||||||
|
<line x1="20" y1="170" x2="32" y2="170"/>
|
||||||
|
<line x1="308" y1="170" x2="320" y2="170"/>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#eyeClip)">
|
||||||
|
<line x1="85" y1="130" x2="255" y2="130" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="145" x2="255" y2="145" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="160" x2="255" y2="160" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="180" x2="255" y2="180" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="195" x2="255" y2="195" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
<line x1="85" y1="210" x2="255" y2="210" stroke="#4a3800" stroke-width="0.5" opacity="0.15"/>
|
||||||
|
</g>
|
||||||
|
<path d="M 89,162 Q 170,119 251,162" fill="none" stroke="#4a3800" stroke-width="1" opacity="0.45"/>
|
||||||
|
<path d="M 85,170 Q 170,124 255,170 Q 170,216 85,170 Z" fill="none" stroke="#4a3800" stroke-width="2.6" stroke-linejoin="round"/>
|
||||||
|
<g stroke="#4a3800" stroke-width="1" opacity="0.6" stroke-linecap="round">
|
||||||
|
<line x1="90.4" y1="173.9" x2="78.7" y2="179.1"/>
|
||||||
|
<line x1="94.3" y1="179.1" x2="85.2" y2="187.0"/>
|
||||||
|
<line x1="99.6" y1="183.0" x2="93.0" y2="193.5"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="32" fill="none" stroke="#4a3800" stroke-width="0.8" opacity="0.5"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(0.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(22.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(45.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(67.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(90.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(112.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(135.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(157.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(180.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(202.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(225.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(247.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(270.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(292.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(315.0 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<line x1="170" y1="153" x2="170" y2="138" transform="rotate(337.5 170 170)" stroke="#4a3800" stroke-width="0.7" opacity="0.55"/>
|
||||||
|
<circle cx="170" cy="170" r="17" fill="#1a1a1a"/>
|
||||||
|
<g stroke="#4a3800" stroke-width="1.6" stroke-linecap="round">
|
||||||
|
<line x1="170" y1="146" x2="170" y2="160"/>
|
||||||
|
<line x1="170" y1="180" x2="170" y2="194"/>
|
||||||
|
<line x1="146" y1="170" x2="160" y2="170"/>
|
||||||
|
<line x1="194" y1="170" x2="180" y2="170"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="170" cy="170" r="2.1" fill="#4a3800"/>
|
||||||
|
<g transform="translate(104,98) rotate(35)" stroke="#4a3800" fill="#4a3800" stroke-linecap="round">
|
||||||
|
<rect x="-3.0" y="-3.0" width="6.0" height="6.0" rx="1.2" stroke="none"/>
|
||||||
|
<rect x="-1.3" y="-9.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<rect x="-1.3" y="3.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<line x1="-3.0" y1="0" x2="-10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
<line x1="3.0" y1="0" x2="10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(242,237) rotate(-60)" stroke="#4a3800" fill="#4a3800" stroke-linecap="round">
|
||||||
|
<rect x="-3.0" y="-3.0" width="6.0" height="6.0" rx="1.2" stroke="none"/>
|
||||||
|
<rect x="-1.3" y="-9.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<rect x="-1.3" y="3.0" width="2.6" height="6.0" rx="0.6" stroke="none" opacity="0.9"/>
|
||||||
|
<line x1="-3.0" y1="0" x2="-10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
<line x1="3.0" y1="0" x2="10.4" y2="0" stroke-width="1.1"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -3,6 +3,7 @@ title: "Mirrors"
|
|||||||
description: "Every place you can read or download the Hitchhiker's Guide."
|
description: "Every place you can read or download the Hitchhiker's Guide."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- toc
|
||||||
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": Organization
|
"@type": Organization
|
||||||
@@ -13,7 +14,6 @@ schema:
|
|||||||
sameAs:
|
sameAs:
|
||||||
- https://github.com/Anon-Planet
|
- https://github.com/Anon-Planet
|
||||||
- https://opencollective.com/anonymousplanetorg
|
- https://opencollective.com/anonymousplanetorg
|
||||||
- https://mastodon.social/@anonymousplanet
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
@@ -118,4 +118,10 @@ schema:
|
|||||||
<a href="http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonymousplanetorg" class="index-card__link">Onion only</a>
|
<a href="http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonymousplanetorg" class="index-card__link">Onion only</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="index-card">
|
||||||
|
<h3 class="index-card__title">Tor Project</h3>
|
||||||
|
<p class="index-card__body">We are not affiliated with Tor Project, but we do love them.</p>
|
||||||
|
<a href="https://gitlab.torproject.org/anonymousplanet/the-hitchhikers-guide" class="index-card__link">Tor Project</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
<!-- for verifying Mastodon -->
|
||||||
|
{% block extrahead %}
|
||||||
|
<link href="https://ioc.exchange/@unknown" rel="me">
|
||||||
|
<link rel="icon" id="ap-favicon" href="{{ 'media/profile.png' | url }}" sizes="any">
|
||||||
|
<link rel="icon" type="image/
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{% set class = "md-header" %}
|
||||||
|
{% if "navigation.tabs.sticky" in features %}
|
||||||
|
{% set class = class ~ " md-header--shadow md-header--lifted" %}
|
||||||
|
{% elif "navigation.tabs" not in features %}
|
||||||
|
{% set class = class ~ " md-header--shadow" %}
|
||||||
|
{% endif %}
|
||||||
|
<header class="{{ class }}" data-md-component="header">
|
||||||
|
<nav class="md-header__inner md-grid" aria-label="{{ lang.t('header') }}">
|
||||||
|
<a href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}" title="{{ config.site_name | e }}" class="md-header__button md-logo" aria-label="{{ config.site_name }}" data-md-component="logo">
|
||||||
|
{% include "partials/logo.html" %}
|
||||||
|
</a>
|
||||||
|
<label class="md-header__button md-icon" for="__drawer">
|
||||||
|
{% set icon = config.theme.icon.menu or "material/menu" %}
|
||||||
|
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||||
|
</label>
|
||||||
|
<div class="md-header__title" data-md-component="header-title">
|
||||||
|
<div class="md-header__ellipsis">
|
||||||
|
<div class="md-header__topic">
|
||||||
|
<span class="md-ellipsis">
|
||||||
|
{{ config.site_name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="md-header__topic" data-md-component="header-topic">
|
||||||
|
<span class="md-ellipsis">
|
||||||
|
{% if page.meta and page.meta.title %}
|
||||||
|
{{ page.meta.title }}
|
||||||
|
{% else %}
|
||||||
|
{{ page.title }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if config.theme.palette %}
|
||||||
|
{% if not config.theme.palette is mapping %}
|
||||||
|
{% include "partials/palette-toggle.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if not config.theme.palette is mapping %}
|
||||||
|
{% include "partials/javascripts/palette.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if config.extra.alternate %}
|
||||||
|
{% include "partials/alternate.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if "material/search" in config.plugins %}
|
||||||
|
{% set search = config.plugins["material/search"] | attr("config") %}
|
||||||
|
{% if search.enabled %}
|
||||||
|
<label class="md-header__button md-icon" for="__search">
|
||||||
|
{% set icon = config.theme.icon.search or "material/magnify" %}
|
||||||
|
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||||
|
</label>
|
||||||
|
{% include "partials/search.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if config.repo_url %}
|
||||||
|
<div class="md-header__source">
|
||||||
|
{% include "partials/source.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
{% if "navigation.tabs.sticky" in features %}
|
||||||
|
{% if "navigation.tabs" in features %}
|
||||||
|
{% include "partials/tabs.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<div class="toggle-container toggle-container--header" id="ap-palette-toggle">
|
||||||
|
<div class="toggle-wrap">
|
||||||
|
<input type="checkbox" class="toggle-input" id="ap-toggle-input" aria-label="Turn the monitor on or off">
|
||||||
|
<label for="ap-toggle-input" class="toggle-track">
|
||||||
|
<div class="holo-glow"></div>
|
||||||
|
<div class="track-lines"><div class="track-line"></div></div>
|
||||||
|
<div class="toggle-thumb">
|
||||||
|
<div class="thumb-core"></div>
|
||||||
|
<div class="thumb-inner"></div>
|
||||||
|
<div class="thumb-scan"></div>
|
||||||
|
<div class="thumb-particles">
|
||||||
|
<div class="thumb-particle"></div>
|
||||||
|
<div class="thumb-particle"></div>
|
||||||
|
<div class="thumb-particle"></div>
|
||||||
|
<div class="thumb-particle"></div>
|
||||||
|
<div class="thumb-particle"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="energy-rings">
|
||||||
|
<div class="energy-ring"></div>
|
||||||
|
<div class="energy-ring"></div>
|
||||||
|
<div class="energy-ring"></div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-data">
|
||||||
|
<div class="data-text off">On</div>
|
||||||
|
<div class="data-text on">Off</div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-status-dot off"></div>
|
||||||
|
<div class="toggle-status-dot on"></div>
|
||||||
|
<div class="interface-lines">
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
<div class="interface-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-reflection"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var KEY = "__palette";
|
||||||
|
var input = document.getElementById("ap-toggle-input");
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
function currentScheme() {
|
||||||
|
return document.body.getAttribute("data-md-color-scheme") || "slate";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyScheme(scheme) {
|
||||||
|
document.body.setAttribute("data-md-color-scheme", scheme);
|
||||||
|
|
||||||
|
var stored = { index: scheme === "default" ? 1 : 0 };
|
||||||
|
try {
|
||||||
|
localStorage.setItem(KEY, JSON.stringify(stored));
|
||||||
|
} catch (e) {
|
||||||
|
/* storage unavailable, fail silently */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.checked = currentScheme() === "default";
|
||||||
|
|
||||||
|
input.addEventListener("change", function () {
|
||||||
|
applyScheme(input.checked ? "default" : "slate");
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
title: "PGP"
|
title: "PGP"
|
||||||
description: "Import our GPG keys to verify our releases and signed content."
|
description: "Import our GPG keys to verify our releases and signed content."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": Organization
|
"@type": Organization
|
||||||
@@ -13,7 +13,6 @@ schema:
|
|||||||
sameAs:
|
sameAs:
|
||||||
- https://github.com/Anon-Planet
|
- https://github.com/Anon-Planet
|
||||||
- https://opencollective.com/anonymousplanetorg
|
- https://opencollective.com/anonymousplanetorg
|
||||||
- https://mastodon.social/@anonymousplanet
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="hero-block">
|
<div class="hero-block">
|
||||||
|
|||||||
@@ -281,3 +281,33 @@ pre.highlight .hll {
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maintainer-bio {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1.25em;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintainer-avatar {
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintainer-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintainer-text p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.maintainer-bio {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,24 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Secondary TOC: restore depth differentiation the global .md-nav__link override removed */
|
||||||
|
.md-nav--secondary .md-nav__item .md-nav__link {
|
||||||
|
padding-left: 0.6rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-nav--secondary .md-nav__item .md-nav__item .md-nav__link {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
font-size: 0.55rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-nav--secondary .md-nav__item .md-nav__item .md-nav__item .md-nav__link {
|
||||||
|
padding-left: 1.8rem;
|
||||||
|
font-size: 0.52rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.md-nav__link--active,
|
.md-nav__link--active,
|
||||||
.md-nav__link[aria-current] {
|
.md-nav__link[aria-current] {
|
||||||
color: var(--accent-green) !important;
|
color: var(--accent-green) !important;
|
||||||
@@ -65,6 +83,38 @@
|
|||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-header__inner {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-header__button,
|
||||||
|
.md-header__source,
|
||||||
|
.toggle-container--header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-search {
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 76.1875em) {
|
||||||
|
.toggle-container--header {
|
||||||
|
width: 56px;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container--header .toggle-wrap {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 44.9375em) {
|
||||||
|
.toggle-container--header {
|
||||||
|
width: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.md-typeset {
|
.md-typeset {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
@@ -90,8 +140,30 @@
|
|||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 76.25em) {
|
.md-top {
|
||||||
.md-main__inner {
|
background-color: var(--bg-card) !important;
|
||||||
flex-direction: row-reverse;
|
color: var(--crt-green) !important;
|
||||||
}
|
border: 1px solid var(--border-color) !important;
|
||||||
|
left: 50% !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
transform: translate(-50%, 0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-top svg,
|
||||||
|
.md-top .md-icon {
|
||||||
|
fill: var(--crt-green) !important;
|
||||||
|
color: var(--crt-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-top:hover {
|
||||||
|
background-color: var(--bg-card) !important;
|
||||||
|
color: var(--accent-green) !important;
|
||||||
|
border-color: var(--accent-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-top:hover svg,
|
||||||
|
.md-top:hover .md-icon {
|
||||||
|
fill: var(--accent-green) !important;
|
||||||
|
color: var(--accent-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
font-family: 'EB Garamond', Georgia, 'Times New Roman', serif !important;
|
font-family: 'EB Garamond', Georgia, 'Times New Roman', serif !important;
|
||||||
font-size: 11pt !important;
|
font-size: 13pt !important;
|
||||||
line-height: 1.65 !important;
|
line-height: 1.45 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
@@ -58,11 +58,11 @@
|
|||||||
|
|
||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 25mm 28mm 28mm 28mm;
|
margin: 12mm 12mm 16mm 12mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@page :first {
|
@page :first {
|
||||||
margin-top: 30mm;
|
margin-top: 12mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 0 0 12mm !important;
|
padding: 0 !important;
|
||||||
float: none !important;
|
float: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,13 +150,13 @@
|
|||||||
content: none !important;
|
content: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, .md-typeset h1 { font-size: 20pt !important; margin: 18pt 0 6pt !important; }
|
h1, .md-typeset h1 { font-size: 23pt !important; margin: 20pt 0 7pt !important; }
|
||||||
h2, .md-typeset h2 { font-size: 15pt !important; margin: 14pt 0 5pt !important; }
|
h2, .md-typeset h2 { font-size: 18pt !important; margin: 16pt 0 6pt !important; }
|
||||||
h3, .md-typeset h3 { font-size: 12pt !important; margin: 10pt 0 4pt !important; }
|
h3, .md-typeset h3 { font-size: 14.5pt !important; margin: 12pt 0 5pt !important; }
|
||||||
h4, .md-typeset h4 { font-size: 11pt !important; margin: 8pt 0 3pt !important; }
|
h4, .md-typeset h4 { font-size: 13pt !important; margin: 9pt 0 4pt !important; }
|
||||||
h5, h6,
|
h5, h6,
|
||||||
.md-typeset h5,
|
.md-typeset h5,
|
||||||
.md-typeset h6 { font-size: 10pt !important; margin: 6pt 0 2pt !important; }
|
.md-typeset h6 { font-size: 12pt !important; margin: 7pt 0 3pt !important; }
|
||||||
|
|
||||||
p, li, dt, dd, blockquote p,
|
p, li, dt, dd, blockquote p,
|
||||||
.md-typeset p,
|
.md-typeset p,
|
||||||
@@ -165,20 +165,49 @@
|
|||||||
.md-typeset dd {
|
.md-typeset dd {
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
font-family: 'EB Garamond', Georgia, serif !important;
|
font-family: 'EB Garamond', Georgia, serif !important;
|
||||||
font-size: 11pt !important;
|
font-size: 13pt !important;
|
||||||
line-height: 1.65 !important;
|
line-height: 1.45 !important;
|
||||||
orphans: 3;
|
orphans: 3;
|
||||||
widows: 3;
|
widows: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-typeset p,
|
||||||
|
.md-typeset ul,
|
||||||
|
.md-typeset ol,
|
||||||
|
.md-typeset dl {
|
||||||
|
margin-top: 0.55em !important;
|
||||||
|
margin-bottom: 0.55em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-typeset li {
|
||||||
|
margin-bottom: 0.15em !important;
|
||||||
|
}
|
||||||
|
|
||||||
a, .md-typeset a {
|
a, .md-typeset a {
|
||||||
color: #00487a !important;
|
color: #00487a !important;
|
||||||
text-decoration: underline !important;
|
text-decoration: underline !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Glossary abbreviations are stripped from the PDF */
|
||||||
|
abbr, abbr[title],
|
||||||
|
.md-typeset abbr,
|
||||||
|
.md-typeset abbr[title] {
|
||||||
|
border-bottom: none !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
cursor: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr::before, abbr::after,
|
||||||
|
abbr[title]::before, abbr[title]::after,
|
||||||
|
.md-typeset abbr::before, .md-typeset abbr::after,
|
||||||
|
.md-typeset abbr[title]::before, .md-typeset abbr[title]::after {
|
||||||
|
content: none !important;
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
code, .md-typeset code {
|
code, .md-typeset code {
|
||||||
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
||||||
font-size: 9pt !important;
|
font-size: 10.5pt !important;
|
||||||
background: #f4f4f4 !important;
|
background: #f4f4f4 !important;
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
border: 1px solid #cccccc !important;
|
border: 1px solid #cccccc !important;
|
||||||
@@ -188,7 +217,7 @@
|
|||||||
|
|
||||||
pre, .md-typeset pre {
|
pre, .md-typeset pre {
|
||||||
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
||||||
font-size: 8.5pt !important;
|
font-size: 10pt !important;
|
||||||
background: #f8f8f8 !important;
|
background: #f8f8f8 !important;
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
border: 1px solid #cccccc !important;
|
border: 1px solid #cccccc !important;
|
||||||
@@ -196,8 +225,8 @@
|
|||||||
padding: 0.75em 1em !important;
|
padding: 0.75em 1em !important;
|
||||||
white-space: pre-wrap !important;
|
white-space: pre-wrap !important;
|
||||||
word-break: break-all !important;
|
word-break: break-all !important;
|
||||||
page-break-inside: avoid;
|
page-break-inside: auto;
|
||||||
break-inside: avoid;
|
break-inside: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code, .md-typeset pre code {
|
pre code, .md-typeset pre code {
|
||||||
@@ -210,23 +239,32 @@
|
|||||||
table, .md-typeset table {
|
table, .md-typeset table {
|
||||||
border-collapse: collapse !important;
|
border-collapse: collapse !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
page-break-inside: auto;
|
||||||
|
break-inside: auto;
|
||||||
|
font-size: 11.5pt !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr, .md-typeset tr {
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
font-size: 10pt !important;
|
}
|
||||||
|
|
||||||
|
thead, .md-typeset thead {
|
||||||
|
display: table-header-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, .md-typeset th {
|
th, .md-typeset th {
|
||||||
background: #eeeeee !important;
|
background: #eeeeee !important;
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
border: 1px solid #aaaaaa !important;
|
border: 1px solid #aaaaaa !important;
|
||||||
padding: 5pt 8pt !important;
|
padding: 3pt 6pt !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, .md-typeset td {
|
td, .md-typeset td {
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
border: 1px solid #cccccc !important;
|
border: 1px solid #cccccc !important;
|
||||||
padding: 4pt 8pt !important;
|
padding: 2.5pt 6pt !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:nth-child(even) td {
|
tr:nth-child(even) td {
|
||||||
@@ -240,8 +278,8 @@
|
|||||||
border-left: 4px solid #555555 !important;
|
border-left: 4px solid #555555 !important;
|
||||||
border: 1px solid #cccccc !important;
|
border: 1px solid #cccccc !important;
|
||||||
color: #1a1a1a !important;
|
color: #1a1a1a !important;
|
||||||
page-break-inside: avoid;
|
page-break-inside: auto;
|
||||||
break-inside: avoid;
|
break-inside: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admonition-title,
|
.admonition-title,
|
||||||
|
|||||||
@@ -0,0 +1,581 @@
|
|||||||
|
/* Adapted from a holographic toggle concept (Uiverse.io by reglobby), recolored to match site palette */
|
||||||
|
|
||||||
|
.toggle-container {
|
||||||
|
position: relative;
|
||||||
|
width: 150px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
perspective: 800px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-self: stretch;
|
||||||
|
height: 60px;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-track {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 30, 10, 0.4);
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow:
|
||||||
|
0 0 15px rgba(0, 240, 128, 0.15),
|
||||||
|
inset 0 0 10px rgba(0, 0, 0, 0.8);
|
||||||
|
overflow: hidden;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
border: 1px solid rgba(0, 240, 128, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-track::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse at center,
|
||||||
|
rgba(0, 240, 128, 0.1) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 70%
|
||||||
|
),
|
||||||
|
linear-gradient(90deg, rgba(0, 90, 40, 0.1) 0%, rgba(0, 30, 10, 0.2) 100%);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-track::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
right: 2px;
|
||||||
|
height: 10px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 240, 128, 0.3) 0%,
|
||||||
|
rgba(3, 150, 52, 0.1) 100%
|
||||||
|
);
|
||||||
|
border-radius: 30px 30px 0 0;
|
||||||
|
opacity: 0.7;
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-lines {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 240, 128, 0.3) 0px,
|
||||||
|
rgba(0, 240, 128, 0.3) 5px,
|
||||||
|
transparent 5px,
|
||||||
|
transparent 15px
|
||||||
|
);
|
||||||
|
animation: track-line-move 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes track-line-move {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-thumb {
|
||||||
|
position: absolute;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
left: 3px;
|
||||||
|
top: 3px;
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(10, 60, 30, 0.9) 0%,
|
||||||
|
rgba(0, 30, 15, 0.8) 100%
|
||||||
|
);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 15px rgba(0, 0, 0, 0.5),
|
||||||
|
inset 0 0 15px rgba(0, 240, 128, 0.5);
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
z-index: 2;
|
||||||
|
border: 1px solid rgba(0, 240, 128, 0.6);
|
||||||
|
overflow: hidden;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-core {
|
||||||
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(0, 240, 128, 0.6) 0%,
|
||||||
|
rgba(3, 150, 52, 0.2) 100%
|
||||||
|
);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 240, 128, 0.5);
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-inner {
|
||||||
|
position: absolute;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(255, 255, 255, 0.8) 0%,
|
||||||
|
rgba(150, 255, 200, 0.5) 100%
|
||||||
|
);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 10px rgba(150, 255, 200, 0.7);
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
animation: pulse 2s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 0.5; transform: translate(-50%, -50%) scale(0.9); }
|
||||||
|
100% { opacity: 0.8; transform: translate(-50%, -50%) scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-scan {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 240, 128, 0.5) 20%,
|
||||||
|
rgba(255, 255, 255, 0.8) 50%,
|
||||||
|
rgba(0, 240, 128, 0.5) 80%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
filter: blur(1px);
|
||||||
|
animation: thumb-scan 2s linear infinite;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes thumb-scan {
|
||||||
|
0% { top: -5px; opacity: 0; }
|
||||||
|
20% { opacity: 0.7; }
|
||||||
|
80% { opacity: 0.7; }
|
||||||
|
100% { top: 54px; opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-particles {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-particle {
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
background: rgba(150, 255, 200, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 5px rgba(150, 255, 200, 0.8);
|
||||||
|
animation: thumb-particle-float 3s infinite ease-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-particle:nth-child(1) { top: 70%; left: 30%; animation-delay: 0.2s; }
|
||||||
|
.thumb-particle:nth-child(2) { top: 60%; left: 60%; animation-delay: 0.6s; }
|
||||||
|
.thumb-particle:nth-child(3) { top: 50%; left: 40%; animation-delay: 1s; }
|
||||||
|
.thumb-particle:nth-child(4) { top: 40%; left: 70%; animation-delay: 1.4s; }
|
||||||
|
.thumb-particle:nth-child(5) { top: 80%; left: 50%; animation-delay: 1.8s; }
|
||||||
|
|
||||||
|
@keyframes thumb-particle-float {
|
||||||
|
0% { transform: translateY(0) scale(1); opacity: 0; }
|
||||||
|
20% { opacity: 0.8; }
|
||||||
|
100% { transform: translateY(-30px) scale(0); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-data {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-text {
|
||||||
|
position: absolute;
|
||||||
|
font-family: var(--code-font, monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-text.off {
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
opacity: 1;
|
||||||
|
color: rgba(0, 240, 128, 0.6);
|
||||||
|
text-shadow: 0 0 5px rgba(0, 240, 128, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-text.on {
|
||||||
|
left: 15px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
color: rgba(231, 217, 163, 0.7);
|
||||||
|
text-shadow: 0 0 5px rgba(231, 217, 163, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-status-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(0, 240, 128, 0.8) 0%,
|
||||||
|
rgba(3, 150, 52, 0.4) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 240, 128, 0.5);
|
||||||
|
animation: blink 2s infinite alternate;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-status-dot.off { top: 25px; right: 15px; }
|
||||||
|
|
||||||
|
.toggle-status-dot.on {
|
||||||
|
top: 25px;
|
||||||
|
left: 15px;
|
||||||
|
opacity: 0;
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(231, 217, 163, 0.8) 0%,
|
||||||
|
rgba(180, 160, 100, 0.4) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 10px rgba(231, 217, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 100% { opacity: 0.5; transform: scale(0.9); }
|
||||||
|
50% { opacity: 1; transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-rings {
|
||||||
|
position: absolute;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
left: 3px;
|
||||||
|
top: 3px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-ring:nth-child(1) {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-top-color: rgba(0, 240, 128, 0.5);
|
||||||
|
border-right-color: rgba(0, 240, 128, 0.3);
|
||||||
|
animation: spin 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-ring:nth-child(2) {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom-color: rgba(0, 240, 128, 0.5);
|
||||||
|
border-left-color: rgba(0, 240, 128, 0.3);
|
||||||
|
animation: spin 2s linear infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-ring:nth-child(3) {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-left-color: rgba(0, 240, 128, 0.5);
|
||||||
|
border-top-color: rgba(0, 240, 128, 0.3);
|
||||||
|
animation: spin 1.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.interface-lines {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interface-line {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(0, 240, 128, 0.3);
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interface-line:nth-child(1) { width: 15px; height: 1px; bottom: -5px; left: 20px; }
|
||||||
|
.interface-line:nth-child(2) { width: 1px; height: 8px; bottom: -12px; left: 35px; }
|
||||||
|
.interface-line:nth-child(3) { width: 25px; height: 1px; bottom: -12px; left: 35px; }
|
||||||
|
.interface-line:nth-child(4) { width: 15px; height: 1px; bottom: -5px; right: 20px; }
|
||||||
|
.interface-line:nth-child(5) { width: 1px; height: 8px; bottom: -12px; right: 35px; }
|
||||||
|
.interface-line:nth-child(6) { width: 25px; height: 1px; bottom: -12px; right: 10px; }
|
||||||
|
|
||||||
|
.toggle-reflection {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.1) 0%,
|
||||||
|
rgba(255, 255, 255, 0) 40%
|
||||||
|
);
|
||||||
|
border-radius: 30px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-family: var(--code-font, monospace);
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(0, 240, 128, 0.7);
|
||||||
|
text-shadow: 0 0 10px rgba(0, 240, 128, 0.5);
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-glow {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse at center,
|
||||||
|
rgba(0, 240, 128, 0.2) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 70%
|
||||||
|
);
|
||||||
|
filter: blur(10px);
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checked state = "default" (light/printing press) scheme, amber accent.
|
||||||
|
Bound to both :checked AND the page's actual data-md-color-scheme so the
|
||||||
|
toggle can never visually drift from the real active palette (e.g. on
|
||||||
|
load, before the init script has a chance to sync the checkbox). */
|
||||||
|
body[data-md-color-scheme="default"] .toggle-input + .toggle-track,
|
||||||
|
.toggle-input:checked + .toggle-track {
|
||||||
|
background: rgba(40, 32, 10, 0.4);
|
||||||
|
border-color: rgba(231, 217, 163, 0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 0 15px rgba(231, 217, 163, 0.2),
|
||||||
|
inset 0 0 10px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track::before, body[data-md-color-scheme="default"] .toggle-input + .toggle-track::before {
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse at center,
|
||||||
|
rgba(231, 217, 163, 0.1) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 70%
|
||||||
|
),
|
||||||
|
linear-gradient(90deg, rgba(120, 100, 50, 0.1) 0%, rgba(40, 32, 10, 0.2) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track::after, body[data-md-color-scheme="default"] .toggle-input + .toggle-track::after {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(231, 217, 163, 0.3) 0%,
|
||||||
|
rgba(180, 160, 100, 0.1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .track-line, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .track-line {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(231, 217, 163, 0.3) 0px,
|
||||||
|
rgba(231, 217, 163, 0.3) 5px,
|
||||||
|
transparent 5px,
|
||||||
|
transparent 15px
|
||||||
|
);
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .toggle-thumb, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .toggle-thumb {
|
||||||
|
left: calc(100% - 57px);
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(90, 75, 30, 0.9) 0%,
|
||||||
|
rgba(50, 40, 15, 0.8) 100%
|
||||||
|
);
|
||||||
|
border-color: rgba(231, 217, 163, 0.6);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 15px rgba(0, 0, 0, 0.5),
|
||||||
|
inset 0 0 15px rgba(231, 217, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .thumb-core, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .thumb-core {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(231, 217, 163, 0.6) 0%,
|
||||||
|
rgba(120, 100, 50, 0.2) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 20px rgba(231, 217, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .thumb-inner, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .thumb-inner {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(255, 255, 255, 0.8) 0%,
|
||||||
|
rgba(255, 240, 200, 0.5) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 10px rgba(255, 240, 200, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .thumb-scan, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .thumb-scan {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(231, 217, 163, 0.5) 20%,
|
||||||
|
rgba(255, 255, 255, 0.8) 50%,
|
||||||
|
rgba(231, 217, 163, 0.5) 80%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .thumb-particle, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .thumb-particle {
|
||||||
|
background: rgba(255, 240, 200, 0.8);
|
||||||
|
box-shadow: 0 0 5px rgba(255, 240, 200, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .data-text.off, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .data-text.off { opacity: 0; }
|
||||||
|
.toggle-input:checked + .toggle-track .data-text.on, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .data-text.on { opacity: 1; }
|
||||||
|
.toggle-input:checked + .toggle-track .toggle-status-dot.off, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .toggle-status-dot.off { opacity: 0; }
|
||||||
|
.toggle-input:checked + .toggle-track .toggle-status-dot.on, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .toggle-status-dot.on { opacity: 1; }
|
||||||
|
.toggle-input:checked + .toggle-track .energy-rings, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .energy-rings { left: calc(100% - 57px); }
|
||||||
|
.toggle-input:checked + .toggle-track .energy-ring, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .energy-ring { opacity: 1; }
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .energy-ring:nth-child(1), body[data-md-color-scheme="default"] .toggle-input + .toggle-track .energy-ring:nth-child(1) {
|
||||||
|
border-top-color: rgba(231, 217, 163, 0.5);
|
||||||
|
border-right-color: rgba(231, 217, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .energy-ring:nth-child(2), body[data-md-color-scheme="default"] .toggle-input + .toggle-track .energy-ring:nth-child(2) {
|
||||||
|
border-bottom-color: rgba(231, 217, 163, 0.5);
|
||||||
|
border-left-color: rgba(231, 217, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .energy-ring:nth-child(3), body[data-md-color-scheme="default"] .toggle-input + .toggle-track .energy-ring:nth-child(3) {
|
||||||
|
border-left-color: rgba(231, 217, 163, 0.5);
|
||||||
|
border-top-color: rgba(231, 217, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .interface-line, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .interface-line {
|
||||||
|
background: rgba(231, 217, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked ~ .toggle-label, body[data-md-color-scheme="default"] .toggle-input ~ .toggle-label {
|
||||||
|
color: rgba(231, 217, 163, 0.7);
|
||||||
|
text-shadow: 0 0 10px rgba(231, 217, 163, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .holo-glow, body[data-md-color-scheme="default"] .toggle-input + .toggle-track .holo-glow {
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse at center,
|
||||||
|
rgba(231, 217, 163, 0.2) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 70%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:hover + .toggle-track {
|
||||||
|
box-shadow:
|
||||||
|
0 0 20px rgba(0, 240, 128, 0.3),
|
||||||
|
inset 0 0 10px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input:checked:hover + .toggle-track, body[data-md-color-scheme="default"] .toggle-input:hover + .toggle-track {
|
||||||
|
box-shadow:
|
||||||
|
0 0 20px rgba(231, 217, 163, 0.3),
|
||||||
|
inset 0 0 10px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container--header {
|
||||||
|
width: 80px;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container--header .toggle-wrap {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
.toggle-container--header .toggle-thumb,
|
||||||
|
.toggle-container--header .energy-rings { width: 28px; height: 28px; }
|
||||||
|
.toggle-container--header .thumb-core { width: 20px; height: 20px; }
|
||||||
|
.toggle-container--header .thumb-inner { width: 12px; height: 12px; }
|
||||||
|
.toggle-container--header .data-text { font-size: 8px; }
|
||||||
|
.toggle-container--header .toggle-label { display: none; }
|
||||||
|
.toggle-container--header .energy-ring:nth-child(1) { width: 26px; height: 26px; }
|
||||||
|
.toggle-container--header .energy-ring:nth-child(2) { width: 20px; height: 20px; }
|
||||||
|
.toggle-container--header .energy-ring:nth-child(3) { width: 15px; height: 15px; }
|
||||||
|
|
||||||
|
.toggle-input:checked + .toggle-track .toggle-thumb,
|
||||||
|
.toggle-container--header .toggle-input:checked + .toggle-track .toggle-thumb,
|
||||||
|
body[data-md-color-scheme="default"] .toggle-container--header .toggle-input + .toggle-track .toggle-thumb {
|
||||||
|
left: calc(100% - 31px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container--header .toggle-input:checked + .toggle-track .energy-rings,
|
||||||
|
body[data-md-color-scheme="default"] .toggle-container--header .toggle-input + .toggle-track .energy-rings {
|
||||||
|
left: calc(100% - 31px);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
title: "Verify"
|
title: "Verify"
|
||||||
description: "Verify the authenticity and integrity of Anonymous Planet releases."
|
description: "Verify the authenticity and integrity of Anonymous Planet releases."
|
||||||
hide:
|
hide:
|
||||||
- toc
|
- navigation
|
||||||
schema:
|
schema:
|
||||||
"@context": https://schema.org
|
"@context": https://schema.org
|
||||||
"@type": Organization
|
"@type": Organization
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ed1fff860d113609cbeaf38beef5342b473d5f09b9263020ef0a82f42ba9c8d4d17ad2743267a76b0f315c4719f9866d3e2018bc8f4eb64a46d0fcbec0c6ee2f thgtoa.pdf
|
||||||
|
1bd77081bf7f2962f8a52a141e286fc7cb571e17b1b551d1baab5fd892cf874a1ace903301f263d7073251a1c53a4d141aa346fb0c87ea0af0b96703127f5133 thgtoa-dark.pdf
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
85e8824dcda6176ac4444fdc92ff5272527065aabb2c240d3e2f87418aa255b3 thgtoa.pdf
|
||||||
|
28675b585406f0e0543e4f63d5cff6507a064bf4d91db9ff82e4a826e8c8959d thgtoa-dark.pdf
|
||||||
@@ -3,12 +3,15 @@ site_author: Anonymous Planet
|
|||||||
site_description: "The comprehensive guide for online #anonymity and #opsec."
|
site_description: "The comprehensive guide for online #anonymity and #opsec."
|
||||||
site_dir: "site"
|
site_dir: "site"
|
||||||
docs_dir: "docs"
|
docs_dir: "docs"
|
||||||
|
exclude_docs: |
|
||||||
|
includes/
|
||||||
site_url: "https://anonymousplanet.net/"
|
site_url: "https://anonymousplanet.net/"
|
||||||
repo_url: "https://github.com/Anon-Planet/thgtoa"
|
repo_url: "https://github.com/Anon-Planet/thgtoa"
|
||||||
repo_name: ""
|
repo_name: ""
|
||||||
#edit_uri: ""blob/main/docs/{path}?plain=1""
|
#edit_uri: ""blob/main/docs/{path}?plain=1""
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
|
custom_dir: docs/overrides
|
||||||
locale: en
|
locale: en
|
||||||
favicon: media/profile.png
|
favicon: media/profile.png
|
||||||
icon:
|
icon:
|
||||||
@@ -21,7 +24,6 @@ theme:
|
|||||||
- navigation.tabs
|
- navigation.tabs
|
||||||
- navigation.sections
|
- navigation.sections
|
||||||
- navigation.path
|
- navigation.path
|
||||||
- toc.integrate
|
|
||||||
- navigation.top
|
- navigation.top
|
||||||
- search.suggest
|
- search.suggest
|
||||||
- search.highlight
|
- search.highlight
|
||||||
@@ -54,6 +56,7 @@ extra_css:
|
|||||||
- stylesheets/hacker.css
|
- stylesheets/hacker.css
|
||||||
- stylesheets/hacker-extra.css
|
- stylesheets/hacker-extra.css
|
||||||
- stylesheets/going-dark.css
|
- stylesheets/going-dark.css
|
||||||
|
- stylesheets/toggle.css
|
||||||
- stylesheets/navigation.css
|
- stylesheets/navigation.css
|
||||||
- stylesheets/footer.css
|
- stylesheets/footer.css
|
||||||
- stylesheets/accessibility.css
|
- stylesheets/accessibility.css
|
||||||
@@ -72,7 +75,7 @@ copyright: >-
|
|||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
- icon: simple/mastodon
|
- icon: simple/mastodon
|
||||||
link: https://ioc.exchange/@anonymousplanet
|
link: https://ioc.exchange/@unknown
|
||||||
name: Mastodon
|
name: Mastodon
|
||||||
- icon: simple/x
|
- icon: simple/x
|
||||||
link: https://x.com/anonplanetorg
|
link: https://x.com/anonplanetorg
|
||||||
@@ -92,6 +95,9 @@ extra:
|
|||||||
- icon: simple/codeberg
|
- icon: simple/codeberg
|
||||||
link: https://codeberg.org/anonymousplanet
|
link: https://codeberg.org/anonymousplanet
|
||||||
name: Codeberg
|
name: Codeberg
|
||||||
|
- icon: simple/torbrowser
|
||||||
|
link: https://gitlab.torproject.org/anonymousplanet/the-hitchhikers-guide
|
||||||
|
name: Gitlab at Tor Project
|
||||||
# - icon: simple/torbrowser
|
# - icon: simple/torbrowser
|
||||||
# link: http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion/ (defunct)
|
# link: http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion/ (defunct)
|
||||||
# name: Hidden service
|
# name: Hidden service
|
||||||
@@ -124,14 +130,17 @@ markdown_extensions:
|
|||||||
- pymdownx.keys: {}
|
- pymdownx.keys: {}
|
||||||
- pymdownx.mark: {}
|
- pymdownx.mark: {}
|
||||||
- pymdownx.tilde: {}
|
- pymdownx.tilde: {}
|
||||||
- pymdownx.snippets: {}
|
- abbr
|
||||||
|
- pymdownx.snippets:
|
||||||
|
base_path: ["docs"]
|
||||||
|
auto_append:
|
||||||
|
- includes/glossary.md
|
||||||
- pymdownx.tasklist:
|
- pymdownx.tasklist:
|
||||||
custom_checkbox: true
|
custom_checkbox: true
|
||||||
- attr_list: {}
|
- attr_list: {}
|
||||||
- def_list: {}
|
- def_list: {}
|
||||||
- md_in_html: {}
|
- md_in_html: {}
|
||||||
- meta: {}
|
- meta: {}
|
||||||
- abbr: {}
|
|
||||||
- tables: {}
|
- tables: {}
|
||||||
- footnotes: {}
|
- footnotes: {}
|
||||||
- toc:
|
- toc:
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
mkdocs==1.6.1
|
||||||
|
mkdocs-material[imaging]==9.7.2
|
||||||
@@ -377,7 +377,7 @@ def main() -> int:
|
|||||||
if not key:
|
if not key:
|
||||||
print("\n✗ Could not resolve a signing key. Aborting.")
|
print("\n✗ Could not resolve a signing key. Aborting.")
|
||||||
print(" Import the release key with:")
|
print(" Import the release key with:")
|
||||||
print(" gpg --import pgp/anonymousplanet-release.asc")
|
print(" gpg --import pgp/anonymousplanet.asc")
|
||||||
return 1
|
return 1
|
||||||
print(f" Resolved to: {key}")
|
print(f" Resolved to: {key}")
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts/build_guide_pdf.py # Light PDF only (clean light theme)
|
python scripts/build_guide_pdf.py # Light PDF only (clean light theme)
|
||||||
python scripts/build_guide_pdf.py --dark # Dark PDF only (requires light PDF to exist)
|
python scripts/build_guide_pdf.py --dark # Dark PDF only (requires a body-only
|
||||||
|
# cache from a prior light build in
|
||||||
|
# this checkout, e.g. via --both)
|
||||||
python scripts/build_guide_pdf.py --both # Light PDF, then dark PDF
|
python scripts/build_guide_pdf.py --both # Light PDF, then dark PDF
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -17,10 +19,12 @@ import argparse
|
|||||||
import html as _html_mod
|
import html as _html_mod
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -160,6 +164,76 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
|||||||
return pdf_out
|
return pdf_out
|
||||||
|
|
||||||
|
|
||||||
|
# Page numbers (stamped with qpdf; no extra Python dependencies)
|
||||||
|
|
||||||
|
def stamp_page_numbers(pdf_path: Path) -> None:
|
||||||
|
"""Stamp a right-aligned page number at the bottom of every page, in place.
|
||||||
|
|
||||||
|
Chromium's --print-to-pdf can't draw page numbers when the default
|
||||||
|
header/footer is disabled, and @page margin boxes need Chrome >= 131
|
||||||
|
(CI pins 120). So build a tiny overlay PDF by hand (standard Times font,
|
||||||
|
no dependencies) and merge it with `qpdf --overlay`.
|
||||||
|
|
||||||
|
Called on the body-only PDF, before the cached copy is taken and before
|
||||||
|
the cover + ToC are prepended, so the body starts at page 1 in both the
|
||||||
|
light and dark builds.
|
||||||
|
"""
|
||||||
|
n_pages = int(
|
||||||
|
subprocess.run(
|
||||||
|
["qpdf", "--show-npages", str(pdf_path)],
|
||||||
|
check=True, capture_output=True, text=True,
|
||||||
|
).stdout.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
width, height = 595.28, 841.89 # A4 in points (matches @page size)
|
||||||
|
font_size = 10
|
||||||
|
baseline_y = 28 # ~10 mm from the bottom edge (inside the 16 mm margin)
|
||||||
|
digit_w = 0.5 * font_size # Times-Roman digits are 500/1000 em
|
||||||
|
right_x = width - 12 * 72 / 25.4 # right text edge (12 mm page margin)
|
||||||
|
|
||||||
|
objs: list[bytes] = [b""] * (3 + 2 * n_pages)
|
||||||
|
kids = " ".join(f"{4 + 2 * i} 0 R" for i in range(n_pages))
|
||||||
|
objs[0] = b"<< /Type /Catalog /Pages 2 0 R >>"
|
||||||
|
objs[1] = f"<< /Type /Pages /Kids [{kids}] /Count {n_pages} >>".encode()
|
||||||
|
objs[2] = b"<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman >>"
|
||||||
|
for i in range(n_pages):
|
||||||
|
label = str(i + 1)
|
||||||
|
x = right_x - digit_w * len(label)
|
||||||
|
stream = f"BT /F1 {font_size} Tf 0.33 g {x:.2f} {baseline_y} Td ({label}) Tj ET".encode()
|
||||||
|
objs[3 + 2 * i] = (
|
||||||
|
f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {width} {height}] "
|
||||||
|
f"/Resources << /Font << /F1 3 0 R >> >> /Contents {5 + 2 * i} 0 R >>"
|
||||||
|
).encode()
|
||||||
|
objs[4 + 2 * i] = (
|
||||||
|
f"<< /Length {len(stream)} >>\nstream\n".encode() + stream + b"\nendstream"
|
||||||
|
)
|
||||||
|
|
||||||
|
out = bytearray(b"%PDF-1.4\n")
|
||||||
|
offsets: list[int] = []
|
||||||
|
for num, body in enumerate(objs, start=1):
|
||||||
|
offsets.append(len(out))
|
||||||
|
out += f"{num} 0 obj\n".encode() + body + b"\nendobj\n"
|
||||||
|
xref_pos = len(out)
|
||||||
|
out += f"xref\n0 {len(objs) + 1}\n".encode()
|
||||||
|
out += b"0000000000 65535 f \n"
|
||||||
|
for off in offsets:
|
||||||
|
out += f"{off:010d} 00000 n \n".encode()
|
||||||
|
out += (
|
||||||
|
f"trailer\n<< /Size {len(objs) + 1} /Root 1 0 R >>\n"
|
||||||
|
f"startxref\n{xref_pos}\n%%EOF\n"
|
||||||
|
).encode()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
overlay = Path(tmp) / "page-numbers.pdf"
|
||||||
|
overlay.write_bytes(bytes(out))
|
||||||
|
stamped = Path(tmp) / "stamped.pdf"
|
||||||
|
subprocess.run(
|
||||||
|
["qpdf", str(pdf_path), "--overlay", str(overlay), "--", str(stamped)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
shutil.move(str(stamped), str(pdf_path))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Front matter: ToC parsing + HTML generation
|
# Front matter: ToC parsing + HTML generation
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -198,7 +272,13 @@ def parse_toc_headings(guide_md: Path) -> list[tuple[int, str]]:
|
|||||||
return headings
|
return headings
|
||||||
|
|
||||||
|
|
||||||
def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool) -> str:
|
def create_toc_html(
|
||||||
|
tmp_dir: str,
|
||||||
|
headings: list[tuple[int, str]],
|
||||||
|
pages: list[int | None],
|
||||||
|
is_dark: bool,
|
||||||
|
scale: float = 1.0,
|
||||||
|
) -> str:
|
||||||
"""Write a research-style Table of Contents HTML file and return its path.
|
"""Write a research-style Table of Contents HTML file and return its path.
|
||||||
|
|
||||||
H2 entries: full-width row with dot leader.
|
H2 entries: full-width row with dot leader.
|
||||||
@@ -219,33 +299,31 @@ def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool
|
|||||||
h3color = "#444466"
|
h3color = "#444466"
|
||||||
|
|
||||||
rows: list[str] = []
|
rows: list[str] = []
|
||||||
for level, title in headings:
|
for (level, title), page in zip(headings, pages):
|
||||||
safe = _html_mod.escape(title)
|
safe = _html_mod.escape(title)
|
||||||
if level == 2:
|
page_html = f'<span class="toc-page">{page}</span>' if page else ""
|
||||||
rows.append(
|
cls = "toc-h2" if level == 2 else "toc-h3"
|
||||||
f'<div class="toc-h2">'
|
rows.append(
|
||||||
f'<span class="toc-title">{safe}</span>'
|
f'<div class="{cls}">'
|
||||||
f'<span class="toc-dots"></span>'
|
f'<span class="toc-title">{safe}</span>'
|
||||||
f"</div>"
|
f'<span class="toc-dots"></span>'
|
||||||
)
|
f"{page_html}"
|
||||||
else:
|
f"</div>"
|
||||||
rows.append(f'<div class="toc-h3">{safe}</div>')
|
)
|
||||||
|
|
||||||
body = "\n".join(rows)
|
body = "\n".join(rows)
|
||||||
|
|
||||||
css = (
|
css = (
|
||||||
"* { box-sizing: border-box; margin: 0; padding: 0; }\n"
|
"* { box-sizing: border-box; margin: 0; padding: 0; }\n"
|
||||||
"@page { size: A4; margin: 0; }\n"
|
"@page { size: A4; margin: 12mm 12mm 16mm 12mm; }\n"
|
||||||
"html, body {\n"
|
"html, body {\n"
|
||||||
f" width: 210mm;\n"
|
|
||||||
f" background: {bg};\n"
|
f" background: {bg};\n"
|
||||||
f" color: {fg};\n"
|
f" color: {fg};\n"
|
||||||
" font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;\n"
|
" font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
".page {\n"
|
".page {\n"
|
||||||
" width: 210mm;\n"
|
" padding: 0;\n"
|
||||||
" min-height: 297mm;\n"
|
f" zoom: {scale};\n"
|
||||||
" padding: 25mm 28mm 28mm 28mm;\n"
|
|
||||||
"}\n"
|
"}\n"
|
||||||
".toc-heading {\n"
|
".toc-heading {\n"
|
||||||
" font-size: 18pt;\n"
|
" font-size: 18pt;\n"
|
||||||
@@ -266,10 +344,14 @@ def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool
|
|||||||
" margin-bottom: 1.5pt;\n"
|
" margin-bottom: 1.5pt;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
".toc-title {\n"
|
".toc-title {\n"
|
||||||
" white-space: nowrap;\n"
|
" flex: 0 1 auto;\n"
|
||||||
" overflow: hidden;\n"
|
" max-width: 88%;\n"
|
||||||
|
"}\n"
|
||||||
|
".toc-page {\n"
|
||||||
" flex-shrink: 0;\n"
|
" flex-shrink: 0;\n"
|
||||||
" max-width: 85%;\n"
|
" padding-left: 4pt;\n"
|
||||||
|
" font-style: normal;\n"
|
||||||
|
" font-variant-numeric: tabular-nums;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
".toc-dots {\n"
|
".toc-dots {\n"
|
||||||
" flex: 1;\n"
|
" flex: 1;\n"
|
||||||
@@ -280,6 +362,8 @@ def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool
|
|||||||
" min-width: 8pt;\n"
|
" min-width: 8pt;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
".toc-h3 {\n"
|
".toc-h3 {\n"
|
||||||
|
" display: flex;\n"
|
||||||
|
" align-items: baseline;\n"
|
||||||
" font-size: 9pt;\n"
|
" font-size: 9pt;\n"
|
||||||
f" color: {h3color};\n"
|
f" color: {h3color};\n"
|
||||||
" padding-left: 10mm;\n"
|
" padding-left: 10mm;\n"
|
||||||
@@ -304,6 +388,76 @@ def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool
|
|||||||
return html_path
|
return html_path
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Locating headings in the built body PDF (for ToC page numbers)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def locate_heading_pages(
|
||||||
|
pdf_path: Path, headings: list[tuple[int, str]]
|
||||||
|
) -> list[int | None]:
|
||||||
|
"""Return the 1-based page each (level, title) heading starts on in pdf_path.
|
||||||
|
|
||||||
|
Uses pdftotext (poppler, already required by convert.py) and matches each
|
||||||
|
heading, in document order, against a whole line (or up to 3 consecutive
|
||||||
|
wrapped lines), so a passing mention in body text can't be mistaken for
|
||||||
|
the heading. Headings that can't be found get None: the ToC row then shows
|
||||||
|
dots only, and a warning is printed.
|
||||||
|
"""
|
||||||
|
def norm(s: str) -> str:
|
||||||
|
s = unicodedata.normalize("NFKC", s).replace("", "")
|
||||||
|
s = (s.replace("‘", "'").replace("’", "'")
|
||||||
|
.replace("“", '"').replace("”", '"')
|
||||||
|
.replace("–", "-").replace("—", "-"))
|
||||||
|
return " ".join(s.split()).casefold()
|
||||||
|
|
||||||
|
raw = subprocess.run(
|
||||||
|
["pdftotext", "-enc", "UTF-8", str(pdf_path), "-"],
|
||||||
|
check=True, capture_output=True,
|
||||||
|
).stdout.decode("utf-8", "replace")
|
||||||
|
|
||||||
|
lines: list[tuple[int, str]] = []
|
||||||
|
for page_no, page_text in enumerate(raw.split("\f"), start=1):
|
||||||
|
for line in page_text.splitlines():
|
||||||
|
n = norm(line)
|
||||||
|
if n:
|
||||||
|
lines.append((page_no, n))
|
||||||
|
|
||||||
|
result: list[int | None] = []
|
||||||
|
missing: list[str] = []
|
||||||
|
cursor = 0
|
||||||
|
for _level, title in headings:
|
||||||
|
target = norm(title)
|
||||||
|
hit: tuple[int, int] | None = None
|
||||||
|
for j in range(cursor, len(lines)):
|
||||||
|
joined = lines[j][1]
|
||||||
|
if not target.startswith(joined):
|
||||||
|
continue
|
||||||
|
for k in range(1, 4):
|
||||||
|
if k > 1:
|
||||||
|
if j + k - 1 >= len(lines):
|
||||||
|
break
|
||||||
|
joined = joined + " " + lines[j + k - 1][1]
|
||||||
|
if not target.startswith(joined):
|
||||||
|
break
|
||||||
|
if joined == target:
|
||||||
|
hit = (j, k)
|
||||||
|
break
|
||||||
|
if hit:
|
||||||
|
break
|
||||||
|
if hit:
|
||||||
|
result.append(lines[hit[0]][0])
|
||||||
|
cursor = hit[0] + hit[1]
|
||||||
|
else:
|
||||||
|
result.append(None)
|
||||||
|
missing.append(title)
|
||||||
|
|
||||||
|
found = len(headings) - len(missing)
|
||||||
|
print(f" Located {found}/{len(headings)} headings in the body PDF.", flush=True)
|
||||||
|
for t in missing:
|
||||||
|
print(f" Warning: no page found for heading: {t!r}", file=sys.stderr)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Shared Chromium render helper
|
# Shared Chromium render helper
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -331,7 +485,12 @@ def _render_html_to_pdf(
|
|||||||
# Front matter assembly: cover + ToC prepended to body PDF
|
# Front matter assembly: cover + ToC prepended to body PDF
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def prepend_front_matter(browser: Path, pdf_path: Path, is_dark: bool) -> Path:
|
def prepend_front_matter(
|
||||||
|
browser: Path,
|
||||||
|
pdf_path: Path,
|
||||||
|
is_dark: bool,
|
||||||
|
page_source: Path | None = None,
|
||||||
|
) -> Path:
|
||||||
"""Render cover + ToC and prepend them to pdf_path in-place.
|
"""Render cover + ToC and prepend them to pdf_path in-place.
|
||||||
|
|
||||||
- Cover HTML is sourced from convert.py (single definition).
|
- Cover HTML is sourced from convert.py (single definition).
|
||||||
@@ -350,6 +509,10 @@ def prepend_front_matter(browser: Path, pdf_path: Path, is_dark: bool) -> Path:
|
|||||||
n_h3 = sum(1 for l, _ in headings if l == 3)
|
n_h3 = sum(1 for l, _ in headings if l == 3)
|
||||||
print(f" Found {len(headings)} headings ({n_h2} H2, {n_h3} H3).", flush=True)
|
print(f" Found {len(headings)} headings ({n_h2} H2, {n_h3} H3).", flush=True)
|
||||||
|
|
||||||
|
# Page numbers come from a text-bearing body PDF: pdf_path itself for the
|
||||||
|
# light build; the cached light body for dark (its pages are rasterised).
|
||||||
|
pages = locate_heading_pages(page_source or pdf_path, headings)
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
print(" Rendering cover page...", flush=True)
|
print(" Rendering cover page...", flush=True)
|
||||||
cover_html = create_cover_page(tmp, is_dark=is_dark)
|
cover_html = create_cover_page(tmp, is_dark=is_dark)
|
||||||
@@ -357,9 +520,26 @@ def prepend_front_matter(browser: Path, pdf_path: Path, is_dark: bool) -> Path:
|
|||||||
_render_html_to_pdf(browser, cover_html, cover_pdf, ci=ci)
|
_render_html_to_pdf(browser, cover_html, cover_pdf, ci=ci)
|
||||||
|
|
||||||
print(" Rendering ToC page...", flush=True)
|
print(" Rendering ToC page...", flush=True)
|
||||||
toc_html = create_toc_html(tmp, headings, is_dark=is_dark)
|
toc_pdf = os.path.join(tmp, "toc.pdf")
|
||||||
toc_pdf = os.path.join(tmp, "toc.pdf")
|
|
||||||
_render_html_to_pdf(browser, toc_html, toc_pdf, ci=ci)
|
def _render_toc(scale: float) -> int:
|
||||||
|
html_path = create_toc_html(tmp, headings, pages, is_dark=is_dark, scale=scale)
|
||||||
|
_render_html_to_pdf(browser, html_path, toc_pdf, ci=ci)
|
||||||
|
return int(subprocess.run(
|
||||||
|
["qpdf", "--show-npages", toc_pdf],
|
||||||
|
check=True, capture_output=True, text=True,
|
||||||
|
).stdout.strip())
|
||||||
|
|
||||||
|
# If the ToC spills onto an extra page, shrink it a little at a time
|
||||||
|
# (down to 85%) until it fits in one page fewer; otherwise keep 100%.
|
||||||
|
toc_pages = _render_toc(1.0)
|
||||||
|
if toc_pages > 1:
|
||||||
|
for scale in (0.97, 0.94, 0.91, 0.88, 0.85):
|
||||||
|
if _render_toc(scale) < toc_pages:
|
||||||
|
print(f" ToC scaled to {scale:.0%} to save a page.", flush=True)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
_render_toc(1.0)
|
||||||
|
|
||||||
merged = str(pdf_path.parent / f".{pdf_path.name}.with-front")
|
merged = str(pdf_path.parent / f".{pdf_path.name}.with-front")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
@@ -373,19 +553,15 @@ def prepend_front_matter(browser: Path, pdf_path: Path, is_dark: bool) -> Path:
|
|||||||
return pdf_path
|
return pdf_path
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
def build_dark_pdf(browser: Path, body_pdf: Path, dark_pdf: Path) -> Path:
|
||||||
# Dark PDF build (delegates to convert.py which adds its own front matter)
|
"""Convert a body-only PDF to dark mode, then prepend fresh front matter."""
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def build_dark_pdf(light_pdf: Path, dark_pdf: Path) -> Path:
|
|
||||||
"""Convert the light PDF to dark mode using scripts/convert.py."""
|
|
||||||
convert_script = repo_root() / "scripts" / "convert.py"
|
convert_script = repo_root() / "scripts" / "convert.py"
|
||||||
print(f"Converting {light_pdf.name} -> {dark_pdf.name} (dark mode)...")
|
print(f"Converting {body_pdf.name} -> {dark_pdf.name} (dark mode)...")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[sys.executable, str(convert_script), str(light_pdf), str(dark_pdf)],
|
[sys.executable, str(convert_script), str(body_pdf), str(dark_pdf)],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
return dark_pdf
|
return prepend_front_matter(browser, dark_pdf, is_dark=True, page_source=body_pdf)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -423,13 +599,23 @@ def main() -> int:
|
|||||||
|
|
||||||
mode = ap.add_mutually_exclusive_group()
|
mode = ap.add_mutually_exclusive_group()
|
||||||
mode.add_argument("--dark", action="store_true",
|
mode.add_argument("--dark", action="store_true",
|
||||||
help="Dark PDF only (light PDF must already exist)")
|
help="Dark PDF only (requires a body-only cache from a prior light build)")
|
||||||
mode.add_argument("--both", action="store_true",
|
mode.add_argument("--both", action="store_true",
|
||||||
help="Build light PDF, then dark PDF")
|
help="Build light PDF, then dark PDF")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
build_light = not args.dark
|
build_light = not args.dark
|
||||||
build_dark = args.dark or args.both
|
build_dark = args.dark or args.both
|
||||||
|
body_cache = args.pdf_light.parent / f".{args.pdf_light.stem}.body.pdf"
|
||||||
|
|
||||||
|
browser = find_chromium_executable()
|
||||||
|
if not browser:
|
||||||
|
print(
|
||||||
|
"No Chromium-based browser found (Chrome, Edge, or Chromium). "
|
||||||
|
"Install Google Chrome or Microsoft Edge, or add Chromium to PATH.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
# --- Light PDF ---
|
# --- Light PDF ---
|
||||||
if build_light:
|
if build_light:
|
||||||
@@ -442,31 +628,30 @@ def main() -> int:
|
|||||||
print(f"Missing {guide_html}; run without --skip-mkdocs first.", file=sys.stderr)
|
print(f"Missing {guide_html}; run without --skip-mkdocs first.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
browser = find_chromium_executable()
|
|
||||||
if not browser:
|
|
||||||
print(
|
|
||||||
"No Chromium-based browser found (Chrome, Edge, or Chromium). "
|
|
||||||
"Install Google Chrome or Microsoft Edge, or add Chromium to PATH.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
out_light = print_to_pdf(browser, guide_html, args.pdf_light)
|
out_light = print_to_pdf(browser, guide_html, args.pdf_light)
|
||||||
|
print(" Stamping page numbers...", flush=True)
|
||||||
|
stamp_page_numbers(out_light)
|
||||||
|
|
||||||
|
import shutil as _shutil # noqa: PLC0415
|
||||||
|
body_cache.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
_shutil.copy2(out_light, body_cache)
|
||||||
|
|
||||||
out_light = prepend_front_matter(browser, out_light, is_dark=False)
|
out_light = prepend_front_matter(browser, out_light, is_dark=False)
|
||||||
size_kb = out_light.stat().st_size // 1024
|
size_kb = out_light.stat().st_size // 1024
|
||||||
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
||||||
|
|
||||||
# --- Dark PDF ---
|
# --- Dark PDF ---
|
||||||
if build_dark:
|
if build_dark:
|
||||||
if not args.pdf_light.exists():
|
if not body_cache.exists():
|
||||||
print(
|
print(
|
||||||
f"Light PDF not found at {args.pdf_light}. "
|
f"No body-only PDF cached at {body_cache}. "
|
||||||
"Run without --dark first, or use --both.",
|
"Run with --both (or a plain light build first) so a fresh "
|
||||||
|
"body-only copy exists before converting to dark mode.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
out_dark = build_dark_pdf(args.pdf_light, args.pdf_dark)
|
out_dark = build_dark_pdf(browser, body_cache, args.pdf_dark)
|
||||||
size_kb = out_dark.stat().st_size // 1024
|
size_kb = out_dark.stat().st_size // 1024
|
||||||
print(f"Wrote {out_dark.resolve()} ({size_kb} KiB) [Dark Mode]")
|
print(f"Wrote {out_dark.resolve()} ({size_kb} KiB) [Dark Mode]")
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ def apply_dark_theme(
|
|||||||
Remap a white-background page image to a dark theme.
|
Remap a white-background page image to a dark theme.
|
||||||
|
|
||||||
Strategy:
|
Strategy:
|
||||||
- Near-white pixels (page background) → bg color
|
- Near-white pixels (page background) → bg color
|
||||||
- Dark, low-saturation pixels (text) → text color
|
- Dark, low-saturation pixels (text) → text color
|
||||||
- Blue-dominant dark pixels (links) → link color
|
- Blue-dominant dark pixels (links) → link color
|
||||||
- High-saturation pixels (photos/images) → preserved exactly as-is
|
- High-saturation pixels (photos/images) → preserved exactly as-is
|
||||||
(no dark remapping; images stay at natural colors)
|
(no dark remapping; images stay at natural colors)
|
||||||
|
|
||||||
@@ -181,17 +181,36 @@ def _check_dependencies() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def create_cover_page(tmp_dir: str, is_dark: bool) -> str:
|
def create_cover_page(tmp_dir: str, is_dark: bool) -> str:
|
||||||
"""Create a research-style text-only cover page for Chromium rendering."""
|
"""Create the hacker-theme text cover page for Chromium rendering."""
|
||||||
|
# Hacker theme: monospace type with green/amber accents, drawn directly on
|
||||||
|
# the page so the cover matches the body pages in both builds.
|
||||||
if is_dark:
|
if is_dark:
|
||||||
bg_color = '#1f1f31'
|
bg_color = '#1f1f31'
|
||||||
text_color = '#e0e0e0'
|
green = '#33ff66'
|
||||||
rule_color = '#4a4a6a'
|
amber = '#ffb000'
|
||||||
meta_color = '#a0a0c0'
|
muted = '#a8b0c0'
|
||||||
|
glow = '0 0 6px rgba(51, 255, 102, 0.45)'
|
||||||
else:
|
else:
|
||||||
bg_color = '#ffffff'
|
bg_color = '#ffffff'
|
||||||
text_color = '#1a1a1a'
|
green = '#005c2e'
|
||||||
rule_color = '#cccccc'
|
amber = '#8a5a00'
|
||||||
meta_color = '#555555'
|
muted = '#555555'
|
||||||
|
glow = 'none'
|
||||||
|
|
||||||
|
# Embed Fira Code so the cover matches the site regardless of what the
|
||||||
|
# machine running Chromium has installed.
|
||||||
|
import base64
|
||||||
|
font_path = (Path(__file__).resolve().parent.parent / 'docs' / 'fonts'
|
||||||
|
/ 'fira-code' / 'fira-code-latin-wght-normal.woff2')
|
||||||
|
font_face = ''
|
||||||
|
if font_path.is_file():
|
||||||
|
b64 = base64.b64encode(font_path.read_bytes()).decode('ascii')
|
||||||
|
font_face = (
|
||||||
|
"@font-face { font-family: 'Fira Code Cover'; "
|
||||||
|
f"src: url(data:font/woff2;base64,{b64}) format('woff2'); "
|
||||||
|
"font-weight: 300 700; font-style: normal; }"
|
||||||
|
)
|
||||||
|
mono = "'Fira Code Cover', 'Fira Code', 'DejaVu Sans Mono', Consolas, 'Courier New', monospace"
|
||||||
|
|
||||||
html_path = os.path.join(tmp_dir, 'cover.html')
|
html_path = os.path.join(tmp_dir, 'cover.html')
|
||||||
with open(html_path, 'w', encoding='utf-8') as f:
|
with open(html_path, 'w', encoding='utf-8') as f:
|
||||||
@@ -200,6 +219,7 @@ def create_cover_page(tmp_dir: str, is_dark: bool) -> str:
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<style>
|
<style>
|
||||||
|
{font_face}
|
||||||
* {{
|
* {{
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -213,8 +233,9 @@ html, body {{
|
|||||||
width: 210mm;
|
width: 210mm;
|
||||||
height: 297mm;
|
height: 297mm;
|
||||||
background: {bg_color};
|
background: {bg_color};
|
||||||
color: {text_color};
|
font-family: {mono};
|
||||||
font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
}}
|
}}
|
||||||
.page {{
|
.page {{
|
||||||
width: 210mm;
|
width: 210mm;
|
||||||
@@ -223,41 +244,51 @@ html, body {{
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 30mm 25mm;
|
padding: 12mm 12mm 16mm 12mm;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}}
|
}}
|
||||||
.title {{
|
.title {{
|
||||||
font-size: 28pt;
|
font-size: 26pt;
|
||||||
font-weight: normal;
|
font-weight: 700;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: -0.01em;
|
||||||
margin-bottom: 10mm;
|
color: {green};
|
||||||
|
text-shadow: {glow};
|
||||||
|
margin-bottom: 12mm;
|
||||||
}}
|
}}
|
||||||
.rule {{
|
.cursor {{
|
||||||
width: 80mm;
|
display: inline-block;
|
||||||
height: 1px;
|
width: 0.55em;
|
||||||
background: {rule_color};
|
height: 0.95em;
|
||||||
margin: 0 auto 10mm auto;
|
margin-left: 0.15em;
|
||||||
|
background: {green};
|
||||||
|
vertical-align: -0.1em;
|
||||||
}}
|
}}
|
||||||
.subtitle {{
|
.subtitle {{
|
||||||
font-size: 13pt;
|
font-size: 12pt;
|
||||||
font-weight: normal;
|
line-height: 1.6;
|
||||||
font-style: italic;
|
color: {muted};
|
||||||
color: {meta_color};
|
|
||||||
margin-bottom: 14mm;
|
margin-bottom: 14mm;
|
||||||
}}
|
}}
|
||||||
.meta {{
|
.subtitle::before {{
|
||||||
font-size: 11pt;
|
content: "// ";
|
||||||
color: {meta_color};
|
color: {amber};
|
||||||
line-height: 1.9;
|
|
||||||
}}
|
}}
|
||||||
.meta strong {{
|
.info {{
|
||||||
color: {text_color};
|
align-self: flex-end;
|
||||||
font-weight: normal;
|
text-align: right;
|
||||||
|
margin-right: 13mm;
|
||||||
|
position: relative;
|
||||||
|
top: 10mm;
|
||||||
|
}}
|
||||||
|
.meta {{
|
||||||
|
font-size: 11.5pt;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: {green};
|
||||||
}}
|
}}
|
||||||
.version {{
|
.version {{
|
||||||
font-size: 11pt;
|
font-size: 11.5pt;
|
||||||
color: {meta_color};
|
color: {amber};
|
||||||
margin-top: 12mm;
|
margin-top: 12mm;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}}
|
}}
|
||||||
@@ -265,15 +296,16 @@ html, body {{
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<p class="title">The Hitchhiker’s Guide<br>to Online Anonymity</p>
|
<h1 class="title">The Hitchhiker's Guide<br>to Online Anonymity</h1>
|
||||||
<div class="rule"></div>
|
|
||||||
<p class="subtitle">The comprehensive guide for online anonymity and OpSec.</p>
|
<p class="subtitle">The comprehensive guide for online anonymity and OpSec.</p>
|
||||||
<div class="meta">
|
<div class="info">
|
||||||
<p><strong>Author</strong> Anonymous Planet</p>
|
<div class="meta">
|
||||||
<p><strong>License</strong> Creative Commons BY-SA 4.0</p>
|
<p>Anonymous Planet</p>
|
||||||
<p><strong>Source</strong> https://anonymousplanet.net</p>
|
<p>Creative Commons BY-SA 4.0</p>
|
||||||
|
<p>https://anonymousplanet.net</p>
|
||||||
|
</div>
|
||||||
|
<p class="version">v1.2.6 — October 24 2026</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="version">v1.2.5 — June 2026</p>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>""")
|
</html>""")
|
||||||
@@ -296,7 +328,9 @@ def convert_pdf_to_dark(
|
|||||||
avoid OOM, then merged with qpdf. Falls back to single-pass Pillow save
|
avoid OOM, then merged with qpdf. Falls back to single-pass Pillow save
|
||||||
if qpdf is not available (fine for small documents).
|
if qpdf is not available (fine for small documents).
|
||||||
|
|
||||||
Adds a cover page at the start with title/subtitle/version info.
|
Does not add a cover page or ToC. Pass a body-only PDF and prepend
|
||||||
|
front matter separately (build_guide_pdf.py does this for both the
|
||||||
|
light and dark builds so it's generated exactly once per PDF).
|
||||||
"""
|
"""
|
||||||
input_path = str(input_path)
|
input_path = str(input_path)
|
||||||
output_path = str(output_path)
|
output_path = str(output_path)
|
||||||
@@ -329,50 +363,15 @@ def convert_pdf_to_dark(
|
|||||||
if out_dir:
|
if out_dir:
|
||||||
os.makedirs(out_dir, exist_ok=True)
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
# Determine theme from filename for cover page
|
# 2. Determine theme from filename for downstream use by callers
|
||||||
|
# (front matter (cover + ToC) is prepended by build_guide_pdf.py,
|
||||||
|
# not here, so it's generated exactly once regardless of light/dark).
|
||||||
pdf_output = Path(output_path)
|
pdf_output = Path(output_path)
|
||||||
pdf_name = pdf_output.stem.lower()
|
pdf_name = pdf_output.stem.lower()
|
||||||
is_dark = 'dark' in pdf_name
|
is_dark = 'dark' in pdf_name
|
||||||
|
|
||||||
print(f" Theme: {'Going dark' if is_dark else 'Light mode'}")
|
print(f" Theme: {'Going dark' if is_dark else 'Light mode'}")
|
||||||
|
|
||||||
# 2. Build front matter: cover + ToC
|
|
||||||
# Import ToC helpers from build_guide_pdf.py (single source of truth).
|
|
||||||
scripts_dir = str(Path(__file__).resolve().parent)
|
|
||||||
if scripts_dir not in sys.path:
|
|
||||||
sys.path.insert(0, scripts_dir)
|
|
||||||
from build_guide_pdf import parse_toc_headings, create_toc_html # noqa: PLC0415
|
|
||||||
|
|
||||||
cover_html_path = create_cover_page(tmp, is_dark)
|
|
||||||
browser = find_chromium_executable()
|
|
||||||
|
|
||||||
if not browser:
|
|
||||||
raise RuntimeError(
|
|
||||||
"No Chromium-based browser found; needed for cover page rendering.\n"
|
|
||||||
"Install Chrome, Edge, or add Chromium to PATH."
|
|
||||||
)
|
|
||||||
|
|
||||||
cover_pdf = os.path.join(tmp, 'cover.pdf')
|
|
||||||
cmd = [str(browser), "--headless=new", "--disable-gpu",
|
|
||||||
"--no-pdf-header-footer", "--print-background", "--no-margins",
|
|
||||||
f"--print-to-pdf={cover_pdf}", cover_html_path]
|
|
||||||
subprocess.run(cmd, check=True, capture_output=True)
|
|
||||||
|
|
||||||
guide_md = Path(__file__).resolve().parent.parent / 'docs' / 'guide' / 'index.md'
|
|
||||||
if guide_md.is_file():
|
|
||||||
print(" Building ToC...", flush=True)
|
|
||||||
headings = parse_toc_headings(guide_md)
|
|
||||||
toc_html_path = create_toc_html(tmp, headings, is_dark)
|
|
||||||
toc_pdf = os.path.join(tmp, 'toc.pdf')
|
|
||||||
cmd_toc = [str(browser), "--headless=new", "--disable-gpu",
|
|
||||||
"--no-pdf-header-footer", "--print-background", "--no-margins",
|
|
||||||
f"--print-to-pdf={toc_pdf}", toc_html_path]
|
|
||||||
subprocess.run(cmd_toc, check=True, capture_output=True)
|
|
||||||
front_pages = [cover_pdf, toc_pdf]
|
|
||||||
else:
|
|
||||||
print(" Warning: guide/index.md not found, skipping ToC.", file=sys.stderr)
|
|
||||||
front_pages = [cover_pdf]
|
|
||||||
|
|
||||||
# 3. Process pages with theme remapping
|
# 3. Process pages with theme remapping
|
||||||
use_batches = total > batch_size and _check_qpdf()
|
use_batches = total > batch_size and _check_qpdf()
|
||||||
|
|
||||||
@@ -396,10 +395,10 @@ def convert_pdf_to_dark(
|
|||||||
_save_images_as_pdf(dark, batch_path)
|
_save_images_as_pdf(dark, batch_path)
|
||||||
batch_files.append(batch_path)
|
batch_files.append(batch_path)
|
||||||
|
|
||||||
# Merge batches with front matter using qpdf
|
# Merge batches into the final output using qpdf
|
||||||
print(" Merging batches and front matter...", flush=True)
|
print(" Merging batches...", flush=True)
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['qpdf', '--empty', '--pages'] + front_pages + batch_files + ['--', output_path],
|
['qpdf', '--empty', '--pages'] + batch_files + ['--', output_path],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -413,15 +412,6 @@ def convert_pdf_to_dark(
|
|||||||
|
|
||||||
_save_images_as_pdf(dark_pages, output_path)
|
_save_images_as_pdf(dark_pages, output_path)
|
||||||
|
|
||||||
# Prepend front matter to the single-pass output
|
|
||||||
if not use_batches:
|
|
||||||
tmp_body = os.path.join(tmp, 'body_only.pdf')
|
|
||||||
os.rename(output_path, tmp_body)
|
|
||||||
subprocess.run(
|
|
||||||
['qpdf', '--empty', '--pages'] + front_pages + [tmp_body] + ['--', output_path],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
size_mb = os.path.getsize(output_path) / 1024 / 1024
|
size_mb = os.path.getsize(output_path) / 1024 / 1024
|
||||||
print(f" Saved → {output_path} ({size_mb:.1f} MB)")
|
print(f" Saved → {output_path} ({size_mb:.1f} MB)")
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,18 @@ def categorise(messages: list[str]) -> dict[str, list[str]]:
|
|||||||
return buckets
|
return buckets
|
||||||
|
|
||||||
|
|
||||||
|
# Distinct admonition type per bucket so icons/colors differ (not all blue "note")
|
||||||
|
ADMONITION_TYPE: dict[str, str] = {
|
||||||
|
"Meta": "abstract",
|
||||||
|
"Added": "tip",
|
||||||
|
"Changed": "info",
|
||||||
|
"Fixed": "success",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def format_admonition(bucket: str, items: list[str]) -> str:
|
def format_admonition(bucket: str, items: list[str]) -> str:
|
||||||
lines = [f'!!! Note "{bucket}"', ""]
|
adm_type = ADMONITION_TYPE.get(bucket, "note")
|
||||||
|
lines = [f'!!! {adm_type} "{bucket}"', ""]
|
||||||
for item in items:
|
for item in items:
|
||||||
lines.append(f" - {item}")
|
lines.append(f" - {item}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
@@ -154,7 +164,7 @@ def format_admonition(bucket: str, items: list[str]) -> str:
|
|||||||
def build_entry(version: str, buckets: dict[str, list[str]], sha: str) -> str:
|
def build_entry(version: str, buckets: dict[str, list[str]], sha: str) -> str:
|
||||||
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||||
lines = [f"## [{version}]", ""]
|
lines = [f"## [{version}]", ""]
|
||||||
lines.append('!!! Note "Meta"')
|
lines.append('!!! abstract "Meta"')
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f" - Released {date} from [`{sha[:7]}`](https://github.com/Anon-Planet/thgtoa/commit/{sha})")
|
lines.append(f" - Released {date} from [`{sha[:7]}`](https://github.com/Anon-Planet/thgtoa/commit/{sha})")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|||||||