534 lines
21 KiB
HTML
534 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>katOS</title>
|
|
|
|
<!-- Google AdSense -->
|
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3876936176422477" crossorigin="anonymous"></script>
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&family=Fira+Code:wght@300;400;500&display=swap" rel="stylesheet">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
--bg: #000;
|
|
--border: rgba(255,255,255,0.15);
|
|
--glass: rgba(0,0,0,0.75);
|
|
--text: #fff;
|
|
}
|
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
body { font-family:"Orbitron", sans-serif; background: var(--bg); color: var(--text); height:100vh; overflow:hidden; }
|
|
.desktop { width:100%; height:100vh; position:relative; }
|
|
.terminal-bg, .video-bg { position:fixed; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; pointer-events:none; opacity: 0.4; }
|
|
.taskbar { position:fixed; bottom:0; width:100%; background:rgba(255,255,255,0.05); border-top:1px solid var(--border); padding:8px 15px; font-size:12px; z-index:10; display: flex; justify-content: space-between; align-items: center; }
|
|
.window { position:absolute; background:linear-gradient(145deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.02) 50%, rgba(0,0,0,0.1) 100%); border:1px solid rgba(255,255,255,0.13); border-radius:14px; backdrop-filter: blur(26px) saturate(180%) brightness(1.06); -webkit-backdrop-filter: blur(26px) saturate(180%) brightness(1.06); box-shadow: 0 4px 30px rgba(0,0,0,0.3), 0 1px 0 rgba(255,255,255,0.1) inset; z-index:2; transition: all 0.35s ease; }
|
|
.window:hover { box-shadow: 0 8px 48px rgba(0,0,0,0.4), 0 0 20px rgba(255,255,255,0.06), 0 1px 0 rgba(255,255,255,0.15) inset; transform: translateY(-3px); backdrop-filter: blur(32px) saturate(200%) brightness(1.1); -webkit-backdrop-filter: blur(32px) saturate(200%) brightness(1.1); }
|
|
.titlebar { padding:8px; border-bottom:1px solid var(--border); font-size:11px; cursor:grab; display:flex; align-items:center; gap:6px; }
|
|
.mac-btn { width:12px; height:12px; border-radius:50%; background:white; border:1px solid #000; }
|
|
.content { padding:15px; font-size:13px; overflow:auto; }
|
|
.mono { font-family:'Fira Code', 'Courier New', monospace; white-space:pre; text-shadow: 0 0 6px rgba(255,255,255,0.25); line-height:1.4; font-size:11px; letter-spacing: 0.3px; }
|
|
#neofetchTerminal { white-space: normal; }
|
|
.link { cursor:pointer; margin-bottom:6px; }
|
|
.link:hover { text-decoration:underline; }
|
|
.glow-links .link { text-shadow: 0 0 8px rgba(255,255,255,0.45), 0 0 20px rgba(255,255,255,0.15); }
|
|
.glow-links .link:hover { text-shadow: 0 0 12px rgba(255,255,255,0.7), 0 0 28px rgba(255,255,255,0.25); }
|
|
.terminal-window { background: linear-gradient(145deg, rgba(0,0,0,0.85), rgba(0,0,0,0.7)); border:1px solid rgba(255,255,255,0.2); backdrop-filter: blur(22px) saturate(180%); border-radius:16px; }
|
|
.terminal-window .content { max-height: calc(100% - 35px); overflow-y: auto; overflow-x: hidden; }
|
|
.profile-icon { width:50px; height:50px; border-radius:50%; margin-bottom:8px; border:1px solid rgba(255,255,255,0.3); display:block; margin-left:auto; margin-right:auto; }
|
|
|
|
/* Custom scrollbar for terminal windows */
|
|
.terminal-window .content::-webkit-scrollbar { width: 8px; }
|
|
.terminal-window .content::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 10px; }
|
|
.terminal-window .content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 10px; }
|
|
.terminal-window .content::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }
|
|
|
|
/* Visualizer canvas container */
|
|
.video-container { width: 100%; height: 100%; position: relative; overflow: hidden; border-radius: 0 0 14px 14px; background: transparent; display: flex; align-items: center; justify-content: center; }
|
|
#vizCanvas { width: 100%; height: 100%; display: block; border-radius: 0 0 13px 13px; }
|
|
.viz-idle-msg { position: absolute; color: rgba(255,255,255,0.22); font-size: 11px; letter-spacing: 2px; pointer-events: none; text-transform: uppercase; }
|
|
|
|
/* Loading screen */
|
|
#loadingScreen {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: #000;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 9999;
|
|
cursor: pointer;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
#loadingScreen.hidden {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
.loading-content {
|
|
text-align: center;
|
|
}
|
|
.loading-logo {
|
|
font-size: 72px;
|
|
font-weight: 700;
|
|
margin-bottom: 30px;
|
|
text-shadow: 0 0 20px rgba(255,255,255,0.5);
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
.loading-text {
|
|
font-size: 16px;
|
|
opacity: 0.7;
|
|
animation: fadeInOut 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
50% { opacity: 0.8; transform: scale(1.05); }
|
|
}
|
|
@keyframes fadeInOut {
|
|
0%, 100% { opacity: 0.5; }
|
|
50% { opacity: 1; }
|
|
}
|
|
|
|
/* Music controls */
|
|
.music-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-size: 11px;
|
|
}
|
|
.music-btn {
|
|
background: rgba(255,255,255,0.1);
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
border-radius: 50%;
|
|
width: 28px;
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
color: var(--text);
|
|
}
|
|
.music-btn:hover {
|
|
background: rgba(255,255,255,0.2);
|
|
transform: scale(1.1);
|
|
}
|
|
.music-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.music-icon {
|
|
animation: spin 3s linear infinite;
|
|
}
|
|
.music-icon.paused {
|
|
animation-play-state: paused;
|
|
}
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.volume-slider {
|
|
width: 60px;
|
|
height: 4px;
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
background: rgba(255,255,255,0.2);
|
|
outline: none;
|
|
border-radius: 2px;
|
|
}
|
|
.volume-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 12px;
|
|
height: 12px;
|
|
background: #fff;
|
|
cursor: pointer;
|
|
border-radius: 50%;
|
|
}
|
|
.volume-slider::-moz-range-thumb {
|
|
width: 12px;
|
|
height: 12px;
|
|
background: #fff;
|
|
cursor: pointer;
|
|
border-radius: 50%;
|
|
border: none;
|
|
}
|
|
|
|
.placeholder-text {
|
|
color: rgba(255,255,255,0.5);
|
|
font-size: 14px;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Loading Screen -->
|
|
<div id="loadingScreen">
|
|
<div class="loading-content">
|
|
<div class="loading-logo">katOS</div>
|
|
<div class="loading-text">Click anywhere to enter</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="desktop">
|
|
<video class="video-bg" id="bgVideo" autoplay muted loop playsinline></video>
|
|
<div class="taskbar">
|
|
<span>katOS</span>
|
|
|
|
<div class="music-controls">
|
|
<div class="music-info">
|
|
<i class="fas fa-compact-disc music-icon" id="musicIcon"></i>
|
|
<span id="musicStatus">Music Ready</span>
|
|
</div>
|
|
<button class="music-btn" id="playPauseBtn" title="Play/Pause">
|
|
<i class="fas fa-play" id="playPauseIcon"></i>
|
|
</button>
|
|
<input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="40" title="Volume">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- About Window -->
|
|
<div class="window" style="top: 79px; left: 41px; width: 320px;">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
about.md
|
|
</div>
|
|
<div class="content">
|
|
I cant stop loving linux, pls speed i need this<br><br>
|
|
minecraft shyt, networking experiments, and linux customization.<br><br>
|
|
i was on the ███████████████<br><br>
|
|
cats > people haha.<br><br>
|
|
i use arch yea <br><br>
|
|
you da real kat 🤍.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Links Window -->
|
|
<div class="window" style="top: 434px; left: 41px; width: 320px;"">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
links.zip
|
|
</div>
|
|
<div class="content">
|
|
<p class="link" onclick="openLink('https://discordapp.com/users/1082968457559146506')"><i class="fab fa-discord"></i> Discord</p><br>
|
|
<p class="link" onclick="openLink('https://www.instagram.com/oiupoyt/')"><i class="fab fa-instagram"></i> Instagram</p><br>
|
|
<p class="link" onclick="openLink('https://www.youtube.com/@OIUPOYT')"><i class="fab fa-youtube"></i> YouTube</p><br>
|
|
<p class="link" onclick="openLink('https://www.roblox.com/users/1529210346/profile')"><svg style="width:13px;height:13px;vertical-align:middle;margin-right:4px;fill:currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M4.686 0L0 19.314 19.314 24 24 4.686zm10.016 14.23l-5.072-1.358 1.358-5.072 5.072 1.358z"/></svg> Roblox</p><br>
|
|
<p class="link" onclick="openLink('https://modrinth.com/user/oiupoyt')"><img src="https://cdn.simpleicons.org/modrinth/ffffff" style="width:13px;height:13px;vertical-align:middle;margin-right:4px;opacity:0.9;"> Modrinth</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="window" style="top: 507px; left: 417px; width: 320px;">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
websites.html
|
|
</div>
|
|
<div class="content glow-links"><br>
|
|
<p class="link" onclick="openLink('https://katdrop.onrender.com/')"><i class="fas fa-share-alt"></i> website to share files cuz ye</p><br>
|
|
<p class="link" onclick="openLink('https://justdownload.netlify.app')"><i class="fas fa-cloud-download-alt"></i> just download yt and insta stuff</p><br>
|
|
<p class="link" onclick="openLink('https://kat-images.netlify.app/')"><i class="fas fa-cat"></i> kat images</p><br>
|
|
</div>
|
|
</div>
|
|
<!-- Skills Window -->
|
|
<div class="window" style="top: 162px; left: 426px; width: 320px;">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
skillz.bash
|
|
</div>
|
|
<div class="content">
|
|
linux / arch<br>Minecraft Servers<br>Self Hosting<br>server networking<br>Java / Plugins<br>yappin<br>im crine<br>tweaking<br>hahahahahha<br>cool content<br>very funny<br>veri fond of cats
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FastFetch Profile Window -->
|
|
<div class="window" style="top: 31px; left: 927px; width: 750px; height: 390px;">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
~fastfetch
|
|
</div>
|
|
<div class="content mono" id="neofetchTerminal"></div>
|
|
</div>
|
|
|
|
<!-- Pipes Terminal Window -->
|
|
<div class="window" style="top: 457.467px; left: 783px; width: 950px; height: 450px;">
|
|
<div class="titlebar">
|
|
<div class="mac-btn"></div><div class="mac-btn"></div><div class="mac-btn"></div>
|
|
visualizer
|
|
</div>
|
|
<div class="video-container" id="pipesContainer">
|
|
<canvas id="vizCanvas"></canvas>
|
|
<span class="viz-idle-msg" id="vizIdleMsg">play music to visualize</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Audio element for music -->
|
|
<audio id="bgMusic" loop>
|
|
<source src="music.mp3" type="audio/mpeg">
|
|
Your browser does not support the audio element.
|
|
</audio>
|
|
|
|
<script>
|
|
function openLink(url){ window.open(url, '_blank'); }
|
|
|
|
document.querySelectorAll(".window").forEach(win=>{
|
|
const bar = win.querySelector(".titlebar");
|
|
if (!bar) return;
|
|
bar.onmousedown = e=>{
|
|
let shiftX = e.clientX - win.getBoundingClientRect().left;
|
|
let shiftY = e.clientY - win.getBoundingClientRect().top;
|
|
function move(x,y){ win.style.left = x - shiftX + 'px'; win.style.top = y - shiftY + 'px'; }
|
|
function mouseMove(e){ move(e.pageX,e.pageY); }
|
|
document.addEventListener('mousemove',mouseMove);
|
|
document.onmouseup = ()=>{ document.removeEventListener('mousemove',mouseMove); document.onmouseup=null; }
|
|
}
|
|
});
|
|
|
|
// Neofetch Output — innerHTML with Font Awesome icons
|
|
const neofetchWin = document.getElementById("neofetchTerminal");
|
|
const i = (cls) => `<i class="${cls}" style="width:1.1em;text-align:center;display:inline-block;opacity:0.8;font-size:10px;"></i>`;
|
|
neofetchWin.innerHTML = [
|
|
` ▄`,
|
|
` ▟█▙`,
|
|
` ▟███▙ <b>oiupoyt</b><span style="opacity:0.4">@</span><b>archlinux</b>`,
|
|
` ▟█████▙ <span style="opacity:0.35">────────────────────────</span>`,
|
|
` ▟███████▙ ${i('fas fa-laptop')} <span style="opacity:0.45;font-size:10px">OS</span> Arch Linux x86_64`,
|
|
` ▂▔▀▜██████▙ ${i('fas fa-microchip')} <span style="opacity:0.45;font-size:10px">Kernel</span> Linux 6.19.8-arch1-1`,
|
|
` ▟██▅▂▝▜█████▙ ${i('fas fa-box-open')} <span style="opacity:0.45;font-size:10px">Packages</span> 1254 (pacman), 60 (flatpak)`,
|
|
` ▟█████████████▙ ${i('fas fa-terminal')} <span style="opacity:0.45;font-size:10px">Shell</span> fish 4.5.0`,
|
|
` ▟███████████████▙ ${i('fas fa-display')} <span style="opacity:0.45;font-size:10px">Terminal</span> kitty 0.46.1`,
|
|
` ▟█████████████████▙ ${i('fas fa-window-restore')} <span style="opacity:0.45;font-size:10px">WM</span> Hyprland 0.54.2 (Wayland)`,
|
|
` ▟███████████████████▙ ${i('fas fa-palette')} <span style="opacity:0.45;font-size:10px">Theme</span> breeze`,
|
|
` ▟█████████▛▀▀▜████████▙ ${i('fas fa-font')} <span style="opacity:0.45;font-size:10px">Font</span> NotoSansMono-Regular (11pt)`,
|
|
` ▟████████▛ ▜███████▙ ${i('fas fa-clock')} <span style="opacity:0.45;font-size:10px">Uptime</span> 9 hours, 16 mins`,
|
|
` ▟█████████ ████████▙ ${i('fas fa-calendar-days')} <span style="opacity:0.45;font-size:10px">Date</span> 2026-4-10`,
|
|
` ▟██████████ █████▆▅▄▃▂ ${i('fas fa-music')} <span style="opacity:0.45;font-size:10px">Music</span> Baiaa081 - Noite Isolada`,
|
|
` ▟██████████▛ ▜█████████▙ ${i('fab fa-firefox-browser')} <span style="opacity:0.45;font-size:10px">Browser</span> katr.netlify.app (Mozilla Zen)`,
|
|
` ▟██████▀▀▀ ▀▀██████▙`,
|
|
` ▟███▀▘ ▝▀███▙`,
|
|
` ▟▛▀ ▀▜▙`,
|
|
].map(l => `<div style="white-space:pre;line-height:1.5;display:flex;align-items:center;gap:6px;">${l}</div>`).join('');
|
|
|
|
|
|
// Music player
|
|
const bgMusic = document.getElementById('bgMusic');
|
|
const playPauseBtn = document.getElementById('playPauseBtn');
|
|
const playPauseIcon = document.getElementById('playPauseIcon');
|
|
const volumeSlider = document.getElementById('volumeSlider');
|
|
const musicStatus = document.getElementById('musicStatus');
|
|
const musicIcon = document.getElementById('musicIcon');
|
|
|
|
let isPlaying = false;
|
|
|
|
// Set initial volume
|
|
bgMusic.volume = volumeSlider.value / 100;
|
|
|
|
// Play/Pause button
|
|
playPauseBtn.addEventListener('click', () => {
|
|
if (isPlaying) {
|
|
bgMusic.pause();
|
|
playPauseIcon.className = 'fas fa-play';
|
|
musicStatus.textContent = 'Paused';
|
|
musicIcon.classList.add('paused');
|
|
isPlaying = false;
|
|
} else {
|
|
bgMusic.play().then(() => {
|
|
playPauseIcon.className = 'fas fa-pause';
|
|
musicStatus.textContent = 'Now Playing';
|
|
musicIcon.classList.remove('paused');
|
|
isPlaying = true;
|
|
}).catch(err => {
|
|
console.error('Music play error:', err);
|
|
musicStatus.textContent = 'Error playing';
|
|
});
|
|
}
|
|
});
|
|
|
|
// Volume control
|
|
volumeSlider.addEventListener('input', (e) => {
|
|
bgMusic.volume = e.target.value / 100;
|
|
});
|
|
|
|
// Update status when music ends
|
|
bgMusic.addEventListener('ended', () => {
|
|
playPauseIcon.className = 'fas fa-play';
|
|
musicStatus.textContent = 'Ended';
|
|
musicIcon.classList.add('paused');
|
|
isPlaying = false;
|
|
});
|
|
|
|
// Loading screen functionality
|
|
const loadingScreen = document.getElementById('loadingScreen');
|
|
const bgVideo = document.getElementById('bgVideo');
|
|
|
|
loadingScreen.addEventListener('click', () => {
|
|
loadingScreen.classList.add('hidden');
|
|
|
|
// Load background video
|
|
tryLoadBgVideo();
|
|
|
|
// Auto-play music
|
|
setTimeout(() => {
|
|
bgMusic.play().then(() => {
|
|
playPauseIcon.className = 'fas fa-pause';
|
|
musicStatus.textContent = 'Now Playing';
|
|
musicIcon.classList.remove('paused');
|
|
isPlaying = true;
|
|
}).catch(err => {
|
|
console.error('Auto-play blocked:', err);
|
|
musicStatus.textContent = 'Click play';
|
|
});
|
|
}, 500);
|
|
});
|
|
|
|
async function tryLoadBgVideo() {
|
|
for (const name of ['backgroundVideo.mp4', 'backgroundVideo.webm']) {
|
|
try {
|
|
const res = await fetch(name);
|
|
if (res.ok) {
|
|
const url = URL.createObjectURL(await res.blob());
|
|
bgVideo.src = url;
|
|
bgVideo.load();
|
|
bgVideo.play().catch(() => {});
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
// ── Audio Visualizer ──────────────────────────────────────────────
|
|
const vizCanvas = document.getElementById('vizCanvas');
|
|
const vizCtx = vizCanvas.getContext('2d');
|
|
const vizIdle = document.getElementById('vizIdleMsg');
|
|
|
|
let audioCtx, analyser, source, vizAnimId;
|
|
let vizConnected = false;
|
|
|
|
function setupAnalyser() {
|
|
if (vizConnected) return;
|
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
analyser = audioCtx.createAnalyser();
|
|
analyser.fftSize = 256;
|
|
analyser.smoothingTimeConstant = 0.82;
|
|
source = audioCtx.createMediaElementSource(bgMusic);
|
|
source.connect(analyser);
|
|
analyser.connect(audioCtx.destination);
|
|
vizConnected = true;
|
|
}
|
|
|
|
function resizeCanvas() {
|
|
vizCanvas.width = vizCanvas.offsetWidth * devicePixelRatio;
|
|
vizCanvas.height = vizCanvas.offsetHeight * devicePixelRatio;
|
|
}
|
|
|
|
function drawViz() {
|
|
vizAnimId = requestAnimationFrame(drawViz);
|
|
resizeCanvas();
|
|
|
|
const W = vizCanvas.width, H = vizCanvas.height;
|
|
const bufLen = analyser.frequencyBinCount;
|
|
const data = new Uint8Array(bufLen);
|
|
analyser.getByteFrequencyData(data);
|
|
|
|
// Clear with a slight fade trail for smoothness
|
|
vizCtx.clearRect(0, 0, W, H);
|
|
|
|
const barCount = 20;
|
|
const gap = W * 0.012;
|
|
const barW = (W - gap * (barCount + 1)) / barCount;
|
|
const step = Math.floor(bufLen / barCount);
|
|
const maxH = H * 0.82;
|
|
const baseY = H * 0.92;
|
|
|
|
for (let i = 0; i < barCount; i++) {
|
|
// Average a small bucket for smoother freq representation
|
|
let sum = 0;
|
|
for (let k = 0; k < step; k++) sum += data[i * step + k];
|
|
const avg = sum / step;
|
|
const ratio = avg / 255;
|
|
|
|
const bh = ratio * maxH;
|
|
const x = gap + i * (barW + gap);
|
|
const y = baseY - bh;
|
|
|
|
// Bar opacity and glow scale with amplitude
|
|
const alpha = 0.25 + ratio * 0.75;
|
|
|
|
// Subtle vertical gradient: bright top → dimmer base
|
|
const grad = vizCtx.createLinearGradient(0, y, 0, baseY);
|
|
grad.addColorStop(0, `rgba(255,255,255,${alpha})`);
|
|
grad.addColorStop(0.6, `rgba(220,220,220,${alpha * 0.6})`);
|
|
grad.addColorStop(1, `rgba(180,180,180,${alpha * 0.15})`);
|
|
|
|
vizCtx.fillStyle = grad;
|
|
|
|
// Rounded top on the bar
|
|
const radius = Math.min(barW / 2, 4);
|
|
vizCtx.beginPath();
|
|
vizCtx.moveTo(x + radius, y);
|
|
vizCtx.lineTo(x + barW - radius, y);
|
|
vizCtx.quadraticCurveTo(x + barW, y, x + barW, y + radius);
|
|
vizCtx.lineTo(x + barW, baseY);
|
|
vizCtx.lineTo(x, baseY);
|
|
vizCtx.lineTo(x, y + radius);
|
|
vizCtx.quadraticCurveTo(x, y, x + radius, y);
|
|
vizCtx.fill();
|
|
|
|
// Subtle glow on loud bars
|
|
if (ratio > 0.5) {
|
|
vizCtx.save();
|
|
vizCtx.shadowColor = `rgba(255,255,255,${(ratio - 0.5) * 0.6})`;
|
|
vizCtx.shadowBlur = 8 * ratio;
|
|
vizCtx.fill();
|
|
vizCtx.restore();
|
|
}
|
|
|
|
// Floating dot cap above bar
|
|
if (bh > 4) {
|
|
vizCtx.fillStyle = `rgba(255,255,255,${alpha * 0.9})`;
|
|
vizCtx.fillRect(x, y - 3, barW, 2);
|
|
}
|
|
}
|
|
|
|
// Baseline rule
|
|
vizCtx.strokeStyle = 'rgba(255,255,255,0.08)';
|
|
vizCtx.lineWidth = 1;
|
|
vizCtx.beginPath();
|
|
vizCtx.moveTo(0, baseY);
|
|
vizCtx.lineTo(W, baseY);
|
|
vizCtx.stroke();
|
|
}
|
|
|
|
function startViz() {
|
|
setupAnalyser();
|
|
if (audioCtx.state === 'suspended') audioCtx.resume();
|
|
vizIdle.style.opacity = '0';
|
|
cancelAnimationFrame(vizAnimId);
|
|
drawViz();
|
|
}
|
|
|
|
function stopViz() {
|
|
cancelAnimationFrame(vizAnimId);
|
|
// Draw flat line when paused
|
|
resizeCanvas();
|
|
const W = vizCanvas.width, H = vizCanvas.height;
|
|
vizCtx.clearRect(0, 0, W, H);
|
|
vizCtx.strokeStyle = 'rgba(255,255,255,0.1)';
|
|
vizCtx.lineWidth = 1;
|
|
vizCtx.beginPath();
|
|
vizCtx.moveTo(0, H * 0.92);
|
|
vizCtx.lineTo(W, H * 0.92);
|
|
vizCtx.stroke();
|
|
vizIdle.style.opacity = '1';
|
|
}
|
|
|
|
// Hook into existing play/pause logic
|
|
bgMusic.addEventListener('play', () => startViz());
|
|
bgMusic.addEventListener('pause', () => stopViz());
|
|
bgMusic.addEventListener('ended', () => stopViz());
|
|
</script>
|
|
</body>
|
|
</html>
|