Nova folx.live stran (Folx TV): brand dizajn, now-playing iz master.biba.live (song/GENIUS/Werbung)

This commit is contained in:
Sebastjan 2026-07-07 09:09:41 +00:00
parent e0ab925d49
commit b14fbc5d5f
18 changed files with 202 additions and 753 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
public/ds/fonts/fonts.css Normal file
View File

@ -0,0 +1,16 @@
/* Geograph (Geogtq) — official brand typeface */
@font-face { font-family: 'Geograph'; src: url('Geogtq-Ul.otf') format('opentype'); font-weight: 200; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Th.otf') format('opentype'); font-weight: 300; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Lg.otf') format('opentype'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Rg.otf') format('opentype'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Md.otf') format('opentype'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Sb.otf') format('opentype'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geogtq-Bd.otf') format('opentype'); font-weight: 800; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-UlIt.otf') format('opentype'); font-weight: 200; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-ThIt.otf') format('opentype'); font-weight: 300; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-LgIt.otf') format('opentype'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-RgIt.otf') format('opentype'); font-weight: 500; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-MdIt.otf') format('opentype'); font-weight: 600; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-SbIt.otf') format('opentype'); font-weight: 700; font-style: italic; font-display: swap; }
@font-face { font-family: 'Geograph'; src: url('Geog-BdIt.otf') format('opentype'); font-weight: 800; font-style: italic; font-display: swap; }

View File

