initial commit

This commit is contained in:
Gregory Gauthier 2026-04-28 13:20:50 +01:00
commit 33c9b16267
12 changed files with 369 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
dist/
build/
*.egg-info/
*.egg
# Virtual environments
venv/
env/
ENV/
# IDE
.idea/
poetry.lock
# Generated files
**/*.mp4

0
cipher/__init__.py Normal file
View File

Binary file not shown.

BIN
cipher/assets/static.wav Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
=== NERDLETTER DAY BOOK MASTER — ISSUE 01 (JUN 2026) ===
PAGE 001 NERDLETTER DAY BOOK #001
VLGKU JXCYH LYMDC ETLUB ZIAUI OOABL RSPXP SEILP LFOOF FQFRY
──────────────────────────────────────────────────────────────────────
=== END OF MASTER PAD — 1 PAGES GENERATED ===

51
cipher/encrypt.py Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
def load_pad(filename="cipher/data/daybook_master_example.txt"):
"""Load the master pad file."""
pad_text = Path(filename).read_text()
# Extract only the 5-letter groups, ignore headers and spaces
groups = [g for g in pad_text.split() if len(g) == 5 and g.isalpha()]
return "".join(groups).upper()
def encrypt_message(plaintext: str, pad: str, start_pos: int):
"""Encrypt using the pad starting at a given position."""
# Clean plaintext: uppercase letters only, remove everything else
pt = "".join(c for c in plaintext.upper() if c.isalpha())
if not pt:
return "ERROR: No letters in message"
ciphertext = []
for i, char in enumerate(pt):
if i + start_pos >= len(pad):
return "ERROR: Pad exhausted! Generate new master."
p = ord(pad[start_pos + i]) - 65
c = ord(char) - 65
d = (c + p) % 26
ciphertext.append(chr(d + 65))
# Format in 5-letter groups
ct_str = "".join(ciphertext)
return " ".join(ct_str[j:j + 5] for j in range(0, len(ct_str), 5))
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 encrypt.py 'Your secret message here' [start_page]")
print("Example: python3 encrypt.py 'Meeting at the retro show on Saturday' 7")
sys.exit(1)
message = sys.argv[1]
start_page = int(sys.argv[2]) if len(sys.argv) > 2 else 1
pad_str = load_pad()
# Each page = 50 letters (10 groups × 5)
start_pos = (start_page - 1) * 50
ct = encrypt_message(message, pad_str, start_pos)
print(f"\n=== NERDLETTER SECRET MESSAGE (Use Day Book PAGE {start_page:03d}) ===")
print(ct)
print("\nCopy the line above to the website.")

23
cipher/instructions.md Normal file
View File

@ -0,0 +1,23 @@
## HOW TO DECRYPT — STEP BY STEP (using your 8-bit machine)
1. Type in the NERDLETTER DECRYPTOR BASIC program (see below)
2. RUN
3. When it asks, type the PAD groups from the appropriate page:
`VLGKU` then `JXCYH` then `LYMDC` then `ETLUB`
4. Then type the CIPHERTEXT groups:
`HLXIB` then `JACJP` then `ERXHN` then `EFM`
5. The machine will instantly print the plaintext!
## RESULT YOU SHOULD SEE:
```
MARYHADALITTLELAMB
```
(Add spaces by hand: “Mary had a little lamb”)
This is perfect secrecy — no computer on Earth can read it without this exact Day Book page.

View File

View File

@ -0,0 +1,122 @@
#!/bin/bash
# NERDLETTER NUMBERS STATION MP4 GENERATOR
# Lincolnshire Poacher style with letter-by-letter cipher readout
#
# Audio structure:
# Base layer (full duration): low-volume shortwave static
# On top, in sequence:
# 1. Lincolnshire Poacher theme
# 2. Voice: announcement + letters (0.5s between letters, 1.0s between groups)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ASSETS_DIR="${SCRIPT_DIR}/../assets"
MESSAGE="$1" # e.g. "HLXIB JACJP ERXHN EFM"
PAGE="$2" # e.g. 001
EPISODE="${3:-01}"
OUTFILE="nerdletter_secret_${EPISODE}_page${PAGE}.mp4"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
SAMPLE_RATE=22050
echo "=== NERDLETTER SECRET TRANSMISSION #${EPISODE} ==="
echo "Page ${PAGE} — Generating MP4..."
# --- 1. Generate voice announcement ---
INTRO_TEXT="NERDLETTER SECRET MESSAGE ${EPISODE}. USE DAY BOOK PAGE ${PAGE}."
echo "$INTRO_TEXT" | \
espeak-ng -ven+f3 -s88 -k50 -a 200 -w "${TMPDIR}/intro.wav" --punct="" 2>/dev/null
# --- 2. Generate letter-by-letter audio with proper gaps ---
# Build an ffmpeg concat list: each letter as a wav, with silence between
CONCAT_LIST="${TMPDIR}/concat.txt"
> "$CONCAT_LIST"
PART=0
# Pre-generate silence files
ffmpeg -f lavfi -i "anullsrc=r=${SAMPLE_RATE}:cl=mono" \
-t 0.45 -c:a pcm_s16le "${TMPDIR}/gap_letter.wav" -y 2>/dev/null
ffmpeg -f lavfi -i "anullsrc=r=${SAMPLE_RATE}:cl=mono" \
-t 0.9 -c:a pcm_s16le "${TMPDIR}/gap_group.wav" -y 2>/dev/null
for group in $MESSAGE; do
echo " Group: $group"
for ((i=0; i<${#group}; i++)); do
letter="${group:$i:1}"
PART_FILE="${TMPDIR}/letter_${PART}.wav"
echo "$letter" | espeak-ng -ven+f3 -s81 -k65 -a 210 -w "$PART_FILE" 2>/dev/null
echo "file '${PART_FILE}'" >> "$CONCAT_LIST"
PART=$((PART + 1))
# 0.5s gap after each letter (except last letter of last group)
if [ $i -lt $((${#group} - 1)) ]; then
echo "file '${TMPDIR}/gap_letter.wav'" >> "$CONCAT_LIST"
fi
done
# 1.0s gap after each group
echo "file '${TMPDIR}/gap_group.wav'" >> "$CONCAT_LIST"
done
# Concatenate all letter parts into one track
ffmpeg -f concat -safe 0 -i "$CONCAT_LIST" \
-c:a pcm_s16le "${TMPDIR}/letters.wav" -y 2>/dev/null
# --- 3. Generate closing announcement ---
OUTRO_TEXT="END TRANSMISSION. TUNE TO OLD COMPUTER NERD DOT COM FOR FURTHER INSTRUCTIONS."
echo "$OUTRO_TEXT" | \
espeak-ng -ven+f3 -s88 -k50 -a 200 -w "${TMPDIR}/outro.wav" --punct="" 2>/dev/null
# --- 4. Concatenate intro + letters + outro ---
VOICE_LIST="${TMPDIR}/voice_concat.txt"
echo "file '${TMPDIR}/intro.wav'" > "$VOICE_LIST"
echo "file '${TMPDIR}/gap_group.wav'" >> "$VOICE_LIST"
echo "file '${TMPDIR}/letters.wav'" >> "$VOICE_LIST"
echo "file '${TMPDIR}/gap_group.wav'" >> "$VOICE_LIST"
echo "file '${TMPDIR}/outro.wav'" >> "$VOICE_LIST"
ffmpeg -f concat -safe 0 -i "$VOICE_LIST" \
-ar ${SAMPLE_RATE} -ac 1 -c:a pcm_s16le "${TMPDIR}/voice.wav" -y 2>/dev/null
# --- 5. Concatenate poacher theme + voice into foreground track ---
FG_LIST="${TMPDIR}/fg_concat.txt"
echo "file '${TMPDIR}/poacher_resampled.wav'" > "$FG_LIST"
echo "file '${TMPDIR}/voice.wav'" >> "$FG_LIST"
# Resample poacher intro to match
ffmpeg -i "${ASSETS_DIR}/poacher_intro.wav" \
-ar ${SAMPLE_RATE} -ac 1 -c:a pcm_s16le "${TMPDIR}/poacher_resampled.wav" -y 2>/dev/null
ffmpeg -f concat -safe 0 -i "$FG_LIST" \
-c:a pcm_s16le "${TMPDIR}/foreground.wav" -y 2>/dev/null
# --- 6. Get foreground duration, generate static to match ---
FG_DUR=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${TMPDIR}/foreground.wav")
echo " Audio duration: ${FG_DUR}s"
ffmpeg -f lavfi -i "anoisesrc=d=${FG_DUR}:c=0.22" \
-af "highpass=f=2500,lowpass=f=9000,volume=0.18" \
-ar ${SAMPLE_RATE} -ac 1 -t "${FG_DUR}" \
-c:a pcm_s16le "${TMPDIR}/static.wav" -y 2>/dev/null
# --- 7. Mix static underneath foreground ---
ffmpeg -i "${TMPDIR}/foreground.wav" -i "${TMPDIR}/static.wav" \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest:normalize=0" \
-c:a pcm_s16le "${TMPDIR}/final_audio.wav" -y 2>/dev/null
# --- 8. Build video to match audio duration ---
# Round up to nearest second for video duration
VIDEO_DUR=$(echo "$FG_DUR" | awk '{printf "%d", $1 + 1}')
ffmpeg -f lavfi -i "color=black:s=1280x720:d=${VIDEO_DUR}" \
-i "${TMPDIR}/final_audio.wav" \
-vf "drawtext=font='Courier':fontsize=50:fontcolor=green@0.95:text='NERDLETTER SECRET TRANSMISSION #${EPISODE}':x=(w-text_w)/2:y=80, \
drawtext=font='Courier':fontsize=36:fontcolor=green@0.85:text='USE DAY BOOK PAGE ${PAGE}':x=(w-text_w)/2:y=170, \
drawtext=font='Courier':fontsize=44:fontcolor=lime@0.95:text='${MESSAGE}':x=(w-text_w)/2:y=270:box=1:boxcolor=black@0.75:boxborderw=15, \
drawtext=font='Courier':fontsize=30:fontcolor=green@0.75:text='DESTROY PAGE AFTER USE — SUBSCRIBERS ONLY':x=(w-text_w)/2:y=640" \
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest "$OUTFILE" -y
echo "Done: ${OUTFILE}"

15
pyproject.toml Normal file
View File

@ -0,0 +1,15 @@
[project]
name = "nerdletter"
version = "0.1.0"
description = ""
authors = [
{name = "Gregory Gauthier",email = "gregory.gauthier@perspectum.com"}
]
requires-python = "^3.14"
dependencies = [
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

4
site/deploy.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env zsh
scp index.html gmgauthier@socrates:/var/www/nerdletter.oldcomputernerd.com

124
site/index.html Normal file
View File

@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Nerdletter • Retro-Modern Fusion</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap'); /* Perfect CRT font */
body {
background: #000;
color: #00ff41;
font-family: 'VT323', monospace;
font-size: 1.4rem;
line-height: 1.3;
margin: 0;
padding: 2rem;
image-rendering: pixelated;
text-shadow: 0 0 8px #00ff41;
}
.crt {
max-width: 800px;
margin: 0 auto;
border: 16px solid #222;
background: #001100;
padding: 2rem;
box-shadow: 0 0 40px #00ff41, inset 0 0 80px rgba(0,255,65,0.2);
}
h1 {
font-size: 3.5rem;
text-align: center;
margin: 0 0 1rem;
text-transform: uppercase;
letter-spacing: 4px;
animation: flicker 1.5s infinite alternate;
}
@keyframes flicker { 0% { opacity: 0.95; } 100% { opacity: 1; } }
.tagline { text-align: center; font-size: 1.8rem; margin-bottom: 2rem; color: #00cc33; }
.scanlines {
position: relative;
}
.scanlines::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: repeating-linear-gradient(transparent 0px, transparent 2px, rgba(0,255,65,0.07) 2px, rgba(0,255,65,0.07) 4px);
pointer-events: none;
}
section { margin: 3rem 0; }
h2 { color: #00ff9d; border-bottom: 3px double #00ff41; padding-bottom: 0.5rem; }
.issue-teaser {
background: #001a00;
padding: 1.5rem;
border: 4px solid #00ff41;
}
.subscribe {
text-align: center;
background: #003300;
padding: 2rem;
border: 6px double #00ff41;
}
a { color: #00ff9d; text-decoration: underline dotted; }
a:hover { color: #ffff00; }
footer { text-align: center; font-size: 1.1rem; margin-top: 4rem; opacity: 0.7; }
</style>
</head>
<body>
<div class="crt scanlines">
<h1>THE NERDLETTER</h1>
<p class="tagline">COMPUTING WHEN IT WAS STILL FUN</p>
<section>
<h2>INCOMING TRANSMISSION</h2>
<div class="issue-teaser">
<video controls width="100%" style="margin-bottom: 1rem; border: 2px solid #00ff41;">
<source src="https://gmgauthier.us-east-1.linodeobjects.com/videos/nerdletter-secret-01.mp4" type="video/mp4">
</video>
If you arrived here by way of a secret transmission, consult your day book
for decyphering instructions. Starting Monday, June 1, there will be a new secret message each week.<br><br>
No day book? You may obtain one by emailing
<a href="mailto:nerdletter@gmgauthier.com">nerdletter@gmgauthier.com</a>
with proof of a $5 PayPal subscription payment. You'll also receive a BASIC program that will help you
decode the secret messages compatible with almost all 8-bit era computers!
</div>
</section>
<section>
<h2>ISSUE #001 COMING SOON</h2>
<div class="issue-teaser">
<strong>Featured in our first issue:</strong><br>
• News and Interviews with the greats of the past!<br>
• DIY projects from community members!<br>
• Modern Projects that are keeping your retro system alive!<br>
• Guest essays from people who know what theyre talking about!<br>
• Bonus content: Nerdy stories!
</div>
</section>
<section>
<h2>Why a print newsletter in 2026?</h2>
<p>We all remember the days when you had to send away for stuff. The self-addressed stamped envelope.
The $5 check. The anxious checking of the post box for your magazine. We also remember the joy of
having that print in our hands. The feeling of opening it up and seeing fresh content inside, and
the eager anticipation of that moment when you'd have time to sit down with a cup of coffee and
read. That's why this exists. Because some things are better with ink and paper. No pop-ups.
No tracking. Just you, a cup of coffee, and stories about the machines that shaped us — plus the
crazy modern hacks that keep them alive.</p>
<p>Small print run. Big passion. For retro-fusion lovers only.</p>
</section>
<div class="subscribe">
<h2>GET YOUR COPY</h2>
<p>Dot-matrix printed edition mailed directly to your door.<br>
Limited run — When the paper runs out, that's it.</p>
<p><strong>Drop me a line:</strong> <a href="mailto:nerdletter@gmgauthier.com">nerdletter@gmgauthier.com</a></p>
<p>Ill send you the details.</p>
</div>
<footer>
© 2026 The Nerdletter • Old Computer Nerd HQ • Oxford, UK<br>
<span style="font-size:0.9rem;">"If it booted in 1985, it'll boot in 2026."</span>
</footer>
</div>
</body>
</html>