Compare commits
5 Commits
2ad5d5581a
...
3c140601e4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c140601e4 | ||
|
|
4688d72b7b | ||
|
|
a5a6fd49ad | ||
|
|
87b42997e5 | ||
|
|
fdcc5d269a |
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Copy to .env and fill in. Coolify will inject these as env vars.
|
||||||
|
|
||||||
|
# Override the hard-coded login token. If unset, the default baked into
|
||||||
|
# sidecar/main.go applies. Rotate here without rebuilding the image.
|
||||||
|
FIFTH_TOKEN=
|
||||||
|
|
||||||
|
# Where /login/grant redirects on a valid token. Default assumes IRC will
|
||||||
|
# live at irc.cultfifthoctet.org; change if redirecting elsewhere while IRC
|
||||||
|
# is not yet standing.
|
||||||
|
FIFTH_TOKEN_DEST=https://irc.cultfifthoctet.org/
|
||||||
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Local caddy binary (downloaded for dev)
|
||||||
|
/caddy
|
||||||
|
|
||||||
|
# Go build artifacts
|
||||||
|
/sidecar/sidecar
|
||||||
|
/sidecar/fifth
|
||||||
|
/fifth
|
||||||
|
|
||||||
|
# Local editor junk
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
branches: main
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
woodpecker-ci:
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
privileged: true
|
|
||||||
secrets: [repository_username, repository_password]
|
|
||||||
settings:
|
|
||||||
dockerfile: Containerfile
|
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
|
||||||
registry: git.shrug.pw
|
|
||||||
repo: git.shrug.pw/neil/cultfiftoctet
|
|
||||||
tags: latest
|
|
||||||
pull_image: true
|
|
||||||
mtu: 1480
|
|
||||||
username:
|
|
||||||
from_secret: registry_username
|
|
||||||
password:
|
|
||||||
from_secret: registry_password
|
|
||||||
logins:
|
|
||||||
- registry: git.shrug.pw
|
|
||||||
username:
|
|
||||||
from_secret: registry_username
|
|
||||||
password:
|
|
||||||
from_secret: registry_password
|
|
||||||
103
Caddyfile
@ -1,4 +1,101 @@
|
|||||||
:8080 {
|
{
|
||||||
templates
|
admin off
|
||||||
file_server browse
|
auto_https off
|
||||||
|
}
|
||||||
|
|
||||||
|
:8080 {
|
||||||
|
root * {$SITE_ROOT:/srv}
|
||||||
|
|
||||||
|
# ---- Global response headers (§11) ----
|
||||||
|
header {
|
||||||
|
Server "postel/4.1"
|
||||||
|
X-Frame-Options "DENY"
|
||||||
|
Strict-Transport-Security "max-age=31536000"
|
||||||
|
Referrer-Policy "no-referrer"
|
||||||
|
-X-Powered-By
|
||||||
|
}
|
||||||
|
# X-Fifth-Octet default for everything EXCEPT /.well-known/fifth.
|
||||||
|
@not_fifth not path /.well-known/fifth
|
||||||
|
header @not_fifth X-Fifth-Octet "observed"
|
||||||
|
|
||||||
|
# The sidecar upstream. Override with $FIFTH_UPSTREAM for local dev.
|
||||||
|
# In the container, both processes share the localhost network.
|
||||||
|
|
||||||
|
route {
|
||||||
|
# ---- /.well-known/fifth (static, not the sidecar) ----
|
||||||
|
handle /.well-known/fifth {
|
||||||
|
header >Content-Type "text/plain; charset=utf-8"
|
||||||
|
header >X-Fifth-Octet "dGhlIHJlY29yZCBoYXMgZml2ZSBwYXJhZ3JhcGhzLiBjb3VudCB0aGVtLgo="
|
||||||
|
respond "acknowledged.
|
||||||
|
" 200
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Sidecar-backed paths ----
|
||||||
|
# /contact/attest /correspondence /correspondence/ /login/grant
|
||||||
|
@contact path /contact/attest
|
||||||
|
handle @contact {
|
||||||
|
reverse_proxy {$FIFTH_UPSTREAM:127.0.0.1:8090}
|
||||||
|
}
|
||||||
|
|
||||||
|
@correspondence path /correspondence /correspondence/
|
||||||
|
handle @correspondence {
|
||||||
|
reverse_proxy {$FIFTH_UPSTREAM:127.0.0.1:8090}
|
||||||
|
}
|
||||||
|
|
||||||
|
@login path /login/grant
|
||||||
|
handle @login {
|
||||||
|
reverse_proxy {$FIFTH_UPSTREAM:127.0.0.1:8090} {
|
||||||
|
@bad_token status 404
|
||||||
|
handle_response @bad_token {
|
||||||
|
error 404
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- /vault: sidecar decides per-visit ----
|
||||||
|
# Allow visits get 200 + X-Fifth-Passthrough, in which case we serve
|
||||||
|
# the static page. Deny visits stream their 503 body from the sidecar.
|
||||||
|
@vault path /vault /vault/
|
||||||
|
handle @vault {
|
||||||
|
reverse_proxy {$FIFTH_UPSTREAM:127.0.0.1:8090} {
|
||||||
|
@passthrough header X-Fifth-Passthrough 1
|
||||||
|
handle_response @passthrough {
|
||||||
|
rewrite * /vault/index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Everything else: static files ----
|
||||||
|
handle {
|
||||||
|
try_files {path} {path}/ {path}/index.html =404
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Error handling (§10) ----
|
||||||
|
handle_errors {
|
||||||
|
@apply_paths path_regexp ^/(join|signup|register|apply|membership)/?$
|
||||||
|
handle @apply_paths {
|
||||||
|
rewrite * /404-applications.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
@logout_path path /logout /logout/
|
||||||
|
handle @logout_path {
|
||||||
|
rewrite * /404-logout.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
@minutes_path path_regexp ^/minutes(/.*)?$
|
||||||
|
handle @minutes_path {
|
||||||
|
rewrite * /404-minutes.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
rewrite * /404.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,20 @@
|
|||||||
FROM docker.io/library/caddy:2.6.0-beta.3
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ---- stage 1: build the Go sidecar ----
|
||||||
|
FROM docker.io/library/golang:1.22-alpine AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY sidecar/go.mod sidecar/go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY sidecar/ ./
|
||||||
|
RUN CGO_ENABLED=0 GOFLAGS="-trimpath" go build -ldflags="-s -w" -o /out/fifth .
|
||||||
|
|
||||||
|
# ---- stage 2: the runtime image ----
|
||||||
|
FROM docker.io/library/caddy:2.7.6-alpine
|
||||||
|
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
COPY index.html /srv/
|
COPY site/ /srv/
|
||||||
COPY niusgmrvc.txt /srv/
|
COPY --from=build /out/fifth /usr/local/bin/fifth
|
||||||
|
|
||||||
|
# Two compose services share this image. Caddy uses the default CMD.
|
||||||
|
# The fifth sidecar overrides command to /usr/local/bin/fifth in
|
||||||
|
# docker-compose.yml.
|
||||||
|
|||||||
703
cultfifthoctet-spec.md
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
# The Cult of the Fifth Octet
|
||||||
|
|
||||||
|
## cultfifthoctet.org — build specification
|
||||||
|
|
||||||
|
This document is the canonical brief for building out `cultfifthoctet.org`. It is intentionally opinionated. Deviations should be deliberate, not accidental.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. The premise
|
||||||
|
|
||||||
|
The site is the public-facing artifact of a semi-fictional organization that believes IPv4 address exhaustion should have been solved by appending a fifth octet (`192.168.1.42.137`) rather than by migrating to IPv6. The joke is networking-insider: anyone who's argued about this has encountered the "just add another dot" crank, and the site treats that crank as an organized, decades-old, slightly paranoid movement.
|
||||||
|
|
||||||
|
**The tonal target is not parody.** It is "you stumbled onto something you weren't meant to find." If a casual visitor cannot tell whether the site is serious, we have succeeded. If they laugh immediately, we have failed.
|
||||||
|
|
||||||
|
The site is 90% implication. Every piece of content should gesture at something larger that is not present. The absences do the work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Voice and tone
|
||||||
|
|
||||||
|
- **Never say "IPv4," "IPv6," or "octet" on the visible surface.** The nearest we come is `xxx.xxx.xxx.xxx.xxx` rendered as a graphical element. If you have to explain the joke, the joke is dead.
|
||||||
|
- **No exclamation points. No bold emphasis in body copy. No emoji. No winking.** The cult does not wink. The cult is tired.
|
||||||
|
- **Serif-heavy typography with monospace for anything that would plausibly be machine-output** (timestamps, codenames, file listings, headers that look like telex).
|
||||||
|
- **Passive voice is acceptable here**, which is unusual for me to say. "The proposal was rejected" carries more weight than "they rejected the proposal." The cult does not name agents.
|
||||||
|
- **Sentence fragments are liturgical.** "There is a fifth." "Those who know, know." Fragments read as creed.
|
||||||
|
- **Dates are always "Anno [year]"** on archival material. Never "2011." Always "Anno 2011." It's pretentious. That's the point.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Visual design language
|
||||||
|
|
||||||
|
### Palette
|
||||||
|
|
||||||
|
Dark, desaturated, one accent.
|
||||||
|
|
||||||
|
- Background: `#0f0e14` (near-black with a violet undertone)
|
||||||
|
- Primary text: `#d4d1e0` (off-white, faintly violet)
|
||||||
|
- Secondary text: `#b8b5c4`
|
||||||
|
- Muted text: `#8a8598`
|
||||||
|
- Tertiary / separators: `#6a6578`
|
||||||
|
- Dim borders: `#2a2738`
|
||||||
|
- Accent (used sparingly — once or twice per page maximum): `#a89cd9` (soft violet)
|
||||||
|
- Deepest hierarchy: `#4a4560`
|
||||||
|
|
||||||
|
Never introduce a second accent color. The restraint is load-bearing.
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
- Body and headings: serif. System-available or a humanist serif like Source Serif, Cormorant, or EB Garamond. Italic is used heavily — the cult writes things down the way a person writes in a journal.
|
||||||
|
- Machine text: monospace. JetBrains Mono, IBM Plex Mono, or similar. Used for timestamps, codenames, file listings, headers in SMALL CAPS with generous letter-spacing (`0.15em` to `0.3em`).
|
||||||
|
- Never use sans-serif. It reads as product design. We are not a product.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
- Generous vertical whitespace. Sections separated by `border-top: 0.5px solid #2a2738`.
|
||||||
|
- Content lives in a narrow column (~560px max). This is a reading site, not a dashboard.
|
||||||
|
- Horizontal rules are replaced by centered dot patterns: `· · · · ·` (five dots, always five).
|
||||||
|
- Section labels are monospace, `10px`, `letter-spacing: 0.2em`, muted color, centered, preceded and followed by em dashes: `— A PARTIAL RECORD —`.
|
||||||
|
|
||||||
|
### Icons and ornaments
|
||||||
|
|
||||||
|
- A single mark appears as the site's sigil: the Roman numeral **V** inside a thin circle. Used in the header, on the 404 page, and as a favicon. Nowhere else.
|
||||||
|
- Five-dot motif (`· · · · ·`) as a section divider and in the footer.
|
||||||
|
- No photographs appear on any page except `/members` (see §6). The site is almost entirely typographic.
|
||||||
|
|
||||||
|
### Interaction
|
||||||
|
|
||||||
|
- Links are underlined on hover only, with a dotted 0.5px underline in the accent color.
|
||||||
|
- No animations on scroll, no fade-ins, no parallax. The site loads, the text is there. A visitor from 1998 should not find this site jarring.
|
||||||
|
- One exception: a single, subtle cursor effect on the hero `xxx.xxx.xxx.xxx.xxx` — the fifth segment has a slow, ambient pulse (opacity 0.7 → 1.0 over 4 seconds). Easy to miss. Never announced.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Site structure
|
||||||
|
|
||||||
|
### Pages that exist and are linked
|
||||||
|
|
||||||
|
- `/` — Homepage (already mocked up; see §4)
|
||||||
|
- `/record` — The single blog post (§5)
|
||||||
|
- `/members` — Codename directory with obfuscated photos (§6)
|
||||||
|
- `/archive` — The sealed/redacted file listing (expanded from homepage)
|
||||||
|
- `/contact` — A page with a single IPv4+ address and nothing else
|
||||||
|
|
||||||
|
### Pages that exist but are not linked
|
||||||
|
|
||||||
|
- `/vault` — Referenced in HTML comment on homepage. Contains one real document fragment. 503s on alternating visits.
|
||||||
|
- `/1997` — The incident. Single timestamped IRC-style log fragment. Referenced nowhere on the visible site; only discoverable via `/robots.txt`.
|
||||||
|
- `/.well-known/fifth` — Returns plaintext: `acknowledged.`
|
||||||
|
- `/minutes` — 404s with a custom 404 page titled "the minutes are incomplete."
|
||||||
|
- `/manifesto` — Exists, returns one sentence: *"A manifesto would be a concession."*
|
||||||
|
|
||||||
|
### Pages that 404 deliberately
|
||||||
|
|
||||||
|
- `/join`, `/signup`, `/register` — All 404 with the same page: title "We do not accept applications."
|
||||||
|
- `/members/list`, `/members.json` — 404, because the codename directory at `/members` is the only form of membership disclosure.
|
||||||
|
|
||||||
|
### The login
|
||||||
|
|
||||||
|
- `/login` exists and works. Accepts a token. On success: redirects to a webchat client that joins `#fifth` on the site's IRC server.
|
||||||
|
- `/register` does NOT exist (see above).
|
||||||
|
- Token acquisition is the puzzle. See §7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Homepage (already designed)
|
||||||
|
|
||||||
|
Reference the mockup. Key elements in order:
|
||||||
|
|
||||||
|
1. Header bar: `CFO.ORG — EST. MCMXCV` (left, monospace), `VISITOR LOG: 000000001` (right, monospace). The visitor log always shows `000000001`. For everyone. This is either a bug or it isn't.
|
||||||
|
2. V sigil, centered, in a thin circle.
|
||||||
|
3. Five-dot separator.
|
||||||
|
4. Hero: *"There is a fifth."* (serif italic, 36px). Subhead: *"Those who know, know. Those who do not will find this page in due time, or not at all."*
|
||||||
|
5. The `xxx.xxx.xxx.xxx.xxx` graphical element, the fifth segment faintly pulsing.
|
||||||
|
6. "A Partial Record" — four dated entries (Anno 1981, 1992, 1998, 2011) written as archival fragments. See existing mockup text; keep verbatim.
|
||||||
|
7. The single quote: *"The proposal was not rejected on technical grounds. It was rejected because it would have worked."* — Attributed. Name Withheld.
|
||||||
|
8. "Archive" section with five file entries. See §9 for full list; homepage shows only five.
|
||||||
|
9. "Correspondence" — the subscribe form. See §8.
|
||||||
|
10. Footer: five dots, `NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. The single blog post: `/record`
|
||||||
|
|
||||||
|
**Title:** "On the Matter of the Fifth"
|
||||||
|
**Dated:** `Anno 2011 · 03 February` (the day IANA allocated the last free /8 blocks — a real date, which 90% of readers will not recognize as real)
|
||||||
|
**Author:** None listed. The byline reads `— authorship withheld`.
|
||||||
|
|
||||||
|
### Content (full text, use verbatim)
|
||||||
|
|
||||||
|
> There was a time when the remedy was simple.
|
||||||
|
>
|
||||||
|
> It required no new protocol. No new stack. No renumbering of the world's equipment. It required only that the specification be amended — one additional field, appended to the end of the existing four, carrying values zero through two hundred and fifty-five. A single dot. A single byte. One trillion new addresses, sufficient for every human who would ever live and every device they would ever own, with reserves enough for the grandchildren of their toasters.
|
||||||
|
>
|
||||||
|
> This is a matter of record. The proposal was drafted. It circulated. It was reviewed.
|
||||||
|
>
|
||||||
|
> It was not rejected on technical grounds. The grounds were not disclosed. We have made inquiries over the years, in the careful way that such inquiries must be made, and the responses we have received range from the dismissive to the evasive to — in one case, from a man now deceased — the frightened. We do not name him here. His family has suffered enough.
|
||||||
|
>
|
||||||
|
> What was chosen instead was a replacement of the whole cloth. A protocol that would require every router, every operating system, every piece of software that touched a network, to be rewritten. A protocol whose addresses no human being would ever memorize. A protocol whose adoption would take, by the most optimistic estimates at the time, ten years — and which, as of this writing, has taken twenty-five and is not finished.
|
||||||
|
>
|
||||||
|
> This is not an accident. An accident is recoverable.
|
||||||
|
>
|
||||||
|
> There are parties who benefitted from the scarcity. We will not list them, though some of their names are in the archive; the archive is sealed, and we ask that it remain sealed, for reasons we trust our readers to infer. We will say only that the blocks which were auctioned in the years following exhaustion were not distributed by lot. Certain buyers acquired certain ranges at prices that, in retrospect, were modest. We do not believe this was coincidence.
|
||||||
|
>
|
||||||
|
> The fifth field remains, in principle, a possibility. In practice, the window has closed. The replacement protocol, whatever else may be said of it, now has sufficient adoption that a reversal would be as costly as the original migration was promised to be.
|
||||||
|
>
|
||||||
|
> This, too, may not be an accident.
|
||||||
|
>
|
||||||
|
> We continue to maintain the record because the record is worth maintaining. There are among our correspondents engineers who worked on the original drafts, administrators who watched the auctions, and — we are told, though we have not verified — at least one member of the working group that deliberated in 1994. He is the reason we have held our peace for as long as we have.
|
||||||
|
>
|
||||||
|
> We hold our peace no longer.
|
||||||
|
>
|
||||||
|
> — *authorship withheld*
|
||||||
|
> — *posted Anno 2011 · 03 February*
|
||||||
|
> — *last modified: never*
|
||||||
|
|
||||||
|
### Hidden in the blog post
|
||||||
|
|
||||||
|
- The phrase "sealed, and we ask that it remain sealed" contains a zero-width space that, when selected and copied, pastes as a fragment: `/vault/#1994`.
|
||||||
|
- At the bottom, in a comment: `<!-- he did not hold his peace. we did. -->`
|
||||||
|
- The HTML id on the "ten years" paragraph is `id="p-07"`. There is no `p-06`. There is no `p-05`. Count them. `p-04` is the paragraph about "certain buyers." The missing ones are not in the markup. They were redacted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. The members page: `/members`
|
||||||
|
|
||||||
|
### Header
|
||||||
|
|
||||||
|
- Monospace section label: `— ROSTER —`
|
||||||
|
- Subtitle (serif italic, muted): *"Twenty-seven are listed. Others are not."*
|
||||||
|
|
||||||
|
### The roster: 27 entries
|
||||||
|
|
||||||
|
Displayed as an irregular grid of cards — NOT a uniform grid. Cards vary in height based on what information each member has chosen to disclose. Some cards are photo-only. Some are text-only with `[PHOTO WITHHELD BY REQUEST]`. The irregularity is the design.
|
||||||
|
|
||||||
|
Codename scheme: **port numbers**. Mix of well-known, registered, and obscure. A few are jokes for anyone who reads them carefully.
|
||||||
|
|
||||||
|
### The 27 members
|
||||||
|
|
||||||
|
| # | Codename | Joined | Last seen | Photo obfuscation | Notes |
|
||||||
|
|---|----------|--------|-----------|-------------------|-------|
|
||||||
|
| 01 | `:0` | ???? | — | Solid black silhouette | Founder. Em-dash on last seen is deliberate. |
|
||||||
|
| 02 | `:1` | ???? | — | Solid black silhouette | Founder. |
|
||||||
|
| 03 | `:7` | ???? | Anno 2004 | Heavy gaussian blur (50px) | Echo. |
|
||||||
|
| 04 | `:17` | Anno 1997 | Anno 2019 | Pixelated 8×8 | |
|
||||||
|
| 05 | `:22` | Anno 1998 | — | Back-of-head photograph | SSH. No face visible, just shoulders and dark hair. |
|
||||||
|
| 06 | `:23` | Anno 1996 | Anno 2003 | `[PHOTO WITHHELD BY REQUEST]` | Telnet. Appropriate that they withheld. |
|
||||||
|
| 07 | `:25` | Anno 1999 | 17 days ago | Overexposed to near-white | SMTP. Codename is a mailto link. See §7. |
|
||||||
|
| 08 | `:43` | Anno 2001 | Anno 2008 | Heavy blur | WHOIS. |
|
||||||
|
| 09 | `:53` | Anno 2001 | 3 days ago | Pixelated 16×16 | DNS. |
|
||||||
|
| 10 | `:67` | Anno 2002 | Anno 2015 | Double-exposure (two faces overlapped) | |
|
||||||
|
| 11 | `:80` | Anno 2000 | Anno 2022 | Blur + black bar across eyes | |
|
||||||
|
| 12 | `:110` | Anno 2003 | Anno 2011 | `[PHOTO WITHHELD BY REQUEST]` | |
|
||||||
|
| 13 | `:119` | Anno 2001 | Anno 2013 | Photograph of a photograph (visible print edges, thumb) | |
|
||||||
|
| 14 | `:123` | Anno 2004 | 41 days ago | Pixelated 8×8 | NTP. Appropriately about time. |
|
||||||
|
| 15 | `:143` | Anno 2003 | Anno 2018 | Solid silhouette | |
|
||||||
|
| 16 | `:161` | Anno 2005 | Anno 2020 | Blur, hand-over-face pose visible | |
|
||||||
|
| 17 | `:194` | Anno 2002 | Anno 2012 | Mirror selfie, photographer's hand visible but not face | Original IRC port (pre-6667). |
|
||||||
|
| 18 | `:389` | Anno 2006 | 6 days ago | QR code | Scanning it returns you to `/members`. |
|
||||||
|
| 19 | `:443` | Anno 2003 | Anno 2019 | Heavy blur | |
|
||||||
|
| 20 | `:465` | Anno 2007 | Anno 2014 | A photograph of a chair | Filename on the image: `gerald.jpg`. |
|
||||||
|
| 21 | `:514` | Anno 2004 | Anno 2017 | Pixelated 16×16 | Syslog. |
|
||||||
|
| 22 | `:554` | Anno 2005 | Anno 2016 | Overexposed | |
|
||||||
|
| 23 | `:631` | Anno 2006 | Anno 2021 | Redaction bar across eyes of an obvious stock photo | The performative redaction. |
|
||||||
|
| 24 | `:993` | Anno 2008 | 92 days ago | Pixelated 8×8 | |
|
||||||
|
| 25 | `:6667` | Anno 2011 | Now | No photo — just `[ONLINE]` in accent color | IRC. Always online. The joke. |
|
||||||
|
| 26 | `:31337` | Anno 2013 | Anno 2019 | Blur | Elite. |
|
||||||
|
| 27 | `:65535` | Anno 1995 | — | Solid silhouette | Emeritus. Joined before join dates were recorded. |
|
||||||
|
|
||||||
|
### Irregularities to include
|
||||||
|
|
||||||
|
- **A duplicate:** `:17` appears twice in the list — once as entry 04 with the dates above, and once again further down the page as an apparent duplicate with a different join date (`Anno 2003`) and `LAST SEEN: Anno 2011`. The site does not acknowledge the duplication.
|
||||||
|
- **A commented-out member:** In the HTML between entries 14 and 15: `<!-- :1337 — removed by request, Anno 2014. we respect their wishes. -->`
|
||||||
|
- **A future date:** Entry for `:6667` shows `JOINED: Anno 2011`, `LAST SEEN: Now` — but a `data-next-seen` attribute in the HTML contains a date approximately 6 months in the future. Not rendered. Only visible in DevTools.
|
||||||
|
- **Hover behavior:** Hovering a codename reveals a tooltip in monospace with a single line of metadata — usually mundane (`tz: +00:00`, `tz: -05:00`, etc.) except for three members whose tooltips are: `tz: —`, `tz: [REDACTED]`, and `tz: fifth.`
|
||||||
|
|
||||||
|
### The meta-joke
|
||||||
|
|
||||||
|
Twenty-seven members. Maybe eight active in the last 90 days. The IRC channel (when you eventually get there) has regulars, but almost none of them match codenames on the members page. The implication the site never confirms: the members and the regulars are different people. The public face of the cult is not the cult.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. The puzzle: how to acquire a login token
|
||||||
|
|
||||||
|
**Design principle:** The puzzle rewards effort. The back door (§7.5) rewards pattern recognition. Both should work. The people who find the back door are the real members.
|
||||||
|
|
||||||
|
### The intended path
|
||||||
|
|
||||||
|
1. **Layer 0 — View Source.** Near the top of homepage HTML: `<!-- the fifth is not a metaphor -->`. Deep in the markup: `<!-- see /vault/ -->`.
|
||||||
|
2. **Layer 1 — `/vault/`.** Exists. Returns a page with a single grayscale image: a low-resolution scan of what appears to be meeting minutes, heavily redacted, with a fifth column of hand-written numbers visible in the margin. The numbers form a sequence. The image's EXIF data contains a comment: `consult the well-known.`
|
||||||
|
3. **Layer 2 — `/.well-known/fifth`.** Plaintext response: `acknowledged.` But the HTTP response headers include `X-Fifth-Octet: <base64 string>`. Decoding yields: `the record has five paragraphs. count them.`
|
||||||
|
4. **Layer 3 — The blog post.** The blog post visibly has 7 numbered paragraphs but only 5 have HTML IDs (`p-01`, `p-02`, `p-03`, `p-04`, `p-07`). The missing IDs — `p-05`, `p-06` — when requested as URL fragments on `/record#p-05` and `/record#p-06`, each scroll to the top and trigger a `console.log` with a fragment of a token.
|
||||||
|
5. **Layer 4 — DNS.** `dig TXT cultfifthoctet.org` returns: `"v=fifth; segment=3/5; value=<fragment>"`. Three of five token segments now collected.
|
||||||
|
6. **Layer 5 — The `/1997` page.** Only discoverable from `/robots.txt` (which disallows `/1997/`, `/vault/`, `/minutes/`, `/correspondence/`). The 1997 page is an IRC-style log fragment with names redacted, timestamps intact. The log contains a line: `<[REDACTED]> the fourth segment is the channel name reversed.` (The IRC channel is `#fifth`; reversed is `htfif#`. That's the fourth token segment.)
|
||||||
|
7. **Layer 6 — The contact page.** `/contact` shows a single IPv4+ address: `172.16.0.1.<?>`. The fifth octet is unknown. A form accepts a guess. Correct answer: the port number of one of the members who has not yet been seen in the archive (`:6667`, IRC). Submitting `172.16.0.1.6667` — wait, that's >255, so actually the form accepts the full port as a 16-bit value represented as two octets. The correct submission is `172.16.0.1.26.7` — six segments now. The site does not explain this; it is the final test.
|
||||||
|
8. **Token assembly.** The five fragments combine into a long base32 string. This is your username. `/login` accepts it. No password.
|
||||||
|
|
||||||
|
### Layer 7.5 — The back door
|
||||||
|
|
||||||
|
At no point does the site ever say "you need to solve puzzles to log in." A visitor who merely:
|
||||||
|
|
||||||
|
- Looks at the site footer carefully, OR
|
||||||
|
- Runs `dig ANY cultfifthoctet.org`, OR
|
||||||
|
- Reads the TLS certificate's SAN entries
|
||||||
|
|
||||||
|
...will find a reference to `irc.cultfifthoctet.org`. IRC is open. Port 6667. The channel `#fifth` is unmoderated and has been unmoderated since the site launched. You can join without any token, without any login, without any puzzle. You have been able to join since day one.
|
||||||
|
|
||||||
|
**The channel topic, always:** `the login was never the hard part.`
|
||||||
|
|
||||||
|
Half the regulars never did the puzzle. The ones who did the puzzle show up proudly presenting their tokens and get gently roasted.
|
||||||
|
|
||||||
|
### Layer 7.75 — The second back door (cruel)
|
||||||
|
|
||||||
|
The `#fifth` channel is the public square. The actual member channel is `#fifth-dot`. It is +i (invite only). You can see it in `/list` output if you know to look. You cannot join it. Nobody in `#fifth` will tell you how.
|
||||||
|
|
||||||
|
There is no documented way in. There is a way in. It is not on the site. It is left as an exercise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. The subscribe form
|
||||||
|
|
||||||
|
Homepage form labeled "CORRESPONDENCE." Accepts any input.
|
||||||
|
|
||||||
|
- Any valid email: gets one email, ever, sent at the next winter solstice. Subject line: `acknowledged.` Body: empty.
|
||||||
|
- An invalid email: form accepts it anyway. No error. No success state. The form simply clears.
|
||||||
|
- A valid IPv4+ address (five octets, each 0-255): triggers a `console.log` response: `noted.` No email.
|
||||||
|
- The string `fifth`: redirects to `/record`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The archive listing
|
||||||
|
|
||||||
|
Homepage shows five entries. `/archive` shows the full list. All entries are `[ SEALED ]` or `[ REDACTED ]` except two, which are `[ PARTIAL ]` and link to actual content.
|
||||||
|
|
||||||
|
Full list:
|
||||||
|
|
||||||
|
1. `the fifth field.txt` — SEALED
|
||||||
|
2. `correspondence, 1994–1997.pdf` — REDACTED
|
||||||
|
3. `on the successor protocol` — SEALED
|
||||||
|
4. `meeting transcript — location withheld` — PARTIAL → links to `/1997`
|
||||||
|
5. `addendum to the addendum` — SEALED
|
||||||
|
6. `the proposal, as submitted` — SEALED
|
||||||
|
7. `the proposal, as amended` — SEALED
|
||||||
|
8. `the proposal, as received` — REDACTED
|
||||||
|
9. `correspondence with [NAME WITHHELD], Anno 1996` — SEALED
|
||||||
|
10. `on the matter of the fifth` — PARTIAL → links to `/record`
|
||||||
|
11. `auction records, Anno 2011–Anno 2014` — REDACTED
|
||||||
|
12. `the list of beneficiaries` — SEALED
|
||||||
|
13. `the list of dissenters` — SEALED
|
||||||
|
14. `the list of the deceased` — SEALED
|
||||||
|
|
||||||
|
The last three are in a slightly smaller font, slightly muted. No visual emphasis, but they are the ones that will stay with a reader.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. The 404 page
|
||||||
|
|
||||||
|
Title: `the record is incomplete.`
|
||||||
|
|
||||||
|
Body (serif italic, centered):
|
||||||
|
|
||||||
|
> You have requested a record that is sealed, redacted, or was never committed to the archive.
|
||||||
|
>
|
||||||
|
> This is not unusual.
|
||||||
|
>
|
||||||
|
> Return to [the index](/), or do not.
|
||||||
|
|
||||||
|
Below, in monospace muted: `404 — requested path: /<path>`
|
||||||
|
|
||||||
|
If the requested path matches certain strings, the 404 page's body changes:
|
||||||
|
|
||||||
|
- `/join`, `/signup`, `/register`, `/apply`, `/membership`: Title becomes `"We do not accept applications."` Body: *"Applications have never been open. They will not open. The matter is closed."*
|
||||||
|
- `/admin`, `/wp-admin`, `/wp-login.php`: Title remains standard, but the `console.log` on page load reads: `we see you.`
|
||||||
|
- `/logout`: Title: `"One does not log out."* Body: *"You were never authenticated in a way that permits logging out. The session was ambient. It persists."*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Headers and meta
|
||||||
|
|
||||||
|
HTTP response headers on every page:
|
||||||
|
|
||||||
|
```
|
||||||
|
Server: postel/4.1
|
||||||
|
X-Fifth-Octet: observed
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
Strict-Transport-Security: max-age=31536000
|
||||||
|
Referrer-Policy: no-referrer
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Server: postel/4.1` is not a real server. It is named for Jon Postel. `4.1` is a wink — IPv4, plus one.
|
||||||
|
|
||||||
|
No `X-Powered-By`. No analytics. No tracking cookies. The footer promise (`NO COOKIES. NO ANALYTICS.`) must be literally true. This is load-bearing — the paranoia reads as genuine only if it is genuine.
|
||||||
|
|
||||||
|
`<meta>` tags on homepage:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<meta name="description" content="There is a fifth.">
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<meta name="author" content="— authorship withheld">
|
||||||
|
<!-- the fifth is not a metaphor -->
|
||||||
|
```
|
||||||
|
|
||||||
|
`robots.txt`:
|
||||||
|
|
||||||
|
```
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /vault/
|
||||||
|
Disallow: /minutes/
|
||||||
|
Disallow: /correspondence/
|
||||||
|
Disallow: /1997/
|
||||||
|
|
||||||
|
# we ask politely. we do not require.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Console behavior
|
||||||
|
|
||||||
|
On every page load, the browser console receives the following, in this order:
|
||||||
|
|
||||||
|
1. A blank line.
|
||||||
|
2. Serif-styled (via `%c` CSS) message: `You found this quickly. That is either a good sign or a bad one.` (style: `color: #a89cd9; font-family: serif; font-size: 14px; font-style: italic;`)
|
||||||
|
3. A `console.group` labeled `[REDACTED]`, collapsed. If expanded, contains:
|
||||||
|
- ASCII art of five dots (`· · · · ·`)
|
||||||
|
- The line: `the record has five paragraphs. count them.`
|
||||||
|
4. A final line, monospace muted: `nothing further.`
|
||||||
|
|
||||||
|
On the blog post page, additionally: scrolling to `#p-05` or `#p-06` triggers token fragment logs as described in §7.
|
||||||
|
|
||||||
|
On the 404 page for suspicious paths, additional messages as described in §10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Technical implementation notes
|
||||||
|
|
||||||
|
- Static site with a thin backend for IRC webchat proxy and the contact-form puzzle validator.
|
||||||
|
- Suggested stack: Astro or plain HTML + small Go/Python backend for the few dynamic routes. Nginx for custom headers and the `/robots.txt` served with the right content-type.
|
||||||
|
- The pulsing fifth octet on the homepage: pure CSS animation, 4s ease-in-out infinite alternate on opacity.
|
||||||
|
- IRC: any ircd will do. Charybdis, ngIRCd, or UnrealIRCd. Configure `#fifth` as +nt, `#fifth-dot` as +nti. No services needed; no NickServ. Anonymity is the point.
|
||||||
|
- Webchat: thelounge, kiwiirc, or a minimal custom one. Self-hosted.
|
||||||
|
- TLS certificate: ensure `irc.cultfifthoctet.org` is a SAN entry on the main cert. Makes the back door discoverable via `openssl s_client -connect cultfifthoctet.org:443 -showcerts`.
|
||||||
|
|
||||||
|
### DNS records
|
||||||
|
|
||||||
|
```
|
||||||
|
cultfifthoctet.org. A <ip>
|
||||||
|
cultfifthoctet.org. AAAA <ipv6> ; we are not above irony
|
||||||
|
irc.cultfifthoctet.org. A <ip>
|
||||||
|
cultfifthoctet.org. TXT "v=fifth; segment=3/5; value=<fragment>"
|
||||||
|
cultfifthoctet.org. TXT "we do not comment on the other segments."
|
||||||
|
_fifth._tcp.cultfifthoctet.org. SRV 0 5 6667 irc.cultfifthoctet.org.
|
||||||
|
```
|
||||||
|
|
||||||
|
The second TXT record is the flex.
|
||||||
|
|
||||||
|
The SRV record is correct. It advertises the IRC server at the _fifth service. Most visitors will never look. The few who do will understand.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. What NOT to build
|
||||||
|
|
||||||
|
- No "Learn more" page.
|
||||||
|
- No about page.
|
||||||
|
- No team page. The members page is the team page, and it obscures more than it reveals.
|
||||||
|
- No privacy policy (the footer is the privacy policy).
|
||||||
|
- No terms of service.
|
||||||
|
- No pricing. We are not selling anything.
|
||||||
|
- No newsletter archive. The solstice email is not archived.
|
||||||
|
- No search. If you are searching this site, you are either lost or looking for something you will not find.
|
||||||
|
- No dark mode toggle. The site is dark. There is no light mode. There has never been a light mode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Final notes
|
||||||
|
|
||||||
|
The whole thing works because it commits. Every element that breaks character — a helpful tooltip, a "get started" button, a stray emoji, a second accent color — unravels several others. The cult is funny because the cult is sincere. The sincerity is the craft.
|
||||||
|
|
||||||
|
If something in this spec feels too on-the-nose, err toward cutting rather than softening. Absence is the tool.
|
||||||
|
|
||||||
|
Five dots. No more.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix A. Build log — Anno 2026 · 20 April
|
||||||
|
|
||||||
|
First pass at implementation, on branch `rework`. Single-container Caddy deploy preserved. IRC server, webchat, DNS records, and the `irc.` SAN are still outside the repo's responsibility.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
- Caddy 2.7.6 serving a static tree at `/srv`, with a handful of non-static handlers configured in the Caddyfile.
|
||||||
|
- No backend service. No database. No analytics. The footer promise is literal.
|
||||||
|
- Deploy path unchanged: Woodpecker builds the `Containerfile`, pushes to `git.shrug.pw/neil/cultfifthoctet`, `nginx.yml` rolls it out behind nginx-ingress + cert-manager.
|
||||||
|
|
||||||
|
### Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
Caddyfile — routing, headers, error pages, token-redirect
|
||||||
|
Containerfile — FROM caddy:2.7.6; COPY Caddyfile + site/
|
||||||
|
nginx.yml — unchanged
|
||||||
|
.woodpecker.yml — unchanged (still main-only)
|
||||||
|
scripts/
|
||||||
|
gen-members.py — one-shot generator for the 27 portrait SVGs
|
||||||
|
site/
|
||||||
|
index.html — homepage (§4)
|
||||||
|
record/ — "On the Matter of the Fifth" (§5)
|
||||||
|
members/ — roster (§6)
|
||||||
|
archive/ — full 14-entry list (§9)
|
||||||
|
contact/ — address form (§7, Layer 6)
|
||||||
|
manifesto/ — single sentence
|
||||||
|
vault/ — redacted minutes scan
|
||||||
|
1997/ — IRC log fragment
|
||||||
|
login/ — token form
|
||||||
|
.well-known/fifth — plaintext "acknowledged.\n"
|
||||||
|
robots.txt — §11
|
||||||
|
favicon.svg — V sigil
|
||||||
|
404.html — default 404
|
||||||
|
404-applications.html
|
||||||
|
404-logout.html
|
||||||
|
404-minutes.html
|
||||||
|
static/
|
||||||
|
css/site.css — palette, typography, pulse animation, five-dot dividers
|
||||||
|
js/site.js — console liturgy, hashchange token fragments, form branching
|
||||||
|
img/members/*.svg — 27 procedurally-generated obfuscations
|
||||||
|
```
|
||||||
|
|
||||||
|
The previous `index.html` ("Expect the Fifth Octet") and the stray `niusgmrvc.txt` are removed.
|
||||||
|
|
||||||
|
### What is implemented as specified
|
||||||
|
|
||||||
|
- Palette, typography (system-font fallback for Source Serif 4 / JetBrains Mono), layout, narrow reading column, five-dot dividers, the V sigil, the dim pulsing fifth segment on the homepage.
|
||||||
|
- Global response headers: `Server: postel/4.1`, `X-Fifth-Octet: observed`, `X-Frame-Options: DENY`, HSTS, `Referrer-Policy: no-referrer`, no `X-Powered-By`.
|
||||||
|
- `/.well-known/fifth` returns plaintext `acknowledged.` and overrides `X-Fifth-Octet` with a base64 string that decodes to `the record has five paragraphs. count them.` (Layer 2 of the puzzle.)
|
||||||
|
- Console liturgy on every page: blank line → serif-italic greeting → collapsed `[REDACTED]` group containing five dots and the paragraph hint → muted `nothing further.`
|
||||||
|
- Homepage: all cabal-version mockup elements, plus the two HTML comments (`the fifth is not a metaphor` and `see /vault/`) and the meta tags.
|
||||||
|
- `/record`: full verbatim text. Paragraph IDs `p-01`, `p-02`, `p-03`, `p-04`, `p-07` only. Zero-width-space payload `/vault/#1994` embedded in "sealed, and we ask that it remain sealed" (inside a `font-size:0` span with `user-select:all` so copy-paste retrieves the fragment). Trailing comment `<!-- he did not hold his peace. we did. -->`. Hashchange handler on `#p-05`/`#p-06` prints token fragments to the console.
|
||||||
|
- `/members`: 27 cards with irregular `grid-row: span 2` tall cards. The duplicate `:17` is present. The removed `:1337` lives in an HTML comment between entries 14 and 15. `:6667` has `data-next-seen="2026-10-14"` with `[ONLINE]` rendered in accent color and no photo. Port 20's portrait filename is `gerald.svg`. Entry 18's portrait is a real QR code (generated with `qrencode`) whose payload links back to `/members/`. Port-number tooltips via `data-tt` attributes; three of them are `tz: —`, `tz: [REDACTED]`, `tz: fifth.`.
|
||||||
|
- `/archive`: 14 entries, last three muted and smaller; `[ PARTIAL ]` rows link to `/1997` and `/record`.
|
||||||
|
- `/contact`: the address plate `172.16.0.1.<?>` and a form. Client-side JS compares against `172.16.0.1.26.7` and surfaces `acknowledged.` locally. Caddy accepts the POST silently (204).
|
||||||
|
- `/manifesto`: the single sentence.
|
||||||
|
- `/vault`: a grayscale SVG "scan" of redacted minutes with the marginal hand-written sequence (`4 · 8 · 15 · 16 · 23 · 42`) and a `#1994` caption. An HTML comment carries the "EXIF" hint, since SVG has no EXIF.
|
||||||
|
- `/1997`: IRC-style log fragment with nicks and location redacted, the reversed-channel-name line intact.
|
||||||
|
- `/login/grant?t=<known-token>` → 302 to `https://irc.cultfifthoctet.org/`. Unknown tokens fall through to the standard 404.
|
||||||
|
- `robots.txt` with the spec's exact contents.
|
||||||
|
- 404 routing: `/join`, `/signup`, `/register`, `/apply`, `/membership` serve the applications page; `/logout` serves the log-out page; anything under `/minutes` serves the minutes page; `/admin`, `/wp-admin`, `/wp-login.php` get the standard 404 plus a `we see you.` console message (triggered client-side via `data-notfound="1"` on the body).
|
||||||
|
- Homepage subscribe form: `fifth` redirects to `/record/`; valid IPv4+ (five octets 0–255) prints `noted.` to the console; anything else clears silently. Caddy accepts the POST silently (204). No email persistence.
|
||||||
|
|
||||||
|
### Deliberate deviations from the spec
|
||||||
|
|
||||||
|
These were discussed and agreed before building; recording them here so the next reader knows why.
|
||||||
|
|
||||||
|
1. **`/vault` alternation is client-side, not server-side.** Caddy has no per-request state and the few CEL time-matchers available in `http.matchers.expression` do not expose minute-parity cleanly. The page uses `sessionStorage` to count visits and rewrites its own body to a styled 503 notice on even-numbered visits. Reads the same to any visitor who returns.
|
||||||
|
2. **EXIF on the vault scan** is replaced with an HTML comment `<!-- consult the well-known. -->`. SVG is not a raster format and does not carry EXIF. The hint is still present; the delivery mechanism is more honest about its medium.
|
||||||
|
3. **No DNS-side content.** The `TXT` records (Layer 4 of the intended path), the `SRV` record, `AAAA`, and the `irc.cultfifthoctet.org` SAN on the TLS certificate all remain manual administrative work outside this repo. The intended puzzle path breaks at Layer 4 until those are in place.
|
||||||
|
4. **No IRC server or webchat.** `/login/grant` is wired to redirect to `https://irc.cultfifthoctet.org/` when presented with the known token; the redirect target will 404 until the ircd and webchat are deployed.
|
||||||
|
5. **No email persistence for the solstice send.** The subscribe form accepts silently; nothing is stored, nothing will be mailed in December. If the solstice acknowledgment is wanted, a sidecar is the cheapest addition.
|
||||||
|
6. **Fonts rely on local fallback** rather than bundled WOFF2 files. The CSS declares Source Serif 4 / JetBrains Mono and falls back through humanist-serif and `ui-monospace` stacks. No Google Fonts link — that would violate §11. If the rendering on production is insufficient, bundling WOFF2s is a follow-up.
|
||||||
|
7. **`/login` accepts a single hardcoded token.** The token is a 64-character base32 string in the Caddyfile; rotate by editing it. The design intent was a `map` of tokens, but Caddy's map directive inside a route is awkward with `redir`; a single `@matcher + query` pair is cleaner for one valid value.
|
||||||
|
8. **Member portraits are procedurally generated SVGs**, not sourced photographs. The obfuscation kinds called out in §6 are approximated: solid silhouettes, CSS-seeded pixel grids, SVG `feGaussianBlur` over silhouettes, overexposed radial gradients, a drawn back-of-head, double-exposure (two offset silhouettes), a rotated photo-of-a-photo with a visible thumb at the bottom, a phone-frame mirror-selfie with a flash dot and a hand entering from the top-right, a drawn wooden chair for `:465` saved as `gerald.svg`, a real QR code for `:389`, and the text-card `[PHOTO WITHHELD BY REQUEST]` where called for. Appropriate for a site that renders under 100KB of images in total.
|
||||||
|
|
||||||
|
### Not yet built, but straightforward to add
|
||||||
|
|
||||||
|
- A small sidecar for the spec's more dynamic bits: true per-visit `/vault` alternation, contact-form validator, subscribe-form email queue for the solstice send, and proper token minting/verification.
|
||||||
|
- Bundled WOFF2 fonts.
|
||||||
|
- The ircd + webchat manifests (Kubernetes side), TLS SAN on `irc.cultfifthoctet.org`, and the DNS records.
|
||||||
|
- `:host=` style per-page `<title>` discipline; a few pages currently title as `"— cfo.org"`, which is intentionally unadorned but could be more specific.
|
||||||
|
|
||||||
|
### Local development
|
||||||
|
|
||||||
|
```
|
||||||
|
SITE_ROOT="$PWD/site" caddy run --config Caddyfile --adapter caddyfile
|
||||||
|
# defaults: listens on :8080, root=site/
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Caddyfile` honors `$SITE_ROOT` so the same file works inside the container (where the site tree is copied to `/srv`) and at the command line.
|
||||||
|
|
||||||
|
### Addendum — Layer 6 correction
|
||||||
|
|
||||||
|
The original §7 Layer 6 called for a six-segment address derived from a port number (`172.16.0.1.26.7`). The corrected Layer 6: the answer is `5`. The clue is everywhere and nowhere — five dots in every divider, five paragraphs in the record (counted including the two redacted), the V in the sigil, the site's name. Wrong guesses respond `no.` in muted monospace. The correct guess responds `at last.` in accent violet and logs fragment 5/5 to the console.
|
||||||
|
|
||||||
|
Implementation changes:
|
||||||
|
|
||||||
|
- `site/static/js/site.js`: replaced the string-match against the six-segment address with a comparison to `"5"`. `no.` / `at last.` are set on the `.out` div with muted / accent colour respectively. The correct guess also prints `fragment 5/5 :: PFXW24DMNF2HI` to the console in the accent style used by the other fragment logs.
|
||||||
|
- `site/static/js/site.js`: the `/record#p-06` console fragment is renumbered from `5/5` to `4/5` to make room for the contact page as the true fifth. Fragment 1/5 remains the DNS `TXT` record (external). Fragment 3/5 remains the reversed-channel hint from `/1997`.
|
||||||
|
- `site/contact/index.html` + `site/static/css/site.css`: input narrowed from 260px to 120px. The inline `color: var(--accent)` on `.out` is removed; the colour is now set per-outcome from JS, with a small `.out` block in the stylesheet handling spacing and font.
|
||||||
|
- `Caddyfile`: unchanged. `/contact/attest` continues to accept silently with a 204. All validation remains client-side.
|
||||||
|
|
||||||
|
### Things worth checking manually
|
||||||
|
|
||||||
|
- Select the middle of the "sealed, and we ask that it remain sealed" phrase on `/record`, copy, paste into a plaintext buffer. The `/vault/#1994` fragment should appear.
|
||||||
|
- Visit `/vault/` twice in the same tab. The second visit renders a 503 page.
|
||||||
|
- DevTools console on `/record/#p-05` — the token fragment is printed.
|
||||||
|
- DevTools console on `/admin` — `we see you.`
|
||||||
|
- `curl -I https://cultfifthoctet.org/` — `Server: postel/4.1`.
|
||||||
|
- `curl -I https://cultfifthoctet.org/.well-known/fifth` — a different `X-Fifth-Octet` from the site-wide default.
|
||||||
|
|
||||||
|
Five dots. No more.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix B. Build log — Anno 2026 · 20 April (sidecar)
|
||||||
|
|
||||||
|
A small Go binary, `fifth`, is added. Single file (`sidecar/main.go`), stdlib + one dep (`modernc.org/sqlite`, pure Go, no CGO). Built into the same image as Caddy via a multi-stage Containerfile; run as a second container in the same Pod.
|
||||||
|
|
||||||
|
### Why
|
||||||
|
|
||||||
|
The earlier Caddy-only pass approximated four behaviours with client-side JS or path-match tricks. Those approximations were fine for getting the site up and none of them showed on the surface, but the spec's implications — real per-visit vault state, server-validated contact answer, persisted subscribe queue, tokens that can rotate — all want real state. A sidecar is the smallest thing that buys all four.
|
||||||
|
|
||||||
|
### What it handles
|
||||||
|
|
||||||
|
| Path | Method | Behaviour |
|
||||||
|
|---|---|---|
|
||||||
|
| `/vault/` and `/vault` | GET | Counts each visit in SQLite. Even-numbered visits respond 503 with a themed body (served directly from the sidecar). Odd-numbered visits respond 200 with `X-Fifth-Passthrough: 1`, which Caddy uses via `handle_response` to serve the real `/vault/index.html`. The client-side `sessionStorage` fake is removed. |
|
||||||
|
| `/contact/attest` | POST | `guess=5` → `200 at last.\n<fragment>\n` with `X-Fifth-Fragment: fragment 5/5 :: PFXW24DMNF2HI` header. Anything else → `200 no.\n`. |
|
||||||
|
| `/correspondence` | POST | Classifies the input (`email`, `ipv4plus`, `fifth`, `blank`, `other`) and records it in SQLite with a timestamp, then returns 204. The client-side JS still handles the `fifth → /record` redirect and the `ipv4plus → console.log("noted.")` console message before the POST is sent, because those are interaction-layer concerns. |
|
||||||
|
| `/login/grant` | GET | Constant-time compare against `FIFTH_TOKEN` (default hard-coded, override via env). Match → 302 to `FIFTH_TOKEN_DEST` (default `https://irc.cultfifthoctet.org/`). Miss → 404, which Caddy's `handle_errors` renders with the standard "the record is incomplete." page. |
|
||||||
|
| `/_fifth/healthz` | GET | 204, for the k8s readiness probe. |
|
||||||
|
|
||||||
|
No admin surface. No management endpoint. The cult is not administered.
|
||||||
|
|
||||||
|
### State
|
||||||
|
|
||||||
|
A single SQLite file at `$FIFTH_DB` (default `/var/lib/fifth/fifth.db`). Two tables:
|
||||||
|
|
||||||
|
- `correspondence(id, addr, kind, created)` — the solstice queue, such as it is.
|
||||||
|
- `counters(name, value)` — currently one row, `vault_visits`.
|
||||||
|
|
||||||
|
WAL journal mode. One writer. The `Deployment` uses `strategy: Recreate` so there is never a second pod trying to open the same file.
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
`nginx.yml` now creates:
|
||||||
|
|
||||||
|
- A `PersistentVolumeClaim` `fifth-data`, 256Mi, RWO.
|
||||||
|
- A two-container Pod: `caddy` (unchanged command) and `fifth` (overrides entrypoint to `/usr/local/bin/fifth`). Both point at the same image, differing only in command.
|
||||||
|
- The `fifth` container optionally reads `FIFTH_TOKEN` from a Secret named `fifth-secrets`. Absent the Secret, it falls back to the built-in default. Provision the Secret out-of-band or tolerate the default.
|
||||||
|
|
||||||
|
The `Service` and `Ingress` are unchanged — Caddy is still the only ingress point; the sidecar is localhost-only inside the Pod.
|
||||||
|
|
||||||
|
### Caddy wiring
|
||||||
|
|
||||||
|
Caddyfile now reverse-proxies the four sidecar paths to `{$FIFTH_UPSTREAM:127.0.0.1:8090}`. The `/vault` handler uses `reverse_proxy { handle_response @passthrough { rewrite * /vault/index.html; file_server } }` so the sidecar acts as a gatekeeper, not a file-serving path. The `/login/grant` handler uses `handle_response @bad_token status 404 { error 404 }` so a bad token triggers the standard 404 pipeline.
|
||||||
|
|
||||||
|
`/.well-known/fifth` remains Caddy-native. Every other path is still a static file.
|
||||||
|
|
||||||
|
### Local development
|
||||||
|
|
||||||
|
```
|
||||||
|
# Terminal 1: sidecar
|
||||||
|
cd sidecar && go build -o ../fifth . && cd ..
|
||||||
|
FIFTH_DB=/tmp/fifth.db FIFTH_ADDR=:8090 ./fifth
|
||||||
|
|
||||||
|
# Terminal 2: caddy
|
||||||
|
SITE_ROOT="$PWD/site" FIFTH_UPSTREAM=127.0.0.1:8090 \
|
||||||
|
caddy run --config Caddyfile --adapter caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit `http://localhost:8080/`. The `FIFTH_UPSTREAM` default matches the in-pod setup; for local dev you only need to override if the sidecar is elsewhere.
|
||||||
|
|
||||||
|
### Changes elsewhere
|
||||||
|
|
||||||
|
- `site/vault/index.html`: the client-side `sessionStorage` visit-counter block is removed. The page is now authoritative when served.
|
||||||
|
- `site/static/js/site.js`: the contact form now posts to the server and surfaces the response; the correct/wrong branching is still reflected in the DOM and the token fragment is still logged to the console (via the `X-Fifth-Fragment` header). A `catch` block keeps the page sane if the sidecar is unreachable during dev.
|
||||||
|
- `Containerfile`: now a two-stage build. Stage 1 compiles `fifth` with `CGO_ENABLED=0`. Stage 2 is the Caddy image with the binary copied to `/usr/local/bin/fifth` and the site tree copied to `/srv`.
|
||||||
|
- `sidecar/main.go`, `sidecar/go.mod`, `sidecar/go.sum`: new.
|
||||||
|
- `nginx.yml`: two containers, one PVC, `Recreate` strategy, readiness probes.
|
||||||
|
|
||||||
|
### Deliberate deviations (carried forward)
|
||||||
|
|
||||||
|
- The client-side correspondence branching (`fifth → /record`, IPv4+ → `console.log("noted.")`) still lives in JS. The server records the submission regardless, but the interaction is client-observable, which matches the tonal intent.
|
||||||
|
- The `X-Fifth-Fragment` header is an out-of-band channel — a reader who opens DevTools sees more than a reader who doesn't. The site never announces this.
|
||||||
|
|
||||||
|
### Not yet built
|
||||||
|
|
||||||
|
- Bundled WOFF2 fonts.
|
||||||
|
- The solstice email job that reads `correspondence.addr` rows and mails `acknowledged.` with an empty body. Needs SMTP credentials and a CronJob manifest.
|
||||||
|
- IRC server (`ngircd` or similar) and webchat pod at `irc.cultfifthoctet.org`. Until these exist, `/login/grant` with a valid token redirects to a host that does not answer.
|
||||||
|
- DNS records (TXT, SRV, AAAA, irc A) and the `irc.` SAN on the TLS certificate.
|
||||||
|
- A smoke test in Woodpecker to catch regressions on the sidecar paths.
|
||||||
|
|
||||||
|
Five dots. No more.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix B — addendum. Anno 2026 · 21 April (Compose / Coolify)
|
||||||
|
|
||||||
|
The Kubernetes deployment target has been retired in favour of Docker
|
||||||
|
Compose on Coolify (deploy.shrug.host, Traefik as the load balancer).
|
||||||
|
Nothing about the application changes; the delivery mechanism changes.
|
||||||
|
|
||||||
|
### What moved
|
||||||
|
|
||||||
|
- `nginx.yml` (Deployment + Service + Ingress + PVC) is deleted.
|
||||||
|
- `docker-compose.yml` replaces it. Two services, one image:
|
||||||
|
- `caddy` — the default CMD of the image; the only service exposed to
|
||||||
|
Traefik. Labels on this service declare the HTTPS router for
|
||||||
|
`cultfifthoctet.org` + `www.`, an HTTP→HTTPS redirect, and a TLS
|
||||||
|
`domains` block whose SANs cover `www.` and `irc.`. The `irc.` SAN is
|
||||||
|
requested now so Phase 2 (the IRC stack) can attach without a cert
|
||||||
|
re-issue.
|
||||||
|
- `fifth` — same image, command overridden to `/usr/local/bin/fifth`.
|
||||||
|
Owns a named volume `fifth-data` mounted at `/var/lib/fifth`. Reaches
|
||||||
|
Caddy only via an internal bridge network. No Traefik labels.
|
||||||
|
- `.woodpecker.yml` is deleted. Coolify builds from git on webhook, so
|
||||||
|
the parallel CI pipeline is unnecessary. A CI smoke test, when it
|
||||||
|
exists, will be added back in a different form.
|
||||||
|
- `.env.example` documents the two secrets Coolify is expected to inject
|
||||||
|
(`FIFTH_TOKEN`, `FIFTH_TOKEN_DEST`). Absent either, the baked-in defaults
|
||||||
|
from `sidecar/main.go` apply.
|
||||||
|
|
||||||
|
### What didn't move
|
||||||
|
|
||||||
|
- Single-writer SQLite guarantee: Compose with no replica count + a
|
||||||
|
single `fifth` service is the same constraint as `strategy: Recreate`.
|
||||||
|
- Caddy remains the only ingress; the sidecar is reachable only at
|
||||||
|
`fifth:8090` on the internal network. `FIFTH_UPSTREAM` is set to that
|
||||||
|
name in the Caddy service's env.
|
||||||
|
- Health checks: Compose `healthcheck` stanzas replace the Kubernetes
|
||||||
|
readiness probes, pointing at `/` on Caddy and `/_fifth/healthz` on
|
||||||
|
the sidecar.
|
||||||
|
|
||||||
|
### Why two services instead of one
|
||||||
|
|
||||||
|
The earlier plan flirted with a single container running both processes
|
||||||
|
via a supervisor. The plan settled on two compose services sharing one
|
||||||
|
image instead. Same image build, same multi-stage Containerfile; two
|
||||||
|
runtime units with independent logs, independent restart, and no
|
||||||
|
supervisor to babysit. The K8s two-container-Pod pattern translates
|
||||||
|
directly.
|
||||||
|
|
||||||
|
### TLS and the IRC SAN
|
||||||
|
|
||||||
|
Traefik requests the certificate for `cultfifthoctet.org` with SANs for
|
||||||
|
`www.cultfifthoctet.org` and `irc.cultfifthoctet.org` via the
|
||||||
|
`domains` block on the HTTPS router. DNS for `irc.` does not need to
|
||||||
|
resolve yet — ACME DNS-01 is not in use; HTTP-01 will only fire when a
|
||||||
|
router actually matches the host. When Phase 2 lands, the IRC service
|
||||||
|
will attach a separate Traefik TCP router on port 6697 and reuse the
|
||||||
|
same cert from the Traefik store.
|
||||||
|
|
||||||
|
### Local development
|
||||||
|
|
||||||
|
Compose works locally too:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit `http://localhost` — Caddy listens on 8080 inside the
|
||||||
|
container; Coolify's Traefik terminates TLS and proxies to :8080. For a
|
||||||
|
purely local session with Traefik out of the picture, the previous
|
||||||
|
process-level workflow (run `fifth` and `caddy` directly) is still
|
||||||
|
supported and remains the fastest inner loop.
|
||||||
|
|
||||||
|
### Not yet built (unchanged from the previous Appendix B)
|
||||||
|
|
||||||
|
- Bundled WOFF2 fonts.
|
||||||
|
- Solstice email CronJob (now a compose sibling + host cron, or a small
|
||||||
|
one-shot container invoked on a schedule by Coolify — TBD).
|
||||||
|
- IRC server and webchat at `irc.cultfifthoctet.org`.
|
||||||
|
- DNS records and `irc.` A/AAAA.
|
||||||
|
- A CI smoke test.
|
||||||
|
|
||||||
|
Five dots. No more.
|
||||||
|
|
||||||
90
docker-compose.yml
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# cultfifthoctet.org — Coolify / Compose deployment.
|
||||||
|
#
|
||||||
|
# Two services, one image:
|
||||||
|
# caddy — serves the static site and reverse-proxies four paths to fifth.
|
||||||
|
# fifth — the Go sidecar. Owns the SQLite file on a named volume.
|
||||||
|
#
|
||||||
|
# Both services use the same image (multi-stage Containerfile builds the
|
||||||
|
# Caddy runtime with the fifth binary at /usr/local/bin/fifth). Caddy is the
|
||||||
|
# only service exposed to Traefik. The sidecar is reachable at fifth:8090
|
||||||
|
# on the internal network only.
|
||||||
|
#
|
||||||
|
# Coolify is expected to build from git on webhook; no registry push needed.
|
||||||
|
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
build: &fifth_build
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
image: &fifth_image cultfifthoctet:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
SITE_ROOT: /srv
|
||||||
|
FIFTH_UPSTREAM: fifth:8090
|
||||||
|
depends_on:
|
||||||
|
fifth:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- coolify
|
||||||
|
- fifth_internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network=coolify
|
||||||
|
# ---- HTTPS router ----
|
||||||
|
- traefik.http.routers.cfo.rule=Host(`cultfifthoctet.org`) || Host(`www.cultfifthoctet.org`)
|
||||||
|
- traefik.http.routers.cfo.entrypoints=websecure
|
||||||
|
- traefik.http.routers.cfo.tls=true
|
||||||
|
- traefik.http.routers.cfo.tls.certresolver=letsencrypt
|
||||||
|
# SAN pre-provisioning: request a cert covering irc. now so Phase 2
|
||||||
|
# (IRC stack) can attach without re-issuing.
|
||||||
|
- traefik.http.routers.cfo.tls.domains[0].main=cultfifthoctet.org
|
||||||
|
- traefik.http.routers.cfo.tls.domains[0].sans=www.cultfifthoctet.org,irc.cultfifthoctet.org
|
||||||
|
- traefik.http.routers.cfo.service=cfo
|
||||||
|
- traefik.http.services.cfo.loadbalancer.server.port=8080
|
||||||
|
# ---- HTTP → HTTPS redirect ----
|
||||||
|
- traefik.http.routers.cfo-http.rule=Host(`cultfifthoctet.org`) || Host(`www.cultfifthoctet.org`)
|
||||||
|
- traefik.http.routers.cfo-http.entrypoints=web
|
||||||
|
- traefik.http.routers.cfo-http.middlewares=cfo-redirect
|
||||||
|
- traefik.http.middlewares.cfo-redirect.redirectscheme.scheme=https
|
||||||
|
- traefik.http.middlewares.cfo-redirect.redirectscheme.permanent=true
|
||||||
|
|
||||||
|
fifth:
|
||||||
|
build: *fifth_build
|
||||||
|
image: *fifth_image
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["/usr/local/bin/fifth"]
|
||||||
|
environment:
|
||||||
|
FIFTH_ADDR: ":8090"
|
||||||
|
FIFTH_DB: /var/lib/fifth/fifth.db
|
||||||
|
FIFTH_TOKEN: ${FIFTH_TOKEN:-}
|
||||||
|
FIFTH_TOKEN_DEST: ${FIFTH_TOKEN_DEST:-https://irc.cultfifthoctet.org/}
|
||||||
|
volumes:
|
||||||
|
- fifth-data:/var/lib/fifth
|
||||||
|
networks:
|
||||||
|
- fifth_internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8090/_fifth/healthz"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 5s
|
||||||
|
# No Traefik labels: sidecar is localhost-of-the-network only. Caddy is
|
||||||
|
# the single ingress point.
|
||||||
|
|
||||||
|
networks:
|
||||||
|
coolify:
|
||||||
|
external: true
|
||||||
|
fifth_internal:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
fifth-data:
|
||||||
|
# Single-writer SQLite. One fifth container at a time (compose default
|
||||||
|
# with no replicas). Backups via Coolify or host-level snapshot of the
|
||||||
|
# named volume.
|
||||||
305
docs/puzzle.md
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
# Puzzle ledger
|
||||||
|
|
||||||
|
Out-of-universe working document. **Not canon.** Update this *before* changing
|
||||||
|
content, not after. The in-universe spec is `cultfifthoctet-spec.md`; this file
|
||||||
|
is the workbench.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- If you add, remove, or renumber a hidden string, header, fragment, token, or
|
||||||
|
path: update the Artifacts table and add a Changelog entry.
|
||||||
|
- If you open a new track or close an open seam: update Tracks and Open seams.
|
||||||
|
- If you add a file to `lore/`: add a row to the Lore catalogue.
|
||||||
|
- The document itself has no tonal constraints. Tables and bullet points are
|
||||||
|
encouraged. Ugly is fine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Tracks
|
||||||
|
|
||||||
|
Tracks are puzzle arcs aimed at different archetypes of visitor. A single
|
||||||
|
artifact can participate in more than one track.
|
||||||
|
|
||||||
|
### 1.1 Scholar track — live
|
||||||
|
|
||||||
|
**Archetype.** Patient reader. Inspects source. Reads the whole record.
|
||||||
|
Treats the site like a text, not a lock.
|
||||||
|
|
||||||
|
**Entry points.**
|
||||||
|
|
||||||
|
- Homepage `site/index.html` — pulsing fifth octet in the address plate,
|
||||||
|
"There is a fifth.", five-paragraph partial record teaser.
|
||||||
|
- `/record` — the essay proper.
|
||||||
|
|
||||||
|
**Step graph.**
|
||||||
|
|
||||||
|
1. Visitor reads `/record`. The article has paragraph IDs `p-01`, `p-02`,
|
||||||
|
`p-03`, `p-04`, then jumps to `p-07`. Missing `p-05` and `p-06` are the
|
||||||
|
first prompt.
|
||||||
|
2. Visiting `/record#p-05` or `/record#p-06` causes `site/static/js/site.js`
|
||||||
|
to log fragment 2/5 or fragment 4/5 to the JS console.
|
||||||
|
3. The zero-width-space in `p-07` (between "sealed" and ", and we ask") hides
|
||||||
|
the text `/vault/#1994` on copy-paste or on selection. This directs the
|
||||||
|
reader to `/vault`.
|
||||||
|
4. `/vault` alternates (every second visit returns 503 with a themed body;
|
||||||
|
odd visits serve the redacted-minutes SVG page). Caption reads `#1994`.
|
||||||
|
5. The SVG minutes show a marginal hand-written Lost-numbers sequence
|
||||||
|
`4 8 15 16 23 42` and a SEALED stamp dated ANNO MCMXCIV. An HTML comment
|
||||||
|
above the SVG reads `consult the well-known.` — this forwards to
|
||||||
|
`/.well-known/fifth`.
|
||||||
|
6. `/.well-known/fifth` returns plaintext `acknowledged.` with header
|
||||||
|
`X-Fifth-Octet: dGhlIHJlY29yZCBoYXMgZml2ZSBwYXJhZ3JhcGhzLiBjb3VudCB0aGVtLgo=`
|
||||||
|
which base64-decodes to `the record has five paragraphs. count them.`
|
||||||
|
7. The reader counts the paragraphs in `/record`: the post *appears* to have
|
||||||
|
eleven visible paragraphs (12 if signing block is counted), but only five
|
||||||
|
are ID'd `p-01`…`p-04` + `p-07`. The "Layer 6 correction" lesson: the
|
||||||
|
answer is `5`, not a six-segment address. The contact form accepts `5`.
|
||||||
|
8. Visitor visits `/contact`, sees partial address `172.16.0.1.<?>`, submits
|
||||||
|
`5` to the form. Sidecar returns body `at last.` with header
|
||||||
|
`X-Fifth-Fragment: fragment 5/5 :: PFXW24DMNF2HI`. JS echoes fragment to
|
||||||
|
console.
|
||||||
|
|
||||||
|
**Payoff.** Completion of the five-fragment sequence. Visible state:
|
||||||
|
console-only. No "you win" page. This is intentional.
|
||||||
|
|
||||||
|
**Files that participate.** `site/index.html`, `site/record/index.html`,
|
||||||
|
`site/vault/index.html`, `site/.well-known/fifth`, `site/contact/index.html`,
|
||||||
|
`site/static/js/site.js`, `sidecar/main.go` (`/contact/attest`).
|
||||||
|
|
||||||
|
### 1.2 Archaeologist track — live
|
||||||
|
|
||||||
|
**Archetype.** Reads `view-source`. Opens devtools. Clicks weird links.
|
||||||
|
Notices when things are irregular.
|
||||||
|
|
||||||
|
**Entry points.**
|
||||||
|
|
||||||
|
- Homepage HTML comment `<!-- see /vault/ -->` at bottom.
|
||||||
|
- Homepage HTML comment `<!-- the fifth is not a metaphor -->` at top.
|
||||||
|
- `/record` HTML comment `<!-- he did not hold his peace. we did. -->`.
|
||||||
|
- `/members` visual irregularities (withheld photos, duplicate `:17`,
|
||||||
|
HTML-commented `:1337`, `:6667` online with `data-next-seen`).
|
||||||
|
- `/1997` IRC log fragment: "the fourth segment is the channel name reversed."
|
||||||
|
- `/archive` entries marked SEALED/REDACTED/PARTIAL.
|
||||||
|
|
||||||
|
**Step graph.**
|
||||||
|
|
||||||
|
1. Visitor reads `/1997`. Speaker says: "the fourth segment is the channel
|
||||||
|
name reversed." This is the telegraph for an IRC channel whose name,
|
||||||
|
reversed, gives the fourth octet. (Current canon: channel `#fifth-dot`.
|
||||||
|
Reversed: `tod-htfif`. The fourth-segment-as-value is not yet enforced in
|
||||||
|
any surface; it's a seam — see §5.)
|
||||||
|
2. Visitor reads `/members`. Notes:
|
||||||
|
- `:17` appears twice (the site does not acknowledge the duplicate).
|
||||||
|
- `:1337` is HTML-commented with note "removed by request, Anno 2014."
|
||||||
|
- `:6667` is marked ONLINE with `data-next-seen="2026-10-14"`.
|
||||||
|
- `:465` member portrait file is `gerald.svg` (joke on SMTP-over-SSL
|
||||||
|
deprecation; orphan lore hook, see Open seams).
|
||||||
|
- `:194` carries the note "Original IRC port (pre-6667)."
|
||||||
|
3. The `:6667` card + `/1997` channel hint + `data-next-seen` future date
|
||||||
|
telegraph: **there is an IRC server, or there will be.** Visitor who
|
||||||
|
follows the trail expects IRC at `irc.cultfifthoctet.org` or equivalent.
|
||||||
|
4. Token discovery path is currently incomplete (see Open seams §5 — the
|
||||||
|
`NVWWC23E…` login token has no in-site breadcrumb yet; it exists only in
|
||||||
|
`sidecar/main.go`). Fixing this is a known gap.
|
||||||
|
5. Once the token is in hand (out-of-band for now), `/login` accepts it and
|
||||||
|
redirects to `loginDest` (currently `https://irc.cultfifthoctet.org/`).
|
||||||
|
|
||||||
|
**Payoff.** Discovery of IRC as the living surface of the cult.
|
||||||
|
|
||||||
|
**Files that participate.** `site/index.html`, `site/1997/index.html`,
|
||||||
|
`site/members/index.html`, `site/archive/index.html`, `site/login/index.html`,
|
||||||
|
`sidecar/main.go` (`/login/grant`).
|
||||||
|
|
||||||
|
### 1.3 Burglar track — not yet designed
|
||||||
|
|
||||||
|
**Archetype.** Probes inputs. Treats the site like a lock. Tries path
|
||||||
|
traversal, parameter fuzzing, HTTP verbs the form doesn't advertise.
|
||||||
|
|
||||||
|
**Intended shape.** Fake-vuln visitor-counter endpoint. Values lead to a
|
||||||
|
simulated path-traversal surface that returns hand-authored payloads from
|
||||||
|
`lore/`. Never actually traverses the filesystem. See §5 Open seams and
|
||||||
|
spec Appendix B's deferred work list.
|
||||||
|
|
||||||
|
**Status.** Deferred. Build `lore/` contents first.
|
||||||
|
|
||||||
|
### 1.4 IRC track — not yet designed
|
||||||
|
|
||||||
|
**Archetype.** Reads DNS. Reads SRV records. Reads TLS certificate SANs.
|
||||||
|
|
||||||
|
**Intended shape.** Discovery via SAN on main cert + SRV record + `:6667`
|
||||||
|
telegraph. Termination on a TLS IRC port (6697). v6-only listener as joke,
|
||||||
|
v4 listener as operational necessity (v4 discoverable via archaeology).
|
||||||
|
Channel `#fifth-dot`, membership bootstrapped via oper invite.
|
||||||
|
|
||||||
|
**Status.** Deferred. Phase 2 of rollout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Artifacts table
|
||||||
|
|
||||||
|
One row per hidden string, header, token, path, or value. `status` is
|
||||||
|
`live` / `deferred` / `retconned`.
|
||||||
|
|
||||||
|
| artifact | value | locations | tracks | status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| token | `NVWWC23ENFZS4OBNMZXW6ZDGNFZS4S3VONSWG5DFOMFEEX2EGVSWK3TKNBSWY3DP` | `sidecar/main.go:42` (env `FIFTH_TOKEN` override) | archaeologist | live (no in-site breadcrumb yet — seam) |
|
||||||
|
| login redirect dest | `https://irc.cultfifthoctet.org/` | `sidecar/main.go:44` (env `FIFTH_TOKEN_DEST`) | archaeologist, IRC | live |
|
||||||
|
| fragment 1/5 | *not yet placed* | — | scholar | **seam** |
|
||||||
|
| fragment 2/5 | `fragment 2/5 :: GVSWK3TK` | `site/static/js/site.js:23` on `#p-05` hashchange | scholar | live |
|
||||||
|
| fragment 3/5 | *not yet placed* | — | scholar | **seam** |
|
||||||
|
| fragment 4/5 | `fragment 4/5 :: NBSWY3DP` | `site/static/js/site.js:24` on `#p-06` hashchange | scholar | live |
|
||||||
|
| fragment 5/5 | `fragment 5/5 :: PFXW24DMNF2HI` | `sidecar/main.go:36`; delivered via `X-Fifth-Fragment` header on successful `/contact/attest` | scholar | live |
|
||||||
|
| `X-Fifth-Fragment` header | `fragment 5/5 :: …` | `sidecar/main.go:244` | scholar | live |
|
||||||
|
| `X-Fifth-Octet` header default | `observed` | `Caddyfile:19` (all paths except `/.well-known/fifth`) | archaeologist | live |
|
||||||
|
| `X-Fifth-Octet` on well-known | `dGhlIHJlY29yZCBoYXMgZml2ZSBwYXJhZ3JhcGhzLiBjb3VudCB0aGVtLgo=` (base64 → `the record has five paragraphs. count them.`) | `Caddyfile:28` | scholar | live |
|
||||||
|
| `X-Fifth-Passthrough` header | `1` | `sidecar/main.go:193`; consumed by Caddy `Caddyfile:61` to allow-serve `/vault` | internal mechanism | live |
|
||||||
|
| `/.well-known/fifth` body | `acknowledged.\n` | `site/.well-known/fifth`; `Caddyfile:29` | scholar | live |
|
||||||
|
| contact answer | `5` | `sidecar/main.go:243`; `site/static/js/site.js:69` comment | scholar | live |
|
||||||
|
| `/record` missing IDs | `p-05`, `p-06` (hash anchors only; paragraphs between `p-04` and `p-07` have no IDs) | `site/record/index.html`; hashchange handler in `site/static/js/site.js:20-35` | scholar | live |
|
||||||
|
| zero-width-space copy-trap | `\u200b/vault/#1994` inside `p-07` | `site/record/index.html:36` | scholar | live |
|
||||||
|
| vault caption | `#1994` | `site/vault/index.html:86` | scholar | live |
|
||||||
|
| vault SVG comment hint | `consult the well-known.` | `site/vault/index.html:21` | scholar | live |
|
||||||
|
| vault marginal sequence | `4 8 15 16 23 42` (Lost numbers, ref-joke only) | `site/vault/index.html:69-76` | scholar (garnish) | live |
|
||||||
|
| homepage comments | `<!-- the fifth is not a metaphor -->`, `<!-- see /vault/ -->` | `site/index.html:2,88` | archaeologist | live |
|
||||||
|
| record footer comment | `<!-- he did not hold his peace. we did. -->` | `site/record/index.html:60` | archaeologist | live |
|
||||||
|
| members `:17` duplicate | two cards with port `:17`, different years | `site/members/index.html:54,232` | archaeologist | live |
|
||||||
|
| members `:1337` commented | removed by request, Anno 2014 | `site/members/index.html:148` | archaeologist | live |
|
||||||
|
| members `:6667` online | `data-next-seen="2026-10-14"` | `site/members/index.html:241` | archaeologist, IRC | live |
|
||||||
|
| members `:465` portrait file | `gerald.svg` (SMTPS-deprecation joke) | `site/members/index.html:193` | archaeologist (garnish) | live |
|
||||||
|
| members `:194` note | "Original IRC port (pre-6667)." | `site/members/index.html:172` | archaeologist, IRC | live |
|
||||||
|
| members `:123` tooltip | `tz: fifth.` (hints a fifth time zone = fifth octet motif) | `site/members/index.html:142` | archaeologist (garnish) | live |
|
||||||
|
| 1997 log channel hint | "the fourth segment is the channel name reversed." | `site/1997/index.html:33` | archaeologist, IRC | live |
|
||||||
|
| 1997 log fifth hint | "the fifth we do not discuss here." | `site/1997/index.html:36` | archaeologist | live |
|
||||||
|
| console liturgy | "You found this quickly…", "the record has five paragraphs. count them." | `site/static/js/site.js:7-15` | scholar | live |
|
||||||
|
| `/admin` 404 console | "we see you." | `site/static/js/site.js:112` | archaeologist (garnish) | live |
|
||||||
|
| `/apply` etc. 404 variant | `404-applications.html` | `Caddyfile:78-82` | archaeologist | live |
|
||||||
|
| `/logout` 404 variant | `404-logout.html` | `Caddyfile:84-88` | archaeologist | live |
|
||||||
|
| `/minutes` 404 variant | `404-minutes.html` | `Caddyfile:90-94` | archaeologist | live |
|
||||||
|
| response `Server` header | `postel/4.1` | `Caddyfile:11` | archaeologist (garnish) | live |
|
||||||
|
| `/vault` denial cadence | every 2nd visit returns 503 | `sidecar/main.go:184` (`n%2 == 0`) | scholar | live |
|
||||||
|
| sidecar healthcheck | `/_fifth/healthz` → 204 | `sidecar/main.go:81` | internal | live |
|
||||||
|
| correspondence classifier | `blank` / `fifth` / `email` / `ipv4plus` / `other` | `sidecar/main.go:284-298` | internal | live |
|
||||||
|
| homepage `fifth` → redirect | typing `fifth` in subscribe form → `/record/` | `site/static/js/site.js:57` | scholar | live |
|
||||||
|
|
||||||
|
### Base32 decode reference
|
||||||
|
|
||||||
|
The three base32-looking fragments decode as follows (for our own sanity;
|
||||||
|
not for visitor consumption):
|
||||||
|
|
||||||
|
| fragment | base32 tail | decodes to |
|
||||||
|
|---|---|---|
|
||||||
|
| 2/5 | `GVSWK3TK` | `5ikk…` — **TODO verify, may be garbage** |
|
||||||
|
| 4/5 | `NBSWY3DP` | `hello` |
|
||||||
|
| 5/5 | `PFXW24DMNF2HI` | **TODO decode + verify** |
|
||||||
|
|
||||||
|
Note: verify these decodes before any future fragment is added. If the intent
|
||||||
|
is that concatenating the fragments yields a coherent instruction, that's not
|
||||||
|
yet guaranteed by construction. Open seam.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Canonical numbers & dates
|
||||||
|
|
||||||
|
Single source of truth. Justifications included so future-us doesn't renumber
|
||||||
|
for "cleanliness."
|
||||||
|
|
||||||
|
| value | meaning | justification / why this specific number |
|
||||||
|
|---|---|---|
|
||||||
|
| `1981` | IPv4 architecture committed | real history (RFC 791) — the cult's grievance begins here |
|
||||||
|
| `1992` | cult's claimed working-group meeting | predates any credible fifth-octet proposal; deliberately feels just-plausible |
|
||||||
|
| `1994` | minutes in `/vault` caption `#1994` + SEALED stamp `ANNO MCMXCIV` | locks vault to a specific fictional meeting |
|
||||||
|
| `1995` | site EST., :65535 Emeritus member joined | round prehistory |
|
||||||
|
| `1997` | persecution origin year; `/1997` log fragment; several member join dates | the cult's felt beginning — chat-era, pre-IRC-mainstream |
|
||||||
|
| `1998` | replacement protocol declared (homepage) | real history (IPv6 RFC 2460) |
|
||||||
|
| `2011` | pool exhaustion on homepage; `/record` post date | real history (IANA IPv4 exhaustion) — this is the moment the cult goes public with the record |
|
||||||
|
| `2014` | `:1337` removed by request | ten years of site existence beat |
|
||||||
|
| `2026` | current in-universe year ("Anno 2026") | matches real deploy year |
|
||||||
|
| `2026-10-14` | `:6667` `data-next-seen` future date | far enough out to feel like a scheduled event, before a year-end |
|
||||||
|
| `5` | the answer | the whole joke |
|
||||||
|
| `17` | member port, duplicated | QOTD (quote-of-the-day) protocol; also a prime; also visually interesting to duplicate |
|
||||||
|
| `194` | pre-6667 IRC port, one member | historical IRC port; reinforces IRC track |
|
||||||
|
| `465` | Gerald portrait (SMTPS-deprecation joke) | obsolete SMTPS port |
|
||||||
|
| `1337` | member commented-out "removed by request" | lore: leet joke, canonically embarrassing |
|
||||||
|
| `6667` | ONLINE member with `data-next-seen` | cleartext IRC port; telegraphs IRC |
|
||||||
|
| `6697` | IRC TLS port (future) | canonical IRC-over-TLS port |
|
||||||
|
| `31337` | "Elite" member | symmetric with 1337 |
|
||||||
|
| `65535` | Emeritus | largest 16-bit port — founder-ish |
|
||||||
|
| five-dot footer divider `· · · · ·` | footer on every page | five dots = five octets, reinforced everywhere |
|
||||||
|
| 27 member cards | `/members` count | "twenty-seven are listed. others are not." |
|
||||||
|
| 5 fragments | scholar track payoff | matches the fifth-octet motif |
|
||||||
|
| 4 dynamic paths | `/vault`, `/contact/attest`, `/correspondence`, `/login/grant` | the four paths Caddy cannot handle alone; sidecar's reason for existing |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Lore catalogue
|
||||||
|
|
||||||
|
Index of `lore/` documents. These are not served by any endpoint yet.
|
||||||
|
The directory exists so we accumulate material before the Burglar track
|
||||||
|
endpoint is built.
|
||||||
|
|
||||||
|
Naming convention: `YYYY-MM-subject.md` where `YYYY-MM` is the fictional
|
||||||
|
in-universe date. Content is canon-bearing prose, same tone as the spec.
|
||||||
|
|
||||||
|
| filename | era | subject | referenced by | canon-status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| *(empty)* | — | — | — | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Open seams
|
||||||
|
|
||||||
|
Things that look meaningful, or have been set up as if they will be
|
||||||
|
meaningful, but don't yet resolve. These are the places where future drift is
|
||||||
|
most likely.
|
||||||
|
|
||||||
|
1. **Fragment 1/5 and fragment 3/5 do not exist.** Scholar track currently
|
||||||
|
has 2/5, 4/5, 5/5 placed; 1/5 and 3/5 are intentional gaps. Decide:
|
||||||
|
place them in new surfaces (e.g. homepage console, `/manifesto`, `/archive`
|
||||||
|
entry text), or intentionally leave them unreachable as a cult-fits-the-gaps
|
||||||
|
joke. **Leaving unplaced until decided.**
|
||||||
|
2. **Token breadcrumb.** The `NVWWC23E…` login token is not discoverable from
|
||||||
|
any in-site surface. The archaeologist track ends before it starts. Need
|
||||||
|
to place the token (or a derivation of it) somewhere: `.well-known/fifth`
|
||||||
|
extended body, a member's hidden data attribute, or an IRC MOTD once IRC
|
||||||
|
is live.
|
||||||
|
3. **Channel name reversal.** `/1997` says "the fourth segment is the channel
|
||||||
|
name reversed." Currently `#fifth-dot` reversed is `tod-htfif`, which is
|
||||||
|
not numeric. Either: (a) change the channel name to something whose
|
||||||
|
reversal produces a valid octet value (0–255), and rewrite the log line
|
||||||
|
to match; or (b) retcon the log line to a different hint. Not both.
|
||||||
|
4. **`X-Fifth-Octet: observed` default.** Every non-well-known response
|
||||||
|
carries this header. It is currently flavor only. Consider whether it
|
||||||
|
should rotate on a schedule or on specific paths as a further seam.
|
||||||
|
5. **`data-next-seen="2026-10-14"`.** The `:6667` card telegraphs a future
|
||||||
|
date. Something should happen on or around that date — IRC going live,
|
||||||
|
a new archive entry appearing, a `/minutes` variant unlocking — otherwise
|
||||||
|
the attribute is a lie. Pick one before the date arrives.
|
||||||
|
6. **Gerald (`:465`).** Orphan lore hook. Needs a `lore/` document or an
|
||||||
|
archive entry mentioning him by name + port, or remove.
|
||||||
|
7. **Lost-numbers sequence in vault SVG.** `4 8 15 16 23 42` is currently
|
||||||
|
pure garnish. Either decide it's garnish and note it here (done), or
|
||||||
|
assign it meaning and build to it.
|
||||||
|
8. **Vault alternation.** Every 2nd visit returns 503. The denial cadence
|
||||||
|
(2) is arbitrary. If the Burglar track ever exercises the vault counter,
|
||||||
|
the cadence interacts with that puzzle. Decide whether to persist the
|
||||||
|
cadence as-is or make it tunable.
|
||||||
|
9. **Base32 fragment decodes.** Verify all three fragments decode to
|
||||||
|
something coherent (or deliberately incoherent). Currently only 4/5
|
||||||
|
verified (`hello`). Do this before placing 1/5 or 3/5.
|
||||||
|
10. **Duplicate `:17`.** Two cards. The site "does not acknowledge" the
|
||||||
|
duplicate. This is garnish unless some future surface (e.g., IRC
|
||||||
|
`/whois`, archive entry) references "the other :17". Decide.
|
||||||
|
11. **`data-tt="tz: fifth."` on `:123`.** Nothing currently consumes the
|
||||||
|
`data-tt` attribute. Either add a tooltip surface or note it's
|
||||||
|
archaeology-only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Changelog
|
||||||
|
|
||||||
|
Append-only. Date in ISO. Brief enough to grep later.
|
||||||
|
|
||||||
|
- **2026-04-21** — `docs/puzzle.md` created. Initial inventory of Scholar
|
||||||
|
and Archaeologist tracks, artifacts table, canonical numbers, open seams.
|
||||||
|
Burglar and IRC tracks stubbed. `lore/` catalogue empty. Authored as the
|
||||||
|
blocking step before Coolify migration.
|
||||||
40
index.html
@ -1,40 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Cult of the Fifth Octet</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #222222;
|
|
||||||
color: #FFFFFF;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
div.question {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
div.answer {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<section>
|
|
||||||
<div class="question">Expect the Fifth Octet</div>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
64
nginx.yml
@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: caddy
|
|
||||||
namespace: cultfifthoctet
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: caddy
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: caddy
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: caddy
|
|
||||||
image: git.shrug.pw/neil/cultfifthoctet
|
|
||||||
ports:
|
|
||||||
- name: tcp
|
|
||||||
containerPort: 8080
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: caddy
|
|
||||||
namespace: cultfifthoctet
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8080
|
|
||||||
targetPort: 8080
|
|
||||||
protocol: TCP
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: caddy
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
kubernetes.io/ingress.class: nginx
|
|
||||||
nginx.ingress.kubernetes.io/client-max-body-size: 20m
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: 20m
|
|
||||||
name: caddy
|
|
||||||
namespace: cultfifthoctet
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: cultfifthoctet.org
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- backend:
|
|
||||||
service:
|
|
||||||
name: caddy
|
|
||||||
port:
|
|
||||||
number: 8080
|
|
||||||
path: /
|
|
||||||
pathType: Prefix
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- cultfifthoctet.org
|
|
||||||
secretName: default-cert
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Hi!
|
|
||||||
338
scripts/gen-members.py
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate the 27 obfuscated member portraits as SVG files.
|
||||||
|
|
||||||
|
One-shot: run from repo root, commits the output under site/static/img/members/.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
OUT = os.path.join(os.path.dirname(__file__), "..", "site", "static", "img", "members")
|
||||||
|
os.makedirs(OUT, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
SIZE = 240
|
||||||
|
|
||||||
|
|
||||||
|
def write(name, body):
|
||||||
|
path = os.path.join(OUT, f"{name}.svg")
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(body)
|
||||||
|
print("wrote", path)
|
||||||
|
|
||||||
|
|
||||||
|
def svg_open(extra=""):
|
||||||
|
return (
|
||||||
|
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {SIZE} {SIZE}" '
|
||||||
|
f'preserveAspectRatio="xMidYMid slice" {extra}>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bg(color="#1a1722"):
|
||||||
|
return f'<rect width="{SIZE}" height="{SIZE}" fill="{color}"/>'
|
||||||
|
|
||||||
|
|
||||||
|
def silhouette(color="#2a2738", bg_color="#16141f", shoulder_y=180, head_cy=100, head_r=50):
|
||||||
|
"""Head + shoulders silhouette."""
|
||||||
|
return (
|
||||||
|
bg(bg_color)
|
||||||
|
+ f'<circle cx="{SIZE/2}" cy="{head_cy}" r="{head_r}" fill="{color}"/>'
|
||||||
|
+ f'<path d="M {SIZE/2 - 80} {SIZE} '
|
||||||
|
f'C {SIZE/2 - 80} {shoulder_y}, {SIZE/2 - 40} {shoulder_y - 30}, {SIZE/2} {shoulder_y - 40} '
|
||||||
|
f'C {SIZE/2 + 40} {shoulder_y - 30}, {SIZE/2 + 80} {shoulder_y}, {SIZE/2 + 80} {SIZE} Z" '
|
||||||
|
f'fill="{color}"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pixelated(seed, cells):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
cell = SIZE / cells
|
||||||
|
parts = [bg("#0f0e14")]
|
||||||
|
# Build a rough head-shape mask using noise concentrated in center-top
|
||||||
|
for y in range(cells):
|
||||||
|
for x in range(cells):
|
||||||
|
# Weight toward a silhouette shape
|
||||||
|
cx = cells / 2
|
||||||
|
dx = (x - cx) / cx
|
||||||
|
dy = (y - cells * 0.35) / (cells * 0.5)
|
||||||
|
d = (dx * dx + dy * dy) ** 0.5
|
||||||
|
base = max(0.0, 1.0 - d * 0.9)
|
||||||
|
v = base * rng.uniform(0.3, 1.0)
|
||||||
|
g = int(20 + v * 110)
|
||||||
|
parts.append(
|
||||||
|
f'<rect x="{x*cell:.2f}" y="{y*cell:.2f}" width="{cell:.2f}" height="{cell:.2f}" '
|
||||||
|
f'fill="rgb({g},{g-4 if g>4 else 0},{g+6})"/>'
|
||||||
|
)
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def blur_silhouette(stddev=20):
|
||||||
|
return (
|
||||||
|
'<defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%">'
|
||||||
|
f'<feGaussianBlur stdDeviation="{stddev}"/></filter></defs>'
|
||||||
|
f'<g filter="url(#b)">{silhouette(color="#3a3548")}</g>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def overexposed():
|
||||||
|
return (
|
||||||
|
bg("#1a1722")
|
||||||
|
+ '<defs><radialGradient id="o" cx="50%" cy="40%" r="60%">'
|
||||||
|
'<stop offset="0%" stop-color="#e8e5f0"/>'
|
||||||
|
'<stop offset="60%" stop-color="#a49fb4" stop-opacity="0.6"/>'
|
||||||
|
'<stop offset="100%" stop-color="#1a1722" stop-opacity="0"/>'
|
||||||
|
'</radialGradient></defs>'
|
||||||
|
f'<rect width="{SIZE}" height="{SIZE}" fill="url(#o)"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def black_bar_eyes():
|
||||||
|
return (
|
||||||
|
blur_silhouette(stddev=10)
|
||||||
|
+ f'<rect x="30" y="85" width="180" height="26" fill="#0f0e14"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def redaction_bar_eyes_stock():
|
||||||
|
# "obvious stock photo" hinted by the muted gradient face
|
||||||
|
return (
|
||||||
|
bg("#1a1722")
|
||||||
|
+ '<defs><linearGradient id="sp" x1="0" x2="0" y1="0" y2="1">'
|
||||||
|
'<stop offset="0%" stop-color="#6a6578"/>'
|
||||||
|
'<stop offset="100%" stop-color="#2a2738"/></linearGradient></defs>'
|
||||||
|
+ f'<circle cx="{SIZE/2}" cy="96" r="52" fill="url(#sp)"/>'
|
||||||
|
+ f'<rect x="20" y="80" width="200" height="28" fill="#0f0e14"/>'
|
||||||
|
+ f'<path d="M 40 {SIZE} C 40 160, 80 150, {SIZE/2} 148 C 160 150, 200 160, 200 {SIZE} Z" fill="url(#sp)"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def back_of_head():
|
||||||
|
return (
|
||||||
|
bg("#16141f")
|
||||||
|
+ f'<path d="M 60 {SIZE} C 60 160, 90 140, {SIZE/2} 140 C 150 140, 180 160, 180 {SIZE} Z" fill="#2a2738"/>'
|
||||||
|
+ f'<ellipse cx="{SIZE/2}" cy="110" rx="52" ry="55" fill="#191624"/>'
|
||||||
|
# hair indication
|
||||||
|
+ '<path d="M 70 95 Q 120 55 170 95 Q 170 120 120 115 Q 70 120 70 95 Z" fill="#100e18"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def double_exposure():
|
||||||
|
a = f'<g opacity="0.55" transform="translate(-18,0)">{silhouette(color="#2a2738")}</g>'
|
||||||
|
b = f'<g opacity="0.55" transform="translate(18,6)">{silhouette(color="#3a3548")}</g>'
|
||||||
|
return bg("#141220") + a + b
|
||||||
|
|
||||||
|
|
||||||
|
def photo_of_photo():
|
||||||
|
# visible print edges, a thumb
|
||||||
|
inner = silhouette(color="#2a2738", bg_color="#1e1a2a")
|
||||||
|
return (
|
||||||
|
bg("#0a0911")
|
||||||
|
# outer print
|
||||||
|
+ f'<g transform="rotate(-3 {SIZE/2} {SIZE/2})">'
|
||||||
|
+ f'<rect x="28" y="24" width="184" height="192" fill="#1e1a2a" stroke="#3a3548" stroke-width="0.8"/>'
|
||||||
|
+ f'<g transform="translate(28 24) scale(0.766)">{inner}</g>'
|
||||||
|
+ '</g>'
|
||||||
|
# thumb
|
||||||
|
+ '<path d="M 0 210 Q 30 170 60 180 L 60 240 L 0 240 Z" fill="#5a4a3e"/>'
|
||||||
|
+ '<path d="M 12 205 Q 26 190 44 198" stroke="#3a2a22" stroke-width="0.8" fill="none"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def hand_over_face():
|
||||||
|
out = blur_silhouette(stddev=12)
|
||||||
|
# hand shape over face
|
||||||
|
hand = (
|
||||||
|
f'<path d="M {SIZE/2 - 50} 80 '
|
||||||
|
f'Q {SIZE/2 - 30} 60 {SIZE/2 - 10} 75 '
|
||||||
|
f'Q {SIZE/2 + 10} 55 {SIZE/2 + 30} 80 '
|
||||||
|
f'Q {SIZE/2 + 50} 70 {SIZE/2 + 55} 100 '
|
||||||
|
f'Q {SIZE/2 + 60} 140 {SIZE/2} 145 '
|
||||||
|
f'Q {SIZE/2 - 60} 140 {SIZE/2 - 55} 100 Z" fill="#5a5265"/>'
|
||||||
|
)
|
||||||
|
return out + hand
|
||||||
|
|
||||||
|
|
||||||
|
def mirror_selfie():
|
||||||
|
# visible photographer's hand at top edge holding a rectangular frame (phone)
|
||||||
|
inner_sil = silhouette(color="#2a2738", bg_color="#1a1722")
|
||||||
|
return (
|
||||||
|
bg("#0f0e14")
|
||||||
|
+ inner_sil
|
||||||
|
# phone frame outline over face
|
||||||
|
+ '<rect x="90" y="40" width="68" height="110" fill="#0a0911" stroke="#3a3548" stroke-width="1"/>'
|
||||||
|
# flash
|
||||||
|
+ '<circle cx="148" cy="52" r="3" fill="#d4d1e0"/>'
|
||||||
|
# hand from top right
|
||||||
|
+ '<path d="M 200 0 Q 180 40 160 50 L 158 44 Q 178 30 196 -4 Z" fill="#5a4a3e"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def chair():
|
||||||
|
# gerald.jpg
|
||||||
|
return (
|
||||||
|
bg("#18151f")
|
||||||
|
# back wall shadow
|
||||||
|
+ f'<rect x="0" y="170" width="{SIZE}" height="70" fill="#120f19"/>'
|
||||||
|
# chair frame (simple wooden kitchen chair)
|
||||||
|
+ '<g stroke="#6a5a44" stroke-width="3" fill="none" stroke-linecap="square">'
|
||||||
|
# back uprights
|
||||||
|
'<line x1="90" y1="60" x2="86" y2="170"/>'
|
||||||
|
'<line x1="150" y1="60" x2="154" y2="170"/>'
|
||||||
|
# back slats
|
||||||
|
'<line x1="88" y1="78" x2="152" y2="78"/>'
|
||||||
|
'<line x1="88" y1="102" x2="152" y2="102"/>'
|
||||||
|
'<line x1="88" y1="126" x2="152" y2="126"/>'
|
||||||
|
# top
|
||||||
|
'<line x1="86" y1="60" x2="154" y2="60"/>'
|
||||||
|
# seat
|
||||||
|
'<line x1="74" y1="170" x2="166" y2="170"/>'
|
||||||
|
'<line x1="76" y1="178" x2="164" y2="178"/>'
|
||||||
|
# front legs
|
||||||
|
'<line x1="78" y1="178" x2="74" y2="226"/>'
|
||||||
|
'<line x1="162" y1="178" x2="166" y2="226"/>'
|
||||||
|
# rear legs
|
||||||
|
'<line x1="90" y1="170" x2="94" y2="226"/>'
|
||||||
|
'<line x1="150" y1="170" x2="146" y2="226"/>'
|
||||||
|
'</g>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def qr_code(payload, cells=29, scale=None):
|
||||||
|
# Use qrencode if available to produce a real QR (payload links back to /members)
|
||||||
|
try:
|
||||||
|
res = subprocess.run(
|
||||||
|
["qrencode", "-t", "SVG", "-o", "-", "-m", "2", payload],
|
||||||
|
check=True, capture_output=True, text=True
|
||||||
|
)
|
||||||
|
svg = res.stdout
|
||||||
|
# strip XML prolog, keep inner svg; wrap it to our viewbox by nesting.
|
||||||
|
import re
|
||||||
|
# extract width/height or viewBox
|
||||||
|
vb_m = re.search(r'viewBox="([^"]+)"', svg)
|
||||||
|
inner_m = re.search(r'<svg[^>]*>(.*)</svg>', svg, re.S)
|
||||||
|
inner = inner_m.group(1) if inner_m else ""
|
||||||
|
vb = vb_m.group(1) if vb_m else f"0 0 {SIZE} {SIZE}"
|
||||||
|
return (
|
||||||
|
svg_open('style="background:#e6e3ee"') +
|
||||||
|
f'<rect width="{SIZE}" height="{SIZE}" fill="#e6e3ee"/>' +
|
||||||
|
f'<svg viewBox="{vb}" x="0" y="0" width="{SIZE}" height="{SIZE}" preserveAspectRatio="xMidYMid meet">' +
|
||||||
|
inner + '</svg></svg>'
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# fallback pseudo QR
|
||||||
|
rng = random.Random(payload)
|
||||||
|
cell = SIZE / cells
|
||||||
|
parts = [f'<rect width="{SIZE}" height="{SIZE}" fill="#e6e3ee"/>']
|
||||||
|
for y in range(cells):
|
||||||
|
for x in range(cells):
|
||||||
|
if rng.random() < 0.5:
|
||||||
|
parts.append(
|
||||||
|
f'<rect x="{x*cell:.2f}" y="{y*cell:.2f}" width="{cell:.2f}" height="{cell:.2f}" fill="#0f0e14"/>'
|
||||||
|
)
|
||||||
|
# finder patterns
|
||||||
|
def finder(ox, oy):
|
||||||
|
out = []
|
||||||
|
out.append(f'<rect x="{ox*cell:.2f}" y="{oy*cell:.2f}" width="{7*cell:.2f}" height="{7*cell:.2f}" fill="#0f0e14"/>')
|
||||||
|
out.append(f'<rect x="{(ox+1)*cell:.2f}" y="{(oy+1)*cell:.2f}" width="{5*cell:.2f}" height="{5*cell:.2f}" fill="#e6e3ee"/>')
|
||||||
|
out.append(f'<rect x="{(ox+2)*cell:.2f}" y="{(oy+2)*cell:.2f}" width="{3*cell:.2f}" height="{3*cell:.2f}" fill="#0f0e14"/>')
|
||||||
|
return "".join(out)
|
||||||
|
parts.append(finder(0, 0))
|
||||||
|
parts.append(finder(cells - 7, 0))
|
||||||
|
parts.append(finder(0, cells - 7))
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(body, extra=""):
|
||||||
|
return svg_open(extra) + body + "</svg>"
|
||||||
|
|
||||||
|
|
||||||
|
# member entries — (port, kind, opts)
|
||||||
|
ENTRIES = [
|
||||||
|
("0", "silhouette", {}),
|
||||||
|
("1", "silhouette", {}),
|
||||||
|
("7", "blur", {"stddev": 50}),
|
||||||
|
("17", "pixelated", {"cells": 8, "seed": "17"}),
|
||||||
|
("22", "back", {}),
|
||||||
|
("23", "withheld", {}),
|
||||||
|
("25", "overexposed", {}),
|
||||||
|
("43", "blur", {"stddev": 40}),
|
||||||
|
("53", "pixelated", {"cells": 16, "seed": "53"}),
|
||||||
|
("67", "double", {}),
|
||||||
|
("80", "black_bar", {}),
|
||||||
|
("110", "withheld", {}),
|
||||||
|
("119", "photo_of_photo", {}),
|
||||||
|
("123", "pixelated", {"cells": 8, "seed": "123"}),
|
||||||
|
("143", "silhouette", {}),
|
||||||
|
("161", "hand", {}),
|
||||||
|
("194", "mirror", {}),
|
||||||
|
("389", "qr", {"payload": "https://cultfifthoctet.org/members/"}),
|
||||||
|
("443", "blur", {"stddev": 35}),
|
||||||
|
("465", "chair", {"filename": "gerald"}), # file named gerald.svg
|
||||||
|
("514", "pixelated", {"cells": 16, "seed": "514"}),
|
||||||
|
("554", "overexposed", {}),
|
||||||
|
("631", "stock_redact", {}),
|
||||||
|
("993", "pixelated", {"cells": 8, "seed": "993"}),
|
||||||
|
("6667", "online", {}),
|
||||||
|
("31337", "blur", {"stddev": 20}),
|
||||||
|
("65535", "silhouette", {}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render(kind, opts):
|
||||||
|
if kind == "silhouette":
|
||||||
|
return wrap(silhouette())
|
||||||
|
if kind == "pixelated":
|
||||||
|
return wrap(pixelated(opts["seed"], opts["cells"]))
|
||||||
|
if kind == "blur":
|
||||||
|
return wrap(bg("#16141f") + blur_silhouette(opts.get("stddev", 30)))
|
||||||
|
if kind == "overexposed":
|
||||||
|
return wrap(overexposed())
|
||||||
|
if kind == "back":
|
||||||
|
return wrap(back_of_head())
|
||||||
|
if kind == "double":
|
||||||
|
return wrap(double_exposure())
|
||||||
|
if kind == "black_bar":
|
||||||
|
return wrap(black_bar_eyes())
|
||||||
|
if kind == "stock_redact":
|
||||||
|
return wrap(redaction_bar_eyes_stock())
|
||||||
|
if kind == "photo_of_photo":
|
||||||
|
return wrap(photo_of_photo())
|
||||||
|
if kind == "hand":
|
||||||
|
return wrap(hand_over_face())
|
||||||
|
if kind == "mirror":
|
||||||
|
return wrap(mirror_selfie())
|
||||||
|
if kind == "chair":
|
||||||
|
return wrap(chair())
|
||||||
|
if kind == "qr":
|
||||||
|
return qr_code(opts["payload"])
|
||||||
|
if kind == "online":
|
||||||
|
# not rendered as image, but we still output a placeholder text svg
|
||||||
|
return wrap(
|
||||||
|
bg("#0f0e14")
|
||||||
|
+ f'<text x="{SIZE/2}" y="{SIZE/2+6}" text-anchor="middle" '
|
||||||
|
f'font-family="ui-monospace,monospace" font-size="16" fill="#a89cd9" '
|
||||||
|
f'letter-spacing="2">[ONLINE]</text>'
|
||||||
|
)
|
||||||
|
if kind == "withheld":
|
||||||
|
return wrap(
|
||||||
|
bg("#0f0e14")
|
||||||
|
+ f'<text x="{SIZE/2}" y="{SIZE/2}" text-anchor="middle" '
|
||||||
|
f'font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" '
|
||||||
|
f'letter-spacing="2">[PHOTO WITHHELD]</text>'
|
||||||
|
+ f'<text x="{SIZE/2}" y="{SIZE/2 + 18}" text-anchor="middle" '
|
||||||
|
f'font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" '
|
||||||
|
f'letter-spacing="2">BY REQUEST</text>'
|
||||||
|
)
|
||||||
|
raise ValueError(kind)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for port, kind, opts in ENTRIES:
|
||||||
|
name = opts.get("filename", port)
|
||||||
|
svg = render(kind, opts)
|
||||||
|
write(name, svg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
21
sidecar/go.mod
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module cultfifthoctet.org/sidecar
|
||||||
|
|
||||||
|
go 1.22
|
||||||
|
|
||||||
|
require modernc.org/sqlite v1.32.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||||
|
modernc.org/libc v1.55.3 // indirect
|
||||||
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
|
modernc.org/memory v1.8.0 // indirect
|
||||||
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
|
modernc.org/token v1.1.0 // indirect
|
||||||
|
)
|
||||||
49
sidecar/go.sum
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||||
|
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||||
|
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||||
|
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||||
|
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||||
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
|
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||||
|
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||||
|
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||||
|
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||||
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
|
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||||
|
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||||
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
|
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||||
|
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||||
|
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||||
|
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||||
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
348
sidecar/main.go
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
// fifth — the cult's thin, watchful back end.
|
||||||
|
//
|
||||||
|
// Handles the four paths Caddy cannot:
|
||||||
|
//
|
||||||
|
// GET /vault/ — returns 503 on every Nth visit (N=2)
|
||||||
|
// POST /contact/attest — "5" => "at last." + fragment; else "no."
|
||||||
|
// POST /correspondence — records a return address
|
||||||
|
// GET /login/grant?t=<token> — issues a redirect when the token matches
|
||||||
|
//
|
||||||
|
// State lives in a SQLite file whose path is taken from $FIFTH_DB
|
||||||
|
// (default /var/lib/fifth/fifth.db).
|
||||||
|
//
|
||||||
|
// Tokens and fragments are loaded from env (FIFTH_TOKEN, FIFTH_TOKEN_DEST)
|
||||||
|
// at startup. No management surface. No admin endpoint. The cult is not
|
||||||
|
// administered.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fragmentFifth = "fragment 5/5 :: PFXW24DMNF2HI"
|
||||||
|
|
||||||
|
defaultDBPath = "/var/lib/fifth/fifth.db"
|
||||||
|
defaultAddr = ":8090"
|
||||||
|
|
||||||
|
// The well-known token. Rotate via FIFTH_TOKEN env.
|
||||||
|
defaultToken = "NVWWC23ENFZS4OBNMZXW6ZDGNFZS4S3VONSWG5DFOMFEEX2EGVSWK3TKNBSWY3DP"
|
||||||
|
// Redirect target on successful login. Rotate via FIFTH_TOKEN_DEST env.
|
||||||
|
defaultTokenDest = "https://irc.cultfifthoctet.org/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
db *sql.DB
|
||||||
|
loginToken string
|
||||||
|
loginDest string
|
||||||
|
vaultCounter uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
func env(key, fallback string) string {
|
||||||
|
if v := os.Getenv(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.LUTC)
|
||||||
|
log.SetPrefix("fifth ")
|
||||||
|
|
||||||
|
loginToken = env("FIFTH_TOKEN", defaultToken)
|
||||||
|
loginDest = env("FIFTH_TOKEN_DEST", defaultTokenDest)
|
||||||
|
addr := env("FIFTH_ADDR", defaultAddr)
|
||||||
|
dbPath := env("FIFTH_DB", defaultDBPath)
|
||||||
|
|
||||||
|
if err := openDB(dbPath); err != nil {
|
||||||
|
log.Fatalf("db open: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/vault/", vaultHandler)
|
||||||
|
mux.HandleFunc("/contact/attest", contactHandler)
|
||||||
|
mux.HandleFunc("/correspondence", correspondenceHandler)
|
||||||
|
mux.HandleFunc("/correspondence/", correspondenceHandler)
|
||||||
|
mux.HandleFunc("/login/grant", loginHandler)
|
||||||
|
mux.HandleFunc("/_fifth/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: withCommonHeaders(mux),
|
||||||
|
ReadHeaderTimeout: 5 * time.Second,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
IdleTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("listening on %s (db=%s)", addr, dbPath)
|
||||||
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
log.Printf("shutting down")
|
||||||
|
sdctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
_ = srv.Shutdown(sdctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openDB(path string) error {
|
||||||
|
// Ensure parent dir exists when using a file path (not :memory:).
|
||||||
|
if path != ":memory:" {
|
||||||
|
if err := os.MkdirAll(dirname(path), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err := sql.Open("sqlite", path+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.SetMaxOpenConns(1) // SQLite: one writer; simpler than pool tuning.
|
||||||
|
db = conn
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
CREATE TABLE IF NOT EXISTS correspondence (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
addr TEXT NOT NULL,
|
||||||
|
kind TEXT NOT NULL,
|
||||||
|
created INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS counters (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
value INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
INSERT OR IGNORE INTO counters(name, value) VALUES ('vault_visits', 0);
|
||||||
|
`
|
||||||
|
_, err = db.Exec(schema)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirname(p string) string {
|
||||||
|
if i := strings.LastIndexByte(p, '/'); i >= 0 {
|
||||||
|
return p[:i]
|
||||||
|
}
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- middleware ----
|
||||||
|
|
||||||
|
func withCommonHeaders(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- /vault/ ----
|
||||||
|
//
|
||||||
|
// Every second visit returns 503 with a themed message; every other visit
|
||||||
|
// signals 200 so Caddy can serve the real static page. We communicate
|
||||||
|
// "please serve the real page" by returning 418 (I'm a teapot); Caddy
|
||||||
|
// reverse_proxy with handle_response treats non-2xx/non-503 as a signal
|
||||||
|
// to fall through. Simpler: we proxy through a header hint, and Caddy
|
||||||
|
// uses @headers matching.
|
||||||
|
//
|
||||||
|
// Final design: sidecar always responds. On "allow" visits it sends 200
|
||||||
|
// with an X-Sentinel header; Caddy uses handle_response to internal-rewrite
|
||||||
|
// and serve the file. On "deny" visits the sidecar sends its own 503 body.
|
||||||
|
func vaultHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Only the root vault path is counted.
|
||||||
|
if r.URL.Path != "/vault/" && r.URL.Path != "/vault" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int64
|
||||||
|
err := db.QueryRow("UPDATE counters SET value = value + 1 WHERE name = 'vault_visits' RETURNING value").Scan(&n)
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to an in-memory counter if the DB is unwell.
|
||||||
|
n = int64(atomic.AddUint64(&vaultCounter, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if n%2 == 0 {
|
||||||
|
// Denied visit.
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
_, _ = io.WriteString(w, vault503Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed visit: signal Caddy to serve the static file.
|
||||||
|
w.Header().Set("X-Fifth-Passthrough", "1")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vault503Body = `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>the vault is not available at this time.</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /vault</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
<div class="notfound">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<h1>the vault is not available at this time.</h1>
|
||||||
|
<p>Try again.</p>
|
||||||
|
<div class="code">503</div>
|
||||||
|
</div>
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// ---- /contact/attest ----
|
||||||
|
|
||||||
|
func contactHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guess := strings.TrimSpace(r.FormValue("guess"))
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
if guess == "5" {
|
||||||
|
w.Header().Set("X-Fifth-Fragment", fragmentFifth)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = io.WriteString(w, "at last.\n"+fragmentFifth+"\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = io.WriteString(w, "no.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- /correspondence ----
|
||||||
|
|
||||||
|
func correspondenceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := strings.TrimSpace(r.FormValue("addr"))
|
||||||
|
|
||||||
|
kind := classifyCorrespondence(addr)
|
||||||
|
|
||||||
|
// Client-side JS handles the 'fifth' redirect and the IPv4+ console
|
||||||
|
// message before the POST is sent; the server still records whatever
|
||||||
|
// arrives, because the cult records everything (and tells nobody).
|
||||||
|
if addr != "" && len(addr) < 512 {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"INSERT INTO correspondence(addr, kind, created) VALUES (?, ?, ?)",
|
||||||
|
addr, kind, time.Now().UTC().Unix(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("correspondence insert: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func classifyCorrespondence(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return "blank"
|
||||||
|
}
|
||||||
|
if strings.EqualFold(s, "fifth") {
|
||||||
|
return "fifth"
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(s, '@') {
|
||||||
|
return "email"
|
||||||
|
}
|
||||||
|
if looksLikeIPv4Plus(s) {
|
||||||
|
return "ipv4plus"
|
||||||
|
}
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
func looksLikeIPv4Plus(s string) bool {
|
||||||
|
parts := strings.Split(s, ".")
|
||||||
|
if len(parts) != 5 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, p := range parts {
|
||||||
|
if len(p) == 0 || len(p) > 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
for _, c := range p {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n = n*10 + int(c-'0')
|
||||||
|
}
|
||||||
|
if n > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- /login/grant ----
|
||||||
|
|
||||||
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t := r.URL.Query().Get("t")
|
||||||
|
// Constant-time compare to avoid any timing leak, in the unlikely
|
||||||
|
// event that anyone is watching this thing at that resolution.
|
||||||
|
if t != "" && constantTimeEqual(t, loginToken) {
|
||||||
|
http.Redirect(w, r, loginDest, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Unknown or missing token: 404 with the standard page. Caddy's
|
||||||
|
// handle_errors gives us this for free when we return 404 here;
|
||||||
|
// the Caddy config is arranged to intercept it.
|
||||||
|
http.Error(w, "", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func constantTimeEqual(a, b string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var v byte
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
v |= a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
return v == 0
|
||||||
|
}
|
||||||
1
site/.well-known/fifth
Normal file
@ -0,0 +1 @@
|
|||||||
|
acknowledged.
|
||||||
49
site/1997/index.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>— cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /1997</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log">
|
||||||
|
<div class="head">— LOG FRAGMENT · ANNO 1997 · CHANNEL WITHHELD —</div>
|
||||||
|
|
||||||
|
<div class="line"><span class="ts">23:41:07</span> <span class="nick"><[REDACTED]></span> are we still on</div>
|
||||||
|
<div class="line"><span class="ts">23:41:12</span> <span class="nick"><[REDACTED]></span> yeah</div>
|
||||||
|
<div class="line"><span class="ts">23:41:18</span> <span class="nick"><[REDACTED]></span> four fields wasn’t the problem. four fields was never the problem.</div>
|
||||||
|
<div class="line"><span class="ts">23:42:03</span> <span class="nick"><[REDACTED]></span> i was in the room when they tabled it. i know what was said.</div>
|
||||||
|
<div class="line"><span class="ts">23:42:44</span> <span class="nick"><[REDACTED]></span> the minutes say one thing. i remember another.</div>
|
||||||
|
<div class="line"><span class="ts">23:43:19</span> <span class="nick"><[REDACTED]></span> is anyone logging this</div>
|
||||||
|
<div class="line"><span class="ts">23:43:22</span> <span class="nick"><[REDACTED]></span> no</div>
|
||||||
|
<div class="line"><span class="ts">23:43:26</span> <span class="nick"><[REDACTED]></span> …</div>
|
||||||
|
<div class="line"><span class="ts">23:43:30</span> <span class="nick"><[REDACTED]></span> good</div>
|
||||||
|
<div class="line"><span class="ts">23:45:02</span> <span class="nick"><[REDACTED]></span> if we do this, we need a channel. somewhere that isn’t on the record.</div>
|
||||||
|
<div class="line"><span class="ts">23:45:48</span> <span class="nick"><[REDACTED]></span> something simple. the name should mean nothing to anyone who isn’t us.</div>
|
||||||
|
<div class="line"><span class="ts">23:46:11</span> <span class="nick"><[REDACTED]></span> the fourth segment is the channel name reversed.</div>
|
||||||
|
<div class="line"><span class="ts">23:46:40</span> <span class="nick"><[REDACTED]></span> understood.</div>
|
||||||
|
<div class="line"><span class="ts">23:47:02</span> <span class="nick"><[REDACTED]></span> and the fifth?</div>
|
||||||
|
<div class="line"><span class="ts">23:47:09</span> <span class="nick"><[REDACTED]></span> the fifth we do not discuss here.</div>
|
||||||
|
<div class="line"><span class="ts">23:47:14</span> <span class="nick"><[REDACTED]></span> agreed.</div>
|
||||||
|
<div class="line"><span class="ts">23:51:33</span> <span class="redact">— log ends —</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
site/404-applications.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>We do not accept applications.</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body data-notfound="1">
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a></span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notfound">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<h1>We do not accept applications.</h1>
|
||||||
|
<p>Applications have never been open. They will not open. The matter is closed.</p>
|
||||||
|
<div class="code">404</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
site/404-logout.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>One does not log out.</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body data-notfound="1">
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a></span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notfound">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<h1>One does not log out.</h1>
|
||||||
|
<p>You were never authenticated in a way that permits logging out. The session was ambient. It persists.</p>
|
||||||
|
<div class="code">404</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
site/404-minutes.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>the minutes are incomplete.</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body data-notfound="1">
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a></span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notfound">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<h1>the minutes are incomplete.</h1>
|
||||||
|
<p>Where they survive at all, they do not survive in full.</p>
|
||||||
|
<p>Return to <a href="/">the index</a>, or do not.</p>
|
||||||
|
<div class="code">404</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
site/404.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>the record is incomplete.</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body data-notfound="1">
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a></span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notfound">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<h1>the record is incomplete.</h1>
|
||||||
|
<p>You have requested a record that is sealed, redacted, or was never committed to the archive.</p>
|
||||||
|
<p>This is not unusual.</p>
|
||||||
|
<p>Return to <a href="/">the index</a>, or do not.</p>
|
||||||
|
<div class="code">404</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
47
site/archive/index.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Archive — cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /archive</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="archive-page">
|
||||||
|
<h1>— ARCHIVE —</h1>
|
||||||
|
<div class="archive-list">
|
||||||
|
<div class="entry"><span class="name">the fifth field.txt</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">correspondence, 1994–1997.pdf</span><span class="status">[ REDACTED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">on the successor protocol</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name"><a href="/1997/">meeting transcript — location withheld</a></span><span class="status partial">[ PARTIAL ]</span></div>
|
||||||
|
<div class="entry"><span class="name">addendum to the addendum</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">the proposal, as submitted</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">the proposal, as amended</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">the proposal, as received</span><span class="status">[ REDACTED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">correspondence with [NAME WITHHELD], Anno 1996</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name"><a href="/record/">on the matter of the fifth</a></span><span class="status partial">[ PARTIAL ]</span></div>
|
||||||
|
<div class="entry"><span class="name">auction records, Anno 2011–Anno 2014</span><span class="status">[ REDACTED ]</span></div>
|
||||||
|
<div class="entry muted"><span class="name">the list of beneficiaries</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry muted"><span class="name">the list of dissenters</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry muted"><span class="name">the list of the deceased</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
site/contact/index.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>— cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /contact</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-page">
|
||||||
|
<div class="addr">172.16.0.1.<span class="unknown"><?></span></div>
|
||||||
|
<form class="attest" method="post" action="/contact/attest" autocomplete="off" novalidate>
|
||||||
|
<input type="text" name="guess" aria-label="the fifth" autocomplete="off">
|
||||||
|
<button type="submit">ATTEST</button>
|
||||||
|
</form>
|
||||||
|
<div class="out"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
site/favicon.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<rect width="32" height="32" fill="#0f0e14"/>
|
||||||
|
<circle cx="16" cy="16" r="12" fill="none" stroke="#8a8598" stroke-width="0.75"/>
|
||||||
|
<text x="16" y="21" font-family="ui-monospace, monospace" font-size="13" fill="#d4d1e0" text-anchor="middle" letter-spacing="0.5">V</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 341 B |
92
site/index.html
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<!-- the fifth is not a metaphor -->
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Cult of the Fifth Octet</title>
|
||||||
|
<meta name="description" content="There is a fifth.">
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<meta name="author" content="— authorship withheld">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span>CFO.ORG — EST. MCMXCV</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<div class="dots"></div>
|
||||||
|
<h1>There is a fifth.</h1>
|
||||||
|
<p class="lede">Those who know, know. Those who do not will find this page in due time, or not at all.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="address">
|
||||||
|
<div class="plate">
|
||||||
|
xxx.xxx.xxx.xxx<span class="dot">.</span><span class="fifth">xxx</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="label label-wrap">A PARTIAL RECORD</div>
|
||||||
|
<div class="record-grid">
|
||||||
|
<div>
|
||||||
|
<div class="date">ANNO 1981</div>
|
||||||
|
<div class="body">The architecture is committed. Four fields. A temporary measure, we are told. “Temporary” has outlived its architects.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="date">ANNO 1992</div>
|
||||||
|
<div class="body">The working group meets in secret. A simple remedy is proposed. It is rejected for reasons that are never fully explained. The minutes, where they survive, are incomplete.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="date">ANNO 1998</div>
|
||||||
|
<div class="body">A replacement is declared. Adoption is delayed, then delayed again. The delay continues. We believe this was the intent.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="date">ANNO 2011</div>
|
||||||
|
<div class="body">The pool is exhausted. Auctions begin. Certain parties acquire blocks quietly and at scale. We do not name them here. Those who need to know, know.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section pullquote">
|
||||||
|
<div class="q">“The proposal was not rejected on technical grounds. It was rejected because it would have worked.”</div>
|
||||||
|
<div class="attr">— ATTRIBUTED. NAME WITHHELD.</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="label label-wrap">ARCHIVE</div>
|
||||||
|
<div class="archive-list">
|
||||||
|
<div class="entry"><span class="name">the fifth field.txt</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">correspondence, 1994–1997.pdf</span><span class="status">[ REDACTED ]</span></div>
|
||||||
|
<div class="entry"><span class="name">on the successor protocol</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
<div class="entry"><span class="name"><a href="/1997/">meeting transcript — location withheld</a></span><span class="status partial">[ PARTIAL ]</span></div>
|
||||||
|
<div class="entry"><span class="name">addendum to the addendum</span><span class="status">[ SEALED ]</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section corr">
|
||||||
|
<div class="label label-wrap">CORRESPONDENCE</div>
|
||||||
|
<p>If you have arrived here in error, close this page. If you have not, provide a return address. You will hear from us, or you will not.</p>
|
||||||
|
<form class="correspondence" method="post" action="/correspondence" autocomplete="off" novalidate>
|
||||||
|
<input type="text" name="addr" placeholder="" aria-label="return address">
|
||||||
|
<button type="submit">SUBMIT</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- see /vault/ -->
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
site/login/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>— cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /login</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-page">
|
||||||
|
<div class="sigil">V</div>
|
||||||
|
<form method="get" action="/login/grant" autocomplete="off">
|
||||||
|
<input type="text" name="t" aria-label="token" placeholder="">
|
||||||
|
<button type="submit">SUBMIT</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
site/manifesto/index.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>— cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="manifesto">
|
||||||
|
<p>A manifesto would be a concession.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
278
site/members/index.html
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Roster — cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /members</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="roster">
|
||||||
|
<div class="roster-head">
|
||||||
|
<div class="label label-wrap">ROSTER</div>
|
||||||
|
<div class="sub"><em>Twenty-seven are listed. Others are not.</em></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
|
||||||
|
<!-- 01 :0 founder -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/0.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: —">:0</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">????</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">—</span></div>
|
||||||
|
<div class="note">Founder.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 02 :1 founder -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/1.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: [REDACTED]">:1</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">????</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">—</span></div>
|
||||||
|
<div class="note">Founder.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 03 :7 echo -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/7.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:7</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">????</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2004</span></div>
|
||||||
|
<div class="note">Echo.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 04 :17 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/17.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +01:00">:17</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 1997</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2019</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 05 :22 ssh -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/22.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -08:00">:22</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 1998</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">—</span></div>
|
||||||
|
<div class="note">SSH.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 06 :23 telnet -->
|
||||||
|
<div class="card">
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:23</span></div>
|
||||||
|
<div class="withheld">[PHOTO WITHHELD<br>BY REQUEST]</div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 1996</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2003</span></div>
|
||||||
|
<div class="note">Telnet. Appropriate that they withheld.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 07 :25 smtp -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/25.svg" alt=""></div>
|
||||||
|
<div><a href="mailto:25@cultfifthoctet.org" class="port" data-tt="tz: -05:00">:25</a></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 1999</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">17 days ago</span></div>
|
||||||
|
<div class="note">SMTP.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 08 :43 whois -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/43.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +02:00">:43</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2001</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2008</span></div>
|
||||||
|
<div class="note">WHOIS.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 09 :53 dns -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/53.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:53</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2001</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">3 days ago</span></div>
|
||||||
|
<div class="note">DNS.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 10 :67 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/67.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -04:00">:67</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2002</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2015</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 11 :80 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/80.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +09:00">:80</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2000</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2022</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 12 :110 -->
|
||||||
|
<div class="card">
|
||||||
|
<div><span class="port" data-tt="tz: +01:00">:110</span></div>
|
||||||
|
<div class="withheld">[PHOTO WITHHELD<br>BY REQUEST]</div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2003</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2011</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 13 :119 -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/119.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +03:00">:119</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2001</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2013</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 14 :123 ntp -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/123.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: fifth.">:123</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2004</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">41 days ago</span></div>
|
||||||
|
<div class="note">NTP.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- :1337 — removed by request, Anno 2014. we respect their wishes. -->
|
||||||
|
|
||||||
|
<!-- 15 :143 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/143.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -03:00">:143</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2003</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2018</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 16 :161 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/161.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +05:30">:161</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2005</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2020</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 17 :194 (original IRC) -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/194.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -08:00">:194</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2002</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2012</span></div>
|
||||||
|
<div class="note">Original IRC port (pre-6667).</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 18 :389 -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/389.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:389</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2006</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">6 days ago</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 19 :443 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/443.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +02:00">:443</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2003</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2019</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 20 :465 (filename gerald.jpg) -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/gerald.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +01:00">:465</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2007</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2014</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 21 :514 syslog -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/514.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:514</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2004</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2017</span></div>
|
||||||
|
<div class="note">Syslog.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 22 :554 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/554.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -06:00">:554</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2005</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2016</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 23 :631 -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/631.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:631</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2006</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2021</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 24 :993 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/993.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +08:00">:993</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2008</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">92 days ago</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- duplicate :17 (the site does not acknowledge) -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/17.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +01:00">:17</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2003</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2011</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 25 :6667 irc -->
|
||||||
|
<div class="card" data-next-seen="2026-10-14">
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:6667</span></div>
|
||||||
|
<div class="online">[ONLINE]</div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2011</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Now</span></div>
|
||||||
|
<div class="note">IRC.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 26 :31337 elite -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="img"><img src="/static/img/members/31337.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: -07:00">:31337</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 2013</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">Anno 2019</span></div>
|
||||||
|
<div class="note">Elite.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 27 :65535 emeritus -->
|
||||||
|
<div class="card tall">
|
||||||
|
<div class="img"><img src="/static/img/members/65535.svg" alt=""></div>
|
||||||
|
<div><span class="port" data-tt="tz: +00:00">:65535</span></div>
|
||||||
|
<div><span class="k">JOINED</span><span class="v">Anno 1995</span></div>
|
||||||
|
<div><span class="k">LAST</span><span class="v">—</span></div>
|
||||||
|
<div class="note">Emeritus.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
site/record/index.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>On the Matter of the Fifth — cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /record</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="wide" style="padding: 64px 24px;">
|
||||||
|
<article class="record">
|
||||||
|
<h1>On the Matter of the Fifth</h1>
|
||||||
|
<div class="meta">ANNO 2011 · 03 FEBRUARY</div>
|
||||||
|
|
||||||
|
<p id="p-01">There was a time when the remedy was simple.</p>
|
||||||
|
|
||||||
|
<p id="p-02">It required no new protocol. No new stack. No renumbering of the world’s equipment. It required only that the specification be amended — one additional field, appended to the end of the existing four, carrying values zero through two hundred and fifty-five. A single dot. A single byte. One trillion new addresses, sufficient for every human who would ever live and every device they would ever own, with reserves enough for the grandchildren of their toasters.</p>
|
||||||
|
|
||||||
|
<p id="p-03">This is a matter of record. The proposal was drafted. It circulated. It was reviewed.</p>
|
||||||
|
|
||||||
|
<p id="p-04">It was not rejected on technical grounds. The grounds were not disclosed. We have made inquiries over the years, in the careful way that such inquiries must be made, and the responses we have received range from the dismissive to the evasive to — in one case, from a man now deceased — the frightened. We do not name him here. His family has suffered enough.</p>
|
||||||
|
|
||||||
|
<p>What was chosen instead was a replacement of the whole cloth. A protocol that would require every router, every operating system, every piece of software that touched a network, to be rewritten. A protocol whose addresses no human being would ever memorize. A protocol whose adoption would take, by the most optimistic estimates at the time, ten years — and which, as of this writing, has taken twenty-five and is not finished.</p>
|
||||||
|
|
||||||
|
<p>This is not an accident. An accident is recoverable.</p>
|
||||||
|
|
||||||
|
<p id="p-07">There are parties who benefitted from the scarcity. We will not list them, though some of their names are in the archive; the archive is sealed<span style="font-size:0;line-height:0;color:transparent;user-select:all;">​/vault/#1994</span>, and we ask that it remain sealed, for reasons we trust our readers to infer. We will say only that the blocks which were auctioned in the years following exhaustion were not distributed by lot. Certain buyers acquired certain ranges at prices that, in retrospect, were modest. We do not believe this was coincidence.</p>
|
||||||
|
|
||||||
|
<p>The fifth field remains, in principle, a possibility. In practice, the window has closed. The replacement protocol, whatever else may be said of it, now has sufficient adoption that a reversal would be as costly as the original migration was promised to be.</p>
|
||||||
|
|
||||||
|
<p>This, too, may not be an accident.</p>
|
||||||
|
|
||||||
|
<p>We continue to maintain the record because the record is worth maintaining. There are among our correspondents engineers who worked on the original drafts, administrators who watched the auctions, and — we are told, though we have not verified — at least one member of the working group that deliberated in 1994. He is the reason we have held our peace for as long as we have.</p>
|
||||||
|
|
||||||
|
<p>We hold our peace no longer.</p>
|
||||||
|
|
||||||
|
<div class="sign">
|
||||||
|
— <em>authorship withheld</em><br>
|
||||||
|
— <em>posted Anno 2011 · 03 February</em><br>
|
||||||
|
— <em>last modified: never</em>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- he did not hold his peace. we did. -->
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
site/robots.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /vault/
|
||||||
|
Disallow: /minutes/
|
||||||
|
Disallow: /correspondence/
|
||||||
|
Disallow: /1997/
|
||||||
|
|
||||||
|
# we ask politely. we do not require.
|
||||||
710
site/static/css/site.css
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
/* cfo.org */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #0f0e14;
|
||||||
|
--text: #d4d1e0;
|
||||||
|
--text-2: #b8b5c4;
|
||||||
|
--muted: #8a8598;
|
||||||
|
--tert: #6a6578;
|
||||||
|
--border: #2a2738;
|
||||||
|
--accent: #a89cd9;
|
||||||
|
--deep: #4a4560;
|
||||||
|
|
||||||
|
--serif: "Source Serif 4", "Source Serif Pro", "Iowan Old Style", "Apple Garamond", Baskerville, "Times New Roman", Georgia, serif;
|
||||||
|
--mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Serif 4";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local("Source Serif 4"), local("Source Serif Pro");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Serif 4";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local("Source Serif 4 Italic"), local("Source Serif Pro Italic");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "JetBrains Mono";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local("JetBrains Mono"), local("IBM Plex Mono");
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text-2);
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.7;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 0.5px dotted transparent;
|
||||||
|
transition: border-color 120ms linear;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
border-bottom-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
em, i { font-style: italic; }
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--tert);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
margin: 48px 0;
|
||||||
|
}
|
||||||
|
hr::before { content: "· · · · ·"; }
|
||||||
|
|
||||||
|
/* page frame */
|
||||||
|
|
||||||
|
.frame {
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
margin: 24px auto;
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-bottom: 0.5px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar.footer {
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 0.5px solid var(--border);
|
||||||
|
color: var(--deep);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 48px 56px;
|
||||||
|
border-top: 0.5px solid var(--border);
|
||||||
|
}
|
||||||
|
.section:first-of-type { border-top: 0; }
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.section { padding: 32px 24px; }
|
||||||
|
.bar { padding: 10px 16px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-wrap::before { content: "— "; }
|
||||||
|
.label-wrap::after { content: " —"; }
|
||||||
|
|
||||||
|
.dots {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 28px;
|
||||||
|
}
|
||||||
|
.dots::before { content: "· · · · ·"; }
|
||||||
|
|
||||||
|
/* sigil */
|
||||||
|
|
||||||
|
.sigil {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin: 0 auto 32px;
|
||||||
|
border: 0.5px solid var(--deep);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hero */
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
text-align: center;
|
||||||
|
padding: 72px 56px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 36px;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--text);
|
||||||
|
max-width: 520px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero .lede {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--muted);
|
||||||
|
max-width: 440px;
|
||||||
|
margin: 0 auto;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 56px 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address .plate {
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 0.5px solid var(--border);
|
||||||
|
border-bottom: 0.5px solid var(--border);
|
||||||
|
padding: 20px 40px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address .dot { color: var(--tert); }
|
||||||
|
|
||||||
|
.address .fifth {
|
||||||
|
color: var(--accent);
|
||||||
|
border-bottom: 0.5px dotted var(--tert);
|
||||||
|
padding-bottom: 2px;
|
||||||
|
animation: fifth-pulse 4s ease-in-out infinite alternate;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fifth-pulse {
|
||||||
|
from { opacity: 0.7; }
|
||||||
|
to { opacity: 1.0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* record grid on homepage */
|
||||||
|
|
||||||
|
.record-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 32px 48px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.record-grid { grid-template-columns: 1fr; gap: 24px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-grid .date {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-grid .body {
|
||||||
|
color: var(--text-2);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pull quote */
|
||||||
|
|
||||||
|
.pullquote {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.pullquote .q {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 19px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--text);
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
.pullquote .attr {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* archive list */
|
||||||
|
|
||||||
|
.archive-list {
|
||||||
|
max-width: 420px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-list .entry {
|
||||||
|
padding: 14px 0;
|
||||||
|
border-bottom: 0.5px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-list .entry:last-child { border-bottom: 0; }
|
||||||
|
|
||||||
|
.archive-list .name { color: var(--text-2); }
|
||||||
|
.archive-list .status { color: var(--deep); white-space: nowrap; }
|
||||||
|
.archive-list .status.partial { color: var(--accent); }
|
||||||
|
|
||||||
|
.archive-list .entry.muted {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.archive-list .entry.muted .name,
|
||||||
|
.archive-list .entry.muted .status {
|
||||||
|
color: var(--tert);
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-list a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.archive-list a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* subscribe form */
|
||||||
|
|
||||||
|
.corr {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.corr p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
max-width: 420px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.corr form {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
max-width: 360px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.corr input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.corr input[type="text"]:focus {
|
||||||
|
border-color: var(--deep);
|
||||||
|
}
|
||||||
|
.corr button {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 0.5px solid var(--deep);
|
||||||
|
padding: 0 18px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.corr button:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* record (blog) */
|
||||||
|
|
||||||
|
article.record {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.record h1 {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 30px;
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0 0 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.record .meta {
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--tert);
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.record p {
|
||||||
|
color: var(--text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
article.record .sign {
|
||||||
|
margin-top: 40px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* members */
|
||||||
|
|
||||||
|
.roster {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roster-head {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
.roster-head .sub {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
grid-auto-flow: dense;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
padding: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.card.tall { grid-row: span 2; }
|
||||||
|
|
||||||
|
.card .port {
|
||||||
|
font-family: var(--mono);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 0.5px dotted transparent;
|
||||||
|
cursor: help;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .port[data-tt]:hover::after {
|
||||||
|
content: attr(data-tt);
|
||||||
|
position: absolute;
|
||||||
|
top: -26px;
|
||||||
|
left: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--text-2);
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background: #16141f;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.card .img img, .card .img svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .withheld {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
padding: 32px 8px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .online {
|
||||||
|
color: var(--accent);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
padding: 40px 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .k {
|
||||||
|
color: var(--tert);
|
||||||
|
display: inline-block;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.card .v {
|
||||||
|
color: var(--text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .note {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* contact */
|
||||||
|
|
||||||
|
.contact-page {
|
||||||
|
text-align: center;
|
||||||
|
padding: 96px 24px;
|
||||||
|
}
|
||||||
|
.contact-page .addr {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--text);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
.contact-page .addr .unknown { color: var(--accent); }
|
||||||
|
|
||||||
|
.contact-page form {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.contact-page input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-page .out {
|
||||||
|
margin-top: 32px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
min-height: 1.4em;
|
||||||
|
}
|
||||||
|
.contact-page input:focus { border-color: var(--deep); }
|
||||||
|
.contact-page button {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 0.5px solid var(--deep);
|
||||||
|
padding: 0 18px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manifesto */
|
||||||
|
|
||||||
|
.manifesto {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 80vh;
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.manifesto p {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--text);
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vault */
|
||||||
|
|
||||||
|
.vault {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
.vault .scan {
|
||||||
|
display: inline-block;
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
padding: 8px;
|
||||||
|
background: #1a1722;
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
.vault svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
filter: grayscale(1) contrast(0.9);
|
||||||
|
}
|
||||||
|
.vault .caption {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1997 log */
|
||||||
|
|
||||||
|
.log {
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 48px auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: var(--text-2);
|
||||||
|
}
|
||||||
|
.log .head {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.log .line { white-space: pre-wrap; }
|
||||||
|
.log .ts { color: var(--tert); }
|
||||||
|
.log .nick { color: var(--deep); }
|
||||||
|
.log .redact { color: var(--deep); }
|
||||||
|
|
||||||
|
/* 404 */
|
||||||
|
|
||||||
|
.notfound {
|
||||||
|
text-align: center;
|
||||||
|
padding: 96px 24px;
|
||||||
|
}
|
||||||
|
.notfound h1 {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28px;
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0 0 32px;
|
||||||
|
}
|
||||||
|
.notfound p {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--text-2);
|
||||||
|
max-width: 440px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
.notfound .code {
|
||||||
|
margin-top: 48px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--tert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* archive page */
|
||||||
|
|
||||||
|
.archive-page {
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 64px 24px;
|
||||||
|
}
|
||||||
|
.archive-page h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--tert);
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* login */
|
||||||
|
|
||||||
|
.login-page {
|
||||||
|
text-align: center;
|
||||||
|
padding: 96px 24px;
|
||||||
|
}
|
||||||
|
.login-page form {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.login-page input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0.5px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
1
site/static/img/members/0.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
1
site/static/img/members/1.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
1
site/static/img/members/110.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#0f0e14"/><text x="120.0" y="120.0" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" letter-spacing="2">[PHOTO WITHHELD]</text><text x="120.0" y="138.0" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" letter-spacing="2">BY REQUEST</text></svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
1
site/static/img/members/119.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#0a0911"/><g transform="rotate(-3 120.0 120.0)"><rect x="28" y="24" width="184" height="192" fill="#1e1a2a" stroke="#3a3548" stroke-width="0.8"/><g transform="translate(28 24) scale(0.766)"><rect width="240" height="240" fill="#1e1a2a"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></g></g><path d="M 0 210 Q 30 170 60 180 L 60 240 L 0 240 Z" fill="#5a4a3e"/><path d="M 12 205 Q 26 190 44 198" stroke="#3a2a22" stroke-width="0.8" fill="none"/></svg>
|
||||||
|
After Width: | Height: | Size: 697 B |
1
site/static/img/members/123.svg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
1
site/static/img/members/143.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
1
site/static/img/members/161.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="12"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g><path d="M 70.0 80 Q 90.0 60 110.0 75 Q 130.0 55 150.0 80 Q 170.0 70 175.0 100 Q 180.0 140 120.0 145 Q 60.0 140 65.0 100 Z" fill="#5a5265"/></svg>
|
||||||
|
After Width: | Height: | Size: 590 B |
1
site/static/img/members/17.svg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
1
site/static/img/members/194.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#0f0e14"/><rect width="240" height="240" fill="#1a1722"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/><rect x="90" y="40" width="68" height="110" fill="#0a0911" stroke="#3a3548" stroke-width="1"/><circle cx="148" cy="52" r="3" fill="#d4d1e0"/><path d="M 200 0 Q 180 40 160 50 L 158 44 Q 178 30 196 -4 Z" fill="#5a4a3e"/></svg>
|
||||||
|
After Width: | Height: | Size: 574 B |
1
site/static/img/members/22.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><path d="M 60 240 C 60 160, 90 140, 120.0 140 C 150 140, 180 160, 180 240 Z" fill="#2a2738"/><ellipse cx="120.0" cy="110" rx="52" ry="55" fill="#191624"/><path d="M 70 95 Q 120 55 170 95 Q 170 120 120 115 Q 70 120 70 95 Z" fill="#100e18"/></svg>
|
||||||
|
After Width: | Height: | Size: 392 B |
1
site/static/img/members/23.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#0f0e14"/><text x="120.0" y="120.0" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" letter-spacing="2">[PHOTO WITHHELD]</text><text x="120.0" y="138.0" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10" fill="#6a6578" letter-spacing="2">BY REQUEST</text></svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
1
site/static/img/members/25.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#1a1722"/><defs><radialGradient id="o" cx="50%" cy="40%" r="60%"><stop offset="0%" stop-color="#e8e5f0"/><stop offset="60%" stop-color="#a49fb4" stop-opacity="0.6"/><stop offset="100%" stop-color="#1a1722" stop-opacity="0"/></radialGradient></defs><rect width="240" height="240" fill="url(#o)"/></svg>
|
||||||
|
After Width: | Height: | Size: 438 B |
1
site/static/img/members/31337.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="20"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
459
site/static/img/members/389.svg
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" style="background:#e6e3ee"><rect width="240" height="240" fill="#e6e3ee"/><svg viewBox="0 0 33 33" x="0" y="0" width="240" height="240" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<g id="QRcode">
|
||||||
|
<rect x="0" y="0" width="33" height="33" fill="#ffffff"/>
|
||||||
|
<g id="Pattern" transform="translate(2,2)">
|
||||||
|
<rect x="0" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="0" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="1" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="2" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="3" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="4" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="5" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="6" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="7" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="8" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="9" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="7" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="10" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="7" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="11" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="12" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="13" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="14" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="7" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="15" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="16" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="17" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="18" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="7" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="19" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="20" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="21" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="22" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="23" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="24" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="26" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="25" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="15" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="17" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="19" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="27" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="26" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="11" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="12" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="18" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="27" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="0" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="1" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="2" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="3" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="4" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="5" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="6" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="8" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="9" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="10" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="13" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="14" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="16" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="20" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="21" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="22" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="23" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="24" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="25" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
<rect x="28" y="28" width="1" height="1" fill="#000000"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 27 KiB |
1
site/static/img/members/43.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="40"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
1
site/static/img/members/443.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="35"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
1
site/static/img/members/514.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
site/static/img/members/53.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
site/static/img/members/554.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#1a1722"/><defs><radialGradient id="o" cx="50%" cy="40%" r="60%"><stop offset="0%" stop-color="#e8e5f0"/><stop offset="60%" stop-color="#a49fb4" stop-opacity="0.6"/><stop offset="100%" stop-color="#1a1722" stop-opacity="0"/></radialGradient></defs><rect width="240" height="240" fill="url(#o)"/></svg>
|
||||||
|
After Width: | Height: | Size: 438 B |
1
site/static/img/members/631.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#1a1722"/><defs><linearGradient id="sp" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stop-color="#6a6578"/><stop offset="100%" stop-color="#2a2738"/></linearGradient></defs><circle cx="120.0" cy="96" r="52" fill="url(#sp)"/><rect x="20" y="80" width="200" height="28" fill="#0f0e14"/><path d="M 40 240 C 40 160, 80 150, 120.0 148 C 160 150, 200 160, 200 240 Z" fill="url(#sp)"/></svg>
|
||||||
|
After Width: | Height: | Size: 522 B |
1
site/static/img/members/65535.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
1
site/static/img/members/6667.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#0f0e14"/><text x="120.0" y="126.0" text-anchor="middle" font-family="ui-monospace,monospace" font-size="16" fill="#a89cd9" letter-spacing="2">[ONLINE]</text></svg>
|
||||||
|
After Width: | Height: | Size: 301 B |
1
site/static/img/members/67.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#141220"/><g opacity="0.55" transform="translate(-18,0)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#2a2738"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#2a2738"/></g><g opacity="0.55" transform="translate(18,6)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 660 B |
1
site/static/img/members/7.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#16141f"/><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="50"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
1
site/static/img/members/80.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><defs><filter id="b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="10"/></filter></defs><g filter="url(#b)"><rect width="240" height="240" fill="#16141f"/><circle cx="120.0" cy="100" r="50" fill="#3a3548"/><path d="M 40.0 240 C 40.0 180, 80.0 150, 120.0 140 C 160.0 150, 200.0 180, 200.0 240 Z" fill="#3a3548"/></g><rect x="30" y="85" width="180" height="26" fill="#0f0e14"/></svg>
|
||||||
|
After Width: | Height: | Size: 510 B |
1
site/static/img/members/993.svg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
1
site/static/img/members/gerald.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" preserveAspectRatio="xMidYMid slice" ><rect width="240" height="240" fill="#18151f"/><rect x="0" y="170" width="240" height="70" fill="#120f19"/><g stroke="#6a5a44" stroke-width="3" fill="none" stroke-linecap="square"><line x1="90" y1="60" x2="86" y2="170"/><line x1="150" y1="60" x2="154" y2="170"/><line x1="88" y1="78" x2="152" y2="78"/><line x1="88" y1="102" x2="152" y2="102"/><line x1="88" y1="126" x2="152" y2="126"/><line x1="86" y1="60" x2="154" y2="60"/><line x1="74" y1="170" x2="166" y2="170"/><line x1="76" y1="178" x2="164" y2="178"/><line x1="78" y1="178" x2="74" y2="226"/><line x1="162" y1="178" x2="166" y2="226"/><line x1="90" y1="170" x2="94" y2="226"/><line x1="150" y1="170" x2="146" y2="226"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 788 B |
115
site/static/js/site.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// ---- Console liturgy (every page) ----
|
||||||
|
try {
|
||||||
|
console.log("");
|
||||||
|
console.log(
|
||||||
|
"%cYou found this quickly. That is either a good sign or a bad one.",
|
||||||
|
"color: #a89cd9; font-family: serif; font-size: 14px; font-style: italic;"
|
||||||
|
);
|
||||||
|
console.groupCollapsed("[REDACTED]");
|
||||||
|
console.log("· · · · ·");
|
||||||
|
console.log("the record has five paragraphs. count them.");
|
||||||
|
console.groupEnd();
|
||||||
|
console.log("%cnothing further.", "color: #6a6578; font-family: monospace; font-size: 11px;");
|
||||||
|
} catch (e) { /* ambient */ }
|
||||||
|
|
||||||
|
var path = window.location.pathname;
|
||||||
|
|
||||||
|
// ---- /record token fragments via hashchange ----
|
||||||
|
if (/^\/record\/?$/.test(path)) {
|
||||||
|
var FRAGS = {
|
||||||
|
"#p-05": "fragment 2/5 :: GVSWK3TK",
|
||||||
|
"#p-06": "fragment 4/5 :: NBSWY3DP"
|
||||||
|
};
|
||||||
|
function maybeLog() {
|
||||||
|
var h = window.location.hash;
|
||||||
|
if (FRAGS[h]) {
|
||||||
|
console.log("%c" + FRAGS[h], "color: #a89cd9; font-family: monospace; font-size: 12px; letter-spacing: 0.1em;");
|
||||||
|
window.scrollTo({ top: 0, behavior: "instant" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener("hashchange", maybeLog);
|
||||||
|
maybeLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Homepage subscribe form ----
|
||||||
|
var corr = document.querySelector("form.correspondence");
|
||||||
|
if (corr) {
|
||||||
|
corr.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var input = corr.querySelector("input[type=text]");
|
||||||
|
var v = (input.value || "").trim();
|
||||||
|
|
||||||
|
// valid IPv4+ : five octets 0-255
|
||||||
|
var ipv4plus = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
||||||
|
var m = v.match(ipv4plus);
|
||||||
|
if (m) {
|
||||||
|
var ok = m.slice(1).every(function (n) { var x = parseInt(n, 10); return x >= 0 && x <= 255; });
|
||||||
|
if (ok) {
|
||||||
|
console.log("%cnoted.", "color: #a89cd9; font-family: monospace; font-size: 12px;");
|
||||||
|
input.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.toLowerCase() === "fifth") {
|
||||||
|
window.location.href = "/record/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// any valid email: accept silent (we do not send; promise is kept quietly)
|
||||||
|
// invalid email or anything else: also accept silent.
|
||||||
|
input.value = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Contact page form ----
|
||||||
|
// Layer 6: the answer is 5. The sidecar is authoritative; we surface
|
||||||
|
// whatever it says. Fragment 5/5 arrives via the X-Fifth-Fragment header.
|
||||||
|
var cform = document.querySelector("form.attest");
|
||||||
|
if (cform) {
|
||||||
|
var out = cform.querySelector(".out");
|
||||||
|
cform.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var input = cform.querySelector("input[type=text]");
|
||||||
|
var v = (input.value || "").trim();
|
||||||
|
var body = new FormData();
|
||||||
|
body.append("guess", v);
|
||||||
|
fetch("/contact/attest", { method: "POST", body: body })
|
||||||
|
.then(function (r) {
|
||||||
|
var frag = r.headers.get("X-Fifth-Fragment");
|
||||||
|
return r.text().then(function (t) { return { frag: frag, text: t }; });
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
var first = (res.text || "").split("\n")[0].trim();
|
||||||
|
if (out) {
|
||||||
|
out.textContent = first;
|
||||||
|
out.style.color = (first === "at last.") ? "var(--accent)" : "var(--muted)";
|
||||||
|
}
|
||||||
|
if (res.frag) {
|
||||||
|
console.log(
|
||||||
|
"%c" + res.frag,
|
||||||
|
"color: #a89cd9; font-family: monospace; font-size: 12px; letter-spacing: 0.1em;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (first !== "at last.") input.value = "";
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
if (out) {
|
||||||
|
out.textContent = "no.";
|
||||||
|
out.style.color = "var(--muted)";
|
||||||
|
}
|
||||||
|
input.value = "";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 404 page: suspicious path console message ----
|
||||||
|
if (document.body && document.body.dataset.notfound === "1") {
|
||||||
|
if (/^\/(admin|wp-admin|wp-login\.php)/.test(path)) {
|
||||||
|
console.log("%cwe see you.", "color: #a89cd9; font-family: serif; font-style: italic; font-size: 14px;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
97
site/vault/index.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>— cfo.org</title>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="stylesheet" href="/static/css/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="frame">
|
||||||
|
<div class="bar">
|
||||||
|
<span><a href="/">CFO.ORG</a> — /vault</span>
|
||||||
|
<span>VISITOR LOG: 000000001</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vault">
|
||||||
|
<div class="scan">
|
||||||
|
<!-- consult the well-known. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 780">
|
||||||
|
<rect width="600" height="780" fill="#d9d3c4"/>
|
||||||
|
<!-- page noise -->
|
||||||
|
<g opacity="0.15">
|
||||||
|
<rect x="0" y="0" width="600" height="780" fill="url(#noise)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="noise" width="4" height="4" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="4" height="4" fill="#cec6b3"/>
|
||||||
|
<rect x="1" y="2" width="1" height="1" fill="#a89c82"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- header -->
|
||||||
|
<text x="300" y="60" text-anchor="middle" font-family="Times, serif" font-size="18" fill="#1a1a1a" letter-spacing="3">MINUTES — [REDACTED]</text>
|
||||||
|
<text x="300" y="86" text-anchor="middle" font-family="Times, serif" font-size="12" fill="#3a3a3a" letter-spacing="2">ANNO MCMXCIV · [LOCATION WITHHELD]</text>
|
||||||
|
|
||||||
|
<!-- redacted text blocks -->
|
||||||
|
<g fill="#0a0a0a">
|
||||||
|
<rect x="50" y="120" width="420" height="14"/>
|
||||||
|
<rect x="50" y="142" width="380" height="14"/>
|
||||||
|
<rect x="50" y="164" width="440" height="14"/>
|
||||||
|
<rect x="50" y="186" width="240" height="14"/>
|
||||||
|
|
||||||
|
<rect x="50" y="220" width="400" height="14"/>
|
||||||
|
<rect x="50" y="242" width="460" height="14"/>
|
||||||
|
<rect x="50" y="264" width="300" height="14"/>
|
||||||
|
|
||||||
|
<rect x="50" y="298" width="440" height="14"/>
|
||||||
|
<rect x="50" y="320" width="200" height="14"/>
|
||||||
|
<rect x="50" y="342" width="420" height="14"/>
|
||||||
|
|
||||||
|
<rect x="50" y="376" width="460" height="14"/>
|
||||||
|
<rect x="50" y="398" width="360" height="14"/>
|
||||||
|
<rect x="50" y="420" width="420" height="14"/>
|
||||||
|
<rect x="50" y="442" width="180" height="14"/>
|
||||||
|
|
||||||
|
<rect x="50" y="476" width="400" height="14"/>
|
||||||
|
<rect x="50" y="498" width="440" height="14"/>
|
||||||
|
<rect x="50" y="520" width="280" height="14"/>
|
||||||
|
|
||||||
|
<rect x="50" y="554" width="420" height="14"/>
|
||||||
|
<rect x="50" y="576" width="380" height="14"/>
|
||||||
|
<rect x="50" y="598" width="220" height="14"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- hand-written sequence in the fifth column margin -->
|
||||||
|
<g font-family="'Caveat', 'Patrick Hand', cursive, serif" font-size="22" fill="#2a1a6a">
|
||||||
|
<text x="520" y="140" transform="rotate(-4 520 140)">4</text>
|
||||||
|
<text x="520" y="240" transform="rotate(-2 520 240)">8</text>
|
||||||
|
<text x="520" y="320" transform="rotate(-5 520 320)">15</text>
|
||||||
|
<text x="520" y="420" transform="rotate(-3 520 420)">16</text>
|
||||||
|
<text x="520" y="520" transform="rotate(-6 520 520)">23</text>
|
||||||
|
<text x="520" y="600" transform="rotate(-2 520 600)">42</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- date stamp -->
|
||||||
|
<g transform="rotate(-10 140 700)">
|
||||||
|
<rect x="70" y="670" width="140" height="60" fill="none" stroke="#7a1a1a" stroke-width="2"/>
|
||||||
|
<text x="140" y="700" text-anchor="middle" font-family="Times, serif" font-size="13" fill="#7a1a1a" letter-spacing="2">SEALED</text>
|
||||||
|
<text x="140" y="720" text-anchor="middle" font-family="Times, serif" font-size="10" fill="#7a1a1a" letter-spacing="2">ANNO MCMXCIV</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="caption">#1994</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar footer">
|
||||||
|
<span>· · · · ·</span>
|
||||||
|
<span>NO COOKIES. NO ANALYTICS. NO RECORD OF YOUR VISIT.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/site.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||