@ -116,6 +116,46 @@ app.get('/stream/:n/variant/:filename', (req, res) => {
res.redirect(302, url);
});
// Now-Playing proxy: reads master.biba.live AirPlay monitor, maps Folx TV current item.
// song -> title (artist - title)
// spot+GENIUS-> "GENIUS"
// spot -> "Werbung"
// jingle/animation -> skipped (label null; client keeps last song)
const NOWPLAYING_URL = process.env.NOWPLAYING_URL || 'https://master.biba.live/api/live';
const NOWPLAYING_STATION = process.env.NOWPLAYING_STATION || 'folx_tv';
function mapNowPlaying(np) {
if (!np) return { kind: 'none', label: null };
const type = (np.play_type || '').toLowerCase();
const camp = (np.campaign || '').toUpperCase();
if (type === 'song') {
const artist = (np.artist || '').trim();
const title = (np.display_title || np.title || '').trim();
const label = artist ? `${artist}${title}` : title;
return { kind: 'song', label, artist, title, started_at: np.started_at };
}
if (type === 'spot') {
if (camp === 'GENIUS') return { kind: 'genius', label: 'GENIUS', started_at: np.started_at };
return { kind: 'werbung', label: 'Werbung', started_at: np.started_at };
}
// jingle, animation (PASICA), contribution -> skip
return { kind: 'skip', label: null };
}
app.get('/api/now-playing', async (_req, res) => {
try {
const r = await fetch(NOWPLAYING_URL, { headers: { 'User-Agent': 'folx-live/1.0' } });
if (!r.ok) return res.status(502).json({ ok: false, error: `origin ${r.status}` });
const arr = await r.json();
const row = Array.isArray(arr) ? arr.find(x => x.station_slug === NOWPLAYING_STATION) : null;
const mapped = mapNowPlaying(row && row.now_playing);
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
res.json({ ok: true, station: NOWPLAYING_STATION, ...mapped });
} catch (err) {
res.status(502).json({ ok: false, error: err.message });
}
});
app.get('/api/health', (_req, res) => {
res.json({
ok: true,

View File

@ -1,793 +1,186 @@
<!DOCTYPE html>
<html lang="de" class="dark">
<html lang="de">
<head>
<!-- Google AdSense -->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4465464714854276" crossorigin="anonymous"></script>
<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" />
<title>FOLX Music Television — Live | Volksmusik &amp; Schlager</title>
<meta name="description" content="FOLX TV Live Stream. Oberkrainer, Bayrische und Zillertaler Musik rund um die Uhr. Volksmusik &amp; Schlager Fernsehsender Nr. 1 in DE, AT und CH." />
<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:description" content="24/7 Volksmusik &amp; Schlager Live-Stream." />
<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/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.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" />
<link rel="stylesheet" href="/ds/fonts/fonts.css" />
<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 controls + GO LIVE pill */
.player-overlay {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 3;
background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0) 18%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.55) 100%);
opacity: 0;
transition: opacity 0.25s;
}
.player-frame:hover .player-overlay,
.player-frame:focus-within .player-overlay,
.player-frame.touched .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);
}
.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;
}
:root { --akzent: #DC1D4D; --font-display: 'Geograph', system-ui, sans-serif; --font-mono: 'JetBrains Mono', ui-monospace, monospace; }
* { box-sizing: border-box; }
html, body { margin: 0; background: #000; }
body { color: #fff; font-family: var(--font-display); }
a { color: var(--akzent); text-decoration: none; }
a:hover { color: #FF4A73; }
@keyframes fx-pulse { 50% { opacity: .3; } }
@keyframes fx-vu { from { transform: scaleY(.15); } to { transform: scaleY(1); } }
input[type="range"].fxvol { accent-color: var(--akzent); }
::selection { background: var(--akzent); color: #fff; }
.wrap { min-height: 100vh; background: #000; display: flex; flex-direction: column; }
header.top { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 14px 24px; border-bottom: 1px solid rgba(255,255,255,0.14); }
header.top .logo { height: 64px; width: auto; display: block; margin: -14px 0; }
.badge-live { display: inline-flex; align-items: center; gap: 8px; background: var(--akzent); color: #fff; padding: 8px 14px; font-size: 11px; font-weight: 700; letter-spacing: 0.22em; text-transform: uppercase; }
.badge-live .d { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: fx-pulse 1s infinite; }
.uhr { font-family: var(--font-mono); font-size: 13px; color: #B8B8B8; letter-spacing: 0.06em; }
main.stage-wrap { width: min(1240px, 100%); margin: 0 auto; padding: clamp(16px,3vw,40px) clamp(12px,2.5vw,24px) 0; flex: 1; width:100%; }
.player-frame { position: relative; aspect-ratio: 16/9; overflow: hidden; isolation: isolate; background: #0A0A0A; }
.player-frame video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; background:#000; }
.stage-poster { position: absolute; inset: 0; width:100%; height:100%; object-fit: cover; z-index: 0; }
.scrim { position: absolute; inset: 0; z-index: 1; background: rgba(10,10,10,0.28); pointer-events:none; }
.bug-live { position: absolute; top: 18px; left: 18px; z-index: 10; display: inline-flex; align-items: center; gap: 8px; background: rgba(10,10,10,0.78); color: #fff; padding: 7px 12px; font-size: 10px; font-weight: 700; letter-spacing: 0.24em; text-transform: uppercase; }
.bug-live .d { width: 7px; height: 7px; border-radius: 50%; background: var(--akzent); animation: fx-pulse 1s infinite; }
.bug-right { position: absolute; top: 18px; right: 18px; z-index: 10; background: rgba(10,10,10,0.78); color: #D0D0D0; padding: 7px 12px; font-size: 10px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; }
.msg { position:absolute; inset:0; z-index:20; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; background:rgba(10,10,10,0.55); color:#fff; font-size:13px; letter-spacing:0.1em; text-transform:uppercase; }
.msg.hidden { display:none; }
.spinner { width:38px; height:38px; border:3px solid rgba(255,255,255,0.25); border-top-color:var(--akzent); border-radius:50%; animation: spin 0.9s 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; }
}
.bauchbinde { position: absolute; left: 18px; right: 18px; bottom: 18px; z-index: 10; display: flex; align-items: stretch; }
.bauchbinde.hidden { display:none; }
.bb-tag { background: var(--akzent); color: #fff; padding: 12px 18px; font-weight: 800; font-size: clamp(13px,1.6vw,17px); letter-spacing: 0.04em; text-transform: uppercase; display: flex; align-items: center; white-space: nowrap; }
.bb-body { flex: 1; min-width: 0; background: rgba(10,10,10,0.78); padding: 10px 16px 9px; }
.bb-title { font-weight: 700; font-size: clamp(15px,2.4vw,24px); line-height: 1.05; text-transform: uppercase; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bb-sub { margin-top: 5px; font-size: 10px; font-weight: 500; letter-spacing: 0.22em; text-transform: uppercase; color: #D0D0D0; }
.ctrl { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; background: #0A0A0A; border: 1px solid rgba(255,255,255,0.14); border-top: 0; padding: 12px 16px; }
.vu { display: flex; gap: 3px; align-items: flex-end; height: 30px; flex: none; }
.vu span { width: 3px; height: 100%; background: var(--akzent); transform-origin: bottom; animation: fx-vu 0.7s infinite ease-in-out alternate; }
.now { flex: 1; min-width: 180px; }
.now .lbl { font-size: 10px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: #6B6B6B; }
.now .t { font-weight: 700; font-size: 15px; text-transform: uppercase; letter-spacing: -0.005em; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ic { width: 40px; height: 40px; border: 0; background: transparent; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.ic:hover { color: #B8B8B8; }
.quality { font-size: 10px; font-weight: 700; letter-spacing: 0.18em; color: #6B6B6B; border: 1px solid rgba(255,255,255,0.25); padding: 4px 8px; white-space: nowrap; }
footer.bot { margin-top: clamp(32px,6vw,64px); border-top: 1px solid rgba(255,255,255,0.14); padding: 18px 24px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
footer.bot span { font-size: 10px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: #6B6B6B; }
:fullscreen .player-frame, .player-frame:fullscreen { aspect-ratio: auto; width:100%; height:100%; }
</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 class="wrap">
<header class="top">
<img class="logo" src="/brand/folx-logo-neg.png" alt="FOLX Music Television" />
<div style="display:flex; align-items:center; gap:12px;">
<div class="badge-live"><span class="d"></span> Auf Sendung</div>
<div class="uhr" id="uhr"></div>
</div>
</header>
<main class="stage-wrap">
<div class="player-frame" id="playerFrame">
<video id="v" autoplay muted playsinline tabindex="-1"
disablepictureinpicture
<img class="stage-poster" id="poster" src="/assets/stage_folx_v2.png" alt="" />
<div class="scrim"></div>
<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 class="bug-live"><span class="d"></span> Live</div>
<div class="bug-right">folx.live</div>
<div class="msg" id="msg"><div class="spinner"></div><div>Stream wird geladen…</div></div>
<div class="bauchbinde hidden" id="bauchbinde">
<div class="bb-tag">Folx</div>
<div class="bb-body">
<div class="bb-title" id="bbTitle">Folx Music Television</div>
<div class="bb-sub" id="bbSub">Live · Folx Music Television</div>
</div>
</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 class="ctrl">
<div class="vu" id="vu"><span style="animation-delay:0ms"></span><span style="animation-delay:120ms"></span><span style="animation-delay:240ms"></span><span style="animation-delay:60ms"></span><span style="animation-delay:300ms"></span><span style="animation-delay:180ms"></span><span style="animation-delay:90ms"></span></div>
<div class="now">
<div class="lbl">Jetzt läuft</div>
<div class="t" id="nowTitle">Folx Music Television</div>
</div>
<div style="display:flex; align-items:center; gap:12px; flex:none;">
<button class="ic" id="btnAudio" title="Ton umschalten" aria-label="Ton umschalten">🔇</button>
<span class="quality">AUTO · HD</span>
<button class="ic" id="btnFs" title="Vollbild" aria-label="Vollbild">⛶</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/>
Sokolska ulica 46<br/>
2000 Maribor · Slowenien
</div>
</div>
<div>
<div class="foot-label">Kontakt</div>
<div class="foot-text">
BoldFrame Productions d.o.o.<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 class="bot">
<span>Folx — Music&nbsp;&nbsp;Television</span>
<span><a href="https://folx.live" style="color:#B8B8B8;">folx.live</a> · © 2026 Folx Network</span>
</footer>
</div>
<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');
let hls = null;
var HLS_URL = <%- JSON.stringify(hlsUrl) %>;
var video = document.getElementById('v');
var msg = document.getElementById('msg');
var poster = document.getElementById('poster');
var hls = null;
function hideMsg(){ msg.classList.add('hidden'); poster.style.display='none'; }
function showMsg(){ msg.classList.remove('hidden'); }
function showMsg(html) { msg.innerHTML = html; msg.classList.add('visible'); }
function hideMsg() { msg.classList.remove('visible'); }
function attach() {
function start(){
try { hls && hls.destroy(); } catch(e){}
hls = null;
// PRIORITY 1: hls.js (works in Chrome, Firefox, Edge — most browsers)
// We test isSupported() FIRST because some Chrome versions now report
// canPlayType('application/vnd.apple.mpegurl') === 'maybe' but native
// playback actually fails silently (black screen). hls.js works.
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,
// ABR — adaptive bitrate switching
startLevel: -1,
capLevelToPlayerSize: false,
abrEwmaDefaultEstimate: 3000000,
testBandwidth: true,
abrBandWidthFactor: 0.95,
abrBandWidthUpFactor: 0.7,
});
hls = new Hls({ liveSyncDurationCount: 3, maxBufferLength: 10, manifestLoadingMaxRetry: 6, levelLoadingMaxRetry: 6, fragLoadingMaxRetry: 6 });
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);
hls.on(Hls.Events.MANIFEST_PARSED, function(){ hideMsg(); video.play().catch(function(){ setTimeout(function(){ video.play().catch(function(){}); }, 200); }); });
hls.on(Hls.Events.ERROR, function(_e, data){
if (data && data.fatal) {
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) { showMsg(); try { hls.startLoad(); } catch(e){ setTimeout(start, 2000); } }
else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) { try { hls.recoverMediaError(); } catch(e){ setTimeout(start, 2000); } }
else { setTimeout(start, 2500); }
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// FALLBACK: iOS Safari native HLS (hls.js not supported on iOS)
video.src = HLS_URL;
video.play().catch(() => {});
video.addEventListener('playing', hideMsg, { once:true });
} else {
showMsg('<div>Stream wird in diesem Browser nicht unterstützt</div>');
video.play().catch(function(){});
}
}
start();
attach();
// Twitch-style: no pause; resume to live edge
video.addEventListener('pause', function(){ if (!document.hidden) { setTimeout(function(){ video.play().catch(function(){}); }, 60); } });
video.addEventListener('contextmenu', function(e){ e.preventDefault(); });
// Twitch-style: pause is disabled. If anything pauses the video, resume + seek to 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);
});
video.addEventListener('seeked', () => {
if (!hls) return;
if (hls.liveSyncPosition && video.currentTime < hls.liveSyncPosition - 10) {
video.currentTime = hls.liveSyncPosition;
}
});
video.addEventListener('contextmenu', (e) => e.preventDefault());
video.disablePictureInPicture = true;
video.setAttribute('disablePictureInPicture', 'true');
try { video.disableRemotePlayback = true; } catch (e) {}
// Uhr
function tickUhr(){ var d=new Date(); document.getElementById('uhr').textContent = d.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}) + ' Uhr'; }
tickUhr(); setInterval(tickUhr, 10000);
// Block keyboard shortcuts that pause
document.addEventListener('keydown', (e) => {
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();
if (video.paused) {
seekToLive();
video.play().catch(() => {});
}
}
});
// Audio toggle
var btnA = document.getElementById('btnAudio');
btnA.addEventListener('click', function(){ video.muted = !video.muted; if(!video.muted && video.volume===0) video.volume=0.8; btnA.textContent = video.muted ? '🔇' : '🔊'; });
// Fullscreen
document.getElementById('btnFs').addEventListener('click', function(){ var f=document.getElementById('playerFrame'); if(document.fullscreenElement){ document.exitFullscreen(); } else { (f.requestFullscreen||f.webkitRequestFullscreen).call(f); } });
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
seekToLive();
video.play().catch(() => {});
// Now-Playing poll
var bb = document.getElementById('bauchbinde'), bbT = document.getElementById('bbTitle'), bbS = document.getElementById('bbSub'), nowT = document.getElementById('nowTitle');
var lastSong = null;
function applyNP(np){
var label = null, sub = 'Live · Folx Music Television';
if (np.kind === 'song') { label = np.label; lastSong = np.label; sub = 'Musikvideo · Folx'; }
else if (np.kind === 'genius') { label = 'GENIUS'; sub = 'Teleshopping · Folx'; }
else if (np.kind === 'werbung') { label = 'Werbung'; sub = 'Werbung · Folx'; }
else { label = lastSong; sub = 'Live · Folx Music Television'; } // skip -> keep last song
if (label) { bbT.textContent = label; bbS.textContent = sub; nowT.textContent = label; bb.classList.remove('hidden'); }
else { bb.classList.add('hidden'); nowT.textContent = 'Folx Music Television'; }
}
});
// Audio toggle (custom button)
const btnAudio = document.getElementById('btnAudio');
const btnFs = document.getElementById('btnFs');
function updateAudio() {
btnAudio.textContent = video.muted ? '🔇' : '🔊';
btnAudio.title = video.muted ? 'Ton einschalten' : 'Stummschalten';
function pollNP(){
fetch('/api/now-playing', { cache:'no-store' }).then(function(r){ return r.json(); }).then(function(d){ if(d && d.ok) applyNP(d); }).catch(function(){});
}
updateAudio();
btnAudio.addEventListener('click', (e) => {
e.stopPropagation();
video.muted = !video.muted;
video.volume = 1.0;
updateAudio();
});
// Fullscreen — handle iOS Safari separately
function enterFullscreen() {
if (typeof video.webkitEnterFullscreen === 'function' && !document.fullscreenEnabled) {
try { video.webkitEnterFullscreen(); return; } catch (e) {}
}
if (video.requestFullscreen) { video.requestFullscreen().catch(() => {}); return; }
if (video.webkitRequestFullscreen) { video.webkitRequestFullscreen(); return; }
if (playerFrame.requestFullscreen) { playerFrame.requestFullscreen().catch(() => {}); return; }
if (playerFrame.webkitRequestFullscreen) { playerFrame.webkitRequestFullscreen(); return; }
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(); });
pollNP(); setInterval(pollNP, 8000);
})();
</script>
</body>