show progress
This commit is contained in:
+222
-31
@@ -41,12 +41,12 @@
|
|||||||
disabled="@isProcessing" />
|
disabled="@isProcessing" />
|
||||||
<button @onclick="DownloadMp3"
|
<button @onclick="DownloadMp3"
|
||||||
@onmousedown="PreventDefault"
|
@onmousedown="PreventDefault"
|
||||||
class="download-btn"
|
class="download-btn @(isProcessing ? "processing" : "")"
|
||||||
disabled="@(isProcessing)">
|
disabled="@(isProcessing)">
|
||||||
<span class="btn-icon">🎵</span>
|
<span class="btn-icon">🎵</span>
|
||||||
@if (isProcessing)
|
@if (isProcessing)
|
||||||
{
|
{
|
||||||
<span>Processing...</span>
|
<span>Processing... (@progressPercentage%)</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -55,6 +55,65 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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 *@
|
@* Video Information Section - Show as soon as we have details *@
|
||||||
@if (currentVideoDetails != null)
|
@if (currentVideoDetails != null)
|
||||||
{
|
{
|
||||||
@@ -69,6 +128,7 @@
|
|||||||
{
|
{
|
||||||
<div class="processing-overlay">
|
<div class="processing-overlay">
|
||||||
<div class="processing-spinner">🎵</div>
|
<div class="processing-spinner">🎵</div>
|
||||||
|
<div class="processing-text">@progressPercentage%</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,29 +140,10 @@
|
|||||||
</div>
|
</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)
|
@if (!string.IsNullOrWhiteSpace(outputMessage) && !isProcessing)
|
||||||
{
|
{
|
||||||
<div class="output-message">
|
<div class="output-message success">
|
||||||
|
<div class="success-icon">✅</div>
|
||||||
<p>@outputMessage</p>
|
<p>@outputMessage</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -111,7 +152,8 @@
|
|||||||
@if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing)
|
@if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing)
|
||||||
{
|
{
|
||||||
<div class="error-message">
|
<div class="error-message">
|
||||||
<p>❌ @apiErrorMessage</p>
|
<div class="error-icon">❌</div>
|
||||||
|
<p>@apiErrorMessage</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +174,7 @@
|
|||||||
@if (isLoadingZip)
|
@if (isLoadingZip)
|
||||||
{
|
{
|
||||||
<span>Preparing ZIP...</span>
|
<span>Preparing ZIP...</span>
|
||||||
|
<div class="loading-spinner">⏳</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -198,10 +241,17 @@
|
|||||||
private bool isLoadingZip = false;
|
private bool isLoadingZip = false;
|
||||||
private int progressPercentage = 0;
|
private int progressPercentage = 0;
|
||||||
private string progressMessage = "";
|
private string progressMessage = "";
|
||||||
|
private string? currentStageDetails = null;
|
||||||
private List<DownloadItem> downloadLinks = new();
|
private List<DownloadItem> downloadLinks = new();
|
||||||
private YouTubeVideoDetails? currentVideoDetails = null;
|
private YouTubeVideoDetails? currentVideoDetails = null;
|
||||||
private string currentSessionId = "";
|
private string currentSessionId = "";
|
||||||
private SessionZipInfo? sessionZipInfo = null;
|
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
|
public class DownloadItem
|
||||||
{
|
{
|
||||||
@@ -211,8 +261,21 @@
|
|||||||
public DateTime CreatedAt { get; set; }
|
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()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
InitializeProgressStages();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Initialize session on component load
|
// 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()
|
private async Task LoadSessionZipInfoAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -292,6 +458,17 @@
|
|||||||
outputMessage = "";
|
outputMessage = "";
|
||||||
apiErrorMessage = "";
|
apiErrorMessage = "";
|
||||||
currentVideoDetails = null;
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -304,6 +481,7 @@
|
|||||||
// Step 1: Start processing (which includes video details fetching)
|
// Step 1: Start processing (which includes video details fetching)
|
||||||
progressMessage = "Starting video processing...";
|
progressMessage = "Starting video processing...";
|
||||||
progressPercentage = 5;
|
progressPercentage = 5;
|
||||||
|
UpdateProgressStage(progressPercentage);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
var request = new ProcessVideoRequest
|
var request = new ProcessVideoRequest
|
||||||
@@ -357,6 +535,7 @@
|
|||||||
bool videoDetailsReceived = false;
|
bool videoDetailsReceived = false;
|
||||||
int maxAttempts = 60; // Maximum polling attempts (about 2 minutes)
|
int maxAttempts = 60; // Maximum polling attempts (about 2 minutes)
|
||||||
int attemptCount = 0;
|
int attemptCount = 0;
|
||||||
|
int lastProgress = 0;
|
||||||
|
|
||||||
while (attemptCount < maxAttempts && isProcessing)
|
while (attemptCount < maxAttempts && isProcessing)
|
||||||
{
|
{
|
||||||
@@ -364,8 +543,15 @@
|
|||||||
|
|
||||||
if (status != null)
|
if (status != null)
|
||||||
{
|
{
|
||||||
// Update progress
|
// Update progress only if it has increased (prevent UI flicker)
|
||||||
progressPercentage = status.PercentageComplete;
|
if (status.PercentageComplete > lastProgress)
|
||||||
|
{
|
||||||
|
progressPercentage = status.PercentageComplete;
|
||||||
|
lastProgress = progressPercentage;
|
||||||
|
UpdateProgressStage(progressPercentage);
|
||||||
|
CalculateEstimatedTime(progressPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
progressMessage = status.Message ?? "Processing...";
|
progressMessage = status.Message ?? "Processing...";
|
||||||
|
|
||||||
// Check if we received video details
|
// Check if we received video details
|
||||||
@@ -428,6 +614,11 @@
|
|||||||
{
|
{
|
||||||
try
|
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
|
// Use the video details from the result if we don't have them yet
|
||||||
if (currentVideoDetails == null && result.VideoDetails != null)
|
if (currentVideoDetails == null && result.VideoDetails != null)
|
||||||
{
|
{
|
||||||
@@ -458,15 +649,15 @@
|
|||||||
CreatedAt = DateTime.Now
|
CreatedAt = DateTime.Now
|
||||||
});
|
});
|
||||||
|
|
||||||
outputMessage = $"✅ Successfully converted: {title}";
|
var processingTime = DateTime.Now - processingStartTime;
|
||||||
progressPercentage = 100;
|
outputMessage = $"Successfully converted: {title} (took {processingTime.TotalSeconds:F1}s)";
|
||||||
progressMessage = "Complete!";
|
|
||||||
|
|
||||||
// Reload session ZIP info after successful download
|
// Reload session ZIP info after successful download
|
||||||
await LoadSessionZipInfoAsync();
|
await LoadSessionZipInfoAsync();
|
||||||
|
|
||||||
// Small delay to show completion
|
// Show completion for a moment
|
||||||
await Task.Delay(500);
|
StateHasChanged();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
isProcessing = false;
|
isProcessing = false;
|
||||||
youtubeUrl = "";
|
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 */
|
/* Responsive Design - Tablet */
|
||||||
@media (min-width: 640px) and (max-width: 1024px) {
|
@media (min-width: 640px) and (max-width: 1024px) {
|
||||||
.hero-section {
|
.hero-section {
|
||||||
@@ -613,6 +1017,39 @@
|
|||||||
padding: 16px 20px;
|
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 {
|
.features {
|
||||||
gap: 35px;
|
gap: 35px;
|
||||||
}
|
}
|
||||||
@@ -680,6 +1117,62 @@
|
|||||||
padding: 16px 24px;
|
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 {
|
.features {
|
||||||
gap: 25px;
|
gap: 25px;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
@@ -792,6 +1285,67 @@
|
|||||||
font-size: 0.9rem;
|
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 {
|
.features {
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user