diff --git a/assets/css/style.css b/assets/css/style.css
index 943c3b3..f98e0f8 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -1,607 +1,455 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
+
:root {
- --primary-color: #3498db;
- --accent-color: #2ecc71;
- --light-bg: #ecf0f1;
- --light-surface: #ffffff;
- --light-text: #2c3e50;
- --dark-bg: #2c3e50;
- --dark-surface: #34495e;
- --dark-text: #ecf0f1;
- --font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ --primary-color: #FF8C00; /* Vibrant Orange */
+ --dark-bg-1: #121212;
+ --dark-bg-2: #1E1E1E;
+ --dark-bg-3: #282828;
+ --dark-text-1: #FFFFFF;
+ --dark-text-2: #B3B3B3;
+ --dark-border: #404040;
+
+ --light-bg-1: #FFFFFF;
+ --light-bg-2: #F0F0F0;
+ --light-bg-3: #E0E0E0;
+ --light-text-1: #000000;
+ --light-text-2: #555555;
+ --light-border: #D1D1D1;
+
+ --font-family: 'Inter', sans-serif;
+ --shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ --border-radius: 12px;
+}
+
+/* Base Styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
}
body {
font-family: var(--font-family);
- margin: 0;
- padding: 0;
- overflow: hidden;
transition: background-color 0.3s, color 0.3s;
}
-body.light-theme {
- background-color: var(--light-bg);
- color: var(--light-text);
- --bg-color: var(--light-bg);
- --surface-color: var(--light-surface);
- --text-color: var(--light-text);
-}
-
-body.dark-theme {
- background-color: var(--dark-bg);
- color: var(--dark-text);
- --bg-color: var(--dark-bg);
- --surface-color: var(--dark-surface);
- --text-color: var(--dark-text);
-}
-
#app-container {
- display: flex;
- height: 100vh;
-}
-
-/* --- Sidebar --- */
-#sidebar {
- width: 350px;
- background-color: var(--surface-color);
display: flex;
flex-direction: column;
- border-right: 1px solid rgba(0,0,0,0.1);
- transition: background-color 0.3s;
+ height: 100vh;
+ overflow: hidden;
}
-#sidebar-header {
- padding: 20px;
+/* Dark Theme (Default) */
+body.dark-theme {
+ background-color: var(--dark-bg-1);
+ color: var(--dark-text-1);
+}
+
+.dark-theme #main-header, .dark-theme .modal-content {
+ background-color: var(--dark-bg-2);
+ border-bottom: 1px solid var(--dark-border);
+}
+
+.dark-theme #player-column, .dark-theme #stations-column {
+ background-color: var(--dark-bg-2);
+}
+
+.dark-theme .tab-content, .dark-theme #now-playing-card {
+ background-color: var(--dark-bg-1);
+}
+
+.dark-theme .control-button, .dark-theme input, .dark-theme select {
+ background-color: var(--dark-bg-3);
+ color: var(--dark-text-1);
+ border: 1px solid var(--dark-border);
+}
+
+.dark-theme .control-button:hover {
+ background-color: var(--primary-color);
+ color: var(--dark-text-1);
+}
+
+.dark-theme .tab-link {
+ color: var(--dark-text-2);
+}
+
+.dark-theme .tab-link.active {
+ color: var(--primary-color);
+ border-bottom: 2px solid var(--primary-color);
+}
+
+.dark-theme #station-list li:hover {
+ background-color: var(--dark-bg-3);
+}
+
+.dark-theme #station-list li.active {
+ background-color: var(--primary-color);
+ color: var(--dark-text-1);
+}
+
+/* Light Theme */
+body.light-theme {
+ background-color: var(--light-bg-2);
+ color: var(--light-text-1);
+}
+
+.light-theme #main-header, .light-theme .modal-content {
+ background-color: var(--light-bg-1);
+ border-bottom: 1px solid var(--light-border);
+}
+
+.light-theme #player-column, .light-theme #stations-column {
+ background-color: var(--light-bg-1);
+}
+
+.light-theme .tab-content, .light-theme #now-playing-card {
+ background-color: var(--light-bg-2);
+}
+
+.light-theme .control-button, .light-theme input, .light-theme select {
+ background-color: var(--light-bg-3);
+ color: var(--light-text-1);
+ border: 1px solid var(--light-border);
+}
+
+.light-theme .control-button:hover {
+ background-color: var(--primary-color);
+ color: var(--light-bg-1);
+}
+
+.light-theme .tab-link {
+ color: var(--light-text-2);
+}
+
+.light-theme .tab-link.active {
+ color: var(--primary-color);
+ border-bottom: 2px solid var(--primary-color);
+}
+
+.light-theme #station-list li:hover {
+ background-color: var(--light-bg-3);
+}
+
+.light-theme #station-list li.active {
+ background-color: var(--primary-color);
+ color: var(--light-bg-1);
+}
+
+/* Header */
+#main-header {
display: flex;
justify-content: space-between;
align-items: center;
- border-bottom: 1px solid rgba(0,0,0,0.1);
+ padding: 1rem 1.5rem;
}
-#sidebar-header h1 {
- margin: 0;
- font-size: 24px;
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.logo i {
color: var(--primary-color);
+ font-size: 1.5rem;
}
-/* Theme Switcher */
-.theme-switcher {
+.logo h1 {
+ font-size: 1.25rem;
+ font-weight: 700;
+}
+
+.header-controls {
display: flex;
- align-items: center;
- gap: 8px;
-}
-.switch {
- position: relative;
- display: inline-block;
- width: 40px;
- height: 20px;
-}
-.switch input { display: none; }
-.slider {
- position: absolute;
- cursor: pointer;
- top: 0; left: 0; right: 0; bottom: 0;
- background-color: #ccc;
- transition: .4s;
- border-radius: 20px;
-}
-.slider:before {
- position: absolute;
- content: "";
- height: 14px;
- width: 14px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: .4s;
- border-radius: 50%;
-}
-input:checked + .slider {
- background-color: var(--primary-color);
-}
-input:checked + .slider:before {
- transform: translateX(20px);
+ gap: 0.5rem;
}
-#sidebar-tabs {
- display: flex;
- border-bottom: 1px solid rgba(0,0,0,0.1);
-}
-
-.tab-link {
- flex: 1;
- padding: 15px;
- background: none;
- border: none;
- font-size: 16px;
- cursor: pointer;
- color: var(--text-color);
- opacity: 0.7;
- transition: opacity 0.2s, border-bottom 0.2s;
- border-bottom: 3px solid transparent;
-}
-
-.tab-link.active {
- opacity: 1;
- border-bottom: 3px solid var(--primary-color);
-}
-
-.tab-content {
- display: none;
- flex-grow: 1;
- overflow-y: auto;
- flex-direction: column;
-}
-
-.tab-content.active {
- display: flex;
-}
-
-.toolbar {
- padding: 15px;
- border-bottom: 1px solid rgba(0,0,0,0.1);
-}
-
-.search-bar {
- position: relative;
- margin-bottom: 10px;
-}
-
-.search-bar input {
- width: 100%;
- padding: 10px 10px 10px 35px;
- border-radius: 5px;
- border: 1px solid #ccc;
- background-color: var(--bg-color);
- color: var(--text-color);
- box-sizing: border-box;
-}
-
-.search-bar .fa-search {
- position: absolute;
- top: 12px;
- left: 12px;
- color: #aaa;
-}
-
-#genre-filter {
- width: 100%;
- padding: 10px;
- border-radius: 5px;
- border: 1px solid #ccc;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-#station-list, #discover-list {
- flex-grow: 1;
- overflow-y: auto;
- padding: 10px;
-}
-
-.station-item, .discover-item {
- display: flex;
- align-items: center;
- padding: 15px;
- border-radius: 5px;
- cursor: pointer;
- transition: background-color 0.2s;
- margin-bottom: 5px;
-}
-
-.station-item:hover, .discover-item:hover {
- background-color: rgba(0,0,0,0.1);
-}
-
-.station-item.playing {
- background-color: var(--primary-color);
- color: white;
-}
-
-.station-item-info {
- flex-grow: 1;
-}
-
-.station-item-info h3 {
- margin: 0;
- font-size: 16px;
-}
-
-.station-item-info p {
- margin: 0;
- font-size: 12px;
- opacity: 0.7;
-}
-
-.station-item-controls button, .discover-item-controls button {
- background: none;
- border: none;
- color: var(--text-color);
- cursor: pointer;
- font-size: 16px;
- margin-left: 10px;
- opacity: 0.7;
- transition: opacity 0.2s;
-}
-
-.station-item.playing .station-item-controls button {
- color: white;
-}
-
-.station-item-controls button:hover, .discover-item-controls button:hover {
- opacity: 1;
-}
-
-.sidebar-btn {
- padding: 15px;
- background-color: var(--accent-color);
- color: white;
- border: none;
- font-size: 16px;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-.sidebar-btn:hover {
- filter: brightness(1.1);
-}
-
-/* --- Main Content --- */
+/* Main Content */
#main-content {
- flex-grow: 1;
display: flex;
- flex-direction: column;
- background-color: var(--bg-color);
+ flex-grow: 1;
+ gap: 1rem;
+ padding: 1rem;
+ overflow: hidden;
}
-#visualizer-container {
- flex-grow: 1;
+#player-column, #stations-column {
+ border-radius: var(--border-radius);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+#player-column {
+ flex-basis: 35%;
+ min-width: 350px;
+}
+
+#stations-column {
+ flex-basis: 65%;
+}
+
+/* Player Column */
+#now-playing-card {
+ text-align: center;
+ padding: 1rem;
+ border-radius: var(--border-radius);
+}
+
+#album-art-container {
position: relative;
- background-color: #000;
+ width: 100%;
+ padding-top: 100%; /* 1:1 Aspect Ratio */
+ margin-bottom: 1.5rem;
+}
+
+#station-logo, #visualizer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: var(--border-radius);
+ object-fit: cover;
}
#visualizer {
- width: 100%;
- height: 100%;
-}
-
-#player-container {
- padding: 30px;
- background-color: var(--surface-color);
- border-top: 1px solid rgba(0,0,0,0.1);
- text-align: center;
-}
-
-#now-playing {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 20px;
-}
-
-#now-playing img {
- width: 80px;
- height: 80px;
- border-radius: 5px;
- margin-right: 20px;
-}
-
-#now-playing h2 {
- margin: 0;
- font-size: 28px;
-}
-
-#now-playing p {
- margin: 0;
- font-size: 16px;
+ z-index: 2;
opacity: 0.7;
}
+#station-info h2 {
+ font-size: 1.5rem;
+ margin-bottom: 0.25rem;
+}
+
+#station-info p {
+ font-size: 1rem;
+ margin-bottom: 0.5rem;
+}
+
+#live-indicator {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.9rem;
+}
+
+#live-indicator .dot {
+ width: 8px;
+ height: 8px;
+ background-color: var(--primary-color);
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 var(--primary-color); }
+ 70% { box-shadow: 0 0 0 10px rgba(255, 140, 0, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(255, 140, 0, 0); }
+}
+
#player-controls {
display: flex;
justify-content: center;
align-items: center;
- gap: 20px;
- margin-bottom: 20px;
+ gap: 1rem;
}
-#player-controls button {
- background: none;
- border: 1px solid var(--text-color);
- color: var(--text-color);
+.control-button {
border-radius: 50%;
- width: 60px;
- height: 60px;
- font-size: 24px;
+ width: 50px;
+ height: 50px;
+ font-size: 1.2rem;
cursor: pointer;
transition: all 0.2s;
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
-#player-controls button#play-pause-btn {
- background-color: var(--primary-color);
- border-color: var(--primary-color);
- color: white;
+.main-button {
width: 70px;
height: 70px;
+ font-size: 2rem;
+ background-color: var(--primary-color) !important;
+ color: white !important;
}
-#player-controls button:hover {
- transform: scale(1.1);
-}
-
-.volume-control {
+#volume-and-more {
display: flex;
align-items: center;
- justify-content: center;
- gap: 10px;
- width: 300px;
- margin: 0 auto;
+ gap: 1rem;
}
-.volume-control input[type="range"] {
+#volume-control {
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+#volume-slider {
+ width: 100%;
+ -webkit-appearance: none;
+ appearance: none;
+ height: 5px;
+ border-radius: 5px;
+ outline: none;
+}
+
+#volume-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--primary-color);
+ cursor: pointer;
+}
+
+#record-button.recording {
+ color: red;
+ animation: pulse-record 1s infinite;
+}
+
+@keyframes pulse-record {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+}
+
+/* Stations Column */
+.tabs {
+ display: flex;
+ border-bottom: 1px solid;
+}
+
+.tab-link {
+ padding: 0.75rem 1rem;
+ cursor: pointer;
+ border: none;
+ background: none;
+ font-size: 1rem;
+ font-weight: 600;
+}
+
+.tab-content {
+ display: none;
+ flex-grow: 1;
+ overflow-y: auto;
+}
+
+.tab-content.active {
+ display: flex;
+ flex-direction: column;
+}
+
+.station-list-header {
+ display: flex;
+ gap: 0.5rem;
+ padding: 1rem 0;
+}
+
+#search-input {
+ flex-grow: 1;
+ padding: 0.75rem;
+ border-radius: var(--border-radius);
+}
+
+#station-list, #discover-list, #genre-list {
+ list-style: none;
+ overflow-y: auto;
flex-grow: 1;
}
-#recommendations-container {
- padding: 20px;
- background-color: var(--bg-color);
- border-top: 1px solid rgba(0,0,0,0.1);
-}
-
-#recommendations-container h3 {
- margin-top: 0;
- text-align: center;
-}
-
-#recommendations-list {
- display: flex;
- gap: 15px;
- justify-content: center;
-}
-
-.rec-item {
- background-color: var(--surface-color);
- padding: 15px;
- border-radius: 5px;
- text-align: center;
+#station-list li, #discover-list li, #genre-list li {
+ padding: 1rem;
cursor: pointer;
- transition: transform 0.2s;
-}
-
-.rec-item:hover {
- transform: translateY(-5px);
-}
-
-/* --- App Controls --- */
-#app-controls {
- position: absolute;
- top: 20px;
- right: 20px;
+ border-radius: var(--border-radius);
+ margin-bottom: 0.5rem;
display: flex;
- gap: 10px;
+ align-items: center;
+ gap: 1rem;
}
-#app-controls button {
- background-color: var(--surface-color);
- border: 1px solid #ccc;
- border-radius: 50%;
+#station-list li img, #discover-list li img {
width: 40px;
height: 40px;
- font-size: 18px;
- cursor: pointer;
- color: var(--text-color);
- transition: all 0.2s;
+ border-radius: 50%;
+ object-fit: cover;
}
-#app-controls button:hover {
- background-color: var(--primary-color);
- color: white;
-}
-
-/* --- Modals --- */
+/* Modals */
.modal {
display: none;
position: fixed;
- z-index: 1000;
+ z-index: 100;
left: 0;
top: 0;
width: 100%;
height: 100%;
- background-color: rgba(0,0,0,0.5);
+ background-color: rgba(0,0,0,0.6);
justify-content: center;
align-items: center;
}
-.modal.active {
+.modal.show {
display: flex;
}
.modal-content {
- background-color: var(--surface-color);
- padding: 30px;
- border-radius: 10px;
width: 90%;
max-width: 500px;
- position: relative;
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
+ padding: 2rem;
+ border-radius: var(--border-radius);
+ box-shadow: var(--shadow);
}
-.close-btn {
- position: absolute;
- top: 15px;
- right: 15px;
- font-size: 24px;
- font-weight: bold;
+.close-button {
+ float: right;
+ font-size: 1.5rem;
cursor: pointer;
}
-#station-form label, .color-picker-section h3, #import-export-modal h3 {
- display: block;
- margin-bottom: 10px;
- font-weight: bold;
+.setting-item, #add-station-form > * {
+ margin-top: 1.5rem;
}
-#station-form input {
- width: 100%;
- padding: 10px;
- margin-bottom: 15px;
- border-radius: 5px;
- border: 1px solid #ccc;
- box-sizing: border-box;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-#station-form button, .modal-content button {
- width: 100%;
- padding: 12px;
- border: none;
- border-radius: 5px;
- background-color: var(--primary-color);
- color: white;
- font-size: 16px;
- cursor: pointer;
- transition: filter 0.2s;
-}
-
-#station-form button:hover, .modal-content button:hover {
- filter: brightness(1.1);
-}
-
-.color-picker-section .color-input {
+/* Equalizer */
+#eq-bands-container {
display: flex;
justify-content: space-between;
align-items: center;
- margin-bottom: 15px;
-}
-
-.color-picker-section input[type="color"] {
- width: 50px;
- height: 30px;
- border: none;
- padding: 0;
- cursor: pointer;
-}
-
-#reset-colors-btn {
- background-color: #95a5a6;
- margin-top: 10px;
-}
-
-#timer-options {
- display: flex;
- justify-content: space-around;
- margin: 20px 0;
-}
-
-#timer-options button {
- width: auto;
- padding: 10px 20px;
-}
-
-#timer-display {
- text-align: center;
- font-size: 18px;
- margin: 20px 0;
-}
-
-#cancel-timer-btn {
- background-color: #e74c3c;
-}
-
-#import-export-modal .import-section, #import-export-modal .export-section {
- margin-bottom: 20px;
-}
-
-#import-export-modal input[type="file"] {
- display: block;
- margin: 10px 0;
-}
-
-/* --- Equalizer --- */
-#equalizer-controls {
- display: flex;
- flex-direction: column;
- gap: 20px;
-}
-
-.eq-presets {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-#eq-presets-select {
- padding: 8px;
- border-radius: 5px;
- border: 1px solid #ccc;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-#eq-bands {
- display: flex;
- justify-content: space-around;
- align-items: flex-end;
- height: 200px;
- gap: 10px;
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 5px;
+ margin-top: 2rem;
}
.eq-band {
display: flex;
flex-direction: column;
align-items: center;
- flex-grow: 1;
+ gap: 0.5rem;
}
-.eq-band input[type="range"] {
- -webkit-appearance: none;
- appearance: none;
- width: 150px;
- height: 8px;
- transform: rotate(-90deg);
- transform-origin: 75px 75px;
- background: #ddd;
- border-radius: 5px;
- outline: none;
- opacity: 0.7;
- transition: opacity .2s;
+.eq-band input[type=range] {
+ -webkit-appearance: slider-vertical;
+ width: 8px;
+ height: 120px;
}
-.eq-band input[type="range"]:hover {
- opacity: 1;
+/* Scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
}
-
-.eq-band label {
- font-size: 12px;
- margin-top: 10px;
+::-webkit-scrollbar-track {
+ background: transparent;
}
-
-#reset-eq-btn {
- background-color: #95a5a6;
+::-webkit-scrollbar-thumb {
+ background: var(--dark-border);
+ border-radius: 4px;
}
-
-#player-controls button#record-btn {
- color: #e74c3c;
- border-color: #e74c3c;
+.light-theme ::-webkit-scrollbar-thumb {
+ background: var(--light-border);
}
-
-#player-controls button#record-btn.recording {
- background-color: #e74c3c;
- color: white;
- animation: pulse 1.5s infinite;
-}
-
-@keyframes pulse {
- 0% {
- box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.7);
- }
- 70% {
- box-shadow: 0 0 0 10px rgba(231, 76, 60, 0);
- }
- 100% {
- box-shadow: 0 0 0 0 rgba(231, 76, 60, 0);
- }
-}
\ No newline at end of file
diff --git a/assets/js/main.js b/assets/js/main.js
index 16910d1..47f65f4 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,275 +1,215 @@
-class RadioWaveApp {
+document.addEventListener('DOMContentLoaded', () => {
+ const app = new RadioWave();
+ app.init();
+});
+
+class RadioWave {
constructor() {
- this.audio = document.getElementById('audio-player');
this.stations = [];
this.currentStationIndex = -1;
- this.discoverStations = this.getDiscoverStations();
+ this.audio = new Audio();
+ this.audio.crossOrigin = 'anonymous';
+ this.isPlaying = false;
this.audioContext = null;
this.analyser = null;
- this.source = null;
- this.sleepTimer = null;
+ this.eqBands = [];
this.mediaRecorder = null;
this.recordedChunks = [];
- this.eqBands = [];
+ this.sleepTimer = null;
- this.loadStations();
- this.loadTheme();
- this.loadColors();
+ this.ui = {}; // To cache UI elements
+ }
+
+ init() {
this.initUI();
+ this.loadStations();
this.bindEvents();
- this.renderStations();
- this.renderDiscoverStations();
+ this.applyAccentColor(localStorage.getItem('accentColor') || '#FF8C00');
+ if (localStorage.getItem('theme') === 'light') {
+ document.body.classList.replace('dark-theme', 'light-theme');
+ this.ui.themeSwitcher.innerHTML = '';
+ }
+ this.populateGenres();
+ this.populateDiscoverStations();
}
initUI() {
- // Cache all UI elements
- this.ui = {
- stationList: document.getElementById('station-list'),
- discoverList: document.getElementById('discover-list'),
- playPauseBtn: document.getElementById('play-pause-btn'),
- nextBtn: document.getElementById('next-station-btn'),
- prevBtn: document.getElementById('prev-station-btn'),
- recordBtn: document.getElementById('record-btn'),
- volumeSlider: document.getElementById('volume-slider'),
- playerStationName: document.getElementById('player-station-name'),
- playerStationGenre: document.getElementById('player-station-genre'),
- stationArt: document.getElementById('station-art'),
- searchInput: document.getElementById('search-input'),
- genreFilter: document.getElementById('genre-filter'),
- themeToggle: document.getElementById('theme-toggle'),
- visualizer: document.getElementById('visualizer'),
- // Modals
- stationModal: document.getElementById('station-modal'),
- settingsModal: document.getElementById('settings-modal'),
- sleepTimerModal: document.getElementById('sleep-timer-modal'),
- importExportModal: document.getElementById('import-export-modal'),
- equalizerModal: document.getElementById('equalizer-modal'),
- // Modal Triggers
- addStationBtn: document.getElementById('add-station-btn'),
- settingsBtn: document.getElementById('settings-btn'),
- sleepTimerBtn: document.getElementById('sleep-timer-btn'),
- importExportBtn: document.getElementById('import-export-btn'),
- equalizerBtn: document.getElementById('equalizer-btn'),
- // Tabs
- sidebarTabs: document.querySelectorAll('.tab-link'),
- tabContents: document.querySelectorAll('.tab-content'),
- // Recommendations
- recommendationsList: document.getElementById('recommendations-list'),
- // EQ
- eqBandsContainer: document.getElementById('eq-bands'),
- eqPresetsSelect: document.getElementById('eq-presets-select'),
- resetEqBtn: document.getElementById('reset-eq-btn'),
- };
-
- this.canvasCtx = this.ui.visualizer.getContext('2d');
+ const ids = [
+ 'app-container', 'main-header', 'theme-switcher', 'equalizer-button', 'settings-button',
+ 'main-content', 'player-column', 'now-playing-card', 'album-art-container', 'station-logo',
+ 'visualizer', 'station-info', 'station-name', 'station-genre', 'live-indicator',
+ 'player-controls', 'prev-station', 'play-pause-button', 'next-station', 'volume-and-more',
+ 'volume-control', 'volume-slider', 'record-button', 'stations-column', 'tabs',
+ 'my-stations-tab', 'discover-tab', 'genres-tab', 'station-list-header', 'search-input',
+ 'add-station-button', 'station-list', 'ai-recommendations', 'recommendations-container',
+ 'discover-list', 'genre-list', 'settings-modal', 'color-picker', 'sleep-timer-select',
+ 'import-button', 'export-button', 'import-file-input', 'add-station-modal', 'add-station-form',
+ 'new-station-name', 'new-station-url', 'new-station-logo', 'new-station-genre',
+ 'equalizer-modal', 'equalizer-controls', 'eq-preset-select', 'eq-reset-button', 'eq-bands-container'
+ ];
+ ids.forEach(id => this.ui[id] = document.getElementById(id));
+
+ this.ui.modalCloseButtons = document.querySelectorAll('.modal .close-button');
+ this.ui.tabLinks = document.querySelectorAll('.tab-link');
}
bindEvents() {
- // Player controls
- this.ui.playPauseBtn.addEventListener('click', () => this.togglePlayPause());
- this.ui.nextBtn.addEventListener('click', () => this.playNext());
- this.ui.prevBtn.addEventListener('click', () => this.playPrevious());
- this.ui.recordBtn.addEventListener('click', () => this.toggleRecording());
- this.ui.volumeSlider.addEventListener('input', (e) => this.setVolume(e.target.value));
+ this.ui.play-pause-button.addEventListener('click', () => this.togglePlayPause());
+ this.ui.next-station.addEventListener('click', () => this.playNextStation());
+ this.ui.prev-station.addEventListener('click', () => this.playPreviousStation());
+ this.ui.volume-slider.addEventListener('input', (e) => this.setVolume(e.target.value));
+ this.ui.station-list.addEventListener('click', (e) => this.handleStationClick(e));
+ this.ui.search-input.addEventListener('input', (e) => this.filterStations(e.target.value));
+ this.ui.theme-switcher.addEventListener('click', () => this.toggleTheme());
- // Station list interactions
- this.ui.stationList.addEventListener('click', (e) => this.handleStationListClick(e));
- this.ui.discoverList.addEventListener('click', (e) => this.handleDiscoverListClick(e));
+ // Modals
+ this.ui.settings-button.addEventListener('click', () => this.showModal('settings-modal'));
+ this.ui.add-station-button.addEventListener('click', () => this.showModal('add-station-modal'));
+ this.ui.equalizer-button.addEventListener('click', () => this.showModal('equalizer-modal'));
+ this.ui.modalCloseButtons.forEach(btn => btn.addEventListener('click', () => this.hideAllModals()));
+ window.addEventListener('click', (e) => {
+ if (e.target.classList.contains('modal')) this.hideAllModals();
+ });
- // Search and filter
- this.ui.searchInput.addEventListener('input', (e) => this.renderStations(e.target.value, this.ui.genreFilter.value));
- this.ui.genreFilter.addEventListener('change', (e) => this.renderStations(this.ui.searchInput.value, e.target.value));
+ // Settings
+ this.ui.color-picker.addEventListener('input', (e) => this.applyAccentColor(e.target.value));
+ this.ui.sleep-timer-select.addEventListener('change', (e) => this.setSleepTimer(e.target.value));
+ this.ui.import-button.addEventListener('click', () => this.ui.import-file-input.click());
+ this.ui.import-file-input.addEventListener('change', (e) => this.importStations(e));
+ this.ui.export-button.addEventListener('click', () => this.exportStations());
- // Theme and settings
- this.ui.themeToggle.addEventListener('change', () => this.toggleTheme());
- this.bindModalEvents();
- this.bindColorPickerEvents();
- this.bindTimerEvents();
- this.bindImportExportEvents();
- this.bindEqualizerEvents();
+ // Add Station Form
+ this.ui.add-station-form.addEventListener('submit', (e) => this.addStation(e));
// Tabs
- this.ui.sidebarTabs.forEach(tab => {
- tab.addEventListener('click', () => this.switchTab(tab.dataset.tab));
+ this.ui.tabLinks.forEach(link => {
+ link.addEventListener('click', () => this.switchTab(link.dataset.tab));
});
-
- // Audio events
- this.audio.addEventListener('play', () => this.ui.playPauseBtn.innerHTML = '');
- this.audio.addEventListener('pause', () => this.ui.playPauseBtn.innerHTML = '');
+
+ // Audio Lifecycle
+ this.audio.addEventListener('playing', () => this.isPlaying = true);
+ this.audio.addEventListener('pause', () => this.isPlaying = false);
+
+ // Equalizer & Recorder
+ this.ui.record-button.addEventListener('click', () => this.toggleRecording());
+ this.initEqualizer();
// Keyboard shortcuts
document.addEventListener('keydown', (e) => this.handleKeyPress(e));
}
- bindModalEvents() {
- const modals = document.querySelectorAll('.modal');
- const closeBtns = document.querySelectorAll('.close-btn');
-
- this.ui.addStationBtn.addEventListener('click', () => this.openStationModal());
- this.ui.settingsBtn.addEventListener('click', () => this.ui.settingsModal.classList.add('active'));
- this.ui.equalizerBtn.addEventListener('click', () => this.ui.equalizerModal.classList.add('active'));
- this.ui.sleepTimerBtn.addEventListener('click', () => this.ui.sleepTimerModal.classList.add('active'));
- this.ui.importExportBtn.addEventListener('click', () => this.ui.importExportModal.classList.add('active'));
-
- closeBtns.forEach(btn => btn.addEventListener('click', () => this.closeAllModals()));
- modals.forEach(modal => modal.addEventListener('click', (e) => {
- if (e.target === modal) this.closeAllModals();
- }));
-
- document.getElementById('station-form').addEventListener('submit', (e) => this.saveStation(e));
- }
-
- closeAllModals() {
- document.querySelectorAll('.modal').forEach(m => m.classList.remove('active'));
- }
-
- /* --- Station Management --- */
-
+ // Station Management
loadStations() {
- const savedStations = localStorage.getItem('radioWaveStations');
- this.stations = savedStations ? JSON.parse(savedStations) : this.getDefaultStations();
- this.updateGenreFilter();
+ const savedStations = localStorage.getItem('stations');
+ this.stations = savedStations ? JSON.parse(savedStations) : [
+ { name: "Lofi Girl", url: "https://play.streamafrica.net/lofiradio", logo: "https://i.ytimg.com/vi/jfKfPfyJRdk/maxresdefault.jpg", genre: "Lofi" },
+ { name: "Classic Rock", url: "http://198.178.123.23:8722/stream", logo: "https://cdn-radiotime-logos.tunein.com/s292341q.png", genre: "Rock" },
+ ];
+ this.renderStationList();
}
saveStations() {
- localStorage.setItem('radioWaveStations', JSON.stringify(this.stations));
- this.updateGenreFilter();
- this.renderStations();
+ localStorage.setItem('stations', JSON.stringify(this.stations));
}
- renderStations(searchTerm = '', genreTerm = 'all') {
- this.ui.stationList.innerHTML = '';
- const filteredStations = this.stations.filter(station => {
- const matchesSearch = station.name.toLowerCase().includes(searchTerm.toLowerCase());
- const matchesGenre = genreTerm === 'all' || station.genre.toLowerCase() === genreTerm.toLowerCase();
- return matchesSearch && matchesGenre;
+ renderStationList() {
+ this.ui.station-list.innerHTML = '';
+ this.stations.forEach((station, index) => {
+ const li = document.createElement('li');
+ li.dataset.index = index;
+ li.className = (index === this.currentStationIndex) ? 'active' : '';
+ li.innerHTML = `
+
+ ${station.name}
+ `;
+ this.ui.station-list.appendChild(li);
});
+ }
- if (filteredStations.length === 0) {
- this.ui.stationList.innerHTML = '
No stations found.
'; + addStation(e) { + e.preventDefault(); + const newStation = { + name: this.ui.new-station-name.value, + url: this.ui.new-station-url.value, + logo: this.ui.new-station-logo.value, + genre: this.ui.new-station-genre.value + }; + this.stations.push(newStation); + this.saveStations(); + this.renderStationList(); + this.ui.add-station-form.reset(); + this.hideAllModals(); + } + + handleStationClick(e) { + const li = e.target.closest('li'); + if (li) { + const index = parseInt(li.dataset.index, 10); + this.playStation(index); + } + } + + filterStations(query) { + const lowerQuery = query.toLowerCase(); + const filtered = this.stations.filter(s => s.name.toLowerCase().includes(lowerQuery)); + // A bit of a hack, but re-rendering the whole list is easiest + this.ui.station-list.innerHTML = ''; + filtered.forEach(station => { + const index = this.stations.indexOf(station); + const li = document.createElement('li'); + li.dataset.index = index; + li.className = (index === this.currentStationIndex) ? 'active' : ''; + li.innerHTML = ` +${station.genre || 'No Genre'}
-${station.genre}
-No recommendations right now.
'; - return; - } - - recommendations.forEach(station => { - const item = document.createElement('div'); - item.className = 'rec-item'; - item.dataset.name = station.name; - item.dataset.url = station.url; - item.dataset.genre = station.genre; - item.innerHTML = `${station.genre}
`; - item.addEventListener('click', () => { - if (confirm(`Add '${station.name}' to your stations?`)) { - this.handleDiscoverListClick({ target: item }); - } + populateDiscoverStations() { + // In a real app, this would be an API call. + const discover = [ + { name: "Jazz Cafe", url: "http://192.99.35.215:5034/stream", logo: "https://cdn-radiotime-logos.tunein.com/s253631q.png", genre: "Jazz" }, + { name: "Classical FM", url: "http://media-ice.musicradio.com/ClassicFMMP3", logo: "https://cdn-radiotime-logos.tunein.com/s25365q.png", genre: "Classical" }, + { name: "Radio Paradise", url: "http://stream.radioparadise.com/flacm", logo: "https://i.radioparadise.com/img/logo-circle-250.png", genre: "Eclectic" }, + ]; + this.ui.discover-list.innerHTML = ''; + discover.forEach(station => { + const li = document.createElement('li'); + li.innerHTML = ` +