Overview

My personal site, styled after the terminal interfaces from SIGNALIS. Everything on it was designed and built entirely by me with no AI assistance. It runs behind nginx on a VPS, with the application layer written in Python using Quart — an async port of Flask. Redis handles caching and session state.

The site has a handful of distinct systems, each living in its own Quart blueprint: an interactive terminal emulator, a live Last.fm music tracker, a daily fractal generator, a guestbook with rate limiting, a writing archive, a collaborative pixel canvas called Paper Lily Place, and a budget tracker I built for myself.

Python Quart Redis nginx aiohttp APScheduler HTML/CSS/JS

Key Systems

// Blueprint architecture
Each major feature is a Quart blueprint. The main server file is small — it just registers them and handles shared middleware like cache headers and request logging.
from fractals       import fractal_bp
from music          import music_bp
from musings        import musings_bp
from paperlilyplace import place_bp
from budget_tracker import budget_bp

app.register_blueprint(fractal_bp)
app.register_blueprint(music_bp)
app.register_blueprint(musings_bp)
app.register_blueprint(place_bp)
app.register_blueprint(budget_bp)
// Last.fm — Redis-cached live music tracker
The music widget polls a local API endpoint every 30 seconds. The server caches Last.fm responses in Redis for 25 seconds so the external API isn't hammered on every poll. The cache key is fixed — there's only ever one user's data.
CACHE_KEY = "lastfm:recent_tracks"
CACHE_TTL = 25   # slightly less than the 30s client poll interval

async def now_playing():
    cached = redis_client.get(CACHE_KEY)
    if cached: return jsonify(json.loads(cached))

    data = await fetch_lastfm('user.getrecenttracks', limit=5)
    result = build_result(data)
    redis_client.setex(CACHE_KEY, CACHE_TTL, json.dumps(result))
    return jsonify(result)
// Paper Lily Place — real-time collaborative canvas via SSE
A collaborative pixel canvas. Canvas state is a flat binary buffer in Redis (one byte = one pixel, encoding a colour index). Writes are a single SETRANGE at offset y*W+x. Real-time updates broadcast to connected clients via server-sent events — Quart's async generator support makes the SSE endpoint clean.
def set_pixel(x, y, colour_idx):
    redis_client.setrange("place:canvas", y * WIDTH + x, bytes([colour_idx]))

@place_bp.route('/place/api/events')
async def sse():
    q = asyncio.Queue()
    _subscribers.append(q)
    async def generate():
        try:
            while True:
                event = await q.get()
                yield f"data: {json.dumps(event)}\n\n"
        finally:
            _subscribers.remove(q)
    return app.response_class(generate(), mimetype='text/event-stream')
// Guestbook — rate-limited with hashed IPs
Rate limiting is a Redis key per IP with a 1-hour TTL. IPs are stored as SHA-256 hashes so the database holds no identifiable visitor data.
if redis_client.get(f"guestbook:ratelimit:{ip}"):
    return jsonify({'error': 'One entry per hour.'}), 429

# store hashed IP — not reversible
ip_hash = hashlib.sha256(ip.encode()).hexdigest()
conn.execute(
    'INSERT INTO guestbook (name, message, ip_hash) VALUES (?,?,?)',
    (name, message, ip_hash)
)
redis_client.setex(f"guestbook:ratelimit:{ip}", 3600, 1)

Links