folx-live/views/index.ejs

771 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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: 12px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-height: 70px;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
color: inherit;
text-decoration: none;
}
.brand-logo {
height: 48px;
width: auto;
display: block;
}
@media (max-width: 520px) {
.brand-logo { height: 36px; }
.topnav-inner { padding: 10px 16px; min-height: 56px; }
}
.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: 12px 24px 24px;
max-width: 1400px;
margin: 0 auto;
}
.hero-header {
margin: 6px 0 14px;
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(28px, 4vw, 52px);
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 layer for the GO LIVE pill — only top of player, doesn't cover native controls */
.player-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 80px;
pointer-events: none;
z-index: 3;
}
/* GO LIVE pill — appears top-right when user is behind live edge */
.go-live-pill {
position: absolute;
top: 14px;
right: 14px;
pointer-events: auto;
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(220, 28, 76, 0.95);
color: white;
border: none;
padding: 7px 12px 7px 10px;
border-radius: 4px;
cursor: pointer;
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
backdrop-filter: blur(8px);
transition: background 0.15s, transform 0.15s;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.go-live-pill:hover {
background: rgba(220, 28, 76, 1);
transform: scale(1.04);
}
.go-live-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: white;
animation: livePulse 1.6s infinite;
}
.go-live-time {
font-family: 'Archivo', monospace;
font-size: 10.5px;
opacity: 0.85;
letter-spacing: 0.05em;
padding-left: 6px;
border-left: 1px solid rgba(255,255,255,0.35);
}
.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;
}
/* Info blocks (3-column under "Was läuft bei FOLX") */
.info-blocks {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2px;
background: var(--border);
border: 1px solid var(--border);
margin-top: 32px;
}
.info-block {
background: var(--bg-1);
padding: 32px 28px;
}
.info-num {
font-family: 'Archivo Black', sans-serif;
font-size: 11px;
color: var(--folx-magenta);
letter-spacing: 0.2em;
margin-bottom: 14px;
}
.info-title {
font-family: 'Archivo Black', sans-serif;
font-size: 18px;
letter-spacing: -0.01em;
margin-bottom: 8px;
text-transform: uppercase;
}
.info-desc {
color: var(--text-muted);
font-size: 14.5px;
line-height: 1.55;
}
/* 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 1.2fr 1.4fr 1.2fr;
gap: 40px;
}
.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-socials {
display: flex;
flex-direction: column;
gap: 0;
}
.foot-socials a {
border-bottom: none;
padding: 0;
display: inline-block;
width: fit-content;
line-height: 1.65;
}
.foot-socials a:hover { color: var(--folx-magenta); }
.foot-divider {
display: block;
width: 24px;
height: 1px;
background: var(--border);
margin: 10px 0 6px;
}
.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: 18px;
width: auto;
opacity: 0.65;
}
/* Responsive */
@media (max-width: 900px) {
.features { grid-template-columns: repeat(2, 1fr); }
.info-blocks { grid-template-columns: 1fr; }
.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>
<span class="dot">•</span>
<span>Worldwide</span>
</div>
</div>
<div class="player-frame" id="playerFrame">
<video id="v" autoplay muted playsinline controls></video>
<div class="player-msg visible" id="msg">
<div class="spinner"></div>
<div>Stream wird geladen…</div>
</div>
<div class="player-overlay">
<button id="btnGoLive" class="go-live-pill" aria-label="Zum Live-Stream" title="Zum Live-Stream springen" style="display:none;">
<span class="go-live-dot"></span>
<span>LIVE</span>
<span id="goLiveLabel" class="go-live-time">0s</span>
</button>
</div>
</div>
</main>
<section>
<div class="features">
<div class="feature">
<div class="feature-num">01</div>
<div class="feature-title">Oberkrainer</div>
<div class="feature-desc">Die Wurzeln der alpenländischen Volksmusik — Polka, Walzer und unvergessliche Klänge der Oberkrainer-Tradition.</div>
</div>
<div class="feature">
<div class="feature-num">02</div>
<div class="feature-title">Bayrisch</div>
<div class="feature-desc">Bayerische und österreichische Volksmusik in ihrer ganzen Vielfalt — von der Blaskapelle bis zum Alpenrock.</div>
</div>
<div class="feature">
<div class="feature-num">03</div>
<div class="feature-title">Zillertaler</div>
<div class="feature-desc">Pure alpine Lebensfreude aus dem Herzen der Tiroler Berge — Ländler, Polka und Tiroler Klassiker.</div>
</div>
<div class="feature">
<div class="feature-num">04</div>
<div class="feature-title">Schlager</div>
<div class="feature-desc">Vom Klassiker bis zum Sommerhit — die größten Schlagerstars und neuen Talente aus DE, AT und CH.</div>
</div>
</div>
</section>
<section>
<div class="section-label">Über FOLX</div>
<h2 class="section-head">Was läuft bei FOLX</h2>
<p class="section-intro">
FOLX Music Television ist der private Fernsehsender für Volksmusik, Schlager und alpine Klänge
in Deutschland, Österreich und der Schweiz. Wir senden das Beste aus der volkstümlichen
Musikszene — von Andreas Gabalier bis Helene Fischer, von Andrea Berg bis zu den jungen
Talenten der Alpen.
</p>
<p class="section-intro">
Zwischen den Musikvideos bieten wir Tele-Shopping mit ausgewählten Produkten sowie Berichte
aus der Volksmusik- und Schlagerszene. Kein festes Programm — einfach die Musik, die Sie
lieben, jederzeit.
</p>
<div class="info-blocks">
<div class="info-block">
<div class="info-num">A</div>
<div class="info-title">Musik</div>
<div class="info-desc">Das Beste aus Volksmusik &amp; Schlagerszene — Oberkrainer, Bayrisch, Zillertaler und mehr.</div>
</div>
<div class="info-block">
<div class="info-num">B</div>
<div class="info-title">Tele-Shopping</div>
<div class="info-desc">Premium Produkte aus dem Sortiment von Genius TV — Innovation, Qualität und das Beste für Ihren Alltag.</div>
</div>
<div class="info-block">
<div class="info-num">C</div>
<div class="info-title">Rund um die Uhr</div>
<div class="info-desc">24 Stunden Live — weltweit empfangbar über folx.live.</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 private Fernsehsender für Musikvideos und Entertainment aus der Volksmusik-
und Schlagerszene in Deutschland, Österreich und der Schweiz.
</div>
</div>
<div>
<div class="foot-label">Herausgeber</div>
<div class="foot-text">
BoldFrame Productions d.o.o.<br/>
Maribor, Slowenien
</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="tel:+491725675800">+49 172 5675800</a><br/>
<a href="mailto:office@folx.tv">office@folx.tv</a>
</div>
</div>
<div>
<div class="foot-label">Folgt uns</div>
<div class="foot-text foot-socials">
<a href="https://www.facebook.com/folxtv/" target="_blank" rel="noopener">Facebook</a>
<a href="https://www.instagram.com/folxtv/" target="_blank" rel="noopener">Instagram</a>
<a href="https://www.youtube.com/@folxmtv" target="_blank" rel="noopener">YouTube</a>
<a href="https://www.tiktok.com/@folx.tv" target="_blank" rel="noopener">TikTok</a>
<span class="foot-divider"></span>
<a href="https://folx.tv">folx.tv</a> — Nachrichten &amp; Videos
</div>
</div>
</div>
<div class="foot-copy">
<div class="foot-mark">
<img src="/brand/folx-wordmark-transparent.png" alt="FOLX" />
<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 playerFrame = document.getElementById('playerFrame');
const btnGoLive = document.getElementById('btnGoLive');
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({
// DVR-friendly: don't force duration=Infinity, allow native scrubber to work
liveDurationInfinity: false,
liveSyncDurationCount: 4,
// Larger back-buffer = more rewind window in browser memory
backBufferLength: 3600, // up to 60 min back-buffer
maxBufferLength: 60,
maxMaxBufferLength: 600,
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);
// DVR mode — pause and seek are allowed within the 60-min DVR window.
// We track "behind live" status and offer a GO LIVE button to skip to the 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);
}
video.play().catch(() => {});
} catch (e) {}
}
// Track distance from live edge → toggle "GO LIVE" pill visibility
function updateLivePill() {
if (!hls || !hls.liveSyncPosition) return;
const behindSec = Math.max(0, hls.liveSyncPosition - video.currentTime);
const pill = document.getElementById('btnGoLive');
if (!pill) return;
if (behindSec > 8) {
pill.style.display = 'inline-flex';
const min = Math.floor(behindSec / 60);
const s = Math.floor(behindSec % 60);
const lbl = document.getElementById('goLiveLabel');
if (lbl) lbl.textContent = min > 0 ? `${min}:${String(s).padStart(2,'0')}` : `${s}s`;
} else {
pill.style.display = 'none';
}
}
setInterval(updateLivePill, 1000);
video.addEventListener('seeked', updateLivePill);
// Recover from unintended pauses (system-level stalls only — NOT user clicks)
// We DON'T auto-resume on pause anymore because DVR mode allows pause.
// Visibility: when tab returns, do nothing (DVR mode allows continuing where paused)
// (no auto-seek-to-live on visibility change in DVR mode)
// GO LIVE pill click → jump to live edge
if (btnGoLive) {
btnGoLive.addEventListener('click', (e) => {
e.stopPropagation();
seekToLive();
});
}
// Unmute on first tap (browser autoplay policy: needs muted)
// Native controls handle audio/fullscreen now
video.addEventListener('volumechange', () => {
// user interacted via native UI — leave alone
});
})();
</script>
</body>
</html>