Skip to content

feat: state sync, Dante's circles, smoke themes, admin mode, generative artwork#5

Open
rmzi wants to merge 18 commits into
mainfrom
state-sharing
Open

feat: state sync, Dante's circles, smoke themes, admin mode, generative artwork#5
rmzi wants to merge 18 commits into
mainfrom
state-sharing

Conversation

@rmzi
Copy link
Copy Markdown
Owner

@rmzi rmzi commented Apr 2, 2026

Summary

  • State sync: Zero-knowledge encrypted sync across devices via username + password. PBKDF2 key derivation → AES-256-GCM — server stores only ciphertext
  • Minimal infra: Single Lambda Function URL + S3 (sync/ prefix in existing tracks bucket) + CloudFront behavior. No DynamoDB, no API Gateway, no Cognito
  • Dante's Circles: 9 unlockable smoke color themes tied to totalUniqueHeard milestones (0→100). CSS custom property driven (--smoke-1/2/3), persisted and synced
  • Smoke background: 4-layer radial gradient with animated pseudo-elements, respects prefers-reduced-motion
  • Cash rain: Enhanced Konami reward — 150 bills, 5 waves, 5 size tiers with parallax depth
  • Generative artwork: Canvas-based animated placeholder for tracks without cover art, deterministic hash per track
  • Profile screen rework: Local stats + inline credential form (unconnected) or sync status + circle progress (connected)
  • Clean enter screen: Just ENTER button, no credential inputs blocking music start
  • Admin debug mode: SHA-256 hashed admin username (configured in site.md, hashed at build time). Full circle unlock + debug strip + action menu
  • Theme config: Circle colors, admin user, all configurable per-instance via site.md frontmatter

Test plan

  • Smoke background visible on load (subtle white/grey gradients)
  • Enter screen is clean — just ENTER, no inputs
  • Music starts immediately after ENTER
  • Profile shows stats + credential form (first visit)
  • Connect with credentials → profile shows sync status + circle progress
  • Play 5 unique tracks → smoke shifts to circle 2, profile icon pulses
  • Tap earlier circle mark on profile → smoke changes
  • Circle preference persists across reload
  • Konami code triggers deep cash rain (150+ bills, parallax)
  • Tracks without artwork show generative animation
  • Long-press artwork still works for download (desktop + mobile)
  • Admin login → full circle unlock + debug strip + debug menu
  • prefers-reduced-motion disables smoke animation
  • Offline playback still works for cached tracks

🤖 Generated with Claude Code

