Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ee67836c3 | ||
|
|
6b34e51bdb |
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🐍 Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
@@ -15,7 +15,6 @@ on:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sign:
|
||||
@@ -29,10 +28,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Checkout (for pgp/ key reference only)
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: pgp
|
||||
sparse-checkout-cone-mode: true
|
||||
|
||||
- name: 📥 Download PDF artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -45,6 +43,7 @@ jobs:
|
||||
- name: 📋 List downloaded files
|
||||
run: ls -lh export/
|
||||
|
||||
# Hash - extensions match export/ conventions: .sha256, .b2sum
|
||||
- name: "#️⃣ Hash PDFs"
|
||||
id: hashes
|
||||
run: |
|
||||
@@ -111,29 +110,23 @@ jobs:
|
||||
|
||||
# Commit export/ back to main
|
||||
- name: 📦 Checkout full repo for commit
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
path: repo
|
||||
|
||||
- name: 📂 Copy export files into repo
|
||||
run: |
|
||||
mkdir -p repo/export
|
||||
cp -v export/* repo/export/
|
||||
run: cp -v export/* repo/export/
|
||||
|
||||
- name: 🔏 Configure GPG commit signing
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
- name: 🔏 Configure SSH commit signing
|
||||
run: |
|
||||
mkdir -p ~/.gnupg
|
||||
echo "$GPG_PRIVATE_KEY" | gpg --batch --quiet --import > /dev/null 2>&1 || true
|
||||
FINGERPRINT=$(gpg --batch --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: '/^fpr/ { print $10; exit }')
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.ACTIONS_SSH_SIGNING_KEY }}" > ~/.ssh/signing_key
|
||||
chmod 600 ~/.ssh/signing_key
|
||||
git config --global gpg.format ssh
|
||||
git config --global user.signingKey ~/.ssh/signing_key
|
||||
git config --global commit.gpgSign true
|
||||
git config --global gpg.program gpg
|
||||
git config --global gpg.exectimeout 180
|
||||
git config --global user.signingkey "$FINGERPRINT"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
@@ -145,10 +138,7 @@ jobs:
|
||||
if git diff --cached --quiet; then
|
||||
echo "Nothing to commit — export/ is already up to date."
|
||||
else
|
||||
export GNUPGHOME=~/.gnupg
|
||||
gpg --list-secret-keys --with-colons > /dev/null 2>&1 || true
|
||||
git commit -S -m "chore(export): update PDFs, hashes and signatures [skip ci]"
|
||||
git config --global credential.helper 'cache --timeout=600'
|
||||
git push origin main
|
||||
fi
|
||||
|
||||
@@ -190,10 +180,10 @@ jobs:
|
||||
needs: sign
|
||||
# Always run so the summary is visible even if sign partially failed
|
||||
if: always()
|
||||
# Do a sparse checkout on given patterns. Each pattern should be separated with new lines
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Checkout scripts and public key
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
scripts/verify_pdf.py
|
||||
@@ -217,7 +207,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y gnupg
|
||||
gpg --import pgp/anonymousplanet.asc
|
||||
gpg --import pgp/anonymousplanet-release.asc
|
||||
|
||||
- name: 🐍 Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
@@ -31,11 +31,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Checkout (for pgp/)
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
sparse-checkout: pgp
|
||||
sparse-checkout-cone-mode: true
|
||||
|
||||
# Download artifacts from the specified sign run
|
||||
- name: 📥 Download signatures artifact
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Use a PAT so the commit triggers downstream workflows (GITHUB_TOKEN won't)
|
||||
token: ${{ secrets.CHANGELOG_PAT || secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,47 +1,19 @@
|
||||
name: 🚀 Publish docs via GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "requirements.txt"
|
||||
- ".github/workflows/publish.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and deploy the Hitchhiker's Guide
|
||||
name: Publish the Hitchhiker's Guide
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 🛠️ Checkout
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 🐍 Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
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
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CUSTOM_DOMAIN: anonymousplanet.net
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# Contributor License Agreement
|
||||
|
||||
This project is licensed under the **Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0)**.
|
||||
|
||||
By submitting a contribution to this repository, you agree to the terms described in this document.
|
||||
|
||||
---
|
||||
|
||||
## What Is a CLA?
|
||||
|
||||
A Contributor License Agreement (CLA) clarifies the terms under which you contribute to this project. It does not transfer ownership of your work—you keep all rights to what you create. It establishes that your contribution can be distributed under this project's license, and that you understand and accept the obligations that come with it.
|
||||
|
||||
---
|
||||
|
||||
## Our License: CC BY-SA 4.0
|
||||
|
||||
This repository uses the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
> **Note on license history:** This project previously used CC BY-NC 4.0 (non-commercial). The switch to CC BY-SA 4.0 removes the non-commercial restriction and replaces it with a share-alike requirement. Contributions submitted before this change were made under the prior license; all new contributions fall under CC BY-SA 4.0.
|
||||
|
||||
In plain terms, CC BY-SA 4.0 means:
|
||||
|
||||
- **Attribution (BY):** Anyone who uses, adapts, or redistributes this work must give appropriate credit to the original authors, provide a link to the license, and indicate if changes were made.
|
||||
- **ShareAlike (SA):** Anyone who distributes adapted versions of this work must do so under the same license—CC BY-SA 4.0—or a compatible one. Derivative works cannot be relicensed under more restrictive terms.
|
||||
|
||||
The full legal text is available at: <https://creativecommons.org/licenses/by-sa/4.0/legalcode>
|
||||
|
||||
---
|
||||
|
||||
## What Counts as a Contribution
|
||||
|
||||
A "contribution" is any material you intentionally submit to this repository for inclusion in the project. This includes but is not limited to:
|
||||
|
||||
- Written content (guides, documentation, articles, explanations)
|
||||
- Edits, corrections, or translations of existing content
|
||||
- Code, scripts, configuration files, or tooling
|
||||
- Issues, bug reports, and feature requests that include original written content
|
||||
- Pull requests and their descriptions
|
||||
- Comments that substantively add to the project's content
|
||||
|
||||
Minor factual corrections, typo fixes, and purely mechanical formatting changes are contributed in good faith and treated as submitted under the same terms.
|
||||
|
||||
---
|
||||
|
||||
## Your Rights as a Contributor
|
||||
|
||||
Contributing to this project does **not** transfer copyright ownership of your work to the project maintainers or the Anonymous Planet collective. You retain full copyright over your original contributions.
|
||||
|
||||
By contributing, you grant the project a perpetual, worldwide, non-exclusive, royalty-free license to use, reproduce, adapt, and distribute your contribution under CC BY-SA 4.0—consistent with the license terms the entire project operates under.
|
||||
|
||||
---
|
||||
|
||||
## Your Obligations as a Contributor
|
||||
|
||||
When you submit a contribution, you confirm that:
|
||||
|
||||
1. **You have the right to submit it.** The contribution is your own original work, or you have the necessary rights to submit it under CC BY-SA 4.0. You are not submitting material that is proprietary, confidential, or owned by a third party without their permission.
|
||||
|
||||
2. **It is compatible with CC BY-SA 4.0.** If your contribution incorporates third-party material, that material must already be licensed under CC BY-SA 4.0 or a [compatible license](https://creativecommons.org/share-your-work/licensing-considerations/compatible-licenses/). Do not submit content under CC BY-NC 4.0 or any other license that conflicts with CC BY-SA 4.0.
|
||||
|
||||
3. **Attribution will carry forward.** You understand that your contribution—along with your credit as a contributor—may be included in redistributed or adapted versions of this project, and that downstream users will be required to attribute the project and preserve the CC BY-SA 4.0 license.
|
||||
|
||||
4. **Derivative works stay open.** You understand that anyone who builds on this project's content (including your contributions) must release their adaptations under CC BY-SA 4.0 or a compatible license. This is the core share-alike obligation.
|
||||
|
||||
---
|
||||
|
||||
## How Acceptance Works
|
||||
|
||||
This project does not use a formal CLA signature process. Acceptance is implicit:
|
||||
|
||||
> **By opening a pull request, submitting an issue with original content, or otherwise contributing material to this repository, you indicate that you have read this document and agree to its terms.**
|
||||
|
||||
If you are contributing on behalf of an organization or employer, you confirm that you are authorized to submit contributions under these terms on their behalf.
|
||||
|
||||
---
|
||||
|
||||
## Questions
|
||||
|
||||
If you are unsure whether your contribution is compatible with CC BY-SA 4.0, or have questions about these terms, open an issue before submitting. Maintainers will do their best to clarify.
|
||||
|
||||
For the full license text: <https://creativecommons.org/licenses/by-sa/4.0/legalcode>
|
||||
@@ -1,32 +1,26 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
<div align="center">
|
||||
<a href="https://anonymousplanet.net">
|
||||
<img src="https://anonymousplanet.net/media/anon_planet.svg"></a>
|
||||
<img src="https://anonymousplanet.net/media/profile.png"></a>
|
||||
|
||||
<p><em>The comprehensive guide for online anonymity and OpSec.</em></p>
|
||||
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/stargazers">
|
||||
<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">
|
||||
<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://opencollective.com/thgtoa">
|
||||
<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>
|
||||
<img src="https://img.shields.io/badge/MkDocs_Material-526CFE?logo=MaterialForMkDocs&logoColor=green"></a>
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/actions/workflows/release.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/Anon-Planet/thgtoa/release.yml?label=release"></a></p>
|
||||
</div>
|
||||
|
||||
Welcome.
|
||||
|
||||
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 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 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.
|
||||
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.
|
||||
|
||||
**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](docs/contribute/index.md) 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.**
|
||||
|
||||
**Ways to read or export the guide**
|
||||
|
||||
- **In your browser:** [Hitchhiker's Guide](https://anonymousplanet.net/) (hosted site). After a local build you can also open `site/guide/index.html` directly.
|
||||
- **Local HTML preview:** from the repository root, with Python 3 and [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/) installed (`pip install mkdocs-material`), run `mkdocs serve` and open the URL printed in the terminal (for example `http://127.0.0.1:8000`).
|
||||
- **Local HTML preview:** from the repository root, with Python 3 and [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/) installed (`pip install mkdocs-material`), run `mkdocs serve --livereload` and open the URL printed in the terminal (for example `http://127.0.0.1:8000`).
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "Anonymous Planet"
|
||||
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
"@id": https://anonymousplanet.net/
|
||||
name: Anonymous Planet
|
||||
url: https://anonymousplanet.net/about/
|
||||
logo: media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
---
|
||||
|
||||
**Anonymous Planet** are the maintainers of [_The Hitchhiker's Guide_](guide/index.md) and the [_PSA Community_](https://psa.anonymousplanet.net). This project is part of our ongoing efforts to provide open-source tools and resources for the community, made by people with extensive knowledge in signals and forensics, and expertise in various distributions of Linux. We are providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. It is written with the hopes that good people (e.g., activists, journalists, scientists, lawyers, whistle-blowers, etc.) will be able to fight oppression, censorship and harassment! The website and projects are free (as in freedom). We are high at risk individuals. We are not simply hackers with a bunch of recommendations and affiliate links like privacytools. To be blunt like Linus Torvalds with a bullshit Merge Request, we don't do that. Here, you will find a trove of information compiled into a single, cohesive set of instructions and sub-guides.
|
||||
|
||||
??? Note "Where do I start?"
|
||||
|
||||
Start either by going to [the beginning](guide/index.md) or using the search at top right of the page. It is also available at whatever point you are in your reading.
|
||||
|
||||
??? Note "Notes on the journey"
|
||||
|
||||
This guide is a work in progress. It will probably never be "finished". You may find broken links when you click on some search results and during some navigation steps. Please report these. Otherwise, most of the search functionality is a great experience and can help you find linked topics. Try to search for something in one section of the reading. It will show up in many other places.
|
||||
|
||||
??? Note "Disclaimer"
|
||||
|
||||
There might be some wrong or outdated information in this guide because no one is perfect. Your experience may vary. Remember, check regularly for an updated version of this guide. Please do your own independent, well-thought research. There is no one resource online that can provide 100% security, anonymity, and/or privacy.
|
||||
|
||||
- For mirrors see [Mirrors](mirrors/index.md) and the links at the bottom right of the page. You should see these on every page.
|
||||
|
||||
- For help in comparing versions see [Comparing versions](guide/index.md#appendix-a6-comparing-versions)
|
||||
|
||||
Feel free to submit issues **(please do report anything wrong)** using GitHub Issues at: <https://github.com/Anon-Planet/thgtoa/issues>. We also accept Merge Requests (MR) from our Gitlab and many other places. Do not hesitate to report issues and suggestions!
|
||||
|
||||
??? tip "Discuss ideas on Matrix for real-time chat"
|
||||
|
||||
We offer a Matrix.org hosted space of our own. Check it out!
|
||||
|
||||
- Read [the rules](https://psa.anonymousplanet.net/), please
|
||||
- Matrix Room: https://matrix.to/#/#nth:anonymousplanet.net
|
||||
- Matrix Space: https://matrix.to/#/#psa:anonymousplanet.net
|
||||
- Admins: @daskolburn:thomcat.rocks and @thehidden:tchncs.de
|
||||
|
||||
???+ tip "Follow us on"
|
||||
|
||||
- X at <https://x.com/anonplanetorg>
|
||||
- Mastodon at <https://ioc.exchange/@unknown>
|
||||
|
||||
To contact Anonymous Planet, see the updated information on the website.
|
||||
|
||||
**Please consider [donating](guide/index.md#donations) if you enjoy the project and want to support the hosting fees or support the funding of initiatives like the hosting of Tor Exit Nodes.**
|
||||
|
||||
???+ example "Recommended Reading"
|
||||
|
||||
Some of those resources may, in order to sustain their project, contain or propose:
|
||||
|
||||
- Sponsored commercial content
|
||||
- Monetized content through third party platforms (such as YouTube)
|
||||
- Affiliate links to commercial services
|
||||
- Paid Services such as consultancy
|
||||
- Premium content such as ad-free content or updated content
|
||||
- Merchandising
|
||||
|
||||
_Note that these websites could contain affiliate/sponsored content and/or merchandising. This guide does not endorse and is not sponsored by any commercial entity in any way._
|
||||
|
||||
If you skipped those, you should really still consider viewing this YouTube playlist from the Techlore Go Incognito project (<https://github.com/techlore-official/go-incognito> <sup>[[Archive.org]](https://web.archive.org/web/https://github.com/techlore-official/go-incognito)</sup>) as an introduction before going further: <https://www.youtube.com/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO> <sup>[[Invidious]](https://yewtu.be/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO)</sup>. This guide will cover many of the topics in the videos of this playlist with more details and references as well as some added topics not covered within that series. This will just take you 2 or 3 hours to watch it all.
|
||||
|
||||
_Anonymous Planet_ **does not** participate in any sponsoring, endorsement, advertising, or other affiliate programs for any entity. We only rely on anonymous donations in a closed, transparent loop system.
|
||||
|
||||
??? tip "Privacy related"
|
||||
|
||||
- AnarSec: <https://www.anarsec.guide/>
|
||||
- EFF Surveillance Self-Defense: <https://ssd.eff.org/>
|
||||
- Prism-Break: <https://prism-break.org/>
|
||||
- Privacy Guides: <https://privacyguides.org>
|
||||
- Techlore: <https://techlore.tech>
|
||||
- The New Oil: <https://thenewoil.org>
|
||||
- PrivacyTools.io: <https://privacytools.io>
|
||||
|
||||
??? tip "Blogs and personal websites"
|
||||
|
||||
- CIA Officer's Blog: <https://officercia.mirror.xyz/>
|
||||
- Continuing Ed: <https://edwardsnowden.substack.com/>
|
||||
- Madaidan's Insecurities: <https://madaidans-insecurities.github.io/>
|
||||
- Seirdy's Home: <https://seirdy.one/>
|
||||
|
||||
??? tip "Useful resources"
|
||||
|
||||
- KYC? Not me: <https://kycnot.me/>
|
||||
- Library Genesis: <https://en.wikipedia.org/wiki/Library_Genesis> <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Library_Genesis)</sup> (see their latest known URL in the Wikipedia article)
|
||||
- Real World Onion Sites: <https://github.com/alecmuffett/real-world-onion-sites>
|
||||
- Sci-Hub <https://en.wikipedia.org/wiki/Sci-Hub> <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Sci-Hub)</sup> (see their latest known URL in the main Wikipedia article)
|
||||
- Terms of Service, Didn't Read: <https://tosdr.org>
|
||||
- Whonix Documentation: <https://www.whonix.org/wiki/Documentation> <sup>[[Archive.org]](https://web.archive.org/web/https://www.whonix.org/wiki/Documentation)</sup>
|
||||
|
||||
??? note "We are not affiliated with Anonymous or Riseup"
|
||||
|
||||
One or two of our community members uses or has used the resources of Riseup. We are not affiliated with Riseup in any manner.
|
||||
|
||||
We also hold **no affiliation** with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> hacker collective.
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
title: "Anonymous Planet"
|
||||
description: "We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space."
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
"@id": https://anonymousplanet.net/
|
||||
name: Anonymous Planet
|
||||
url: https://anonymousplanet.net/about/
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Made by people with skin in the game.</div>
|
||||
<h1 class="hero-title">Anonymous Planet<span class="hero-subtitle">Open source. No ads. No affiliates. No profit.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Written by people with backgrounds in signals intelligence, forensics, and Linux systems,
|
||||
not influencers with affiliate links.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="../guide/" class="hero-cta hero-cta--primary">Read the Guide</a>
|
||||
<a href="../contribute/" class="hero-cta hero-cta--secondary">Support the Project</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Who We Are { #who }
|
||||
|
||||
Anonymous Planet are the maintainers of [_The Hitchhiker's Guide_](../guide/index.md) and the [_PSA Community_](https://psa.anonymousplanet.net). The project exists to provide open-source tools and resources for people who need them: activists, journalists, scientists, lawyers, whistleblowers, and anyone facing oppression, censorship, or harassment.
|
||||
|
||||
We have extensive backgrounds in signals intelligence, forensics, and various Linux distributions. We are not a privacy influencer operation with a bunch of recommendations and affiliate links. Everything here is compiled into a single, cohesive set of instructions and sub-guides — free as in freedom.
|
||||
|
||||
We are high-risk individuals. We do this because it matters.
|
||||
|
||||
!!! note "Disclaimer"
|
||||
This guide is a work in progress and will probably never be "finished". Some information may be outdated. Always cross-reference independently and check for updated versions regularly. No single resource can provide 100% security, anonymity, or privacy.
|
||||
|
||||
!!! tip "Where to start"
|
||||
Go to [the beginning of the guide](../guide/index.md) or use the search at the top right. The [Techlore Go Incognito playlist](https://www.youtube.com/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO) is also a good 2–3 hour introduction before diving in.
|
||||
|
||||
---
|
||||
|
||||
## 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 }
|
||||
|
||||
<div class="index-grid">
|
||||
|
||||
<!-- <div class="index-card">
|
||||
<h3 class="index-card__title">Matrix Space</h3>
|
||||
<p class="index-card__body">Our Matrix.org hosted community space. Read the rules before joining. Admins: <code>@daskolburn:thomcat.rocks</code> and <code>@thehidden:tchncs.de</code>.</p>
|
||||
<a href="https://matrix.to/#/#psa:anonymousplanet.net" class="index-card__link">Join the Space</a>
|
||||
<a href="https://matrix.to/#/#nth:anonymousplanet.net" class="index-card__link" style="margin-top:0.4em;">Join the Room</a>
|
||||
<a href="https://psa.anonymousplanet.net/" class="index-card__link" style="margin-top:0.4em;">Read the Rules</a>
|
||||
</div> -->
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Mastodon</h3>
|
||||
<p class="index-card__body">Follow us for announcements on the Fediverse.</p>
|
||||
<a href="https://ioc.exchange/@unknown" class="index-card__link" rel="me">@unknown@ioc.exchange</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">X / Twitter</h3>
|
||||
<p class="index-card__body">Announcements also cross-posted to X.</p>
|
||||
<a href="https://x.com/anonplanetorg" class="index-card__link">@anonplanetorg</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">GitHub Issues</h3>
|
||||
<p class="index-card__body">Report errors, broken links, or outdated content. Pull requests welcome across all our mirrors.</p>
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/issues" class="index-card__link">Open an issue</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Contact { #contact }
|
||||
|
||||
Email: <contact@anonymousplanet.net>
|
||||
|
||||
For mirrors, see the [Mirrors](../mirrors/index.md) page and the links in the site footer.
|
||||
For version comparison, see [Comparing Guide Versions](../guide/index.md#comparing-versions).
|
||||
|
||||
---
|
||||
|
||||
## Recommended Reading { #reading }
|
||||
|
||||
!!! warning "Disclosure"
|
||||
Some linked resources may contain sponsored content, affiliate links, paid services, or monetized content. Anonymous Planet does not endorse and is not sponsored by any commercial entity. We do not participate in any affiliate programs.
|
||||
|
||||
??? tip "Privacy & security"
|
||||
- [AnarSec](https://www.anarsec.guide/)
|
||||
- [EFF Surveillance Self-Defense](https://ssd.eff.org/)
|
||||
- [Prism-Break](https://prism-break.org/)
|
||||
- [Privacy Guides](https://privacyguides.org)
|
||||
- [Techlore](https://techlore.tech)
|
||||
- [The New Oil](https://thenewoil.org)
|
||||
- [PrivacyTools.io](https://privacytools.io)
|
||||
|
||||
??? tip "Blogs"
|
||||
- [CIA Officer's Blog](https://officercia.mirror.xyz/)
|
||||
- [Edward Snowden — Continuing Ed](https://edwardsnowden.substack.com/)
|
||||
- [Madaidan's Insecurities](https://madaidans-insecurities.github.io/)
|
||||
- [Seirdy's Home](https://seirdy.one/)
|
||||
|
||||
??? tip "Useful resources"
|
||||
- [KYC? Not me](https://kycnot.me/)
|
||||
- [Library Genesis](https://en.wikipedia.org/wiki/Library_Genesis)
|
||||
- [Real World Onion Sites](https://github.com/alecmuffett/real-world-onion-sites)
|
||||
- [Sci-Hub](https://en.wikipedia.org/wiki/Sci-Hub)
|
||||
- [Terms of Service, Didn't Read](https://tosdr.org)
|
||||
- [Whonix Documentation](https://www.whonix.org/wiki/Documentation)
|
||||
|
||||
---
|
||||
|
||||
!!! note "We are not affiliated with Anonymous or Riseup"
|
||||
We hold no affiliation with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) hacker collective or with Riseup. One or two community members uses Riseup resources personally — that is the full extent of any connection.
|
||||
@@ -1,68 +1,31 @@
|
||||
---
|
||||
title: "Changelog"
|
||||
description: "Release notes for the Hitchhiker's Guide to Online Anonymity."
|
||||
hide:
|
||||
- navigation
|
||||
title: "Release Notes"
|
||||
description: "Release Notes"
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
"@id": https://anonymousplanet.net/
|
||||
name: Anonymous Planet
|
||||
url: https://anonymousplanet.net/changelog/
|
||||
url: https://anonymousplanet.net/
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
- https://mastodon.social/@anonymousplanet
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Auto-generated after every release.</div>
|
||||
<h1 class="hero-title">Changelog<span class="hero-subtitle">What changed, and when.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Follows <a href="https://keepachangelog.com/en/1.1.0/">Keep a Changelog</a> and
|
||||
<a href="https://semver.org/spec/v2.0.0.html">Semantic Versioning</a>.
|
||||
Generated from conventional commits by machine, only.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/releases" class="hero-cta hero-cta--primary">GitHub Releases</a>
|
||||
<a href="../verify/" class="hero-cta hero-cta--secondary">Verify a Release</a>
|
||||
</div>
|
||||
</div>
|
||||
Notable changes to the guide. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
<!-- Change nothing below this line, please. -->
|
||||
---
|
||||
|
||||
## [v1.2.6-pre1]
|
||||
|
||||
!!! abstract "Meta"
|
||||
|
||||
- Various website improvements (CSS)
|
||||
|
||||
!!! 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
|
||||
|
||||
!!! tip "Added"
|
||||
|
||||
- COM Class Identifiers (CLSID/GUID) (NEW)
|
||||
- Windows Global Device Identifier (GDID) (NEW)
|
||||
- USB bus telemetry (device enumeration history) (NEW)
|
||||
|
||||
!!! info "Improved"
|
||||
|
||||
- Glossary (`docs/includes/glossary.md`) and removed inline expansions where applicable
|
||||
|
||||
## [v1.2.5]
|
||||
|
||||
!!! abstract "Meta"
|
||||
!!! Note "Meta"
|
||||
|
||||
- Website theme customization
|
||||
- License change to accomodate our fiscal host Open Source Collective
|
||||
|
||||
!!! info "Changed"
|
||||
!!! Note "Changed"
|
||||
|
||||
- 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
|
||||
@@ -76,28 +39,27 @@ schema:
|
||||
- Added comprehensive upgrade path guidance with backup procedures
|
||||
- Updated 196! Wikipedia reference links throughout the entire guide
|
||||
|
||||
!!! tip "Added"
|
||||
!!! Note "Added"
|
||||
|
||||
- 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
|
||||
- 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)
|
||||
|
||||
!!! info "Improved"
|
||||
!!! Note "Improved"
|
||||
|
||||
- Virtualbox hardening section reorganized
|
||||
- AppArmor configuration guidance added where applicable (Whonix 18.x)
|
||||
- KVM alternative documented in Appendix N for Linux users seeking better security than VirtualBox
|
||||
- Whonix Improvements subsection added before "Pick your guest workstation" VMs section
|
||||
- Removed appendix-style writing
|
||||
|
||||
## [v1.2.4]
|
||||
|
||||
!!! abstract "Meta"
|
||||
!!! Note "Meta"
|
||||
|
||||
- Rename workflows (GH - now we can know the order)
|
||||
|
||||
!!! info "Changed"
|
||||
!!! Note "Changed"
|
||||
|
||||
- Change the repo URL for our tor mirror
|
||||
- Fix recommended reading admonition
|
||||
@@ -117,7 +79,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.
|
||||
|
||||
!!! 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.
|
||||
- **Three independent CI workflows** replacing the old monolithic `build-sign-release.yml`:
|
||||
@@ -153,7 +115,7 @@ CI/CD pipeline split into independent stages, dark PDF quality improved, release
|
||||
|
||||
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.
|
||||
- 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.
|
||||
@@ -172,7 +134,6 @@ 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.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
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
title: "Content Contributions"
|
||||
description: "How to contribute to the Hitchhiker's Guide — building, signing, releasing, and writing."
|
||||
hide:
|
||||
- toc
|
||||
- navigation
|
||||
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
@@ -16,21 +13,6 @@ schema:
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Open source. Every PR matters.</div>
|
||||
<h1 class="hero-title">Contribute<span class="hero-subtitle">Help us improve the Hitchhiker's Guide..</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Contributions range from fixing a typo to writing entire new sections.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/issues/new" class="hero-cta hero-cta--primary">Open an Issue</a>
|
||||
<a href="#pipeline" class="hero-cta hero-cta--secondary">Release Pipeline</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Setup { #setup }
|
||||
|
||||
Install these before anything else.
|
||||
|
||||
@@ -57,13 +39,13 @@ Install these before anything else.
|
||||
```powershell
|
||||
# Python 3.11+ from https://python.org
|
||||
|
||||
# poppler: https://github.com/oschwartz10612/poppler-windows/releases
|
||||
# poppler: download from https://github.com/oschwartz10612/poppler-windows/releases
|
||||
# Extract and add the bin\ folder to PATH
|
||||
|
||||
# qpdf: https://github.com/qpdf/qpdf/releases
|
||||
# qpdf: download from https://github.com/qpdf/qpdf/releases
|
||||
# Extract and add the bin\ folder to PATH
|
||||
|
||||
# GPG: https://gpg4win.org
|
||||
# GPG: download Gpg4win from https://gpg4win.org
|
||||
|
||||
# Python dependencies
|
||||
pip install "mkdocs-material[imaging]" pillow numpy
|
||||
@@ -71,9 +53,54 @@ Install these before anything else.
|
||||
|
||||
You also need **Google Chrome** or **Microsoft Edge** installed for the light-mode PDF build (headless Chromium).
|
||||
|
||||
---
|
||||
You can [submit bugs and feature requests](https://github.com/Anon-Planet/thgtoa/issues/new) with detailed information about your issue or idea:
|
||||
|
||||
## Repository Layout { #layout }
|
||||
- If you'd like to propose an addition, please follow the standards outlined here.
|
||||
- If you're reporting an issue, please be sure to include the expected behaviour, the observed behaviour, and steps to reproduce the problem.
|
||||
- This can require technical knowledge, but you can also get involved in conversations about bug reports and feature requests. This is a great way to get involved without getting too overwhelmed!
|
||||
- [Help fellow committers test recently submitted pull requests](https://github.com/Anon-Planet/thgtoa/pulls). Simply by pulling down a pull request and testing it, you can help ensure our new code contributions for stability and quality.
|
||||
|
||||
For those of you who are looking to add content to the guide, include the following:
|
||||
|
||||
- **Do** create a [topic branch] to work on instead of working directly on `main`. This helps to:
|
||||
+ Protect the process.
|
||||
+ Ensures users are aware of commits on the branch being considered for merge.
|
||||
+ Allows for a location for more commits to be offered without mingling with other contributor changes.
|
||||
+ Allows contributors to make progress while a PR is still being reviewed.
|
||||
- **Do** follow the [50/72 rule] for Git commit messages.
|
||||
- **Do** write "WIP" on your PR and/or open a [draft PR] if submitting unfinished changes..
|
||||
- **Do** make sure the title of a draft PR makes it immediately clear that it's a draft
|
||||
- **Do** target your pull request to the **main branch**.
|
||||
- **Do** specify a descriptive title to make searching for your pull request easier.
|
||||
- **Don't** leave your pull request description blank.
|
||||
- **Don't** abandon your pull request. Being responsive helps us land your changes faster.
|
||||
- **Don't** post questions in older closed PRs.
|
||||
- **Do** stick to the guide to find common style issues.
|
||||
- **Don't** make mass changes (such as replacing "I" with "we") using automated search/replace functionality.
|
||||
+ Search/replace doesn't understand context, and as such, will inevitably cause inconsistencies and make the guide harder to read.
|
||||
+ If it's part of a larger PR, it'll also make the reviewer's life harder, as they'll have to go through manually and undo everything by hand.
|
||||
+ _If you're going to make mass changes, take the time to do it properly_. Otherwise we'll just have to undo it anyway.
|
||||
+ If your change contains backslashes (`\`), either escape them with another backslash (`\\`) or put them in a ```code block```.
|
||||
|
||||
When reporting guide issues:
|
||||
|
||||
- **Do** write a detailed description of your issue and use a descriptive title.
|
||||
- **Do** make it as detailed as possible and don't just submit 50 line changes without explaining.
|
||||
- **Don't** file duplicate reports; search for your bug before filing a new report.
|
||||
- **Don't** attempt to report issues on a closed PR.
|
||||
|
||||
Please split large sets of changes into multiple PRs. For example, a PR that adds Windows 11 support, removes Windows AME references, and fixes typos can be split into 3 PRs. This makes PRs easier to review prior to merging.
|
||||
|
||||
For an example of what _not_ to do, see: <https://github.com/Anon-Planet/thgtoa/pull/51>. This PR contains enough changes to split into multiple smaller and individually reviewable PRs.
|
||||
|
||||
While a PR is being reviewed, modifications may be made to it by the reviewer prior to merging. If this is the case, a new branch will be created for the PR's review. If you would like to submit a change to a PR that is in the process of being reviewed, _do not update the PR directly_. This will only cause merge conflicts and delay the PR from being merged. Instead, submit your changes to the PR's review branch.
|
||||
|
||||
For an example of what _not_ to do, see: <https://github.com/Anon-Planet/thgtoa/pull/51>. Instead of submitting changes to the PR directly, they should have been submitted as changes to the PR's associated review branch.
|
||||
|
||||
**Thank you** for taking the few moments to read this far! You're already way ahead of the
|
||||
curve, so keep it up!
|
||||
|
||||
## Repository layout
|
||||
|
||||
```txt
|
||||
.github/
|
||||
@@ -92,83 +119,192 @@ pgp/ # public signing keys
|
||||
scripts/
|
||||
build_guide_pdf.py # MkDocs + Chromium PDF builder
|
||||
convert.py # pixel-based dark mode PDF converter
|
||||
install_fonts.py # install fonts locally
|
||||
update_changelog.py # auto-generates changelog entries from git log
|
||||
setup_workflow.py # GitHub Secrets setup assistant
|
||||
verify_pdf.py # signature verification helper
|
||||
archived/
|
||||
tag_release.py # ARCHIVED — GPG tag helper (not used in current flow)
|
||||
tag_release.py # ARCHIVED - GPG tag helper (not used in current flow)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building Locally { #build }
|
||||
## Building locally
|
||||
|
||||
```sh
|
||||
python scripts/build_guide_pdf.py --both
|
||||
```
|
||||
|
||||
Builds the MkDocs site, renders it to `export/thgtoa.pdf` via headless Chromium, then produces `export/thgtoa-dark.pdf`.
|
||||
This builds the MkDocs site, renders it to `export/thgtoa.pdf` via headless Chromium, then calls `scripts/convert.py` to produce `export/thgtoa-dark.pdf`.
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--both` | Light PDF then dark PDF |
|
||||
| _(none)_ | Light PDF only |
|
||||
| (no flag) | Light PDF only |
|
||||
| `--dark` | Dark PDF only (light PDF must already exist) |
|
||||
|
||||
Build only the dark PDF from an existing light:
|
||||
Build only the dark PDF from an existing light PDF:
|
||||
|
||||
```sh
|
||||
python scripts/convert.py export/thgtoa.pdf export/thgtoa-dark.pdf
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--dpi` | `200` | Rasterization DPI |
|
||||
| `--batch-size` | `50` | Pages per batch — reduce if OOM |
|
||||
| `--dpi` | `200` | Rasterization DPI. 150 = smaller file, 300 = sharper but slow |
|
||||
| `--batch-size` | `50` | Pages per batch. Reduce if you hit OOM |
|
||||
| `--bg` | `1f1f31` | Background colour (hex) |
|
||||
| `--text` | `e0e0e0` | Body text colour (hex) |
|
||||
| `--link` | `5e8bde` | Link colour (hex) |
|
||||
| `--link` | `5e8bde` | Link / blue element colour (hex) |
|
||||
|
||||
Preview the site:
|
||||
# Preview the MkDocs site
|
||||
|
||||
```sh
|
||||
mkdocs serve
|
||||
# Opens at http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
Opens at `http://127.0.0.1:8000`.
|
||||
|
||||
# CI/CD pipeline overview
|
||||
|
||||
The pipeline is fully manual after the initial build - no step automatically triggers the next. This prevents version mismatches between what was built, what was signed, and what gets released. The workflows are numbered to help guide you.
|
||||
|
||||
```txt
|
||||
push to main (or manual trigger)
|
||||
│
|
||||
▼
|
||||
01-build.yml
|
||||
Builds thgtoa.pdf + thgtoa-dark.pdf.
|
||||
Uploads artifact: pdfs
|
||||
Note the run ID.
|
||||
│
|
||||
│ # manually trigger 02-sign.yml with the build run ID
|
||||
▼
|
||||
02-sign.yml
|
||||
Downloads pdfs artifact. Hashes (SHA-256 + BLAKE2b) and GPG-signs
|
||||
all files. Commits export/ back to main. Uploads artifacts:
|
||||
signatures, pdfs-signed
|
||||
Note the run ID.
|
||||
│
|
||||
│ # manually trigger 03-release.yml with the sign run ID
|
||||
▼
|
||||
03-release.yml
|
||||
Downloads signatures + pdfs-signed artifacts. Runs VirusTotal.
|
||||
Creates GitHub Release tagged release-YYYYMMDD-<short-sha>.
|
||||
│
|
||||
│ # manually trigger 04-changelog.yml with the version string
|
||||
▼
|
||||
04-changelog.yml
|
||||
Runs update_changelog.py, prepends a new ## [vX.Y.Z] entry,
|
||||
commits back to main.
|
||||
```
|
||||
|
||||
Each stage is independent. If signing fails (e.g. an expired/revoked key, other problems in CI), re-run only `02-sign.yml` pointing at the existing build artifact - no need to rebuild the PDFs.
|
||||
|
||||
!!! warning "Before you push"
|
||||
|
||||
- Make sure the working tree is clean (`git status`)
|
||||
- Run `mkdocs build` locally if you changed `docs/` to catch broken links before CI does
|
||||
- If you added new footnotes, verify they have both a definition `[^N]:` and at least one inline citation `[^N]`
|
||||
|
||||
---
|
||||
|
||||
## Contributing Content { #contributing }
|
||||
# Release process
|
||||
|
||||
<div class="index-grid">
|
||||
## Trigger a build
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Use a Topic Branch</h3>
|
||||
<p class="index-card__body">Never commit directly to <code>main</code>. Use a topic branch per change so PRs stay reviewable and independent.</p>
|
||||
</div>
|
||||
Push to `main` - `01-build.yml` runs automatically when `docs/`, `mkdocs.yml`, or `scripts/` change. You can also trigger it manually from **Actions → Build PDFs → Run workflow**.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Small PRs</h3>
|
||||
<p class="index-card__body">Split large changes into multiple PRs — one for new content, one for fixes, one for style. Big PRs block merges and create review debt.</p>
|
||||
</div>
|
||||
Once it completes successfully, **note the run ID** from the URL or the Actions list.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Conventional Commits</h3>
|
||||
<p class="index-card__body">All commits must follow <code><type>(<scope>): <description></code> format. Enforced by the <code>commitizen</code> pre-commit hook.</p>
|
||||
</div>
|
||||
---
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Describe Your Changes</h3>
|
||||
<p class="index-card__body">Never leave a PR description blank. Include what changed, why, and any context a reviewer needs. Link related issues.</p>
|
||||
</div>
|
||||
## Sign the PDFs
|
||||
|
||||
</div>
|
||||
Go to **Actions → Sign PDFs → Run workflow**.
|
||||
|
||||
### Commit Types
|
||||
| Input | Value |
|
||||
|-------|-------|
|
||||
| `build_run_id` | The run ID from step 1 |
|
||||
|
||||
| Type | Changelog bucket |
|
||||
|------|-----------------|
|
||||
`02-sign.yml` will:
|
||||
|
||||
- Download the PDFs artifact from the build run
|
||||
- Compute SHA-256 and BLAKE2b hashes, writing `thgtoa.pdf.sha256`, `thgtoa.pdf.b2sum`, `sha256sums.txt`, `b2sums.txt`, and the dark equivalents
|
||||
- GPG-sign all PDFs and hash files, writing `.asc` detached signature files
|
||||
- Commit the updated `export/` directory back to `main`
|
||||
- Upload two artifacts: `signatures` and `pdfs-signed`
|
||||
|
||||
Once it completes successfully, **note the run ID**.
|
||||
|
||||
---
|
||||
|
||||
## Publish the release
|
||||
|
||||
Go to **Actions → Release → Run workflow**.
|
||||
|
||||
| Input | Value |
|
||||
|-------|-------|
|
||||
| `sign_run_id` | The run ID from step 2 |
|
||||
| `prerelease` | `false` for a normal release |
|
||||
|
||||
`03-release.yml` will:
|
||||
|
||||
- Download `signatures` and `pdfs-signed` artifacts from the sign run
|
||||
- Upload both PDFs to VirusTotal
|
||||
- Auto-generate a release tag in the format `release-YYYYMMDD-<short-sha>` (e.g. `release-20260527-abc1234`)
|
||||
- Create a GitHub Release with all PDFs, hash files, and signatures attached, and the VirusTotal report URLs in the body
|
||||
|
||||
No version number needs to be chosen at this step - the tag is derived from the date and commit SHA, so it is always unique and always traceable.
|
||||
|
||||
---
|
||||
|
||||
## Update the changelog
|
||||
|
||||
Go to **Actions → Update Changelog → Run workflow**.
|
||||
|
||||
| Input | Value |
|
||||
|-------|-------|
|
||||
| `version` | The human-readable version string, e.g. `v1.2.4` |
|
||||
| `dry_run` | `true` to preview without committing |
|
||||
|
||||
`04-changelog.yml` runs `scripts/update_changelog.py`, which:
|
||||
|
||||
- Reads git log since the last `## [vX.Y.Z]` heading in the changelog
|
||||
- Categorises commits into Added / Changed / Fixed using conventional-commit prefixes
|
||||
- Prepends a new `## [version]` admonition block to `docs/changelog/index.md`
|
||||
- Commits the result back to `main`
|
||||
|
||||
The version string is the only human decision in the release process. It goes into the changelog only - it does not affect the release tag.
|
||||
|
||||
!!! tip "Previewing the changelog entry"
|
||||
Run with `dry_run: true` first to review the generated entry before it is committed.
|
||||
|
||||
---
|
||||
|
||||
## Release tag format
|
||||
|
||||
Release tags use the format `release-YYYYMMDD-<short-sha>`, for example:
|
||||
|
||||
```txt
|
||||
release-20260527-abc1234
|
||||
```
|
||||
|
||||
This format is always unique, requires no version decision at release time, and is directly traceable to the commit that was built. The version string (e.g. `v1.2.4`) is a separate, human-assigned label that lives only in the changelog.
|
||||
|
||||
---
|
||||
|
||||
## Commit message format
|
||||
|
||||
All commits must follow the [Conventional Commits](https://www.conventionalcommits.org) format. This is enforced by the `commitizen` pre-commit hook. Not because we want to limit cooperation with others, but becasue it promotes a cleaner Changelog; we can avoid all the noise by doing this programatically.
|
||||
|
||||
```txt
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
Accepted types and their changelog bucket:
|
||||
|
||||
| Type | Bucket |
|
||||
|------|--------|
|
||||
| `feat`, `feature`, `add` | Added |
|
||||
| `fix`, `bugfix`, `revert`, `security` | Fixed |
|
||||
| `perf`, `refactor`, `change`, `chore`, `ci`, `docs`, `style`, `test`, `build` | Changed |
|
||||
@@ -182,77 +318,13 @@ docs: update developer workflow guide
|
||||
chore(ci): pin Chrome version to 120
|
||||
```
|
||||
|
||||
### Rules
|
||||
# Verifying a release
|
||||
|
||||
- **Do** target PRs at the `main` branch
|
||||
- **Do** write "WIP" or open a draft PR for unfinished work
|
||||
- **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for commit messages
|
||||
- **Don't** make mass search/replace changes without context-checking every instance
|
||||
- **Don't** abandon a PR mid-review — stay responsive
|
||||
- **Don't** modify a PR directly while it's under active review — submit changes to the review branch instead
|
||||
|
||||
For an example of what _not_ to do, see [PR #51](https://github.com/Anon-Planet/thgtoa/pull/51).
|
||||
|
||||
!!! warning "Before you push"
|
||||
- Make sure the working tree is clean (`git status`)
|
||||
- Run `mkdocs build` locally if you changed `docs/` to catch broken links
|
||||
- If you added footnotes, verify each has both a definition `[^N]:` and at least one inline citation `[^N]`
|
||||
|
||||
---
|
||||
|
||||
## Release Pipeline { #pipeline }
|
||||
|
||||
The pipeline is fully manual after the initial build — no step triggers the next automatically. This prevents version mismatches between what was built, what was signed, and what gets released.
|
||||
|
||||
```txt
|
||||
push to main (or manual trigger)
|
||||
│
|
||||
▼
|
||||
01-build.yml
|
||||
Builds thgtoa.pdf + thgtoa-dark.pdf.
|
||||
Uploads artifact: pdfs — note the run ID.
|
||||
│
|
||||
│ manually trigger 02-sign.yml with the build run ID
|
||||
▼
|
||||
02-sign.yml
|
||||
Hashes (SHA-256 + BLAKE2b) and GPG-signs all files.
|
||||
Commits export/ back to main.
|
||||
Uploads: signatures, pdfs-signed — note the run ID.
|
||||
│
|
||||
│ manually trigger 03-release.yml with the sign run ID
|
||||
▼
|
||||
03-release.yml
|
||||
Runs VirusTotal. Creates GitHub Release tagged release-YYYYMMDD-<short-sha>.
|
||||
│
|
||||
│ manually trigger 04-changelog.yml with the version string
|
||||
▼
|
||||
04-changelog.yml
|
||||
Prepends a new ## [vX.Y.Z] entry to docs/changelog/index.md and commits.
|
||||
```
|
||||
|
||||
### Release Tags
|
||||
|
||||
Tags use the format `release-YYYYMMDD-<short-sha>`, e.g. `release-20260527-abc1234`. No version decision is needed at release time — the tag is always unique and traceable to the exact commit.
|
||||
|
||||
The version string (e.g. `v1.2.4`) is a separate, human-assigned label that lives only in the changelog.
|
||||
|
||||
### Triggering Each Step
|
||||
|
||||
**Build:** Push to `main` or go to **Actions → Build PDFs → Run workflow**. Note the run ID.
|
||||
|
||||
**Sign:** **Actions → Sign PDFs → Run workflow**, enter the build run ID. Note the run ID.
|
||||
|
||||
**Release:** **Actions → Release → Run workflow**, enter the sign run ID.
|
||||
|
||||
**Changelog:** **Actions → Update Changelog → Run workflow**, enter the version string. Use `dry_run: true` to preview.
|
||||
|
||||
---
|
||||
|
||||
## Verifying a Release { #verify }
|
||||
Anyone can verify the authenticity of a release download.
|
||||
|
||||
```sh
|
||||
# Import the release signing key
|
||||
gpg --import pgp/anonymousplanet.asc
|
||||
gpg --import pgp/anonymousplanet-release.asc
|
||||
|
||||
# Verify the PDFs
|
||||
gpg --verify thgtoa.pdf.asc thgtoa.pdf
|
||||
@@ -267,7 +339,7 @@ sha256sum -c sha256sums.txt
|
||||
b2sum -c b2sums.txt
|
||||
```
|
||||
|
||||
Expected output:
|
||||
A successful verify looks like:
|
||||
|
||||
```txt
|
||||
gpg: Signature made Sun 31 May 2026 03:23:26 AM EDT
|
||||
@@ -276,35 +348,61 @@ gpg: Good signature from "Anonymous Planet Release Signing Key" [ultimate]
|
||||
Primary key fingerprint: C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2
|
||||
```
|
||||
|
||||
You can safely ignore GitHub/Codeberg warnings like "The email in this signature doesn't match the committer email."
|
||||
You can safely ignore Github, Codeberg, etc. warnings like "The email in this signature doesn’t match the committer email."
|
||||
|
||||
```txt
|
||||
λ > git tag -v v1.2.3
|
||||
object cdc54d8b3bc2b286827b23921d8d4062f85295cf
|
||||
type commit
|
||||
tag v1.2.3
|
||||
tagger nopeitsnothing <no@anonymousplanet.net> 1780212206 -0400
|
||||
|
||||
v1.2.3
|
||||
gpg: Signature made Sun 31 May 2026 03:23:26 AM EDT
|
||||
gpg: using EDDSA key C3023DBEA3FB38C438BA1EEDCEC60AEDE8B992A2
|
||||
gpg: Good signature from "Anonymous Planet Release Signing Key" [ultimate]
|
||||
Primary key fingerprint: C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting { #troubleshooting }
|
||||
## Troubleshooting
|
||||
|
||||
**`cairosvg` missing during MkDocs build**
|
||||
`pip install "mkdocs-material[imaging]"` — required by the `social` plugin.
|
||||
Install the imaging extras: `pip install "mkdocs-material[imaging]"`. Required by the `social` plugin.
|
||||
|
||||
**`KeyError: 'JPEG'` in convert.py**
|
||||
`sudo apt install libjpeg-dev && pip install --force-reinstall pillow`
|
||||
Pillow needs libjpeg. Reinstall after installing the system lib: `sudo apt install libjpeg-dev && pip install --force-reinstall pillow`.
|
||||
|
||||
**`qpdf: can't find PDF header`**
|
||||
qpdf only accepts PDF inputs — ensure you are on the current version of `convert.py`.
|
||||
Ensure you are on the current version of `convert.py` - qpdf only accepts PDF inputs, not PNG.
|
||||
|
||||
**GPG signing fails — `No secret key`**
|
||||
Re-export: `gpg --armor --export-secret-keys <fingerprint>` and re-paste the full block including headers into the `GPG_PRIVATE_KEY` secret.
|
||||
**GPG signing fails on CI with `No secret key`**
|
||||
The `GPG_PRIVATE_KEY` secret is missing or malformed. Re-export with `gpg --armor --export-secret-keys <fingerprint>` and paste the full block including header and footer lines.
|
||||
|
||||
**GPG signing fails — `Bad passphrase`**
|
||||
The `GPG_PASSPHRASE` secret has a trailing space or newline. Re-paste without surrounding whitespace.
|
||||
**GPG signing fails with `Bad passphrase`**
|
||||
The `GPG_PASSPHRASE` secret has a trailing space or newline. Paste it again with no surrounding whitespace.
|
||||
|
||||
**`03-release.yml` fails on VirusTotal**
|
||||
`VT_API_KEY` is missing, invalid, or over the 500 req/day free-tier limit. Re-run after a few minutes.
|
||||
The `VT_API_KEY` is missing, invalid, or over the rate limit (500 requests/day on the free tier). Check the secret and re-run after a few minutes.
|
||||
|
||||
**`02-sign.yml` fails downloading PDF artifact**
|
||||
Wrong `build_run_id`, or the artifact expired (90-day retention). Trigger a new build.
|
||||
The `build_run_id` is wrong, or the artifact has expired (90-day retention). Trigger a new build and use the fresh run ID.
|
||||
|
||||
**Changelog already contains version X**
|
||||
`update_changelog.py` errors if the version is already present. Choose the next version string.
|
||||
`update_changelog.py` will error if `MANUAL_VERSION` is set to a version already in the changelog. Choose the next version string.
|
||||
|
||||
**Footnote warnings — `link '#fnref:N' has no anchor`**
|
||||
A definition `[^N]:` exists without a matching inline citation. Add the citation or remove the orphaned definition.
|
||||
**Footnote warnings from MkDocs (`link '#fnref:N' has no anchor`)**
|
||||
A footnote definition `[^N]:` exists without a matching inline citation. Add the citation or remove the orphaned definition.
|
||||
|
||||
[discussions]: https://github.com/Anon-Planet/thgtoa/discussions
|
||||
[issues]: https://github.com/Anon-Planet/thgtoa/issues
|
||||
[help fellow users with open issues]: https://github.com/Anon-Planet/thgtoa/issues
|
||||
[topic branch]: http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches
|
||||
[Qubes#7457]: https://github.com/QubesOS/qubes-issues/issues/7457
|
||||
[50/72 rule]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||
[draft pr]: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests
|
||||
[console output]: https://docs.github.com/en/free-pro-team@latest/github/writing-on-github/creating-and-highlighting-code-blocks#fenced-code-blocks
|
||||
[verification steps]: https://docs.github.com/en/free-pro-team@latest/github/writing-on-github/basic-writing-and-formatting-syntax#task-lists
|
||||
[reference associated issues]: https://github.com/blog/1506-closing-issues-via-pull-requests
|
||||
[help fellow committers test recently submitted pull requests]: https://github.com/Anon-Planet/thgtoa/pulls
|
||||
|
||||
@@ -1,82 +1,91 @@
|
||||
---
|
||||
title: "A Constitution"
|
||||
description: "The rules, values, and principles governing the Anonymous Planet initiative and PSA community."
|
||||
hide:
|
||||
- toc
|
||||
- navigation
|
||||
title: Impressum
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<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>
|
||||
</div>
|
||||
To amend the rules and regulations of the network and of the PSA community, this constitution is hereby set forth. It is applicable to all the projects of the initiative, especially the Hitchhiker's Guide to Online Anonymity. All members/collaborators must abide by these lines when contributing within the context of the initiative.
|
||||
|
||||
<div class="index-grid">
|
||||
# Requirements
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Anonymity Above Everything</h3>
|
||||
<p class="index-card__body">Anonymity is necessary to maintain the balance of power — for journalists, whistleblowers, lawyers, scientists, and victims of oppression. Anonymity first, even if that means using proprietary means. Security and privacy are second. The ends may at times justify proprietary means.</p>
|
||||
</div>
|
||||
### Anonymity above everything
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Independence</h3>
|
||||
<p class="index-card__body">Anonymous Planet has no affiliation with the "Anonymous" collective and does not endorse their activities. Any overlap between their activities and this guide is purely coincidental.</p>
|
||||
</div>
|
||||
Anonymity is necessary to maintain the balance of power, specifically to help journalists, whistleblowers, lawyers, scientists, and victims of oppression. Anonymity first, even if that means using non-free and/or proprietary means. Security and privacy are second, again, even if using non-free or non-open-source and/or proprietary means. In this sense, the ends may at times justify proprietary means.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Accessibility</h3>
|
||||
<p class="index-card__body">The Hitchhiker's Guide will always be available online, offline (PDF, ODT), and via the Tor network. Removing any of these access methods is hostile to the mission.</p>
|
||||
</div>
|
||||
### Independence
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Freedom</h3>
|
||||
<p class="index-card__body">All projects are and will remain free, open-source, and non-commercial. Proprietary tools may be recommended where they serve anonymity. All scientific knowledge should be free — we support Sci-Hub and LibGen. Any attempt to erode the freedom of information in our projects is hostile.</p>
|
||||
</div>
|
||||
The Anonymous Planet initiative has no affiliation with the "Anonymous" collective and does not endorse their activities
|
||||
Any overlap of their activities and our guide are purely coincidental.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Verifiability & Reproducibility</h3>
|
||||
<p class="index-card__body">We will be transparent about our biases — anyone claiming to be unbiased is lying. All content shall be verifiable and fact-checked via academic references, reputable media, official documentation, expert review, and direct testing by collaborators.</p>
|
||||
</div>
|
||||
### Accessibility
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Innocence</h3>
|
||||
<p class="index-card__body">Suspected offenders are innocent until proven guilty. Zero tolerance for abuse of power. Accusers bear the burden of proof. Offenders have the right to face their accuser, appeal to an uninvolved third party, and receive due process.</p>
|
||||
</div>
|
||||
We will strive to always keep available the following methods of reading the Hitchhiker's Guide:
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Freedom of Thought</h3>
|
||||
<p class="index-card__body">Open-minded and pragmatic — no gatekeeping. Critical thinking and harsh criticism are welcome. Ad-hominem attacks and slurs are not.</p>
|
||||
</div>
|
||||
- online;
|
||||
- offline (e.g., PDF, ODT);
|
||||
- via the Tor network
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">No Tolerance for Intolerance</h3>
|
||||
<p class="index-card__body">See the <a href="https://en.wikipedia.org/wiki/Paradox_of_tolerance">Paradox of Tolerance</a>. Hate speech is included.</p>
|
||||
</div>
|
||||
### Freedom
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">No Analytics</h3>
|
||||
<p class="index-card__body">We will never use analytics. Hosting platforms (e.g. GitHub Pages) may gather analytics outside our control. As the initiative grows, we will move away from these platforms where possible.</p>
|
||||
</div>
|
||||
Maintain free, open-source, and non-commercial nature of all our projects. This does not mean proprietary and/or closed-source tools won't be recommendeded. All scientific knowledge should be free for anyone and we support and encourage Sci-Hub and LibGen. Any attempt to erode the freedom of information and flow of knowledge of our projects, in any manner, is hostile.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">No Profit</h3>
|
||||
<p class="index-card__body">Excess donations fund our projects first, then initiatives like Tor exit nodes. All donations, spendings, source code, and goals are public. No sponsored content, no affiliate links, no product placements, no advertising. Donations never influence recommendations.</p>
|
||||
</div>
|
||||
### Verifiability, falsifiability and reproducibility
|
||||
|
||||
</div>
|
||||
We will make every effort to be transparent about any and all bias we have.
|
||||
Anyone claiming to be unbiased is lying, therefore we will not falsely claim to be.
|
||||
|
||||
It is possible that, coincidentally, a donation could correlate with a recommendation. Where this occurs it will be clearly stated that the donation was welcome, but confers no visibility, coverage, endorsement, or preferential recommendation.
|
||||
All our content shall be verifiable, reproducible and fact-checked:
|
||||
|
||||
---
|
||||
- academic references (e.g., studies, papers, and peer reviewed publications);
|
||||
- reputable media references (e.g., articles, videos, and documentaries);
|
||||
- official documentation (e.g., manuals, field guides, and technical documents);
|
||||
- renowned and reputable expert review;
|
||||
- direct testing by our own collaborators for falsifiablity
|
||||
|
||||
## Goals { #goals }
|
||||
### Innocence
|
||||
|
||||
**Core goal:** Help people who need anonymity maintain their physical and digital safety.
|
||||
Suspected offenders are innocent until proven guilty, with zero tolerance for abuse of power or position.
|
||||
|
||||
**Non-goal:** Help people use this knowledge for harmful purposes.
|
||||
Any accusing/moderating member is:
|
||||
|
||||
We know our content can be misused. We believe that giving one good person a safe, anonymous voice is worth the risk of a few using it for ill — in the same way that a fair rule of law accepts that freeing nine criminals is better than imprisoning one innocent person.
|
||||
- Subject to the burden of proving the wrong-doing of the offender
|
||||
- Required to motivate any sanction
|
||||
|
||||
---
|
||||
Any offender has the right to:
|
||||
|
||||
_Yours faithfully, Anonymous Planet_
|
||||
- Face their accuser (know who is accusing them)
|
||||
- Appeal sanctions to an uninvolved third party
|
||||
- Participate in their own incrimination (the burden of proof lies with the accuser)
|
||||
- Due process of the above
|
||||
|
||||
### Freedom of thought
|
||||
|
||||
Open-minded and pragmatic - with no tolerance for gatekeeping.
|
||||
|
||||
Critical thinking and fact-checking are strongly encouraged; we welcome criticism including of a harsh nature (excluding ad-hominem and slurs).
|
||||
|
||||
### We do not tolerate intolerance
|
||||
|
||||
See the [Paradox of Tolerance](https://en.wikipedia.org/wiki/Paradox_of_tolerance), which includes hate speech.
|
||||
|
||||
### No analytics
|
||||
|
||||
Note that, while we will never use analytics, the (now free) platforms hosting our content might be gathering such analytics outside of our control, such as Github pages. As the initiative progresses, we will strive to avoid these as soon as possible.
|
||||
|
||||
### No profit
|
||||
|
||||
Any excess donations will only be used to support our main projects first and possibly support other intitiatives (like hosting Tor exit nodes). In all cases, we abide by the following principles:
|
||||
|
||||
- Funding transparency (i.e., all donations, spendings, source code, and future goals will be public)
|
||||
- Acceptance of donations from any entity anonymously or acknowledged (opt-in) will not have any influence on our content
|
||||
- No sponsored content
|
||||
- No affiliate links
|
||||
- No product placements
|
||||
- No advertising
|
||||
|
||||
**Disclaimer: it is possible that, coincidentally, a donation could correlate with a recommendation. It will then be clearly stated that while the donation was welcome, the donating entity will not be gaining visibility/coverage/endorsement/recommendations due to such a donation.**
|
||||
|
||||
# Core Goals
|
||||
|
||||
- Help people in need of anonymity to maintain both their physical and digital safety.
|
||||
- Help any people who are using this knowledge for bad purposes. Helping people takes precedence and we know our content can be used nefariously. Our initiative believes in having one good person given an anonymous voice, safely, is worth the risk of having several using our content for evil. As we do adhere to a fair "rule of law" system which, having 9 criminals and 1 innocent person free, is much better than having one innocent person in prison among 9 criminals.
|
||||
|
||||
**Yours faithfully, Anonymous Planet**
|
||||
|
||||
@@ -1,175 +1,149 @@
|
||||
---
|
||||
title: Contribute & Donate
|
||||
description: "Support the Anonymous Planet project through crypto donations or content contributions. No fiat. No tracking. No profit."
|
||||
title: How to Get Involved
|
||||
hide:
|
||||
- toc
|
||||
- navigation
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Support the project if you can!</div>
|
||||
<h1 class="hero-title">No ads. No grants.<br><span class="hero-subtitle">Anonymous donations welcome.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Donations fund Tor .onion hosting, domain registration, bulletproof mail hosting, and exit node operations.
|
||||
Every cent is logged below. No profit is ever made.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="#xmr" class="hero-cta hero-cta--primary">Donate with Monero (XMR)</a>
|
||||
<a href="#btc" class="hero-cta hero-cta--secondary">Donate with Bitcoin (BTC)</a>
|
||||
</div>
|
||||
</div>
|
||||
# How can I help the collective?
|
||||
|
||||
<div class="donate-goals-block">
|
||||
<h3 class="donate-goals-title">Current Goals</h3>
|
||||
<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> Domain <code>anonymousplanet.net</code> — <strong>secured until 2029</strong></li>
|
||||
<li class="donate-goal donate-goal--open"><span class="donate-goal-icon">○</span> Decent mail hosting</li>
|
||||
<li class="donate-goal donate-goal--open"><span class="donate-goal-icon">○</span> VPS for general project services</li>
|
||||
</ul>
|
||||
</div>
|
||||
Donations to support Anonymous Planet are welcome, but are entirely optional. Those donations are mainly used to pay for VPS and mail hosting, domain name registration, and to maintain/run Tor exit nodes. **No profit is ever being made**. All donations and spendings are being logged here below for transparency and a more robust designated page will be put up in the future to maintain it. For now, it's all hand-typed & calculated. Some costs for load balancer servers have been omitted for privacy reasons, but are not paid for with existing Anonymous Planet finances. Additionally, some services cannot be described as they are meant to be anonymous, such as if we used personal funds to pay for a private card aliasing service to facilitate .onion hosting. Below is all information in regards to our fiscal host (which handles our legal and financial investments) and our organizational goals.
|
||||
|
||||
---
|
||||
## What is a fiscal host?
|
||||
|
||||
## Donate with Monero (XMR) { #xmr }
|
||||
A **fiscal host** is an organization that accepts donations on behalf of 501(c)(3) nonprofits or other charitable entities. They handle the legal and financial infrastructure needed to process tax-deductible contributions, issue receipts for donors, manage audits, and handle payroll or grants — essentially acting as a middle layer between you (the donor) and your favorite privacy-focused project.
|
||||
|
||||
Monero is the preferred donation method. Transactions are private by default.
|
||||
Think of it like a bank for nonprofits: if you wanted to donate directly to an independent developer building free tools, that money wouldn't qualify as tax-deductible unless the developer was already incorporated as a nonprofit with their own fiscal operations. Fiscal hosts solve this by wrapping around smaller projects with professional infrastructure.
|
||||
|
||||
<div class="donate-stat-row">
|
||||
<div class="donate-stat"><span class="donate-stat-label">Total received</span><span class="donate-stat-value">7.101317 XMR</span></div>
|
||||
<div class="donate-stat"><span class="donate-stat-label">Remaining balance</span><span class="donate-stat-value">0 XMR</span></div>
|
||||
</div>
|
||||
### Our Fiscal Host: Open Source Collective
|
||||
|
||||
<div class="donate-address-block">
|
||||
<div class="donate-address-label">XMR address</div>
|
||||
<code class="donate-address">46crzj54eL493BA68pPT4A1MZyKQxrpZu9tVNsfsoa5nT85QqCt8cDTfy1fcTH1oyjdtUbhmpZ4QcVtfEXB337Ng6PS21ML</code>
|
||||
<div class="donate-qr">
|
||||
<img src="../media/monero.png" alt="Monero QR code" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
We use [Open Source Collective](https://opensourcecollective.com/) as our fiscal host. This platform is designed for independent developers building open-source software — they handle the 501(c)(3) compliance, legal requirements, and tax receipts while keeping overhead minimal.
|
||||
|
||||
---
|
||||
## How We Plan to Use Funds
|
||||
|
||||
## Donate with Bitcoin (BTC) { #btc }
|
||||
All donations support hosting infrastructure and operational costs:
|
||||
|
||||
Bitcoin donations are accepted on both SegWit and legacy addresses.
|
||||
- **VPS servers** for our .onion services hosting
|
||||
- **Domain registration** for long-term stability
|
||||
- **Mail hosting** for anonymous email services (Simplelogin credits, aliases)
|
||||
- **Tor exit node maintenance** to support the privacy network
|
||||
- **Load balancers** for high-availability and redundancy
|
||||
|
||||
<div class="donate-stat-row">
|
||||
<div class="donate-stat"><span class="donate-stat-label">Total received</span><span class="donate-stat-value">1.89353 mBTC</span></div>
|
||||
<div class="donate-stat"><span class="donate-stat-label">Remaining balance</span><span class="donate-stat-value">0 mBTC</span></div>
|
||||
</div>
|
||||
## Future Goals
|
||||
|
||||
<div class="donate-address-block">
|
||||
<div class="donate-address-label">SegWit address</div>
|
||||
<code class="donate-address">bc1qp9g2c6dquh5lnvft50esxsl97kupdpyqyd4kkv</code>
|
||||
<div class="donate-address-label" style="margin-top:1em;">Legacy address</div>
|
||||
<code class="donate-address">1BBgBSVe6w4DWq2BewUQhDEjsNovhfPswD</code>
|
||||
<div class="donate-qr donate-qr--row">
|
||||
<figure>
|
||||
<img src="../media/bitcoin-segwit.png" alt="Bitcoin SegWit QR code" loading="lazy">
|
||||
<figcaption>SegWit</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../media/bitcoin-legacy.png" alt="Bitcoin Legacy QR code" loading="lazy">
|
||||
<figcaption>Legacy</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
- Discourse for the organization, our contributors, and maintainers
|
||||
- Anyone interested in privacy and anonymity technologies
|
||||
- Knowledge sharing about security, Tor, onion services, and encrypted communications
|
||||
|
||||
---
|
||||
Funds raised will help us expand into additional community tools while keeping our mission focused on supporting free and open-source privacy projects.
|
||||
|
||||
## Contribute Content { #contribute }
|
||||
## Financial Goals
|
||||
|
||||
<div class="index-grid">
|
||||
We have met two of our main financial goals.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Submit a PR</h3>
|
||||
<p class="index-card__body">Fix errors, add sections, or update outdated advice. Follow the contributing guidelines and use conventional commit syntax.</p>
|
||||
<a href="../code/" class="index-card__link">Developer guide</a>
|
||||
</div>
|
||||
- <span style="color: var(--status-success)">Funding for a decent mail hosting ✓</span>
|
||||
- <span style="color: var(--status-success)">Funding for domain (long-term) ✓</span>
|
||||
- <span style="color: var(--status-warning)">Funding for a VPS for hosting our .onion & other services</span>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Use a Mirror</h3>
|
||||
<p class="index-card__body">GitHub, Codeberg, GitLab, Disroot, and a Tor onion. Pick the one that fits your threat model.</p>
|
||||
<a href="../mirrors/" class="index-card__link">View mirrors</a>
|
||||
</div>
|
||||
## Donate using Monero (XMR)
|
||||
|
||||
</div>
|
||||
- Total Monero donations received: **7.101317184263 XMR**
|
||||
- Total Monero remaining: **2.059336719397 XMR**
|
||||
|
||||
---
|
||||
Here is the address for the main project:
|
||||
|
||||
## Transparency Log
|
||||
`46crzj54eL493BA68pPT4A1MZyKQxrpZu9tVNsfsoa5nT85QqCt8cDTfy1fcTH1oyjdtUbhmpZ4QcVtfEXB337Ng6PS21ML`
|
||||
|
||||
All donations and spendings are recorded here. <span style="color:var(--status-error)">Red entries</span> indicate funds tied to services that were lost.
|
||||
![][1]
|
||||
|
||||
### Donations
|
||||
## Donate using Bitcoin (BTC)
|
||||
|
||||
| Date / Time | Amount | Note |
|
||||
|---|---|---|
|
||||
| 2021-02-06 16:48 | 0.1 XMR | |
|
||||
| 2021-03-15 00:09 | 1.24869 mBTC | |
|
||||
| 2021-03-15 08:41 | 0.07896 mBTC | |
|
||||
| 2021-03-31 16:28 | 1 XMR | :heart: Generous donation |
|
||||
| 2021-04-03 22:31 | 0.5 XMR | :heart: Generous donation |
|
||||
| 2021-05-07 06:22 | 0.010433 XMR | |
|
||||
| 2021-06-16 03:05 | 0.03 XMR | |
|
||||
| 2021-06-27 18:39 | 0.05 XMR | |
|
||||
| 2021-07-12 07:24 | 0.02 XMR | |
|
||||
| 2021-07-16 14:31 | 0.1 mBTC | |
|
||||
| 2021-07-20 21:01 | 0.058981 XMR | |
|
||||
| 2021-07-24 15:16 | 0.000000000001 XMR | |
|
||||
| 2021-07-25 02:37 | 0.000000000001 XMR | |
|
||||
| 2021-08-03 00:17 | 0.04119 XMR | |
|
||||
| 2021-08-07 15:05 | 0.206328 XMR | |
|
||||
| 2021-08-10 11:42 | 0.21 mBTC | |
|
||||
| 2021-08-13 00:25 | 0.25 XMR | |
|
||||
| 2021-08-14 04:58 | 0.25588 mBTC | |
|
||||
| 2021-08-30 17:32 | 0.000000000001 XMR | |
|
||||
| 2021-09-17 14:34 | 0.018 XMR | |
|
||||
| 2021-10-01 06:23 | 0.000000002137 XMR | |
|
||||
| 2021-10-02 19:16 | 1 XMR | :heart: Generous donation |
|
||||
| 2021-10-17 15:40 | 0.02 XMR | |
|
||||
| 2021-10-18 16:06 | 0.1958 XMR | |
|
||||
| 2021-11-12 20:42 | 0.02 XMR | |
|
||||
| 2021-11-14 18:28 | 0.018 XMR | |
|
||||
| 2021-12-03 21:38 | 0.10134 XMR | |
|
||||
| 2021-12-16 01:16 | 1 XMR | :heart: Generous donation |
|
||||
| 2021-12-16 18:06 | 0.017 XMR | |
|
||||
| 2022-01-09 17:54 | 0.045918 XMR | |
|
||||
| 2022-01-15 17:35 | 0.014 XMR | |
|
||||
| 2022-01-24 21:08 | 0.010786 XMR | |
|
||||
| 2022-01-26 12:07 | 0.010391 XMR | |
|
||||
| 2022-02-03 19:59 | 0.013013 XMR | |
|
||||
| 2022-02-18 17:27 | 0.019 XMR | |
|
||||
| 2022-03-14 10:25 | 0.0139887 XMR | |
|
||||
| 2022-07-30 03:51 | 0.0222 XMR | |
|
||||
| 2022-09-28 05:13 | 2 XMR | |
|
||||
| 2022-08-19 | SimpleLogin Lifetime Premium | In-kind |
|
||||
| 2022-09-19 | 0.345024 XMR | :heart: From a previous maintainer |
|
||||
- Total Bitcoin donations received: **1.89353 mBTC**
|
||||
- Total Bitcoin remaining: **0 mBTC**
|
||||
|
||||
### Spendings
|
||||
Here are the addresses for the main project:
|
||||
|
||||
| Date | Amount | Purpose | Status |
|
||||
|---|---|---|---|
|
||||
| 2021-03-12 | 0.08181 XMR | Domain `anonymousplanet.net` (1 yr) | Active |
|
||||
| 2021-03-16 | 1.20179 mBTC | Domain renewal (+3 yr, 4 yr total) | Active |
|
||||
| 2021-04-01 | 0.8317 XMR | VPS for Tor mirror hosting | Active |
|
||||
| 2021-04-05 | 0.99367 mBTC | Mail hosting (1 yr) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-04-13 | 0.71895 mBTC | Mail hosting (ext. to 2 yr) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-04-25 | 0.02892 mBTC | Wallet transfer fee | — |
|
||||
| 2021-07-13 | 0.78463 mBTC | BTC→XMR consolidation swap | — |
|
||||
| 2021-07-13 | 0.067261 XMR | Tor exit node 01 (3 mo) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-07-15 | 0.151959 XMR | Tor exit node 02 (6 mo) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-08-16 | 0.253331 XMR | Tor exit node 03 (12 mo) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-08-18 | −0.56588 mBTC / +0.081904 XMR | AtomicSwap BTC→XMR | — |
|
||||
| 2021-08-19 | 0.0644 XMR | Mail hosting extension | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-09-18 | 0.246971 XMR | Tor exit node 01 renewal (1 yr) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-10-04 | 0.26954 XMR | Domain ext. to 2029 | Active |
|
||||
| 2021-10-06 | 0.236073 XMR | Tor exit node 04 (12 mo) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2021-10-18 | 0.01952 XMR | Privex.io VPS test (1 mo) | <span class="status-indicator status-warning">Ended</span> |
|
||||
| 2021-10-30 | 0.240787 XMR | Synapse hosting VPS (12 mo) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2022-01-01 | 0.28055 XMR | Tor exit node 02 renewal (1 yr) | <span class="status-indicator status-error">Lost</span> |
|
||||
| 2022-02-02 | 0.966793 XMR | Special project sponsorship (UDHR) | <span class="status-indicator status-error">Lost</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> |
|
||||
- SegWit address: ```bc1qp9g2c6dquh5lnvft50esxsl97kupdpyqyd4kkv```
|
||||
|
||||
**Entries marked <span class="status-indicator status-error">Lost</span> represent funds spent on services that are no longer available.**
|
||||
![][2]
|
||||
|
||||
- Legacy address: ```1BBgBSVe6w4DWq2BewUQhDEjsNovhfPswD```
|
||||
|
||||
![][3]
|
||||
|
||||
## Content Contributions
|
||||
|
||||
You can easily contribute code or information suggestions at our code repositories listed at the bottom of the website and on the [Mirrors](../mirrors/index.md) tab above. We have many options that are easily accessible. Please follow our [contributing guidelines](../code/index.md) and use good PR syntax. Be sure to go to the [developer guide](../code/index.md) first.
|
||||
|
||||
**Thank you for any contribution. All donations will be mentioned on this page.**
|
||||
|
||||
??? tip "Donations log"
|
||||
|
||||
- 2021-02-06 16:48: 0.1 XMR
|
||||
- 2021-03-15 00:09: 1.24869 mBTC
|
||||
- 2021-03-15 08:41: 0.07896 mBTC
|
||||
- 2021-03-31 16:28: 1 XMR (Special thanks for this very generous donation)
|
||||
- 2021-04-03 22:31: 0.5 XMR (Special thanks for this very generous donation)
|
||||
- 2021-05-07 06:22: 0.010433355105 XMR
|
||||
- 2021-06-16 03:05: 0.03 XMR
|
||||
- 2021-06-27 18:39: 0.05 XMR
|
||||
- 2021-07-12 07:24: 0.02 XMR
|
||||
- 2021-07-16 14:31: 0.1 mBTC
|
||||
- 2021-07-20 21:01: 0.058981 XMR
|
||||
- 2021-07-24 15:16: 0.000000000001 XMR
|
||||
- 2021-07-25 02:37: 0.000000000001 XMR
|
||||
- 2021-08-03 00:17: 0.04119191113 XMR
|
||||
- 2021-08-07 15:05: 0.206328241262 XMR
|
||||
- 2021-08-10 11:42: 0.21 mBTC
|
||||
- 2021-08-13 00:25: 0.25 XMR
|
||||
- 2021-08-14 04:58: 0.25588 mBTC
|
||||
- 2021-08-30 17:32: 0.000000000001 XMR
|
||||
- 2021-09-17 14:34: 0.018 XMR
|
||||
- 2021-10-01 06:23: 0.000000002137 XMR
|
||||
- 2021-10-02 19:16: 1 XMR (Special thanks for this very generous donation)
|
||||
- 2021-10-17 15:40: 0.02 XMR
|
||||
- 2021-10-18 16:06: 0.1958 XMR
|
||||
- 2021-11-12 20:42: 0.02 XMR
|
||||
- 2021-11-14 18:28: 0.018 XMR
|
||||
- 2021-12-03 21:38: 0.10134722595 XMR
|
||||
- 2021-12-16 01:16: 1 XMR (Special thanks for this very generous donation)
|
||||
- 2021-12-16 18:06: 0.017 XMR
|
||||
- 2022-01-09 17:54: 0.045918219893 XMR
|
||||
- 2022-01-15 17:35: 0.014 XMR
|
||||
- 2022-01-24 21:08: 0.010786 XMR
|
||||
- 2022-01-26 12:07: 0.010391 XMR
|
||||
- 2022-02-03 19:59: 0.013013984 XMR
|
||||
- 2022-02-18 17:27: 0.019 XMR
|
||||
- 2022-03-14 10:25: 0.0139887 XMR
|
||||
- 2022-07-30 03:51: 0.0222 XMR
|
||||
- 2022-09-28 05:13: 2 XMR
|
||||
- 2022-08-19: SimpleLogin.io Lifetime Premium
|
||||
- 2022-09-19: 0.345024603905 XMR (Special thanks to a previous maintainer)
|
||||
|
||||
??? note "Spendings log"
|
||||
|
||||
- 2021-03-12: 0.08181086 XMR (+fees) for domain anonymousplanet.net (1 year)
|
||||
- 2021-03-16: 1.20179 mBTC (+fees) for domain anonymousplanet.net renewal (extension 3 years totalling 4 years)
|
||||
- 2021-04-01: 0.8317 XMR (+fees) for basic VPS for Tor Mirror hosting
|
||||
- <del>2021-04-05: 0.99367 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (1 year): <span style="color: red">**Lost**</span>
|
||||
- <del>2021-04-13: 0.71895 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (extension to 2 years)</del>: <span style="color: red">**Lost**</span>
|
||||
- 2021-04-25: 0.02892 mBTC (Wallet to Wallet transfer fee)
|
||||
- 2021-07-13: 0.78463 mBTC (+fees +exchange from BTC to XMR) for consolidation
|
||||
- <del>2021-07-13: 0.067261698061 XMR (+fees) for a Tor Exit Node (01) Hosting (3 months)</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2021-07-15: 0.151959953047 XMR (+fees) for a Tor Exit Node (02) Hosting (6 months)</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2021-08-16: 0.253331471239 XMR (+fees) for a Tor Exit Node (03) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
|
||||
- 2021-08-18: AtomicSwap conversion from remaining mBTC (-0.56588) to XMR (+0.081904862179)
|
||||
- <del>2021-08-19: 0.0644 XMR (+fees) for Mail Hosting extension</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2021-09-18: 0.246971511836 XMR (+fees) for renewal 1 year of Tor Exit Node 01</del>: <span style="color: red">**Lost**</span>
|
||||
- 2021-10-04: 0.26954 XMR (+fees) for domain anonymousplanet.net extension until 2029
|
||||
- <del>2021-10-06: 0.236073464623 XMR (+fees) for a Tor Exit Node (04) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2021-10-18: 0.01952 XMR (+fees) for testing a new VPS hosting provider (Privex.io) for one month</del>: <span style="color: red">**Ended**</span>
|
||||
- <del>2021-10-30: 0.240787814495 XMR (+fees) for a Synapse Hosting VPS (12 months) with bots to help grow the community. This is a test program that will be converted into a Tor Exit Node in case of failure</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2022-01-01: 0.28055816111 XMR (+fees) for renewal 1 year of Tor Exit Node 02</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2022-02-02: 0.966793601024 XMR (+fees) to sponsor a special project (w/ Universal Declaration of Human Rights)</del>: <span style="color: red">**Lost**</span>
|
||||
- <del>2022-07-11: 0.503232784687 XMR (+fees) for 1984.is VPS (12 months)</del>: <span style="color: red">**Ended**</span>
|
||||
- <del>2022-09-19: 0.345024603905 XMR (+fees) for upgrading VPS RAM/Disk</del>: <span style="color: red">**Ended**</span>
|
||||
|
||||
[1]: ../media/monero.png
|
||||
[2]: ../media/bitcoin-segwit.png
|
||||
[3]: ../media/bitcoin-legacy.png
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
<!-- 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,63 +1,78 @@
|
||||
---
|
||||
title: "Home"
|
||||
description: "The Hitchhiker's Guide to Online Anonymity"
|
||||
meta_title: "The Hitchhiker's Guide to Online Anonymity"
|
||||
description: "The comprehensive guide to online anonymity and #opsec"
|
||||
hide:
|
||||
- toc
|
||||
- navigation
|
||||
schema:
|
||||
-
|
||||
"@context": https://schema.org
|
||||
"@type": WebPage
|
||||
"@type": Organization
|
||||
"@id": https://anonymousplanet.net/
|
||||
name: Anonymous Planet
|
||||
url: https://anonymousplanet.net/
|
||||
logo: media/profile.svg
|
||||
logo: media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
-
|
||||
---
|
||||
<div class="hero-block">
|
||||
<h1 class="hero-title">The Hitchhiker's Guide<br><span class="hero-subtitle">to Online Anonymity (and Privacy)</span></h1>
|
||||
<p class="hero-tagline">
|
||||
You are being tracked. Your metadata is being sold. Your identity is a product. This guide is your manual out.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="guide/" class="hero-cta hero-cta--primary">Read the Guide</a>
|
||||
<a href="verify/" class="hero-cta hero-cta--secondary">Verify & Download</a>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; font-size: 2.7rem; font-weight: 700; line-height: 1; margin: 2em 0;">
|
||||
<span>Privacy.</span><br>
|
||||
<span style="color: var(--crt-green);">Security.</span><br>
|
||||
<span>Anonymity.</span>
|
||||
</div>
|
||||
|
||||
<div class="principles-strip">
|
||||
<span class="principle"><span class="principle-no">No ads.</span></span>
|
||||
<span class="principle-sep">/</span>
|
||||
<span class="principle"><span class="principle-no">No affiliate links.</span></span>
|
||||
<span class="principle-sep">/</span>
|
||||
<span class="principle"><span class="principle-no">No bullshit.</span></span>
|
||||
<div style="text-align: center; font-size: 2rem; font-weight: 400; line-height: 0.9; margin: 2em 0;">
|
||||
<span style="color: var(--crt-green);">No ads.</span><br>
|
||||
<span style="color: var(--crt-amber);">No affiliate links.</span><br>
|
||||
<span style="color: var(--crt-red);">No bullshit.</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="index-grid">
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Read Online</h3>
|
||||
<p class="index-card__body">The full guide — tracking techniques, ID verification, anonymous identity creation, and operational security from first principles.</p>
|
||||
<a href="guide/" class="index-card__link">Open guide</a>
|
||||
<div style="text-align: center; background: rgba(255, 0, 0, 0.1); border: 1px solid rgba(255, 0, 0, 0.3);
|
||||
border-left: 4px solid #ff4444; padding: 1em; max-width: 70ch; margin: 3em auto; color: var(--crt-red)">
|
||||
<span style="color: #ff6b6b;">⚠</span> <strong style="color: var(--status-error);">NOTICE:</strong> Always
|
||||
verify you are on <a href="https://anonymousplanet.net/" style="color:
|
||||
var(--status-error);">anonymousplanet.net</a><strong style="color: #ff6b6b;"> ⚠</strong>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Download PDF</h3>
|
||||
<p class="index-card__body">Offline copy as PDF or ODT. Verify the file's integrity with the provided SHA256 checksum and GPG signature before trusting it.</p>
|
||||
<a href="export/thgtoa.pdf" class="index-card__link">Get the files</a>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; max-width:
|
||||
1000px; margin: 0 auto;">
|
||||
|
||||
<div class="quick-access-card">
|
||||
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Online</h3>
|
||||
<p style="margin: 0 0 1em;">Comprehensive coverage of tracking techniques, ID verification, and how to create
|
||||
truly anonymous identities.</p>
|
||||
<a href="guide/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Read online
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Tor Mirror</h3>
|
||||
<p class="index-card__body">Access over the Tor network. The .onion address means your IP and destination stay private — no exit node exposure.</p>
|
||||
<a href="mirrors/" class="index-card__link">View mirrors</a>
|
||||
<div class="quick-access-card">
|
||||
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">PDF</h3>
|
||||
<p style="margin: 0 0 1em;">Download the guide as a PDF (best for readability) or ODT. Verify integrity with the
|
||||
provided public key before trusting any downloaded file.</p>
|
||||
<a href="export/thgtoa.pdf" class="btn-download" style="min-width: auto !important; color: var(--crt-amber);">Get
|
||||
the files
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Verify Authenticity</h3>
|
||||
<p class="index-card__body">Check GPG signatures, SHA256 hashes, and fetch our public keyring. Never trust a download you haven't verified.</p>
|
||||
<a href="verify/" class="index-card__link">How to verify</a>
|
||||
<div class="quick-access-card">
|
||||
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Tor</h3>
|
||||
<p style="margin: 0 0 1em;">Access the guide over the Tor network for maximum privacy. The .onion address ensures
|
||||
you reach us without exposing your IP or destination.</p>
|
||||
<a href="mirrors/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Our .onion
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; font-family: var(--code-font); font-size: 0.8rem; padding: 1.5em 0; border-top:
|
||||
1px solid rgba(255,255,255,0.1); margin-top: 4em; color: var(--crt-green); opacity: 0.9;">
|
||||
<strong>The Hitchhiker's Guide</strong> is
|
||||
licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" style="color: var(--crt-amber);">CC BY-SA
|
||||
4.0</a>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 120 KiB |
@@ -1,79 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
@@ -1,7 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,7 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1,36 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,36 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,102 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 7.7 KiB |
@@ -1,102 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,9 +1,6 @@
|
||||
---
|
||||
title: "Mirrors"
|
||||
description: "Every place you can read or download the Hitchhiker's Guide."
|
||||
hide:
|
||||
- toc
|
||||
- navigation
|
||||
title: "Our Mirrors"
|
||||
description: Maintainers of the Hitchhiker's Guide and the PSA Community.
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
@@ -14,114 +11,26 @@ schema:
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
- https://mastodon.social/@anonymousplanet
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Censorship-resistant by design.</div>
|
||||
<h1 class="hero-title">Mirrors<span class="hero-subtitle">Pick the one that fits your threat model.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
The guide is mirrored across GitHub, Codeberg, GitLab, Disroot, and a Tor onion.
|
||||
We have contingencies in place.
|
||||
A signed PDF is also available for offline reading.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="#read" class="hero-cta hero-cta--primary">Read the Guide</a>
|
||||
<a href="#git" class="hero-cta hero-cta--secondary">Git Mirrors</a>
|
||||
</div>
|
||||
</div>
|
||||
???+ tip "Where to find the Hitchhiker's Guide"
|
||||
|
||||
---
|
||||
- [Original](https://anonymousplanet.net)
|
||||
- [Tor v3](http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion) **Down**
|
||||
- [Archive.org](https://web.archive.org/web/https://anonymousplanet.net)
|
||||
- [Archive.today](https://archive.fo/anonymousplanet.net)
|
||||
- [Archive.today over Tor](http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/anonymousplanet.net)
|
||||
|
||||
## Read the Guide { #read }
|
||||
!!! Note "PDF export (single file)"
|
||||
|
||||
<div class="index-grid">
|
||||
The guide is also available as a **PDF** (images and layout preserved). It is built automatically. See the [Releases](https://github.com/Anon-Planet/thgtoa/releases). More detail is in the [repository README](https://github.com/Anon-Planet/thgtoa#ways-to-read-or-export-the-guide).
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Original</h3>
|
||||
<p class="index-card__body">The canonical source. Hosted on GitHub Pages at the project's primary domain.</p>
|
||||
<a href="https://anonymousplanet.net" class="index-card__link">anonymousplanet.net</a>
|
||||
</div>
|
||||
!!! Note "Our official git mirrors"
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Tor Onion</h3>
|
||||
<p class="index-card__body">v3 onion service. Currently <strong>down</strong>. Check back or use a clearnet mirror.</p>
|
||||
<a href="http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion" class="index-card__link">Onion address</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">PDF Export</h3>
|
||||
<p class="index-card__body">A signed, single-file PDF built automatically from the source. Both light and dark editions. See the release notes for hash and signature files.</p>
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/releases" class="index-card__link">Releases</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Archive.org</h3>
|
||||
<p class="index-card__body">Wayback Machine snapshot. Useful if the primary domain is unreachable.</p>
|
||||
<a href="https://web.archive.org/web/https://anonymousplanet.net" class="index-card__link">Wayback Machine</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Archive.today</h3>
|
||||
<p class="index-card__body">Alternative archival snapshot. Also accessible via their Tor onion.</p>
|
||||
<a href="https://archive.fo/anonymousplanet.net" class="index-card__link">Archive.today</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Archive.today (Tor)</h3>
|
||||
<p class="index-card__body">Archive.today over Tor for censorship-resistant access.</p>
|
||||
<a href="http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/anonymousplanet.net" class="index-card__link">Onion</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Git Mirrors { #git }
|
||||
|
||||
<div class="index-grid">
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">GitHub</h3>
|
||||
<p class="index-card__body">Primary development host. Issues, pull requests, and CI live here.</p>
|
||||
<a href="https://github.com/anon-planet" class="index-card__link">github.com/anon-planet</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Codeberg</h3>
|
||||
<p class="index-card__body">EU-based, non-profit Gitea instance. No Microsoft.</p>
|
||||
<a href="https://codeberg.org/anonymousplanet" class="index-card__link">codeberg.org</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">GitLab</h3>
|
||||
<p class="index-card__body">GitLab.com mirror. Accepts merge requests.</p>
|
||||
<a href="https://gitlab.com/anonymousplanetorg" class="index-card__link">gitlab.com</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">0xacab</h3>
|
||||
<p class="index-card__body">Activist-run GitLab instance. Also accessible via Tor onion.</p>
|
||||
<a href="https://0xacab.org/anonypla" class="index-card__link">0xacab.org</a>
|
||||
<a href="http://wmj5kiic7b6kjplpbvwadnht2nh2qnkbnqtcv3dyvpqtz7ssbssftxid.onion/anonypla" class="index-card__link" style="margin-top:0.4em;">Onion</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Disroot</h3>
|
||||
<p class="index-card__body">Privacy-respecting Gitea instance. Also accessible via Tor onion.</p>
|
||||
<a href="https://git.disroot.org/anon-planet" class="index-card__link">git.disroot.org</a>
|
||||
<a href="http://kgtz2pmmov5jfvn3z4mqryffjnnw6krzrgxxoyaqhqckjrr4pckyhsqd.onion/anon-planet" class="index-card__link" style="margin-top:0.4em;">Onion</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Darktea</h3>
|
||||
<p class="index-card__body">Tor-only Gitea mirror. Only accessible via the onion address.</p>
|
||||
<a href="http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonymousplanetorg" class="index-card__link">Onion only</a>
|
||||
</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>
|
||||
- [Github](https://github.com/anon-planet)
|
||||
- [Darktea](http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonymousplanetorg) (Tor Only)
|
||||
- [Gitlab](https://gitlab.com/anonymousplanetorg)
|
||||
- [0xacab](https://0xacab.org/anonypla) ([Tor Onion](http://wmj5kiic7b6kjplpbvwadnht2nh2qnkbnqtcv3dyvpqtz7ssbssftxid.onion/anonypla))
|
||||
- [Codeberg](https://codeberg.org/anonymousplanet)
|
||||
- [Disroot](https://git.disroot.org/anon-planet) ([Tor Onion](http://kgtz2pmmov5jfvn3z4mqryffjnnw6krzrgxxoyaqhqckjrr4pckyhsqd.onion/anon-planet))
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{% 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 %}
|
||||
@@ -1,66 +0,0 @@
|
||||
{% 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>
|
||||
@@ -1,70 +0,0 @@
|
||||
<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>
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: "PGP"
|
||||
description: "Import our GPG keys to verify our releases and signed content."
|
||||
hide:
|
||||
- navigation
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
@@ -13,63 +11,34 @@ schema:
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
- https://mastodon.social/@anonymousplanet
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Trust, but verify.</div>
|
||||
<h1 class="hero-title">Our Public Keys<span class="hero-subtitle">Import. Verify. Trust nothing blindly.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Anonymous Planet signs all releases and communications with GnuPG (PGP) keys.
|
||||
Verify fingerprints against multiple independent sources before importing.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="anonymousplanet.asc" class="hero-cta hero-cta--primary">Download Public Keyring</a>
|
||||
<a href="#keys" class="hero-cta hero-cta--secondary">View Fingerprints</a>
|
||||
</div>
|
||||
# PGP
|
||||
|
||||
<div style="font-family: var(--text-primary); color: var(--crt-green); font-size: 1.1rem; margin-bottom: 2em;">
|
||||
<a href="anonymousplanet.asc" class="btn-download">
|
||||
>>Download Our Public Keyring<<
|
||||
</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
Anonymous Planet uses GnuPG keys as the standard for encryption/signing. Import this keyring to verify the authenticity of our releases, signed content, and secure your emails to us.
|
||||
|
||||
## Our Keys { #keys }
|
||||
## Anonymous Planet Keys
|
||||
|
||||
The full keyring is at `https://anonymousplanet.net/pgp/anonymousplanet.asc`.<br>Always verify fingerprints against our [GitHub releases](https://github.com/Anon-Planet/thgtoa/releases) before importing.
|
||||
Our entire keyring is available at <https://anonymousplanet.net/pgp/anonymousplanet.asc> (click the above button to get it). It is also pasted below.
|
||||
|
||||
<div class="index-grid">
|
||||
| Name | Key ID | Purpose |
|
||||
|------|--------|---------|
|
||||
| Master Signing Key (MSK) | `9FA5436D0EE360985157382517ECA05F768DEFDA` | Announcements, signing subkeys, etc. |
|
||||
| Release Signing Key (RSK) | `C3023DBEA3FB38C438BA1EECEC60AEDE8B992A2` | Release signing, occasional commit signing |
|
||||
| Email Encryption/Signing Key (ESK) | `FCBD2CABDEFD1FBA2E9E7591A1A82CD2DD2CF890` | Secure email |
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Master Signing Key (MSK)</h3>
|
||||
<p class="index-card__body">Announcements, signing subkeys, and trust anchoring. The root of our web of trust.</p>
|
||||
<code class="pgp-fingerprint">9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6</code>
|
||||
</div>
|
||||
**Fingerprint verification:** Always verify key fingerprints against our [GitHub announcements](https://github.com/Anon-Planet/thgtoa/releases) before importing.
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Release Signing Key (RSK)</h3>
|
||||
<p class="index-card__body">Signs all guide releases and distribution artifacts. Check this against every release.</p>
|
||||
<code class="pgp-fingerprint">C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2</code>
|
||||
</div>
|
||||
## Key Rotation
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Email Encryption / Signing Key (ESK)</h3>
|
||||
<p class="index-card__body">Use this to send us encrypted mail or verify signed correspondence from us.</p>
|
||||
<code class="pgp-fingerprint">FCBD 2CAB DEFD 1FBA 2E9E 7591 A1A8 2CD2 DD2C F890</code>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Key Rotation { #rotation }
|
||||
|
||||
Keys may be rotated periodically. Rotation is always announced via [GitHub Releases](https://github.com/Anon-Planet/thgtoa/releases) and the [changelog](../changelog/index.md). If a key you have on file does not appear here, treat it as compromised and re-import.
|
||||
|
||||
---
|
||||
|
||||
## Public Keyring { #keyring }
|
||||
|
||||
<div class="donate-address-block">
|
||||
<div class="donate-address-label">Keyring URL</div>
|
||||
<code class="donate-address">https://anonymousplanet.net/pgp/anonymousplanet.asc</code>
|
||||
</div>
|
||||
We may rotate keys periodically. Check our [GitHub Releases](https://github.com/Anon-Planet/thgtoa/releases) and [changelog](../changelog/index.md) for announcements.
|
||||
|
||||
```txt
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
.md-typeset code,
|
||||
.md-typeset .admonition code {
|
||||
background-color: rgba(30, 10, 40, 0.80);
|
||||
border: 1px solid rgba(125, 70, 152, 0.30);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
color: var(--crt-amber);
|
||||
}
|
||||
|
||||
.md-typeset a::after,
|
||||
a:not(.btn)::after {
|
||||
content: "";
|
||||
@@ -24,48 +15,81 @@ a:not(.btn):hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.md-typeset code {
|
||||
background-color: rgba(0, 26, 3, 0.226) !important;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.md-typeset .admonition code {
|
||||
background-color: #0000005e !important;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
border-left: 4px solid var(--crt-amber);
|
||||
background: linear-gradient(180deg, transparent 0%, rgba(0, 255, 0, 0.03) 100%);
|
||||
}
|
||||
|
||||
.admonition-note,
|
||||
.admonition-todo,
|
||||
.admonition-important {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-secondary) 0%,
|
||||
rgba(0, 255, 0, 0.05) 100%
|
||||
) !important;
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0, 255, 0, 0.05) 100%) !important;
|
||||
}
|
||||
|
||||
.admonition-tip,
|
||||
.admonition-hint,
|
||||
.admonition-important,
|
||||
.admonition-new {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-secondary) 0%,
|
||||
rgba(0, 255, 0, 0.05) 100%
|
||||
) !important;
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0, 255, 0, 0.05) 100%) !important;
|
||||
}
|
||||
|
||||
.admonition-caution,
|
||||
.admonition-warning {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-secondary) 0%,
|
||||
rgba(255, 165, 0, 0.08) 100%
|
||||
) !important;
|
||||
.admonition-warning,
|
||||
.admonition-failure {
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(255, 165, 0, 0.08) 100%) !important;
|
||||
}
|
||||
|
||||
.admonition-error,
|
||||
.admonition-failure {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-secondary) 0%,
|
||||
rgba(239, 68, 68, 0.08) 100%
|
||||
) !important;
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(239, 68, 68, 0.08) 100%) !important;
|
||||
}
|
||||
|
||||
.md-typeset__table th {
|
||||
background-color: var(--bg-primary) !important;
|
||||
border-bottom: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.md-typeset h1,
|
||||
.md-typeset h2,
|
||||
.md-typeset h3,
|
||||
.md-footer h4,
|
||||
.md-footer p {
|
||||
animation: none !important;
|
||||
.md-typeset h3 {
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__overlay .md-search__scrollwrap .md-search__form {
|
||||
background-color: var(--bg-card) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__overlay .md-search__scrollwrap .md-search__form input {
|
||||
background-color: var(--bg-card) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__overlay .md-search__results {
|
||||
background-color: var(--bg-card) !important;
|
||||
}
|
||||
|
||||
.md-footer {
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 100%);
|
||||
border-top: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
/* High contrast mode overrides */
|
||||
@media (prefers-contrast: high) {
|
||||
.admonition-note,
|
||||
.admonition-tip,
|
||||
@@ -74,35 +98,8 @@ a:not(.btn):hover::after {
|
||||
background: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.md-nav__link {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.md-typeset h1,
|
||||
.md-typeset h2,
|
||||
.md-typeset h3,
|
||||
.md-footer h4,
|
||||
.md-footer p {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
code, pre, kbd,
|
||||
.md-typeset code,
|
||||
.md-typeset pre,
|
||||
.md-typeset kbd {
|
||||
font-family: 'Fira Code VF', 'Fira Code', monospace;
|
||||
font-feature-settings: "calt" 1;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
|
||||
@supports not (font-variation-settings: normal) {
|
||||
code, pre, kbd,
|
||||
.md-typeset code,
|
||||
.md-typeset pre,
|
||||
.md-typeset kbd {
|
||||
font-family: 'Fira Code', monospace;
|
||||
nav[data-md-level="1"] .md-nav__link,
|
||||
nav[data-md-level="2"] .md-nav__link {
|
||||
color: var(--bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,351 +1,153 @@
|
||||
.md-footer {
|
||||
background-color: var(--bg-primary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
#md-content > :first-child:is(h1, h2, h3),
|
||||
.md-typeset__content > :first-child:is(h1, h2, h3) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.md-footer-meta {
|
||||
background-color: var(--bg-primary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.md-footer-meta__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.md-footer-copyright {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.md-footer-copyright a {
|
||||
color: var(--crt-amber);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.md-footer-copyright a:hover {
|
||||
color: var(--crt-green);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.md-social {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.75em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.md-social__link {
|
||||
display: inline-flex;
|
||||
.social,
|
||||
.social__link {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.75em !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.social svg,
|
||||
.social__link svg {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
#social,
|
||||
.social-container,
|
||||
.md-social-list {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
gap: 0.75em !important;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.md-social,
|
||||
[data-md-color-scheme="slate"] .md-typeset a[href*="social"],
|
||||
.md-social__link {
|
||||
display: inline-flex !important;
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.social span,
|
||||
.social__link span {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#social > :is(a[href], .md-icon),
|
||||
.social__link > :is(a[href], .md-icon) {
|
||||
display: inline-flex !important;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.md-social-list,
|
||||
.social-container {
|
||||
flex-wrap: nowrap !important;
|
||||
}
|
||||
|
||||
body .social,
|
||||
body .md-footer__social {
|
||||
display: inline-flex !important;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
opacity: 0.65;
|
||||
transition: opacity 0.15s ease;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.md-social__link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.md-social__link svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
fill: var(--crt-green);
|
||||
}
|
||||
|
||||
.md-footer__link {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.75rem;
|
||||
color: var(--crt-amber);
|
||||
}
|
||||
|
||||
.md-footer__link:hover {
|
||||
color: var(--crt-green);
|
||||
.social::before,
|
||||
.social::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 60em) {
|
||||
.md-social { gap: 0.5em; }
|
||||
.md-social__link svg { width: 18px; height: 18px; }
|
||||
.md-footer-meta__inner { flex-direction: column; align-items: flex-start; }
|
||||
.social {
|
||||
margin-right: 0.5em !important;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.md-footer *,
|
||||
.md-footer-meta * {
|
||||
animation: none !important;
|
||||
.social svg,
|
||||
.social__link svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-contrast: high) {
|
||||
.md-footer { border-top: 2px solid var(--text-primary); }
|
||||
.md-social__link { opacity: 1; }
|
||||
#social {
|
||||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-block {
|
||||
text-align: center;
|
||||
padding: 4em 1em 3em;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.md-footer h4,
|
||||
.md-footer p {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-eyebrow {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1em;
|
||||
.md-social {
|
||||
display: inline-flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
gap: 0.75em !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.hero-eyebrow::before {
|
||||
content: "// ";
|
||||
color: var(--accent-green);
|
||||
.md-social__link {
|
||||
display: inline-flex !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
white-space: nowrap !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-family: var(--font-main);
|
||||
font-size: clamp(2.2rem, 6vw, 3.6rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
color: var(--crt-green) !important;
|
||||
margin: 0 0 0.25em;
|
||||
text-shadow: 0 0 30px rgba(0, 240, 128, 0.25);
|
||||
.md-social__link span {
|
||||
font-size: 0.9rem !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.hero-block .hero-title::before {
|
||||
content: none !important;
|
||||
.md-social__link svg {
|
||||
display: inline-flex !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
color: var(--crt-amber);
|
||||
display: block;
|
||||
font-size: 0.65em;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.9;
|
||||
.md-footer-meta__inner {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
background-color: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.hero-tagline {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
max-width: 56ch;
|
||||
margin: 1.5em auto 2em;
|
||||
@media (max-width: 60em) {
|
||||
.md-social {
|
||||
gap: 0.5em !important;
|
||||
}
|
||||
|
||||
.hero-cta-row {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5em;
|
||||
.md-social__link svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.6em 1.6em;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
transition: all 0.18s ease;
|
||||
display: inline-block;
|
||||
.md-social__link span {
|
||||
font-size: 0.8rem !important;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.hero-cta--primary {
|
||||
background: rgba(0, 240, 128, 0.12);
|
||||
border: 1px solid var(--accent-green);
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.hero-cta--primary:hover {
|
||||
background: rgba(0, 240, 128, 0.22);
|
||||
box-shadow: 0 0 16px rgba(0, 240, 128, 0.25);
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.hero-cta--secondary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--crt-amber);
|
||||
}
|
||||
|
||||
.hero-cta--secondary:hover {
|
||||
border-color: var(--crt-amber);
|
||||
color: var(--crt-amber);
|
||||
background: rgba(231, 217, 163, 0.07);
|
||||
}
|
||||
|
||||
.alert-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75em;
|
||||
background: rgba(255, 76, 76, 0.07);
|
||||
border: 1px solid rgba(255, 76, 76, 0.28);
|
||||
border-left: 4px solid var(--crt-red);
|
||||
padding: 0.85em 1.2em;
|
||||
max-width: 70ch;
|
||||
margin: 2.5em auto 3em;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.88rem;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.alert-banner .alert-icon {
|
||||
color: var(--crt-red);
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.05em;
|
||||
}
|
||||
|
||||
.alert-banner a {
|
||||
color: var(--crt-red);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.alert-banner a:hover {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.alert-banner code {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.82em;
|
||||
background: rgba(255, 76, 76, 0.12);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 3px;
|
||||
color: #ff8888;
|
||||
}
|
||||
|
||||
.index-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.25em;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto 3.5em;
|
||||
}
|
||||
|
||||
.index-card {
|
||||
background: rgba(125, 70, 152, 0.10);
|
||||
border: 1px solid rgba(125, 70, 152, 0.35);
|
||||
border-radius: 6px;
|
||||
padding: 1.5em 1.4em 1.3em;
|
||||
transition: border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.index-card:hover {
|
||||
border-color: rgba(125, 70, 152, 0.70);
|
||||
box-shadow: 0 4px 20px rgba(125, 70, 152, 0.15);
|
||||
}
|
||||
|
||||
.index-card__title {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.9rem;
|
||||
color: var(--crt-amber) !important;
|
||||
margin: 0 0 0.5em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.index-card .index-card__title::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.index-card__body {
|
||||
font-size: 0.88rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.55;
|
||||
flex: 1;
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
.index-card__link {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.75rem;
|
||||
color: var(--crt-green);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 0.15s ease;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.index-card__link:hover {
|
||||
color: var(--accent-green);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.principles-strip {
|
||||
text-align: center;
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 2em 1em 3em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.principle-no {
|
||||
color: var(--crt-green);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.principle-sep {
|
||||
margin: 0 0.75em;
|
||||
color: var(--border-color);
|
||||
}
|
||||
|
||||
.quick-access-card {
|
||||
background: rgba(125, 70, 152, 0.10);
|
||||
border: 1px solid rgba(125, 70, 152, 0.35);
|
||||
border-radius: 6px;
|
||||
padding: 1.4em 1.3em 1.2em;
|
||||
transition: border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.quick-access-card:hover {
|
||||
border-color: rgba(125, 70, 152, 0.70);
|
||||
box-shadow: 0 4px 16px rgba(125, 70, 152, 0.15);
|
||||
}
|
||||
|
||||
.quick-access-card h3,
|
||||
.quick-access-card h4 {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.95rem;
|
||||
color: var(--crt-amber) !important;
|
||||
margin: 0 0 0.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quick-access-card p {
|
||||
font-size: 0.88rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.55;
|
||||
margin: 0 0 0.9em;
|
||||
}
|
||||
|
||||
.quick-access-card a {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.78rem;
|
||||
color: var(--crt-amber);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.quick-access-card a:hover {
|
||||
color: var(--crt-green);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@@ -1,152 +1,22 @@
|
||||
@font-face {
|
||||
font-family: 'Fira Code VF';
|
||||
src: url('../fonts/fira-code/fira-code-latin-ext-wght-normal.woff2') format('woff2');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F,
|
||||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
|
||||
U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Code VF';
|
||||
src: url('../fonts/fira-code/fira-code-latin-wght-normal.woff2') format('woff2');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-ext-400-normal.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F,
|
||||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
|
||||
U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-400-normal.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-ext-400-italic.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F,
|
||||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
|
||||
U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-400-italic.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-ext-700-normal.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F,
|
||||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
|
||||
U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-700-normal.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-ext-700-italic.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F,
|
||||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
|
||||
U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EB Garamond';
|
||||
src: url('../fonts/eb-garamond/eb-garamond-latin-700-italic.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
--md-primary-fg-color:#0000005e;
|
||||
--bg-color: #020307;
|
||||
--bg-primary: #060d06;
|
||||
--bg-secondary: #0a150a;
|
||||
--bg-card: #0f1f0f;
|
||||
--text-primary: #c5c5c5;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-muted: #6b7a6b;
|
||||
--accent-green: #00f080;
|
||||
--accent-green-dim: #039634;
|
||||
--crt-green: #00f080;
|
||||
--crt-amber: #e7d9a3;
|
||||
--crt-blue: #00d9ff;
|
||||
--crt-red: #ff4c4c;
|
||||
--accent-color: #039634;
|
||||
--border-color: #0b85005d;
|
||||
--terminal-green-glow: rgba(0, 255, 0, 0.15);
|
||||
--terminal-line-color: rgba(0, 255, 0, 0.30);
|
||||
--terminal-border-faint: rgba(0, 255, 0, 0.08);
|
||||
--status-success: #4ade80;
|
||||
--status-warning: #fbbf24;
|
||||
--status-error: #ef4444;
|
||||
--status-info: #60a5fa;
|
||||
--code-font: 'Fira Code VF', 'Fira Code', monospace;
|
||||
--font-main: 'EB Garamond', Georgia, serif;
|
||||
--md-primary-fg-color: #4052b500;
|
||||
--md-default-bg-color: rgba(0, 255, 0, 0.08);
|
||||
--font-main: 'EB Garamond', serif;
|
||||
--code-font: 'Office Code Pro', serif;
|
||||
}
|
||||
|
||||
|
||||
*, *::before, *::after {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.no-js .md-sidebar {
|
||||
align-self: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
@@ -158,7 +28,7 @@ body {
|
||||
|
||||
h1 {
|
||||
color: var(--crt-green);
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
@@ -171,21 +41,26 @@ h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
animation: none !important;
|
||||
background: none !important;
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -210,37 +85,14 @@ tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.no-js .md-sidebar {
|
||||
align-self: auto;
|
||||
}
|
||||
|
||||
[data-md-component="content"] .md-content__inner,
|
||||
.md-typeset {
|
||||
}
|
||||
|
||||
body:has([data-md-url*="/guide/"]) .md-typeset p,
|
||||
body:has([data-md-url*="/guide/"]) .md-typeset li,
|
||||
body:has([data-md-url*="/guide/"]) .md-typeset dt,
|
||||
body:has([data-md-url*="/guide/"]) .md-typeset dd,
|
||||
body:has([data-md-url*="/guide/"]) .md-typeset blockquote p {
|
||||
font-family: var(--font-main);
|
||||
font-size: 18px;
|
||||
line-height: 1.75;
|
||||
color: var(--text-primary);
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 20px 0;
|
||||
filter: brightness(1.1);
|
||||
animation: none !important;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@@ -254,8 +106,7 @@ body:has([data-md-url*="/guide/"]) .md-typeset blockquote p {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.card,
|
||||
table th {
|
||||
.card, table th {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
background-color: var(--bg-card) !important;
|
||||
@@ -270,217 +121,3 @@ body:has([data-md-url*="/guide/"]) .md-typeset blockquote p {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] {
|
||||
--bg-color: #ffffff;
|
||||
--bg-primary: #f8f8f8;
|
||||
--bg-secondary: #f0f0f0;
|
||||
--bg-card: #ececec;
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #2c2c2c;
|
||||
--text-muted: #555555;
|
||||
--accent-green: #006633;
|
||||
--accent-green-dim: #008844;
|
||||
--crt-green: #005522;
|
||||
--crt-amber: #4a3800;
|
||||
--crt-blue: #003d6b;
|
||||
--crt-red: #8b0000;
|
||||
--border-color: #cccccc;
|
||||
--terminal-green-glow: rgba(0, 102, 51, 0.08);
|
||||
--terminal-line-color: #bbbbbb;
|
||||
--terminal-border-faint: #e0e0e0;
|
||||
--status-success: #1a5c2e;
|
||||
--status-warning: #7a4f00;
|
||||
--status-error: #8b0000;
|
||||
--status-info: #003d6b;
|
||||
--md-default-bg-color: #ffffff;
|
||||
--md-default-fg-color: #1a1a1a;
|
||||
--md-primary-fg-color: #1a1a1a;
|
||||
--md-primary-bg-color: #ffffff;
|
||||
--md-typeset-color: #1a1a1a;
|
||||
--md-typeset-a-color: #003d6b;
|
||||
--md-code-bg-color: #f4f4f4;
|
||||
--md-code-fg-color: #1a1a1a;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] body::before,
|
||||
[data-md-color-scheme="default"] body::after {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
background: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] h1,
|
||||
[data-md-color-scheme="default"] h2,
|
||||
[data-md-color-scheme="default"] h3,
|
||||
[data-md-color-scheme="default"] h4,
|
||||
[data-md-color-scheme="default"] h5,
|
||||
[data-md-color-scheme="default"] h6,
|
||||
[data-md-color-scheme="default"] .md-typeset h1,
|
||||
[data-md-color-scheme="default"] .md-typeset h2,
|
||||
[data-md-color-scheme="default"] .md-typeset h3,
|
||||
[data-md-color-scheme="default"] .md-typeset h4,
|
||||
[data-md-color-scheme="default"] .md-typeset h5,
|
||||
[data-md-color-scheme="default"] .md-typeset h6 {
|
||||
color: var(--crt-green) !important;
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] body,
|
||||
[data-md-color-scheme="default"] .md-typeset {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] a,
|
||||
[data-md-color-scheme="default"] .md-typeset a {
|
||||
color: var(--crt-blue);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] img,
|
||||
[data-md-color-scheme="default"] .md-typeset img {
|
||||
filter: none !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] pre,
|
||||
[data-md-color-scheme="default"] .md-typeset pre, {
|
||||
background: #f4f4f4 !important;
|
||||
color: #1a1a1a !important;
|
||||
border-left-color: #888888 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] code,
|
||||
[data-md-color-scheme="default"] .md-typeset code {
|
||||
background: #eeeeee !important;
|
||||
color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-header {
|
||||
background-color: #1a1a1a;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-header__button,
|
||||
[data-md-color-scheme="default"] .md-header__topic,
|
||||
[data-md-color-scheme="default"] .md-header__title,
|
||||
[data-md-color-scheme="default"] .md-header__ellipsis {
|
||||
color: #e8e8e8 !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-header__topic::before {
|
||||
color: #a8d8a8;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-tabs {
|
||||
background-color: #2a2a2a;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-tabs__link {
|
||||
color: #cccccc !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-tabs__link--active,
|
||||
[data-md-color-scheme="default"] .md-tabs__link:hover {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-tabs__link--active::after {
|
||||
background-color: #a8d8a8;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-search__input {
|
||||
background-color: rgba(255,255,255,0.12);
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-search__input::placeholder {
|
||||
color: rgba(232,232,232,0.55);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .md-source {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
|
||||
[data-md-color-scheme="default"] .admonition {
|
||||
background: #f8f8f8 !important;
|
||||
border-left-color: var(--accent-green) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] .admonition-title {
|
||||
background: #eeeeee !important;
|
||||
color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] th {
|
||||
background-color: #eeeeee !important;
|
||||
border-bottom-color: var(--crt-amber) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] td,
|
||||
[data-md-color-scheme="default"] tr {
|
||||
border-color: #cccccc !important;
|
||||
color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
background:
|
||||
linear-gradient(rgba(18, 32, 12, 0) 50%, rgba(0, 20, 0, 0.10) 50%),
|
||||
linear-gradient(90deg, rgba(12, 36, 18, 0) 50%, rgba(0, 16, 0, 0.08) 50%);
|
||||
background-size: 100% 4px, 6px 100%;
|
||||
animation: scanline 4ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scanline {
|
||||
0% { background-position: 0 0, 0 0; }
|
||||
100% { background-position: 0 4px, 6px 100%; }
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9997;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.30) 100%
|
||||
);
|
||||
animation: phosphorDecay 8s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes phosphorDecay {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
[data-md-color-scheme="slate"] body::before {
|
||||
background-size: 100% 2px, 3px 100%;
|
||||
animation-duration: 6ms;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-md-color-scheme="slate"] body::before,
|
||||
[data-md-color-scheme="slate"] body::after {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] img,
|
||||
[data-md-color-scheme="slate"] .md-typeset img {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.command-palette {
|
||||
font-family: var(--code-font);
|
||||
font-family: var(--font-main);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-align: center;
|
||||
@@ -7,11 +7,13 @@
|
||||
padding: 0.5em;
|
||||
border-top: 1px dashed var(--terminal-line-color);
|
||||
border-bottom: 1px dashed var(--terminal-line-color);
|
||||
background: linear-gradient(90deg,
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(0, 255, 0, 0.03) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.command-palette::before,
|
||||
@@ -21,6 +23,11 @@
|
||||
animation: cursorBlink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes cursorBlink {
|
||||
0%, 49% { opacity: 1; }
|
||||
50%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.headerlink {
|
||||
color: var(--crt-green);
|
||||
font-size: 0.8rem;
|
||||
@@ -28,14 +35,184 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.terminal-session {
|
||||
font-family: var(--code-font);
|
||||
text-align: center;
|
||||
padding: 0.25em 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.system-status {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.4em 1em;
|
||||
border-radius: 4px;
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-secondary);
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 12px rgba(0, 255, 0, 0.1);
|
||||
}
|
||||
|
||||
.system-status .status-dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--status-success);
|
||||
margin-right: 0.5em;
|
||||
box-shadow: 0 0 4px var(--status-success);
|
||||
}
|
||||
|
||||
.terminal-corner {
|
||||
position: fixed;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
border-top: 2px solid var(--accent-green);
|
||||
border-left: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.top-right {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
border-top: 2px solid var(--accent-green);
|
||||
border-right: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.bottom-left {
|
||||
bottom: 60px;
|
||||
left: 20px;
|
||||
border-bottom: 2px solid var(--accent-green);
|
||||
border-left: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.hud-line {
|
||||
position: fixed;
|
||||
left: 5%;
|
||||
top: 30%;
|
||||
right: 5%;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(0, 255, 0, 0.1) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hud-coordinate {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
left: 20px;
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.65rem;
|
||||
color: var(--bg-color);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.hud-coordinate::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 50%;
|
||||
width: 20px;
|
||||
height: 1px;
|
||||
background: var(--terminal-line-color);
|
||||
}
|
||||
|
||||
.data-stream {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.6rem;
|
||||
color: var(--status-success);
|
||||
opacity: 0.5;
|
||||
z-index: 9999;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.md-nav__link.nav-primary,
|
||||
.md-nav__link.nav-secondary {
|
||||
font-family: var(--code-font);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.admonition-terminal {
|
||||
border-left: 4px solid var(--accent-green);
|
||||
background: linear-gradient(180deg,
|
||||
transparent 0%,
|
||||
rgba(0, 255, 0, 0.05) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.admonition-terminal .admonition-title {
|
||||
font-family: var(--code-font);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
background: var(--terminal-green-glow);
|
||||
}
|
||||
|
||||
.file-tree {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.8rem;
|
||||
padding-left: 1em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.system-metadata {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.65rem;
|
||||
text-align: right;
|
||||
color: var(--text-muted);
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
pre.highlight {
|
||||
box-shadow: inset 0 0 20px rgba(20, 5, 30, 0.6);
|
||||
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
pre.highlight .hll {
|
||||
background-color: rgba(0, 255, 0, 0.05);
|
||||
}
|
||||
|
||||
@keyframes matrixFall {
|
||||
0% { transform: translateY(-100vh); opacity: 0; }
|
||||
10% { opacity: 0.5; }
|
||||
90% { opacity: 0.5; }
|
||||
100% { transform: translateY(100vh); opacity: 0; }
|
||||
}
|
||||
|
||||
.matrix-char {
|
||||
position: fixed;
|
||||
color: var(--accent-green);
|
||||
font-family: var(--code-font);
|
||||
font-size: 1rem;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
animation: matrixFall 15s linear infinite;
|
||||
}
|
||||
|
||||
.palette-section {
|
||||
margin-top: 4em;
|
||||
}
|
||||
|
||||
.palette-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
@@ -69,6 +246,52 @@ pre.highlight .hll {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.network-viz {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.network-viz::before,
|
||||
.network-viz::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.network-viz::before {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: networkSpin 20s linear infinite;
|
||||
}
|
||||
|
||||
.network-viz::after {
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
animation: networkSpin 15s linear infinite reverse;
|
||||
}
|
||||
|
||||
@keyframes networkSpin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.terminal-decorator {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.6rem;
|
||||
color: var(--text-muted);
|
||||
z-index: 9999;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.system-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
@@ -86,228 +309,36 @@ pre.highlight .hll {
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body::before,
|
||||
body::after,
|
||||
.command-palette::before,
|
||||
.command-palette::after,
|
||||
.system-notification {
|
||||
.matrix-char,
|
||||
.network-viz::before,
|
||||
.network-viz::after {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.donate-goals-block {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid var(--crt-amber);
|
||||
border-radius: 6px;
|
||||
padding: 1.5em 2em;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.donate-goals-title {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--crt-amber);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
.donate-goals-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.donate-goal {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.92rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6em;
|
||||
padding: 0.35em 0;
|
||||
}
|
||||
|
||||
.donate-goal-icon {
|
||||
font-size: 1rem;
|
||||
width: 1.2em;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.donate-goal--done {
|
||||
color: var(--status-success);
|
||||
text-decoration: line-through;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.donate-goal--open {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.donate-goal--open .donate-goal-icon {
|
||||
color: var(--crt-amber);
|
||||
}
|
||||
|
||||
.donate-stat-row {
|
||||
display: flex;
|
||||
gap: 1.5em;
|
||||
flex-wrap: wrap;
|
||||
margin: 1em 0 1.5em;
|
||||
}
|
||||
|
||||
.donate-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2em;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 0.75em 1.25em;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.donate-stat-label {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.donate-stat-value {
|
||||
font-family: var(--code-font);
|
||||
font-size: 1.15rem;
|
||||
font-weight: bold;
|
||||
color: var(--crt-amber);
|
||||
}
|
||||
|
||||
.donate-address-block {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5em;
|
||||
margin: 1em 0 2em;
|
||||
}
|
||||
|
||||
.donate-address-label {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
.donate-address {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
font-size: 0.78rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 0.6em 0.9em;
|
||||
color: var(--accent-green);
|
||||
user-select: all;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.donate-qr {
|
||||
margin-top: 1.25em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.donate-qr img {
|
||||
max-width: 150px;
|
||||
height: auto;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.donate-qr--row {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.donate-qr figure {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
}
|
||||
|
||||
.donate-qr figcaption {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.donate-stat-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
.donate-address {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.donate-qr--row {
|
||||
flex-direction: column;
|
||||
@media screen and (min-width: 76.25em) {
|
||||
.md-main__inner {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
.pgp-fingerprint {
|
||||
display: block;
|
||||
margin-top: 0.75em;
|
||||
word-break: break-all;
|
||||
font-size: 0.78rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 0.5em 0.75em;
|
||||
color: var(--crt-green);
|
||||
user-select: all;
|
||||
cursor: text;
|
||||
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;
|
||||
@media screen {
|
||||
[data-md-color-scheme="slate"] {
|
||||
--md-default-bg-color: var(--bg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +1,185 @@
|
||||
:root {
|
||||
--crt-amber: #e7d9a3;
|
||||
--crt-blue: #00d9ff;
|
||||
--crt-green: #00f080;
|
||||
--crt-red: #ff4c4c;
|
||||
--phosphor-dim: #1a3a1a;
|
||||
--status-success: #4ade80;
|
||||
--status-warning: #fbbf24;
|
||||
--status-error: #ef4444;
|
||||
--status-info: #60a5fa;
|
||||
--terminal-green-glow: rgba(0, 255, 0, 0.15);
|
||||
--terminal-line-color: rgba(0, 255, 0, 0.3);
|
||||
--terminal-border-faint: rgba(0, 255, 0, 0.08);
|
||||
--md-default-bg-color: rgba(0, 255, 0, 0.08);
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
|
||||
background: linear-gradient(
|
||||
rgba(18, 32, 12, 0) 50%,
|
||||
rgba(0, 20, 0, 0.1) 50%
|
||||
),
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(12, 36, 18, 0) 50%,
|
||||
rgba(0, 16, 0, 0.08) 50%
|
||||
);
|
||||
|
||||
background-size: 100% 4px, 6px 100%;
|
||||
animation: scanline 4ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scanline {
|
||||
0% { background-position: 0 0, 0 0; }
|
||||
100% { background-position: 0 4px, 6px 100%; }
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.3) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes cursorBlink {
|
||||
0%, 49% { opacity: 1; }
|
||||
50%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.md-header__topic::before {
|
||||
.md-header__topic::before,
|
||||
body > *:first-child::before {
|
||||
content: "λ ";
|
||||
color: var(--crt-amber);
|
||||
font-family: var(--code-font);
|
||||
margin-right: 0.25em;
|
||||
animation: cursorBlink 1s step-end infinite;
|
||||
color: var(--crt-amber);
|
||||
font-family: var(--font-main);
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
@keyframes terminalLoad {
|
||||
from { opacity: 0; transform: translateY(25px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
.terminal-window {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-primary) 0%,
|
||||
var(--bg-card) 100%
|
||||
);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5em;
|
||||
position: relative;
|
||||
box-shadow: 0 8px 32px rgba(0, 255, 0, 0.1);
|
||||
}
|
||||
|
||||
.md-typeset h1 {
|
||||
animation: terminalLoad 200ms ease-out forwards;
|
||||
.terminal-window::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
var(--accent-green),
|
||||
transparent
|
||||
);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
strong {
|
||||
text-shadow: 0 0 1px var(--border-color);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: var(--crt-amber);
|
||||
font-family: var(--font-main);
|
||||
font-weight: bold;
|
||||
margin-right: 0.02em;}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
color: var(--crt-amber);
|
||||
font-family: var(--font-main);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-right: 0.02em;
|
||||
}
|
||||
|
||||
strong,
|
||||
p strong {
|
||||
p strong,
|
||||
strong {
|
||||
text-shadow: 0 0 1px var(--border-color);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: rgba(30, 10, 40, 0.85);
|
||||
border-left: 3px solid rgba(125, 70, 152, 0.55);
|
||||
box-shadow: inset 0 0 0 1px rgba(125, 70, 152, 0.12);
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-color) 0%,
|
||||
#0d150d 100%
|
||||
);
|
||||
border-left: 3px solid var(--accent-green);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
pre[role="presentation"] [data-line] {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--code-font);
|
||||
font-family: var(--font-main);
|
||||
user-select: none;
|
||||
min-width: 3em;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
pre code .token-keyword { color: var(--crt-blue); font-weight: bold; }
|
||||
pre code .token-string { color: var(--status-success); }
|
||||
pre code .token-comment { color: var(--text-muted); font-style: italic; }
|
||||
pre code .token-keyword {
|
||||
color: var(--crt-blue);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre code .token-string {
|
||||
color: var(--status-success);
|
||||
}
|
||||
|
||||
pre code .token-comment {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
kbd,
|
||||
.kbd,
|
||||
kbd,
|
||||
[data-type="kbd"] {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 0.2em 0.5em;
|
||||
font-family: var(--code-font);
|
||||
font-family: var(--font-main);
|
||||
font-size: 0.85em;
|
||||
color: var(--accent-green);
|
||||
text-shadow: 0 1px 2px rgba(0, 255, 0, 0.10);
|
||||
text-shadow: 0 1px 2px rgba(0, 255, 0, 0.1);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre {
|
||||
font-family:var(--font-main)
|
||||
}
|
||||
|
||||
kbd:hover {
|
||||
border-color: var(--accent-green);
|
||||
box-shadow: 0 2px 8px rgba(0, 255, 0, 0.20);
|
||||
box-shadow: 0 2px 8px rgba(0, 255, 0, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.admonition {
|
||||
border-left: 4px solid var(--accent-green);
|
||||
background: linear-gradient(180deg, transparent 0%, rgba(0, 255, 0, 0.03) 100%);
|
||||
}
|
||||
|
||||
.admonition-title {
|
||||
font-family: var(--code-font);
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85em;
|
||||
color: var(--accent-green);
|
||||
background: var(--terminal-green-glow);
|
||||
padding: 0.4em 0.8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.md-typeset details.admonition {
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.md-typeset table:not([class]) {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.md-typeset__table th {
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 2px solid var(--crt-amber);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.md-typeset__table tr:hover {
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid var(--accent-green);
|
||||
margin: 1.5em 0;
|
||||
padding-left: 1.5em;
|
||||
background: linear-gradient(90deg, rgba(0, 255, 0, 0.05) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
blockquote p:first-of-type::before {
|
||||
content: "› ";
|
||||
color: var(--accent-green);
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
|
||||
.md-typeset mark,
|
||||
.highlight-mark {
|
||||
background-color: rgba(255, 235, 0, 0.30);
|
||||
color: inherit;
|
||||
padding: 0.1em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -147,35 +192,115 @@ blockquote p:first-of-type::before {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-success { color: var(--status-success); background: rgba(74, 222, 128, 0.10); border: 1px solid rgba(74, 222, 128, 0.30); }
|
||||
.status-warning { color: var(--status-warning); background: rgba(251, 191, 36, 0.10); border: 1px solid rgba(251, 191, 36, 0.30); }
|
||||
.status-error { color: var(--status-error); background: rgba(239, 68, 68, 0.10); border: 1px solid rgba(239, 68, 68, 0.30); }
|
||||
.status-info { color: var(--status-info); background: rgba(96, 165, 250, 0.10); border: 1px solid rgba(96, 165, 250, 0.30); }
|
||||
|
||||
.terminal-window {
|
||||
background: linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-card) 100%);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5em;
|
||||
position: relative;
|
||||
box-shadow: 0 8px 32px rgba(0, 255, 0, 0.10);
|
||||
.status-success {
|
||||
color: var(--status-success);
|
||||
background: rgba(74, 222, 128, 0.1);
|
||||
border: 1px solid rgba(74, 222, 128, 0.3);
|
||||
}
|
||||
|
||||
.terminal-window::before {
|
||||
.status-warning {
|
||||
color: var(--status-warning);
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||
}
|
||||
|
||||
.status-error {
|
||||
color: var(--status-error);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.status-info {
|
||||
color: var(--status-info);
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
border: 1px solid rgba(96, 165, 250, 0.3);
|
||||
}
|
||||
|
||||
.admonition {
|
||||
border-left: 4px solid var(--accent-green);
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.admonition-title {
|
||||
font-family: var(--font-main);
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85em;
|
||||
color: var(--accent-green);
|
||||
background: transparent !important;
|
||||
padding: 0.4em 0.8em;
|
||||
}
|
||||
|
||||
.md-typeset details.admonition summary,
|
||||
.md-typeset details > .admonition-title {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.md-typeset details.admonition {
|
||||
border: none;
|
||||
background: transparent !important;
|
||||
border-radius: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.md-typeset a.headerlink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Force all admonition summaries to be transparent */
|
||||
details[open] > summary,
|
||||
summary[open] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.md-typeset table:not([class]) {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.md-typeset__table th {
|
||||
background: linear-gradient(180deg,
|
||||
var(--bg-secondary) 0%,
|
||||
var(--bg-card) 100%
|
||||
);
|
||||
border-bottom: 2px solid var(--accent-green);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.md-typeset__table tr:hover {
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
blockquote, .admonition-blockquote {
|
||||
border-left: 3px solid var(--accent-green);
|
||||
margin: 1.5em 0;
|
||||
padding-left: 1.5em;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
blockquote p:first-of-type::before {
|
||||
content: "› ";
|
||||
color: var(--accent-green);
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
|
||||
.md-typeset a::after,
|
||||
a:not(.btn)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, var(--accent-green), transparent);
|
||||
opacity: 0.5;
|
||||
bottom: -3px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: var(--bg-card);
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg-primary); }
|
||||
::-webkit-scrollbar-thumb { background: var(--terminal-green-glow); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--accent-green-dim); }
|
||||
.md-typeset a:hover::after,
|
||||
a:not(.btn):hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--code-font);
|
||||
@@ -185,10 +310,43 @@ blockquote p:first-of-type::before {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.md-typeset .admonition-title,
|
||||
div.admonition > div:first-of-type::before {
|
||||
font-family: var(--code-font);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.md-typeset mark,
|
||||
.highlight-mark {
|
||||
background-color: rgba(255, 235, 0, 0.3);
|
||||
color: inherit;
|
||||
padding: 0.1em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--terminal-green-glow);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-green-dim);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
[data-md-color-scheme="slate"] body::before {
|
||||
body::before {
|
||||
background-size: 100% 2px, 3px 100%;
|
||||
animation-duration: 6ms;
|
||||
animation-duration: 6s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,11 +355,163 @@ blockquote p:first-of-type::before {
|
||||
--accent-green: #fff;
|
||||
--bg-primary: #000;
|
||||
}
|
||||
|
||||
body::before {
|
||||
background-size: 100% 3px, 4px 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.md-header__topic::before,
|
||||
.md-typeset h1 {
|
||||
animation: none;
|
||||
@keyframes terminalLoad {
|
||||
0% { opacity: 0; transform: translateY(25px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.md-typeset h1,
|
||||
.md-content {
|
||||
animation: terminalLoad 200ms ease-out forwards;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9997;
|
||||
background: radial-gradient(
|
||||
circle at center,
|
||||
rgba(0, 255, 0, 0.03) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
animation: phosphorDecay 8s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes phosphorDecay {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
body::after::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
background: radial-gradient(
|
||||
circle at center,
|
||||
transparent 40%,
|
||||
rgba(0, 0, 0, 0.6) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* Ensure TOC nav elements can display properly with high z-index */
|
||||
.md-nav {
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
|
||||
.md-nav--secondary {
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
|
||||
/* Ensure TOC nav links always have readable text */
|
||||
.md-nav__link,
|
||||
.md-nav__item {
|
||||
color: var(--crt-green) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Prevent TOC nested items from inheriting bad backgrounds */
|
||||
.md-nav__list > .md-nav__nested {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Smooth TOC expansion/animation */
|
||||
.md-nav__inner {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.md-nav__item.is-collapsed .md-nav__link > .md-nav__icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Ensure nested nav items have proper z-index on hover/focus */
|
||||
.md-nav__link:focus,
|
||||
.md-nav__link:hover {
|
||||
outline-color: var(--crt-green) !important;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* Material for MkDocs TOC color overrides */
|
||||
.md-nav__link .md-nav__icon {
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.md-nav__item.is-parent > .md-nav__link > .md-nav__icon {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.md-nav__item.is-parent > .md-nav__link:hover > .md-nav__icon,
|
||||
.md-nav__item.is-parent > .md-nav__link[aria-expanded="true"] > .md-nav__icon {
|
||||
transform: rotate(90deg);
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.terminal-banner {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.75em;
|
||||
letter-spacing: 0.2em;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 1em 0;
|
||||
border-top: 1px dashed var(--terminal-line-color);
|
||||
border-bottom: 1px dashed var(--terminal-line-color);
|
||||
margin: 3em 0;
|
||||
}
|
||||
|
||||
.holographic-grid {
|
||||
background-image:
|
||||
linear-gradient(var(--terminal-border-faint) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--terminal-border-faint) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.holographic-grid::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(
|
||||
rgba(0, 255, 0, 0.03) 10px,
|
||||
transparent 10px
|
||||
),
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 255, 0, 0.03) 10px,
|
||||
transparent 10px
|
||||
);
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
|
||||
/* Ensure TOC section titles are visible */
|
||||
.md-nav__title,
|
||||
.md-nav__list > .md-nav__item > .md-nav__link > .md-nav__content {
|
||||
color: var(--crt-green) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Handle open/close animation properly */
|
||||
.md-typeset details[open] {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.md-typeset summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.md-sidebar {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.md-sidebar__scrollwrap {
|
||||
@@ -7,126 +7,212 @@
|
||||
}
|
||||
|
||||
.md-header__topic {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.62rem;
|
||||
font-family: var(--font-main);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: -0.02em;
|
||||
opacity: 0.75;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.md-header__title {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.md-typeset h1::before {
|
||||
content: "λ ";
|
||||
.md-typeset h1::before,
|
||||
div[data-md-component="content"] > h1::before {
|
||||
content: 'λ ';
|
||||
color: var(--text-primary);
|
||||
font-family: var(--code-font);
|
||||
font-family: 'JetBrains Mono';
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.md-nav__link {
|
||||
font-family: var(--code-font);
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--crt-amber);
|
||||
transition: color 0.15s ease;
|
||||
.md-typeset h2::before,
|
||||
div[data-md-component="content"] > h2::before {
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono';
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.md-nav--primary > .md-nav__list > .md-nav__item > .md-nav__link {
|
||||
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[aria-current] {
|
||||
color: var(--accent-green) !important;
|
||||
font-weight: 600;
|
||||
border-left: 2px solid var(--accent-green);
|
||||
padding-left: 0.5em;
|
||||
background-color: rgba(0, 255, 0, 0.06);
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.md-nav__link:hover {
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.md-nav__link:focus-visible {
|
||||
outline: 1px solid var(--accent-green);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] {
|
||||
--md-default-bg-color: var(--bg-primary);
|
||||
.md-typeset h3::before,
|
||||
div[data-md-component="content"] > h3::before {
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono';
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.md-header__button.md-icon {
|
||||
padding: 0.5em;
|
||||
animation: none;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.md-header__inner {
|
||||
flex-wrap: nowrap;
|
||||
nav[data-md-level="1"] .md-nav__link,
|
||||
nav[data-md-level="2"] .md-nav__link {
|
||||
font-family: var(--font-main);
|
||||
font-size: 0.9rem;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.md-header__button,
|
||||
.md-header__source,
|
||||
.toggle-container--header {
|
||||
flex-shrink: 0;
|
||||
.md-sidebar.nav-primary > nav > .md-nav__list > li > .md-nav__link {
|
||||
font-weight: 600;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.md-search {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a {
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 76.1875em) {
|
||||
.toggle-container--header {
|
||||
width: 56px;
|
||||
margin: 0 4px;
|
||||
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a:hover,
|
||||
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a.md-nav--opened {
|
||||
color: var(--crt-red);
|
||||
}
|
||||
|
||||
.toggle-container--header .toggle-wrap {
|
||||
height: 28px;
|
||||
}
|
||||
.md-sidebar__section.md-nav__inner > .md-nav__title {
|
||||
font-family: var(--text-primary);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
padding: 0.75em 1.5em 0.25em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 44.9375em) {
|
||||
.toggle-container--header {
|
||||
width: 44px;
|
||||
.md-sidebar__inner > .md-nav__title {
|
||||
font-family: 'EB Garamond', serif;
|
||||
font-size: 0.8rem;
|
||||
color: var(--crt-red);
|
||||
padding: 1em 1.5em;
|
||||
}
|
||||
|
||||
.md-sidebar__inner > .md-nav__title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
/* Ensure integrated TOC nav is always visible */
|
||||
.md-nav__link,
|
||||
.md-nav__item {
|
||||
color: var(--crt-green) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.md-nav__list > .md-nav__nested > li > a {
|
||||
color: var(--crt-green) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.md-nav__list > .md-nav__nested > li > a:hover,
|
||||
.md-nav__list > .md-nav__nested > li > a.md-nav--opened,
|
||||
.md-nav__list > .md-nav__nested > li > a.is-active {
|
||||
color: var(--crt-green) !important;
|
||||
}
|
||||
|
||||
/* Ensure nested TOC items are visible */
|
||||
.md-typeset details summary,
|
||||
.md-typeset details[open] {
|
||||
padding: 0.5em;
|
||||
margin: 0.25em 0;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.md-typeset details summary::-webkit-details-marker {
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
/* Ensure admonition summaries are always transparent */
|
||||
.md-typeset details.admonition summary,
|
||||
summary[data-type="admonition"],
|
||||
.summary-admonition {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Smooth expansion of nested nav items */
|
||||
.md-nav__inner {
|
||||
transition: max-height 0.3s ease-out;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Ensure all nested TOC links are readable */
|
||||
.md-nav__list > li > .md-nav__link,
|
||||
.md-nav__list > li.is-parent > .md-nav__nested > li > .md-nav__link {
|
||||
color: var(--crt-green) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Active/expanded states for integrated TOC */
|
||||
.md-nav__link[aria-current="page"],
|
||||
.md-nav__item.is-active > .md-nav__link,
|
||||
.md-nav__list > li.is-parent > .md-nav__nested > li > a.md-nav--opened {
|
||||
color: var(--crt-green) !important;
|
||||
}
|
||||
|
||||
/* Fix for expanded nested lists */
|
||||
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.md-typeset {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.md-typeset a {
|
||||
color: var(--crt-green);
|
||||
word-break: break-word;
|
||||
.quick-access,
|
||||
div[data-md-include]:has(h3:contains("Quick Access")) {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: 3em;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
.md-typeset .md-typeset__grid,
|
||||
div[data-md-type="grid"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5em;
|
||||
}
|
||||
|
||||
.quick-access-card,
|
||||
.md-typeset__section:not(:last-child) {
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 1.25em;
|
||||
}
|
||||
|
||||
.quick-access-card h3 {
|
||||
font-family: var(--code-font);
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
color: var(--crt-green);
|
||||
}
|
||||
|
||||
.quick-access-card > p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.quick-access-card .md-icon,
|
||||
.quick-access-card svg {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
color: var(--crt-green);
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
.quick-access-card a:hover {
|
||||
text-decoration-color: var(--crt-green);
|
||||
}
|
||||
|
||||
.md-footer-meta {
|
||||
background-color: var(--bg-primary) !important;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
pre[role="presentation"] [data-line] {
|
||||
padding-left: calc(var(--per-page) * 0.75em + 1.25em);
|
||||
}
|
||||
|
||||
.md-typeset a {
|
||||
color:var(--crt-green);
|
||||
word-break:break-word
|
||||
}
|
||||
|
||||
.md-typeset h1,
|
||||
@@ -135,35 +221,7 @@
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
.md-footer-meta {
|
||||
background-color: var(--bg-primary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.md-top {
|
||||
background-color: var(--bg-card) !important;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
@media print {
|
||||
|
||||
/* Reset all custom properties to light values */
|
||||
:root,
|
||||
[data-md-color-scheme],
|
||||
[data-md-color-scheme="slate"] {
|
||||
--bg-color: #ffffff;
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f8f8;
|
||||
--bg-card: #f4f4f4;
|
||||
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #2c2c2c;
|
||||
--text-muted: #555555;
|
||||
|
||||
--accent-green: #005c2e;
|
||||
--accent-green-dim: #007a3d;
|
||||
|
||||
--crt-green: #005c2e;
|
||||
--crt-amber: #5c4000;
|
||||
--crt-blue: #00487a;
|
||||
--crt-red: #8b0000;
|
||||
|
||||
--border-color: #cccccc;
|
||||
--terminal-green-glow: transparent;
|
||||
--terminal-line-color: #aaaaaa;
|
||||
--terminal-border-faint: #dddddd;
|
||||
|
||||
--status-success: #1a5c2e;
|
||||
--status-warning: #7a4f00;
|
||||
--status-error: #8b0000;
|
||||
--status-info: #003d6b;
|
||||
|
||||
--md-default-bg-color: #ffffff;
|
||||
--md-default-fg-color: #1a1a1a;
|
||||
--md-primary-fg-color: #1a1a1a;
|
||||
--md-typeset-color: #1a1a1a;
|
||||
--md-typeset-a-color: #00487a;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background: #ffffff !important;
|
||||
color: #1a1a1a !important;
|
||||
font-family: 'EB Garamond', Georgia, 'Times New Roman', serif !important;
|
||||
font-size: 11pt !important;
|
||||
line-height: 1.65 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::after {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
background: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 28mm 28mm 28mm;
|
||||
}
|
||||
|
||||
@page :first {
|
||||
margin-top: 30mm;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
text-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Hide footnotes and inline reference superscripts in PDF output */
|
||||
.footnote,
|
||||
.md-typeset .footnote,
|
||||
section.footnotes,
|
||||
.md-typeset section.footnotes,
|
||||
[id^="fn:"],
|
||||
.md-typeset [id^="fn:"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide inline footnote back-reference superscripts [^N] */
|
||||
sup.footnote-ref,
|
||||
.md-typeset sup.footnote-ref,
|
||||
a.footnote-backref,
|
||||
.md-typeset a.footnote-backref,
|
||||
sup > a[href^="#fn"],
|
||||
.md-typeset sup > a[href^="#fn"],
|
||||
sup > a[href^="#fnref"],
|
||||
.md-typeset sup > a[href^="#fnref"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.md-header,
|
||||
.md-footer,
|
||||
.md-sidebar,
|
||||
.md-nav,
|
||||
.md-search,
|
||||
.md-tabs,
|
||||
.md-social,
|
||||
.md-top,
|
||||
[data-md-component="header"],
|
||||
[data-md-component="sidebar"],
|
||||
[data-md-component="footer"],
|
||||
[data-md-component="toc"],
|
||||
.headerlink,
|
||||
.md-content__button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.md-main,
|
||||
.md-main__inner,
|
||||
.md-content {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.md-content__inner {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 0 0 12mm !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.md-typeset h1,
|
||||
.md-typeset h2,
|
||||
.md-typeset h3,
|
||||
.md-typeset h4,
|
||||
.md-typeset h5,
|
||||
.md-typeset h6 {
|
||||
color: #1a1a1a !important;
|
||||
font-family: 'EB Garamond', Georgia, serif !important;
|
||||
font-weight: 700 !important;
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.md-typeset h1::before,
|
||||
.md-typeset h2::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
h1, .md-typeset h1 { font-size: 20pt !important; margin: 18pt 0 6pt !important; }
|
||||
h2, .md-typeset h2 { font-size: 15pt !important; margin: 14pt 0 5pt !important; }
|
||||
h3, .md-typeset h3 { font-size: 12pt !important; margin: 10pt 0 4pt !important; }
|
||||
h4, .md-typeset h4 { font-size: 11pt !important; margin: 8pt 0 3pt !important; }
|
||||
h5, h6,
|
||||
.md-typeset h5,
|
||||
.md-typeset h6 { font-size: 10pt !important; margin: 6pt 0 2pt !important; }
|
||||
|
||||
p, li, dt, dd, blockquote p,
|
||||
.md-typeset p,
|
||||
.md-typeset li,
|
||||
.md-typeset dt,
|
||||
.md-typeset dd {
|
||||
color: #1a1a1a !important;
|
||||
font-family: 'EB Garamond', Georgia, serif !important;
|
||||
font-size: 11pt !important;
|
||||
line-height: 1.65 !important;
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
|
||||
a, .md-typeset a {
|
||||
color: #00487a !important;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
code, .md-typeset code {
|
||||
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
||||
font-size: 9pt !important;
|
||||
background: #f4f4f4 !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #cccccc !important;
|
||||
padding: 0.1em 0.3em !important;
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
|
||||
pre, .md-typeset pre {
|
||||
font-family: 'Fira Code VF', 'Fira Code', 'Courier New', monospace !important;
|
||||
font-size: 8.5pt !important;
|
||||
background: #f8f8f8 !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #cccccc !important;
|
||||
border-left: 3px solid #555555 !important;
|
||||
padding: 0.75em 1em !important;
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-all !important;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
pre code, .md-typeset pre code {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
table, .md-typeset table {
|
||||
border-collapse: collapse !important;
|
||||
width: 100% !important;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
font-size: 10pt !important;
|
||||
}
|
||||
|
||||
th, .md-typeset th {
|
||||
background: #eeeeee !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #aaaaaa !important;
|
||||
padding: 5pt 8pt !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
td, .md-typeset td {
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #cccccc !important;
|
||||
padding: 4pt 8pt !important;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background: #f9f9f9 !important;
|
||||
}
|
||||
|
||||
.admonition,
|
||||
.md-typeset .admonition,
|
||||
details.admonition {
|
||||
background: #f8f8f8 !important;
|
||||
border-left: 4px solid #555555 !important;
|
||||
border: 1px solid #cccccc !important;
|
||||
color: #1a1a1a !important;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.admonition-title,
|
||||
.md-typeset .admonition-title {
|
||||
background: #eeeeee !important;
|
||||
color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
blockquote, .md-typeset blockquote {
|
||||
border-left: 3px solid #999999 !important;
|
||||
background: none !important;
|
||||
color: #333333 !important;
|
||||
padding-left: 1em !important;
|
||||
margin: 0.75em 0 !important;
|
||||
}
|
||||
|
||||
blockquote p::before,
|
||||
.md-typeset blockquote p:first-of-type::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
img,
|
||||
.md-typeset img,
|
||||
figure img,
|
||||
[data-md-color-scheme="slate"] img,
|
||||
[data-md-color-scheme="slate"] .md-typeset img {
|
||||
display: block !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
filter: none !important;
|
||||
-webkit-filter: none !important;
|
||||
opacity: 1 !important;
|
||||
mix-blend-mode: normal !important;
|
||||
image-rendering: auto !important;
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
}
|
||||
|
||||
html::before, html::after,
|
||||
body::before, body::after,
|
||||
[data-md-color-scheme="slate"] body::before,
|
||||
[data-md-color-scheme="slate"] body::after,
|
||||
.md-header::before, .md-header::after {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
background: none !important;
|
||||
animation: none !important;
|
||||
opacity: 0 !important;
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
kbd, .md-typeset kbd {
|
||||
background: #eeeeee !important;
|
||||
border: 1px solid #aaaaaa !important;
|
||||
border-bottom: 2px solid #888888 !important;
|
||||
color: #1a1a1a !important;
|
||||
font-family: 'Fira Code VF', 'Fira Code', monospace !important;
|
||||
text-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
@@ -1,581 +0,0 @@
|
||||
/* 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);
|
||||
}
|
||||
@@ -1,146 +1,141 @@
|
||||
---
|
||||
title: "Verify"
|
||||
description: "Verify the authenticity and integrity of Anonymous Planet releases."
|
||||
hide:
|
||||
- navigation
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
"@id": https://anonymousplanet.net/
|
||||
name: Anonymous Planet
|
||||
url: https://anonymousplanet.net/verify/
|
||||
logo: ../media/profile.png
|
||||
title: "Verifying authenticity"
|
||||
---
|
||||
|
||||
<div class="hero-block">
|
||||
<div class="hero-eyebrow">Never blindly trust anything you download.</div>
|
||||
<h1 class="hero-title">Verify Releases<span class="hero-subtitle">Signatures. Hashes. Trust nothing blindly.</span></h1>
|
||||
<p class="hero-tagline">
|
||||
Every release is GPG-signed and hashed. Verify before you read.
|
||||
</p>
|
||||
<div class="hero-cta-row">
|
||||
<a href="#quick-verification" class="hero-cta hero-cta--primary">Quick Verification</a>
|
||||
<a href="../pgp/" class="hero-cta hero-cta--secondary">Import Our Keys</a>
|
||||
<div style="font-family: var(--code-font); color: var(--crt-red); font-size: 0.9rem; margin-bottom: 2em;">
|
||||
Never blindly trust the information you see online.
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; max-width: 1000px; margin: 0 auto;">
|
||||
|
||||
<div class="quick-access-card">
|
||||
<h4 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Get our keyring</h4>
|
||||
<p style="margin: 0 0 1em;">The Anonymous Planet MSK and other keys in our keyring can be found here.</p>
|
||||
<a href="../../pgp/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Access our public keyring <span style="font-size: 0.7em;"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
## Files Provided
|
||||
|
||||
## What We Publish { #artifacts }
|
||||
For each release, you'll receive:
|
||||
|
||||
<div class="index-grid">
|
||||
| File Type | Purpose | Verification Command |
|
||||
|-----------|---------|---------------------|
|
||||
| **PDF** (`thgtoa.pdf`) | The actual guide document | Check hash + signature |
|
||||
| **.sig file** | GPG detached signature for authenticity | `gpg --verify file.sig file.pdf` |
|
||||
| **.sha256** | SHA256 checksum for integrity | `sha256sum -c file.sha256` |
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">PDF Guide</h3>
|
||||
<p class="index-card__body"><code>thgtoa.pdf</code> and <code>thgtoa-dark.pdf</code> — the full guide in light and dark mode. The only canonical single-file export.</p>
|
||||
<a href="https://github.com/Anon-Planet/thgtoa/releases" class="index-card__link">Latest release</a>
|
||||
</div>
|
||||
## Quick Verification
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Detached Signatures</h3>
|
||||
<p class="index-card__body"><code>.asc</code> files for every PDF and hash file, signed with the Release Signing Key (RSK). Verify with <code>gpg --verify</code>.</p>
|
||||
<a href="../pgp/" class="index-card__link">Our keys</a>
|
||||
</div>
|
||||
|
||||
<div class="index-card">
|
||||
<h3 class="index-card__title">Hash Files</h3>
|
||||
<p class="index-card__body"><code>sha256sums.txt</code> and <code>b2sums.txt</code> for integrity. Both are also signed. Check with <code>sha256sum -c</code> or <code>b2sum -c</code>.</p>
|
||||
<a href="#manual-verification" class="index-card__link">Manual steps</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Quick Verification { #quick-verification }
|
||||
|
||||
### Using the Python Script (Recommended)
|
||||
### Using Python Script (Recommended)
|
||||
|
||||
```sh
|
||||
# Verify everything — hashes, signatures, and optionally VirusTotal
|
||||
# Verify everything (hashes, signatures, and optionally VirusTotal)
|
||||
python scripts/verify_pdf.py --all
|
||||
|
||||
# Hashes only
|
||||
# Only verify hashes
|
||||
python scripts/verify_pdf.py --hashes
|
||||
|
||||
# GPG signatures only
|
||||
# Only verify GPG signatures
|
||||
python scripts/verify_pdf.py --signatures
|
||||
|
||||
# VirusTotal scan status (requires VT_API_KEY env var)
|
||||
# Check VirusTotal scan status (requires VT_API_KEY environment variable)
|
||||
python scripts/verify_pdf.py --vt
|
||||
```
|
||||
|
||||
---
|
||||
### Manual Verification
|
||||
|
||||
## Manual Verification { #manual-verification }
|
||||
#### 1. Verify SHA256 Hash
|
||||
|
||||
### 1. Import the key
|
||||
**Linux/macOS:**
|
||||
|
||||
```sh
|
||||
cd /path/to/repo
|
||||
sha256sum -c sha256sum-light.txt
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
|
||||
```powershell
|
||||
Get-FileHash -Algorithm SHA256 export\thgtoa.pdf | Select-Object Hash
|
||||
# Compare with the hash in thgtoa.pdf.sha256
|
||||
```
|
||||
|
||||
#### 2. Verify GPG Signature
|
||||
|
||||
First, import the public key:
|
||||
|
||||
```sh
|
||||
gpg --import pgp/anonymousplanet.asc
|
||||
```
|
||||
|
||||
Verify the fingerprint against our [PGP page](../pgp/index.md) and [GitHub releases](https://github.com/Anon-Planet/thgtoa/releases) before trusting it.
|
||||
|
||||
### 2. Verify the PDFs
|
||||
Then verify the signature:
|
||||
|
||||
```sh
|
||||
gpg --verify export/thgtoa.pdf.asc export/thgtoa.pdf
|
||||
gpg --verify export/thgtoa-dark.pdf.asc export/thgtoa-dark.pdf
|
||||
gpg --verify export/thgtoa.pdf.sig export/thgtoa.pdf
|
||||
gpg --verify export/thgtoa-dark.pdf.sig export/thgtoa-dark.pdf
|
||||
```
|
||||
|
||||
Expected output:
|
||||
**Example output for successful verification:**
|
||||
|
||||
```text
|
||||
gpg: Signature made Sun 31 May 2026 03:23:26 AM EDT
|
||||
gpg: using EDDSA key C3023DBEA3FB38C438BA1EEDCEC60AEDE8B992A2
|
||||
gpg: Good signature from "Anonymous Planet Release Signing Key" [ultimate]
|
||||
Primary key fingerprint: C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2
|
||||
gpg: Signature made Mon 20 Apr 2026 01:46:40 AM EDT
|
||||
gpg: using EDDSA key 9FA5436D0EE360985157382517ECA05F768DEFDA
|
||||
gpg: Good signature from "Anonymous Planet Master Signing Key" [unknown]
|
||||
gpg: WARNING: This key is not certified with a trusted signature!
|
||||
gpg: There is no indication that the signature belongs to the owner.
|
||||
Primary key fingerprint: 9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6
|
||||
```
|
||||
|
||||
!!! note "About the WARNING"
|
||||
`WARNING: This key is not certified with a trusted signature` is expected. It means the key has not been co-signed by another key in your web of trust — not that the signature is invalid.
|
||||
**Note:** The "WARNING" is expected - it means the key hasn't been signed by another trusted key. This is normal for independent signing keys.
|
||||
|
||||
### 3. Check hashes
|
||||
#### 3. Check VirusTotal Status
|
||||
|
||||
=== "Linux / macOS"
|
||||
Visit the VirusTotal report links (automatically generated in release notes):
|
||||
- Light mode: `https://www.virustotal.com/gui/file/[hash]`
|
||||
- Dark mode: `https://www.virustotal.com/gui/file/[hash]`
|
||||
|
||||
```sh
|
||||
sha256sum -c sha256sums.txt
|
||||
b2sum -c b2sums.txt
|
||||
```
|
||||
|
||||
=== "Windows (PowerShell)"
|
||||
|
||||
```powershell
|
||||
Get-FileHash -Algorithm SHA256 export\thgtoa.pdf | Select-Object Hash
|
||||
# Compare with the value in thgtoa.pdf.sha256
|
||||
```
|
||||
|
||||
### 4. VirusTotal (optional)
|
||||
Or use the Python script with API key:
|
||||
|
||||
```sh
|
||||
export VT_API_KEY=your_vt_api_key
|
||||
python scripts/verify_pdf.py --vt
|
||||
```
|
||||
|
||||
Or open the VirusTotal report URLs listed in the release notes directly.
|
||||
## Automated Verification in CI/CD
|
||||
|
||||
---
|
||||
The GitHub Actions workflows automatically:
|
||||
|
||||
## Troubleshooting { #troubleshooting }
|
||||
1. **Build PDFs** from MkDocs source
|
||||
2. **Generate SHA256 hashes** and save to root directory
|
||||
3. **Sign files with GPG** using the repository's private key
|
||||
4. **Scan with VirusTotal** and update release notes
|
||||
5. **Create releases** with all verification artifacts
|
||||
|
||||
**"Good signature" but wrong owner?**
|
||||
Ensure you imported the correct key from [`pgp/`](../pgp/index.md). Check the fingerprint matches the RSK: `C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2`.
|
||||
## Security Best Practices
|
||||
|
||||
**Hash mismatch?**
|
||||
Re-download the file. Verify you are using the correct hash file for the edition (light vs dark). Check for disk errors.
|
||||
1. **Always verify signatures** before opening PDFs from untrusted sources
|
||||
2. **Check hashes** to ensure files weren't corrupted during download
|
||||
3. **Review VirusTotal results** for any suspicious detections
|
||||
4. **Import keys securely** - verify key fingerprints with the project maintainers
|
||||
5. **Keep verification scripts updated** to match current security standards
|
||||
|
||||
**GPG not installed?**
|
||||
## Troubleshooting
|
||||
|
||||
| Platform | Command |
|
||||
|---|---|
|
||||
| Debian / Ubuntu | `sudo apt install gnupg` |
|
||||
| RHEL / Fedora | `sudo dnf install gnupg2` |
|
||||
| macOS | `brew install gnupg` |
|
||||
| Windows | [Gpg4win](https://gpg4win.org) |
|
||||
### "Good signature" but wrong owner?
|
||||
|
||||
- Ensure you imported the correct public key from [`pgp/`](../pgp/index.md)
|
||||
- Check the key fingerprint matches the official one from the repository announcements
|
||||
|
||||
### Hash mismatch?
|
||||
|
||||
- Re-download the file (corruption during transfer)
|
||||
- Verify you're checking against the correct hash file for the mode (light/dark)
|
||||
- Check for disk errors on your system
|
||||
|
||||
### GPG not found?
|
||||
|
||||
- **Linux/Debian:** `sudo apt install gnupg`
|
||||
- **Linux/RHEL/CentOS:** `sudo yum install gnupg2` or `sudo dnf install gnupg2`
|
||||
- **macOS:** `brew install gnupg` or use Homebrew Casks: `brew install --cask gnupg`
|
||||
- **Windows:** Use [Gpg4win](https://www.gpg4win.org/)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
ed1fff860d113609cbeaf38beef5342b473d5f09b9263020ef0a82f42ba9c8d4d17ad2743267a76b0f315c4719f9866d3e2018bc8f4eb64a46d0fcbec0c6ee2f thgtoa.pdf
|
||||
1bd77081bf7f2962f8a52a141e286fc7cb571e17b1b551d1baab5fd892cf874a1ace903301f263d7073251a1c53a4d141aa346fb0c87ea0af0b96703127f5133 thgtoa-dark.pdf
|
||||
39e7f8098d6c9511b98f83f4548ef8bac0d604fe820c4dbe1f731dbdff47676c0800872ba329492427cdfdf66734f55d03e3b4dd95b48e9e2ca2b3b4cd716213 thgtoa.pdf
|
||||
ba29fcd4ee9bd43a7ed96752bc372f7d374d69f3d37e33e04d07fd14fe4e62afccbc05471e8ad89632d31045a56eee9bde7c15a0c405f64c977e5e4ac30654fa thgtoa-dark.pdf
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
85e8824dcda6176ac4444fdc92ff5272527065aabb2c240d3e2f87418aa255b3 thgtoa.pdf
|
||||
28675b585406f0e0543e4f63d5cff6507a064bf4d91db9ff82e4a826e8c8959d thgtoa-dark.pdf
|
||||
ad7b3e327559dd835755615103bb1c59ef6f41ba652f6ee40c8fcdd082914f49 thgtoa.pdf
|
||||
1174ec6f1e074b6b0115cea54ee135e82e56771d7129dcf367037a7020d5b39c thgtoa-dark.pdf
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iHUEABYKAB0WIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwNewAKCRDOxgrt6LmS
|
||||
opw7AQDdsg3JaS2vy2ZYCI4L1F+guKHF/zItJUSTj76DdOVzSAD+PKDCa4Io6OO9
|
||||
7v2odiJHOrbYNmte5FhhffUZL8Nz1A4=
|
||||
=oBfF
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1 @@
|
||||
ba29fcd4ee9bd43a7ed96752bc372f7d374d69f3d37e33e04d07fd14fe4e62afccbc05471e8ad89632d31045a56eee9bde7c15a0c405f64c977e5e4ac30654fa
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDIRsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqJkrQD/etBsZk8BI71Dn0mgTDIQ
|
||||
HaYuAqtld5MmKaV9AxlniWABANt6V/0ivcXSsxajFdvpdu4TI9D4GR07ZeKFjYXV
|
||||
EZsM
|
||||
=/p57
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDHxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqIsEgD+PNgOOJy7GPQUYuaDlxeh
|
||||
ldQWf58ivLfQ6zpgeSSTiqIA/19EDw+Un9AYuxikZGp39vcNFxEhnwD7dRWZo/Ie
|
||||
ZyAE
|
||||
=OrTx
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iHUEABYKAB0WIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwNegAKCRDOxgrt6LmS
|
||||
ov0tAQCNiaIONY2A6zRVXUcOolOOCJY1pi9SvuJ/yalbTQewawEAsi7bhFYAo6c0
|
||||
yAy/jBcGD5E5HzLlmjkGvYcwsvWPfQo=
|
||||
=lnwq
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1 @@
|
||||
39e7f8098d6c9511b98f83f4548ef8bac0d604fe820c4dbe1f731dbdff47676c0800872ba329492427cdfdf66734f55d03e3b4dd95b48e9e2ca2b3b4cd716213
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDIBsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqJ6/QEAk2Ta0gygpWKSKstLjKwX
|
||||
wmqIyrEza93Xk22owhYi3FAA/jQslZb0MahgPZyf3PQ8syUlBJS8gKQ8nBEpf5BO
|
||||
Q/EK
|
||||
=Fvmv
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDHxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqIN4gEA2T011PhyNNqhGcj0uVTD
|
||||
47AZKLxWhZXnLzD0sRUHY/oBAMWFfSXrKN5q8yml5dWLbvFqbcIpefgHD8smBd6v
|
||||
fzUH
|
||||
=3Cxi
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,16 @@
|
||||
## VirusTotal Scan Results
|
||||
|
||||
**Scan Date:** 2026-04-19 01:48 UTC
|
||||
|
||||
---
|
||||
|
||||
### thgtoa.pdf
|
||||
- **SHA256 Hash:** `f82f6f53319315568fc2524b4eaf01126fe52356a20363cd358ad5977388ba28`
|
||||
- **VirusTotal Report:** VT_API_KEY not configured, scan skipped
|
||||
|
||||
### thgtoa-dark.pdf
|
||||
- **SHA256 Hash:** `94a0c8e3b81b0aeeb921029a41713d81b836da893a9bc9f905ca7296e82bd70f`
|
||||
- **VirusTotal Report:** VT_API_KEY not configured, scan skipped
|
||||
|
||||
---
|
||||
*Scan performed automatically by GitHub Actions*
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fYpCgEA209U3QewChp7mdrrFjH1
|
||||
CaBMIk2sCHwRMCcmbMDkNTAA/RIchAKex13ZjZWC9xsJpZEktvBENFsQLsNPReqR
|
||||
UZ8C
|
||||
=TYsa
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -3,27 +3,27 @@ site_author: Anonymous Planet
|
||||
site_description: "The comprehensive guide for online #anonymity and #opsec."
|
||||
site_dir: "site"
|
||||
docs_dir: "docs"
|
||||
exclude_docs: |
|
||||
includes/
|
||||
site_url: "https://anonymousplanet.net/"
|
||||
repo_url: "https://github.com/Anon-Planet/thgtoa"
|
||||
repo_name: ""
|
||||
#edit_uri: ""blob/main/docs/{path}?plain=1""
|
||||
#edit_uri: ""
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: docs/overrides
|
||||
locale: en
|
||||
favicon: media/profile.png
|
||||
icon:
|
||||
logo: material/bird
|
||||
repo: simple/github
|
||||
font: false # all fonts self-hosted in docs/fonts/
|
||||
font:
|
||||
text: Public Sans
|
||||
code: Liberation Mono
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.tabs
|
||||
- navigation.sections
|
||||
- navigation.path
|
||||
- toc.integrate
|
||||
- navigation.top
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
@@ -35,15 +35,6 @@ theme:
|
||||
- scheme: slate
|
||||
primary: hacker
|
||||
accent: green
|
||||
toggle:
|
||||
icon: material/monitor-off
|
||||
name: Printing Press
|
||||
- scheme: default
|
||||
primary: custom
|
||||
accent: custom
|
||||
toggle:
|
||||
icon: material/monitor
|
||||
name: Monitor
|
||||
|
||||
plugins:
|
||||
- social: {}
|
||||
@@ -51,16 +42,14 @@ plugins:
|
||||
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
||||
# - meta: {}
|
||||
|
||||
# Soon this will all be minified (It's a personal WIP that I already have stashed in git)
|
||||
# Soon this will all be minified (It's a personal WIP)
|
||||
extra_css:
|
||||
- stylesheets/hacker.css
|
||||
- stylesheets/hacker-extra.css
|
||||
- stylesheets/going-dark.css
|
||||
- stylesheets/toggle.css
|
||||
- stylesheets/navigation.css
|
||||
- stylesheets/footer.css
|
||||
- stylesheets/accessibility.css
|
||||
# print-light.css is NOT included here - it's only injected during PDF builds via build_guide_pdf.py
|
||||
|
||||
# extra_css:
|
||||
# - stylesheets/hacker-core.css
|
||||
@@ -68,18 +57,13 @@ extra_css:
|
||||
# - stylesheets/accessibility.css
|
||||
# - stylesheets/print-and-utilities.css
|
||||
|
||||
copyright: >-
|
||||
© 2023-2026 Anonymous Planet & contributors —
|
||||
licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="license">CC BY-SA 4.0</a>.
|
||||
|
||||
extra:
|
||||
scope: /
|
||||
homepage: /
|
||||
social:
|
||||
- icon: simple/mastodon
|
||||
link: https://ioc.exchange/@unknown
|
||||
name: Mastodon
|
||||
- icon: simple/x
|
||||
link: https://x.com/anonplanetorg
|
||||
name: X
|
||||
name: "Follow us on Mastodon"
|
||||
- icon: simple/gitlab
|
||||
link: http://wmj5kiic7b6kjplpbvwadnht2nh2qnkbnqtcv3dyvpqtz7ssbssftxid.onion/
|
||||
name: "0xacab"
|
||||
@@ -95,11 +79,11 @@ extra:
|
||||
- icon: simple/codeberg
|
||||
link: https://codeberg.org/anonymousplanet
|
||||
name: Codeberg
|
||||
- icon: simple/torbrowser
|
||||
link: https://gitlab.torproject.org/anonymousplanet/the-hitchhikers-guide
|
||||
name: Gitlab at Tor Project
|
||||
- icon: simple/x
|
||||
link: https://x.com/anonplanetorg
|
||||
name: "Follow us on X"
|
||||
# - icon: simple/torbrowser
|
||||
# link: http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion/ (defunct)
|
||||
# link: http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion/
|
||||
# name: Hidden service
|
||||
|
||||
markdown_extensions:
|
||||
@@ -130,17 +114,14 @@ markdown_extensions:
|
||||
- pymdownx.keys: {}
|
||||
- pymdownx.mark: {}
|
||||
- pymdownx.tilde: {}
|
||||
- abbr
|
||||
- pymdownx.snippets:
|
||||
base_path: ["docs"]
|
||||
auto_append:
|
||||
- includes/glossary.md
|
||||
- pymdownx.snippets: {}
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- attr_list: {}
|
||||
- def_list: {}
|
||||
- md_in_html: {}
|
||||
- meta: {}
|
||||
# - meta: {}
|
||||
- abbr: {}
|
||||
- tables: {}
|
||||
- footnotes: {}
|
||||
- toc:
|
||||
@@ -148,7 +129,7 @@ markdown_extensions:
|
||||
toc_depth: 3
|
||||
|
||||
nav:
|
||||
- "Home": index.md
|
||||
- "Home": "index.md"
|
||||
- "Verify": verify/index.md
|
||||
- "Guide": guide/index.md
|
||||
- "Code": code/index.md
|
||||
@@ -156,5 +137,5 @@ nav:
|
||||
- "A Constitution": constitution/index.md
|
||||
- "Mirrors": mirrors/index.md
|
||||
- "Changelog": changelog/index.md
|
||||
- "Anonymous Planet": about/index.md
|
||||
- "About": about.md
|
||||
- "PGP": pgp/index.md
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material[imaging]==9.7.2
|
||||
@@ -377,7 +377,7 @@ def main() -> int:
|
||||
if not key:
|
||||
print("\n✗ Could not resolve a signing key. Aborting.")
|
||||
print(" Import the release key with:")
|
||||
print(" gpg --import pgp/anonymousplanet.asc")
|
||||
print(" gpg --import pgp/anonymousplanet-release.asc")
|
||||
return 1
|
||||
print(f" Resolved to: {key}")
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Build light-mode PDF with MkDocs + Chromium, then produce dark-mode PDF via convert.py.
|
||||
|
||||
Usage:
|
||||
python scripts/build_guide_pdf.py # Light PDF only (clean light theme)
|
||||
python scripts/build_guide_pdf.py # Light PDF only
|
||||
python scripts/build_guide_pdf.py --dark # Dark PDF only (requires light PDF to exist)
|
||||
python scripts/build_guide_pdf.py --both # Light PDF, then dark PDF
|
||||
|
||||
@@ -14,12 +14,10 @@ Examples:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html as _html_mod
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
@@ -29,51 +27,37 @@ def repo_root() -> Path:
|
||||
|
||||
|
||||
def find_chromium_executable() -> Path | None:
|
||||
"""Find a Chromium-based browser on the system (prioritizes WSL/Linux paths).
|
||||
|
||||
On WSL Windows, checks WSL tools first, then falls back to Windows paths.
|
||||
"""
|
||||
import os as _os
|
||||
import shutil
|
||||
|
||||
wsl_paths = [
|
||||
"/usr/bin/google-chrome",
|
||||
"/usr/bin/google-chrome-stable",
|
||||
"/usr/bin/chromium-browser",
|
||||
"/usr/bin/chromium",
|
||||
"/usr/bin/microsoft-edge",
|
||||
"/usr/bin/microsoft-edge-stable",
|
||||
"/usr/bin/microsoft-edge-dev",
|
||||
"/snap/bin/chromium",
|
||||
"/usr/local/bin/chrome",
|
||||
"/opt/google-chrome/",
|
||||
]
|
||||
|
||||
for p in wsl_paths:
|
||||
if os.path.isfile(p):
|
||||
return Path(p)
|
||||
|
||||
for name in ("google-chrome-stable", "google-chrome", "chromium-browser", "chromium",
|
||||
"microsoft-edge-stable", "microsoft-edge", "msedge", "chrome"):
|
||||
w = shutil.which(name)
|
||||
if w:
|
||||
return Path(w)
|
||||
|
||||
if sys.platform == "win32":
|
||||
paths = [
|
||||
Path(_os.environ.get("PROGRAMFILES(X86)", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(_os.environ.get("LOCALAPPDATA", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(_os.environ.get("PROGRAMFILES", "")) / "Google/Chrome/Application/chrome.exe",
|
||||
Path(os.environ.get("PROGRAMFILES(X86)", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(os.environ.get("LOCALAPPDATA", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(os.environ.get("PROGRAMFILES", "")) / "Google/Chrome/Application/chrome.exe",
|
||||
Path(os.environ.get("PROGRAMFILES(X86)", "")) / "Google/Chrome/Application/chrome.exe",
|
||||
Path(os.environ.get("LOCALAPPDATA", "")) / "Google/Chrome/Application/chrome.exe",
|
||||
]
|
||||
for p in paths:
|
||||
if p.is_file():
|
||||
return p
|
||||
|
||||
for name in ("chrome", "msedge"):
|
||||
w = shutil.which(name)
|
||||
if w:
|
||||
return Path(w)
|
||||
elif sys.platform == "darwin":
|
||||
for p in (
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
):
|
||||
if os.path.isfile(p):
|
||||
return Path(p)
|
||||
for name in ("google-chrome-stable", "google-chrome", "chromium-browser", "chromium", "chrome"):
|
||||
w = shutil.which(name)
|
||||
if w:
|
||||
return Path(w)
|
||||
return None
|
||||
|
||||
|
||||
def run_mkdocs(site_dir: Path) -> None:
|
||||
"""Build MkDocs site."""
|
||||
site_dir.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "mkdocs", "build", "-d", str(site_dir)],
|
||||
@@ -82,56 +66,29 @@ def run_mkdocs(site_dir: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def inject_print_light_css(html_file: Path) -> Path:
|
||||
"""Inject print-light.css inline into the built HTML for light PDF builds."""
|
||||
html_path = html_file.resolve()
|
||||
print_light_css = repo_root() / "docs" / "stylesheets" / "print-light.css"
|
||||
|
||||
if not print_light_css.is_file():
|
||||
print(f"Warning: {print_light_css} not found, skipping CSS injection.", file=sys.stderr)
|
||||
return html_path
|
||||
|
||||
css_content = print_light_css.read_text(encoding="utf-8")
|
||||
html_content = html_path.read_text(encoding="utf-8")
|
||||
|
||||
head_match = re.search(r"<head(?:[^>]*)>", html_content, re.IGNORECASE)
|
||||
if not head_match:
|
||||
print("Warning: Could not find <head> tag in HTML, skipping CSS injection.", file=sys.stderr)
|
||||
return html_path
|
||||
|
||||
insert_pos = head_match.end()
|
||||
style_block = (
|
||||
"\n<!-- Light PDF theme: injected inline so file:// URI resolves correctly -->\n"
|
||||
"<style>\n"
|
||||
+ css_content
|
||||
+ "\n</style>\n"
|
||||
)
|
||||
|
||||
new_html = html_content[:insert_pos] + style_block + html_content[insert_pos:]
|
||||
|
||||
output_file = html_path.with_stem(html_path.stem + "-light-pdf")
|
||||
output_file.write_text(new_html, encoding="utf-8")
|
||||
return output_file
|
||||
|
||||
|
||||
def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||
"""Render html_file to pdf_out via headless Chromium."""
|
||||
"""Render html_file to pdf_out via headless Chromium.
|
||||
|
||||
Uses a temp file first so an open guide.pdf on Windows does not block the
|
||||
build; if the final path is still locked, writes guide-new.pdf instead.
|
||||
"""
|
||||
pdf_out.parent.mkdir(parents=True, exist_ok=True)
|
||||
partial = pdf_out.parent / f".{pdf_out.name}.writing"
|
||||
partial.unlink(missing_ok=True)
|
||||
|
||||
html_file = inject_print_light_css(html_file)
|
||||
uri = html_file.resolve().as_uri()
|
||||
|
||||
cmd = [str(browser)]
|
||||
if os.environ.get("CI"):
|
||||
cmd += ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
|
||||
cmd += [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
]
|
||||
cmd += [
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--no-pdf-header-footer",
|
||||
"--print-background",
|
||||
"--no-margins",
|
||||
f"--print-to-pdf={partial.resolve()}",
|
||||
uri,
|
||||
]
|
||||
@@ -159,228 +116,11 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||
partial.replace(pdf_out)
|
||||
return pdf_out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Front matter: ToC parsing + HTML generation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def parse_toc_headings(guide_md: Path) -> list[tuple[int, str]]:
|
||||
"""Extract H2 and H3 headings from the guide markdown source.
|
||||
|
||||
Returns a list of (level, title) tuples in document order.
|
||||
Strips MkDocs anchor suffixes like { #some-id } and common inline markup.
|
||||
Skips headings inside fenced code blocks.
|
||||
"""
|
||||
headings: list[tuple[int, str]] = []
|
||||
in_fence = False
|
||||
fence_re = re.compile(r"^\s*```")
|
||||
heading_re = re.compile(r"^(#{2,3})\s+(.+?)(?:\s*\{[^}]*\})?\s*$")
|
||||
inline_re = re.compile(
|
||||
r"\*{1,2}([^*]+)\*{1,2}" # **bold** / *italic*
|
||||
r"|`[^`]+`" # `code`
|
||||
r"|\[([^\]]+)\]\([^)]*\)" # [text](url)
|
||||
)
|
||||
|
||||
for line in guide_md.read_text(encoding="utf-8").splitlines():
|
||||
if fence_re.match(line):
|
||||
in_fence = not in_fence
|
||||
continue
|
||||
if in_fence:
|
||||
continue
|
||||
m = heading_re.match(line)
|
||||
if m:
|
||||
level = len(m.group(1))
|
||||
raw = m.group(2).strip()
|
||||
title = inline_re.sub(
|
||||
lambda x: x.group(1) or x.group(2) or "", raw
|
||||
).strip()
|
||||
headings.append((level, title))
|
||||
return headings
|
||||
|
||||
|
||||
def create_toc_html(tmp_dir: str, headings: list[tuple[int, str]], is_dark: bool) -> str:
|
||||
"""Write a research-style Table of Contents HTML file and return its path.
|
||||
|
||||
H2 entries: full-width row with dot leader.
|
||||
H3 entries: indented, italic, muted colour.
|
||||
Layout matches the cover page (A4, same font stack, no Chromium margins).
|
||||
"""
|
||||
if is_dark:
|
||||
bg = "#1f1f31"
|
||||
fg = "#e0e0e0"
|
||||
muted = "#a0a0c0"
|
||||
rule = "#4a4a6a"
|
||||
h3color = "#b8b8d8"
|
||||
else:
|
||||
bg = "#ffffff"
|
||||
fg = "#1a1a1a"
|
||||
muted = "#555555"
|
||||
rule = "#cccccc"
|
||||
h3color = "#444466"
|
||||
|
||||
rows: list[str] = []
|
||||
for level, title in headings:
|
||||
safe = _html_mod.escape(title)
|
||||
if level == 2:
|
||||
rows.append(
|
||||
f'<div class="toc-h2">'
|
||||
f'<span class="toc-title">{safe}</span>'
|
||||
f'<span class="toc-dots"></span>'
|
||||
f"</div>"
|
||||
)
|
||||
else:
|
||||
rows.append(f'<div class="toc-h3">{safe}</div>')
|
||||
|
||||
body = "\n".join(rows)
|
||||
|
||||
css = (
|
||||
"* { box-sizing: border-box; margin: 0; padding: 0; }\n"
|
||||
"@page { size: A4; margin: 0; }\n"
|
||||
"html, body {\n"
|
||||
f" width: 210mm;\n"
|
||||
f" background: {bg};\n"
|
||||
f" color: {fg};\n"
|
||||
" font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;\n"
|
||||
"}\n"
|
||||
".page {\n"
|
||||
" width: 210mm;\n"
|
||||
" min-height: 297mm;\n"
|
||||
" padding: 25mm 28mm 28mm 28mm;\n"
|
||||
"}\n"
|
||||
".toc-heading {\n"
|
||||
" font-size: 18pt;\n"
|
||||
" font-weight: normal;\n"
|
||||
" letter-spacing: 0.04em;\n"
|
||||
" margin-bottom: 8mm;\n"
|
||||
" padding-bottom: 3mm;\n"
|
||||
f" border-bottom: 1px solid {rule};\n"
|
||||
f" color: {fg};\n"
|
||||
"}\n"
|
||||
".toc-h2 {\n"
|
||||
" display: flex;\n"
|
||||
" align-items: baseline;\n"
|
||||
" font-size: 10.5pt;\n"
|
||||
" font-weight: normal;\n"
|
||||
f" color: {fg};\n"
|
||||
" margin-top: 3.5pt;\n"
|
||||
" margin-bottom: 1.5pt;\n"
|
||||
"}\n"
|
||||
".toc-title {\n"
|
||||
" white-space: nowrap;\n"
|
||||
" overflow: hidden;\n"
|
||||
" flex-shrink: 0;\n"
|
||||
" max-width: 85%;\n"
|
||||
"}\n"
|
||||
".toc-dots {\n"
|
||||
" flex: 1;\n"
|
||||
f" border-bottom: 1px dotted {muted};\n"
|
||||
" margin: 0 4pt;\n"
|
||||
" position: relative;\n"
|
||||
" top: -2pt;\n"
|
||||
" min-width: 8pt;\n"
|
||||
"}\n"
|
||||
".toc-h3 {\n"
|
||||
" font-size: 9pt;\n"
|
||||
f" color: {h3color};\n"
|
||||
" padding-left: 10mm;\n"
|
||||
" margin-top: 1pt;\n"
|
||||
" margin-bottom: 1pt;\n"
|
||||
" font-style: italic;\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
html = (
|
||||
'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n'
|
||||
f"<style>\n{css}</style>\n</head>\n<body>\n"
|
||||
'<div class="page">\n'
|
||||
' <p class="toc-heading">Table of Contents</p>\n'
|
||||
f"{body}\n"
|
||||
"</div>\n</body>\n</html>\n"
|
||||
)
|
||||
|
||||
html_path = os.path.join(tmp_dir, "toc.html")
|
||||
with open(html_path, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
return html_path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared Chromium render helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _render_html_to_pdf(
|
||||
browser: Path, html_path: str, pdf_path: str, ci: bool = False
|
||||
) -> None:
|
||||
"""Render a local HTML file to PDF via headless Chromium."""
|
||||
cmd = [
|
||||
str(browser),
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--no-pdf-header-footer",
|
||||
"--print-background",
|
||||
"--no-margins",
|
||||
f"--print-to-pdf={pdf_path}",
|
||||
Path(html_path).resolve().as_uri(),
|
||||
]
|
||||
if ci:
|
||||
cmd[1:1] = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
|
||||
subprocess.run(cmd, check=True, capture_output=True, timeout=120)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Front matter assembly: cover + ToC prepended to body PDF
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def prepend_front_matter(browser: Path, pdf_path: Path, is_dark: bool) -> Path:
|
||||
"""Render cover + ToC and prepend them to pdf_path in-place.
|
||||
|
||||
- Cover HTML is sourced from convert.py (single definition).
|
||||
- ToC headings are parsed from docs/guide/index.md.
|
||||
- Merged order: cover -> toc -> body, via a single qpdf call.
|
||||
"""
|
||||
sys.path.insert(0, str(repo_root() / "scripts"))
|
||||
from convert import create_cover_page # noqa: PLC0415
|
||||
|
||||
guide_md = repo_root() / "docs" / "guide" / "index.md"
|
||||
ci = bool(os.environ.get("CI"))
|
||||
|
||||
print(" Parsing ToC headings...", flush=True)
|
||||
headings = parse_toc_headings(guide_md)
|
||||
n_h2 = sum(1 for l, _ in headings if l == 2)
|
||||
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)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
print(" Rendering cover page...", flush=True)
|
||||
cover_html = create_cover_page(tmp, is_dark=is_dark)
|
||||
cover_pdf = os.path.join(tmp, "cover.pdf")
|
||||
_render_html_to_pdf(browser, cover_html, cover_pdf, ci=ci)
|
||||
|
||||
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")
|
||||
_render_html_to_pdf(browser, toc_html, toc_pdf, ci=ci)
|
||||
|
||||
merged = str(pdf_path.parent / f".{pdf_path.name}.with-front")
|
||||
subprocess.run(
|
||||
["qpdf", "--empty", "--pages",
|
||||
cover_pdf, toc_pdf, str(pdf_path),
|
||||
"--", merged],
|
||||
check=True,
|
||||
)
|
||||
|
||||
Path(merged).replace(pdf_path)
|
||||
return pdf_path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dark PDF build (delegates to convert.py which adds its own front matter)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Use scripts/convert.py in place of broken dark-mode hack
|
||||
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"
|
||||
print(f"Converting {light_pdf.name} -> {dark_pdf.name} (dark mode)...")
|
||||
print(f"Converting {light_pdf.name} → {dark_pdf.name} (dark mode)…")
|
||||
subprocess.run(
|
||||
[sys.executable, str(convert_script), str(light_pdf), str(dark_pdf)],
|
||||
check=True,
|
||||
@@ -388,10 +128,6 @@ def build_dark_pdf(light_pdf: Path, dark_pdf: Path) -> Path:
|
||||
return dark_pdf
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
ap = argparse.ArgumentParser(
|
||||
@@ -422,16 +158,15 @@ def main() -> int:
|
||||
)
|
||||
|
||||
mode = ap.add_mutually_exclusive_group()
|
||||
mode.add_argument("--dark", action="store_true",
|
||||
help="Dark PDF only (light PDF must already exist)")
|
||||
mode.add_argument("--both", action="store_true",
|
||||
help="Build light PDF, then dark PDF")
|
||||
mode.add_argument("--dark", action="store_true", help="Dark PDF only (light PDF must already exist)")
|
||||
mode.add_argument("--both", action="store_true", help="Build light PDF, then dark PDF")
|
||||
# default (no flag) = light only
|
||||
args = ap.parse_args()
|
||||
|
||||
build_light = not args.dark
|
||||
build_dark = args.dark or args.both
|
||||
|
||||
# --- Light PDF ---
|
||||
# --- Light PDF (Chromium) ---
|
||||
if build_light:
|
||||
guide_html = args.site_dir / "guide" / "index.html"
|
||||
|
||||
@@ -452,11 +187,16 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
out_light = print_to_pdf(browser, guide_html, args.pdf_light)
|
||||
out_light = prepend_front_matter(browser, out_light, is_dark=False)
|
||||
size_kb = out_light.stat().st_size // 1024
|
||||
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
||||
if out_light.resolve() != args.pdf_light.resolve():
|
||||
print(
|
||||
f"Note: {args.pdf_light.name} was in use; "
|
||||
"close it and rename or replace with the file above.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# --- Dark PDF ---
|
||||
# --- Dark PDF (pixel converter) ---
|
||||
if build_dark:
|
||||
if not args.pdf_light.exists():
|
||||
print(
|
||||
|
||||
@@ -15,8 +15,6 @@ Usage:
|
||||
Examples:
|
||||
python scripts/convert.py export/thgtoa.pdf export/thgtoa-dark.pdf
|
||||
python scripts/convert.py export/thgtoa.pdf --dpi 150 --bg 0d1117
|
||||
|
||||
Note: Adds a cover page at the start with title/subtitle/version info.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -56,76 +54,42 @@ def apply_dark_theme(
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Remap a white-background page image to a dark theme.
|
||||
|
||||
Strategy:
|
||||
- Near-white pixels (page background) → bg color
|
||||
- Dark, low-saturation pixels (text) → text color
|
||||
- Blue-dominant dark pixels (links) → link color
|
||||
- High-saturation pixels (photos/images) → preserved exactly as-is
|
||||
(no dark remapping; images stay at natural colors)
|
||||
|
||||
The key fix vs. the old version: output is initialized from the *original*
|
||||
pixels, so any pixel that doesn't match a remap mask keeps its source color.
|
||||
This prevents the "black blobs" bug where image regions fell through to the
|
||||
zero-initialized buffer.
|
||||
- Near-white pixels → bg color
|
||||
- Dark pixels (ink/text) → text color
|
||||
- Blue-ish pixels → link color
|
||||
"""
|
||||
arr = np.array(img.convert('RGB'), dtype=np.float32)
|
||||
orig = arr.copy()
|
||||
norm = arr / 255.0
|
||||
|
||||
# Luminance (Rec. 601)
|
||||
lum = (
|
||||
0.299 * norm[:, :, 0] +
|
||||
0.587 * norm[:, :, 1] +
|
||||
0.114 * norm[:, :, 2]
|
||||
lightness = (
|
||||
0.299 * norm[:, :, 0]
|
||||
+ 0.587 * norm[:, :, 1]
|
||||
+ 0.114 * norm[:, :, 2]
|
||||
)
|
||||
|
||||
# Saturation (HSV model, vectorised)
|
||||
ch_min = np.min(norm, axis=2)
|
||||
ch_max = np.max(norm, axis=2)
|
||||
# Suppress the numpy divide error, idc
|
||||
# division happens on all elements including the zero ones
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
sat = np.where(ch_max > 0.001, (ch_max - ch_min) / ch_max, 0.0).astype(np.float32)
|
||||
|
||||
# --- Masks ---
|
||||
# High-saturation = image/photo content — leave untouched
|
||||
is_image = sat > 0.20
|
||||
|
||||
# Near-white page background
|
||||
is_bg = (lum > 0.88) & ~is_image
|
||||
|
||||
# Blue-ish hyperlinks: blue channel dominant, dark, not an image
|
||||
is_link = (
|
||||
~is_image &
|
||||
~is_bg &
|
||||
(norm[:, :, 2] > 0.30) &
|
||||
(norm[:, :, 2] > norm[:, :, 0] * 1.20) &
|
||||
(lum < 0.75)
|
||||
r, g, b = orig[:, :, 0], orig[:, :, 1], orig[:, :, 2]
|
||||
link_mask = (
|
||||
(b > 100)
|
||||
& (b > r * 1.3)
|
||||
& (b > g * 0.9)
|
||||
& (lightness < 0.85)
|
||||
)
|
||||
content_mask = (lightness < 0.85) & ~link_mask
|
||||
blend = ((1.0 - lightness) / 0.85).clip(0, 1)
|
||||
|
||||
# Dark ink (text, borders, rules): not image, not bg, not link
|
||||
is_text = ~is_image & ~is_bg & ~is_link & (lum < 0.85)
|
||||
bg_f = [c / 255.0 for c in bg]
|
||||
text_f = [c / 255.0 for c in text]
|
||||
link_f = [c / 255.0 for c in link]
|
||||
|
||||
# --- Build output from original pixels ---
|
||||
# Start from a copy so anything not matched keeps source color.
|
||||
out = arr.copy()
|
||||
out = np.zeros_like(norm)
|
||||
for i, (b_c, t, lc) in enumerate(zip(bg_f, text_f, link_f)):
|
||||
channel = np.full(lightness.shape, b_c)
|
||||
channel = np.where(content_mask, b_c + blend * (t - b_c), channel)
|
||||
channel = np.where(link_mask, b_c + blend * (lc - b_c), channel)
|
||||
out[:, :, i] = channel
|
||||
|
||||
bg_f = np.array(bg, dtype=np.float32)
|
||||
text_f = np.array(text, dtype=np.float32)
|
||||
link_f = np.array(link, dtype=np.float32)
|
||||
|
||||
# Full-strength remap for text: anything that was dark ink becomes text_color
|
||||
# at full brightness. No partial blend — partial blends leave mid-gray elements
|
||||
# (captions, borders, muted labels) at unreadable intermediate values.
|
||||
bg_mask3 = is_bg[..., np.newaxis]
|
||||
text_mask3 = is_text[..., np.newaxis]
|
||||
link_mask3 = is_link[..., np.newaxis]
|
||||
|
||||
out = np.where(bg_mask3, bg_f, out)
|
||||
out = np.where(text_mask3, text_f, out)
|
||||
out = np.where(link_mask3, link_f, out)
|
||||
|
||||
return Image.fromarray(out.clip(0, 255).astype(np.uint8))
|
||||
return Image.fromarray((out * 255).clip(0, 255).astype('uint8'))
|
||||
|
||||
|
||||
def _save_images_as_pdf(images: list, output_path: str) -> None:
|
||||
@@ -180,106 +144,6 @@ def _check_dependencies() -> None:
|
||||
)
|
||||
|
||||
|
||||
def create_cover_page(tmp_dir: str, is_dark: bool) -> str:
|
||||
"""Create a research-style text-only cover page for Chromium rendering."""
|
||||
if is_dark:
|
||||
bg_color = '#1f1f31'
|
||||
text_color = '#e0e0e0'
|
||||
rule_color = '#4a4a6a'
|
||||
meta_color = '#a0a0c0'
|
||||
else:
|
||||
bg_color = '#ffffff'
|
||||
text_color = '#1a1a1a'
|
||||
rule_color = '#cccccc'
|
||||
meta_color = '#555555'
|
||||
|
||||
html_path = os.path.join(tmp_dir, 'cover.html')
|
||||
with open(html_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {{
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}}
|
||||
@page {{
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}}
|
||||
html, body {{
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
background: {bg_color};
|
||||
color: {text_color};
|
||||
font-family: 'EB Garamond', Georgia, 'Times New Roman', serif;
|
||||
}}
|
||||
.page {{
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30mm 25mm;
|
||||
text-align: center;
|
||||
}}
|
||||
.title {{
|
||||
font-size: 28pt;
|
||||
font-weight: normal;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 0.01em;
|
||||
margin-bottom: 10mm;
|
||||
}}
|
||||
.rule {{
|
||||
width: 80mm;
|
||||
height: 1px;
|
||||
background: {rule_color};
|
||||
margin: 0 auto 10mm auto;
|
||||
}}
|
||||
.subtitle {{
|
||||
font-size: 13pt;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
color: {meta_color};
|
||||
margin-bottom: 14mm;
|
||||
}}
|
||||
.meta {{
|
||||
font-size: 11pt;
|
||||
color: {meta_color};
|
||||
line-height: 1.9;
|
||||
}}
|
||||
.meta strong {{
|
||||
color: {text_color};
|
||||
font-weight: normal;
|
||||
}}
|
||||
.version {{
|
||||
font-size: 11pt;
|
||||
color: {meta_color};
|
||||
margin-top: 12mm;
|
||||
letter-spacing: 0.05em;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<p class="title">The Hitchhiker’s Guide<br>to Online Anonymity</p>
|
||||
<div class="rule"></div>
|
||||
<p class="subtitle">The comprehensive guide for online anonymity and OpSec.</p>
|
||||
<div class="meta">
|
||||
<p><strong>Author</strong> Anonymous Planet</p>
|
||||
<p><strong>License</strong> Creative Commons BY-SA 4.0</p>
|
||||
<p><strong>Source</strong> https://anonymousplanet.net</p>
|
||||
</div>
|
||||
<p class="version">v1.2.6 — July 12 2026</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>""")
|
||||
return html_path
|
||||
|
||||
|
||||
def convert_pdf_to_dark(
|
||||
input_path: str | Path,
|
||||
output_path: str | Path,
|
||||
@@ -295,8 +159,6 @@ def convert_pdf_to_dark(
|
||||
For large documents, pages are processed in batches of `batch_size` to
|
||||
avoid OOM, then merged with qpdf. Falls back to single-pass Pillow save
|
||||
if qpdf is not available (fine for small documents).
|
||||
|
||||
Adds a cover page at the start with title/subtitle/version info.
|
||||
"""
|
||||
input_path = str(input_path)
|
||||
output_path = str(output_path)
|
||||
@@ -329,51 +191,7 @@ def convert_pdf_to_dark(
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Determine theme from filename for cover page
|
||||
pdf_output = Path(output_path)
|
||||
pdf_name = pdf_output.stem.lower()
|
||||
is_dark = 'dark' in pdf_name
|
||||
|
||||
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
|
||||
# 2. Process in batches
|
||||
use_batches = total > batch_size and _check_qpdf()
|
||||
|
||||
if use_batches:
|
||||
@@ -395,11 +213,12 @@ def convert_pdf_to_dark(
|
||||
dark = [apply_dark_theme(Image.open(p), bg, text, link) for p in batch]
|
||||
_save_images_as_pdf(dark, batch_path)
|
||||
batch_files.append(batch_path)
|
||||
del dark
|
||||
|
||||
# Merge batches with front matter using qpdf
|
||||
print(" Merging batches and front matter...", flush=True)
|
||||
# 3. Merge batches with qpdf
|
||||
print(" Merging batches…", flush=True)
|
||||
subprocess.run(
|
||||
['qpdf', '--empty', '--pages'] + front_pages + batch_files + ['--', output_path],
|
||||
['qpdf', '--empty', '--pages'] + batch_files + ['--', output_path],
|
||||
check=True,
|
||||
)
|
||||
|
||||
@@ -413,67 +232,10 @@ def convert_pdf_to_dark(
|
||||
|
||||
_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
|
||||
print(f" Saved → {output_path} ({size_mb:.1f} MB)")
|
||||
|
||||
|
||||
def find_chromium_executable() -> Path | None:
|
||||
"""Find a Chromium-based browser on the system (prioritizes WSL/Linux paths).
|
||||
|
||||
On WSL Windows, checks WSL tools first, then falls back to Windows paths.
|
||||
"""
|
||||
import os as _os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# First, check WSL/Linux locations (common for WSL Windows)
|
||||
wsl_paths = [
|
||||
"/usr/bin/google-chrome",
|
||||
"/usr/bin/google-chrome-stable",
|
||||
"/usr/bin/chromium-browser",
|
||||
"/usr/bin/chromium",
|
||||
"/usr/bin/microsoft-edge",
|
||||
"/usr/bin/microsoft-edge-stable",
|
||||
"/usr/bin/microsoft-edge-dev",
|
||||
"/snap/bin/chromium",
|
||||
"/usr/local/bin/chrome",
|
||||
"/opt/google-chrome/",
|
||||
]
|
||||
|
||||
for p in wsl_paths:
|
||||
if _os.path.isfile(p):
|
||||
return Path(p)
|
||||
|
||||
# Then check shutil.which (standard PATH, includes WSL paths)
|
||||
for name in ("google-chrome-stable", "google-chrome", "chromium-browser", "chromium",
|
||||
"microsoft-edge-stable", "microsoft-edge", "msedge", "chrome"):
|
||||
w = shutil.which(name)
|
||||
if w:
|
||||
return Path(w)
|
||||
|
||||
# Finally, Windows-specific paths (if running natively on Windows)
|
||||
if sys.platform == "win32":
|
||||
paths = [
|
||||
Path(_os.environ.get("PROGRAMFILES(X86)", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(_os.environ.get("LOCALAPPDATA", "")) / "Microsoft/Edge/Application/msedge.exe",
|
||||
Path(_os.environ.get("PROGRAMFILES", "")) / "Google/Chrome/Application/chrome.exe",
|
||||
]
|
||||
for p in paths:
|
||||
if p.is_file():
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# CLI
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Download self-hosted fonts for the thgtoa MkDocs site.
|
||||
Run once from the repo root. Requires network access (uses npm registry
|
||||
via a temporary npm pack, or direct URLs from fontsource CDN as fallback).
|
||||
|
||||
Usage:
|
||||
python scripts/install_fonts.py
|
||||
|
||||
Fonts installed:
|
||||
docs/fonts/eb-garamond/ - EB Garamond 400/700 normal+italic (latin, latin-ext)
|
||||
docs/fonts/fira-code/ - Fira Code variable (latin, latin-ext)
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
FONTS_DIR = os.path.join(REPO_ROOT, "docs", "fonts")
|
||||
|
||||
EB_GARAMOND_DIR = os.path.join(FONTS_DIR, "eb-garamond")
|
||||
FIRA_CODE_DIR = os.path.join(FONTS_DIR, "fira-code")
|
||||
|
||||
EB_GARAMOND_FILES = [
|
||||
"eb-garamond-latin-400-normal.woff2",
|
||||
"eb-garamond-latin-400-italic.woff2",
|
||||
"eb-garamond-latin-700-normal.woff2",
|
||||
"eb-garamond-latin-700-italic.woff2",
|
||||
"eb-garamond-latin-ext-400-normal.woff2",
|
||||
"eb-garamond-latin-ext-400-italic.woff2",
|
||||
"eb-garamond-latin-ext-700-normal.woff2",
|
||||
"eb-garamond-latin-ext-700-italic.woff2",
|
||||
]
|
||||
|
||||
FIRA_CODE_FILES = [
|
||||
"fira-code-latin-wght-normal.woff2",
|
||||
"fira-code-latin-ext-wght-normal.woff2",
|
||||
]
|
||||
|
||||
|
||||
def npm_pack(package: str, tmpdir: str) -> str:
|
||||
"""Run npm pack and return the path to the extracted package dir."""
|
||||
result = subprocess.run(
|
||||
["npm", "pack", package, "--silent"],
|
||||
cwd=tmpdir, capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"npm pack failed for {package}:\n{result.stderr}")
|
||||
tgz = os.path.join(tmpdir, result.stdout.strip())
|
||||
with tarfile.open(tgz, "r:gz") as tf:
|
||||
tf.extractall(tmpdir)
|
||||
return os.path.join(tmpdir, "package")
|
||||
|
||||
|
||||
def install_package(package: str, files: list[str], dest: str) -> None:
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
print(f" Fetching {package} ...")
|
||||
pkg_dir = npm_pack(package, tmpdir)
|
||||
files_dir = os.path.join(pkg_dir, "files")
|
||||
for fname in files:
|
||||
src = os.path.join(files_dir, fname)
|
||||
if not os.path.exists(src):
|
||||
print(f" WARNING: {fname} not found in package", file=sys.stderr)
|
||||
continue
|
||||
dst = os.path.join(dest, fname)
|
||||
shutil.copy2(src, dst)
|
||||
size = os.path.getsize(dst)
|
||||
print(f" + {fname} ({size // 1024}K)")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("Installing self-hosted fonts for thgtoa...\n")
|
||||
|
||||
if shutil.which("npm") is None:
|
||||
print("ERROR: npm not found. Install Node.js and re-run.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("EB Garamond (fontsource):")
|
||||
install_package("@fontsource/eb-garamond", EB_GARAMOND_FILES, EB_GARAMOND_DIR)
|
||||
|
||||
print("\nFira Code Variable (fontsource):")
|
||||
install_package("@fontsource-variable/fira-code", FIRA_CODE_FILES, FIRA_CODE_DIR)
|
||||
|
||||
print("\nDone. Font files written to docs/fonts/")
|
||||
print("Commit docs/fonts/ to the repository so it is served with the site.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -143,18 +143,8 @@ def categorise(messages: list[str]) -> dict[str, list[str]]:
|
||||
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:
|
||||
adm_type = ADMONITION_TYPE.get(bucket, "note")
|
||||
lines = [f'!!! {adm_type} "{bucket}"', ""]
|
||||
lines = [f'!!! Note "{bucket}"', ""]
|
||||
for item in items:
|
||||
lines.append(f" - {item}")
|
||||
lines.append("")
|
||||
@@ -164,7 +154,7 @@ def format_admonition(bucket: str, items: list[str]) -> str:
|
||||
def build_entry(version: str, buckets: dict[str, list[str]], sha: str) -> str:
|
||||
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
lines = [f"## [{version}]", ""]
|
||||
lines.append('!!! abstract "Meta"')
|
||||
lines.append('!!! Note "Meta"')
|
||||
lines.append("")
|
||||
lines.append(f" - Released {date} from [`{sha[:7]}`](https://github.com/Anon-Planet/thgtoa/commit/{sha})")
|
||||
lines.append("")
|
||||
|
||||