π¨ Enhanced Speaker Color System
Problem Statement
Original Issue
With only 10 predefined speaker colors using modulo operation (speaker_id % 10), the system had several aesthetic problems:
Color Collisions: When having more than 10 speakers, speakers would share the same color
- Speaker 0 and Speaker 10 β Same red
- Speaker 1 and Speaker 11 β Same blue
Transcript Not Colored: Speaker tags in the transcript had a generic blue color, not matching their timeline segment color
Poor Visual Distinction: Users couldn't easily track which speaker was which
User Impact
- π Confusing when multiple speakers have the same color
- π Can't distinguish speakers at a glance
- π Timeline doesn't match transcript visually
- π₯ Difficult to follow conversations with many speakers
Solution Implemented
1. Expanded Color Palette (30 Colors) β
Increased from 10 to 30 distinct, carefully chosen colors:
const SPEAKER_COLORS = [
'#ef4444', // Red
'#3b82f6', // Blue
'#10b981', // Green
'#f59e0b', // Amber
'#8b5cf6', // Purple
'#ec4899', // Pink
'#14b8a6', // Teal
'#f97316', // Orange
'#06b6d4', // Cyan
'#84cc16', // Lime
'#dc2626', // Dark Red
'#2563eb', // Dark Blue
'#059669', // Dark Green
'#d97706', // Dark Amber
'#7c3aed', // Dark Purple
'#db2777', // Dark Pink
'#0d9488', // Dark Teal
'#ea580c', // Dark Orange
'#0891b2', // Dark Cyan
'#65a30d', // Dark Lime
'#f87171', // Light Red
'#60a5fa', // Light Blue
'#34d399', // Light Green
'#fbbf24', // Light Amber
'#a78bfa', // Light Purple
'#f472b6', // Light Pink
'#2dd4bf', // Light Teal
'#fb923c', // Light Orange
'#22d3ee', // Light Cyan
'#a3e635', // Light Lime
];
Color Organization:
- Base 10 colors (vibrant, distinct)
- Dark variants (10 darker shades)
- Light variants (10 lighter shades)
This provides excellent visual distinction for up to 30 speakers!
2. Centralized Color Helper Function β
function getSpeakerColor(speakerId) {
if (typeof speakerId !== 'number') return null;
return SPEAKER_COLORS[speakerId % SPEAKER_COLORS.length];
}
Benefits:
- Single source of truth for speaker colors
- Consistent across timeline and transcript
- Easy to modify/extend palette
- Null-safe handling
3. Dynamic Color Application β
A. Timeline Segments
Before (Static CSS classes):
// Old approach - Limited to 10 colors
segment.classList.add(`speaker-${utt.speaker % 10}`);
After (Dynamic inline styles):
// New approach - 30 colors, dynamic
const speakerColor = getSpeakerColor(utt.speaker);
if (speakerColor) {
segment.style.backgroundColor = speakerColor + '66'; // 40% opacity
}
Opacity: 66 in hex = 40% transparency, allowing timeline background to show through
B. Speaker Tags in Transcript
Before (Generic blue):
.speaker-tag {
background: rgba(129, 140, 248, 0.2); /* Always blue */
}
After (Dynamic per-speaker):
if (speakerColor) {
speakerTag.style.backgroundColor = speakerColor + '40'; // 25% opacity
speakerTag.style.borderColor = speakerColor;
speakerTag.style.color = '#ffffff';
}
Result: Each speaker tag now shows its unique color!
4. Enhanced Visual Design β
Improved speaker tag styling:
.speaker-tag {
font-size: 0.75rem;
padding: 0.1rem 0.5rem;
border-radius: 999px;
border: 1px solid; /* Border uses speaker color */
font-weight: 500;
letter-spacing: 0.01em;
}
.editable-speaker:hover {
filter: brightness(1.15); /* Brighten on hover */
transform: scale(1.05); /* Slight growth */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
Effects:
- Colored border matching background
- Better readability with white text
- Smooth hover effects
- Professional appearance
Technical Implementation
Color Palette Design Principles
Distinct Base Colors (0-9):
- Primary colors from different hue families
- Maximum perceptual difference
- Good on dark backgrounds
Dark Variants (10-19):
- Darker versions of base colors
- Still distinguishable from base
- Good contrast with light elements
Light Variants (20-29):
- Lighter versions of base colors
- Complement dark variants
- Maintain visibility
Hexadecimal Opacity Values
Opacity | Hex | Decimal
--------|-----|--------
100% | FF | 255
80% | CC | 204
60% | 99 | 153
40% | 66 | 102 β Timeline segments
25% | 40 | 64 β Speaker tags
20% | 33 | 51
Why different opacities?
- Timeline: 40% allows background texture to show
- Tags: 25% ensures text readability against transcript background
Dynamic Style Application
// Timeline segment
segment.style.backgroundColor = speakerColor + '66';
// Speaker tag
speakerTag.style.backgroundColor = speakerColor + '40';
speakerTag.style.borderColor = speakerColor;
speakerTag.style.color = '#ffffff';
Advantages of inline styles:
- β No limit to number of speakers
- β No need to generate CSS classes dynamically
- β Easier to maintain
- β More flexible for future changes
Before vs After Comparison
Visual Example (10+ Speakers)
Before:
Timeline:
Speaker 0: π₯ Red
Speaker 1: π¦ Blue
Speaker 2: π© Green
...
Speaker 10: π₯ Red β Same as Speaker 0! β
Speaker 11: π¦ Blue β Same as Speaker 1! β
Transcript:
Speaker 0: π΅ Generic blue tag
Speaker 1: π΅ Generic blue tag β All same! β
Speaker 10: π΅ Generic blue tag
After:
Timeline:
Speaker 0: π₯ Red
Speaker 1: π¦ Blue
Speaker 2: π© Green
...
Speaker 10: π΄ Dark Red β Unique! β
Speaker 11: π΅ Dark Blue β Unique! β
Transcript:
Speaker 0: π₯ Red tag β Matches timeline! β
Speaker 1: π¦ Blue tag β Matches timeline! β
Speaker 10: π΄ Dark Red tag β Matches timeline! β
Color Accessibility
Contrast Ratios
All colors tested against:
- Dark background (#0f172a): β Good contrast
- White text: β Readable with opacity
Distinguishability
Adjacent color pairs tested:
- Red vs Orange: β Clear difference
- Blue vs Cyan: β Clear difference
- Green vs Lime: β Clear difference
Dark vs Light variants:
- Each variant visibly different from base
- Easy to distinguish even with similar hues
Edge Cases Handled
1. More Than 30 Speakers
speaker_id % SPEAKER_COLORS.length
- Wraps around after 30
- Still provides good distribution
- Rare in practice (most podcasts have < 10 speakers)
2. No Diarization
if (typeof utt.speaker === 'number') {
// Apply color
} else {
segment.style.backgroundColor = 'rgba(148, 163, 184, 0.5)';
}
- Falls back to neutral gray
- No errors or broken display
3. Speaker Name Editing
speakerTag.style.backgroundColor = speakerColor + '40';
- Color preserved during editing
- Reapplied after save
- Visual continuity maintained
Performance Considerations
Color Computation
SPEAKER_COLORS[speakerId % SPEAKER_COLORS.length]
- O(1) lookup time
- Array access is extremely fast
- No performance impact
Style Application
element.style.backgroundColor = color;
- Modern browsers optimize inline styles
- No reflow/repaint issues
- Faster than class manipulation for many elements
Memory Usage
- 30 color strings: ~500 bytes
- Negligible memory footprint
- No dynamic CSS generation needed
Browser Compatibility
| Feature | Support |
|---|---|
| Hex color with transparency | β All modern browsers |
| Inline styles | β All browsers |
| String concatenation | β All browsers |
| Modulo operator | β All browsers |
filter: brightness() |
β All modern browsers |
transform: scale() |
β All modern browsers |
Testing Checklist
Visual Tests
- β 10 speakers: All have distinct colors
- β 20 speakers: All have distinct colors
- β 30 speakers: All have distinct colors
- β 35 speakers: Colors wrap correctly
- β Speaker tags match timeline segments
- β Hover effects work on tags
- β Text readable on all colors
- β No diarization: Gray fallback works
Functional Tests
- β getSpeakerColor() returns correct color
- β Color applied to timeline segments
- β Color applied to speaker tags
- β Edit speaker name preserves color
- β Active segment highlighting works
- β Click segment to seek works
Edge Cases
- β Speaker ID 0: Works (Red)
- β Negative speaker ID: Handled gracefully
- β Non-number speaker: Returns null, uses fallback
- β Very large speaker ID: Wraps correctly
Future Enhancements (Optional)
1. User-Customizable Colors
Allow users to pick their own colors for each speaker:
state.speakerCustomColors = {
0: '#your-color',
1: '#another-color',
};
2. Color Themes
Predefined color schemes:
- Pastel theme: Softer colors
- Vibrant theme: Brighter colors
- Monochrome theme: Grayscale variants
- High contrast theme: Maximum distinction
3. Automatic Color Assignment
Smart algorithm to maximize distinction:
function assignOptimalColors(speakerCount) {
// Distribute colors evenly across hue spectrum
// Skip similar adjacent colors
// Prioritize high-contrast pairs
}
4. Color Legend
Display a legend showing all speakers and their colors:
<div class="speaker-legend">
<div class="legend-item">
<span class="color-dot" style="background: #ef4444"></span>
<span>Speaker 1</span>
</div>
<!-- ... -->
</div>
Code Changes Summary
Files Modified
frontend/app.js
- Added:
SPEAKER_COLORSarray (30 colors) - Added:
getSpeakerColor()helper function - Modified:
createUtteranceElement()to apply speaker colors to tags - Modified:
renderTimelineSegments()to use dynamic colors - Lines changed: ~40
- Added:
frontend/styles.css
- Modified:
.speaker-tagstyling - Enhanced:
.editable-speakerhover effects - Removed: Static
.speaker-0through.speaker-9classes - Lines changed: ~20
- Modified:
Backward Compatibility
β Fully backward compatible:
- Old recordings without diarization: Work perfectly
- Old code using speaker IDs: Works seamlessly
- No breaking changes to API or state structure
Results
Visual Impact
- π¨ 30 distinct colors instead of 10
- π― 100% color match between timeline and transcript
- β¨ Professional appearance with smooth transitions
- ποΈ Better visual tracking of speakers
Technical Benefits
- π Centralized color management
- π Scalable to 30+ speakers
- π No performance impact
- π‘οΈ Null-safe and robust
User Experience
- π Easier to follow conversations
- π Clear speaker identification
- π Consistent visual language
- π― Professional podcast experience
Conclusion
The enhanced speaker color system provides a much better visual experience for podcast transcription with multiple speakers. By expanding the color palette to 30 colors and applying them consistently across both the timeline and transcript, users can now easily track and distinguish different speakers at a glance.
The implementation is clean, performant, and maintainable, with room for future enhancements like user-customizable colors and color themes.
Bug fixed! Visual distinction achieved! π¨β¨