show progress
This commit is contained in:
+222
-31
@@ -41,12 +41,12 @@
|
||||
disabled="@isProcessing" />
|
||||
<button @onclick="DownloadMp3"
|
||||
@onmousedown="PreventDefault"
|
||||
class="download-btn"
|
||||
class="download-btn @(isProcessing ? "processing" : "")"
|
||||
disabled="@(isProcessing)">
|
||||
<span class="btn-icon">🎵</span>
|
||||
@if (isProcessing)
|
||||
{
|
||||
<span>Processing...</span>
|
||||
<span>Processing... (@progressPercentage%)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -55,6 +55,65 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@* Enhanced Progress Section - Show immediately when processing starts *@
|
||||
@if (isProcessing)
|
||||
{
|
||||
<div class="enhanced-progress-section">
|
||||
<div class="progress-header">
|
||||
<h3 class="progress-title">
|
||||
<span class="progress-icon @GetProgressIconClass()">@GetProgressIcon()</span>
|
||||
@GetProgressStageTitle()
|
||||
</h3>
|
||||
<div class="progress-stats">
|
||||
<span class="progress-percentage">@progressPercentage%</span>
|
||||
<span class="progress-elapsed">⏱️ @GetElapsedTime()</span>
|
||||
@if (estimatedTimeRemaining.HasValue && estimatedTimeRemaining.Value > TimeSpan.Zero)
|
||||
{
|
||||
<span class="progress-eta">ETA: @GetEstimatedTimeRemaining()</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-background">
|
||||
<div class="progress-bar-fill" style="width: @(progressPercentage)%">
|
||||
<div class="progress-bar-shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-stages">
|
||||
@foreach (var stage in progressStages)
|
||||
{
|
||||
<div class="progress-stage @GetStageClass(stage)">
|
||||
<div class="stage-icon">@stage.Icon</div>
|
||||
<div class="stage-label">@stage.Label</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-details">
|
||||
<div class="progress-message">
|
||||
<span class="message-text">@progressMessage</span>
|
||||
@if (progressPercentage > 0 && progressPercentage < 100)
|
||||
{
|
||||
<div class="progress-dots">
|
||||
<span class="dot">.</span>
|
||||
<span class="dot">.</span>
|
||||
<span class="dot">.</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (currentStageDetails != null)
|
||||
{
|
||||
<div class="stage-details">
|
||||
<small>@currentStageDetails</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Video Information Section - Show as soon as we have details *@
|
||||
@if (currentVideoDetails != null)
|
||||
{
|
||||
@@ -69,6 +128,7 @@
|
||||
{
|
||||
<div class="processing-overlay">
|
||||
<div class="processing-spinner">🎵</div>
|
||||
<div class="processing-text">@progressPercentage%</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -80,29 +140,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (isProcessing)
|
||||
{
|
||||
<div class="progress-container @(currentVideoDetails != null ? "with-video-details" : "")">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: @(progressPercentage)%"></div>
|
||||
</div>
|
||||
<div class="progress-text">
|
||||
@if (currentVideoDetails != null && progressPercentage > 15)
|
||||
{
|
||||
<span class="progress-highlight">@progressMessage</span>
|
||||
<span class="progress-percentage">(@progressPercentage%)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@progressMessage (@progressPercentage%)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(outputMessage) && !isProcessing)
|
||||
{
|
||||
<div class="output-message">
|
||||
<div class="output-message success">
|
||||
<div class="success-icon">✅</div>
|
||||
<p>@outputMessage</p>
|
||||
</div>
|
||||
}
|
||||
@@ -111,7 +152,8 @@
|
||||
@if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing)
|
||||
{
|
||||
<div class="error-message">
|
||||
<p>❌ @apiErrorMessage</p>
|
||||
<div class="error-icon">❌</div>
|
||||
<p>@apiErrorMessage</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -132,6 +174,7 @@
|
||||
@if (isLoadingZip)
|
||||
{
|
||||
<span>Preparing ZIP...</span>
|
||||
<div class="loading-spinner">⏳</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -198,10 +241,17 @@
|
||||
private bool isLoadingZip = false;
|
||||
private int progressPercentage = 0;
|
||||
private string progressMessage = "";
|
||||
private string? currentStageDetails = null;
|
||||
private List<DownloadItem> downloadLinks = new();
|
||||
private YouTubeVideoDetails? currentVideoDetails = null;
|
||||
private string currentSessionId = "";
|
||||
private SessionZipInfo? sessionZipInfo = null;
|
||||
|
||||
// Enhanced progress tracking
|
||||
private DateTime processingStartTime;
|
||||
private TimeSpan? estimatedTimeRemaining;
|
||||
private List<ProgressStage> progressStages = new();
|
||||
private string currentProgressStage = "initializing";
|
||||
|
||||
public class DownloadItem
|
||||
{
|
||||
@@ -211,8 +261,21 @@
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class ProgressStage
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Label { get; set; } = "";
|
||||
public string Icon { get; set; } = "";
|
||||
public bool IsCompleted { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public int MinProgress { get; set; }
|
||||
public int MaxProgress { get; set; }
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
InitializeProgressStages();
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize session on component load
|
||||
@@ -231,6 +294,109 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeProgressStages()
|
||||
{
|
||||
progressStages = new List<ProgressStage>
|
||||
{
|
||||
new() { Id = "initializing", Label = "Initializing", Icon = "🚀", MinProgress = 0, MaxProgress = 15 },
|
||||
new() { Id = "fetching", Label = "Fetching Video", Icon = "📡", MinProgress = 15, MaxProgress = 35 },
|
||||
new() { Id = "extracting", Label = "Extracting Audio", Icon = "🔄", MinProgress = 35, MaxProgress = 70 },
|
||||
new() { Id = "converting", Label = "Converting to MP3", Icon = "🎵", MinProgress = 70, MaxProgress = 90 },
|
||||
new() { Id = "finalizing", Label = "Finalizing", Icon = "✨", MinProgress = 90, MaxProgress = 100 }
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateProgressStage(int percentage)
|
||||
{
|
||||
foreach (var stage in progressStages)
|
||||
{
|
||||
stage.IsCompleted = percentage > stage.MaxProgress;
|
||||
stage.IsActive = percentage >= stage.MinProgress && percentage <= stage.MaxProgress;
|
||||
}
|
||||
|
||||
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
|
||||
if (activeStage != null && currentProgressStage != activeStage.Id)
|
||||
{
|
||||
currentProgressStage = activeStage.Id;
|
||||
UpdateStageDetails(activeStage.Id, percentage);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStageDetails(string stageId, int percentage)
|
||||
{
|
||||
currentStageDetails = stageId switch
|
||||
{
|
||||
"initializing" => "Preparing download request and validating URL...",
|
||||
"fetching" => $"Analyzing video content and metadata... ({percentage}%)",
|
||||
"extracting" => $"Extracting high-quality audio stream... ({percentage}%)",
|
||||
"converting" => $"Converting to MP3 format with optimal settings... ({percentage}%)",
|
||||
"finalizing" => $"Completing conversion and preparing download... ({percentage}%)",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private string GetProgressStageTitle()
|
||||
{
|
||||
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
|
||||
return activeStage?.Label ?? "Processing";
|
||||
}
|
||||
|
||||
private string GetProgressIcon()
|
||||
{
|
||||
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
|
||||
return activeStage?.Icon ?? "🎵";
|
||||
}
|
||||
|
||||
private string GetProgressIconClass()
|
||||
{
|
||||
if (progressPercentage < 25) return "spinning";
|
||||
if (progressPercentage < 50) return "pulse";
|
||||
if (progressPercentage < 75) return "bounce";
|
||||
if (progressPercentage < 100) return "glow";
|
||||
return "success";
|
||||
}
|
||||
|
||||
private string GetStageClass(ProgressStage stage)
|
||||
{
|
||||
if (stage.IsCompleted) return "completed";
|
||||
if (stage.IsActive) return "active";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
private string GetElapsedTime()
|
||||
{
|
||||
var elapsed = DateTime.Now - processingStartTime;
|
||||
return elapsed.TotalMinutes >= 1
|
||||
? $"{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"
|
||||
: $"{elapsed.Seconds}s";
|
||||
}
|
||||
|
||||
private string GetEstimatedTimeRemaining()
|
||||
{
|
||||
if (!estimatedTimeRemaining.HasValue) return "";
|
||||
|
||||
var eta = estimatedTimeRemaining.Value;
|
||||
return eta.TotalMinutes >= 1
|
||||
? $"{eta.Minutes:D2}:{eta.Seconds:D2}"
|
||||
: $"{eta.Seconds}s";
|
||||
}
|
||||
|
||||
private void CalculateEstimatedTime(int currentProgress)
|
||||
{
|
||||
if (currentProgress > 5) // Only estimate after some progress
|
||||
{
|
||||
var elapsed = DateTime.Now - processingStartTime;
|
||||
var progressRate = currentProgress / elapsed.TotalSeconds;
|
||||
var remainingProgress = 100 - currentProgress;
|
||||
var estimatedSeconds = remainingProgress / progressRate;
|
||||
|
||||
if (estimatedSeconds > 0 && estimatedSeconds < 300) // Cap at 5 minutes
|
||||
{
|
||||
estimatedTimeRemaining = TimeSpan.FromSeconds(estimatedSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSessionZipInfoAsync()
|
||||
{
|
||||
try
|
||||
@@ -292,6 +458,17 @@
|
||||
outputMessage = "";
|
||||
apiErrorMessage = "";
|
||||
currentVideoDetails = null;
|
||||
processingStartTime = DateTime.Now;
|
||||
estimatedTimeRemaining = null;
|
||||
currentStageDetails = null;
|
||||
|
||||
// Reset progress stages
|
||||
foreach (var stage in progressStages)
|
||||
{
|
||||
stage.IsCompleted = false;
|
||||
stage.IsActive = false;
|
||||
}
|
||||
progressStages.First().IsActive = true;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -304,6 +481,7 @@
|
||||
// Step 1: Start processing (which includes video details fetching)
|
||||
progressMessage = "Starting video processing...";
|
||||
progressPercentage = 5;
|
||||
UpdateProgressStage(progressPercentage);
|
||||
StateHasChanged();
|
||||
|
||||
var request = new ProcessVideoRequest
|
||||
@@ -357,6 +535,7 @@
|
||||
bool videoDetailsReceived = false;
|
||||
int maxAttempts = 60; // Maximum polling attempts (about 2 minutes)
|
||||
int attemptCount = 0;
|
||||
int lastProgress = 0;
|
||||
|
||||
while (attemptCount < maxAttempts && isProcessing)
|
||||
{
|
||||
@@ -364,8 +543,15 @@
|
||||
|
||||
if (status != null)
|
||||
{
|
||||
// Update progress
|
||||
progressPercentage = status.PercentageComplete;
|
||||
// Update progress only if it has increased (prevent UI flicker)
|
||||
if (status.PercentageComplete > lastProgress)
|
||||
{
|
||||
progressPercentage = status.PercentageComplete;
|
||||
lastProgress = progressPercentage;
|
||||
UpdateProgressStage(progressPercentage);
|
||||
CalculateEstimatedTime(progressPercentage);
|
||||
}
|
||||
|
||||
progressMessage = status.Message ?? "Processing...";
|
||||
|
||||
// Check if we received video details
|
||||
@@ -428,6 +614,11 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
// Ensure we reach 100% completion
|
||||
progressPercentage = 100;
|
||||
progressMessage = "Complete!";
|
||||
UpdateProgressStage(progressPercentage);
|
||||
|
||||
// Use the video details from the result if we don't have them yet
|
||||
if (currentVideoDetails == null && result.VideoDetails != null)
|
||||
{
|
||||
@@ -458,15 +649,15 @@
|
||||
CreatedAt = DateTime.Now
|
||||
});
|
||||
|
||||
outputMessage = $"✅ Successfully converted: {title}";
|
||||
progressPercentage = 100;
|
||||
progressMessage = "Complete!";
|
||||
var processingTime = DateTime.Now - processingStartTime;
|
||||
outputMessage = $"Successfully converted: {title} (took {processingTime.TotalSeconds:F1}s)";
|
||||
|
||||
// Reload session ZIP info after successful download
|
||||
await LoadSessionZipInfoAsync();
|
||||
|
||||
// Small delay to show completion
|
||||
await Task.Delay(500);
|
||||
// Show completion for a moment
|
||||
StateHasChanged();
|
||||
await Task.Delay(1000);
|
||||
|
||||
isProcessing = false;
|
||||
youtubeUrl = "";
|
||||
|
||||
@@ -585,6 +585,410 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Progress Section Styles */
|
||||
.enhanced-progress-section {
|
||||
margin: 25px 0;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f3f4ff 100%);
|
||||
border: 1px solid #e1e5ff;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.08);
|
||||
animation: slideIn 0.5s ease-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.enhanced-progress-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #6366f1, transparent);
|
||||
animation: progressShimmer 2s infinite;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-icon {
|
||||
font-size: 1.4rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-icon.spinning {
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.progress-icon.pulse {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.progress-icon.bounce {
|
||||
animation: iconBounce 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.progress-icon.glow {
|
||||
animation: glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.progress-icon.success {
|
||||
color: #10b981;
|
||||
animation: successPop 0.5s ease-out;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
font-size: 0.85rem;
|
||||
color: #6b7280;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: #6366f1;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.progress-elapsed,
|
||||
.progress-eta {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar-background {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: linear-gradient(90deg, #e5e7eb 0%, #f3f4f6 100%);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
|
||||
border-radius: 6px;
|
||||
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 0 10px rgba(99, 102, 241, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.progress-bar-shimmer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.progress-stages {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-stage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stage-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.progress-stage.pending .stage-icon {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.progress-stage.pending .stage-label {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.progress-stage.active .stage-icon {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
border-color: #6366f1;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(99, 102,241, 0.3);
|
||||
animation: stageActivePulse 2s infinite;
|
||||
}
|
||||
|
||||
.progress-stage.active .stage-label {
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-stage.completed .stage-icon {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
border-color: #10b981;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.progress-stage.completed .stage-label {
|
||||
color: #10b981;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 0.95rem;
|
||||
color: #4b5563;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-dots {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.progress-dots .dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #6b7280;
|
||||
border-radius: 50%;
|
||||
animation: dotPulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.progress-dots .dot:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.progress-dots .dot:nth-child(3) {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.stage-details {
|
||||
color: #6b7280;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Enhanced download button processing state */
|
||||
.download-btn.processing {
|
||||
background: linear-gradient(135deg, #6b7280 0%, #9ca3af 100%);
|
||||
animation: buttonPulse 2s infinite;
|
||||
}
|
||||
|
||||
.download-btn.processing .btn-icon {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Enhanced success and error messages */
|
||||
.output-message.success {
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #f0fdf4 100%);
|
||||
border: 1px solid #bbf7d0;
|
||||
color: #065f46;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
animation: successSlide 0.6s ease-out;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 1.2rem;
|
||||
animation: successPop 0.5s ease-out;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: linear-gradient(135deg, #fef2f2 0%, #fef7f7 100%);
|
||||
border: 1px solid #fecaca;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
animation: errorShake 0.5s ease-out;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Enhanced loading states */
|
||||
.loading-spinner {
|
||||
font-size: 1rem;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.processing-text {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Video info enhancements */
|
||||
.processing-text {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes progressShimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
@keyframes stageActivePulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes iconBounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-4px); }
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% {
|
||||
text-shadow: 0 0 5px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
50% {
|
||||
text-shadow: 0 0 15px rgba(99, 102, 241, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes successPop {
|
||||
0% { transform: scale(0.8); opacity: 0; }
|
||||
50% { transform: scale(1.2); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes dotPulse {
|
||||
0%, 80%, 100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes buttonPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 4px 12px rgba(107, 114, 128, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 4px 20px rgba(107, 114, 128, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes successSlide {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes errorShake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
}
|
||||
|
||||
/* Responsive Design - Tablet */
|
||||
@media (min-width: 640px) and (max-width: 1024px) {
|
||||
.hero-section {
|
||||
@@ -613,6 +1017,39 @@
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
/* Enhanced progress tablet styles */
|
||||
.enhanced-progress-section {
|
||||
padding: 18px;
|
||||
margin: 22px 0;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.progress-stages {
|
||||
gap: 6px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.stage-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
gap: 35px;
|
||||
}
|
||||
@@ -680,6 +1117,62 @@
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
/* Enhanced progress mobile styles */
|
||||
.enhanced-progress-section {
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
align-self: stretch;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.progress-stages {
|
||||
gap: 4px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.stage-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
font-size: 0.65rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.progress-bar-background {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stage-details {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
gap: 25px;
|
||||
justify-content: space-around;
|
||||
@@ -792,6 +1285,67 @@
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Enhanced progress extra small mobile styles */
|
||||
.enhanced-progress-section {
|
||||
padding: 12px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
font-size: 0.75rem;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.progress-elapsed,
|
||||
.progress-eta {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.progress-stages {
|
||||
gap: 2px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.stage-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
font-size: 0.6rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-bar-background {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.stage-details {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user