{$DOMAIN} {
	# Compress everything EXCEPT the SSE stream — gzip buffering delays
	# "real-time" likes/comments until the ~30s keep-alive tick.
	@compressible not path /api/v1/stream
	encode @compressible zstd gzip

	# Site-wide security headers (defense-in-depth). HSTS is free since Caddy
	# already terminates TLS. nosniff also covers all of /media/*.
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
	}

	# SvelteKit frontend — static assets with long-lived cache (content-hashed filenames)
	@hashed_assets path_regexp hashed /_app/immutable/.*\.[a-f0-9]{8,}\.(js|css|woff2)$
	header @hashed_assets Cache-Control "public, max-age=31536000, immutable"

	# Media previews and thumbnails
	@previews path /media/previews/* /media/thumbnails/*
	header @previews Cache-Control "public, max-age=3600"

	# Original media files (private — only host can download). Force download
	# rather than inline rendering as defense-in-depth against any future
	# content-type confusion (previews/thumbnails are re-encoded and stay inline).
	@originals path /media/originals/*
	header @originals Cache-Control "private, max-age=86400"
	header @originals Content-Disposition "attachment"

	# API — never cache
	@api path /api/*
	header @api Cache-Control "no-store"

	# Route API and media requests to the Rust backend
	reverse_proxy /api/* app:3000
	reverse_proxy /media/* app:3000

	# Everything else goes to SvelteKit frontend
	reverse_proxy frontend:3001
}
