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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user