folx-live/views/index.ejs

765 lines
25 KiB
Plaintext

<!DOCTYPE html>
<html lang="de" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FOLX Music Television — Live | Volksmusik & Schlager</title>
<!-- SEO -->
<meta name="description" content="FOLX TV Live Stream. Oberkrainer, Bayrische und Zillertaler Musik rund um die Uhr. Volksmusik & Schlager Fernsehsender Nr. 1 in DE, AT und CH." />
<meta name="keywords" content="Volksmusik, Schlager, Folx TV Live, Folx Music Television, Oberkrainer, Bayrische Musik, Zillertaler Musik, Live Stream, Musiksender, Alpenmusik, Volksmusik Live" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://folx.live/" />
<!-- Open Graph -->
<meta property="og:title" content="FOLX Music Television — Live" />
<meta property="og:description" content="24/7 Volksmusik & Schlager Live-Stream. Oberkrainer, Bayrische und Zillertaler Musik." />
<meta property="og:type" content="video.other" />
<meta property="og:site_name" content="FOLX Music Television" />
<meta property="og:url" content="https://folx.live/" />
<meta property="og:image" content="https://folx.live/brand/folx-with-tagline-neg.png" />
<meta property="og:image:width" content="1241" />
<meta property="og:image:height" content="875" />
<meta property="og:locale" content="de_DE" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="FOLX Music Television — Live" />
<meta name="twitter:description" content="24/7 Volksmusik & Schlager Live-Stream." />
<meta name="twitter:image" content="https://folx.live/brand/folx-with-tagline-neg.png" />
<!-- Favicon -->
<link rel="icon" type="image/png" href="/brand/folx-full-poz.png" />
<link rel="apple-touch-icon" href="/brand/folx-full-poz.png" />
<!-- Fonts: Druk (Archivo Black proxy) for display + Inter for body -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Archivo:wght@400;500;600;700;800;900&display=swap" rel="stylesheet" />
<style>
:root {
--folx-magenta: #dc1c4c;
--folx-magenta-bright: #ff2e63;
--folx-magenta-deep: #9d0f32;
--bg-0: #000000;
--bg-1: #0a0a0a;
--bg-2: #141414;
--bg-3: #1e1e1e;
--border: #2a2a2a;
--text: #f5f5f5;
--text-muted: #888888;
--text-faint: #555555;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-0);
color: var(--text);
font-family: 'Archivo', -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 500;
-webkit-font-smoothing: antialiased;
line-height: 1.5;
overflow-x: hidden;
}
/* ======================= NAVIGATION ======================= */
.topnav {
position: sticky;
top: 0;
z-index: 50;
background: rgba(0, 0, 0, 0.92);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.topnav-inner {
max-width: 1400px;
margin: 0 auto;
padding: 10px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-height: 58px;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
color: inherit;
text-decoration: none;
}
.brand-logo {
height: 38px;
width: auto;
display: block;
}
@media (max-width: 520px) {
.brand-logo { height: 30px; }
.topnav-inner { padding: 8px 16px; min-height: 48px; }
}
.brand-sep {
width: 1px;
height: 24px;
background: var(--border);
}
.brand-tagline {
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
letter-spacing: 0.22em;
color: var(--text-muted);
text-transform: uppercase;
}
.live-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px 6px 8px;
border-radius: 3px;
background: var(--folx-magenta);
color: white;
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.live-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: white;
animation: livePulse 1.6s infinite;
}
@keyframes livePulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.85); }
}
/* ======================= HERO PLAYER ======================= */
.hero {
padding: 24px;
max-width: 1400px;
margin: 0 auto;
}
.hero-header {
margin: 24px 0 18px;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 14px;
}
.hero-title {
font-family: 'Archivo Black', sans-serif;
font-size: clamp(32px, 5vw, 64px);
line-height: 0.95;
letter-spacing: -0.02em;
text-transform: uppercase;
}
.hero-title .accent { color: var(--folx-magenta); }
.hero-meta {
font-size: 13px;
color: var(--text-muted);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero-meta .dot {
color: var(--folx-magenta);
padding: 0 8px;
}
/* Player wrapper — always 16:9 */
.player-frame {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
border: 1px solid var(--border);
overflow: hidden;
}
.player-frame video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: #000;
object-fit: contain;
display: block;
}
/* Overlay — live badge + controls */
.player-overlay {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 3;
background: linear-gradient(180deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 18%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.65) 100%);
opacity: 0;
transition: opacity 0.25s;
}
.player-frame:hover .player-overlay,
.player-frame:focus-within .player-overlay {
opacity: 1;
}
.player-controls {
position: absolute;
bottom: 14px;
right: 14px;
display: flex;
gap: 6px;
pointer-events: auto;
}
.pctl {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.15);
color: white;
width: 42px;
height: 42px;
border-radius: 3px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.15s;
padding: 0;
}
.pctl:hover {
background: var(--folx-magenta);
border-color: var(--folx-magenta);
}
/* Status & loading */
.player-msg {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 12px;
color: var(--text-muted);
font-size: 14px;
letter-spacing: 0.05em;
pointer-events: none;
z-index: 2;
opacity: 0;
transition: opacity 0.3s;
background: rgba(0,0,0,0.4);
}
.player-msg.visible { opacity: 1; }
.spinner {
width: 30px;
height: 30px;
border: 2px solid var(--border);
border-top-color: var(--folx-magenta);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ======================= SECTIONS ======================= */
section {
max-width: 1400px;
margin: 0 auto;
padding: 48px 24px;
}
/* Feature row */
.features {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
background: var(--border);
border: 1px solid var(--border);
}
.feature {
background: var(--bg-1);
padding: 28px 24px;
}
.feature-num {
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
color: var(--folx-magenta);
letter-spacing: 0.2em;
margin-bottom: 14px;
}
.feature-title {
font-family: 'Archivo Black', sans-serif;
font-size: 20px;
letter-spacing: -0.01em;
margin-bottom: 8px;
text-transform: uppercase;
}
.feature-desc {
color: var(--text-muted);
font-size: 14px;
line-height: 1.55;
}
/* Programm section — editorial list */
.section-label {
font-family: 'Archivo Black', sans-serif;
font-size: 12px;
letter-spacing: 0.24em;
color: var(--folx-magenta);
text-transform: uppercase;
margin-bottom: 10px;
}
.section-head {
font-family: 'Archivo Black', sans-serif;
font-size: clamp(28px, 4vw, 48px);
line-height: 1;
letter-spacing: -0.02em;
margin-bottom: 8px;
text-transform: uppercase;
}
.section-intro {
max-width: 720px;
color: var(--text-muted);
font-size: 15px;
line-height: 1.65;
margin-bottom: 40px;
}
.programm-list {
border-top: 1px solid var(--border);
}
.programm-item {
display: grid;
grid-template-columns: 200px 1fr 180px;
gap: 32px;
padding: 24px 0;
border-bottom: 1px solid var(--border);
align-items: start;
}
.programm-name {
font-family: 'Archivo Black', sans-serif;
font-size: 18px;
letter-spacing: -0.01em;
text-transform: uppercase;
}
.programm-desc {
color: var(--text-muted);
font-size: 14.5px;
line-height: 1.6;
}
.programm-desc strong {
color: var(--text);
font-weight: 700;
}
.programm-time {
font-family: 'Archivo Black', sans-serif;
font-size: 12px;
letter-spacing: 0.1em;
color: var(--folx-magenta);
text-transform: uppercase;
text-align: right;
}
/* Footer */
footer {
border-top: 1px solid var(--border);
padding: 48px 24px 32px;
margin-top: 32px;
}
.footer-inner {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 48px;
}
.foot-label {
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
color: var(--folx-magenta);
letter-spacing: 0.22em;
text-transform: uppercase;
margin-bottom: 14px;
}
.foot-title {
font-family: 'Archivo Black', sans-serif;
font-size: 20px;
text-transform: uppercase;
margin-bottom: 6px;
}
.foot-text {
color: var(--text-muted);
font-size: 13.5px;
line-height: 1.65;
}
.foot-text a {
color: var(--text);
text-decoration: none;
border-bottom: 1px solid var(--text-faint);
transition: border-color 0.15s;
}
.foot-text a:hover { border-color: var(--folx-magenta); }
.foot-copy {
max-width: 1400px;
margin: 32px auto 0;
padding-top: 24px;
border-top: 1px solid var(--border);
display: flex;
justify-content: space-between;
color: var(--text-faint);
font-size: 12px;
letter-spacing: 0.05em;
}
.foot-mark {
display: flex;
align-items: center;
gap: 10px;
}
.foot-mark img {
height: 20px;
width: auto;
opacity: 0.5;
}
/* Responsive */
@media (max-width: 900px) {
.features { grid-template-columns: repeat(2, 1fr); }
.programm-item { grid-template-columns: 1fr; gap: 6px; }
.programm-time { text-align: left; }
.footer-inner { grid-template-columns: 1fr; gap: 32px; }
.foot-copy { flex-direction: column; gap: 12px; }
}
@media (max-width: 520px) {
.features { grid-template-columns: 1fr; }
.brand-tagline, .brand-sep { display: none; }
.hero { padding: 16px; }
section { padding: 32px 16px; }
}
</style>
</head>
<body>
<nav class="topnav">
<div class="topnav-inner">
<a href="/" class="brand">
<img src="/brand/folx-logo-transparent.png?v=2" alt="FOLX Music Television" class="brand-logo" />
</a>
<span class="live-pill">
<span class="live-dot"></span>
Jetzt live
</span>
</div>
</nav>
<main class="hero">
<div class="hero-header">
<h1 class="hero-title">
Volksmusik<br/>
<span class="accent">Rund um die Uhr.</span>
</h1>
<div class="hero-meta">
<span>24 / 7 Live</span>
<span class="dot">•</span>
<span>HD</span>
<span class="dot">•</span>
<span>DE · AT · CH</span>
</div>
</div>
<div class="player-frame" id="playerFrame">
<video id="v" autoplay muted playsinline tabindex="-1"
disablepictureinpicture
controlsList="nodownload nofullscreen noremoteplayback noplaybackrate"></video>
<div class="player-msg visible" id="msg">
<div class="spinner"></div>
<div>Stream wird geladen…</div>
</div>
<div class="player-overlay">
<div class="player-controls">
<button class="pctl" id="btnAudio" aria-label="Ton umschalten" title="Ton umschalten">🔇</button>
<button class="pctl" id="btnFs" aria-label="Vollbild" title="Vollbild">⛶</button>
</div>
</div>
</div>
</main>
<section>
<div class="features">
<div class="feature">
<div class="feature-num">01</div>
<div class="feature-title">24 / 7 Live</div>
<div class="feature-desc">Rund um die Uhr das Beste aus der Volksmusik- und Schlagerszene — ohne Unterbrechung.</div>
</div>
<div class="feature">
<div class="feature-num">02</div>
<div class="feature-title">Oberkrainer</div>
<div class="feature-desc">Die Legenden der Oberkrainer Musik, traditionell und modern interpretiert.</div>
</div>
<div class="feature">
<div class="feature-num">03</div>
<div class="feature-title">Bayrisch</div>
<div class="feature-desc">Von klassischer Blasmusik bis zeitgenössischem Schlager aus Bayern und Tirol.</div>
</div>
<div class="feature">
<div class="feature-num">04</div>
<div class="feature-title">Zillertaler</div>
<div class="feature-desc">Pure alpine Lebensfreude aus dem Herzen der Zillertaler Berge.</div>
</div>
</div>
</section>
<section>
<div class="section-label">Programm</div>
<h2 class="section-head">Das läuft bei FOLX</h2>
<p class="section-intro">
Ob junge Menschen, Mid Ager oder Best Ager — für jeden ist etwas dabei. Die größten Stars,
die modernsten Videos und die beste volkstümliche Musik, die das Herz jedes Fans höher schlagen lässt.
</p>
<div class="programm-list">
<div class="programm-item">
<div class="programm-name">Morgenstund hat Gold im Mund</div>
<div class="programm-desc">Folx TV macht das Aufstehen zum Genuss. Starten Sie Ihren Tag mit der besten Musik und den neuesten Informationen.</div>
<div class="programm-time">Täglich ab 06:00</div>
</div>
<div class="programm-item">
<div class="programm-name">Folx Interaktiv</div>
<div class="programm-desc">Unsere Moderatoren und Zuschauer werden von den Gästen aus der Volks- und Schlagermusikszene begleitet. Täglich aktuelle News, Sport, Klatsch und Tratsch.</div>
<div class="programm-time">Di & Do ab 14:00</div>
</div>
<div class="programm-item">
<div class="programm-name">Folx Box</div>
<div class="programm-desc">Eine unwiderstehliche Mischung. Sie kriegen nie genug von Ihren beliebtesten volkstümlichen Musikvideos — hier entscheiden SIE, was gespielt wird.</div>
<div class="programm-time">Di & Do ab 20:00</div>
</div>
<div class="programm-item">
<div class="programm-name">Folx Live</div>
<div class="programm-desc"><strong>100 % Live.</strong> Gruppen, die sich noch wagen live zu spielen. Ein echter Ohrenschmaus.</div>
<div class="programm-time">Freitag ab 20:15</div>
</div>
<div class="programm-item">
<div class="programm-name">Folx Stadl</div>
<div class="programm-desc">Legenden der Volksmusik- und Schlagerszene präsentieren sich im einzigartigen Hüttenambiente.</div>
<div class="programm-time">Samstag ab 20:15</div>
</div>
</div>
</section>
<footer>
<div class="footer-inner">
<div>
<div class="foot-label">Sender</div>
<div class="foot-title">FOLX Music Television</div>
<div class="foot-text">
Der erste private Fernsehsender für Musikvideos und Entertainment aus der Volksmusik-
und Schlagerszene in Deutschland, Österreich und der Schweiz — <strong style="color:var(--text);">24 Stunden, 7 Tage die Woche.</strong>
</div>
</div>
<div>
<div class="foot-label">Kontakt</div>
<div class="foot-text">
PRIME TIME Consulting GmbH<br/>
Sigmaringer Str. 10<br/>
72379 Hechingen · Deutschland<br/><br/>
<a href="mailto:office@folx.network">office@folx.network</a>
</div>
</div>
<div>
<div class="foot-label">Mehr</div>
<div class="foot-text">
<a href="https://folx.tv">folx.tv</a> — Nachrichten & Videos<br/>
<a href="https://folx.network">folx.network</a> — Netzwerk<br/>
<a href="https://www.youtube.com/@folxmtv">YouTube</a><br/>
<a href="https://www.facebook.com/folxmtv">Facebook</a>
</div>
</div>
</div>
<div class="foot-copy">
<div class="foot-mark">
<img src="/brand/folx-full-poz.png" alt="" />
<span>© <%= new Date().getFullYear() %> FOLX Music Television</span>
</div>
<div>Jederzeit, überall — folx.live</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script>
<script>
(function () {
const HLS_URL = <%- JSON.stringify(hlsUrl) %>;
const video = document.getElementById('v');
const msg = document.getElementById('msg');
const btnAudio = document.getElementById('btnAudio');
const btnFs = document.getElementById('btnFs');
const playerFrame = document.getElementById('playerFrame');
let hls = null;
function showMsg(html) { msg.innerHTML = html; msg.classList.add('visible'); }
function hideMsg() { msg.classList.remove('visible'); }
function attach() {
try { hls && hls.destroy(); } catch (e) {}
hls = null;
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = HLS_URL;
video.play().catch(() => {});
video.addEventListener('playing', hideMsg, { once: true });
return;
}
if (window.Hls && window.Hls.isSupported()) {
hls = new Hls({
liveDurationInfinity: true,
liveSyncDurationCount: 4,
backBufferLength: 30,
maxBufferLength: 60,
manifestLoadingTimeOut: 10000,
manifestLoadingMaxRetry: 6,
fragLoadingTimeOut: 20000,
fragLoadingMaxRetry: 6,
startLevel: -1,
capLevelToPlayerSize: false,
abrEwmaDefaultEstimate: 50000000,
testBandwidth: false,
});
hls.loadSource(HLS_URL);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
hideMsg();
video.play().catch(() => setTimeout(() => video.play().catch(() => {}), 200));
});
hls.on(Hls.Events.ERROR, (_e, data) => {
if (!data.fatal) return;
showMsg('<div class="spinner"></div><div>Verbindung wird wiederhergestellt…</div>');
setTimeout(() => { attach(); }, 3000);
});
} else {
showMsg('<div>HLS wird von diesem Browser nicht unterstützt.</div>');
}
}
attach();
// Stall detection
let lastT = 0, stalls = 0;
setInterval(() => {
if (video.paused) { video.play().catch(() => {}); return; }
if (video.currentTime === lastT && !video.seeking) {
stalls++;
if (stalls >= 3) {
stalls = 0;
showMsg('<div class="spinner"></div><div>Verbindung wird wiederhergestellt…</div>');
setTimeout(() => { attach(); }, 500);
}
} else stalls = 0;
lastT = video.currentTime;
}, 5000);
// Live-only mode (Twitch style) — pause is disabled.
// If anything pauses the video, resume immediately AND seek to the live edge.
function seekToLive() {
try {
if (hls && hls.liveSyncPosition) {
video.currentTime = hls.liveSyncPosition;
} else if (video.seekable && video.seekable.length > 0) {
video.currentTime = video.seekable.end(video.seekable.length - 1);
}
} catch (e) {}
}
video.addEventListener('pause', () => {
setTimeout(() => {
seekToLive();
video.play().catch(() => {});
}, 80);
});
// If user seeks back (e.g. via keyboard or scrubber), force forward to live
video.addEventListener('seeked', () => {
if (!hls) return;
if (hls.liveSyncPosition && video.currentTime < hls.liveSyncPosition - 10) {
video.currentTime = hls.liveSyncPosition;
}
});
// Block right-click context menu (hides "Pause", "Save video as", etc.)
video.addEventListener('contextmenu', (e) => e.preventDefault());
// Block Picture-in-Picture (its UI exposes pause)
video.disablePictureInPicture = true;
video.setAttribute('disablePictureInPicture', 'true');
// Block remote playback (AirPlay/Cast — can pause unexpectedly)
try { video.disableRemotePlayback = true; } catch (e) {}
// Block keyboard shortcuts that could pause: spacebar, K, Media keys
document.addEventListener('keydown', (e) => {
// Only intercept if focus is on body or video (not in input fields)
const tag = (e.target && e.target.tagName) || '';
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
if (e.key === ' ' || e.key === 'Spacebar' || e.code === 'Space' ||
e.key === 'k' || e.key === 'K' ||
e.key === 'MediaPlayPause' || e.key === 'MediaPause') {
e.preventDefault();
// Make sure we keep playing
if (video.paused) {
seekToLive();
video.play().catch(() => {});
}
}
});
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
seekToLive();
video.play().catch(() => {});
}
});
// Audio toggle
function updateAudio() {
btnAudio.textContent = video.muted ? '🔇' : '🔊';
btnAudio.title = video.muted ? 'Ton einschalten' : 'Stummschalten';
}
updateAudio();
btnAudio.addEventListener('click', (e) => {
e.stopPropagation();
video.muted = !video.muted;
video.volume = 1.0;
updateAudio();
});
// Fullscreen — must handle iOS Safari separately (no fullscreen API on container,
// only on the video element via webkitEnterFullscreen)
function enterFullscreen() {
// iOS Safari path: only video.webkitEnterFullscreen works
if (typeof video.webkitEnterFullscreen === 'function' && !document.fullscreenEnabled) {
try { video.webkitEnterFullscreen(); return; } catch (e) {}
}
// Standard Fullscreen API on the video element (most reliable cross-browser)
if (video.requestFullscreen) { video.requestFullscreen().catch(() => {}); return; }
if (video.webkitRequestFullscreen) { video.webkitRequestFullscreen(); return; }
// Fallback: try the container
if (playerFrame.requestFullscreen) { playerFrame.requestFullscreen().catch(() => {}); return; }
if (playerFrame.webkitRequestFullscreen) { playerFrame.webkitRequestFullscreen(); return; }
// Last-resort iOS path
if (typeof video.webkitEnterFullscreen === 'function') {
try { video.webkitEnterFullscreen(); } catch (e) {}
}
}
function exitFullscreen() {
if (document.exitFullscreen) { document.exitFullscreen().catch(() => {}); return; }
if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); return; }
if (typeof video.webkitExitFullscreen === 'function') { try { video.webkitExitFullscreen(); } catch (e) {} }
}
function isFullscreen() {
return !!(document.fullscreenElement || document.webkitFullscreenElement || video.webkitDisplayingFullscreen);
}
btnFs.addEventListener('click', (e) => {
e.stopPropagation();
if (isFullscreen()) exitFullscreen();
else enterFullscreen();
});
video.addEventListener('click', () => { video.muted = !video.muted; video.volume = 1.0; updateAudio(); });
video.addEventListener('dblclick', (e) => { e.preventDefault(); btnFs.click(); });
})();
</script>
</body>
</html>