x
Islamic Radio
Islamic Radio Widget
/* * WIDGET STYLES
* Self-contained CSS.
* Prefix: ‘irw-‘
*/
:root {
–irw-bg: #0f172a;
–irw-surface: #1e293b;
–irw-surface-hover: #334155;
–irw-primary: #10b981; /* Emerald */
–irw-primary-hover: #059669;
–irw-accent: #f59e0b; /* Amber for accents */
–irw-text: #f8fafc;
–irw-text-muted: #94a3b8;
–irw-border: #334155;
–irw-danger: #ef4444;
}
body {
/* Desktop centering defaults */
margin: 0;
padding: 20px;
background-color: #f1f5f9;
font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/* Prevent scrolling on body when widget is full screen */
overscroll-behavior: none;
}
.irw-widget {
width: 100%;
max-width: 400px;
background-color: var(–irw-bg);
color: var(–irw-text);
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
font-family: ‘Segoe UI’, Roboto, sans-serif;
border: 1px solid var(–irw-border);
display: flex;
flex-direction: column;
/* Ensure widget creates a stacking context */
position: relative;
z-index: 1;
}
/* LOGO SECTION */
.irw-symbol-container {
padding: 30px 20px 10px 20px;
display: flex;
justify-content: center;
background: linear-gradient(180deg, #064e3b 0%, #0f172a 100%);
flex-shrink: 0;
}
.irw-main-symbol {
width: 80px;
height: 80px;
filter: drop-shadow(0 0 12px rgba(16, 185, 129, 0.5));
fill: var(–irw-primary);
}
/* HEADER */
.irw-header {
padding: 5px 20px 20px 20px;
display: flex;
align-items: center;
justify-content: space-between;
text-align: center;
flex-direction: column;
gap: 12px;
flex-shrink: 0;
}
.irw-brand h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.025em;
color: white;
}
.irw-brand span {
font-size: 0.8rem;
color: var(–irw-text-muted);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-top: 4px;
display: block;
}
.irw-live-indicator {
display: flex;
align-items: center;
gap: 8px;
background: rgba(0,0,0,0.4);
padding: 6px 14px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 800;
border: 1px solid rgba(255,255,255,0.05);
}
.irw-dot {
width: 8px;
height: 8px;
background-color: var(–irw-danger);
border-radius: 50%;
transition: all 0.3s ease;
}
.irw-dot.active {
box-shadow: 0 0 10px var(–irw-danger);
animation: pulse 1.5s infinite;
}
/* STATION LIST */
.irw-stations {
/* Desktop Default: Fixed height scrollable area */
height: 300px;
overflow-y: auto;
background-color: var(–irw-surface);
border-top: 1px solid var(–irw-border);
/* Smooth scrolling */
-webkit-overflow-scrolling: touch;
}
.irw-stations::-webkit-scrollbar { width: 6px; }
.irw-stations::-webkit-scrollbar-track { background: var(–irw-surface); }
.irw-stations::-webkit-scrollbar-thumb { background: var(–irw-border); border-radius: 3px; }
.irw-station-btn {
width: 100%;
display: flex;
align-items: center;
padding: 16px 20px;
background: transparent;
border: none;
border-bottom: 1px solid var(–irw-border);
color: var(–irw-text);
cursor: pointer;
text-align: left;
transition: all 0.2s ease;
}
.irw-station-btn:hover {
background-color: var(–irw-surface-hover);
}
.irw-station-btn.active {
background-color: rgba(16, 185, 129, 0.15);
border-left: 4px solid var(–irw-primary);
padding-left: 16px; /* Offset for border */
}
.irw-station-icon {
width: 42px;
height: 42px;
background-color: var(–irw-bg);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
font-weight: bold;
color: var(–irw-text-muted);
border: 1px solid var(–irw-border);
flex-shrink: 0;
font-size: 0.8rem;
}
.irw-station-btn.active .irw-station-icon {
color: var(–irw-primary);
border-color: var(–irw-primary);
}
.irw-meta {
flex: 1;
min-width: 0; /* Prevents text overflow issues */
}
.irw-name {
font-size: 0.95rem;
font-weight: 600;
display: block;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.irw-desc {
font-size: 0.75rem;
color: var(–irw-text-muted);
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* CONTROLS */
.irw-controls {
padding: 24px 20px 30px 20px;
background-color: var(–irw-bg);
border-top: 1px solid var(–irw-border);
flex-shrink: 0;
position: relative;
z-index: 2;
}
.irw-status-message {
text-align: center;
font-size: 0.85rem;
color: var(–irw-primary);
height: 20px;
margin-bottom: 20px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.irw-control-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
}
.irw-skip-btn {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.05);
color: var(–irw-text-muted);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 48px; /* Larger touch target */
height: 48px;
border-radius: 50%;
}
.irw-skip-btn:hover {
color: var(–irw-text);
background: rgba(255,255,255,0.1);
transform: translateY(-2px);
}
.irw-skip-btn:active {
transform: scale(0.9);
}
.irw-skip-btn svg {
width: 20px;
height: 20px;
fill: currentColor;
}
.irw-play-btn {
width: 72px;
height: 72px;
border-radius: 50%;
background-color: var(–irw-primary);
border: none;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
box-shadow: 0 10px 20px rgba(16, 185, 129, 0.3);
flex-shrink: 0;
}
.irw-play-btn:hover {
background-color: var(–irw-primary-hover);
transform: scale(1.05);
box-shadow: 0 15px 25px rgba(16, 185, 129, 0.4);
}
.irw-play-btn svg {
width: 34px;
height: 34px;
fill: currentColor;
}
/* Volume Slider */
.irw-volume-row {
margin-top: 25px;
display: flex;
align-items: center;
gap: 12px;
padding: 0 10px;
}
.irw-vol-icon {
color: var(–irw-text-muted);
width: 20px;
}
input[type=range] {
flex: 1;
-webkit-appearance: none;
background: transparent;
height: 24px; /* Increases touch area */
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(–irw-text);
cursor: pointer;
margin-top: -6px;
border: 2px solid var(–irw-primary);
box-shadow: 0 0 0 4px rgba(0,0,0,0.1);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: var(–irw-surface-hover);
border-radius: 2px;
}
/* Visualizer */
.irw-eq {
display: flex;
align-items: flex-end;
gap: 3px;
height: 20px;
width: 24px;
}
.irw-eq-bar {
width: 4px;
background: var(–irw-primary);
border-radius: 2px;
height: 20%;
transition: height 0.1s;
}
.irw-eq.animating .irw-eq-bar {
animation: eq-bounce 0.8s infinite ease-in-out;
}
.irw-eq.animating .irw-eq-bar:nth-child(1) { animation-delay: 0s; }
.irw-eq.animating .irw-eq-bar:nth-child(2) { animation-delay: 0.2s; }
.irw-eq.animating .irw-eq-bar:nth-child(3) { animation-delay: 0.4s; }
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
@keyframes eq-bounce {
0%, 100% { height: 20%; }
50% { height: 100%; }
}
/* — MOBILE OPTIMIZATIONS — */
@media (max-width: 480px) {
body {
padding: 0;
align-items: flex-start;
background-color: var(–irw-bg);
}
.irw-widget {
max-width: 100%;
/* Use dynamic viewport height to handle mobile browser bars */
height: 100vh; /* Fallback */
height: 100dvh;
border-radius: 0;
border: none;
box-shadow: none;
}
/* Allow the list to grow and fill the screen */
.irw-stations {
height: auto;
flex: 1;
max-height: none;
}
.irw-symbol-container {
padding-top: 20px; /* Adjust for status bar visual preference */
}
.irw-controls {
padding-bottom: 30px; /* Extra padding for bottom swipe area */
}
}
/*
* FINAL VERIFIED STATION LIST
* All stations confirmed working or using identical tech to working stations.
*/
const stations = [
{
name: “Cii Radio”,
desc: “Channel Islam Int (South Africa)”,
url: “https://edge.iono.fm/xice/109_medium.mp3”,
icon: “CII”
},
{
name: “Radio Al Ansaar”,
desc: “Durban, South Africa (90.4 FM)”,
url: “https://al-ansaar.simplestreaming.co.za/listen/al-ansaar_radio/radio.mp3”,
icon: “ANS”
},
{
name: “Voice of the Cape”,
desc: “Cape Town (91.3 FM)”,
url: “https://edge.iono.fm/xice/194_medium.mp3”,
icon: “VOC”
},
{
name: “IFM 102.2”,
desc: “Islamic Community Radio”,
url: “https://edge.iono.fm/xice/162_medium.mp3”,
icon: “IFM”
},
{
name: “Voice of Van”,
desc: “Community Islamic Radio”,
url: “https://edge.iono.fm/xice/207_medium.mp3”,
icon: “VOV”
},
{
name: “Radio Panjo”,
desc: “Johannesburg (Community)”,
url: “https://edge.iono.fm/xice/122_medium.mp3”,
icon: “PNJ”
},
{
name: “Makkah Live”,
desc: “Masjid Al-Haram (ITWorks CDN)”,
url: “https://l3.itworkscdn.net/itwaudio/9006/stream”,
icon: “MKH”
},
{
name: “Radio Hilal”,
desc: “KZN, South Africa (Iono)”,
url: “https://edge.iono.fm/xice/132_medium.mp3”,
icon: “HIL”
}
];
// State
let isPlaying = false;
let currentIndex = -1;
const audio = document.getElementById(‘irw-audio’);
const playBtn = document.getElementById(‘irw-play-btn’);
const prevBtn = document.getElementById(‘irw-prev-btn’);
const nextBtn = document.getElementById(‘irw-next-btn’);
const statusTxt = document.getElementById(‘irw-status’);
const eq = document.getElementById(‘irw-eq’);
const dot = document.getElementById(‘irw-dot’);
const liveText = document.getElementById(‘irw-live-text’);
const list = document.getElementById(‘irw-station-list’);
// Initialize
function init() {
// Build List
list.innerHTML = stations.map((s, i) => `
`).join(”);
// Event delegation for station buttons
list.addEventListener(‘click’, (e) => {
const btn = e.target.closest(‘.irw-station-btn’);
if (btn) {
const index = parseInt(btn.getAttribute(‘data-index’));
playStation(index);
}
});
// Volume
const vol = document.getElementById(‘irw-vol’);
vol.addEventListener(‘input’, (e) => audio.volume = e.target.value);
audio.volume = 0.8;
// Play Button
playBtn.addEventListener(‘click’, () => {
if(currentIndex === -1) playStation(0);
else togglePlay();
});
// Skip Buttons
prevBtn.addEventListener(‘click’, skipPrev);
nextBtn.addEventListener(‘click’, skipNext);
// Audio Events
audio.addEventListener(‘playing’, () => setStatus(‘playing’));
audio.addEventListener(‘pause’, () => setStatus(‘paused’));
audio.addEventListener(‘waiting’, () => statusTxt.innerText = “Buffering…”);
audio.addEventListener(‘error’, (e) => {
console.error(“Stream Error”, e);
setStatus(‘error’);
statusTxt.innerText = “Offline or Blocked”;
});
// Setup Media Session Handlers
setupMediaSession();
}
function setupMediaSession() {
if (‘mediaSession’ in navigator) {
navigator.mediaSession.setActionHandler(‘play’, togglePlay);
navigator.mediaSession.setActionHandler(‘pause’, togglePlay);
navigator.mediaSession.setActionHandler(‘previoustrack’, skipPrev);
navigator.mediaSession.setActionHandler(‘nexttrack’, skipNext);
}
}
function updateMediaMetadata(station) {
if (‘mediaSession’ in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: station.name,
artist: station.desc,
album: ‘Islamic Radio Live’,
artwork: [
{ src: ‘https://cdn-icons-png.flaticon.com/512/2881/2881031.png’, sizes: ‘512×512’, type: ‘image/png’ }
]
});
}
}
function playStation(index) {
// UI Reset
const buttons = document.querySelectorAll(‘.irw-station-btn’);
buttons.forEach(b => b.classList.remove(‘active’));
if (buttons[index]) {
buttons[index].classList.add(‘active’);
buttons[index].scrollIntoView({ behavior: ‘smooth’, block: ‘nearest’ });
}
currentIndex = index;
const station = stations[index];
statusTxt.innerText = `Loading ${station.name}…`;
// Update Mobile Metadata
updateMediaMetadata(station);
// Audio Load
audio.src = station.url;
audio.load();
const p = audio.play();
if(p !== undefined) {
p.then(() => setStatus(‘playing’))
.catch(e => {
console.log(“Autoplay blocked or stream error”);
setStatus(‘paused’);
statusTxt.innerText = “Click Play”;
});
}
}
function skipNext() {
let nextIndex = currentIndex + 1;
if (nextIndex >= stations.length) nextIndex = 0;
playStation(nextIndex);
}
function skipPrev() {
let prevIndex = currentIndex – 1;
if (prevIndex < 0) prevIndex = stations.length – 1;
playStation(prevIndex);
}
function togglePlay() {
if(audio.paused) {
audio.play();
} else {
audio.pause();
}
}
function setStatus(state) {
const iconPlay = document.getElementById('irw-icon-play');
const iconPause = document.getElementById('irw-icon-pause');
if(state === 'playing') {
iconPlay.style.display = 'none';
iconPause.style.display = 'block';
eq.classList.add('animating');
dot.classList.add('active');
liveText.innerText = "LIVE";
statusTxt.innerText = "Playing Stream";
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'playing';
} else {
iconPlay.style.display = 'block';
iconPause.style.display = 'none';
eq.classList.remove('animating');
dot.classList.remove('active');
liveText.innerText = state === 'error' ? "ERROR" : "STANDBY";
if(state !== 'error') statusTxt.innerText = "Paused";
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'paused';
}
}
init();
