feat: create professional, polished UI with perfect spacing

- Add generous padding and spacing throughout (p-10, p-12)
- Constrain content width with max-w-4xl and max-w-5xl
- Increase font sizes for better hierarchy (text-3xl, text-lg)
- Add gradient backgrounds to cards and elements
- Implement smooth transitions and hover states
- Create professional progress stepper with labels
- Add shadow effects with color-matched glows
- Increase button padding and font sizes
- Add gradient borders on hover
- Implement proper visual breathing room
- Create polished, premium aesthetic
- Add smooth cubic-bezier transitions
- Improve form input styling with focus rings
- Create professional card hover effects
This commit is contained in:
2025-12-09 13:29:48 +00:00
parent 2f200aa61b
commit 4a53c897c5
6 changed files with 303 additions and 278 deletions

View File

@@ -159,116 +159,118 @@ export default function VoiceAssignmentSection({
const isGenerateDisabled = !allRolesAssigned || isGenerating;
return (
<div className="card-modern p-8 space-y-8">
<div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Assign Voices</h2>
<p className="text-gray-600">Choose a voice for each character in your story.</p>
</div>
<div className="max-w-5xl mx-auto">
<div className="card-modern p-10 sm:p-12 space-y-10">
<div>
<h2 className="text-3xl font-bold text-gray-900 mb-3">Assign Voices</h2>
<p className="text-lg text-gray-600">Choose a voice for each character in your story.</p>
</div>
{/* Voice Assignments */}
<div className="space-y-4">
{roles.map((role, index) => (
<div key={role.id} className="flex flex-col sm:flex-row sm:items-center gap-3 p-4 bg-gray-50 rounded-xl border border-gray-200">
<div className="flex items-center gap-3 sm:w-48">
<div className="flex items-center justify-center w-8 h-8 bg-indigo-100 text-indigo-700 rounded-lg font-semibold text-sm">
{index + 1}
{/* Voice Assignments */}
<div className="space-y-5">
{roles.map((role, index) => (
<div key={role.id} className="flex flex-col sm:flex-row sm:items-center gap-4 p-6 bg-gradient-to-br from-gray-50 to-white rounded-2xl border-2 border-gray-200 hover:border-indigo-300 transition-all duration-200">
<div className="flex items-center gap-4 sm:w-56">
<div className="flex items-center justify-center w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 text-white rounded-xl font-bold text-sm shadow-lg shadow-indigo-500/30">
{index + 1}
</div>
<label className="font-bold text-gray-900 text-lg">
{role.label}
</label>
</div>
<label className="font-semibold text-gray-900">
{role.label}
</label>
<select
value={voiceAssignments.get(role.id) || ''}
onChange={(e) => handleVoiceSelect(role.id, e.target.value)}
className="flex-1 p-4 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 focus:outline-none bg-white text-base font-medium transition-all duration-200"
>
<option value="">Select a voice...</option>
{KOKORO_VOICES.map((voice) => (
<option key={voice.id} value={voice.id}>
{voice.label}
</option>
))}
</select>
</div>
))}
</div>
{/* Settings Grid */}
<div className="grid sm:grid-cols-2 gap-8 pt-4">
{/* Language */}
<div>
<label className="block text-base font-bold text-gray-900 mb-3">
Language
</label>
<select
value={voiceAssignments.get(role.id) || ''}
onChange={(e) => handleVoiceSelect(role.id, e.target.value)}
className="flex-1 p-3 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 focus:outline-none bg-white"
value={language}
onChange={handleLanguageSelect}
className="w-full p-4 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 focus:outline-none bg-white text-base font-medium transition-all duration-200"
>
<option value="">Select a voice...</option>
{KOKORO_VOICES.map((voice) => (
<option key={voice.id} value={voice.id}>
{voice.label}
{LANGUAGES.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.label}
</option>
))}
</select>
</div>
))}
</div>
{/* Settings Grid */}
<div className="grid sm:grid-cols-2 gap-6">
{/* Language */}
<div>
<label className="block text-sm font-semibold text-gray-900 mb-2">
Language
</label>
<select
value={language}
onChange={handleLanguageSelect}
className="w-full p-3 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 focus:outline-none bg-white"
>
{LANGUAGES.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.label}
</option>
))}
</select>
</div>
{/* Speed */}
<div>
<label className="block text-sm font-semibold text-gray-900 mb-2">
Playback Speed: {speed.toFixed(1)}x
</label>
<input
type="range"
min="0.5"
max="1.5"
step="0.1"
value={speed}
onChange={handleSpeedChange}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>0.5x</span>
<span>1.5x</span>
{/* Speed */}
<div>
<label className="block text-base font-bold text-gray-900 mb-3">
Playback Speed: <span className="text-indigo-600">{speed.toFixed(1)}x</span>
</label>
<input
type="range"
min="0.5"
max="1.5"
step="0.1"
value={speed}
onChange={handleSpeedChange}
className="w-full h-3"
/>
<div className="flex justify-between text-sm text-gray-500 mt-2 font-medium">
<span>0.5x</span>
<span>1.5x</span>
</div>
</div>
</div>
</div>
{/* Validation Message */}
{!allRolesAssigned && !isGenerating && (
<div className="p-4 bg-yellow-50 border border-yellow-200 text-yellow-800 rounded-xl">
<p className="text-sm font-medium">Please assign voices to all roles before continuing.</p>
</div>
)}
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-3 justify-end">
{onSkipReview && (
<button
onClick={onSkipReview}
disabled={isGenerateDisabled}
className="px-6 py-3 border-2 border-gray-300 text-gray-700 font-semibold rounded-xl hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
>
Skip Review & Generate
</button>
{/* Validation Message */}
{!allRolesAssigned && !isGenerating && (
<div className="p-5 bg-yellow-50 border-2 border-yellow-200 text-yellow-900 rounded-2xl">
<p className="font-semibold">⚠️ Please assign voices to all roles before continuing.</p>
</div>
)}
<button
onClick={onGenerate}
disabled={isGenerateDisabled}
className="btn-primary"
>
{isGenerating ? (
<span className="flex items-center gap-2">
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent"></div>
Generating...
</span>
) : (
<span className="flex items-center gap-2">
Continue
<span>→</span>
</span>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-4 justify-end pt-4">
{onSkipReview && (
<button
onClick={onSkipReview}
disabled={isGenerateDisabled}
className="px-8 py-4 border-2 border-gray-300 text-gray-700 font-semibold rounded-xl hover:bg-gray-50 hover:border-gray-400 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
>
Skip Review & Generate
</button>
)}
</button>
<button
onClick={onGenerate}
disabled={isGenerateDisabled}
className="btn-primary"
>
{isGenerating ? (
<span className="flex items-center gap-3">
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent"></div>
Generating...
</span>
) : (
<span className="flex items-center gap-3">
Continue
<span className="text-lg">→</span>
</span>
)}
</button>
</div>
</div>
</div>
);