Replace Blazor loader with custom music-themed splash
Replaces default Blazor loading spinner with a custom, animated music-themed loader featuring a gradient background, floating musical notes, logo, equalizer animation, and dynamic progress bar/messages. Removes old loader CSS from app.css and adds new styles to index.html. Loader progress and messages are now animated via JavaScript, with interactive note effects on click. Loader and background fade out smoothly when the app is ready.
This commit is contained in:
@@ -15,6 +15,11 @@ html, body {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* Smooth transition when app loads */
|
||||
.app-loaded #app {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -76,49 +81,6 @@ a, .btn-link {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
inset: 20vh 0 auto 0;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
@@ -18,15 +18,291 @@
|
||||
|
||||
<link href="TuneRipper.styles.css" rel="stylesheet" />
|
||||
<script type="importmap"></script>
|
||||
|
||||
<style>
|
||||
/* Custom WASM Loader Styles */
|
||||
#app {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-loader {
|
||||
text-align: center;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loader-logo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto 30px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loader-logo img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.loader-logo::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(45deg, #ff6b6b, #feca57, #48dbfb, #ff9ff3);
|
||||
border-radius: 22px;
|
||||
z-index: -1;
|
||||
animation: border-glow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loader-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
background: linear-gradient(45deg, #fff, #f0f8ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
animation: title-glow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loader-subtitle {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0.9;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* Progress Bar */
|
||||
.progress-container {
|
||||
width: 300px;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto 20px;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #ff6b6b, #feca57, #48dbfb, #ff9ff3);
|
||||
background-size: 300% 100%;
|
||||
border-radius: 10px;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
animation: gradient-shift 2s ease-in-out infinite;
|
||||
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
margin-top: 15px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Floating Musical Notes */
|
||||
.music-notes {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.note {
|
||||
position: absolute;
|
||||
font-size: 2rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
animation: float-note 6s linear infinite;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.note:nth-child(1) { left: 10%; animation-delay: 0s; animation-duration: 8s; }
|
||||
.note:nth-child(2) { left: 20%; animation-delay: 1s; animation-duration: 7s; }
|
||||
.note:nth-child(3) { left: 30%; animation-delay: 2s; animation-duration: 9s; }
|
||||
.note:nth-child(4) { left: 40%; animation-delay: 0.5s; animation-duration: 6s; }
|
||||
.note:nth-child(5) { left: 50%; animation-delay: 3s; animation-duration: 8s; }
|
||||
.note:nth-child(6) { left: 60%; animation-delay: 1.5s; animation-duration: 7s; }
|
||||
.note:nth-child(7) { left: 70%; animation-delay: 2.5s; animation-duration: 9s; }
|
||||
.note:nth-child(8) { left: 80%; animation-delay: 4s; animation-duration: 6s; }
|
||||
.note:nth-child(9) { left: 90%; animation-delay: 0.8s; animation-duration: 8s; }
|
||||
|
||||
/* Equalizer bars */
|
||||
.equalizer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.eq-bar {
|
||||
width: 4px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 2px;
|
||||
animation: equalizer 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.eq-bar:nth-child(1) { height: 20px; animation-delay: 0s; }
|
||||
.eq-bar:nth-child(2) { height: 35px; animation-delay: 0.1s; }
|
||||
.eq-bar:nth-child(3) { height: 25px; animation-delay: 0.2s; }
|
||||
.eq-bar:nth-child(4) { height: 40px; animation-delay: 0.3s; }
|
||||
.eq-bar:nth-child(5) { height: 30px; animation-delay: 0.4s; }
|
||||
.eq-bar:nth-child(6) { height: 45px; animation-delay: 0.5s; }
|
||||
.eq-bar:nth-child(7) { height: 25px; animation-delay: 0.6s; }
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
@keyframes border-glow {
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes title-glow {
|
||||
0%, 100% { text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); }
|
||||
50% { text-shadow: 0 2px 20px rgba(255, 255, 255, 0.5); }
|
||||
}
|
||||
|
||||
@keyframes float-note {
|
||||
0% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gradient-shift {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
@keyframes equalizer {
|
||||
0%, 100% { transform: scaleY(1); }
|
||||
50% { transform: scaleY(0.3); }
|
||||
}
|
||||
|
||||
/* Hide when loaded */
|
||||
.app-loaded #app .custom-loader {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition: all 0.8s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-loaded #app .music-notes {
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease-out;
|
||||
}
|
||||
|
||||
.app-loaded #app {
|
||||
background: none !important;
|
||||
transition: background 0.8s ease-out;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 480px) {
|
||||
.loader-logo {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.loader-logo img {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.loader-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
<!-- Floating Musical Notes Background -->
|
||||
<div class="music-notes">
|
||||
<div class="note">♪</div>
|
||||
<div class="note">♫</div>
|
||||
<div class="note">♪</div>
|
||||
<div class="note">♬</div>
|
||||
<div class="note">♫</div>
|
||||
<div class="note">♪</div>
|
||||
<div class="note">♬</div>
|
||||
<div class="note">♫</div>
|
||||
<div class="note">♪</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Loader -->
|
||||
<div class="custom-loader">
|
||||
<div class="loader-logo">
|
||||
<img src="images/logo.png" alt="TuneRipper Logo" />
|
||||
</div>
|
||||
<h1 class="loader-title">TuneRipper</h1>
|
||||
<p class="loader-subtitle">YouTube to MP3 Converter</p>
|
||||
|
||||
<!-- Equalizer Animation -->
|
||||
<div class="equalizer">
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
<div class="eq-bar"></div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" id="loadingProgress"></div>
|
||||
</div>
|
||||
<div class="loading-text" id="loadingText">Initializing audio engine...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
@@ -34,6 +310,129 @@
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Enhanced progress tracking with music-themed messages
|
||||
let progress = 0;
|
||||
let messageIndex = 0;
|
||||
const progressBar = document.getElementById('loadingProgress');
|
||||
const loadingTextEl = document.getElementById('loadingText');
|
||||
const loadingMessages = [
|
||||
'Initializing audio engine...',
|
||||
'Loading sound codecs...',
|
||||
'Tuning the digital instruments...',
|
||||
'Preparing the recording studio...',
|
||||
'Setting up the mixing board...',
|
||||
'Calibrating audio levels...',
|
||||
'Loading music libraries...',
|
||||
'Warming up the amplifiers...',
|
||||
'Almost ready to rock! 🎸'
|
||||
];
|
||||
|
||||
function updateProgress() {
|
||||
// Simulate realistic loading progress
|
||||
const increment = Math.random() * 8 + 3;
|
||||
progress += increment;
|
||||
|
||||
if (progress > 95) progress = 95; // Stop at 95% until Blazor is actually loaded
|
||||
|
||||
progressBar.style.width = progress + '%';
|
||||
|
||||
// Update message based on progress
|
||||
const newMessageIndex = Math.min(
|
||||
Math.floor((progress / 95) * loadingMessages.length),
|
||||
loadingMessages.length - 1
|
||||
);
|
||||
|
||||
if (newMessageIndex !== messageIndex) {
|
||||
messageIndex = newMessageIndex;
|
||||
loadingTextEl.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
loadingTextEl.textContent = loadingMessages[messageIndex];
|
||||
loadingTextEl.style.opacity = '0.8';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
if (progress < 95) {
|
||||
setTimeout(updateProgress, Math.random() * 400 + 300);
|
||||
}
|
||||
}
|
||||
|
||||
function completeLoading() {
|
||||
progress = 100;
|
||||
progressBar.style.width = '100%';
|
||||
loadingTextEl.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
loadingTextEl.textContent = 'Ready to convert! 🎵';
|
||||
loadingTextEl.style.opacity = '0.8';
|
||||
}, 150);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('app-loaded');
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Start progress simulation
|
||||
setTimeout(updateProgress, 500);
|
||||
|
||||
// Listen for Blazor events
|
||||
let blazorLoaded = false;
|
||||
|
||||
// Check if Blazor is loaded via multiple methods
|
||||
function checkBlazorLoaded() {
|
||||
if (blazorLoaded) return;
|
||||
|
||||
if (window.Blazor || document.querySelector('[data-blazor-loaded]')) {
|
||||
blazorLoaded = true;
|
||||
completeLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// Method 1: Standard load event
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(checkBlazorLoaded, 200);
|
||||
});
|
||||
|
||||
// Method 2: Blazor specific events
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
if (window.Blazor) {
|
||||
blazorLoaded = true;
|
||||
completeLoading();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Method 3: Fallback timer (ensure it loads eventually)
|
||||
setTimeout(() => {
|
||||
if (!blazorLoaded) {
|
||||
blazorLoaded = true;
|
||||
completeLoading();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Add some interactive notes on click/touch
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!blazorLoaded && Math.random() > 0.7) {
|
||||
const note = document.createElement('div');
|
||||
note.className = 'note';
|
||||
note.textContent = ['♪', '♫', '♬'][Math.floor(Math.random() * 3)];
|
||||
note.style.left = e.clientX + 'px';
|
||||
note.style.top = e.clientY + 'px';
|
||||
note.style.position = 'fixed';
|
||||
note.style.zIndex = '20';
|
||||
note.style.animation = 'float-note 2s linear forwards';
|
||||
document.body.appendChild(note);
|
||||
|
||||
setTimeout(() => {
|
||||
note.remove();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user