{"project":{"id":"qF3T8PM","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Brutalist Audio Player","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link href=\"https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Outfit:wght@300;500;700&display=swap\" rel=\"stylesheet\">\n<script src=\"https://unpkg.com/lucide@latest\"></script>\n</head>\n<body>\n<div class=\"player\">\n\t<div class=\"player__noise\"></div>\n\n\t<div class=\"player__artwork\">\n\t\t<img src=\"https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=400&h=400&fit=crop&q=80\" alt=\"Album Art\" class=\"player__artwork-img\" id=\"albumArt\">\n\t\t<div class=\"player__vinyl\" id=\"vinyl\">\n\t\t\t<div class=\"player__vinyl-groove\"></div>\n\t\t\t<div class=\"player__vinyl-groove player__vinyl-groove--2\"></div>\n\t\t\t<div class=\"player__vinyl-groove player__vinyl-groove--3\"></div>\n\t\t\t<div class=\"player__vinyl-label\"></div>\n\t\t</div>\n\t</div>\n\n\t<div class=\"player__info\">\n\t\t<div class=\"player__tag\">NOW PLAYING</div>\n\t\t<h1 class=\"player__title\" id=\"trackTitle\">Midnight Drive</h1>\n\t\t<p class=\"player__artist\" id=\"trackArtist\">Neon Pulse</p>\n\t</div>\n\n\t<div class=\"player__waveform\" id=\"waveformContainer\">\n\t\t<canvas id=\"waveform\" width=\"600\" height=\"80\"></canvas>\n\t\t<div class=\"player__progress-overlay\" id=\"progressOverlay\"></div>\n\t</div>\n\n\t<div class=\"player__time\">\n\t\t<span id=\"currentTime\">0:00</span>\n\t\t<span id=\"duration\">0:00</span>\n\t</div>\n\n\t<div class=\"player__controls\">\n\t\t<button class=\"player__btn player__btn--secondary\" id=\"prevBtn\" aria-label=\"Previous track\">\n      <i data-lucide=\"skip-back\"></i>\n    </button>\n\t\t<button class=\"player__btn player__btn--play\" id=\"playBtn\" aria-label=\"Play\">\n      <i data-lucide=\"play\" id=\"playIcon\"></i>\n    </button>\n\t\t<button class=\"player__btn player__btn--secondary\" id=\"nextBtn\" aria-label=\"Next track\">\n      <i data-lucide=\"skip-forward\"></i>\n    </button>\n\t</div>\n\n\t<div class=\"player__extras\">\n\t\t<div class=\"player__volume\">\n\t\t\t<button class=\"player__btn player__btn--icon\" id=\"muteBtn\" aria-label=\"Mute\">\n        <i data-lucide=\"volume-2\" id=\"volumeIcon\"></i>\n      </button>\n\t\t\t<input type=\"range\" min=\"0\" max=\"100\" value=\"75\" class=\"player__slider player__slider--volume\" id=\"volumeSlider\" aria-label=\"Volume\">\n\t\t</div>\n\t\t<div class=\"player__actions\">\n\t\t\t<button class=\"player__btn player__btn--icon\" id=\"shuffleBtn\" aria-label=\"Shuffle\">\n        <i data-lucide=\"shuffle\"></i>\n      </button>\n\t\t\t<button class=\"player__btn player__btn--icon\" id=\"repeatBtn\" aria-label=\"Repeat\">\n        <i data-lucide=\"repeat\"></i>\n      </button>\n\t\t</div>\n\t</div>\n\n\t<div class=\"player__playlist\" id=\"playlist\">\n\t\t<div class=\"player__playlist-header\">\n\t\t\t<span class=\"player__tag\">PLAYLIST</span>\n\t\t\t<span class=\"player__track-count\" id=\"trackCount\">4 tracks</span>\n\t\t</div>\n\t\t<ul class=\"player__tracklist\" id=\"tracklist\"></ul>\n\t</div>\n</div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"// ─── Track Data (Free sample audio from samplelib.com) ───\nconst tracks = [{\n\t\ttitle: 'Midnight Drive',\n\t\tartist: 'Neon Pulse',\n\t\tsrc: 'https://samplelib.com/lib/preview/mp3/sample-15s.mp3',\n\t\tart: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=400&h=400&fit=crop&q=80'\n\t},\n\t{\n\t\ttitle: 'Cinematic Chill',\n\t\tartist: 'Ambient Collective',\n\t\tsrc: 'https://samplelib.com/lib/preview/mp3/sample-12s.mp3',\n\t\tart: 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=400&h=400&fit=crop&q=80'\n\t},\n\t{\n\t\ttitle: 'Deep Urban',\n\t\tartist: 'BeatMakers',\n\t\tsrc: 'https://samplelib.com/lib/preview/mp3/sample-9s.mp3',\n\t\tart: 'https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=400&h=400&fit=crop&q=80'\n\t},\n\t{\n\t\ttitle: 'Lofi Vibes',\n\t\tartist: 'ChillHop Studio',\n\t\tsrc: 'https://samplelib.com/lib/preview/mp3/sample-6s.mp3',\n\t\tart: 'https://images.unsplash.com/photo-1459749411175-04bf5292ceea?w=400&h=400&fit=crop&q=80'\n\t}\n];\n\n// ─── State ───\nlet currentTrack = 0;\nlet isPlaying = false;\nlet isShuffle = false;\nlet repeatMode = 0; // 0=off, 1=all, 2=one\nlet isMuted = false;\nlet prevVolume = 0.75;\n\n// ─── Audio Element ───\nconst audio = new Audio();\naudio.preload = 'auto';\naudio.volume = 0.75;\n\n// ─── DOM refs ───\nconst player = document.querySelector('.player');\nconst playBtn = document.getElementById('playBtn');\nconst playIcon = document.getElementById('playIcon');\nconst prevBtn = document.getElementById('prevBtn');\nconst nextBtn = document.getElementById('nextBtn');\nconst trackTitle = document.getElementById('trackTitle');\nconst trackArtist = document.getElementById('trackArtist');\nconst albumArt = document.getElementById('albumArt');\nconst currentTimeEl = document.getElementById('currentTime');\nconst durationEl = document.getElementById('duration');\nconst volumeSlider = document.getElementById('volumeSlider');\nconst muteBtn = document.getElementById('muteBtn');\nconst volumeIcon = document.getElementById('volumeIcon');\nconst shuffleBtn = document.getElementById('shuffleBtn');\nconst repeatBtn = document.getElementById('repeatBtn');\nconst tracklist = document.getElementById('tracklist');\nconst waveformContainer = document.getElementById('waveformContainer');\nconst canvas = document.getElementById('waveform');\nconst ctx = canvas.getContext('2d');\nconst progressOverlay = document.getElementById('progressOverlay');\n\n// ─── Generate waveform bars for visual ───\nconst waveformData = tracks.map(() => {\n\tconst bars = 80;\n\tconst data = [];\n\tfor (let i = 0; i < bars; i++) {\n\t\tconst base = 0.15 + Math.random() * 0.55;\n\t\tconst wave = Math.sin((i / bars) * Math.PI) * 0.3;\n\t\tdata.push(Math.min(1, base + wave));\n\t}\n\treturn data;\n});\n\nfunction drawWaveform(trackIndex, progress = 0) {\n\tconst dpr = window.devicePixelRatio || 1;\n\tconst rect = canvas.getBoundingClientRect();\n\tcanvas.width = rect.width * dpr;\n\tcanvas.height = rect.height * dpr;\n\tctx.scale(dpr, dpr);\n\n\tconst data = waveformData[trackIndex];\n\tconst w = rect.width;\n\tconst h = rect.height;\n\tctx.clearRect(0, 0, w, h);\n\tconst barW = w / data.length;\n\tconst gap = 1.5;\n\n\tdata.forEach((val, i) => {\n\t\tconst barH = val * h * 0.85;\n\t\tconst x = i * barW;\n\t\tconst y = (h - barH) / 2;\n\t\tconst pct = i / data.length;\n\n\t\tif (pct <= progress) {\n\t\t\tctx.fillStyle = '#e8e4d9';\n\t\t} else {\n\t\t\tctx.fillStyle = 'rgba(232,228,217,0.2)';\n\t\t}\n\t\tctx.fillRect(x + gap / 2, y, barW - gap, barH);\n\t});\n}\n\nfunction formatTime(seconds) {\n\tif (isNaN(seconds) || !isFinite(seconds)) return '0:00';\n\tconst m = Math.floor(seconds / 60);\n\tconst s = Math.floor(seconds % 60);\n\treturn `${m}:${s.toString().padStart(2, '0')}`;\n}\n\n// ─── Update playing state UI ───\nfunction updatePlayingUI(playing) {\n\tisPlaying = playing;\n\tif (playing) {\n\t\tplayer.classList.add('player--playing');\n\t\tplayIcon.setAttribute('data-lucide', 'pause');\n\t} else {\n\t\tplayer.classList.remove('player--playing');\n\t\tplayIcon.setAttribute('data-lucide', 'play');\n\t}\n\tlucide.createIcons();\n}\n\n// ─── Highlight active track in playlist ───\nfunction highlightTrack(index) {\n\tdocument.querySelectorAll('.player__track').forEach((el, i) => {\n\t\tif (i === index) {\n\t\t\tel.classList.add('active');\n\t\t} else {\n\t\t\tel.classList.remove('active');\n\t\t}\n\t});\n}\n\n// ─── Load Track ───\nfunction loadTrack(index, autoplay = false) {\n\tcurrentTrack = index;\n\tconst track = tracks[index];\n\ttrackTitle.textContent = track.title;\n\ttrackArtist.textContent = track.artist;\n\talbumArt.src = track.art;\n\n\taudio.src = track.src;\n\taudio.load();\n\n\tdrawWaveform(index, 0);\n\tprogressOverlay.style.width = '0%';\n\tcurrentTimeEl.textContent = '0:00';\n\tdurationEl.textContent = '0:00';\n\n\thighlightTrack(index);\n\n\tif (autoplay) {\n\t\tconst playPromise = audio.play();\n\t\tif (playPromise !== undefined) {\n\t\t\tplayPromise.catch((err) => {\n\t\t\t\tconsole.log('Autoplay blocked:', err);\n\t\t\t});\n\t\t}\n\t}\n}\n\n// ─── Play / Pause ───\nfunction togglePlay() {\n\tif (isPlaying) {\n\t\taudio.pause();\n\t} else {\n\t\tconst playPromise = audio.play();\n\t\tif (playPromise !== undefined) {\n\t\t\tplayPromise.catch((err) => {\n\t\t\t\tconsole.log('Play failed:', err);\n\t\t\t});\n\t\t}\n\t}\n}\n\naudio.addEventListener('play', () => updatePlayingUI(true));\naudio.addEventListener('pause', () => updatePlayingUI(false));\n\n// ─── Time Updates ───\naudio.addEventListener('loadedmetadata', () => {\n\tdurationEl.textContent = formatTime(audio.duration);\n});\n\naudio.addEventListener('durationchange', () => {\n\tdurationEl.textContent = formatTime(audio.duration);\n});\n\naudio.addEventListener('timeupdate', () => {\n\tconst progress = audio.duration ? audio.currentTime / audio.duration : 0;\n\tcurrentTimeEl.textContent = formatTime(audio.currentTime);\n\tprogressOverlay.style.width = `${progress * 100}%`;\n\tdrawWaveform(currentTrack, progress);\n});\n\n// ─── Track End ───\naudio.addEventListener('ended', () => {\n\tif (repeatMode === 2) {\n\t\taudio.currentTime = 0;\n\t\taudio.play();\n\t} else if (repeatMode === 1) {\n\t\tplayNext();\n\t} else {\n\t\t// repeatMode 0 — play next, stop at end of list\n\t\tconst next = currentTrack + 1;\n\t\tif (next < tracks.length || isShuffle) {\n\t\t\tplayNext();\n\t\t} else {\n\t\t\tloadTrack(0, false);\n\t\t\tupdatePlayingUI(false);\n\t\t}\n\t}\n});\n\n// ─── Error handling ───\naudio.addEventListener('error', (e) => {\n\tconsole.error('Audio error:', audio.error);\n\t// Try next track on error\n\tconst next = (currentTrack + 1) % tracks.length;\n\tif (next !== currentTrack) {\n\t\tloadTrack(next, isPlaying);\n\t}\n});\n\n// ─── Seek via waveform click ───\nwaveformContainer.addEventListener('click', (e) => {\n\tif (!audio.duration) return;\n\tconst rect = waveformContainer.getBoundingClientRect();\n\tconst pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n\taudio.currentTime = pct * audio.duration;\n});\n\n// ─── Controls ───\nplayBtn.addEventListener('click', togglePlay);\n\nprevBtn.addEventListener('click', () => {\n\tif (audio.currentTime > 3) {\n\t\taudio.currentTime = 0;\n\t} else {\n\t\tconst prev = (currentTrack - 1 + tracks.length) % tracks.length;\n\t\tloadTrack(prev, isPlaying);\n\t}\n});\n\nnextBtn.addEventListener('click', () => playNext());\n\nfunction playNext() {\n\tlet next;\n\tif (isShuffle) {\n\t\tdo {\n\t\t\tnext = Math.floor(Math.random() * tracks.length);\n\t\t} while (next === currentTrack && tracks.length > 1);\n\t} else {\n\t\tnext = (currentTrack + 1) % tracks.length;\n\t}\n\tloadTrack(next, true);\n}\n\n// ─── Volume ───\nvolumeSlider.addEventListener('input', (e) => {\n\tconst val = e.target.value / 100;\n\taudio.volume = val;\n\tisMuted = val === 0;\n\tupdateVolumeIcon(val);\n});\n\nmuteBtn.addEventListener('click', () => {\n\tisMuted = !isMuted;\n\tif (isMuted) {\n\t\tprevVolume = audio.volume;\n\t\taudio.volume = 0;\n\t\tvolumeSlider.value = 0;\n\t\tupdateVolumeIcon(0);\n\t} else {\n\t\taudio.volume = prevVolume;\n\t\tvolumeSlider.value = prevVolume * 100;\n\t\tupdateVolumeIcon(prevVolume);\n\t}\n});\n\nfunction updateVolumeIcon(val) {\n\tlet icon = 'volume-2';\n\tif (val === 0) icon = 'volume-x';\n\telse if (val < 0.4) icon = 'volume-1';\n\tvolumeIcon.setAttribute('data-lucide', icon);\n\tlucide.createIcons();\n}\n\n// ─── Shuffle & Repeat ───\nshuffleBtn.addEventListener('click', () => {\n\tisShuffle = !isShuffle;\n\tshuffleBtn.classList.toggle('active', isShuffle);\n});\n\nrepeatBtn.addEventListener('click', () => {\n\trepeatMode = (repeatMode + 1) % 3;\n\trepeatBtn.classList.toggle('active', repeatMode > 0);\n\tconst icons = ['repeat', 'repeat', 'repeat-1'];\n\trepeatBtn.querySelector('i').setAttribute('data-lucide', icons[repeatMode]);\n\tlucide.createIcons();\n});\n\n// ─── Build Playlist ───\nfunction buildPlaylist() {\n\ttracklist.innerHTML = '';\n\ttracks.forEach((track, i) => {\n\t\tconst li = document.createElement('li');\n\t\tli.className = `player__track${i === 0 ? ' active' : ''}`;\n\t\tli.innerHTML = `\n      <span class=\"player__track-num\"><span>${String(i + 1).padStart(2, '0')}</span></span>\n      <div class=\"player__track-info\">\n        <span class=\"player__track-name\">${track.title}</span>\n        <span class=\"player__track-dur\">${track.artist}</span>\n      </div>\n    `;\n\t\tli.addEventListener('click', () => {\n\t\t\tloadTrack(i, true);\n\t\t});\n\t\ttracklist.appendChild(li);\n\t});\n}\n\n// ─── Keyboard Shortcuts ───\ndocument.addEventListener('keydown', (e) => {\n\tif (e.target.tagName === 'INPUT') return;\n\tswitch (e.code) {\n\t\tcase 'Space':\n\t\t\te.preventDefault();\n\t\t\ttogglePlay();\n\t\t\tbreak;\n\t\tcase 'ArrowRight':\n\t\t\tif (audio.duration) audio.currentTime = Math.min(audio.duration, audio.currentTime + 5);\n\t\t\tbreak;\n\t\tcase 'ArrowLeft':\n\t\t\taudio.currentTime = Math.max(0, audio.currentTime - 5);\n\t\t\tbreak;\n\t\tcase 'ArrowUp':\n\t\t\te.preventDefault();\n\t\t\taudio.volume = Math.min(1, audio.volume + 0.05);\n\t\t\tvolumeSlider.value = audio.volume * 100;\n\t\t\tupdateVolumeIcon(audio.volume);\n\t\t\tbreak;\n\t\tcase 'ArrowDown':\n\t\t\te.preventDefault();\n\t\t\taudio.volume = Math.max(0, audio.volume - 0.05);\n\t\t\tvolumeSlider.value = audio.volume * 100;\n\t\t\tupdateVolumeIcon(audio.volume);\n\t\t\tbreak;\n\t}\n});\n\n// ─── Init ───\nbuildPlaylist();\nloadTrack(0, false);\nlucide.createIcons();\n\n// Initial waveform draw after a tick\nsetTimeout(() => drawWaveform(0, 0), 100);"},{"name":"style.css","content":":root {\n\t--bg: #0a0a0a;\n\t--surface: #141414;\n\t--surface-2: #1e1e1e;\n\t--text: #e8e4dd;\n\t--text-dim: #958e88;\n\t--accent: #e8440a;\n\t--accent-glow: rgba(232, 68, 10, 0.3);\n\t--accent-soft: #ff6b35;\n\t--border: #2a2520;\n\t--radius: 6px;\n\t--font-display: 'Space Mono', monospace;\n\t--font-body: 'Outfit', sans-serif;\n}\n\n* {\n\tmargin: 0;\n\tpadding: 0;\n\tbox-sizing: border-box;\n}\n\nbody {\n\tbackground: var(--bg);\n\tcolor: var(--text);\n\tfont-family: var(--font-body);\n\tmin-height: 100vh;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\nbody::before {\n\tcontent: '';\n\tposition: fixed;\n\tinset: 0;\n\tbackground:\n\t\tradial-gradient(ellipse at 20% 50%, rgba(232, 68, 10, 0.06) 0%, transparent 60%),\n\t\tradial-gradient(ellipse at 80% 80%, rgba(232, 68, 10, 0.03) 0%, transparent 50%);\n\tpointer-events: none;\n}\n\n.player {\n\tposition: relative;\n\twidth: 420px;\n\tbackground: var(--surface);\n\tborder: 1px solid var(--border);\n\tborder-radius: 16px;\n\tpadding: 28px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 20px;\n\toverflow: hidden;\n\tbox-shadow:\n\t\t0 40px 80px rgba(0, 0, 0, 0.6),\n\t\t0 0 0 1px rgba(255, 255, 255, 0.03) inset;\n}\n\n.player__noise {\n\tposition: absolute;\n\tinset: 0;\n\tbackground-image: url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E\");\n\tpointer-events: none;\n\tz-index: 0;\n\tborder-radius: 16px;\n}\n\n.player>* {\n\tposition: relative;\n\tz-index: 1;\n}\n\n/* Artwork */\n.player__artwork {\n\tposition: relative;\n\twidth: 100%;\n\taspect-ratio: 1;\n\tborder-radius: 12px;\n\toverflow: hidden;\n\tbackground: var(--bg);\n}\n\n.player__artwork-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\ttransition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s;\n}\n\n.player--playing .player__artwork-img {\n\ttransform: scale(1.05);\n}\n\n.player__vinyl {\n\tposition: absolute;\n\ttop: 50%;\n\tright: -30%;\n\ttransform: translateY(-50%);\n\twidth: 70%;\n\taspect-ratio: 1;\n\tborder-radius: 50%;\n\tbackground: #111;\n\topacity: 0;\n\ttransition: right 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s;\n\tbox-shadow: 0 0 40px rgba(0, 0, 0, 0.8);\n}\n\n.player--playing .player__vinyl {\n\tright: -20%;\n\topacity: 1;\n\tanimation: spin 4s linear infinite;\n}\n\n@keyframes spin {\n\tto {\n\t\ttransform: translateY(-50%) rotate(360deg);\n\t}\n}\n\n.player__vinyl-groove {\n\tposition: absolute;\n\tborder-radius: 50%;\n\tborder: 1px solid rgba(255, 255, 255, 0.05);\n}\n\n.player__vinyl-groove:first-of-type {\n\tinset: 15%;\n}\n\n.player__vinyl-groove--2 {\n\tinset: 25%;\n}\n\n.player__vinyl-groove--3 {\n\tinset: 35%;\n}\n\n.player__vinyl-label {\n\tposition: absolute;\n\tinset: 38%;\n\tborder-radius: 50%;\n\tbackground: var(--accent);\n\tbox-shadow: 0 0 20px var(--accent-glow);\n}\n\n/* Info */\n.player__info {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.player__tag {\n\tfont-family: var(--font-display);\n\tfont-size: 10px;\n\tletter-spacing: 3px;\n\tcolor: var(--accent);\n\tfont-weight: 700;\n}\n\n.player__title {\n\tfont-family: var(--font-display);\n\tfont-size: 22px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tcolor: var(--text);\n}\n\n.player__artist {\n\tfont-size: 14px;\n\tfont-weight: 300;\n\tcolor: var(--text-dim);\n}\n\n/* Waveform */\n.player__waveform {\n\tposition: relative;\n\twidth: 100%;\n\theight: 50px;\n\tcursor: pointer;\n\tborder-radius: var(--radius);\n\toverflow: hidden;\n}\n\n#waveform {\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: block;\n}\n\n.player__progress-overlay {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\theight: 100%;\n\twidth: 0%;\n\tbackground: linear-gradient(90deg, var(--accent), var(--accent-soft));\n\tmix-blend-mode: color;\n\tpointer-events: none;\n\ttransition: width 0.1s linear;\n}\n\n/* Time */\n.player__time {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tfont-family: var(--font-display);\n\tfont-size: 11px;\n\tcolor: var(--text-dim);\n\tmargin-top: -12px;\n}\n\n/* Controls */\n.player__controls {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 16px;\n}\n\n.player__btn {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--text);\n\tcursor: pointer;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttransition: all 0.2s;\n}\n\n.player__btn:hover {\n\tcolor: var(--accent);\n}\n\n.player__btn--secondary {\n\twidth: 44px;\n\theight: 44px;\n\tborder-radius: 50%;\n\tbackground: var(--surface-2);\n\tborder: 1px solid var(--border);\n}\n\n.player__btn--secondary:hover {\n\tborder-color: var(--accent);\n\tbox-shadow: 0 0 16px var(--accent-glow);\n}\n\n.player__btn--secondary svg {\n\twidth: 18px;\n\theight: 18px;\n}\n\n.player__btn--play {\n\twidth: 64px;\n\theight: 64px;\n\tborder-radius: 50%;\n\tbackground: var(--accent);\n\tcolor: #fff;\n\tborder: none;\n\tbox-shadow: 0 4px 24px var(--accent-glow);\n\ttransition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.player__btn--play:hover {\n\ttransform: scale(1.08);\n\tbox-shadow: 0 6px 32px rgba(232, 68, 10, 0.5);\n\tcolor: #fff;\n}\n\n.player__btn--play:active {\n\ttransform: scale(0.95);\n}\n\n.player__btn--play svg {\n\twidth: 24px;\n\theight: 24px;\n}\n\n.player__btn--icon {\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 6px;\n\tcolor: var(--text-dim);\n}\n\n.player__btn--icon:hover {\n\tcolor: var(--accent);\n\tbackground: var(--surface-2);\n}\n\n.player__btn--icon.active {\n\tcolor: var(--accent);\n}\n\n.player__btn--icon svg {\n\twidth: 16px;\n\theight: 16px;\n}\n\n/* Extras */\n.player__extras {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n}\n\n.player__volume {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.player__actions {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n/* Range slider */\n.player__slider {\n\t-webkit-appearance: none;\n\tappearance: none;\n\theight: 4px;\n\tborder-radius: 2px;\n\tbackground: var(--surface-2);\n\toutline: none;\n\twidth: 100px;\n}\n\n.player__slider::-webkit-slider-thumb {\n\t-webkit-appearance: none;\n\twidth: 14px;\n\theight: 14px;\n\tborder-radius: 50%;\n\tbackground: var(--accent);\n\tcursor: pointer;\n\tbox-shadow: 0 0 8px var(--accent-glow);\n\ttransition: transform 0.15s;\n}\n\n.player__slider::-webkit-slider-thumb:hover {\n\ttransform: scale(1.3);\n}\n\n.player__slider::-moz-range-thumb {\n\twidth: 14px;\n\theight: 14px;\n\tborder-radius: 50%;\n\tbackground: var(--accent);\n\tcursor: pointer;\n\tborder: none;\n}\n\n/* Playlist */\n.player__playlist-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 10px;\n}\n\n.player__track-count {\n\tfont-size: 11px;\n\tcolor: var(--text-dim);\n\tfont-family: var(--font-display);\n}\n\n.player__tracklist {\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.player__track {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 10px 12px;\n\tborder-radius: var(--radius);\n\tcursor: pointer;\n\ttransition: all 0.2s;\n\tfont-size: 13px;\n}\n\n.player__track:hover {\n\tbackground: var(--surface-2);\n}\n\n.player__track.active {\n\tbackground: var(--surface-2);\n\tborder-left: 2px solid var(--accent);\n}\n\n.player__track-num {\n\tfont-family: var(--font-display);\n\tfont-size: 10px;\n\tcolor: var(--text-dim);\n\twidth: 20px;\n\ttext-align: center;\n}\n\n.player__track.active .player__track-num {\n\tcolor: var(--accent);\n}\n\n.player__track-info {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.player__track-name {\n\tfont-weight: 500;\n\tcolor: var(--text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.player__track.active .player__track-name {\n\tcolor: var(--accent-soft);\n}\n\n.player__track-dur {\n\tfont-family: var(--font-display);\n\tfont-size: 10px;\n\tcolor: var(--text-dim);\n}\n\n/* Pulse animation for playing track */\n.player__track.active .player__track-num::before {\n\tcontent: '';\n\tdisplay: inline-block;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--accent);\n\tanimation: pulse 1.5s ease-in-out infinite;\n}\n\n.player__track.active .player__track-num span {\n\tdisplay: none;\n}\n\n@keyframes pulse {\n\t0%, 100% {\n\t\topacity: 1;\n\t\ttransform: scale(1);\n\t}\n\n\t50% {\n\t\topacity: 0.4;\n\t\ttransform: scale(0.7);\n\t}\n}\n\n/* Responsive */\n@media (max-width: 480px) {\n\t.player {\n\t\twidth: 100%;\n\t\tborder-radius: 0;\n\t\tmin-height: 100vh;\n\t\tjustify-content: center;\n\t}\n}"}],"folders":[]},"variants":null,"createdAt":"2026-03-26T22:38:08.170Z","updatedAt":"2026-03-26T22:38:12.101Z","hasThumbnail":true}}