___ __ ____ ____ _ _ __ __
/ (_ / / / __ \ / __ \(_)___ _(_) /_____ _/ /
/ / __ __/ / / / / /_____/ / / / / __ `/ / __/ __ `/ /
/ / / // / / / /_/ /_____/ /_/ / / /_/ / / /_/ /_/ / /
\_\ \_,_/_/ /_____/ /_____/_/\__, /_/\__/\__,_/_/
/____/
iPod Classic Digital Clone
๐ต Live Demo โข ๐ Docs โข ๐ค Contributing โข ๐๏ธ Architecture
Drag & drop your song and create an iPod-like digital footprint
- Metadata Editing: Title, artist, album, artwork, rating (1-5 stars)
- Click Wheel Navigation: Authentic iPod controls simulation
- Progress Seeking: Scrub through your track with visual feedback
- Track Numbers: Full album context with track X of Y
- Authentic Battery Logic: Natural discharging cycle that drains slowly while the app is active
| Control | Menu Screen | Now Playing Screen |
|---|---|---|
| Wheel rotation | Scroll through menu items | Seek through track |
| Center button | Select highlighted item | Toggle edit mode |
| Menu button | โ | Return to menu |
| Play/Pause | Jump to Now Playing | Visual feedback |
| Prev / Next | Cycle menu items | Seek -5s / +5s |
- iPod OS Mode (default): Full menu navigation with 9 items (Music, Videos, Photos, Podcasts, Extras, Settings, Shuffle Songs, Now Playing, About)
- Center button on Now Playing: Toggles edit mode โ tap to make title, artist, album, rating, and time editable, tap again to lock
graph LR
A[๐ฑ 2D Edit] --> B[๐ฎ 3D Experience]
B --> C[๐ฌ Marquee Preview]
C --> D[๐ป ASCII Mode]
D --> E[๐ Focus Mode]
E --> A
- 2D Edit Mode: Full metadata editing interface
- 3D iPod Classic: Interactive 3D rendering with Three.js
- Marquee Preview: Animated scrolling text simulation
- ASCII Mode: Terminal-style text representation
- Focus Mode: Minimal, distraction-free view
flowchart TD
A[Export Request] --> B{Export Type}
B -->|PNG| C[Create Detached Clone]
B -->|GIF/MP4| D[Pipelined Capture Flow]
C --> E[html-to-image]
E -->|Success| F[Process Blob]
E -->|Fail| G[html2canvas Fallback]
G --> F
D --> H[Main Thread Capture]
H -->|ImageBitmap| I[Worker Encoding]
I -->|Parallel| H
I --> J[Finalize & Mux]
J --> F
F --> K{Platform}
K -->|Mobile| L[Web Share API]
K -->|Desktop| M[Blob Download]
L -->|Fail| N[Fallback Download]
- PNG Export: 4x resolution (1200ร1600px) with dual fallback strategy
- Animated GIF: 12 FPS marquee animation with
gifencencoding - MP4 Export: H.264 video with 12Mbps bitrate for high-quality motion capture
- Pipelined Architecture: Concurrent capture and encoding using Web Workers and
ImageBitmapfor zero-jank UI during export - Real-time ETA: Live estimation of remaining export time based on frame capture velocity
- Smart Animation Capture:
- Marquee: Automatically scrolls for long titles (overflow-based)
- Song Progress: Progress bar and timestamps are automatically animated to reflect the export duration
- Consistency: The export preview dialog provides a 1:1 simulation of the final rendered file
- Platform Detection: Web Share API for mobile, download for desktop
- Automatic Fallback: Graceful degradation if primary method fails
- 138+ Grey Tones: 6 undertone families (Neutral, Warm, Cool, Greige, Sage, Lavender) ร 23 perceptual lightness stops + OKLCH spectrum picker
- 9 Apple Colors: Classic iPod finishes from graphite to starlight
- Dual Theming: iPod case color + Background color independent control
- Color History: Track and revisit recent color choices
- Live Preview: Real-time updates across all view modes
sequenceDiagram
participant U as User
participant C as Component
participant S as Storage
participant L as localStorage
U->>C: Edit Metadata
C->>S: saveMetadata()
S->>L: Store JSON
U->>C: Save Snapshot
C->>S: saveSnapshot()
S->>L: Full State
U->>C: Reload Page
L->>S: Restore Data
S->>C: Hydrate State
C->>U: Restored UI
- Auto-save: Metadata and UI state persist automatically
- Manual Snapshots: Save complete state snapshots
- Quick Restore: One-click restoration of saved snapshots
- localStorage: Client-side persistence, no backend required
- Installable as Progressive Web App
- Offline-capable with service worker caching
- Mobile-optimized responsive design
- Touch-friendly interface
- pnpm 10+
- Git for cloning the repository
This project ships with shell helpers for faster git workflows:
| Tool | What It Does |
|---|---|
| forgit | Interactive git (add, log, diff, checkoutโฆ) via fzf |
| gh-repo-fzf | Fuzzy-find and clone GitHub repos |
To enable forgit aliases (ga, glo, gcb, gdโฆ) in this directory:
brew install direnv # one-time
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
direnv allow # inside this projectsource /opt/homebrew/opt/forgit/share/forgit/forgit.plugin.zshTo use gh-repo-fzf:
gh repo-fzf # fuzzy-find repos from your GitHubBoth for
forgitaliases andgh repo-fzfrequirefzfandghto be installed:brew install fzf gh forgit && gh extension install kavinvalli/gh-repo-fzf
# Clone the repository
git clone https://github.com/stussysenik/v0-ipod.git
cd v0-ipod
# Install dependencies
pnpm install
# Start development server
pnpm devThe app will be available at http://localhost:4001
This repo also includes a small local Cursor SDK harness based on the official
cursor/cookbook quickstart example.
# add your Cursor API key to .env.local or export it in your shell
echo 'CURSOR_API_KEY="crsr_..."' >> .env.local
# ask Cursor to inspect this workspace
pnpm cursor:harness -- "Summarize the export pipeline in this repo"Optional flags:
--model composer-2--cwd /absolute/path/to/workspace--forceto allow local execution with uncommitted changes
๐ก Tip: Override the port if needed:
PORT=4010 pnpm dev PORT=4010 pnpm start
pnpm build
pnpm startThis repository now uses Storybook as the default review surface for shared UI, tokens, and the physical iPod assembly.
Use the app page when you need full integration behavior:
- metadata editing flow
- persistence and snapshot behavior
- export behavior
- broader UX validation across the whole workbench
Use Storybook when you want to change a visual system input or a component and inspect it in isolation first.
A story is a small isolated example of a component or visual surface.
Examples:
- an
IconButtonin default, hover, focus, and disabled states - the iPod status bar by itself
- the full physical iPod assembly with real screen and click-wheel parts
- a token manifest board that lets you inspect source values before changing component code
Stories are not the app. They are focused review fixtures for one component, one assembly, or one source-of-truth surface at a time.
# Run Storybook locally for day-to-day work
pnpm storybook
# Build the static Storybook site
pnpm build-storybook
# Run Storybook-linked tests
pnpm storybook:test
# Run build + Storybook tests together
pnpm storybook:validateHow to use them:
- Use
pnpm storybookwhile actively designing or refactoring. - Use
pnpm build-storybookto make sure the Storybook site compiles cleanly. - Use
pnpm storybook:testto verify story interactions and test coverage. - Use
pnpm storybook:validatebefore landing larger Storybook-related changes.
pnpm build-storybook is not the main daily command. It is the static
verification/build command. The main interactive command is pnpm storybook.
There are three main design ownership layers in this repo:
-
tokens/*Shared reusable UI tokens. Today this is mainlytokens/shared-ui.json. -
scripts/*.jsonProduct-owned iPod manifests. These are not shared design-system primitives. They hold iPod-specific color, finish, chrome, and surface data. -
components/*The actual implementation layer.components/ui/*is shared UI.components/ipod/*is product-owned iPod assembly code.
If you want to change a shared primitive:
- Edit
tokens/shared-ui.jsonif the styling change is semantic and reusable. - Open
tokens/shared-ui/Manifestin Storybook. - Open the affected
components/ui/*story, for examplecomponents/ui/IconButton. - Adjust the component only if the token change is not enough.
- Run
pnpm build-storybookwhen the change is ready to verify.
If you want to change the physical iPod finish, screen chrome, or wheel:
- Edit the owning product source:
scripts/color-manifest.json,scripts/design-system.json, or acomponents/ipod/*file. - Open the matching Storybook entry first:
scripts/color-manifest/ProductFinishes,components/ipod/display/IpodStatusBar,components/ipod/display/IpodScreen,components/ipod/controls/IpodClickWheel,components/ipod/device/PhysicalIpod. - Review the isolated element first, then the full physical assembly story.
- Open the app workbench only after the Storybook pass looks right.
The Storybook tree mirrors repository ownership on purpose:
Foundations/*tokens/*components/ui/*components/ipod/*scripts/*
That means you should be able to move from a Storybook entry to the owning file without translation.
Recommended first-read order inside Storybook:
Foundations/Start Heretokens/shared-ui/Manifestcomponents/ipod/device/PhysicalIpod- the relevant isolated component story
scripts/color-manifest/ProductFinisheswhen tuning finish direction
Important:
- changing Storybook controls does not persist anything globally
- changing the real source file does
If you tweak a color in Storybook controls, you are only previewing a temporary
runtime state for that story. If you edit tokens/shared-ui.json,
scripts/color-manifest.json, scripts/design-system.json, or the owning
components/* file, every consumer of that source updates globally.
If design work happens in Figma:
- shared primitive changes still start in repository token files
- Storybook is used to review the result in code
- Tokens Studio syncs to the repository JSON
The repository stays authoritative. Figma sync is downstream collaboration, not a parallel source of truth.
The click wheel labels (MENU, โถโธ, โฎ, โญ) are positioned using a source-truth proportion system derived from the official Wikipedia SVG diagram of the iPod click wheel.
The Wikipedia SVG provides precise coordinate data:
| Element | Outer Circle | Center Button | Ring Width |
|---|---|---|---|
| Radius | 245 units | 90 units | 155 units |
Label positions were measured from raw SVG coordinates:
| Label | Distance from outer edge | % of ring width |
|---|---|---|
| MENU | 25 units | 16.1% |
| Play/Pause | 26 units | 16.8% |
| Prev/Next | 28 units | 18.1% |
The constraint that keeps labels true to the original hardware:
0.12 โค (inset_px / ring_width) โค 0.20
Where ring_width = (wheel_size - center_size) / 2 and inset_px = inset_% ร wheel_size.
Each preset is checked against the inequality by solving for the percentage that places labels at ~15โ18% of the ring from the outer edge:
inset_% โ 0.15 ร ring_width / wheel_size
| Preset | Old inset | Ring position | New inset | Ring position |
|---|---|---|---|---|
| 2007 | 10.5% | 33% โ | 5% | 16% โ |
| 2008 | 2% | 6% โ | unchanged | โ |
| 2009 | 10% | 30% โ | 5% | 15% โ |
This ensures no preset places labels closer to the center button than to the outer edge โ matching the visual balance of the original hardware.
graph LR
A[๐ต Upload Song] --> B[โ๏ธ Edit Metadata]
B --> C[๐จ Customize Appearance]
C --> D{Choose View}
D -->|2D| E[๐ Edit Mode]
D -->|3D| F[๐ฎ 3D Experience]
D -->|Preview| G[๐ฌ Marquee Preview]
D -->|ASCII| H[๐ป Terminal Mode]
E --> I[๐ค Export PNG]
G --> J[๐ค Export GIF]
H --> J
v0-ipod/
โโโ app/ # Next.js 15 app directory
โ โโโ layout.tsx # Root layout with PWA manifest
โ โโโ page.tsx # Main iPod component page
โโโ components/
โ โโโ ipod/
โ โ โโโ ipod-classic.tsx # Main iPod container
โ โ โโโ ipod-screen.tsx # Screen display logic
โ โ โโโ ascii-ipod.tsx # ASCII mode renderer
โ โ โโโ grey-palette-picker.tsx # OKLCH grey palette picker
โ โ โโโ click-wheel.tsx # Navigation controls
โ โโโ three/
โ โ โโโ three-d-ipod.tsx # 3D iPod with Three.js
โ โโโ ui/ # Radix UI components
โโโ lib/
โ โโโ export-utils.ts # PNG/GIF export pipeline
โ โโโ storage.ts # localStorage persistence
โ โโโ utils.ts # Utility functions
โโโ public/
โโโ manifest.json # PWA manifest
graph TD
A[IPodClassic] --> B[IPodScreen]
A --> C[ClickWheel]
A --> D[ThreeDIPod]
B --> E[MarqueeText]
B --> F[ProgressBar]
B --> G[StarRating]
B --> H[EditableText]
A --> I[ExportUtils]
I --> J[PNG Export]
I --> K[GIF Export]
style A fill:#3178C6,stroke:#fff,color:#fff
style I fill:#61DAFB,stroke:#000,color:#000
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (React 19) |
| Language | TypeScript (strict mode) |
| 3D Rendering | Three.js + React Three Fiber |
| UI Components | Radix UI + Tailwind CSS |
| Export Pipeline | html-to-image + html2canvas + gifenc |
| State Management | React useReducer + Context |
| Storage | localStorage API |
| PWA | @ducanh2912/next-pwa |
| Deployment | Vercel |
This repository currently does not ship with a committed automated test suite. Use the existing quality gates plus focused manual checks while rebuilding test ownership:
pnpm lint
pnpm type-check
pnpm build| Script | Description |
|---|---|
pnpm dev |
Start development server on port 4001 |
pnpm build |
Build production bundle |
pnpm start |
Start production server |
pnpm clean:next |
Remove .next if a stale local build causes runtime issues |
pnpm lint |
Run OXC (oxlint) |
pnpm lint:fix |
Auto-fix OXC lint issues |
pnpm lint:eslint |
Run the legacy Next/ESLint ruleset |
pnpm type-check |
Run TypeScript type checking |
pnpm validate |
Run lint + type check |
pnpm storybook |
Start Storybook locally on port 6006 |
pnpm build-storybook |
Build the static Storybook site |
pnpm storybook:test |
Run Storybook-linked Vitest coverage |
pnpm storybook:validate |
Run Storybook build + Storybook tests |
graphite โข silver โข starlight โข midnight โข blue โข pink โข purple โข red โข green
6 undertone families โ Neutral, Warm, Cool, Greige, Sage, Lavender โ each with 23 perceptually-spaced lightness stops. Hex deduplication ensures unique swatches. Gradient preview bar, curated favorites, and undertone tab persistence via localStorage.
Full spectrum color picker with infinite color possibilities
Shades and palette matches are driven by perceptually uniform color math, not naive RGB interpolation:
hex โ RGB โ CIEXYZ โ CIELAB โ ฮE 2000
| Step | Formula | Purpose |
|---|---|---|
hex โ RGB |
sRGB transfer | Linearize gamma-compressed values |
RGB โ XYZ |
CIE 1931 matrix | Device-independent tristimulus |
XYZ โ LAB |
CIE 1976 | Perceptually uniform opponent space |
LAB โ ฮE |
CIEDE2000 | Industry-standard color difference |
ฮE 2000 corrects the remaining non-uniformities in CIELAB:
- Hue weighting (
t): blue region has smaller tolerance than yellow - Lightness compensation (
sl): dark colors have smaller JND - Chroma compensation (
sc): low-saturation colors behave differently - Hue-chroma rotation (
rt): elliptical tolerance in blue region
Shades are displayed only when ฮE โค 15 โ beyond that a color is no longer
meaningfully "a shade of" the target. A ฮE of ~2.3 is JND (just noticeable
difference); ~1.0 is barely perceptible.
The same pipeline also derives the screen gasket color from the case skin, keeping the physical gap cohesive on every theme.
This README is the central starting point. Use the documents below for deeper detail:
- Design-System Foundation: component ownership, token boundaries, Storybook workflow
- Architecture: system structure and implementation detail
- Contributing: contribution workflow and expectations
- Docs Index: entry point to the rest of the repository docs
- Vision: product direction and intent
- Roadmap: planned work
- Tech Stack: stack overview
- iPod Assembly Notes: product-specific assembly context
- Pull Request Template: contribution guidelines
We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Make your changes using semantic commits
- Run validation:
pnpm validate - Push and create a PR
See CONTRIBUTING.md for detailed guidelines including:
- Semantic commit conventions
- Code style guidelines
- Testing requirements
- PR review process
If pnpm dev fails because port 4001 is occupied:
PORT=4010 pnpm devThe app uses the Web Share API on mobile devices. If your browser doesn't support it, the export will automatically fall back to direct download.
If you experience performance issues with the 3D view:
- Try switching to 2D edit mode
- Close other browser tabs
- Update your graphics drivers
- Use a modern browser (Chrome, Edge, Safari)
MIT License - see LICENSE for details
- Inspired by the iconic iPod Classic design
- Built with modern web technologies
- Export pipeline leverages multiple fallback strategies for reliability
- Testing ensures cross-platform compatibility
Made with โค๏ธ for music lovers