rmzi and others added 9 commits April 2, 2026 04:09
- Creates lambda-sync.tf with Lambda function, Function URL, IAM role, and S3 policy scoped to sync/ prefix
- Adds sync-lambda origin and /sync/* cache behavior (no-cache, first in order) to CloudFront distribution
- Outputs sync_lambda_url for reference after apply

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- events.js: add sync modal (openSyncModal/closeSyncModal/setupSyncModalHandlers),
  wire stateSyncBtn click handler, auto-pull on load if credentials exist
- player.js: call debouncedPush() after saveFavoriteTracks() in toggleFavorite()
- tracks.js: call debouncedPush() after each saveHeardTracks() call
- pwa.js: isTrackCached() now checks both SW Cache API and state.cachedTracks (IndexedDB)
- ui.js: stateSyncBtn always shown (not gated behind secret mode)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- S3 GetObject returns AccessDenied instead of NoSuchKey without ListBucket
- Lambda Function URL needs lambda:InvokeFunction permission
- CORS set to allow all origins (CloudFront handles restriction)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CloudFront custom error responses intercept 404/403, returning HTML
  instead of JSON. Lambda now returns 200 with {found:false} or {error:...}
- Sync button was hidden until secret mode; now shown on init for all users
- Fixed escaped \! characters in sync.js that broke the obfuscator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move #state-sync-btn from search header to secondary-controls bar;
  always visible like share-btn (no hidden class)
- Replace sync modal with browser prompt() for username/password;
  delete openSyncModal, closeSyncModal, setupSyncModalHandlers and all
  modal HTML/CSS
- Sync playHistory and historyIndex in serializeState/mergeState;
  add savePlayHistory/loadPlayHistory to storage.js and wire them into
  player.js (on push, prev, fwd navigation) and startPlayer
- Clean up elements.js (remove 9 modal element refs), ui.js (remove
  stateSyncBtn show logic), and main.css (remove sync modal styles)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sync button now lives in secondary controls (with share, fav, browse)
- Always visible, no secret mode gate
- Uses browser prompt() instead of custom modal — much simpler
- First click: prompts for username/password, syncs, saves credentials
- Subsequent clicks: syncs immediately (force sync)
- Play history (playHistory + historyIndex) now persisted to localStorage
  and synced across devices (keep longer history on merge)
- Removed ~200 lines of modal HTML/CSS/JS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New modules (sync.js, crypto.js) were missing from the service worker's
SHELL_ASSETS list, so the old cached events.js (without sync handler)
was being served via stale-while-revalidate. Bumping to shell-v2 forces
a full reinstall. Also passthrough /sync/* API requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rmzi and others added 8 commits April 2, 2026 20:53
- Add profile screen: username, listen time, tracks heard, favorites,
  last played, sync status with remediation, debug panel
- Replace sync button with profile icon (person silhouette) in player
- Add listen stats tracking: totalListenSeconds, totalUniqueHeard,
  lastPlayedAt — accumulated on play/pause/end, persisted + synced
- Enter screen: returning users see "WELCOME BACK" + username,
  new users see credential inputs with "CONNECT" button
- Stats merge strategy: max() for counters, most-recent for timestamps
- Debug-in-UI skill for visual debug during development
- Bump SW shell cache to v3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Lambda SyntaxError from escaped \\! in index.mjs (bash heredoc artifact)
- Move enter-content lower (bottom: 20vh) with z-index: 11 above title logo
  so credential inputs aren't obscured
- Profile heard stat now uses heardTracks.size (same as player %) instead of
  totalUniqueHeard (which can exceed catalog size after full cycles)

Addresses: #7 items 1, 2, 4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, generative artwork

Smoke background: layered radial-gradient pseudo-elements with CSS custom
properties (--smoke-1/2/3) driven by the circle system. Reduced-motion
media query disables animation.

Dante's Circles: 9 unlockable smoke color themes tied to totalUniqueHeard
milestones (0→100). Auto-applies on advancement with a subtle profile icon
pulse. Users can manually switch between unlocked circles on the profile
screen. Circle state persists locally and syncs across devices (highest
circle wins on merge).

Profile screen rework: unconnected users see stats + inline credential
form ("SYNC ACROSS DEVICES"), replacing the prompt() flow. Connected
users see stats + sync status + circle progress marks.

Cash rain: 150 bills across 5 size tiers with parallax depth (tiny/far
to huge/close), weighted distribution, per-tier opacity and fall speed.

Generative artwork: canvas-based animated placeholder for tracks without
cover art. Deterministic blobs derived from track metadata hash — each
track gets a unique visual identity. Long-press download and click-to-
search gestures moved from <img> to container for compatibility.

Enter screen: stripped to just ENTER button — no credential inputs, no
greeting. Music starts immediately.

Theme config: site.md frontmatter now supports theme.circles overrides
for per-instance circle colors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Activates when synced as "rmzi": auto-unlocks secret mode and all
circles (totalUniqueHeard=999), shows a persistent green-on-black
debug strip at the bottom of every screen, and a slide-up action
menu for testing heard counts, cash rain, force sync, and secret
mode toggle. Deactivates cleanly on disconnect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin user is now set via `admin: rmzi` in site.md frontmatter,
passed through build-config.js to SITE.admin, and checked in
events.js instead of hardcoded string comparison.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
site.md stores admin username in plaintext (private repo), but
build-config.js now hashes it with SHA-256 before writing to
site.config.js. Runtime checks use Web Crypto to hash the entered
username and compare against the stored hash. The admin username
never appears in the shipped JS bundle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rmzi rmzi changed the title feat: state sync across devices + offline bug fix feat: state sync, Dante's circles, smoke themes, admin mode, generative artwork Apr 3, 2026
Replace inline credential form on profile screen with a proper modal
dialog. Triggered by "SYNC ACROSS DEVICES" button. Includes input
validation (max 20 chars, required fields, no spaces in username),
live character counts, error/success feedback, and Enter key submit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant