Add comprehensive tempo conversion system supporting:
- BPM (beats per minute) as base unit
- Musical note durations: whole, half, quarter, eighth, sixteenth, thirty-second
- Dotted notes (1.5x duration): dotted-half, dotted-quarter, dotted-eighth, dotted-sixteenth
- Triplet notes (2/3 duration): quarter-triplet, eighth-triplet, sixteenth-triplet
- Time units: milliseconds, seconds, Hertz
Technical implementation:
- Created lib/tempo.ts with custom measure definition
- Extended lib/units.ts with tempo integration and reciprocal conversion logic
- Added tempo category color (orange #F97316) to globals.css
- Conversion formula: milliseconds per beat = 60000 / BPM
- Special handling for BPM ↔ time unit conversions using reciprocal relationship
The tempo converter integrates seamlessly with existing UX:
- Appears as "Tempo / BPM" category in category selector
- Supports all features: search, favorites, history, visual comparison, draggable bars
- Enables musicians to convert between BPM and note durations for tempo calculations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Standardized all layout sections to use consistent max-width:
- Changed from inconsistent container/max-w-6xl to unified max-w-7xl
- Header: container mx-auto → w-full max-w-7xl mx-auto
- Main: container mx-auto → w-full max-w-7xl mx-auto
- Footer: container mx-auto → w-full max-w-7xl mx-auto
- MainConverter: removed max-w-6xl (inherits from parent)
All sections now align perfectly with matching left/right edges
for a clean, professional layout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced wget with curl in HEALTHCHECK instruction:
- Install curl in nginx:alpine image (apk add --no-cache curl)
- Changed health check from wget to curl -f
- curl -f fails silently on HTTP errors (simpler than wget flags)
- More commonly available and standard for health checks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed TypeScript compilation errors preventing Docker build:
1. Added type declarations for convert-units library:
- Created types/convert-units.d.ts with proper interfaces
- Defined Unit and Converter interfaces
- Made value parameter optional in convert() function
- Methods like measures() accessible on Converter instance
2. Fixed CommandPalette type error:
- Added explicit type annotation to commands array
- Made color property optional (color?: string)
- Theme commands don't have color, measure commands do
Build now completes successfully:
- TypeScript compilation passes
- Static pages generate correctly
- Ready for Docker build
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated GitHub Actions workflow based on kit.pivoine.art template:
Permissions:
- Added id-token: write (required for attestations)
- Added attestations: write (enables build provenance)
Improvements:
- Added workflow_dispatch trigger for manual runs
- Updated docker/build-push-action from v5 to v6
- Added conditional login (skip on pull requests)
- Added artifact attestation step with actions/attest-build-provenance@v2
- Generates and pushes build provenance to registry
- Provides supply chain security and transparency
Attestation benefits:
- Verifiable build provenance
- SLSA (Supply chain Levels for Software Artifacts) compliance
- Cryptographically signed metadata about build process
- Helps users verify image authenticity
The workflow now matches modern Docker image publishing best practices
with full attestation support for enhanced security.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix: stopped switching selectedUnit during drag.
Problem:
- We were switching selectedUnit to the dragged unit
- This changed the conversion base
- All units maintained proportional relationships in log scale
- Bars didn't visually change size because ratios stayed the same
- On drag end, bars would snap back
Root cause: Switching source unit doesn't change bar proportions!
Example:
- Start: 1 meter → 3.28 feet (feet bar at 50% in log scale)
- Drag feet bar: switch to feet as source
- Now: 3.28 feet → 1 meter (meter bar at 50% in log scale)
- Proportions are IDENTICAL! No visual change!
Solution: Keep selectedUnit stable, only change inputValue
- Convert dragged value back to currently selected unit
- Update inputValue with converted amount
- selectedUnit stays unchanged
- All conversions scale proportionally from same base
- Bars resize because absolute values change!
How it works now:
- Dragging "feet" bar with "meters" selected
- Calculate new feet value from drag position
- Convert feet → meters: convertUnit(feetValue, 'ft', 'm')
- Update inputValue with meter equivalent
- All bars recalculate from meters (same base)
- Bars resize correctly because meter input changed!
Result: Bars now properly resize during drag AND stay at position on release!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where bars would snap back to original position on drag release.
Problem:
- On mouseup/touchend, we cleared draggedPercentage immediately
- Conversions hadn't updated yet (async state updates)
- Bar switched from draggedPercentage to old item.percentage
- Bar visually "snapped back" to original position
Solution: Delay clearing drag state until conversions update
- On drag end: only clear draggingUnit, keep draggedPercentage
- Added useEffect that watches conversions + draggingUnit
- When !draggingUnit && draggedPercentage !== null → drag just ended
- This means conversions have updated, safe to clear visual state
- Now clears draggedPercentage and baseConversionsRef
Flow:
1. User releases mouse/touch
2. handleMouseUp/handleTouchEnd: calls onValueChange(..., false)
3. Sets draggingUnit = null (stops active drag)
4. Keeps draggedPercentage (maintains visual position)
5. MainConverter updates inputValue and selectedUnit
6. Conversions recalculate
7. useEffect detects: !draggingUnit && draggedPercentage
8. Clears draggedPercentage → bar smoothly transitions to calculated position
Result: Bar stays at dragged position and smoothly settles to final value!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed the draggable bars to show immediate visual feedback!
Problem: Bars didn't resize during drag because:
- Switching source unit kept relative proportions the same (log scale)
- Bar width was using item.percentage which didn't update visually
- No direct visual feedback for the dragged bar
Solution: Use draggedPercentage state for immediate visual updates
- Added draggedPercentage state to track visual position during drag
- Save baseConversionsRef when drag starts (preserves original scale)
- Calculate new value from percentage using BASE scale (not updated scale)
- Use displayPercentage = isDragging ? draggedPercentage : item.percentage
- Bar width and percentage label both use displayPercentage
How it works now:
1. Mouse/touch down: save base conversions and current percentage
2. Mouse/touch move: calculate new percentage from drag delta
3. Set draggedPercentage state immediately (visual update!)
4. Calculate value from percentage using BASE scale
5. Call onValueChange to update conversions
6. Dragged bar shows draggedPercentage, others show calculated percentage
7. On release: clear draggedPercentage, bars settle to calculated positions
Result: The dragged bar now visually follows your cursor in real-time!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed two critical issues with draggable bars:
1. History now only saves on drag end (not during dragging):
- Added isDragging state to MainConverter
- onValueChange callback now accepts dragging boolean parameter
- History useEffect skips saving when isDragging is true
- On mouseup/touchend, call onValueChange with dragging=false to trigger save
- Prevents hundreds of history entries from a single drag
2. Throttled drag updates for better performance:
- Added lastUpdateTime ref to track update frequency
- Limited updates to 60fps (every 16ms)
- Prevents React from being overwhelmed with rapid state updates
- Smoother, more responsive dragging experience
How it works:
- During drag: onValueChange(value, unit, true) → isDragging=true → history skips
- On drag end: onValueChange(value, unit, false) → isDragging=false → history saves
- Drag move: throttled to max 60 updates per second
This should make bars update smoothly during drag and history
clean with only one entry per drag operation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed dragging behavior so all bars update when you drag:
Problem:
- Dragging a bar would change values but bars wouldn't visually update
- The bar would reset to original length on release
- Other bars wouldn't change at all
Root cause:
- We were converting the dragged value back to the selected unit
- This kept the source unit the same, so relative percentages didn't change
- The logarithmic scale maintained proportions, preventing visual updates
Solution:
- When dragging a bar, switch to that unit as the new source unit
- Set both inputValue and selectedUnit to the dragged unit's value/name
- This changes the conversion base, making all other bars recalculate
- Removed draggedPercentage state (not needed with this approach)
- All bars now use calculated percentages from conversions
How it works now:
- Drag the "feet" bar → becomes the source unit (selectedUnit = 'ft')
- All conversions recalculate from feet
- All bars update their percentages based on new conversion base
- No transitions during drag for instant visual feedback
- Smooth animation when drag ends
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The bar length now updates in real-time as you drag!
Problem: When dragging, all conversions recalculate proportionally, causing
relative percentages to stay the same (no visual change in bar length).
Solution: Track draggedPercentage state and display it directly on the bar
being dragged, bypassing the calculated percentage from conversions.
How it works:
- Added draggedPercentage state to track the current drag position
- Updated handleMouseMove/handleTouchMove to set draggedPercentage
- Use draggedPercentage for the dragging bar, calculated percentage for others
- Clear draggedPercentage on drag end (mouseup/touchend)
- Bar width and percentage label both use displayPercentage
Now when you drag a bar, you get instant visual feedback as the bar
resizes to follow your cursor, making the interaction feel responsive
and tactile.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed two issues with draggable bars:
1. Bar length now updates correctly during drag:
- Convert dragged unit value back to the selected unit
- Update input value with converted amount
- Keep selectedUnit unchanged (user still inputting in same unit)
- Disable transitions on ALL bars while ANY bar is dragging
- This makes bars resize smoothly and correctly as you drag
2. Removed duplicate value display from bars:
- Value is already shown above each bar
- Removed redundant value display from inside the colored segment
- Keeps only the percentage indicator inside bars
- Cleaner, less cluttered visual design
The dragging now works intuitively: dragging any bar adjusts the input
value (in the currently selected unit) to match the dragged bar's value.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where dragged bars weren't updating visually because a single
barRef was shared across all bars. Now each bar passes its element reference
to the drag handlers, allowing proper width calculations during drag.
Changes:
- Renamed barRef to activeBarRef for clarity
- Updated handleMouseDown/handleTouchStart to accept bar element parameter
- Pass e.currentTarget as bar element in onMouseDown/onTouchStart handlers
- Clear activeBarRef on drag end (mouseup/touchend)
This fixes the visual feedback - bars now correctly resize as you drag them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement innovative drag-to-adjust interaction in visual comparison view:
Visual feedback:
- Cursor changes to grab/grabbing when draggable
- Active bar scales up and shows ring focus indicator
- Hover overlay displays "Drag to adjust" hint
- Smooth transitions when not dragging, instant updates while dragging
Drag mechanics:
- Mouse drag support for desktop
- Touch drag support for mobile devices
- Logarithmic scale conversion preserves intuitive feel
- Clamped percentage range (3-100%) prevents invalid values
- Dragging updates input value and selected unit in real-time
Technical implementation:
- Added onValueChange callback to VisualComparison component
- Reverse logarithmic calculation converts drag position to value
- Global event listeners for smooth drag-outside-element tracking
- Prevents scrolling during touch drag on mobile
- MainConverter integrates drag callback to update state
This creates a highly tactile, visual way to adjust conversion values
by directly manipulating the bar chart representation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update all category-related UI components to use getCategoryColorHex() function
instead of CSS variables for consistent color application across the app.
Changes:
- MainConverter: category buttons now use hex colors for background/border
- MainConverter: quick result display uses hex color for text and border
- MainConverter: result cards use hex color for left border
- CommandPalette: measure commands use hex colors for color indicators
- SearchUnits: category color dots use hex colors
This ensures all category colors are consistently applied using the same
hex color values defined in the OKLCH color palette.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed the colored bar segments not showing:
🎨 Direct Color Implementation:
- Added getCategoryColorHex() function to return actual hex values
- Changed from CSS variables to direct backgroundColor
- No more var(--color-*) that wasn't resolving
- Direct hex colors like #3B82F6 for length, #10B981 for mass, etc.
✨ Visual Improvements:
- Taller bars (h-8, 32px) for better visibility
- Drop shadow on percentage labels for readability
- White text on bars >30% filled
- Foreground color text on smaller bars
- pointer-events-none on overlay to prevent interaction issues
🔧 Updated Components:
- MainConverter: Import and use getCategoryColorHex()
- VisualComparison: Accept hex color string directly
- lib/units: Added getCategoryColorHex() with all 23 colors
The bars will now definitely show with vibrant colors!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Completely rebuilt the bar rendering logic for guaranteed visibility:
🔧 Improved Logarithmic Calculation:
- Better handling of edge cases (zero, infinity)
- Minimum 3% bar width for all visible values
- Maximum 100% cap to prevent overflow
- 6 orders of magnitude default range
- Proper log scale normalization
🎨 Simplified Bar Styling:
- Removed complex overlay positioning
- Larger bars (h-6 for better visibility)
- Solid background colors using CSS variables
- Simple border for definition
- White percentage text on colored bars
- Text only shows when bar is >15% wide
- Drop shadow for text readability
✨ Robust Value Handling:
- Handles zero values (2% minimal bar)
- Handles infinite/NaN values gracefully
- Uses Math.abs for negative values
- Proper min/max value detection
- Filters out invalid values before calculation
The bars will now ALWAYS be visible with proper widths,
and the logarithmic scale ensures good visual distribution
across different orders of magnitude.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced the visual comparison chart for better readability:
📊 Logarithmic Scale:
- Use log10 scale instead of linear for better visualization
- Handles units with vastly different magnitudes (mm vs km)
- Minimum bar width of 5% for visibility
- Normalizes across the full range (minLog to maxLog)
- Prevents tiny bars that are hard to see
✨ Visual Improvements:
- Taller bars (h-3 instead of h-2) for better visibility
- Larger, bolder value display (text-lg font-bold)
- Better spacing (space-y-1.5)
- Percentage indicator on each bar
- White text on bars >50% filled
- Shadow on bars for depth
- Improved typography hierarchy
- Better gap spacing between label and value
🎨 Layout Enhancements:
- Unit name on left, value on right
- Value with unit abbreviation in muted color
- Flex layout with proper wrapping
- Tabular numbers for alignment
- Relative positioning for percentage labels
This makes the chart view much more useful for comparing units
with different orders of magnitude (e.g., nanometers to kilometers).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive UX enhancements with innovative features:
🔍 Fuzzy Search Component (SearchUnits.tsx):
- Powered by Fuse.js for intelligent fuzzy matching
- Searches across unit abbreviations, names, and categories
- Real-time dropdown with results
- Keyboard shortcut: Press "/" to focus search
- Press Escape to close
- Click outside to dismiss
- Shows measure category with color dot
- Top 10 results displayed
- Smart weighting: abbr (2x), singular/plural (1.5x), measure (1x)
💾 Conversion History (ConversionHistory.tsx):
- LocalStorage persistence (max 50 entries)
- Auto-saves conversions as user types
- Collapsible history panel
- Click to restore previous conversion
- Clear all with confirmation
- Shows relative time (just now, 5m ago, etc.)
- Live updates across tabs with storage events
- Custom event dispatch for same-window updates
🌓 Dark Mode Support:
- ThemeProvider with light/dark/system modes
- Persistent theme preference in localStorage
- Smooth theme transitions
- ThemeToggle component with animated sun/moon icons
- Gradient text adapts to theme
- System preference detection
⭐ Favorites & Copy Features:
- Star button to favorite units (localStorage)
- Copy to clipboard with visual feedback
- Hover to reveal action buttons
- Check icon confirmation for 2 seconds
- Yellow star fill for favorited units
⌨️ Keyboard Shortcuts:
- "/" - Focus search input
- "Escape" - Close search, blur inputs
- More shortcuts ready to add (Tab, Ctrl+K, etc.)
📦 LocalStorage Utilities (lib/storage.ts):
- saveToHistory() - Add conversion record
- getHistory() - Retrieve history
- clearHistory() - Clear all history
- getFavorites() / addToFavorites() / removeFromFavorites()
- toggleFavorite() - Toggle favorite status
- Type-safe ConversionRecord interface
- Automatic error handling
🎨 Enhanced MainConverter:
- Integrated search at top
- Conversion history at bottom
- Copy & favorite buttons on each result card
- Hover effects with opacity transitions
- Auto-save to history on conversion
- Click history item to restore conversion
- Visual feedback for all interactions
📱 Updated Layout & Page:
- ThemeProvider wraps entire app
- suppressHydrationWarning for SSR
- Top navigation bar with theme toggle
- Keyboard hint for search
- Dark mode gradient text variants
Dependencies Added:
- fuse.js 7.1.0 - Fuzzy search engine
- lucide-react 0.553.0 - Icon library (Search, Copy, Star, Check, etc.)
Features Now Working:
✅ Intelligent fuzzy search across 187 units
✅ Conversion history with persistence
✅ Dark mode with system detection
✅ Copy any result to clipboard
✅ Favorite units for quick access
✅ Keyboard shortcuts (/, Esc)
✅ Smooth animations and transitions
✅ Mobile-responsive design
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete Phase 2 implementation with working unit converter:
Core Conversion Engine (lib/units.ts):
- Type-safe wrapper for convert-units library
- Support for all 23 measures with TypeScript types
- getAllMeasures() - Get all available categories
- getUnitsForMeasure() - Get units for specific measure
- getUnitInfo() - Get detailed unit information
- convertUnit() - Convert between two units
- convertToAll() - Convert to all compatible units
- getCategoryColor() - Get Tailwind color class for measure
- formatMeasureName() - Format measure names for display
- searchUnits() - Fuzzy search across all units
Utility Functions (lib/utils.ts):
- cn() - Merge Tailwind classes with clsx and tailwind-merge
- formatNumber() - Smart number formatting with scientific notation
- debounce() - Debounce helper for inputs
- parseNumberInput() - Parse user input to number
- getRelativeTime() - Format timestamps
UI Components:
- Input - Styled input with focus states
- Button - 6 variants (default, destructive, outline, secondary, ghost, link)
- Card - Card container with header, title, description, content, footer
Main Converter Component (components/converter/MainConverter.tsx):
- Real-time conversion as user types
- Category selection with 23 color-coded buttons
- Input field with unit selector
- Grid display of all conversions in selected measure
- Color-coded result cards with category colors
- Responsive layout (1/2/3 column grid)
Homepage Updates:
- Integrated MainConverter component
- Clean header with gradient text
- Uses design system colors
Dependencies Added:
- clsx - Class name utilities
- tailwind-merge - Merge Tailwind classes intelligently
Features Working:
✓ Select from 23 measurement categories
✓ Real-time conversion to all compatible units
✓ Color-coded categories
✓ Formatted number display
✓ Responsive design
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced globals.css based on pastel-ui best practices:
Features added:
- @source directives for automatic component discovery
- @custom-variant for dark mode support
- Complete design system with :root and .dark themes
- OKLCH color space for better color precision
- shadcn/ui compatible color tokens (background, foreground, card, etc.)
- @theme inline block with Tailwind v4 color definitions
- 23 category colors converted to OKLCH format
- Custom animations system with 8 predefined animations
- @layer base with global styles
- Smooth transitions and custom scrollbar styling
- Screen reader utility class (.sr-only)
- Animation keyframes for all custom animations
Design system includes:
- Light/dark mode support with OKLCH colors
- Semantic color tokens (primary, secondary, muted, accent, destructive)
- Border radius variable (--radius)
- Smooth 200ms transitions
- Category-specific colors for all 23 unit types in OKLCH
- Custom animations: fadeIn, slideUp/Down, slideInRight/Left, scaleIn, bounceGentle, shimmer
Accessibility features:
- Proper outline ring on focus
- Theme transition prevention during theme switch
- Custom scrollbar with hover states
- Screen reader only utility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Set up complete Next.js 16 development environment:
- Next.js 16.0.1 with App Router and Turbopack
- React 19.2.0 with TypeScript 5.9
- Tailwind CSS 4.1.17 with custom category colors
- ESLint 9 with Next.js config
- Static site generation enabled (output: 'export')
Project structure:
- app/ directory with layout, page, and globals.css
- Tailwind config with 23 category-specific colors
- TypeScript config with strict mode and path aliases
- Complete .gitignore for Next.js projects
Dependencies installed:
- convert-units 2.3.4 for unit conversions
- All React 19 and Next.js 16 dependencies
- Development tooling (TypeScript, ESLint, Tailwind)
Ready for Phase 2: Core conversion engine implementation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update IMPLEMENTATION_PLAN.md to reflect accurate count from convert-units:
- Correct from estimated 13-27 categories to actual 23 categories
- Add complete list of all 23 measures with individual unit counts
- Total of 187 individual units across all categories
- Expand color palette from 8 to 23 distinct category colors
- Update visual comparison section with all 23 color assignments
New categories include:
- Angle, Apparent Power, Current, Illuminance, Pace
- Parts Per, Reactive Energy, Reactive Power, Voltage
- Volume Flow Rate (37 units - largest category)
Discovered through direct library inspection of convert-units package.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add detailed implementation plan covering:
- Innovative UX features (real-time conversion, visual comparison, power user features)
- Technical architecture with Next.js 16, TypeScript, and Tailwind CSS 4
- Component structure and state management design
- 8-phase implementation roadmap
- Design system with category-based color palette
- Support for 13+ unit categories from convert-units library
- Performance targets and accessibility requirements
- Mobile-first responsive design strategy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>