Commit Graph
26 Commits
Author SHA1 Message Date
rin.kagamine 4ca89577d7 feat: Add download button to player bar
Player.tsx: Download button added to the left of the album art thumbnail
in the track info section (hidden on mobile). Uses window.open() to trigger
browser download. Styled as text-lyria-muted/60 base, hover to lyria-accent.
Consistent with secondary action tier styling.
2026-05-29 21:42:29 -04:00
rin.kagamine 905201f239 fix: Establish clear button color hierarchy in Gallery
Button color tiers (all views):
- Tier 1 (Primary actions): Download, Delete — text-lyria-muted base,
  hover to accent/red. Always visible.
- Tier 2 (Secondary actions): Rename, Artwork upload — text-lyria-muted/60
  base, hover to white. Subtler but still discoverable.

Previously all buttons were text-lyria-muted/40 (barely visible) or mixed
inconsistently. Now there's a clear visual hierarchy:
  Primary > Secondary > Background elements

Applies to both ribbon view (w-4 h-4 icons) and tile view (w-3 h-3 icons).
2026-05-29 21:36:20 -04:00
rin.kagamine 119b75fbcd feat: MP3 download/export capability
Backend:
- New endpoint: GET /api/clips/{id}/download
  - Returns MP3 with Content-Disposition: attachment header
  - Filename derived from clip title (sanitized) or prompt, falls back to clip ID
  - Sanitization: alphanumeric + spaces/hyphens/underscores only, max 60 chars
  - Uses FastAPI FileResponse with media_type=audio/mpeg

Frontend:
- Gallery ribbons: Download button (downward arrow icon) next to delete button
  - Uses window.open() to trigger browser download
  - Hover: lyria-accent color
- Gallery tiles: Download button in tile footer action row
  - Small icon (w-3 h-3), same hover behavior
- DetailPanel: Full-width 'Download MP3' button above Delete Track
  - lyria-accent styled, with download icon
  - Uses <a href> with download attribute for native browser handling

