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); 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) => { app.get('/api/health', (_req, res) => {
res.json({ res.json({
ok: true, ok: true,

View File

@ -1,794 +1,187 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de" class="dark"> <html lang="de">
<head> <head>
<!-- Google AdSense -->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4465464714854276" crossorigin="anonymous"></script> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4465464714854276" crossorigin="anonymous"></script>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FOLX Music Television — Live | Volksmusik & Schlager</title> <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." />
<!-- 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" /> <meta name="robots" content="index, follow" />
<link rel="canonical" href="https://folx.live/" /> <link rel="canonical" href="https://folx.live/" />
<!-- Open Graph -->
<meta property="og:title" content="FOLX Music Television — Live" /> <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:type" content="video.other" />
<meta property="og:site_name" content="FOLX Music Television" />
<meta property="og:url" content="https://folx.live/" /> <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" 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/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" /> <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" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="stylesheet" href="/ds/fonts/fonts.css" />
<!-- 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> <style>
:root { :root { --akzent: #DC1D4D; --font-display: 'Geograph', system-ui, sans-serif; --font-mono: 'JetBrains Mono', ui-monospace, monospace; }
--folx-magenta: #dc1c4c; * { box-sizing: border-box; }
--folx-magenta-bright: #ff2e63; html, body { margin: 0; background: #000; }
--folx-magenta-deep: #9d0f32; body { color: #fff; font-family: var(--font-display); }
--bg-0: #000000; a { color: var(--akzent); text-decoration: none; }
--bg-1: #0a0a0a; a:hover { color: #FF4A73; }
--bg-2: #141414; @keyframes fx-pulse { 50% { opacity: .3; } }
--bg-3: #1e1e1e; @keyframes fx-vu { from { transform: scaleY(.15); } to { transform: scaleY(1); } }
--border: #2a2a2a; input[type="range"].fxvol { accent-color: var(--akzent); }
--text: #f5f5f5; ::selection { background: var(--akzent); color: #fff; }
--text-muted: #888888; .wrap { min-height: 100vh; background: #000; display: flex; flex-direction: column; }
--text-faint: #555555; 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; }
* { box-sizing: border-box; margin: 0; padding: 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; }
html, body { .badge-live .d { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: fx-pulse 1s infinite; }
background: var(--bg-0); .uhr { font-family: var(--font-mono); font-size: 13px; color: #B8B8B8; letter-spacing: 0.06em; }
color: var(--text); 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%; }
font-family: 'Archivo', -apple-system, BlinkMacSystemFont, sans-serif; .player-frame { position: relative; aspect-ratio: 16/9; overflow: hidden; isolation: isolate; background: #0A0A0A; }
font-weight: 500; .player-frame video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; background:#000; }
-webkit-font-smoothing: antialiased; .stage-poster { position: absolute; inset: 0; width:100%; height:100%; object-fit: cover; z-index: 0; }
line-height: 1.5; .scrim { position: absolute; inset: 0; z-index: 1; background: rgba(10,10,10,0.28); pointer-events:none; }
overflow-x: hidden; .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; }
/* ======================= NAVIGATION ======================= */ .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; }
.topnav { .msg.hidden { display:none; }
position: sticky; .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; }
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;
}
@keyframes spin { to { transform: rotate(360deg); } } @keyframes spin { to { transform: rotate(360deg); } }
.bauchbinde { position: absolute; left: 18px; right: 18px; bottom: 18px; z-index: 10; display: flex; align-items: stretch; }
/* ======================= SECTIONS ======================= */ .bauchbinde.hidden { display:none; }
section { .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; }
max-width: 1400px; .bb-body { flex: 1; min-width: 0; background: rgba(10,10,10,0.78); padding: 10px 16px 9px; }
margin: 0 auto; .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; }
padding: 48px 24px; .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; }
/* Feature row */ .vu span { width: 3px; height: 100%; background: var(--akzent); transform-origin: bottom; animation: fx-vu 0.7s infinite ease-in-out alternate; }
.features { .now { flex: 1; min-width: 180px; }
display: grid; .now .lbl { font-size: 10px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: #6B6B6B; }
grid-template-columns: repeat(4, 1fr); .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; }
gap: 2px; .ic { width: 40px; height: 40px; border: 0; background: transparent; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; }
background: var(--border); .ic:hover { color: #B8B8B8; }
border: 1px solid var(--border); .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; }
.feature { footer.bot span { font-size: 10px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: #6B6B6B; }
background: var(--bg-1); :fullscreen .player-frame, .player-frame:fullscreen { aspect-ratio: auto; width:100%; height:100%; }
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> </style>
</head> </head>
<body> <body>
<div class="wrap">
<nav class="topnav"> <header class="top">
<div class="topnav-inner"> <img class="logo" src="/brand/folx-logo-neg.png" alt="FOLX Music Television" />
<a href="/" class="brand"> <div style="display:flex; align-items:center; gap:12px;">
<img src="/brand/folx-logo-transparent.png?v=2" alt="FOLX Music Television" class="brand-logo" /> <div class="badge-live"><span class="d"></span> Auf Sendung</div>
</a> <div class="uhr" id="uhr"></div>
<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>
</header>
<main class="stage-wrap">
<div class="player-frame" id="playerFrame"> <div class="player-frame" id="playerFrame">
<video id="v" autoplay muted playsinline tabindex="-1" <img class="stage-poster" id="poster" src="/assets/stage_folx_v2.png" alt="" />
disablepictureinpicture <div class="scrim"></div>
<video id="v" autoplay muted playsinline tabindex="-1" disablepictureinpicture
controlsList="nodownload nofullscreen noremoteplayback noplaybackrate"></video> controlsList="nodownload nofullscreen noremoteplayback noplaybackrate"></video>
<div class="player-msg visible" id="msg"> <div class="bug-live"><span class="d"></span> Live</div>
<div class="spinner"></div> <div class="bug-right">folx.live</div>
<div>Stream wird geladen…</div>
</div>
<div class="player-overlay"> <div class="msg" id="msg"><div class="spinner"></div><div>Stream wird geladen…</div></div>
<div class="player-controls">
<button class="pctl" id="btnAudio" aria-label="Ton umschalten" title="Ton umschalten">🔇</button> <div class="bauchbinde hidden" id="bauchbinde">
<button class="pctl" id="btnFs" aria-label="Vollbild" title="Vollbild">⛶</button> <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>
</div> </div>
<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> </main>
<section> <footer class="bot">
<div class="features"> <span>Folx — Music&nbsp;&nbsp;Television</span>
<div class="feature"> <span><a href="https://folx.live" style="color:#B8B8B8;">folx.live</a> · © 2026 Folx Network</span>
<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> </footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script>
<script> <script>
(function () { (function(){
const HLS_URL = <%- JSON.stringify(hlsUrl) %>; var HLS_URL = <%- JSON.stringify(hlsUrl) %>;
const video = document.getElementById('v'); var video = document.getElementById('v');
const msg = document.getElementById('msg'); var msg = document.getElementById('msg');
const playerFrame = document.getElementById('playerFrame'); var poster = document.getElementById('poster');
let hls = null; 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 start(){
function hideMsg() { msg.classList.remove('visible'); } try { hls && hls.destroy(); } catch(e){}
hls = null;
function attach() { if (window.Hls && window.Hls.isSupported()) {
try { hls && hls.destroy(); } catch (e) {} hls = new Hls({ liveSyncDurationCount: 3, maxBufferLength: 10, manifestLoadingMaxRetry: 6, levelLoadingMaxRetry: 6, fragLoadingMaxRetry: 6 });
hls = null; hls.loadSource(HLS_URL);
hls.attachMedia(video);
// PRIORITY 1: hls.js (works in Chrome, Firefox, Edge — most browsers) hls.on(Hls.Events.MANIFEST_PARSED, function(){ hideMsg(); video.play().catch(function(){ setTimeout(function(){ video.play().catch(function(){}); }, 200); }); });
// We test isSupported() FIRST because some Chrome versions now report hls.on(Hls.Events.ERROR, function(_e, data){
// canPlayType('application/vnd.apple.mpegurl') === 'maybe' but native if (data && data.fatal) {
// playback actually fails silently (black screen). hls.js works. if (data.type === Hls.ErrorTypes.NETWORK_ERROR) { showMsg(); try { hls.startLoad(); } catch(e){ setTimeout(start, 2000); } }
if (window.Hls && window.Hls.isSupported()) { else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) { try { hls.recoverMediaError(); } catch(e){ setTimeout(start, 2000); } }
hls = new Hls({ else { setTimeout(start, 2500); }
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.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 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>');
}
}
attach();
// 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()); } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.disablePictureInPicture = true; video.src = HLS_URL;
video.setAttribute('disablePictureInPicture', 'true'); video.addEventListener('playing', hideMsg, { once:true });
try { video.disableRemotePlayback = true; } catch (e) {} video.play().catch(function(){});
}
}
start();
// Block keyboard shortcuts that pause // Twitch-style: no pause; resume to live edge
document.addEventListener('keydown', (e) => { video.addEventListener('pause', function(){ if (!document.hidden) { setTimeout(function(){ video.play().catch(function(){}); }, 60); } });
const tag = (e.target && e.target.tagName) || ''; video.addEventListener('contextmenu', function(e){ e.preventDefault(); });
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(() => {});
}
}
});
document.addEventListener('visibilitychange', () => { // Uhr
if (!document.hidden) { function tickUhr(){ var d=new Date(); document.getElementById('uhr').textContent = d.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}) + ' Uhr'; }
seekToLive(); tickUhr(); setInterval(tickUhr, 10000);
video.play().catch(() => {});
}
});
// Audio toggle (custom button) // Audio toggle
const btnAudio = document.getElementById('btnAudio'); var btnA = document.getElementById('btnAudio');
const btnFs = document.getElementById('btnFs'); btnA.addEventListener('click', function(){ video.muted = !video.muted; if(!video.muted && video.volume===0) video.volume=0.8; btnA.textContent = video.muted ? '🔇' : '🔊'; });
function updateAudio() { // Fullscreen
btnAudio.textContent = video.muted ? '🔇' : '🔊'; document.getElementById('btnFs').addEventListener('click', function(){ var f=document.getElementById('playerFrame'); if(document.fullscreenElement){ document.exitFullscreen(); } else { (f.requestFullscreen||f.webkitRequestFullscreen).call(f); } });
btnAudio.title = video.muted ? 'Ton einschalten' : 'Stummschalten';
}
updateAudio();
btnAudio.addEventListener('click', (e) => {
e.stopPropagation();
video.muted = !video.muted;
video.volume = 1.0;
updateAudio();
});
// Fullscreen — handle iOS Safari separately // Now-Playing poll
function enterFullscreen() { var bb = document.getElementById('bauchbinde'), bbT = document.getElementById('bbTitle'), bbS = document.getElementById('bbSub'), nowT = document.getElementById('nowTitle');
if (typeof video.webkitEnterFullscreen === 'function' && !document.fullscreenEnabled) { var lastSong = null;
try { video.webkitEnterFullscreen(); return; } catch (e) {} function applyNP(np){
} var label = null, sub = 'Live · Folx Music Television';
if (video.requestFullscreen) { video.requestFullscreen().catch(() => {}); return; } if (np.kind === 'song') { label = np.label; lastSong = np.label; sub = 'Musikvideo · Folx'; }
if (video.webkitRequestFullscreen) { video.webkitRequestFullscreen(); return; } else if (np.kind === 'genius') { label = 'GENIUS'; sub = 'Teleshopping · Folx'; }
if (playerFrame.requestFullscreen) { playerFrame.requestFullscreen().catch(() => {}); return; } else if (np.kind === 'werbung') { label = 'Werbung'; sub = 'Werbung · Folx'; }
if (playerFrame.webkitRequestFullscreen) { playerFrame.webkitRequestFullscreen(); return; } else { label = lastSong; sub = 'Live · Folx Music Television'; } // skip -> keep last song
if (typeof video.webkitEnterFullscreen === 'function') { if (label) { bbT.textContent = label; bbS.textContent = sub; nowT.textContent = label; bb.classList.remove('hidden'); }
try { video.webkitEnterFullscreen(); } catch (e) {} else { bb.classList.add('hidden'); nowT.textContent = 'Folx Music Television'; }
} }
} function pollNP(){
function exitFullscreen() { fetch('/api/now-playing', { cache:'no-store' }).then(function(r){ return r.json(); }).then(function(d){ if(d && d.ok) applyNP(d); }).catch(function(){});
if (document.exitFullscreen) { document.exitFullscreen().catch(() => {}); return; } }
if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); return; } pollNP(); setInterval(pollNP, 8000);
if (typeof video.webkitExitFullscreen === 'function') { try { video.webkitExitFullscreen(); } catch (e) {} } })();
} </script>
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> </body>
</html> </html>