Architecture KB updated with Export/Download section.
2026-05-29 21:32:41 -04:00
rin.kagamine 57b9f9d78e fix: Correct key/BPM colors and cleaner badge text
Key color fix:
- Previous offset put D# major at green (~150°) instead of orange (~30°)
- New offset: (position + 8) * 30° so position 5 (D#/Eb) = 390° mod 360 = 30° orange
- Position 8 (C) = 480° mod 360 = 120° green. Adjacent positions = adjacent hues.

BPM color fix:
- Previous wrap-around math went blue→purple→red, missing cyan/green/yellow/orange
- New: simple linear interpolation 240° → 0° (long way: 240→180→120→60→0)
- 60 BPM = blue (240°), 120 BPM = green (120°), 180 BPM = red (0°)

Badge text cleanup:
- BPM: just the number (e.g., '140'), no 'BPM' text. Tooltip shows full value.
- Key: shortened format — 'D#m' for minor, 'D#' for major. Unicode flats/sharps.
  Tooltip shows full 'D# major' / 'D# minor'.
- DetailPanel: same short format for consistency.

Auto-fetch analysis:
- App.tsx fetchClips() now fetches analysis for any ready clips missing BPM/key
- Runs in background after clips load, updates state silently
- No more needing to click into detail panel to see colors
2026-05-29 21:27:12 -04:00
rin.kagamine 2dcb297087 feat: Camelot wheel key colors + BPM gradient for all badges
New musicColors.ts utility:
- getKeyColor(): Maps musical key to Camelot wheel position → hue.
  12 positions × 30° = full color wheel. Major = brighter/more saturated.
  Minor = slightly darker/less saturated. Returns {bg, text, border}.
- getBPMColor(): Gradient from slow (blue, 240°) to fast (red, 0°).
  Default range: 60 BPM → 180 BPM. Configurable via BPMRange.
  Clamps outside range. Returns {bg, text, border}.

Gallery.tsx:
- Ribbons: BPM and key badges now use dynamic HSL colors
- Tiles: Same colored badges in tile footer
- Both use inline styles for bg/text/border

DetailPanel.tsx:
- BPM and key metadata items now show as colored badges
- Same color system as gallery badges

All badges: subtle translucent background, colored text, thin colored border.
Consistent visual language across ribbon, tile, and detail views.
2026-05-29 21:16:47 -04:00
rin.kagamine 1e21b7dcc8 feat: BPM and musical key detection for all clips
Backend:
- New analysis.py module using librosa for BPM + key detection
  - detect_bpm(): librosa.beat.beat_track() returns tempo
  - detect_key(): Krumhansl-Schmuckler key profiles on chroma features
  - analyze_clip(): combined call returning {bpm, key}
- New dependency: librosa==0.10.2 + soundfile==0.12.1
- Database migrations: bpm (REAL) and key (TEXT) columns added
- New set_analysis() function in database.py
- New endpoint: GET /api/clips/{id}/analysis
  - Returns cached analysis if available
  - Computes on-demand if missing, caches result
  - 400 if clip not ready, 404 if no audio file

Frontend:
- Clip interface extended with bpm?: number | null, key?: string | null
- Gallery: BPM and key badges shown in ribbon and tile views
  - BPM badge: lyria-accent background
  - Key badge: white/5 background
- DetailPanel: BPM and key shown in metadata grid when available
- App.tsx: handleSelectClip() fetches analysis on-demand when opening
  detail panel if not already cached

Docs:
- Architecture KB updated with Audio Analysis section
- Tasks KB: T-028 marked In Progress → will mark Done after commit
2026-05-29 21:09:04 -04:00
rin.kagamine 2d47f77820 tweak: Even faster marquee scroll
Min duration: 4s → 3s
Per-character multiplier: 0.2s → 0.15s

A 20-character title now scrolls in ~6s.
2026-05-29 20:55:25 -04:00
rin.kagamine 4f30245850 tweak: Faster marquee scroll speed
Reduced marquee animation duration:
- Min duration: 6s → 4s
- Per-character multiplier: 0.3s → 0.2s

A 20-character title now scrolls in ~8s instead of ~12s.
2026-05-29 20:53:40 -04:00
rin.kagamine 17ff0b2157 fix: Make marquee text visible on hover by removing overflow clip
The marquee animation was working but the text was still clipped by
overflow:hidden on both the parent container and the text div itself.
Added group-hover:overflow-visible to both containers so the full
text becomes visible when the marquee animation starts on hover.
2026-05-29 20:52:28 -04:00
rin.kagamine a14ec40872 fix: Register marquee animation in Tailwind config
The marquee scroll effect wasn't working because 'animate-marquee'
was defined as an inline CSS class, not a Tailwind utility. Tailwind's
JIT compiler strips unknown classes, so group-hover:animate-marquee
was being removed at build time.

Fix: Added 'marquee-scroll' keyframes and 'marquee' animation to
tailwind.config.js theme.extend. Now 'animate-marquee' is a proper
Tailwind utility that works with group-hover and responsive variants.

Also removed the inline <style> block from Player.tsx since the
animation is now handled by Tailwind.
2026-05-29 20:50:36 -04:00
rin.kagamine 5e18d77e29 feat: Marquee scrolling track name in player bar
Player.tsx:
- Track name now scrolls horizontally when hovered (marquee effect)
- Uses CSS @keyframes animation with duplicated text for seamless loop
- Animation duration scales with text length (min 6s, ~0.3s per char)
- Default state: truncate with ellipsis. Hover: whitespace-nowrap + animate
- Wrapped player in React fragment with inline <style> for keyframes
- Uses Tailwind group-hover to trigger animation only on hover
2026-05-29 20:46:05 -04:00
rin.kagamine 56bb484fd9 feat: Inline title editing in DetailPanel — no more JS prompt
DetailPanel.tsx:
- Replaced window.prompt with native inline editing
- Click title text or 'Rename' button to enter edit mode
- Input field with lyria-accent border, focus ring, and placeholder
- Auto-focus + select-all on enter edit mode (useEffect)
- Enter to save, Escape to cancel, blur to save
- Title text has hover:bg-white/5 for visual feedback
- Non-editing title is clickable (cursor-pointer, title tooltip)
- Uses useState for edit mode and draft text, useRef for input focus
2026-05-29 20:41:16 -04:00
rin.kagamine 16f2dd6788 fix: Add bottom padding to main content for player clearance
App.tsx main element now has pb-24 (96px bottom padding) so the
gallery content always has breathing room above the fixed player bar.
Prevents content from sitting flush against the player controls.
2026-05-29 20:39:12 -04:00
rin.kagamine d77d9100fb feat: Add album art thumbnail to player bar
Player.tsx track info section now shows a 32x32 album art thumbnail
left of the track name. Uses the clip's artwork endpoint with an
onError fallback to the treble clef placeholder (same SVG text
approach as gallery placeholders). Hidden on mobile (sm breakpoint).
Layout: flex row with 10px gap between thumbnail and title text.
2026-05-29 20:36:57 -04:00
rin.kagamine b0755b9063 feat: Add repeat button and fix skip-to-beginning icon
Player.tsx updates:
- Repeat button: ISO repeat indefinitely icon with glowing toggle state.
  When active: lyria-accent color, bg-lyria-accent/10, orange glow shadow.
  When inactive: muted gray, hover to white. Toggles audio.loop.
- Skip to beginning: Now uses ISO skip-backwards icon (filled, two bars
  pointing left) instead of circular arrow. Title: 'Skip to beginning'.
- Track end handler respects repeat state — if repeating, stays playing
  and loops; if not, pauses as before.
- New state: isRepeating (useState), toggleRepeat function sets both
  React state and audioRef.current.loop.
2026-05-29 20:35:00 -04:00
rin.kagamine 8218234eb9 feat: Full audio player with controls and scrubber
Player.tsx completely rewritten with professional controls:
- Play/Pause: large circular button with play/pause icons
- Stop: square icon, resets audio to 0 and pauses
- Restart: circular arrow, jumps to beginning and plays
- Time display: elapsed / total (M:SS format, tabular-nums)
- Scrubber: clickable progress bar with drag support
  - Visual progress fill (lyria-accent)
  - White scrubber handle on hover
  - Mouse drag anywhere on page while holding
- Volume: mute toggle + range slider (0-1, step 0.01)
  - Three volume icons: full, half, muted
  - Custom styled range input with white thumb
- Track info: title or prompt (hidden on mobile)
- Close button: X icon
- All buttons have title tooltips
- Uses useRef for audio element, useState for UI state
- useCallback for seek handler, useEffect for drag events
- Responsive: track info hidden on small screens
2026-05-29 20:28:20 -04:00
rin.kagamine 4fab947d6c fix: Clean up SettingsModal layout
- Both danger zone rows now use flex items-start gap-4 for consistent
  text+button alignment with proper breathing room
- Added shrink-0 to buttons so they don't compress
- Added whitespace-nowrap to Delete All button so text doesn't wrap
- Shortened confirm button text to just 'Confirm' (was 'Confirm Delete')
- Moved confirmation warning inline under the description text instead
  of as a separate block below, keeping the row compact
- Shortened description text: 'Remove N clips. Cannot be undone.'
2026-05-29 20:23:33 -04:00
rin.kagamine e74c89e1b0 feat: Settings modal with danger zone admin operations
Backend:
- POST /api/admin/clear-waveforms — deletes all cached waveform SVGs
- POST /api/admin/delete-all-clips — nuclear option: deletes all clips,
  MP3s, artwork, and waveforms. Returns deleted count.
- database.py: delete_all_clips() for bulk DB wipe
- Clip deletion cascade-removes waveform SVGs

Frontend:
- SettingsModal.tsx: center-stage modal with backdrop blur (bg-black/60
  backdrop-blur-sm). Danger zone with red-styled border and header.
- Two-step confirmation for Delete All (click once to arm, click again
  to confirm). Shows clip count dynamically.
- Gear icon trigger in header bar
- App.tsx: handleClearWaveforms + handleDeleteAll handlers

Docs:
- Architecture KB updated with album art, waveform, settings modal sections
- Tasks KB updated with T-021 (waveform) and TASK-001 (album art) marked done
- T-026 (settings modal) added to backlog
2026-05-29 20:21:05 -04:00
rin.kagamine 8f5c8a87d1 feat: Add themed FFT waveform visualization to detail panel
Backend:
- Install ffmpeg + numpy in Docker image
- New waveform.py: FFT-based SVG generator with 64 frequency bands
- GET /api/clips/{id}/waveform — generates on demand, caches to /data/waveforms/
- DELETE /api/clips/{id} now cascade-removes waveform SVG
- Warm amber-to-orange HSL gradient (25° to 35° hue)
- Mirrored bars: top full opacity, bottom 60% opacity

Frontend:
- DetailPanel shows waveform image beneath album art (80px height)
- Only shown for clips with status='ready'

Storage: /data/waveforms/ persisted via existing ./data volume mount
2026-05-29 20:11:01 -04:00
rin.kagamine 3c6a5d1a84 fix: Add onSelect click handler to ribbon view rows
Ribbon view was missing the onClick handler that opens the detail panel.
Tile view had it; ribbon view did not. Now both views open the slide-out
panel when clicking a clip row/card.
2026-05-29 19:57:07 -04:00
rin.kagamine 5868be5f93 fix: Move album art upload button out of ribbon thumbnail
Ribbon view artwork hover now shows only play button (larger, cleaner).
Upload art button moved to action row alongside rename/delete.
Order: rename → upload art → delete. All buttons 4x4, consistent spacing.
2026-05-29 19:55:06 -04:00
rin.kagamine 069170398f fix: Use proper Unicode treble clef (𝄞) for album art placeholders
Replaces broken SVG path with Unicode U+1D11E treble clef symbol
rendered via SVG text element. Applied to all three locations:
- Gallery ribbon: small treble clef (5x5)
- Gallery tile: medium treble clef (10x10)
- DetailPanel: large treble clef (14x14)
2026-05-29 19:41:49 -04:00
rin.kagamine c21a5f50e0 feat: Replace placeholder icons with treble clef SVG
Album art placeholders now show a treble clef (Rin's signature)
instead of generic icons. Applied to all three locations:
- Gallery ribbon: small treble clef (5x5)
- Gallery tile: medium treble clef (10x10)
- DetailPanel: large treble clef (14x14) with 'No album art' label
2026-05-29 18:58:34 -04:00
rin.kagamine 8ba7133269 feat: Add album artwork support
- Backend: artwork_filename column on clips table (auto-migration)
- Backend: PATCH /api/clips/{id}/artwork (upload image, 5MB limit)
- Backend: GET /api/clips/{id}/artwork (serve image with correct content-type)
- Backend: DELETE /api/clips/{id}/artwork (remove artwork file + null out DB)
- Backend: DELETE /api/clips/{id} now cascade-removes artwork file
- Frontend: Clip interface includes artwork_filename
- Frontend: Gallery ribbons show artwork thumbnail with hover overlay (play + upload)
- Frontend: Gallery tiles show artwork with hover overlay (play + upload)
- Frontend: DetailPanel shows full artwork with upload/change/remove controls
- Frontend: App.tsx handleUploadArtwork + handleRemoveArtwork handlers
- Storage: /data/artwork/ directory for image files
2026-05-29 18:27:28 -04:00
rin.kagamine 23672387e5 Fix Lyria API response parsing — audio in parts[1], not parts[0]
The Lyria API returns a candidates[].content.parts[] array where:
- parts[0] = text metadata (e.g. '<instrumental>')
- parts[1] = inlineData with the actual audio payload

Previous code only checked parts[0] for inlineData, which always failed
with 'No audio data in response'. Fixed by iterating all parts to find
the inlineData entry.

Successfully generated first real audio clip (lo-fi hip hop, 30s, 728KB MP3).

Also includes: .env.example, requirements updates, docker-compose fixes,
frontend vite config, logger setup.
2026-05-29 16:59:28 -04:00
rin.kagamine a2bc785b14 Initial scaffold: FastAPI backend + React frontend + Docker Compose
- Backend: FastAPI with SQLite, clip CRUD, health check
- Frontend: React + Vite + TypeScript + Tailwind, dark theme
- Components: Generator, Gallery, Player
- Docker: Compose setup for backend + frontend
- Architecture docs in Palace KB
2026-05-26 18:18:55 -04:00